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:
- ViewModel - provides a way to create and retrieve objects that are bound to a specific lifecycle. A
ViewModel
typically stores the state of a view's data and communicates with other components, such as data repositories or the domain layer which handles business logic.
To read an introductory guide to this topic, see ViewModel. - LifecycleOwner -
LifecycleOwner
is an interface implemented by theAppCompatActivity
andFragment
classes. You can subscribe other components to owner objects which implement this interface, to observe changes to the lifecycle of the owner.
To read an introductory guide to this topic, see Handling Lifecycles. - LiveData - allows you to observe changes to data across multiple components of your app without creating explicit, rigid dependency paths between them.
LiveData
respects the complex lifecycles of your app components, including activities, fragments, services, or anyLifecycleOwner
defined in your app.LiveData
manages observer subscriptions by pausing subscriptions to stoppedLifecycleOwner
objects, and cancelling subscriptions toLifecycleOwner
objects that are finished.
To read an introductory guide to this topic, see LiveData.
Prerequisites
This codelab has been designed for Android developers with some basic experience. To proceed, you must be familiar with the Android activity lifecycle.
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
- Android Studio 3.5 or greater.
In this step, you download the code for the entire codelab and then run a simple example app.
$ git clone git@github.com:googlecodelabs/android-lifecycles.git
Alternatively you can download the repository as a Zip file:
Once you have the code:
- Open the project Android Studio version 2.5 or newer.
- Copy over the Android Studio run configurations that will be used in this codelab. From the code root directory:
MacOS / Linux:
mkdir -p .idea/runConfigurations
cp runConfigurations/* .idea/runConfigurations/
Windows:
MKDIR .idea\runConfigurations
COPY runConfigurations\* .idea\runConfigurations\
Once you have run the command, you should see run configurations for each step:
- Run the Step 1 run configuration on a device or emulator.
The app runs and displays a screen similar to the following screenshot:
- 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
= new ViewModelProvider(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 (choose Step 2 in the Run Configurations dropdown) and confirm the timer doesn't reset when you perform either of the following actions:
- Rotate the screen.
- 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 schedule recurrent tasks. 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 notified by the timer. 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 AppCompatActivity
which is a subclass of ComponentActivity
. Take a look at the reference documentation and notice that ComponentActivity
implements LifecycleOwner
. LifecycleOwner
is an interface that is used by any class that has an Android lifecycle. Practically, it means that the class has a Lifecycle
object that represents its lifecycle.
Both ViewModel
and LiveData
can bind to a Lifecycle
.
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 post the new value with LiveData.postValue()
Replace the comment with the following statement:
mElapsedTime.postValue(newValue);
3. Run the app and open Logcat 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:
- Subscribe, or initialize the component or library.
- 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:
- Subscribe to changes and automatically update the UI using
LiveData
. - Create a wrapper of the
LocationManager
that registers and unregisters, based on changes to the status of the activity.
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'll update a class called BoundLocationManager
to be lifecycle-aware: it will bind to, observe and react to changes in a LifecycleOwner
.
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:
- A single activity.
- Two instances of a fragment, each one with a
SeekBar
. - A single
ViewModel
with aLiveData
field.
Run this step (Select run "Step 5" in run configurations) 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.
As the system runs low on memory, it kills processes in the cache beginning with the process least recently used. When the user navigates back to the app, the system will restart the app in a new process.
Since this only happens if the user has not interacted with the app for a while, it might be permissible to have them return to the app and find it in the initial state. However, there are cases where you might want to save the state of the app or part of it, so that that information is not lost if the process happens to be killed.
The lifecycle-viewmodel-savedstate
module provides access to the saved state in ViewModels.
The gradle dependency for this module is
"androidx.lifecycle:lifecycle-viewmodel-savedstate:$savedStateVersion"
Once you have the dependency, as long as you are using a default Fragment or Activity, you'll have access to a SavedStateHandle in your ViewModel. A SavedStateHandle
is a key-value mapping that survives process death.
First, let's try out step 6 without these changes:
1. Open run configuration "Step 6"
You will see a simple form:
2. Change the name and click "SAVE". This will store it in a LiveData inside the ViewModel.
3. Simulate the system killing the process (requires emulator running P+). First make sure the process is running by typing:
$ adb shell ps -A |grep lifecycle
This should output the running process with name com.example.android.codelabs.lifecycle
Press Home on your device or emulator and then run:
$ adb shell am kill com.example.android.codelabs.lifecycle
If you then type again
$ adb shell ps -A |grep lifecycle
You should get nothing, indicating that the process has been killed correctly.
4. Open the app again (Look for LC Step6 in your app launcher).
The value in the ViewModel was not persisted however the EditText
restored its state. How is this possible?
Actually, the lifecycle-viewmodel-savedstate
module also uses onSaveInstanceState
and onRestoreInstanceState
to persist the ViewModel state but it makes these operations more convenient.
Implement a saved state for ViewModels
In the SavedStateViewModel.java
file you need to add a new constructor that takes a SavedStateHandle
and store the state in a private field:
private SavedStateHandle mState; public SavedStateViewModel(SavedStateHandle savedStateHandle) { mState = savedStateHandle; }
Now you'll use the module's LiveData support so you don't need to store and expose a LiveData in your ViewModel anymore. Replace the existing getter and saveNewName
with:
private static final String NAME_KEY = "name"; // Expose an immutable LiveData LiveData<String> getName() { return mState.getLiveData(NAME_KEY); } void saveNewName(String newName) { mState.set(NAME_KEY, newName); }
Now that you're using LiveData from mState
, the MutableLiveData name isn't used anymore and can be removed.
Now you can try the same process again. Open the app, change the name and save it. Then, press Home and kill the process with:
$ adb shell am kill com.example.android.codelabs.lifecycle
If you reopen the app, you'll see that the state in the ViewModel has been saved this time.
With the SavedStateHandler you can save and restore primitives, Bundles, Parcelables, Serializables and other types of data. Check out the Saved State module for ViewModel documentation for the acceptable classes and more details.
You've learned how to introduce lifecycle-aware architecture components to your Android apps. Continue to learn more about Android architecture components at developer.android.com.
All rights reserved. Java is a registered trademark of Oracle and/or its affiliates.