跪拜 Guibai
← All articles
Android Studio · Android Jetpack · Kotlin

Ditching LiveData: StateFlow and SharedFlow as the Kotlin-first replacement, with a migration playbook

By Android_实战之道 ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

Google's own guidance and the widening Kotlin ecosystem are moving Android state management away from LiveData toward Flow-based tools. Teams on older LiveData stacks face a concrete, incremental migration path that removes single-observer event hacks and unlocks compositional stream operators.

Summary

LiveData's design from the Java era is showing its limits in Kotlin-first Android development. It lacks native coroutine integration, offers only a handful of operators (map, switchMap), forces main-thread callbacks, and its sticky behavior makes it a poor fit for one-time events. Flow, Kotlin's own reactive stream, provides hundreds of operators, arbitrary thread control, and explicit hot/cold flow semantics. StateFlow and SharedFlow are the practical replacements: StateFlow is a state holder with a mandatory initial value and automatic deduplication; SharedFlow makes sticky behavior configurable via the replay parameter, enabling multi-observer one-time events without SingleLiveEvent hacks.

Lifecycle safety is handled by repeatOnLifecycle, the official extension that starts and cancels collection as the Activity/Fragment enters and leaves STARTED. It replaces the leak-prone launchWhenStarted pattern, which suspends callbacks but leaves the flow running. The migration path is incremental: convert ViewModel fields first, switch UI collection from observe to lifecycleScope + repeatOnLifecycle, and use Flow.asLiveData()/liveData.asFlow() for mixed codebases. A complete refactor of a note-taking app demonstrates debounce, distinctUntilChanged, flatMapLatest, and stateIn with SharingStarted.WhileSubscribed.

Takeaways
— LiveData lacks native coroutine integration, has only map/switchMap operators, and forces callbacks to the main thread.
— Flow provides hundreds of standard operators covering debounce, distinct, flatMapLatest, combine, and more.
— Cold flows execute per collector; hot flows share a single data stream and exist independently of observers.
— StateFlow requires an initial value, is sticky, and automatically deduplicates values using equality.
— SharedFlow allows configurable stickiness via replay: replay=0 gives non-sticky one-time events with multiple observers.
— repeatOnLifecycle is the only officially recommended way to collect flows in UI; it cancels collection when the lifecycle leaves STARTED.
— launchWhenStarted only pauses callbacks while the flow keeps running, wasting resources and risking leaks.
— Migration can be incremental: replace ViewModel LiveData with StateFlow, convert UI observation to repeatOnLifecycle, and use Flow.asLiveData() to mix temporarily.
— Room DAOs and DataStore natively return Flow, so the entire data-to-UI chain can be Flow without conversion.
— For one-time events like toasts and navigation, MutableSharedFlow(replay = 0) is the standard replacement for SingleLiveEvent.
Conclusions

StateFlow's mandatory initial value pushes teams to model UI state with sealed classes, which is stricter than LiveData's nullable approach and often prevents null-state logic bugs.

The shift to Flow makes thread switching and stream composition explicit in the type system, so code becomes more self-documenting about where I/O happens.

SharingStarted.WhileSubscribed(5000) keeps upstream resources alive for five seconds after the last collector leaves, a practical balance between re-creation overhead and wasted work.

The migration strategy's pragmatism (mixing LiveData and Flow via conversion functions) acknowledges that replacing all LiveData in one sprint is unrealistic for mature apps.

Concepts & terms
Cold flow
A Flow that does not execute until a collector calls collect. Each collector runs the flow independently, so data is not shared across subscribers.
Hot flow
A Flow that runs regardless of collectors, emitting values continuously. Observers subscribe to the existing stream and share the same data. StateFlow and SharedFlow are hot flows.
StateFlow
A hot, observable state holder with a mandatory initial value. It is sticky (new subscribers immediately receive the latest value) and deduplicates equal values automatically. Used for UI state in ViewModels.
SharedFlow
A hot, configurable shared stream that supports multiple observers and customizable replay (cached event count). With replay=0 it becomes a non-sticky one-time event channel, replacing SingleLiveEvent.
repeatOnLifecycle
A lifecycle-ktx extension that launches a collection coroutine only while the Lifecycle is in a specified state (e.g., STARTED) and cancels it automatically when leaving that state, preventing leaks.
stateIn
A Flow operator that converts a cold flow into a StateFlow by sharing results among collectors. Its 'started' parameter (e.g., WhileSubscribed) controls when upstream collection begins and stops.
Source: juejin.cn ↗ Google Translate ↗ Backup ↗