Architecture Components / LiveData

Architecture Components: LiveData

Architecture Components were announced at Google I/O 2017 and provide some frameworks to help developers create more maintainable and more robust apps. The architecture components don’t specifically do anything which will prevent apps from crashing or otherwise misbehaving, but they offer some frameworks which encourages decoupled components within the ap. This should lead to better behaved app. In this series we’ll take a look at these components and see how we can benefit from them. In this second article we’ll take a look at LiveData.

Previously we saw how we could hook up different LocationProvider implementation to the Fragment lifecycle in different places depending on the actual implementation. However, a LocationProvider is actually an interesting case because it will continue to push location updates for as long as we’re subscribed to it (OK, the dummy implementation wouldn’t, but you get the idea). With our current implementation we have used a callback interface named LocationListener whose updateLocation() method will be called for each location update, but we can actually simplify this by using LiveData.

LiveData is an implementation of the publish / subscribe pattern for a data object. A LiveData object can be subscribed to by an interested party, and each time the data changes the subscriber will be notified. For those familiar with RxJava this is a very similar concept to an Observable object. The big advantage of LiveData is that a LiveData object will automatically hook onto an Activity or Fragment’s lifecycle and will only become active when the lifecycle is in STARTED, or RESUMED state. Symmetrically it will be shutdown when the lifecycle transitions to any state outside of that set.

Let’s see this in practice, first we’ll look at our dummy implementation:

public class LocationLiveData extends LiveData<CommonLocation> {
    private static final double LATITUDE = 51.649836;
    private static final double LONGITUDE = -0.401486;
    private static final float ACCURACY = 5f;
    private static final CommonLocation LOCATION = new CommonLocation(LATITUDE, LONGITUDE, ACCURACY);

    public LocationLiveData(Context context) {
        //NO-OP
    }

    @Override
    protected void onActive() {
        super.onActive();
        setValue(LOCATION);
    }
}

In this case we only want to emit an initial CommonLocation value once everything is initialised, and that is done in the onActive() method. We no longer need to hook into the Lifecycle – that is done automatically by LiveData. The onActive() and onInactive() methods will be called when we enter and exit active state (STARTED, OR RESUMED). So we no longer have the granularity of hooking into specific states, but if we needed that we can still implement lifecycle directly.

The important method here is when we call setValue(LOCATION). This will both set the internal value of the LiveData object, but will also trigger updates to all active observers of this LiveData object.

Let’s now look at the changes that we need to make to our LocationFragment to consume updates from this LiveData implementation:

public class LocationFragment extends LifecycleFragment implements LocationListener {
    private static final String FRACTIONAL_FORMAT = "%.4f";
    private static final String ACCURACY_FORMAT = "%.1fm";

    private TextView latitudeValue;
    private TextView longitudeValue;
    private TextView accuracyValue;

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        LiveData<CommonLocation> liveData = new LocationLiveData(context);
        liveData.observe(this, new Observer<CommonLocation>() {
            @Override
            public void onChanged(@Nullable CommonLocation commonLocation) {
                updateLocation(commonLocation);
            }
        });
    }
    .
    .
    .
}

We first create an instance of the LocationLiveData class that we just looked at. We then call its observe() method, which takes two arguments: the first is a LifecycleOwner which is what the LiveData object uses internally to determine that active/inactive state, and the second is an Observer<> implementation which will receive callbacks whenever the internal value of the LiveData object changes. In the case of a LocationProvider the onChanged() method will be called each time the location updates.

The rest of the LocationFragment is exactly the same as before.

Let’s now take a look at the Play Services LocationProvider implementation:

public class LocationLiveData extends LiveData<CommonLocation> {
    private final Context context;

    private FusedLocationProviderClient fusedLocationProviderClient = null;

    public LocationLiveData(Context context) {
        this.context = context;
    }

    @Override
    protected void onActive() {
        super.onActive();
        if (ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED
                && ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
            return;
        }
        FusedLocationProviderClient locationProviderClient = getFusedLocationProviderClient();
        LocationRequest locationRequest = LocationRequest.create();
        Looper looper = Looper.myLooper();
        locationProviderClient.requestLocationUpdates(locationRequest, locationCallback, looper);
    }

    @NonNull
    private FusedLocationProviderClient getFusedLocationProviderClient() {
        if (fusedLocationProviderClient == null) {
            fusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(context);
        }
        return fusedLocationProviderClient;
    }

    @Override
    protected void onInactive() {
        if (fusedLocationProviderClient != null) {
            fusedLocationProviderClient.removeLocationUpdates(locationCallback);
        }
    }

    private LocationCallback locationCallback = new LocationCallback() {
        @Override
        public void onLocationResult(LocationResult locationResult) {
            Location newLocation = locationResult.getLastLocation();
            double latitude = newLocation.getLatitude();
            double longitude = newLocation.getLongitude();
            float accuracy = newLocation.getAccuracy();
            CommonLocation location = new CommonLocation(latitude, longitude, accuracy);
            setValue(location);
        }
    };
}

There’s a little bit more going on here, but it’s fairly easy to understand. When the LifecycleOwner enters an active state, then onActive() is called. We first check that we have the necessary permissions and only attempt to register for location updates if we do. If we go though the permission request user flow, then the LocationFragment will become inactive while permissions are requested from the user. When the user returns to LocationFragment then onActive() will be called again, so we will correctly register for location updates if permission was granted.

Whenever the LifecycleOwner exits an active state, then onInactive() is called and we’ll unregister for location updates, if we have a valid FusedLocationProvider instance.

An important point here is that a LiveData object may have more than one observer. If that is the case then it will enter an active state when any of the observers becomes active; but will only become inactive when all of the observers are inactive.

The remainder of the code in here is just to handle location updates, and in onLocationResult() we call setValue() which will update the internal value and make a callback to any active observers.

Using LiveData is a really simple approach when it comes to managing data which will change periodically, and it removes the need to manually hook onto lifecycle changes.

Hopefully it is now obvious why an explanation of the lifecycle components was necessary to make the understanding of LiveData somewhat easier.

In the next article in this series we’ll take a look at ViewModel and see how that can work in conjunction with LiveData to further make things easier for us.

The source code for this article is available here.

Many thanks to Yiğit Boyar and Sebastiano Poggi for proof-reading services – this post would have been much worse without their input. Any errors or typos that remain are purely my fault!

© 2017, Mark Allison. All rights reserved.

Copyright © 2017 Styling Android. All Rights Reserved.
Information about how to reuse or republish this work may be available at http://blog.stylingandroid.com/license-information.

6 Comments

    1. The LiveData instance will be tied to the Fragment lifecycle. Calling observe() binds the LiveData to the Fragment lifecycle. The Fragment is the LifecycleOwner, and the LiveData will respond to the Fragment being destroyed by removing the observer, and any this releases any references that are being held by the Architecture Components on the LiveData. Provided you don’t hold any references to the LiveData object outside of the LifecycleOwner (i.e. the Fragment) then it can be GC’d when the Fragment is GC’d.

  1. Where would I implement SettingsClient to ensure Location is activated on the device? And more importantly, if i need to take action, where do I place ResolvableApiException.startResolutionForResult()? since it requires an activity…. Obviously, I want to avoid clouding my activity/fragments (that need location ) with boilerplate callbacks etc.

    1. That’s going to vary depending on the requirements of your app. There is already a Context held by the LiveData, it would be easy enough to replace that with an Activity.

      There is already code in the examples which handles requesting missing permissions from the user which is essentially the same pattern as ResolvableApiException.startResolutionForResult() – start an external Activity and wait for the result. Take a similar approach to that.

  2. Hmmm – I’m happy with the app permissions check being done in the activity (only requires ActivityCompat ), but Id like the Location to be encapsulated if possible… I’m new to Android dev and struggling to understand how I pass context from the LiveData constructor to the SettingsClient.checkLocationSettings ‘onFailureListener’ since the listener is anonymous.
    It does work if keeping a private reference to context in LiveData and cast to activity in the listener but (as per ViewModel) wouldn’t this cause a leak? might LiveData also outlive the activity/context? Obviously a knowledge gap I know but having trouble finding help for this specific issue combined with the subject of Architecture Components. My ViewModel is expecting a Location object from livedata so I can’t even return a “settings required” message from LiveData (or can I??). Any pointers or best practices for dispatching messages or an alternative approach would be appreciated very much.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.