Consuming flows safely in Jetpack Compose
Collecting flows in a lifecycle-aware manner is the recommended way to collect flows on Android. If you’re building an Android app with Jetpack Compose, use the
collectAsStateWithLifecycle API to collect flows in a lifecycle-aware manner from your UI.
collectAsStateWithLifecycle allows your app to save app resources when not needed, such as when the app is in the background. Keeping resources alive unnecessarily can impact the user’s device health. Such resources may include firebase queries, location or network updates, and database connections.
Keep reading to know more about this API, why you should collect in a lifecycle-aware manner, and how it compares to the
collectAsStateWithLifecycle is a composable function that collects values from a flow and represents the latest value as Compose
State in a lifecycle-aware manner. Every time a new flow emission occurs, the value of this
State object updates. This causes a recomposition of every
State.value usage in the Composition.
Lifecycle.State.STARTED to start and stop collecting values from the flow. This occurs when the Lifecycle moves in and out of the target state. This lifecycle state is something you can configure in the
The following snippet demonstrates is how to use
collectAsStateWithLifecycle to collect a StateFlow’s
uiState field that a ViewModel in your composable function has exposed:
Every time the
uiState emits a new
AuthorRoute will be recomposed. For more usages of
collectAsStateWithLifecycle, check out the Now in Android app, and its migration PR.
To start using the
collectAsStateWithLifecycle API in your project, add the
androidx.lifecycle.lifecycle-runtime-compose artifact to your project.
Under the hood
collectAsStateWithLifecycle saves you from typing the boilerplate code shown below that also collects flows in a lifecycle-aware manner from a composable function:
Flow collection in your architecture
Types in your app architecture shouldn’t know about the implementation details of other types. The UI shouldn’t know how the ViewModel produces the UI state. If the UI is not visible on the screen, the flow collection should stop to free up app resources if appropriate.
The UI can help free up resources by collecting the UI state using
collectAsStateWithLifecycle. The ViewModel can do the same by producing the UI state in a collector-aware manner. If there are no collectors , such as when the UI isn’t visible on screen, stop the upstream flows coming from the data layer. You can do so using the
.stateIn(WhileSubscribed) flow API when producing the UI state. For more information about this, watch this part of the Kotlin flows in practice talk. For testing the ViewModel producing UI state in this way, check out the testing guide.
Consumers and producers of flows don’t need to know how each other is implemented. Figuring out implementation details in a big app with multiple environments, variants, libraries, and features can be very time consuming. And even worse, maintaining code that relies on implementation details is hard.
Keeping resources active in the background
Android apps can run on a myriad of Android devices. Unfortunately, not all devices and not all users have endless resources. Apps usually run in a constrained environment. When an Android app is running, there are important factors that impact the user experience and device system health:
- CPU usage: CPUs have the highest battery consumption of all device components. Battery life is a perennial user concern. If abused, users might uninstall your app.
- Data usage: Reducing network traffic in an app when not connected to Wi-Fi can help users save money.
- Memory usage: How an app uses memory can have a very large impact on the overall stability and performance of the device.
An Android developer that wants to respect the user, device system health, or build for billions should optimize these different factors depending on the market, devices, or countries they’re targeting. Keeping unneeded resources alive can have a negative impact depending on the type of device and the Android version the device is running. Using
collectAsStateWithLifecycle in the UI layer enables the rest of the hierarchy to free up resources.
Developers often ask: if
collectAsStateWithLifecycle is the safest way to collect flows from composable functions in Android, why do we need the
collectAsState API now? or why not add the lifecycle-aware functionality to
collectAsState instead of creating a new API?
The lifecycle of a composable function is agnostic to the platform Compose is running on. As documented on the Lifecycle of composables page, instances of composable functions enter the Composition, recompose 0 or more times, and leave the Composition.
collectAsState API follows the Composition’s lifecycle. It starts collecting the flow when the composable enters the Composition and stops collecting when it leaves the Composition.
collectAsState is the platform-agnostic API you can use to collect flows.
However, when using Compose in an Android app, the Android lifecycle also plays a critical role in how resources should be managed. Even if Compose halts recompositions while the Android app is in the background,
collectAsState keeps the collection active. This makes it impossible for the rest of the hierarchy to free up resources.
collectAsStateWithLifecycle have a purpose in Compose. The latter when developing Android apps, the former when developing for other platforms.
collectAsStateWithLifecycle is a no-brainer:
Collecting flows in a lifecycle-aware manner is the recommended way to collect flows on Android to enable other parts of your app to free up resources if needed.
If you’re building an Android app with Jetpack Compose, use the
collectAsStateWithLifecycle composable function to do this.