Unidirectional Data Flow for Android UIs
Tags: kotlin, android, mobile
Published: 2021-02-14 14:40:09

# Unidirectional Data Flow for Android UIs
![heading image](https://cdn-images-1.medium.com/max/1600/0*P6Z3qqMIauO2azTP)
>Photo by Марьян Блан | @marjanblan on Unsplash

## Where is the code?
[Here](https://github.com/j1c1m1b1/UDF)

## Inspiration
The ideas explored in this article are based on multiple sources that I recommend checking before diving any further:
* [Etienne Caron - Simple MVI Architecture for Android](https://www.youtube.com/watch?v=A2xyPZyoFUo)
* [Managing State with RxJava by Jake Wharton](https://www.youtube.com/watch?v=0IKHxjkgop4)
* [Android Unidirectional Flow with LiveData](https://hackernoon.com/android-unidirectional-flow-with-livedata-bf24119e747)
* [Modeling UI State on Android](https://lordraydenmk.github.io//2021/modelling-ui-state/)

## Introduction
One of the main tasks of the [HSE](https://www.hse.de/) Android app team is to rebuild the company’s [Android app](https://play.google.com/store/apps/details?id=de.hse24): taking old code as a reference and replace it with new code — a recurring Android development nightmare. Reading the old code made us ask the some questions I have asked myself before:
* How can different possible reactions to user inputs be written in a comprehensible manner, in order to make the code readable, robust and scalable?
* How can we avoid having multiple flags in our Activities and Fragments that represent how the UI should look like at a given point in time?

These questions are intended to be solved by the following snippets of code, heavily inspired by the sources listed at the top of the article.

---

There are multiple ways to achieve the proper display of information to the users and react to the input, the Android framework has evolved at an exponential rate in all directions, an example of this is are the Lifecycle library and specifically `ViewModel`s and `LiveData`.

`ViewModel`s are often used as direct intermediaries between user events over UI widgets and data sources, because of their tight connection to the lifecycle of their host `Fragment`s and `Activities`.

Although there is a huge advantage of having `ViewModel`s that receive user inputs and expose new data from these interactions, the generated code is often messy and difficult to translate into the clear differentiated states that the UI has to represent. An over-generalized user interaction in an Android app often looks something like this:

* The user navigates to a `Fragment` or `Activity`
* Data starts to load from some data source
* An indication of progress is shown to the user
* Data is loaded
* The UI is rendered
* The user interacts with a widget in the view component
* The input of the interaction (explicit or implicit) is sent to the data source to be operated on and obtain new information
* An indication of progress is shown to the user…

So on and so forth. It is easy to extract a common pattern from this, in fact it is so common that that's exactly the reason LCE models exist.

## Components
We use 3 basic elements that can model the unidirectional cycle of user interactions, data obtention and information rendering.
* **State**: Represents a single state of the UI. It can be short or long lived.
* **Action**: Implements the method that takes: the input from an event, the contents of the previous state, and combines them through some business logic into a different state.
* **UI Event**: Represents an input given implicitly or explicitly from the user.

Please note the order of these components. Normally the implementation tends to make the most sense when it is developed in the way these elements are listed.

Also of importance is the `suspend` modifier of the `perform` function, denoting the asynchronous execution of a transaction.

```kotlin
interface UiEvent<A : Action<S>, S : State> {
    suspend fun toAction(): Flow<A>
}

interface Action<S : State> {
    suspend fun perform(getPreviousState: () -> S): Flow<S>
}

interface State {
    interface Loading : State
    interface Complete<T : Any?> : State {
        val result: T
    }

    interface Error : State {
        val throwable: Throwable?
    }
}
```

### The event processor/state publisher
In this case we are using a [`Channel`](https://kotlinlang.org/docs/reference/coroutines/channels.html) that sends UI Events, then is consumed as a [`Flow`](https://kotlinlang.org/docs/reference/coroutines/flow.html) and finally can be transformed: first into an Action `Flow` and then to a State `Flow` by performing each emitted action. This resulting `Flow` then can be collected to modify the UI according to the new resulting state.

The engine of the state machine is composed by an extension function on the `Channel` field of UI Events. The implementation can be summarized as follows:

* The Events `Channel` is consumed as a `Flow`
* Transform the Events `Flow` into an Actions `Flow` by calling `toAction()` on each emitted Event
* Transform the Actions `Flow` into a States `Flow` by calling `perform()` on each emitted action
* Collect the obtained states by passing each one in the `collectionHandler` lambda parameter

There are some `Flow` transformation operations to ensure the correct sequential and distinct emission of the resulting states to be collected in the collector lambda parameter.

```kotlin
interface UiEventsProcessor<E : UiEvent<A, S>, A : Action<S>, S : State> {
    
    val getPreviousState: () -> S
    val events: Channel<E>

    suspend fun Channel<E>.consumeAsStatesFlow(collectHandler: (state: S) -> (Unit)) {
        this.consumeAsFlow()
            .toAction()
            .toState()
            .distinctUntilChanged()
            .collect { collectHandler(it) }
    }

    suspend fun dispatchEvent(event: E) {
        events.send(event)
    }

    private fun Flow<E>.toAction(): Flow<A> = this.flatMapMerge { event ->
        event.toAction()
    }

    private fun Flow<A>.toState(): Flow<S> = this.flatMapMerge { action ->
        action.perform(getPreviousState)
    }
}
```

The previous state needs to be provided as this state machine doesn’t keep tabs on the previous states and delegates this functionality to its implementation.

Because the state machine is an interface, it doesn’t make sense for the `Channel` to be initialized since it has to be overridden by the implementation.

The choice of using an interface was made because this implementation is not limited to Android applications, and in the example project from where this code is extracted, the interface is declared in a Kotlin only module.

---

The next steps describe the implementation of this state machine and its components in an Android module. In the example project the presentation module that hosts the state machine interface and the base and concrete components is included into an Android app module as a dependency.

### The Abstract ViewModel
The logical place for the abstract parts of the state machine to be implemented is a `ViewModel`, because we have multiple suspending functions being called and the `ViewModel` provides the `viewModelScope` where it is safe to execute this suspending methods, since it is tied directly to the `ViewModel` lifecycle.

However, if we are to re-use the `ViewModel` implementation we might as well create an abstract `ViewModel` and handle the specifics of each case scenario in children `ViewModel`s. Here you can see how this abstract `ViewModel` is declared:

```kotlin
abstract class UniDirectionalFlowViewModel<E : UiEvent<A, S>, A : Action<S>, S : State> :
    ViewModel(), UiEventsProcessor<E, A, S> {

    protected abstract val initialState: S

    private val stateLiveData: MutableLiveData<S> = initializeStateLiveData()

    final override val getPreviousState: () -> S
        get() = { stateLiveData.requireValue() }

    final override val events: Channel<E> = Channel()

    init {
        startEventsProcessing()
    }

    private fun startEventsProcessing() {
        viewModelScope.launch {
            events.consumeAsStatesFlow {
                stateLiveData.value = it
            }
        }
    }

    protected fun E.dispatch() {
        viewModelScope.launch { dispatchEvent(this@dispatch) }
    }

    private fun initializeStateLiveData(): MutableLiveData<S> =
        MutableLiveData(initialState)

    private fun MutableLiveData<S>.requireValue() = requireNotNull(value) { "State is null!" }

    fun observe(owner: LifecycleOwner, observingBlock: (S?) -> Unit): Observer<S> =
        Observer<S>(observingBlock).also { stateLiveData.observe(owner, it) }
}
```

Live Data is being used here to host the states that are received after dispatching the events. Making the new states be available in the `observingBlock` of the `observe` function declared at the bottom of the class.

It is mandatory to provide an initial state for the state machine to work, since it is necessary for actions to perform. This was chosen over having a nullable value for the `perform()` method in the `Action` interface, because of the cyclic nature of the paradigm to be applied here: from a state some events can be triggered, this events converted into states by transforming actions, taking the optional output of the previous state. A `null` state in this cycle just doesn't make sense.

---

## Implementations
All the elements necessary for the state machine to function are in place, now we just have to add the specific implementations.

### Implemented Components
We are making use of `sealed class`es in order to be able to represent the different states, actions and events of our simple example project. Since sealed classes support nesting we can be as specific as we want.

### States

```kotlin
sealed class MainState : State {
    object Initial: MainState()
    object Loading : MainState(), State.Loading
    data class Complete(override val result: String) : MainState(), State.Complete<String>
    data class Error(override val throwable: Throwable? = null) : MainState(), State.Error
}
```

### Events
They are simple containers of inputs from the UI.

```kotlin
sealed class MainEvent : UiEvent<MainAction, MainState> {
    data class SuccessRequest(private val message: String) : MainEvent() {
        override suspend fun toAction(): Flow<MainAction> =
            MainAction.SendSuccess(message = message).toFlow()
    }

    object ErrorRequest : MainEvent() {
        override suspend fun toAction(): Flow<MainAction> = MainAction.SendError.toFlow()
    }
}
```

### Actions
As you can see there is 1 to 1 mapping from event to action, which makes sense since we want a user event to trigger one transformation and get one event containing all the newUI information to be displayed.

Actions tend to be bigger since they are links to the execution of the business logic, taking the previous and the inputs from the events as parameters for the transactions under the hood. This won't be a problem anymore when Kotlin 1.5 arrives, though since sealed classes are going to be allowed to be implemented in different files than their declaration.

Here we are emulating the business logic of the app by calling a random delay on both the `SendSuccess` and `SendError` actions.

```kotlin
sealed class MainAction : Action<MainState> {
    protected val delayMillis: Long
        get() = loadingSecondsRange.random().let { TimeUnit.SECONDS.toMillis(it) }

    data class SendSuccess(private val message: String) : MainAction(), SuccessOperationSimulator {

        override suspend fun perform(getPreviousState: () -> MainState): Flow<MainState> = flow {
            emit(MainState.Loading)
            val newMessage = getPreviousState().getNewMessage()
            emit(MainState.Complete(result = newMessage))
        }

        /**
         * Simulates a time consuming asynchronous successful operation
         */
        private suspend fun MainState.getNewMessage(): String {
            delay(delayMillis)
            return this.let { it as? MainState.Complete }?.result.appendMessageAbove(message)
        }
    }

    object SendError : MainAction(), ErroneousOperationSimulator {
        override suspend fun perform(getPreviousState: () -> MainState): Flow<MainState> = flow {
            emit(MainState.Loading)
            // Simulates a time consuming asynchronous unsuccessful operation
            delay(delayMillis)
            val exception = getException()
            emit(MainState.Error(throwable = exception))
        }
    }

    private companion object {
        val loadingSecondsRange: LongRange = 1L..5L
    }
}
```
### The ViewModel
Now that our components are done, the `ViewModel` can be implemented allowing us to declare the initial state and dispatch both events.

```kotlin
class MainActivityViewModel : UniDirectionalFlowViewModel<MainEvent, MainAction, MainState>() {

    override val initialState: MainState
        get() = MainState.Initial

    fun sendError() {
        MainEvent.ErrorRequest.dispatch()
    }

    fun sendSuccess(message: String) {
        MainEvent.SuccessRequest(message = message).dispatch()
    }
}
```

### MainActivity

```kotlin
class MainActivity : BaseActivity<ActivityMainBinding>() {

    private val viewModel: MainActivityViewModel by viewModels()

    override fun inflateBinding(): ActivityMainBinding = ActivityMainBinding.inflate(layoutInflater)

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        viewModel.observe(this) { it?.render() }
        binding.errorButton.setOnClickListener { viewModel.sendError() }
        binding.eventsButton.setOnClickListener {
            viewModel.sendSuccess(
                Random.nextLong().toString()
            )
        }
    }

    private fun MainState.render() {
        when (this) {
            is MainState.Loading -> renderLoading()
            is MainState.Error -> render()
            is MainState.Complete -> render()
            else -> return
        }
    }

    private fun renderLoading() {
        binding.progressBar.isVisible = true
        switchButtonState(false)
    }

    private fun MainState.Error.render() {
        binding.progressBar.isGone = true
        Snackbar.make(
            binding.root,
            getMessage(),
            Snackbar.LENGTH_LONG
        ).onDismissed {
            switchButtonState(true)
        }.show()
    }

    private fun MainState.Error.getMessage(): String =
        this.throwable?.message ?: getString(R.string.unknown_error)

    private fun MainState.Complete.render() {
        showAlertDialog(result)
        binding.progressBar.isGone = true
        switchButtonState(true)
    }

    private fun switchButtonState(isEnabled: Boolean) {
        binding.apply {
            eventsButton.isEnabled = isEnabled
            errorButton.isEnabled = isEnabled
        }
    }
}
```

The activity code is now reduced to the initialization of the layout elements and the observing of the state change, that is hooked with a when statement.

In this way the logic of rendering the fragments and activities can be isolated to the current state that is being handled. No weird flags necessary.

## Testing
How can we ensure that the states are being correctly generated after each event? The process of testing can be described as follows:
* Dispatch an event
* Transform it into an action and perform it
* Assert the generated states

Here is the implementation:

```kotlin
internal inline fun <reified S : State, A : Action<S>, E : UiEvent<A, S>> E.dispatchAndAssertStateTransitions(
    initialState: S,
    vararg expectedStates: S
) {
    runBlocking {
        toAction()
            .flatMapConcat { it.perform { initialState } }
            .collectIndexed { index, actualState ->
                assertEquals(expectedStates.getOrNull(index), actualState)
            }
    }
}
```

The implementation of this function looks very familiar, this is because it is a minimized version of the `UiEventsProcessor`.

This function will validate that the expected states are exactly the same ones that are generated after calling the `perform()` function on the Action generated by the dispatched UI Event. The complexity of the tests that use this function lie in the definition of the initial and expected states.

Below are some examples of the over-simplistic approach of the implementation of the example project, but a variation of the method declared above is used in the tests for the production application.

```kotlin
    @Test
    fun whenSendSuccessIsSentWithInitialStateResultingStateIsSuccess() {
        val eventMessage = "Hello world"
        val expectedCompleteMessage =
            successOperationSimulator.getMessage(eventMessage)
        val expectedFirstState = MainState.Loading
        val expectedFinalState = MainState.Complete(result = expectedCompleteMessage)

        MainEvent.SuccessRequest(message = eventMessage).dispatchAndAssertStateTransitions(
            initialState = MainState.Initial, expectedFirstState, expectedFinalState
        )
    }

    @Test
    fun whenSendErrorIsSentWithInitialStateResultingStateIsSuccess() {
        val expectedThrowable = erroneousOperationSimulator.getException()
        val expectedFirstState = MainState.Loading
        val expectedFinalState = MainState.Error(throwable = expectedThrowable)

        MainEvent.ErrorRequest.dispatchAndAssertStateTransitions(
            initialState = MainState.Initial, expectedFirstState, expectedFinalState
        )
    }

    @Test
    fun whenSendSuccessIsSentAfterPreviousCompleteBothMessagesAreAppended() {
        val previousMessage = "Message: first message 😀"
        val eventMessage = "second Message 💪"
        val expectedCompleteMessage =
            successOperationSimulator.getMessage(
                previousMessage = previousMessage,
                newMessage = eventMessage
            )
        val expectedFirstState = MainState.Loading
        val expectedFinalState = MainState.Complete(result = expectedCompleteMessage)

        MainEvent.SuccessRequest(message = eventMessage).dispatchAndAssertStateTransitions(
            initialState = MainState.Complete(result = previousMessage), expectedFirstState, expectedFinalState
        )
    }
```

In this example the final and more complex expected states are generated by the `successOperationSimulator` and `erroneousOperationSimulator`, encapsulations of the simulated business logic the application uses. It is evident that in order to have proper tests these kinds of encapsulations should be provided to both the test and the real application modules, but this is another topic.

---

## Conclusion
There is no silver bullet for Android apps implementation, there are multiple valid approaches to the complexities of the parts that make an Android application, but having specific division of the states of the UI has worked very well in the reconstruction of HSE, and has allowed to provide control over a part of the Android implementation that has always posed a problem to multiple developers.

Thanks for reading and Kotlin your problems away!