Components

Architecture components are a set of Android libraries that help you structure your app in a way that is robust, testable, and maintainable.

This codelab introduces you to the following lifecycle-aware architecture components for building Android apps:

What you'll build

In this codelab, you implement examples of each of the components described above. You begin with a sample app and add code through a series of steps, integrating the various architecture components as you progress.

What you'll need

In this step, you download the code for the entire codelab and then run a simple example app.

Click the following button to download all the code for this codelab:

Download source code

  1. Unzip the code, and then open the project Android Studio version 2.3 or newer.
  2. Run the Step 1 run configuration on a device or emulator.

The app runs and displays a screen similar to the following screenshot:

  1. Rotate the screen and notice that the timer resets!

You now need to update the app to persist state across screen rotations. You can use a ViewModel because instances of this class survive configuration changes, such as screen rotation.

In this step, you use a ViewModel to persist state across screen rotations and address the behaviour you observed in the previous step. In the previous step, you ran an activity that displays a timer. This timer is reset when a configuration change, such as screen rotation, destroys an activity.

You can use a ViewModel to retain data across the entire lifecycle of an activity or a fragment. As the previous step demonstrates, an activity is a poor choice to manage app data. Activities and fragments are short-lived objects which are created and destroyed frequently as a user interacts with an app. A ViewModel is also better suited to managing tasks related to network communication, as well as data manipulation and persistence.

Persisting the state of the Chronometer using a ViewModel

Open ChronoActivity2 and examine how the class retrieves and uses a ViewModel:

ChronometerViewModel chronometerViewModel
        = ViewModelProviders.of(this).get(ChronometerViewModel.class);

this refers to an instance of LifecycleOwner. The framework keeps the ViewModel alive as long as the scope of the LifecycleOwner is alive. A ViewModel is not destroyed if its owner is destroyed for a configuration change, such as screen rotation. The new instance of the owner re-connects to the existing ViewModel, as illustrated by the following diagram:

Try it out

Run the app and confirm the timer doesn't reset when you perform either of the following actions:

  1. Rotate the screen.
  2. Navigate to another app and then return.

However, if you or the system exit the app, then the timer resets.

In this step, you replace the chronometer used in previous steps with a custom one which uses a Timer, and updates the UI every second. A Timer is a java.util class that you can use to recurrently schedule tasks in the future. You add this logic to the LiveDataTimerViewModel class, and leave the activity to focus on managing the interaction between the user and the UI.

The activity updates the UI when the timer notifies it to. To help avoid memory leaks, the ViewModel doesn't include references to the activity. For example, a configuration change, such as a screen rotation, might result in references in a ViewModel to an activity that should be garbage collected. The system retains instances of ViewModel until the corresponding activity or lifecycle owner no longer exists.

Instead of modifying views directly from the ViewModel, you configure an activity or fragment to observe a data source, receiving the data when it changes. This arrangement is called the observer pattern.

You may be familiar with the observer pattern if you've used the Data Binding Library, or other reactive libraries like RxJava. LiveData is a special observable class which is lifecycle-aware, and only notifies active observers.

LifecycleOwner

ChronoActivity3 is an instance of LifecycleActivity, which can provide the state of a lifecycle. This is the class declaration:

public class LifecycleActivity extends FragmentActivity implements LifecycleRegistryOwner {...}

The LifecycleRegistryOwner is used to bind the lifecycle of instances of ViewModel and LiveData to the activity or fragment. The equivalent class for fragments is LifecycleFragment.

Update ChronoActivity

1. Add the following code to the ChronoActivity3 class, in the subscribe() method, to create the subscription:

mLiveDataTimerViewModel.getElapsedTime().observe(this, elapsedTimeObserver);

2. Next, set the new elapsed time value in the LiveDataTimerViewModel class. Find the following comment:

//TODO set the new value

Replace the comment with the following statement:

mElapsedTime.setValue(newValue);

3. Run the app and open the Android Monitor in Android Studio. Notice that the log updates every second unless you navigate to another app. If your device supports multi-window mode, you may like to try using it. Rotating the screen does not affect how the app behaves.

Many Android components and libraries require you to:

  1. Subscribe, or initialize the component or library.
  2. Unsubscribe, or stop the component or library.

Failing to complete the steps above can lead to memory leaks and subtle bugs.

A lifecycle owner object can be passed to new instances of lifecycle-aware components, to ensure they're aware of the current state of a lifecycle.

You can query the current state of a lifecycle using the following statement:

lifecycleOwner.getLifecycle().getCurrentState()

The statement above returns a state, such as Lifecycle.State.RESUMED, or Lifecycle.State.DESTROYED.

A lifecycle-aware object that implements LifecycleObserver can also observe changes in the state of a lifecycle owner:

lifecycleOwner.getLifecycle().addObserver(this);

You can annotate the object to instruct it to call the appropriate methods when required:

@OnLifecycleEvent(Lifecycle.EVENT.ON_RESUME)
void addLocationListener() { ... }

Create a lifecycle-aware component

In this step, you create a component that reacts to an activity lifecycle owner. Similar principles and steps apply when using a fragment as the lifecycle owner.

You use the Android framework's LocationManager to get the current latitude and longitude and display them to the user. This addition allows you to:

You would typically subscribe a LocationManager to changes in either the onStart() or onResume() methods of an activity, and remove the listener in the onStop() or onPause() methods:

// Typical use, within an activity.

@Override
protected void onResume() {
    mLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, mListener);
}

@Override
protected void onPause() {
    mLocationManager.removeUpdates(mListener);
}

In this step, you use an implementation of LifecycleOwner called LifecycleRegistryOwner in a class called BoundLocationManager. The name of the BoundLocationManager class refers to the fact that instances of the class bind to the activity's lifecycle.

For the class to observe the activity's lifecycle, you must add it as an observer. To accomplish this, instruct the BoundLocationManager object to observe the lifecycle by adding the following code to its constructor:

lifecycleOwner.getLifecycle().addObserver(this);

To call a method when a lifecycle change occurs, you can use the @OnLifecycleEvent annotation. Update the addLocationListener() and removeLocationListener() methods with the following annotations in the BoundLocationListener class:

@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
void addLocationListener() {
    ...
}
@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
void removeLocationListener() {
    ...
}

Run the app and verify that the Log Monitor displays the following actions, when you rotate the device:

D/BoundLocationMgr: Listener added
D/BoundLocationMgr: Listener removed
D/BoundLocationMgr: Listener added
D/BoundLocationMgr: Listener removed

Use the Android Emulator to simulate changing the location of the device (click the three dots to show the extended controls). The TextView is updated when it changes:

Share a ViewModel between fragments

Complete the following additional steps using a ViewModel to enable communication between fragments and the following:

Run this step and notice two instances of SeekBar which are independent of each other:

Connect the fragments with a ViewModel so that when one SeekBar is changed, the other SeekBar is updated:

There's no step-by-step manual for this exercise but you can find a solution in the step5_solution package.

All rights reserved. Java is a registered trademark of Oracle and/or its affiliates.