Simple app to demonstrate Android lifecycle-aware LiveData usages and behaviors - setValue(), postValue(), observe(), and observeAsState().
This is part of the asynchronous flow series:
- Part 1 - Exploring Android LiveData Usages and Behaviors
- Part 2 - Introduction to Kotlin Flows and Channels
- Part 3 - Exploring Different Ways to Collect Kotlin Flow
- Part 4 - Convert Flow to SharedFlow and StateFlow
- Part 5 - Flow, SharedFlow, StateFlow Class Diagram
Android LiveData
is observable and lifecycle-aware data.
Assuming the lifecycle owner is an activity, lifecycle-aware means it will only send updates to the UI when the activity is active. Activity is active means the UI is visible either in the background (started state) or in the foreground (resumed state).
LiveData
also automatically removes all Observer
objects when the activity is destroyed.
Basic LiveData Usages
Emit LiveData - setValue() / postValue()
First, you need to
Create MutableLiveData
class LiveDataViewModel: ViewModel() {
private val _liveData = MutableLiveData<Int>()
val liveData: LiveData<Int> = _liveData
/*...*/
}
To emit value in LiveData
, you can either use
MutableLiveData.setValue() / MutableLiveData.value
viewModelScope.launch {
repeat(10000) { value ->
delay(1000)
_liveData.value = value
}
}
or
MutableLiveData.postValue()
viewModelScope.launch(Dispatchers.IO) {
repeat(10000) { value ->
delay(1000)
_liveData.postValue(value)
}
}
- Coroutine is used to simulate the asynchronous flow because we don't want to block the UI / main thread.
-
setValue()
must be run on main thread,postValue()
can be on either main or background thread
Observe LiveData - observe() / observeAsState()
To observe the LiveData
, you can either manually observe usingLiveData.observe()
or LiveData.observeAsState()
API.
LiveData.observe()
- Create a
MutableState
data - Create an
Observer
object - Observe the
LiveData
(pass in theObserver
object) - Remove the
Observer
object fromLiveData
@Composable
fun LiveDataScreen() {
val viewModel: LiveDataViewModel = viewModel()
val lifecycleOwner = LocalLifecycleOwner.current
//(1) Create a MutableState data
val manualObserveLiveDataState:MutableState<Int?> =
remember { mutableStateOf(null) }
//(2) Create an observer object
val liveDataObserver = remember {
Observer<Int> { value ->
manualObserveLiveDataState.value = value
}
}
Column {
Button(onClick = {
//(3) Observe the LiveData
viewModel.liveData.observe(lifecycleOwner, liveDataObserver)
}) {
Text(text = "Manually Start Observe LiveData")
}
Button(onClick = {
// (4) Remove the observer from LiveData
viewModel.liveData.removeObserver(liveDataObserver)
}) {
Text(text = "Manually Remove Observer")
}
}
}
In step 2 above,
remember {}
is required for creating theObserver
object so we don't recreate theObserver
object every time during recomposition. I made this mistake. Thus, it causes the memory leak - observers growth.
remember {}
is like caching. If you don't know what it is, the article below gives you some examples.
LiveData.observeAsState()
LiveData.observeAsState()
returns MutableState<Int?>
object, so you don't need to explicitly create it.
@Composable
fun LiveDataScreen() {
val viewModel: LiveDataViewModel = viewModel()
// Create MutableState by observing the LiveData
val observeAsStateLiveData =
viewModel.liveData.observeAsState(initial = null)
}
Internally, it calls the DisposableEffect()
side-effect. The most important of this effect is the onDispose()
function which takes care of removing the Observer
object for you automatically when the effect leaves the composition.
@Composable
fun <R, T : R> LiveData<T>.observeAsState(initial: R): State<R> {
val lifecycleOwner = LocalLifecycleOwner.current
val state = remember { mutableStateOf(initial) }
DisposableEffect(this, lifecycleOwner) {
val observer = Observer<T> { state.value = it }
observe(lifecycleOwner, observer)
onDispose { removeObserver(observer) }
}
return state
}
Investigate setValue() vs postValue() Behaviors
To study the behavior setValue()
vs postValue()
, these are a few scenarios to try:
- Activity is created/stopped (not visible in background)
- Activity is started/paused (visible in background)
- Activity is resumed (visible in foreground)
- When UI is busy
- Run
postValue()
in main thread
Simulate UI is Busy
To simulate UI is busy, you can either emit the value fast (e.g. every 1 ms)
job = viewModelScope.launch {
repeat(10000) { value ->
delay(1)
_liveData.postValue = value
}
}
or simply call Thread.sleep()
- I prefer this method.
Button(onClick = {
Thread.sleep(3000)
}) {
Text(text = "Simulate Busy UI")
}
Simulate Activity is Paused (Visible in Background)
To simulate an activity that is paused/loses focus, you can start another transparent activity on top of your current app.
Add Logging in Observer Object
In order to prove the data is sent to the UI, you add the following logging to the Observer
object.
val liveDataObserver = remember {
Observer<Int> { value ->
Log.d(tag, "[ManualObserver]: Assigning $value to manualObserveLiveDataState.value")
manualObserveLiveDataState.value = value
}
}
Summary - setValue() vs postValue()
After performing various scenarios, these are the differences between setValue()
and postValue()
.
Scenarios | setValue() | postValue() |
---|---|---|
Can run in main thread? | Yes | Yes |
Can run in background thread? | No | Yes |
Activity is created/stopped (not visible in background) | Data is NOT sent to UI | Same as setValue()
|
Activity is started/paused (visible in background) | Data is sent to UI | Same as setValue()
|
Activity is resumed (visible in foreground) | Data is sent to UI | Same as setValue()
|
When UI is busy | Data is queued and is NOT dropped | Data is dropped |
Run postValue() in main thread |
N/A | No difference, same as postValue() running in background thread - data is still dropped when UI is busy. |
This testing above is done by calling the
observe()
(which doesn't remove the observer automatically). This way we can observe the actual behavior ofLiveData
respecting the activity lifecycle.
The most important difference is when UI is busy, postValue()
drops the data and setValue()
doesn't.
Investigate observe() vs observeAsState() Behaviors
If you use observe()
, you need to manually call the removeObserver()
API. If you use observeAsState()
(which uses DisposableEffect
internally), it automatically calls the removeObserver()
API when the DisposableEffect
leaves the composition.
These are the scenarios to try:
- Activity is created/stopped (not visible in background)
- Activity is started/paused (visible in background)
- Activity is resumed (visible in foreground)
- Activity is destroyed (screen rotation)
- Leaving composition
Simulate Leaving Composition
To simulate leaving composition, you can implement CommonScreen()
composable function below, which consists of buttons to hide and show the actual composable content
. When the content
is hidden, it simulates the leaving composition.
@Composable
fun CommonScreen(content: @Composable () -> Unit) {
var showContent by rememberSaveable { mutableStateOf(true) }
val context = LocalContext.current
Column(modifier = Modifier.verticalScroll(rememberScrollState())){
if (showContent) {
content()
Button(onClick = {
showContent = false
}) {
Text("Hide Content (Simulate Leaving Composition)")
}
}
else {
Button(onClick = {
showContent = true
}) {
Text("Show Content")
}
}
}
}
Add Logging in observeAsState()
Let's duplicate LiveData<T>.observeAsState
to LiveData<T>.observeAsStateWithLogging()
extension function with logging information to indicate whether the data is sent to UI.
@Composable
fun <R, T: R> LiveData<T>.observeAsStateWithLogging(initial: R): State<R> {
/*...*/
DisposableEffect(this, lifecycleOwner) {
val observer = Observer<T> {
Log.d(tag, "[ObserveAsState]: Assigning $it to state.value")
state.value = it
}
/*...*/
}
return state
}
Summary - observe() vs observeAsState()
After playing around with different scenarios, here is the summary:
Scenarios | observe() | observerAsState() |
---|---|---|
Activity is created/stopped (not visible in background) |
Observer retains - data is NOT sent to UI (due to LiveData is lifecycle-aware component) |
Same as observe()
|
Activity is started/paused (visible in background) |
Observer retains - data is sent to UI |
Same as observe()
|
Activity is resumed (visible in foreground) |
Observer retains - data is sent to UI |
Same as observe()
|
Activity is destroyed |
Observer is removed - data is NOT sent to UI |
Same as observe()
|
Leaving composition |
Observer retains - data is sent to UI (due to activity is still active/visible) |
Observer is removed - Data is sent to UI |
For
observe()
, since we haven’t put any special code to remove theObserver
object, theObserver
always retains during leaving composition. It only gets removed when the activity is destroyed.Why we want to remove the
Observer
when activity is not visible? Because it saves resources by NOT running any execution that doesn't have any impact on UI.
The most important difference of observeAsState()
is it removes the Observer
when the effects (where the observeAsState()
is called) leaves the composition.
LiveData Lifecycle
The LiveData
lifecycle is tied to the ViewModel
. Since ViewModel
is tied to the activity lifecycle (in this example), ViewModel
is destroyed when activity is finished / application is exited. The LiveData
is destroyed only when ViewModel
is destroyed. So as long as the application is alive, the setValue()
or postValue()
keeps emitting the data even the activity is not active (e.g. activity is not visible in background).
This is the logging that keeps printing in background in this app example:
[ViewModel]: setValue with 1167
Depending on whether navigation component is used, the ViewModel
lifecycle doesn't always tie to the activity lifecycle. To understand the details of ViewModel
lifecycle, see the following article:
Conclusion
The good thing about LiveData
is activity lifecycle-aware, it doesn't waste any resources when the UI is not visible (created/ stopped). LiveData
also automatically removes all the observers when activity is destroyed.
However, it doesn't work with leaving composition (because the activity is still active/visible). To save resources, you use observeAsState()
(for Jetpack Compose of course) which automatically removes the Observer
explicitly when it is no longer in the composition loop. Thus, no update is sent to the UI.
What about using setValue()
or postValue()
? I prefer to use setValue()
because it doesn't drop any data when UI is busy. I can put the data fetching into the background thread and update the LiveData
in main thread, so no data is dropped and still remains asynchronous.
Given how fast Android development has evolved,
LiveData
is probably going to be obsolete eventually and replaced withStateFlow
- will be covered in the next topic.
Source Code
GitHub Repository: Demo_AsyncFlow (see the LiveDataActivity
)
Originally published at https://vtsen.hashnode.dev.
Top comments (0)