State Hoisting with Sateless Composables

Composables work based on state and events. Compose defines the State object as a value holder, and changes to the state value trigger a recomposition.

Model View Intent Architecture with Compose

In my previous post using JetPack Compose I explained how the Andorid modern UI toolkit for building modern native UIs simplifies and accelerates UI development on Android. I used Composables, which are functions that take data and emit UI elements. But in that example that has a list view and that opens a fragment with details of the item clicked, the data is static in the sense that is not being updated by any means. Once the user goes back, the list is being recreated and on clicking one item, the new fragment with the related data will be displayed. Nothing fancy except that it is being done internally different.

From the MVVM to the MVI

In the Model-View-ViewModel architecture, the view observes properties of ViewModel for changes, which processes user input based on business logic and modifies respective observable properites. However in the Model-View-Intent, the view exposes view events and observes the model for view-state changes. From an architecture point of view, this model is the one that makes the difference. Think about the MVVM: when the model (your dao) changes and these changes are transfered to the view via LiveData, your view may have parts of it updated and others not, there is not a uniq source of truth and this overlaping may cause issues.

Here is where the MVI patern solves the problem with the addition of a model layer. This model layer is observed by the view for changes. Intents (which are not the usual Android intents to start componenents of the SDK) represent an intention to perform an action which may come either from the user or from the the app itself. For every action, a View receives an Intent which is trigered either by user actions on the UI or from the DAO, both observed by the view for state changes.

Recomposition in @Compose

This is the process of calling a composable function when input changes. With composable functions having as parameters labmda functions, every time that the composalbe function parameters change, the composable is "recomposed". The point here is that only those parts of the UI that display changed data will be recomposed and all data changing will triger a recomposition of that part of the UI. Worth to highlight here is the use of lambda functions as parameters: if this is not clear, this means that one of the "variables" of your composable will be actually a "function" which will be called in response either to events or from changes in the model coming from the DAO (and thus MVI model layer changing data).

This project repository is available at

CountDown ViewModel

Holds the timer and references to the LiveData and the MutableLiveData accesed by the UI and by the fixedRateTimer. The functions handling the UI events are also in the ViewModel which in turn will update data being observed by the UI.


Creates a timer that executes the specified action periodically in a thread. Setting the mutable live data from a different thread requires to post the values to the main thread looper, which when executed will notify the observers.

CountDown screen

Here is where we create the UI layout with the "CountDown Settings" and the "CoundDown value" composables. We also observe here the state from the ViewModel of the the different values that are being displayed, and set them as parameters of the Composables in the same way that we provide the viewModel functions handling state changes as Composable parameters.

CountDown Settings

Setting the timer countdown time will be done by changing the values of the OutlineTextField composable (provided by Compose) which will in turn trigger an event for the values that are changed. This event will be handled in one particupar purpose function of the viewmodel and passed to the settingsTime composable as a lambda function.


The value will be displayed in a composable which parameters are the observed values from the model. In the diagram we can see that whereas the settingsTime recieves state and emits events, the countDown only recieves the state of the values that have changed and that are displayed in the view as a result of recomposing it after an observed value change.

Marc Farssac
Interim Android
Developer & Team Leader

Let's talk now !
+34 644 22 00 88

Get in touch

Marc Farssac

WeWork - Espai d'oficines i coworking
Carrer de Tànger 86
08018 Barcelona
Catalonia, Spain

E-Mail: This email address is being protected from spambots. You need JavaScript enabled to view it.
Mobile: +34 644 22 00 88
Landline: +34 93 460 86 39

© Marc Farssac. All rights reserved.
Legacy site
Find my apps on the Google play store