Flow or LiveData?
Tuesday, November 2, 2021When Kotlin Flow arrived and the Android team started recommending it over LiveData, I was confused. LiveData works fine. Why change?
After using both together for a while the difference is clearer.
LiveData
LiveData is simple. It is observable, lifecycle-aware, and Android-specific.
val userName: LiveData<String> = MutableLiveData()
The Activity observes it and automatically stops receiving updates when paused or destroyed. No unsubscribe needed. This was revolutionary in 2017.
The problem is LiveData is tied to Android. You can not use it in a pure Kotlin module with no Android dependency. It also has no operators. You can not map, filter, or combine LiveData streams without Transformations helpers that are awkward.
Flow
Flow is Kotlin-native, cold, and has full operator support:
val temperatures: Flow<Int> = flow {
while (true) {
emit(sensor.read())
delay(1000)
}
}
temperatures
.filter { it > 30 }
.map { "Temperature: $it°C" }
.collect { message -> showAlert(message) }
Cold means nothing happens until someone collects. This is different from LiveData which is always active.
The lifecycle problem with Flow
Flow does not know about Android lifecycle. If you collect in an Activity naively:
lifecycleScope.launch {
viewModel.data.collect { updateUI(it) }
}
This collects even when the Activity is in background. For UI updates this is wasted work. For navigation side effects it can cause crashes.
The fix is repeatOnLifecycle:
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.data.collect { updateUI(it) }
}
}
Now collection only happens when the Activity is STARTED. This is the right way to collect Flow from UI but it is more boilerplate than LiveData.
StateFlow and SharedFlow
StateFlow is a Flow that always has a value and replays the last value to new collectors. It replaces MutableLiveData in ViewModels:
private val _uiState = MutableStateFlow(UiState.Loading)
val uiState: StateFlow<UiState> = _uiState.asStateFlow()
SharedFlow is for events that should not be replayed. Navigation events, snackbar messages, things that should happen once:
private val _events = MutableSharedFlow<UiEvent>()
val events: SharedFlow<UiEvent> = _events.asSharedFlow()
How I use them today
ViewModel exposes StateFlow and SharedFlow. The UI layer collects with repeatOnLifecycle. This is what the official documentation recommends now.
Repository and data layers use plain Flow because they have no lifecycle. They transform and combine streams with operators.
LiveData is mostly for legacy code. For new features I use Flow everywhere.
The migration is gradual. LiveData still works and there is no need to change everything at once. But new code should use Flow.