LiveData is a data holder class that holds a value and allows observation of that value. Unlike ordinary observables, LiveData follows the Lifecycle of an application component so that the Observer can specify a Lifecycle that it should follow.

If the Observer’s Lifecycle is STARTED or RESUMED, LiveData considers the Observer to be active.

public class LocationLiveData extends LiveData<Location> { private LocationManager locationManager; private SimpleLocationListener listener = new SimpleLocationListener() { @Override public void onLocationChanged(Location location) { setValue(location); }}; public LocationLiveData(Context context) { locationManager = (LocationManager) context.getSystemService( Context.LOCATION_SERVICE); } @Override protected void onActive() { locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, listener); } @Override protected void onInactive() { locationManager.removeUpdates(listener); }}Copy the code

The implementation of the Location listener has three important parts:

  • OnActive () : This method is called when LiveData has an active observer, which means it needs to start observing location updates from the device.

  • VonInactive () : This method is called when LiveData has no active observers. Since no observer is listening, there is no reason to keep the connection to the LocationManager. This is important, because staying connected consumes a lot of power and does no good.

  • SetValue () : Call this method to update the value of the LiveData instance and notify the active observer of the change.

The new LocationLiveData can be used as follows:

public class MyFragment extends LifecycleFragment { public void onActivityCreated (Bundle savedInstanceState) { LiveData<Location> myLocationListener = ... ; Util.checkUserStatus(result -> { if (result) { myLocationListener.addObserver(this, location -> { // update UI }); }}); }}Copy the code

Notice that the addObserver() method passes LifecycleOwner as the first argument. Doing so means that the observer should be bound to Lifecycle, meaning:

  • If Lifecycle is not active (STARTED or RESUMED), the observer will not be called even if the value changes.

  • If Lifecycle is destroyed, the observer is automatically removed.

The fact that LiveData is lifecycle aware opens up a new possibility: it can be shared between multiple activities, fragments, and so on. To keep the instance simple, it can be treated as a singleton, as follows:

public class LocationLiveData extends LiveData<Location> { private static LocationLiveData sInstance; private LocationManager locationManager; @MainThread public static LocationLiveData get(Context context) { if (sInstance == null) { sInstance = new LocationLiveData(context.getApplicationContext()); } return sInstance; } private SimpleLocationListener listener = new SimpleLocationListener() { @Override public void onLocationChanged(Location location) { setValue(location); }}; private LocationLiveData(Context context) { locationManager = (LocationManager) context.getSystemService( Context.LOCATION_SERVICE); } @Override protected void onActive() { locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, listener); } @Override protected void onInactive() { locationManager.removeUpdates(listener); }}Copy the code

Now fragment can be used like this:

public class MyFragment extends LifecycleFragment { public void onActivityCreated (Bundle savedInstanceState) { Util.checkUserStatus(result -> { if (result) { LocationLiveData.get(getActivity()).observe(this, location -> { // update UI }); }}); }}Copy the code

There may be multiple fragments and activities observing instances of MyLocationListener, and LiveData can properly manage them so that they only connect to system services when any one of them is visible (i.e. active).

LiveData has the following advantages:

  • There are no memory leaks: Because observers are bound to their own Lifecycle object, they can be cleaned up automatically when their Lifecycle is destroyed.

  • The activity will not crash if it stops: If the Observer’s Lifecycle is idle (for example, while the activity is in the background), they will not receive a change event.

  • Always keep data up to date: If Lifecycle is restarted (for example, an activity is returned from the background to the start state), location data will be received up to date unless it is not already available.

  • Handle configuration changes correctly: If an activity or fragment is recreated due to a configuration change (such as a device rotation), it will immediately receive the latest valid location data.

  • Resource sharing: You can keep only one instance of MyLocationListener, connect to the system service only once, and correctly support all observers in your application.

  • No more manual management of the life cycle: Fragments simply observe data when needed, without fear of being stopped or starting observation after stopping. Since the Fragment provides its Lifecycle as it observes the data, LiveData automatically manages this.

LiveData conversion

Sometimes you may need to change the value of LiveData before sending it to an observer, or you may need to return a different instance of LiveData with another LiveData.

Lifecycle provides a quick class that contains methods to help you do these things.

LiveData<User> userLiveData = ... ; LiveData<String> userName = Transformations.map(userLiveData, user -> { user.name + " " + user.lastName });Copy the code
private LiveData<User> getUser(String id) { ... ; } LiveData<String> userId = ... ; LiveData<User> user = Transformations.switchMap(userId, id -> getUser(id) );Copy the code

Using these transformations allows the observer’s Lifecycle information to be carried throughout the call chain so that the transformations are performed only if the observer observes the return of LiveData. This lazy computational nature of the transformation allows you to implicitly pass lifecycle related behavior without having to add explicit calls or dependencies.

Whenever you think that Lifecycle class is needed in the ViewModel, transformations may be the solution.

For example, suppose you have a UI where the user enters an address and receives a zip code for that address. A simple ViewModel for the UI might look like this:

class MyViewModel extends ViewModel { private final PostalCodeRepository repository; public MyViewModel(PostalCodeRepository repository) { this.repository = repository; } private LiveData<String> getPostalCode(String address) { // DON'T DO THIS return repository.getPostCode(address); }}Copy the code

With an implementation like this, the UI needs to be unregistered from the previous LiveData and re-registered with the new instance each time getPostalCode() is called. In addition, if the UI is recreated, it will trigger a new repository.getPostcode () call instead of using the results of the previous call.

Instead of using that approach, you should implement converting address input to zip code information.

class MyViewModel extends ViewModel { private final PostalCodeRepository repository; private final MutableLiveData<String> addressInput = new MutableLiveData(); public final LiveData<String> postalCode = Transformations.switchMap(addressInput, (address) -> { return repository.getPostCode(address); }); public MyViewModel(PostalCodeRepository repository) { this.repository = repository } private void setInput(String address) { addressInput.setValue(address); }}Copy the code

Note that we even make the postalCode field public final because it never changes. PostalCode is defined as a transformation of addressInput, so when addressInput changes and there are active observers, repository.getPostcode () will be called. If there is no active observer at the time of the call, no operations are performed until the observer is added.

This mechanism allows LiveData to be created lazily on demand with fewer resources. Viewmodels can easily take LiveData and define transformation rules on them.

Follow our official account for more content: