Complete guide to LiveData and Flow: Answering — Why, Where, When, and Which.
A crucial part of android development is how efficiently UI can be updated with any change in data at lower layers, decoupling all layers, avoiding callback hell(Reactive programming) so that layers can be tested independently(worth mentioning that domain and data layer should be independent of an android framework)
If you are a legacy developer (someone like me with 11 years old association wIth android ecosystem), would understand the pain of writing boilerplate code to handle data changes inlined with activity/fragment lifecycle. Back then, every android interview would have this million-dollar question “How would yOU handle AsyncTask updating data in between activity death and rebirth?”. When I started android development there were no classes/builders or libraries like ViewModel, LiveData/Flow, and JetPack. Activities or fragments were usually bloated with a lot of code lines: business logic, population data to UI, and whatnot; which makes the code practically impossible to unit test.
But now android development has notably evolved and I would say much easier if one understands the basics. There are always multiple ways to perform a given task within android and one can only decide the best alternative if the basic difference, pros, and cons of all options are mastered.
Having said this, let's try to address today what developers often ask (What to choose between LiveData and Flow).
What is LiveData?.
Observable data class holder: it can hold data and that data can be observed from android components.
LiveData is LifeCycle aware: Would it make any sense to publish data to UI when UI itself is not shown/visible to the user?. Exactly! you got it right and that's what lifecycle awareness is all about. Strangely, these components are lifecycle aware and we humans are still nowhere near to life awareness. (Ok let's keep this for another meditation-related blog :P ).
MVVM or MVVM clean; LiveData has its presence in all the latest architecture patterns being used in android development. LiveData published data from viewModel to UI while respecting the lifecycle of UI.
LiveData
is closely associated with the Android platform
Benefits ?.
All subscribers(new and old subscribers) would always get the latest data.
Always check if the subscriber is active before publishing data.
Survive activity recreation i.e subscribers would always get data on moving from inactive state to active state.
Few pitfalls and ways to eradicate them?
What if we need to consume data only once. Consider below example wherein we wish to move to another fragment when profileDetailsUpdateLiveData gets updated(on success of API response from profileUseCase.updateProfile() or repository.updateProfile() )
Fragment A:
As you rightly guessed, this code would launch fragmentB on the update of liveData.
Now, what would be the series of events that would take place when the user presses the back button at fragmentB ?.
onViewCreate() of Fragment A will be invoked again and as we know profileDetailsUpdateLiveData always holds the latest value. So in this case fragmentB would be launched again. Weird use case though :)
Boilerplate Solution :
To avoid this we can have some sort of boolean flag to ignore data from LiveData.
That’s a boilerplate and you would end up keeping multiple such flags for all liveData you are subscribing to.
The same is true when we just have to show snackbar or toast once.
Pro Solution :
Check this nice blog : Wrapper to LiveData : EventLiveData and SingleLiveData .
Or
You can create an extension like below
So now we know what is live data and worth talking about Flow also before we jump to conclusion on scenarios where we to use flow or LiveData, let’s dive a little on flow basics.
What is Flow?
Built on top of a coroutines, a flow emits multiple values sequentially.
Its a stream of data that can be computed sequentially.
Provides an intermediate operators to modify the stream without consuming values. ( So basically its an alternate to RxJava. Bye bye RxJava :) )
Flows by nature are not lifeCycle aware unlike LiveData. Which makes sense as its not a part of android component but a type from kotlin language. However, this can be resolved by responsibly collecting flow values within liceCycleScopes via coroutines and other ways (which we will discuss this later in this blog.)
Flow is declarative/cold : It can only be executed on collection. and there are hot flows as well (SharedFlow and StateFlow ). (cold : Stops emission when any collector is not active. Hot : it remains in memory as long as the flow is collected or as long as any other references to it exist from a garbage collection root.) We will discuss cold and hot stream in detail.
If you are new to Flows ,i would recommend that you master the below concepts:
Flow builders : flow and callbackFlow
Cold and hot flows: sharedIn , stateFlow and ShareFlow
Switch CoroutineContext : flowOn
Intermediate operators : and lot more
Till this point its clear that liveData is coupled with android framework and its best to use liveData in presentation layer as lower layer (domain , repository and data) should be platform independent, its worth using flow in lower layers. (Even flow can be used in presentation layer by making it lifecycle aware and some practical examples where flow suits best in Presentation layer too).
Presentation layer : Flow or LiveData ?.
Data should only be rendered/observed/subscribed only if UI is visible and this is exactly what LiveData offers.
Flow collection can also be made lifecycle aware using below :
1. repeatonLifeCycle(LifecycleState.STARTED) or flowWithLifeCycle(LifeCycleState.STARTED)
2. .asLiveData()
Similar to liveData, flow helps to write reactive code/modules for layers below presentation (domain /repository and data). if you are familiar with Rxjava (which has steep learning curve) , flows offers almost the same capabilities along with power of coroutines.
So everything that liveData can do can be performed by flow in addition to lot more capabilities being offered by flow.
Verdict : Both suits well for presentation layer and its purely a matter of choice.
Domain/Repository/Data layer : Flow or LiveData ?.
Before we conclude, lets quickly check how to create and collect flow :
Below are the option to create flow :
flowof()
asFlow()
flow{}
channelFlow{}
callbackFlow{}
MutableStateFlow and MutableSharedFlow
Lets discuss the builder functions i.e flow{} and channelFlow{}
Code block below is called producer and since flow is declarative, it will only emit once we start collecting. Its producing Int from 1 to 20 every 3 seconds.
val flowOfNumbers = flow {
repeat(20) {
delay(3000)
emit(it)
}
}
Collecting a flow
In viewModel :
viewModelScope.launch {
flowOfNumbers.collect {
//collect numbers here
}
}
here the steam of data from producer will be closed when ViewModel is cleared i.e. when viewModelScope is cancelled. And this is how cold flow behaves.
Now what if you for some reasons are not collecting in ViewModel but rather in fragment or any view ?.
In this case we would not want to collect the stream when UI is not visible i.e similar to what we get via livData.
For this, there are 2 alternatives :
#1. Converting flow to liveData
_flowNumberLiveData = repository.flowOfNumbers.asLiveData()
Now you can observe the same in view as you would normally observe liveData i.e
flowsNumnerLiveData.observe(viewLifecycleOwnner){ numbers -> })
Note : you have to include androidx lifecycle ktx library for above.
#2. Collecting flow in fragment/Activity.
Though UI should be as dumb as possible and all data should be driven from viewModel and below layer but at times we need to collect data directly in UI.
For instance, AppStateFlow that represents the current state of an app: is it fetching something from server or not(just an imaginary weird requirement :P )
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
appState.collect { appState ->
when(appState)
{
APPSTATE.LOADING ->
//green indicator somewhere in UI
APPSTATE.IDLE ->
//do not turn the indicator to green
}
}
}
}
OR
viewLifecycleOwner.lifecycleScope.launch {
appState.flowWithLifecycle(viewLifecycleOwner.lifecycle, Lifecycle.State.STARTED).collect { appState ->
when(appState)
{
APPSTATE.LOADING ->
//green indicator somewhere in UI
APPSTATE.IDLE ->
//do not turn the indicator to green
}
}
}
Check this nice blog to read more about repeatonLifeCycle and flowWithLifeCycle
So what’s your excuse now for not using flow knowing the fact that it has more capabilities than liveData. Is it because liveData suits best for UI layer ?? Which is actually true BUT what about lower layers ?.flow at lower layers (domain ,data) enables you to write code in reactive way.
Why should be bother about below layers being reactive ?
Reactive programming where data is pushed not pulled (getting data from database etc ) from layer. For instance, room has added support to liveData and now flow to support reactive programming wherein data is always pushed on every other change.
Reactive programming at below layer provides:
1. A flow a day keeps “callback hells” away.
2. Operators to transform data at below layers itself.
3. Cleaner and more readable code.
But why can’t we just use LiveData too in lower layers to facilitate reactive programming ?.
1. Lower layers should be platform independent (liveData is android thing after all) which makes testing a little complicated at below layers.
2.Flow has way more capabilities than liveData (lot more operators for instance).
3. Handles backPressure very well (slow collector and fast emitter. Do read about flowOn() and buffer()).
4. Flow enables stream of data.
5. Flow + coroutines a deadly combination == RxJava. Makes asynchronous coding way more easier.Verdict : flow is clearly a winner and should be considered for lower layers without any doubt to make you layers reactive.
Now that we are convinced to incorporate flow in lower layers, let quickly dive deep into more concepts from flow:
callback{}builder :
A flow builder that turn callback based code to flows.
Usually we have 2 types of callback from async functions :
- One-shot async call :
Lets say we are making a call to third party lib to provide us with top rated movies and this call is a async called by lib.
Its a one-shot, i.e lib would call onMoviesFetched() callback only once.
someMoviesLib?.addMoviewCallback { movies, _ ->
override fun onMoviesFetched(value: List<Movies>) {
//on shot movie lsit
}
override fun onApiError(cause: Throwable) {
}
}
For such scenarios we would use suspendCancellableCoroutine or suspendCoroutine builder
2. Streaming callback .
LocationCallback
is a perfect example here; wherein callback onLocationResult
would be called whenever there is a new location available. This is a stream of locations that means flow suits best here. As this too is async callback we can consider using callback{} builder
fun getLocationUpdateSteam() = callbackFlow<Location> {
// A new Flow is created. This code executes in a coroutine!
// 1. Create callback and add elements into the flow
val callback = object : LocationCallback() {
override fun onLocationResult(result: LocationResult?) {
result ?: return // Ignore null responses
for (location in result.locations) {
try {
offer(location) // Send location to the flow
} catch (t: Throwable) {
// Location couldn't be sent to the flow
}
}
}
}
// 2. Register the callback to get location updates by calling requestLocationUpdates
requestLocationUpdates(
createLocationRequest(),
callback,
Looper.getMainLooper()
).addOnFailureListener { e ->
close(e) // in case of error, close the Flow
}
// 3. awaitClose will be executed when the flow is either closed or cancelled and hence its make sense removeLocationUpdates(callback) here
awaitClose {
// Clean up code goes here
removeLocationUpdates(callback)
}
}
If you are still with me, we will explore few more concepts :
1. cold and hot flows.
2. Exploring SharedFlow and StateFlow.
3. And lastly, few use cases where to use flow.
Cold flows:
flows are by default cold (created by flow{..} ) builders.
Cold flow/stream : code block inside flow{..} is not active until any terminal operator call is made (for instance collect). Its not active before the collect and after ending the stream of data.
Can have only one subscriber. Any new subscriber would create a new execution of flow{..}.
Because flow {..} does nor perform anything , returns immediately(does not suspend) and starts only when instructed to do so (via terminal operator collect etc.), functions returning flow are without suspend keyword.
public fun <T> flow(@BuilderInference block: suspend FlowCollector<T>.() -> Unit): Flow<T> = SafeFlow(block)
Notice that block is suspendable that means we can use delay() inside block even though returning fun startFlow is non-suspend
fun startFlow() : Flow<Int> = flow{
repeat(12)
{
delay(1000)
emit(i)
}
}viewModelScope.launch {
startFlow().collect {
value -> println(value)
}
}
Flow is sequential and suspends the calling coroutine and thus it would take 12 seconds to print all number.
Comment below your answers “How many seconds it would take to print” in below case:
fun startFlow() : Flow<Int> = flow{
repeat(12)
{ emit(i)
}
}viewModelScope.launch {
startFlow().collect {
delay(1000)
value -> println(value)
}
}
Hot Steams:
Shared between multiple collectors; only one instance of flow runs for all multiple collectors. (for instance, a flow that provides a steam of location updates. It make sense to have 1 instance of flow so that all collectors gets the updated and most recent location.)
SharedFlow and StateFlow are HOT flows as they propagates items to multiple consumers. StateFlow is a special shareFlow that maintain the current state(by .value ).
Collecting from the hot flow doesn’t trigger any producer code (code block that updates the value of shared or stateFlow).
Analogy for hot stream : live video feed, all viewers will get to see exact same timeline of video being played.
Now, we know what exactly are hot streams/flow. Let’e see how to create them.
You can convert any cold flow (flow{..}) to hot flow by two intermediate operators.
stateIn
shareIn
flow {
repeat(100) {
delay(3000)
Log.d("Flow", "***************************** ")
Log.d("Flow", "emitting = $it ")
emit(it)
}
Log.d("Flow", "emission done ")
}.shareIn(scope, SharingStarted.Eagerly,replay =1)flow {
repeat(100) {
delay(3000)
Log.d("Flow", "***************************** ")
Log.d("Flow", "emitting = $it ")
emit(it)
}
Log.d("Flow", "emission done ")
}.stateIn(scope, SharingStarted.WhileSubscribed(), initialValue=1)
Lets look at respective functions and try to understand
fun <T> Flow<T>.stateIn(
scope: CoroutineScope,
started: SharingStarted,
initialValue: T
): StateFlow<T> (source)fun <T> Flow<T>.shareIn(
scope: CoroutineScope,
started: SharingStarted,
replay: Int = 0
): SharedFlow<T> (source)
from the docs :
Scope : the coroutine scope in which sharing is started. Where computation takes place.
Started : the strategy that controls when sharing is started and stopped.
SharingStarted.Eagerly : Sharing is started immediately and never stops.
SharingStarted.Lazily : Sharing is started when the first subscriber appears and never stops.
SharingStarted.WhileSubscribed() : starts emitting/sharing only when subscribers turns from
0
to1
, and stop sharing when the number of subscribers turns from1
to0. Making it exactly similar to LiveData?. Yes!, but with few tweaks which we already discussed before i.e use
repeatonLifeCycle(LifecycleState.STARTED) or flowWithLifeCycle(LifeCycleState.STARTED)
Quick Revision : when we collect flow in a coroutine launched with launchWhenStarted{}
, the coroutine get paused on onStop()
and resumed on onStart()
. However, it will still be subscribed to the flow.
As mentioned above, WhileSubscribed() stops sharing when the number of subscribers turns from 1
to 0
. In this case with launchWhenStarted{}
MutableSharedFlow<T>.subscriptionCount
will not change for paused coroutines and hence it hot flow will continue emission . To make proper use of SharingStarted.WhileSubscribed()
, we need to cancel the collecting coroutine onStop()
, and subscribe again on onStart()
. This is exacly what repeatonLifeCycle(LifecycleState.STARTED) does under the hood.
initialValue :This is applicable to stateFlow and it’s the initial value of the state flow.
replay : Applicable only to sharedFlow (though stateflow internally has set value of replay to 1). Number of values that newly subscribed subscriber should get.
SharedFlow and StateFlow : Which one to choose ?
I need to have latest value at any point of time using .value : StateFlow.
More than latest value using replay : SharedFlow.
Need repeated values : SharedFlow.
That’s it. As its an endless topics and there is definitely lot more to cover, i will try to continue this with part 2. But for now, have tried adding links to most valuable blogs for respective concepts. Please do check them as well .
If this added anything to your knowledge, don’t forget to clap 👏👏.