Published in


Architecture Components: Easy Mapping of Actions and UI State

When building an app, most of the time what we’re doing is pretty much mapping direct/indirect actions to some UI state.

While using Architecture Components, achieving this is quite easy with the help of LiveData + Coroutines + ViewModels — but it does require a bit of code to set it up.

Reason being that in order to observe “state” of a LiveData, we have to write a wrapper around its value, as well as integrate actions around this state.

Let’s take an example, there’s a list-based UI where

  • data is loaded from an API
  • user can swipe-refresh and retry API call etc

Given these requirements, the actions would be:


Swipe Refresh


And based on these actions, UI state can be one of these at any given time:







The State Machine

If we were to map the states and actions mentioned above using a diagram — it’ll look something like:

Actions can either be implicit or explicit. The difference is that explicit actions (shown as blue arrows) are those actions that are triggered by the user and implicit aren’t.

Let’s code it out!


Starting with the State, let’s create a wrapper for state representation using sealed classes.


Similar to the states, we’ll create a wrapper for representing actions


We’ll need a custom LiveData that handles all actions and spits out appropriate state based on them (similar to what reducers do in Redux).

It should also do API call using Coroutines and propagate exceptions.


Using the new liveData block (that is actually a suspend block) and emit method — we can execute async code and emit values.

The switchMap block is also the new syntax for doing Transformations.switchMap() on a mutable LiveData.

Last part are the methods for dispatching actions.


As we’re dealing with Coroutines, we’ll specify the scope to be viewModelScope and use Dispatchers.IO as the coroutine context.

val users = ActionStateLiveData(viewModelScope.coroutineContext + Dispatchers.IO) {

viewModelScope → Bind the lifetime of our Coroutine to the lifetime of ViewModel

Dipatchers.IO → Run coroutine block asynchronously

UI (Fragment/Activity)

Once all done — UI is pretty straight forward, we initialize the viewModel, dispatch the initial load action and observe result.

val viewModel: ProfileViewModel by viewModels()// In onCreate
swipeRefreshLayout.setOnRefreshListener {
retryButton.setOnClickListener {
viewModel.users.state.observe(this) { state ->
when (state) {
Loading -> // show progress bar
Success -> // load up data
Failure -> // show error
Retrying -> // a different loader
SwipeRefreshing -> // show swipe refresh loader
SwipeRefreshingFailure -> // show error

That’s it for now — this was a basic example of how we can use LiveData + Coroutine + ViewModel to map actions & UI state. Things get a bit more tricky when dealing with pagination and unorthodox UI rules — I’ll try to cover those as well in the future.

Happy coding!




The (retired) Pub(lication) for Android & Tech, focused on Development

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Ahmed Rizwan

Ahmed Rizwan

Web | Android | Senior Software Engineer @QuixelTools

More from Medium

More on Hilt: Custom components & Custom scope

Decoding Jetpack Compose — LazyColumn, Navigation Architecture, Data Model, Grid, and TabBar View

Jetpack Compose: Custom Google Map Marker | Erselan Khan

Jetpack Compose: MVVM State management in a simple way