Using the event bus pattern in Android with Kotlin
Tags: mobile, android, kotlin, tristan
Published: 2023-08-25 19:03:45

### Table of contents
1. [Event bus patter](#event)
2. [SharedFlow](#shared)
3. [Extending the event bus pattern](#extend)


### The code
- [GitHub](https://github.com/thePlebDev/Clicker)

### Introduction <a name="intro"></a>
- Recently I have had to deal with some relatively complex state logic for a login system and this was how I dealt with it.


### Event bus pattern <a name="event"></a>
- So in nerd talk we can describe the Event bus pattern as [this](https://medium.com/elixirlabs/event-bus-implementation-s-d2854a9fafd5): `Event-driven architecture pattern is a distributed asynchronous architecture pattern to create highly scalable reactive applications. The pattern suits for every level application stack from small to complex ones. The main idea is delivering and processing events asynchronously.`

- Which basically means we can have an event producer object and event subscriber objects that wait and listen for the event producer to produce events. The subscribers can then consume and respond to those events. The classic mental model is down below:

![Event bus pattern](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1sg4begqwexwwiptrc9a.png)


### SharedFlow <a name="shared"></a>
- Now if you are unfamiliar with what a SharedFlow is, just know that it is a [hot flow](https://developer.android.com/kotlin/flow/stateflow-and-sharedflow). Which means, that when the terminal operator `collect{}` is called it will always be active. 
- However, we are going to use a specialized version of a SharedFlow called the [StateFlow](https://developer.android.com/kotlin/flow/stateflow-and-sharedflow#stateflow) for our event bus:

```kotlin
class DataStoreViewModel(): ViewModel() {
private val _oAuthUserToken:MutableStateFlow<String?> = MutableStateFlow(null)

init{
  viewModelScope.launch{
     _oAuthUserToken.collect{ token ->
        token?.let{oAuthToken ->
     // make request now that the oAuthToken is not null
    }
}
  }
}

fun setOAuthToken(token:String){
   _oAuthUserToken.tryEmit(token)

}
}

```
- In the code block above we are `registering a subscriber` when calling `_oAuthUserToken.collect{}`. Meaning that it will always be active as long as the scope it is calling is active. if `viewModelScope` is destroyed then so is the subscriber.


### Extending the event bus pattern <a name="extend"></a>
- Now there are going to be times when your authentication process has multiple steps and each step relies on the previous one. Also, at anytime, one of those steps can fail meaning that the steps that relied on that one also fail. So how do we model this problem? We extend the event bus pattern like so:

```kotlin
data class MainBusState(
    val oAuthToken:String? = null,
    val authUser:ValidatedUser? = null,
)

private val authenticatedUserFlow = combine(
        flow =MutableStateFlow<String?>(null),
        flow2 =MutableStateFlow<ValidatedUser?>(null)
    ){
        oAuthToken,validatedUser ->
        MainBusState(oAuthToken,validatedUser)

    }.stateIn(viewModelScope, SharingStarted.Lazily,
        MainBusState(oAuthToken = null, authUser = null)
    )
private val mutableAuthenticatedUserFlow = MutableStateFlow(authenticatedUserFlow.value)



private fun collectAuthenticatedUserFlow() =viewModelScope.launch {
        mutableAuthenticatedUserFlow.collect{mainState ->
            mainState.oAuthToken?.let{notNullToken ->
                validateOAuthToken(notNullToken)
            }
            mainState.authUser?.let {user ->
                _uiState.value = _uiState.value.copy(
                    clientId = user.clientId,
                    userId = user.userId
                )
            }
        }
    }

```

- The above code can be broken down into 4 steps:


      **1) model your main state**
      **2) combine your flows**
      **3) register the subscribers**
      **4) emit to the flow**

### 1) Model your main state
```kotlin
data class MainBusState(
    val oAuthToken:String? = null,
    val authUser:ValidatedUser? = null,
)

```
- each value in the MainBusState represents a step inside of the authentication process. With the code you can tell that my app needs an oAuth Authentication token and it needs to validate the user. The validated user step can not happen if there is no oAuth Authentication token


### 2) Combine your flows
```kotlin
private val authenticatedUserFlow = combine(
        flow =MutableStateFlow<String?>(null),
        flow2 =MutableStateFlow<ValidatedUser?>(null)
    ){
        oAuthToken,validatedUser ->
        MainBusState(oAuthToken,validatedUser)

    }.stateIn(viewModelScope, SharingStarted.Lazily,
        MainBusState(oAuthToken = null, authUser = null)
    )

```
- This is done so we can take multiple flows and combined them into one object. This makes it much easier and more readable, we simply have to subscribe to a single flow. The `stateIn()` is just us turning the cold flow produced by `combine()` into a hot flow

### 3) register the subscribers

```kotlin
private fun collectAuthenticatedUserFlow() =viewModelScope.launch {
        mutableAuthenticatedUserFlow.collect{mainState ->
            mainState.oAuthToken?.let{notNullToken ->
                validateOAuthToken(notNullToken)
            }
            mainState.authUser?.let {user ->
                _uiState.value = _uiState.value.copy(
                    clientId = user.clientId,
                    userId = user.userId
                }
            }
        }
    }

```
- When this function is run, it will last as long as the viewModelScope and it registers 2 subscribers. One subscriber is listening to the oAuthToken and the other to the authUser. Both of which have their respective functions to run when they are not null.

### 4) Emit to the flow
```kotlin
 mutableAuthenticatedUserFlow.tryEmit(
                    mutableAuthenticatedUserFlow.value.copy(
                        oAuthToken = newTokenStrin
                    )
                )

```
- Then to update the value to our mutableAuthenticatedUserFlow we simple call tryEmit() with the updated values of our MainBusState. The new emitted value will be given to our mutableAuthenticatedUserFlow and the values that are updated will given to their respective listeners.


### Conclusion
- Thank you for taking the time out of your day to read this blog post of mine. If you have any questions or concerns please comment below or reach out to me on [Twitter](https://twitter.com/AndroidTristan).