This post is a short introduction to a topic described in the “State” lesson of the Jetpack Compose and internals course. Check the complete outline on the link.
In View world
Before Compose, most of us put our ViewModel / Presenter / Controller / whatever at the root of our hierarchy (Activity
, Fragment
), with the only intention to decouple UI logic from business logic. Then, we segregated our views as dummy / passive custom views that would only display some part of the UI state, and notify events back via callbacks, so the ViewModel
at the root level could handle them.
By avoiding to pass the ViewModel
down the tree, we made Views more reusable across the board, and the whole UI interactions / behaviors much more testable (decoupled from the Android SDK).
In Compose world
Compose is no different. The goal is to make our Composables always stateless, with a single exception: the one at the root level.
This creates the following differentiation:
Stateful 🤓
Creates and manages its own state.
Used when caller does not need to manage its state.
Less reusable.
More frequent at the root of the tree.
Stateless 🤷♂️
Hoists its state (callers pass the state to it).
More reusable.
Makes state shareable, interceptable, decoupled.
My advice is to avoid passing the ViewModel down the tree at all costs, so we don’t lose all the benefits from stateless Composables and also keep our ability to easily test them on isolation with the existing tooling.
If your screens are Fragments, you can put the ViewModel on them, and make the complete Composable trees stateless.