Architecture Components, LiveData and FusedLocationProvider

Please note that this article is an exercise for Architecture Components and LiveData. Where FusedLocationProvider is the best case to introduce LiveData concept to new people. Google it self has deprecated FusedLocationProvider and introduce FusedLocationProviderClient API that look very similar to code at the end of this article.

Have you ever try to monitor current location changes in android? From the first introduction of the location API into FusedLocationProvider API. Handling this case have been quite a problem for developers. Not because the dificulty, but because the repetition and the amount of boiler plate code that we must write.

Lets dive in to the code that uses FusedLocatinProvider API. To monitor the current location first you need to build the GoogleApiClient, call connect() and wait for the GoogleApiClient to be connected.

protected synchronized void buildGoogleApiClient() {
googleApiClient = new GoogleApiClient.Builder(this)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.addApi(LocationServices.API)
.build();

googleApiClient.connect();
}

After it’s been connected then you can start requestion for location update. This will tell the GoogleServices to let your app know whenever there’s a location change.

@Override
public void onConnected(Bundle connectionHint) {
Log.d(TAG, "connected to google api client");
location = LocationServices.FusedLocationApi.getLastLocation(googleApiClient);

if (location != null) {
Log.d(TAG, "LatLng: " + location.getLatitude() + "," + location.getLongitude());
}

Log.d(TAG, "Starting location updates");
locationRequest = LocationHelper.createRequest();
startLocationUpdates();
}

protected void startLocationUpdates() {
LocationServices.FusedLocationApi.requestLocationUpdates(
googleApiClient, locationRequest, this);
}

When there’s a change in the location we want our app to respond accordingly. Thus we handle it by implementing an onLocationChanged() method. In this method we finally write some code that specific to our apps. The rest was boiler plate code.

@Override
public void onLocationChanged(Location location) {
Log.d(TAG, "Location changed received: " + location);
this.location = location;
// do something about the location changes
}

But that’s not all. To make sure your app work well in real android environment, you need to handle when cases when your user pauses and resume your activity. Make sure the app stop requesting location when it doesn’t need it and drain you user battery. And start the service again so the app will work when the user resume the activity. Thus you need to override the onPause() and onResume() method and implement some code in it. More boiler plate code.

@Override
protected void onPause() {
super.onPause();
stopLocationUpdates();
}

@Override
public void onResume() {
super.onResume();
if (googleApiClient.isConnected()) {
startLocationUpdates();
}
}

There are few other methods that you need to implement such as onConnectionSuspended(), onConnectionFailed(), etc. But you get the point. And the most annoying part is that if you think you only need to write this once, you’re wrong. You need to write all of this boiler plate code for each activity that monitor current location changes.

The obvious next step is trying to extract the boiler plate code to an utility class, probably make it a singleton so we can access it from anywhere. But no matter how hard you try there will be some code clutter left. In each activity there will still be code in the onPause() and onResume() method that needs to be called for handling current location changes. In another words your activity still need to babysit the lifecycle of our utility class. Because that’s just the way Android lifecylcle works.

And thus come the …

Architecture Components

Architecture components is a new collection of libraries from google that help you design robust, testable, and maintainable apps. It consist of four parts

  • Lifecycle Aware Components (LAC)
  • View Model
  • LiveData
  • Room

In this article we would only talk about 2 of those component, Lifecycle Aware Components (LAC) and LiveData. LAC are components that can automatically adjust their behavior based on the current lifecycle of an activity or fragment.

LiveData is an observable data holder class. Unlike a regular observable, LiveData is lifecycle-aware, meaning it respects the lifecycle of other app components, such as activities, fragments, or services. This awareness ensures LiveData only updates app component observers that are in an active lifecycle state.

Using this two parts of the Architecture component we can create a better approach to monitor current location in Android.

Implementing LiveData

First we create a new class called CurrentLocationListener. We will make this class as a singleton and build the google api client when the first instance is called.

public static CurrentLocationListener getInstance(Context appContext) {
if (instance == null) {
instance = new CurrentLocationListener(appContext);
}
    return instance;
}
private CurrentLocationListener(Context appContext) {
buildGoogleApiClient(appContext);
}
private synchronized void buildGoogleApiClient(Context appContext) {
Log.d(TAG, "Build google api client");
googleApiClient = new GoogleApiClient.Builder(appContext)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.addApi(LocationServices.API)
.build();
}

And then well make the class extends a LiveData. There are 2 methods that we can override in LiveData onActive() and onInactive(). onActive() will be called when the number of active observers change from 0 to 1. onInactive() will be called when the number of active observers change from 1 to 0.

public class CurrentLocationListener extends LiveData<Location> {
    // singleton and build google api are ommited
@Override
protected void onActive() {
googleApiClient.connect();
}

@Override
protected void onInactive() {
if (googleApiClient.isConnected()) {
LocationServices.FusedLocationApi.removeLocationUpdates(
googleApiClient, this);
}
googleApiClient.disconnect();
}
@Override
public void onConnected(@Nullable Bundle connectionHint) {
Log.d(TAG, "connected to google api client");

Location lastLocation = LocationServices.FusedLocationApi.getLastLocation(googleApiClient);

if (lastLocation != null) {
setValue(lastLocation);
} else {
Log.e(TAG, "onConnected: last location value is NULL");
}

if(hasActiveObservers() && googleApiClient.isConnected()) {
// LocationRequest locationRequest = LocationHelper.createRequest();
LocationRequest locationRequest = LocationRequest.create();
LocationServices.FusedLocationApi.requestLocationUpdates(googleApiClient, locationRequest, this);
}
}
@Override
public void onLocationChanged(Location location) {
Log.d(TAG, "Location changed received: " + location);
setValue(location);
}

@Override
public void onConnectionSuspended(int cause) {
Log.w(TAG, "On Connection suspended " + cause);
}

@Override
public void onConnectionFailed(@NonNull ConnectionResult result) {
Log.e(TAG, "GoogleApiClient connection has failed " + result);
}
}

In onActivate() we connect to the google api client. That’s when connected, will call the onConnected() method. In the onConnected() method, we will get the last known location, set the value of our LiveData and listen for subsequent changes by creating a request location updates.

Whenever we receive location changes all we have to do is set the value of the new location for our LiveData by calling setValue in the onLocationChanges.

Lastly we implement onInActive() method, when there’s no more observer that listen to this LiveData, we remove the locationUpdate request and disconnect from the service. That’s it for the CurrentLocationListener class.

How It works

To use the CurrentLocationListener (LiveData) class. All we have to do is get the singleton and then start observing the changes from our onCreate() method. That’s it. No more writing the boiler plate code in each activity, or even babysitting the lifecycle of our utility class.

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_event);
    subscribeToLocationUpdate();
}
private void subscribeToLocationUpdate() {
CurrentLocationListener.getInstance(getApplicationContext()).observe(this, new Observer<Location>() {
@Override
public void onChanged(@Nullable Location location) {
Log.d(TAG, "onChanged: location updated " + location);
// do your stuff
}
});
}

The way LiveData works. When a class connected to a live data (by calling it observe method) if that class is not in DESTROYED state, that class will be added to a list of observer for the live data.

Thus the first observe() call will change the observer count from 0 to 1. Triggerring the onActive() method in our CurrentLocationListener LiveData class. And start the process of connecting and updating our location data.

LiveData will considers an observer to be in an active state if the observer’s lifecycle is in either the STARTED or RESUMED state. Thus adding another activity (by observing the LiveData) will increase the observer count. And pausing an activity or destroying an activity will decrease the active observer count. When the count finally decreased to 0, then the inActive() method will be called freeing the resource we use.

Why this works now, not before?

If you have a keen eye, you’ll notice that the observe() method has two parameter. A lifecycle aware component, and an Observer. Starting from support library version 26.1.0, Fragment, FragmentActivity and AppCompatActivity now implement the LifecycleOwner interface from Architecture Components. Thus passing activity that extends one of those class from support library would make us free from having to babysit the lifecycle of our activity manually.

Enjoy!