
We’ve already covered a lot of ground in our Android Architecture Components series. We started out talking about the idea behind the new architecture and looking at the key components presented at Google I/O. In the second post, we began our deep exploration of the main components of the package, taking a close look at the Lifecycle and LiveModel components. In this post, we’ll continue to explore the Architecture Components, this time analyzing the awesome LiveData component.
I’m assuming that you’re familiar with the concepts and components covered in the last tutorials, like Lifecycle, LifecycleOwner, and LifecycleObserver. If you’re not, take a look at the first post in this series, where I discuss the general idea behind the new Android Architecture and its components.
We’ll continue to build on the sample application we started in the last part of this series. You can find it in the tutorial GitHub repo.
1. The LiveData Component
LiveData is a data holder. It is capable of being observed, it can hold any kind of data, and on top of that, it is also lifecycle-aware. In practical terms, LiveData can be configured to only send data updates when its observer is active. Thanks to its lifecycle awareness, when observed by a LifecycleOwner the LiveData component will send updates only when the observer’s Lifecycle is still active, and it will remove the observed relation once the observer’s Lifecycle is destroyed.
The LiveData component has many interesting characteristics:
- prevents memory leaks when the observer is bound to a
Lifecycle - prevents crashes due to stopped activities
- data is always up to date
- handles configuration changes smoothly
- makes it possible to share resources
- automatically handles lifecycle
2. Observing LiveData
A LiveData component sends data updates only when its observer is “active”. When observed by a LifecycleOwner, the LiveData component considers the observer to be active only while its Lifecycle is on the states STARTED or RESUMED, otherwise it will regard the observer as inactive. During the observer’s inactive state, LiveData will stop the data update stream, until its observer becomes active once again. If the observer is destroyed, LiveData will remove its reference to the observer.

To achieve this behavior, LiveData creates a tight relation with the observer’s Lifecycle when observed by a LifecycleOwner. This capacity makes it easier to avoid memory leaks when observing LiveData. However, if the observing process is called without a LifecycleOwner, the LiveData component won’t react to Lifecycle state, and the observer’s status must be handled manually.
To observe a LiveData, call observe(LifecycleOwner, Observer or observeForever(Observer.
-
observe(LifecycleOwner, Observer: This is the standard way to observe a) LiveData. It ties the observer to aLifecycle, changingLiveData‘s active and inactive state according to theLifecycleOwnercurrent state. -
observeForever(Observer: This method doesn’t use a) LifecycleOwner, so theLiveDatawon’t be able to respond toLifecycleevents. When using this method, it is very important to callremoveObserver(Observer, otherwise the observer may not be garbage collected, causing a memory leak.)
// called from a LifecycleOwner
location.observe(
// LifecycleOwner
this,
// creating an observer
Observer {
location ->
info("location: ${location!!.latitude},
${location.longitude}")
})
}
// Observing without LifecycleOwner
val observer = Observer {
location ->
info("location: ${location!!.latitude},
${location.longitude}")
})
location.observeForever(observer)
// when observer without a LivecyleOwner
// it is necessary to remove the observers at some point
location.removeObserver( observer )
3. Implementing LiveData
The generic type in the LiveData class defines the type of data that will be held. For example, LiveData holds Location data. Or LiveData holds a String.
There are two main methods that must be considered in the component implementation: onActive() and onInactive(). Both methods react to the observer’s state.
Example Implementation
In our sample project we used lots of LiveData objects, but we only implemented one: LocationLiveData. The class deals with GPS Location, passing the current position only for an active observer. Notice that the class updates its value on the onLocationChanged method, passing to the current active observer the refreshed Location data.
class LocationLiveData
@Inject
constructor(
context: Context
) : LiveData(), LocationListener, AnkoLogger {
private val locationManager: LocationManager =
context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
@SuppressLint("MissingPermission")
override fun onInactive() {
info("onInactive")
locationManager.removeUpdates(this)
}
@SuppressLint("MissingPermission")
fun refreshLocation() {
info("refreshLocation")
locationManager.requestSingleUpdate(LocationManager.GPS_PROVIDER, this, null )
}
}
4. The MutableLiveData Helper Class
The MutableLiveData is a helper class that extends LiveData, and exposes postValue and setValue methods. Other than that, it behaves exactly like its parent. To use it, define the type of data that it holds, like MutableLiveData to hold a String, and create a new instance.
val myData: MutableLiveData= MutableLiveData()
To send updates to an observer, call postValue or setValue. The behavior of these methods is quite similar; however, setValue will directly set a new value and can only be called from the main thread, while postValue creates a new task on the main thread to set the new value and can be called from a background thread.
fun updateData() {
// must be called from the main thread
myData.value = api.getUpdate
}
fun updateDataFromBG(){
// may be called from bg thread
myData.postValue(api.getUpdate)
}
It is important to consider that, since the postValue method creates a new Task and posts on the main thread, it will be slower than direct calls to setValue.
5. Transformations of LiveData
Eventually, you’ll need to change a LiveData and propagate its new value to its observer. Or maybe you need to create a chain reaction between two LiveData objects, making one react to changes on another. To deal with both situations, you may use the Transformations class.
Map Transformations
Transformations.map applies a function on a LiveData instance and dispatches the result to its observers, giving you the opportunity to manipulate the data value.

It is very easy to implement Transformations.map. All you’ll have to do is provide a LiveData to be observed and a Function to be called when the observed LiveData changes, remembering that the Function must return the new value of the transformed LiveData.
Suppose that you have a LiveData that must call an API when a String value, like a search field, changes.
// LiveData that calls api // when 'searchLive' changes its value val apiLive: LiveData= Transformations.map( searchLive, { query -> return@map api.call(query) } ) // Every time that 'searchLive' have // its value updated, it will call // 'apiLive' Transformation.map fun updateSearch( query: String ) { searchLive.postValue( query ) }
SwitchMap Transformations
The Transformations.switchMap is quite similar to Transformations.map, but it must return a LiveData object as a result. It’s a little harder to use, but it allows you to construct powerful chain reactions.
In our project, we used Transformations.switchMap to create a reaction between LocationLiveData and ApiResponse.
- Our
Transformation.switchMapobservesLocationLiveDatachanges. - The
LocationLiveDataupdated value is used to call theMainRepositoryto get the weather for the specified location. - The repository calls the
OpenWeatherServicethat produces aLiveDataas a result.> - Then, the returned
LiveDatais observed by aMediatorLiveData, which is responsible for modifying the received value and updating the weather exhibited in the view layer.
class MainViewModel
@Inject
constructor(
private val repository: MainRepository
)
: ViewModel(), AnkoLogger {
// Location
private val location: LocationLiveData =
repository.locationLiveDa()
private var weatherByLocationResponse:
LiveData> =
Transformations.switchMap(
location,
{
l ->
info("weatherByLocation: nlocation: $l")
return@switchMap repository.getWeatherByLocation(l)
}
)
}
Watch out for time-consuming operations in your LiveData transformations, though. In the code above, both Transformations methods run on the main thread.
6. The MediatorLiveData
MediatorLiveData is a more advanced type of LiveData. It has capabilities very similar to those of the Transformations class: it is able to react to other LiveData objects, calling a Function when the observed data changes. However, it has many advantages when compared with Transformations, since it doesn’t need to run on the main thread and can observe multiple LiveDatas at once.
To observe a LiveData, call addSource(LiveData, making the observer react to the , Observer)onChanged method from the given LiveData. To stop the observation, call removeSource(LiveData.)
val mediatorData: MediatorLiveData= MediatorLiveData() mediatorData.addSource( dataA, { value -> // react to value info("my value $value") } ) mediatorData.addSource( dataB, { value -> // react to value info("my value $value") // we can remove the source once used mediatorData.removeSource(dataB) } )
In our project, the data observed by the view layer that contains the weather to exhibit is a MediatorLiveData. The component observes two other LiveData objects: weatherByLocationResponse, which receives weather updates by location, and weatherByCityResponse, which receives weather updates by city name. Each time that these objects are updated, weatherByCityResponse will update the view layer with the current weather requested.
In the MainViewModel, we observe the LiveData and provide the weather object to view.
class MainViewModel
@Inject
constructor(
private val repository: MainRepository
)
: ViewModel(), AnkoLogger {
// ...
// Value observed by View.
// It transform a WeatherResponse to a WeatherMain.
private val weather:
MediatorLiveData> =
MediatorLiveData()
// retrieve weather LiveData
fun getWeather(): LiveData> {
info("getWeather")
return weather
}
private fun addWeatherSources(){
info("addWeatherSources")
weather.addSource(
weatherByCityResponse,
{
w ->
info("addWeatherSources: nweather: ${w!!.data!!}")
updateWeather(w.data!!)
}
)
weather.addSource(
weatherByLocationResponse,
{
w ->
info("addWeatherSources: weatherByLocationResponse: n${w!!.data!!}")
updateWeather(w.data!!)
}
)
}
private fun updateWeather(w: WeatherResponse){
info("updateWeather")
// getting weather from today
val weatherMain = WeatherMain.factory(w)
// save on shared preferences
repository.saveWeatherMainOnPrefs(weatherMain)
// update weather value
weather.postValue(ApiResponse(data = weatherMain))
}
init {
// ...
addWeatherSources()
}
}
In the MainActivity, the weather is observed and its result is shown to the user.
private fun initModel() {
// Get ViewModel
viewModel = ViewModelProviders.of(this, viewModelFactory)
.get(MainViewModel::class.java)
if (viewModel != null) {
// observe weather
viewModel!!.getWeather().observe(
this@MainActivity,
Observer {
r ->
if ( r != null ) {
info("Weather received on MainActivity:n $r")
if (!r.hasError()) {
// Doesn't have any errors
info("weather: ${r.data}")
if (r.data != null)
setUI(r.data)
} else {
// error
error("error: ${r.error}")
isLoading(false)
if (r.error!!.statusCode != 0) {
if (r.error!!.message != null)
toast(r.error.message!!)
else
toast("An error occurred")
}
}
}
}
)
}
}
The MediatorLiveData was also used as the basis of an object that handles calls to the OpenWeatherMap API. Take a look at this implementation; it’s more advanced than the ones above, and it’s really worth studying. If you’re interested, take a look at OpenWeatherService, paying special attention to the Mediator class.
7. Conclusion
We’re almost at the end of our exploration of Android’s Architecture Components. By now, you should understand enough to create some powerful apps. In the next post, we’ll explore Room, an ORM that wraps SQLite and can produce LiveData results. The Room component fits perfectly in this Architecture, and it is the final piece of the puzzle.
See you soon! And in the meantime, check out some of our other posts on Android app development!
Android SDKHow to Use the Google Cloud Vision API in Android Apps
JavaAndroid Design Patterns: The Observer Pattern
KotlinKotlin From Scratch: Variables, Basic Types, and Arrays
KotlinKotlin From Scratch: Nullability, Loops, and Conditions
Android SDKWhat Are Android Instant Apps?
Powered by WPeMatico












