App Development

Google I/O 2017: Lifecycles of the rich and famous

artistic spiral image of books

The Activity/Fragment Lifecycle: At once both beautiful and terrifying to Android developers the world over. Failing to grasp the Lifecycle of your components can bring a lifetime of suffering. Registering and unregistering various receivers, listeners, and observables is tedious work, and who here hasn’t forgotten to unregister at times? Do you want memory leaks? Because that’s how you get memory leaks. At the same time, your onStart and onStop (or any other lifecycle callback methods you may be using) become these giant monolithic oceans of code.

If only there was a better way…

A New Hero Emerges

Google announced a set of Architecture libraries at I/O this year, one of which is called Lifecycle. Lifecycle solves multiple problems related to managing the interaction between your app and the Android Activity Lifecycle. Probably why it’s named Lifecycle. One of these problems is making lifecycle-aware components easier to create and use.

First off, adding it to your project:

allprojects {
    repositories {
        jcenter()
        maven { url 'https://maven.google.com' }
    }
}


dependencies {
   ...
   compile "android.arch.lifecycle:extensions:1.0.0-alpha1"
   annotationProcessor "android.arch.lifecycle:compiler:1.0.0-alpha1"
   ...
}

Let’s take a simple example of managing a Barometer sensor. Normally you’d need to handle registering and unregistering the sensor in your Activity’s (or Fragment’s) lifecycle callback methods, like so:

public class BarometerActivity extends AppCompatActivity {
    private SensorManager sensorManager;
    private SensorEventListener pressureListener;

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        this.sensorManager = (SensorManager) getSystemService(Service.SENSOR_SERVICE);
        this.pressureListener = new SensorEventListener() {
            @Override
            public void onSensorChanged(SensorEvent event) {
                //do things on the ui
            }

            @Override
            public void onAccuracyChanged(Sensor sensor, int accuracy) {}
        };
    }

    @Override
    protected void onResume() {
        super.onResume();
        sensorManager.registerListener(pressureListener,
                sensorManager.getDefaultSensor(Sensor.TYPE_PRESSURE),
                SensorManager.SENSOR_DELAY_FASTEST);
    }

    @Override
    protected void onPause() {
        super.onPause();
        sensorManager.unregisterListener(pressureListener);
    }
}

This is fine and dandy but this all looks awfully tied to the BarometerActivity, and we know we can split this up. In Lifecycle, there is a set of classes we can use to manage everything about this Sensor’s lifecycle: An interface named LifecycleOwner, which has a single method named getLifecycle(); and a new interface called LifecycleObserver, which can be added to a Lifecycle. Included in the library is a set of components to extend, LifecycleActivity and LifecycleFragment, both of which are LifecycleOwners and allow you to hook into the LifecycleRegistry.

We’ll create a BarometerManager to handle creating and registering our Barometer. We’ll make it extend LifecycleObserver to get access to those sweet sweet lifecycle events without a hard couple to the Activity. We get access to these events via the magic of Annotations, using @OnLifeCycleEvent.

public class BarometerManager implements LifecycleObserver {
    private SensorManager sensorManager;
    private SensorEventListener pressureListener;

    public BarometerManager(Context context, SensorEventListener pressureListener) {
        this.sensorManager = (SensorManager) context.getSystemService(Service.SENSOR_SERVICE);
        this.pressureListener = pressureListener;
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
    public void onResume() {
        sensorManager.registerListener(pressureListener,
                sensorManager.getDefaultSensor(Sensor.TYPE_PRESSURE),
                SensorManager.SENSOR_DELAY_FASTEST);
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
    public void onPause() {
        sensorManager.unregisterListener(pressureListener);
    }
  
  @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
    public void onDestroy() {
        pressureListener = null;
    }
}

We can then change our BarometerActivity to extend LifecycleActivity to get access to the Lifecycle (for now. Since Lifecycle is in Alpha, it can’t be added to AppCompatActivity, but when it’s stable you’ll just extend AppCompatActivity as usual and it will handle these new Lifecycle tools), and then add our BarometerManager as a LifecycleObserver:

public class BarometerActivity extends LifecycleActivity {
    private BarometerManager barometerManager;

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        barometerManager = new BarometerManager(this, new SensorEventListener() {
            @Override
            public void onSensorChanged(SensorEvent event) {
                //do things on the ui
            }

            @Override
            public void onAccuracyChanged(Sensor sensor, int accuracy) {}
        });
        getLifecycle().addObserver(barometerManager);
    }
}

Tada! Lifecycle-aware Barometer sensor!

We can go deeper though! Lifecycle also includes a feature called LiveData. These are also Lifecycle-aware, and are data holder classes which allow a value to be observed. We can clean up this whole fiasco even more by creating a LiveData class that can be observed. When the SensorEventListener gets a callback, it will just set the value on the LiveData, and anything listening gets pushed the new value.

public class BarometerLiveData extends MutableLiveData<Float> {
    private SensorManager sensorManager;
    private SensorEventListener pressureListener;

    public BarometerLiveData(Context context) {
        sensorManager = (SensorManager) context.getSystemService(Service.SENSOR_SERVICE);
        pressureListener = new SensorEventListener() {
            @Override
            public void onSensorChanged(SensorEvent event) {
                setValue(event.values[0]);
            }

            @Override
            public void onAccuracyChanged(Sensor sensor, int accuracy) {}
        };
    }

    @Override
    protected void onActive() {
        super.onActive();
        sensorManager.registerListener(pressureListener,
                sensorManager.getDefaultSensor(Sensor.TYPE_PRESSURE),
                SensorManager.SENSOR_DELAY_FASTEST);    }

    @Override
    protected void onInactive() {
        super.onInactive();
        sensorManager.unregisterListener(pressureListener);
    }
}

Then our BarometerActivity reaches its final form, which is simply creating our BarometerLiveData and then observing it. This allows us to stay up to date when new sensor events come in so we can change our UI. Since it’s tied to our Lifecycle, it’ll handle its lifecycle automatically by removing the observer during the @OnDestroy Lifecycle callback, which makes our lives easier, and our apps more bug-free.

public class BarometerActivity extends LifecycleActivity {
    private BarometerLiveData barometerData;

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        barometerData = new BarometerLiveData(this);
        barometerData.observe(this, aFloat -> {
            //do things on the ui
        });
    }
}

As you can see, the new Lifecycle library can make managing Lifecycle-aware components much less frustrating, and cleanup your code in the process! No more worrying about whether you unregistered listeners and the like, because Lifecycle can handle it all for you, seamlessly. Lifecycle callbacks and LiveData are only a portion of what Lifecycle brings to the table, but it’s two of the major reasons why you should use it.

Tidbit: Until the functionality of LifecycleActivity is included in AppCompatActivity, you can use the following to get the features of both: https://gist.github.com/Richie97/c0f681c152a4d01c076b4ad5573d9a5d#file-lifecycleappcompatactivity-java

Quickstart-Guide-to-Kotlin-Multiplatform

A Quick Start Guide to Kotlin Multiplatform

Kotlin Multiplatform, though still experimental, is a great up-and-coming solution...

Read the article