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:
- show progress indicator while we perform the network request to fetch some data from the backend (
Loading
state); - after receiving the response from the server, we show the content to the users (
Content
state); - if an error happened (bad Internet connection, invalid data etc.), we need to show an appropriate message explaining to the users (
Error
state)
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.