Lab 7.1. Stages of The Activity Lifecycle
Lab 7.1. Stages of The Activity Lifecycle
1. Welcome
Introduction
In this codelab, you learn more about a fundamental part of Android: the activity. The activity
lifecycle is the set of states an activity can be in during its lifetime. The lifecycle extends from
when the activity is initially created to when it is destroyed and the system reclaims that
activity's resources. As a user navigates between activities in your app (and into and out of your
app), those activities each transition between different states in the activity lifecycle.
As an Android developer, you need to understand the activity lifecycle. If your activities do not
correctly respond to lifecycle state changes, your app could generate strange bugs, confusing
behavior for your users, or use too many Android system resources. Understanding the Android
lifecycle, and responding correctly to lifecycle state changes, is critical to being a good Android
citizen.
What you'll do
• Modify a starter app called DessertClicker to add logging information that's displayed in
the Logcat.
• Override lifecycle callback methods and log changes to the activity state.
• Run the app and note the logging information that appears as the activity is started,
stopped, and resumed.
• Implement the onSaveInstanceState() method to retain app data that may be lost if the
device configuration changes. Add code to restore that data when the app starts again.
2. App overview
In this codelab, you work with a starter app called DessertClicker. In this app, each time the user
taps a dessert on the screen, the app "purchases" the dessert for the user. The app updates values
in the layout for the number of desserts that were purchased, and for the total amount the user
spent.
This app contains several bugs related to the Android lifecycle: For example, in certain
circumstances, the app resets the dessert values to 0. Understanding the Android lifecycle will
help you understand why these problems happen, and how to fix them.
If you use the starter code from GitHub, note that the folder name is android-basics-kotlin-
dessert-clicker-app-starter. Select this folder when you open the project in Android
Studio.
To get the code for this codelab and open it in Android Studio, do the following.
3. In the Import Project dialog, navigate to where the unzipped project folder is located
(likely in your Downloads folder).
4. Double-click on that project folder.
5. Wait for Android Studio to open the project.
6. Click the Run button to build and run the app. Make sure it builds as expected.
7. Browse the project files in the Project tool window to see how the app is set-up.
Similarly, the activity lifecycle is made up of the different states that an activity can go through,
from when the activity is first initialized to when it is finally destroyed and its memory reclaimed
by the system. As the user starts your app, navigates between activities, navigates inside and
outside of your app, the activity changes state. The diagram below shows all the activity lifecycle
states. As their names indicate, these states represent the status of the activity.
Often, you want to change some behavior, or run some code when the activity lifecycle state
changes. Therefore the Activity class itself, and any subclasses of Activity such as
AppCompatActivity, implement a set of lifecycle callback methods. Android invokes these
callbacks when the activity moves from one state to another, and you can override those methods
in your own activities to perform tasks in response to those lifecycle state changes. The
following diagram shows the lifecycle states along with the available overridable callbacks.
It's important to know when these callbacks are invoked and what to do in each callback method.
But both of these diagrams are complex and can be confusing. In this codelab, instead of just
reading what each state and callback means, you're going to do some detective work and build
your understanding of what's going on.
A simple way to do that is to use the Android logging functionality. Logging enables you to
write short messages to a console while the app runs, and you can use it to show you when
different callbacks are triggered.
1. Run the Dessert Clicker app, and tap several times on the picture of the dessert. Note how
the value for Desserts Sold and the total dollar amount changes.
2. Open MainActivity.kt and examine the onCreate() method for this activity:
In the activity lifecycle diagram, you may have recognized the onCreate() method, because
you've used this callback before. It's the one method every activity must implement. The
onCreate() method is where you should do any one-time initializations for your activity. For
example, in onCreate() you inflate the layout, define click listeners, or set up view binding.
The onCreate() lifecycle method is called once, just after the activity is initialized (when the
new Activity object is created in memory). After onCreate() executes, the activity is
considered created.
Note: When you override the onCreate() method, you must call the superclass implementation
to complete the creation of the Activity, so within it, you must immediately call
super.onCreate(). The same is true for other lifecycle callback methods.
3. In the onCreate() method, just after the call to super.onCreate(), add the following
line:
4. Import the Log class if necessary (press Alt+Enter, or Option+Enter on a Mac, and
select Import.) If you enabled auto imports, this should happen automatically.
import android.util.Log
The Log class writes messages to the Logcat. The Logcat is the console for logging messages.
Messages from Android about your app appear here, including the messages you explicitly send
to the log with the Log.d() method or other Log class methods.
• The priority of the log message, that is, how important the message is. In this case, the
Log.d() method writes a debug message. Other methods in the Log class include
Log.i() for informational messages, Log.e() for errors, Log.w() for warnings, or
Log.v() for verbose messages.
• The log tag (the first parameter), in this case "MainActivity". The tag is a string that
lets you more easily find your log messages in the Logcat. The tag is typically the name
of the class.
• The actual log message (the second parameter), is a short string, which in this case is
"onCreate called".
and use that in subsequent calls to the log methods, like below:
A compile-time constant is a value that won't change. Use const before a variable declaration to
mark it as a compile-time constant.
5. Compile and run the DessertClicker app. You don't see any behavior differences in the
app when you tap the dessert. In Android Studio, at the bottom of the screen, click the
Logcat tab.
Your log message includes the date and time, the name of the package
(com.example.android.dessertclicker), your log tag (with D/ at the start), and the actual
message. Because this message appears in the log, you know that onCreate() has been
executed.
1. In Android Studio, with MainActivity.kt open and the cursor within the MainActivity
class, select Code > Override Methods or press Control+o (Command+o on Mac). A
dialog appears with a huge list of all the methods you can override in this class.
2. Start entering onStart to search for the right method. To scroll to the next matching
item, use the down arrow. Choose onStart() from the list, and click OK to insert the
boilerplate override code. The code looks like this:
override fun onStart() {
super.onStart()
}
3. Add the following constant at the top level of the MainActivity.kt, that is above the
class declaration, class MainActivity.
5. Compile and run the DessertClicker app, and open the Logcat pane. Type
D/MainActivity into the search field to filter the log. Notice that both the onCreate()
and onStart() methods were called one after the other, and that your activity is visible
on screen.
6. Press the Home button on the device, and then use the recents screen to return to the
activity. Notice that the activity resumes where it left off, with all the same values, and
that onStart() is logged a second time to Logcat. Notice also that the onCreate()
method is usually not called again.
Note: As you experiment with your device and observe the lifecycle callbacks, you might notice
unusual behavior when you rotate your device. You'll learn about that behavior later in this
codelab.
1. Override the remainder of the lifecycle methods in your MainActivity, and add log
statements for each one. Here's the code:
2. Compile and run DessertClicker again and examine Logcat. This time notice that in
addition to onCreate() and onStart(), there's a log message for the onResume()
lifecycle callback.
When an activity starts from scratch, you see all three of these lifecycle callbacks called in order:
Despite the name, the onResume() method is called at startup, even if there is nothing to resume.
4. Explore lifecycle use cases
Now that the DessertClicker app is set up for logging, you're ready to start using the app in
various ways, and ready to explore how the lifecycle callbacks are triggered in response to those
uses.
1. Compile and run the DessertClicker app, if it is not already running. As you've seen, the
onCreate(), onStart(), and onResume() callbacks are called when the activity starts
for the first time.
2020-10-16 10:27:33.244 22064-22064/com.example.android.dessertclicker
D/MainActivity: onCreate Called
2020-10-16 10:27:33.453 22064-22064/com.example.android.dessertclicker
D/MainActivity: onStart Called
2020-10-16 10:27:33.454 22064-22064/com.example.android.dessertclicker
D/MainActivity: onResume Called
In this case, using the Back button causes the activity (and the app) to be entirely closed. The
execution of the onDestroy() method means that the activity was fully shut down and can be
garbage-collected. Garbage collection refers to the automatic cleanup of objects that you'll no
longer use. After onDestroy() is called, the system knows that those resources are discardable,
and it starts cleaning up that memory.
Your activity may also be completely shut down if your code manually calls the activity's
finish() method, or if the user force-quits the app. (For example, the user can force-quit or
close the app in the recents screen.) The Android system may also shut down your activity on its
own if your app has not been on-screen for a long time. Android does this to preserve battery,
and to allow your app's resources to be used by other apps.
4. Return to the DessertClicker app by finding all open apps on the Overview screen. (Note
this is also known as the Recents screen or recent apps.) Here's the Logcat:
The activity was destroyed in the previous step, so when you return to the app, Android starts up
a new activity and calls the onCreate(), onStart(), and onResume() methods. Notice that
none of the DessertClicker logs from the previous activity has been retained.
Note: The key point here is that onCreate() and onDestroy() are only called once during the
lifetime of a single activity instance: onCreate() to initialize the app for the very first time, and
onDestroy() to clean up the resources used by your app.
The onCreate() method is an important step; this is where all your first-time initialization goes,
where you set up the layout for the first time by inflating it, and where you initialize your
variables.
Your activity does not close down entirely every time the user navigates away from that activity:
• When your activity is no longer visible on screen, this is known as putting the activity
into the background. (The opposite of this is when the activity is in the foreground, or on
screen.)
• When the user returns to your app, that same activity is restarted and becomes visible
again. This part of the lifecycle is called the app's visible lifecycle.
When your app is in the background, it generally should not be actively running, to preserve
system resources and battery life. You use the Activity lifecycle and its callbacks to know
when your app is moving to the background so that you can pause any ongoing operations. Then
you restart the operations when your app comes into the foreground.
In this step, you look at the activity lifecycle when the app goes into the background and returns
again to the foreground.
1. With the DessertClicker app running, click the cupcake a few times.
2. Press the Home button on your device and observe the Logcat in Android Studio.
Returning to the home screen puts your app into the background rather than shutting
down the app altogether. Notice that the onPause() method and onStop() methods are
called, but onDestroy() is not.
When onPause() is called, the app no longer has focus. After onStop(), the app is no longer
visible on screen. Although the activity has been stopped, the Activity object is still in memory,
in the background. The activity has not been destroyed. The user might return to the app, so
Android keeps your activity resources around.
3. Use the recents screen to return to the app. Notice in Logcat that the activity is restarted
with onRestart() and onStart(), then resumed with onResume().
When the activity returns to the foreground, the onCreate() method is not called again. The
activity object was not destroyed, so it doesn't need to be created again. Instead of onCreate(),
the onRestart() method is called. Notice that this time when the activity returns to the
foreground, the Desserts Sold number is retained.
4. Start at least one app other than DessertClicker so that the device has a few apps in its
recents screen.
5. Bring up the recents screen and open another recent activity. Then go back to recent apps
and bring DessertClicker back to the foreground.
Notice that you see the same callbacks in Logcat here as when you pressed the Home button.
onPause() and onStop() are called when the app goes into the background, and then
onRestart(), onStart(), and onResume() when it comes back.
Note: The important point here is that onStart() and onStop() are called multiple times as the
user navigates to and from the activity.
These methods are called when the app is stopped and moved into the background, or when the
app is started again when it returns to the foreground. If you need to do some work in your app
during these cases, then override the relevant lifecycle callback method.
So what about onRestart()? The onRestart() method is much like onCreate(). Either
onCreate() or onRestart() is called before the activity becomes visible. The onCreate()
method is called only the first time, and onRestart() is called after that. The onRestart()
method is a place to put code that you only want to call if your activity is not being started for
the first time.
When the app goes into the background, the focus is lost after onPause(), and the app is no
longer visible after onStop().
The difference between focus and visibility is important because it is possible for an activity to
be partially visible on the screen, but not have the user focus. In this step, you look at one case
where an activity is partially visible, but doesn't have user focus.
1. With the DessertClicker app running, click the Share button in the top right of the screen.
The sharing activity appears in the lower half of the screen, but the activity is still visible in the
top half.
2. Examine Logcat and note that only onPause() was called.
In this use case, onStop() is not called, because the activity is still partially visible. But the
activity does not have user focus, and the user can't interact with it—the "share" activity that's in
the foreground has the user focus.
Why is this difference important? The interruption with only onPause() usually lasts a short
time before returning to your activity or navigating to another activity or app. You generally
want to keep updating the UI so the rest of your app doesn't appear to freeze.
Whatever code runs in onPause() blocks other things from displaying, so keep the code in
onPause() lightweight. For example, if a phone call comes in, the code in onPause() may delay
the incoming-call notification.
3. Click outside the share dialog to return to the app, and notice that onResume() is called.
Both onResume() and onPause() have to do with focus. The onResume() method is called
when the activity has focus, and onPause() is called when the activity loses focus.
A configuration change happens when the state of the device changes so radically that the easiest
way for the system to resolve the change is to completely shut down and rebuild the activity. For
example, if the user changes the device language, the whole layout might need to change to
accommodate different text directions and string lengths. If the user plugs the device into a dock
or adds a physical keyboard, the app layout may need to take advantage of a different display
size or layout. And if the device orientation changes—if the device is rotated from portrait to
landscape or back the other way—the layout may need to change to fit the new orientation. Let's
look at how the app behaves in this scenario.
Notice that when the device or emulator rotates the screen, the system calls all the lifecycle
callbacks to shut down the activity. Then, as the activity is re-created, the system calls all the
lifecycle callbacks to start the activity.
4. When the device is rotated and the activity is shut down and re-created, the activity starts
up with default values—the number of desserts sold and the revenue have reset to zeroes.
Note: Sometimes Android shuts down an entire app process, which includes every activity
associated with the app. Android does this kind of shutdown when the system is stressed and in
danger of visually lagging, so no additional callbacks or code is run at this point. Your app's
process is simply shut down, silently, in the background. But to the user, it doesn't look like the
app has been closed. When the user navigates back to an app that the Android system has shut
down, Android restarts that app. You'll want to ensure that the user doesn't experience any data
loss when this happens.
Saving the data each time ensures that updated data in the bundle is available to restore, if it is
needed.
1. In MainActivity, override the onSaveInstanceState() callback, and add a log
statement.
Note: There are two overrides for onSaveInstanceState(), one with just an outState
parameter, and one that includes outState and outPersistentState parameters. Use the one
shown in the code above, with the single outState parameter.
2. Compile and run the app, and click the Home button to put it into the background. Notice
that the onSaveInstanceState() callback occurs just after onPause() and onStop():
3. At the top of the file, just before the class definition, add these constants:
You will use these keys for both saving and retrieving data from the instance state bundle.
outState.putInt(KEY_REVENUE, revenue)
The putInt() method (and similar methods from the Bundle class like putFloat() and
putString() takes two arguments: a string for the key (the KEY_REVENUE constant), and the
actual value to save.
outState.putInt(KEY_DESSERT_SOLD, dessertsSold)
Use onCreate() to restore bundle data
The Activity state can be restored in onCreate(Bundle) or
onRestoreInstanceState(Bundle) (the Bundle populated by onSaveInstanceState()
method will be passed to both lifecycle callback methods).
Notice that onCreate() gets a Bundle each time it is called. When your activity is restarted due
to a process shut down, the bundle that you saved is passed to onCreate(). If your activity was
starting fresh, this Bundle in onCreate() is null. So if the bundle is not null, you know you're
"re-creating" the activity from a previously known point.
Note: If the activity is being re-created, the onRestoreInstanceState() callback is called after
onStart(), also with the bundle. Most of the time, you restore the activity state in onCreate().
But because onRestoreInstanceState() is called after onStart(), if you ever need to restore
some state after onCreate() is called, you can use onRestoreInstanceState().
2. Add this code to onCreate(), just after the binding variable is set:
if (savedInstanceState != null) {
revenue = savedInstanceState.getInt(KEY_REVENUE, 0)
}
The test for null determines whether there is data in the bundle, or if the bundle is null, which
in turn tells you if the app has been started fresh or has been re-created after a shutdown. This
test is a common pattern for restoring data from the bundle.
Notice that the key you used here (KEY_REVENUE) is the same key you used for putInt(). To
make sure you use the same key each time, it is a best practice to define those keys as constants.
You use getInt() to get data out of the bundle, just as you used putInt() to put data into the
bundle. The getInt() method takes two arguments:
• A string that acts as the key, for example "key_revenue" for the revenue value.
• A default value in case no value exists for that key in the bundle.
The integer you get from the bundle is then assigned to the revenue variable, and the UI will use
that value.
3. Add getInt() methods to restore the revenue and the number of desserts sold.
if (savedInstanceState != null) {
revenue = savedInstanceState.getInt(KEY_REVENUE, 0)
dessertsSold = savedInstanceState.getInt(KEY_DESSERT_SOLD, 0)
}
4. Compile and run the app. Press the cupcake at least five times until it switches to a donut.
5. Rotate the device. Notice that this time the app displays the correct revenue and desserts
sold values from the bundle. But also notice that the dessert has returned to a cupcake.
There's one more thing left to do to ensure that the app returns from a shutdown exactly the way
it was left.
This method relies on the number of desserts sold to choose the right image. Therefore, you don't
need to do anything to store a reference to the image in the bundle in onSaveInstanceState().
In that bundle, you're already storing the number of desserts sold.
7. In onCreate(), in the block that restores the state from the bundle, call
showCurrentDessert():
if (savedInstanceState != null) {
revenue = savedInstanceState.getInt(KEY_REVENUE, 0)
dessertsSold = savedInstanceState.getInt(KEY_DESSERT_SOLD, 0)
showCurrentDessert()
}
8. Compile and run the app, and rotate the screen. Note now that both the values for desserts
sold, total revenue, and the dessert image are correctly restored.
6. Summary
Activity lifecycle
• The activity lifecycle is a set of states through which an activity migrates. The activity
lifecycle begins when the activity is first created and ends when the activity is destroyed.
• As the user navigates between activities and inside and outside of your app, each activity
moves between states in the activity lifecycle.
• Each state in the activity lifecycle has a corresponding callback method you can override
in your Activity class. The core set of lifecycle methods are:
onCreate()onStart()onPause()onRestart()onResume()onStop()onDestroy()
• To add behavior that occurs when your activity transitions into a lifecycle state, override
the state's callback method.
• To add skeleton override methods to your classes in Android Studio, select Code >
Override Methods or press Control+o.
Configuration changes
• A configuration change happens when the state of the device changes so radically that
the easiest way for the system to resolve the change is to destroy and rebuild the activity.
• The most common example of a configuration change is when the user rotates the device
from portrait to landscape mode, or from landscape to portrait mode. A configuration
change can also occur when the device language changes or a hardware keyboard is
plugged in.
• When a configuration change occurs, Android invokes all the activity lifecycle's
shutdown callbacks. Then Android restarts the activity from scratch, running all the
lifecycle startup callbacks.
• When Android shuts down an app because of a configuration change, it restarts the
activity with the state bundle that is available to onCreate().
• As with process shutdown, save your app's state to the bundle in
onSaveInstanceState().
7. Learn more
• Log class
• Write and View Logs with Logcat
• Compile-time constants
• Activity class
• AppCompatActivity class
• Activity Developer Guide