How we manage UI states in Android

Our main Freeletics app like many other modern mobile applications often uses client-server communications. It means that the data should be loaded from the server before being shown to the user. Therefore, the most common pattern while building features is: 

We often face us building the required behavior from scratch. So we came over to the idea: states the Loading and the Error are common and could be extracted and reused. On the other hand, Content state is usually unique for every screen.

After a few brainstorming sessions, we came up with the conclusion to build StateLayout. It is a simple library to manage common UI states in Android codebase such as Loading - Content - Error. Let’s have a look at how it works.

As a first step, we need to add StateLayout to the layout.

<com.freeletics.statelayout.StateLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/state_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

StateLayout is a custom view where different states will be rendered.

As a next step, we need to define different states. For example, this is how we can declare Loading state.

val loadingState = ViewState.create(R.layout.view_state_loading)

Since Loading state is a simple one containing only progress bar, we don’t need to do anything because it’s enough to pass the layout. But Content state is usually more complex an can be created by extending ViewState.Inflatable class:

class BindableContent(
    val text: String
) : ViewState.Inflatable(R.layout.view_state_content) {
    override fun onBindView(view: View) {
        val textView = view.findViewById<TextView>(R.id.text)
        textView.text = text
    }
}

The abstract class ViewState.Inflatable can be used as a base class for any custom view that needs to be shown in StateLayout. onBindView() method can be overridden to manipulate with individual views in the layout. We could use this approach for building Loading or Error state as well, in case we would like to have a more dynamic UI.

After we have defined all the necessary states, we should be able to use them. Switching between the states can be done with the method showState()

val stateLayout = findViewById<StateLayout>(R.id.state_layout)
stateLayout.showState(loadingState)
...
stateLayout.showState(contentState)

Apart from that, it is possible to provide transition animation while switching between the states. Pass transition animation object as a second param to the showState() method.

val transition = TransitionInflater.from(context).inflateTransition(android.R.transition.slide_left)
transition.duration = 500
stateLayout.showState(contentState, transition)

This is it. We hope you like this library and enjoy using it. If you spot any problem or have any suggestion, feel free to submit an issue or open a pull request.