The New Era of Android: Kotlin and Compose
First Contact: Android Studio and Composables
Mastering State: The Heart of Compose
Moving Between Screens: Navigation and Architecture
Connecting to the World: Data and Networking
The Final Mile: Testing and Publishing
You build a counter. A button, a number on screen. You tap the button. Nothing changes. You tap again. Still nothing. The number sits there, frozen. This is not a logic bug. The logic is fine. The problem is where you stored the number. You used a plain local variable. Compose ran the function again, created a fresh variable, and your count reset to zero. Because composables can run multiple times and out of order, mutable state in plain local variables is invisible to the composition system. Compose cannot track it. Cannot react to it. The UI stays frozen. In Compose, all UI is driven by state. Composables describe how the UI should look for a given state, and they recompose automatically when that state changes. But that only works if the state is observable. Last time, you learned that Compose is declarative. You describe the UI, and the framework handles the rest. Now, that promise only holds if Compose knows when something changed. The primary tool for that is mutableStateOf. It creates a State object holding a single value. When that value changes, any composable reading it gets scheduled for recomposition. Automatic. No manual wiring. But mutableStateOf alone is not enough. Let's explore practical scenarios where state management is crucial, such as handling complex UI interactions and optimizing performance through efficient state scoping. Suppose you have a text field composable that stores the typed text internally. Nothing outside it can read or control that value. It becomes an island. The better pattern is state hoisting. You move the mutable state up to the caller. The caller passes the current value down as a parameter. The child calls a lambda, like onValueChange, to signal something happened. The parent decides what to do. State flows down. Events flow up. This is unidirectional data flow, and it is the architecture Compose is built around. The child composable becomes stateless, reusable, and trivially testable because its behavior is entirely determined by its inputs. That means no hidden internal surprises. For screen-level state, hoisting goes one level higher. The key idea is to store UI state in a ViewModel and expose it as a StateFlow or Flow. Inside your composable, you collect that flow using collectAsState or collectAsStateWithLifecycle. Both convert the flow into Compose State that triggers recomposition when new values arrive. The viewModel() API from androidx.lifecycle.compose handles ViewModel retrieval and ensures ViewModel-scoped state survives configuration changes. Long-running or async operations run safely off the main thread using viewModelScope and coroutines, while Compose observes updates on the main thread during recomposition. Compose's recomposition is granular. When state changes, Compose recomposes only the parts of the UI tree that read that state. Not the entire screen. That efficiency is real, Adrià, but it is fragile if you misuse state. Sharing mutable state across unrelated composables forces unnecessary recompositions. Over-hoisting, lifting all state to a single top-level container, can actually hurt performance. A small change can ripple through a large subtree. Keep state as close as possible to where it is used. Scope it narrowly. That is how you keep rendering fast. Remember this: the official Compose mental model rests on three pillars. State, recomposition, and side effects. Mastering state is foundational to everything else. The pattern is consistent. Use mutableStateOf wrapped in remember for local state. Use rememberSaveable when that state needs to survive rotation. Hoist state upward so composables stay stateless, reusable, and testable. For screen-level state, let a ViewModel own it and expose it as an observable stream. [short pause] The takeaway for you, Adrià, is this: in Compose, your composables do not manage state. They react to it. Get that distinction right, and building interactive, correct, maintainable apps becomes dramatically more straightforward.