Navigating the Flow: LaunchWhenStarted vs. repeatOnLifecycle(STARTED) in Jetpack Compose
Collecting Flows in Jetpack Compose can be a powerful way to manage data and UI updates. But choosing the right lifecycle-aware operator can be tricky. Two popular options are launchWhenStarted
and repeatOnLifecycle(STARTED)
. While they seem similar, their behavior and use cases differ significantly.
The Scenario:
Imagine you're building a screen that displays the user's location. You use a Flow to emit location updates from a background service. To update the UI, you need a way to collect the Flow while respecting the activity's lifecycle.
Original Code:
@Composable
fun LocationScreen() {
val locationFlow = remember {
// Flow emitting location updates
}
val locationState by remember {
locationFlow.collectAsState(initial = Location(0.0, 0.0))
}
// ... UI elements using locationState
}
Problem:
This code will start collecting the location flow as soon as the Composable is composed. This might be inefficient and cause problems if the activity isn't in the STARTED
state (like when it's in the background or paused).
Let's break down the key differences:
1. launchWhenStarted: Start collecting on Activity's Started State
- Behavior:
launchWhenStarted
starts collecting the Flow as soon as the Activity enters theSTARTED
state. This means that even if the Activity is in theRESUMED
state, the collection will be paused when the Activity transitions to thePAUSED
state. - Use Case: Ideal for flows that need to be active as long as the user is interacting with the activity (e.g., fetching data for a screen, updating UI elements).
2. repeatOnLifecycle(STARTED): Collect repeatedly when Activity is Started
- Behavior:
repeatOnLifecycle(STARTED)
launches a new collection every time the Activity enters theSTARTED
state. This means it will collect the Flow again when the Activity resumes from thePAUSED
state. - Use Case: Perfect for flows that need to be collected continuously as long as the Activity is in the
STARTED
state, regardless of the current state. This is useful for continuous updates, such as location updates, real-time data streams, and UI events.
Code Examples:
// Using launchWhenStarted
@Composable
fun LocationScreen() {
val locationFlow = remember {
// Flow emitting location updates
}
val locationState by remember {
locationFlow.collectAsState(initial = Location(0.0, 0.0))
}
LaunchedEffect(key1 = Unit) {
locationFlow.launchWhenStarted {
// ... Collect location updates
}
}
// ... UI elements using locationState
}
// Using repeatOnLifecycle(STARTED)
@Composable
fun LocationScreen() {
val locationFlow = remember {
// Flow emitting location updates
}
val locationState by remember {
locationFlow.collectAsState(initial = Location(0.0, 0.0))
}
LaunchedEffect(key1 = Unit) {
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
// ... Collect location updates
}
}
}
// ... UI elements using locationState
}
Key Takeaways:
launchWhenStarted
starts collecting once the Activity entersSTARTED
and pauses when it transitions toPAUSED
.repeatOnLifecycle(STARTED)
collects the Flow again every time the Activity transitions back toSTARTED
, even fromPAUSED
.
In Conclusion:
The choice between launchWhenStarted
and repeatOnLifecycle(STARTED)
depends on your specific needs. launchWhenStarted
is suitable for one-time collections or flows that need to be active only in STARTED
. repeatOnLifecycle(STARTED)
is perfect for continuous collections that require updates in STARTED
regardless of the previous state. Understanding these differences will help you create more efficient and lifecycle-aware Jetpack Compose applications.