Android Lifecycle
Understanding the Android lifecycle and responding correctly to state changes is crucial to building apps with fewer bugs that use fewer resources and provide a good user experience. By Denis Buketa.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Android Lifecycle
25 mins
- Getting Started
- Understanding the Role of the Lifecycle in Apps
- Exploring the Activity Lifecycle
- Understanding Activity Lifecycle Callbacks
- Saving and Restoring the Activity Instance State
- Saving Instance State
- Restoring Instance State
- Passing Data Between Screens
- Understanding Intent Data
- Exploring Fragment Lifecycle
- Understanding Fragment Lifecycle Callbacks
- Using ViewModel to Store UI Data
- Where to Go From Here?
Saving and Restoring the Activity Instance State
If you’ve played a bit with the app, you might’ve noticed a couple bugs. Increase the counters by tapping the cards on the Main screen.
Now, rotate the device to change the screen orientation. If your device’s display has auto-rotate enabled, you’ll see something like this:
The counter’s state got lost during the screen orientation change. Inspect the logs:
You can see that when the screen orientation change started, the app destroyed portrait activity, then created and resumed the new landscape activity. Since in MainActivity.kt you don’t have any logic for saving and restoring the counter state, it was lost during this process.
You’ll fix that next! :]
Saving Instance State
Open MainActivity.kt and add the following code:
override fun onSaveInstanceState(outState: Bundle) {
Timber.i("PuppyCounter - MainActivity - onSaveInstanceState()")
// Save the dog count state
outState.putParcelable(STATE_DOG_COUNT, dogCount)
// Always call the superclass so it can save the view hierarchy state
super.onSaveInstanceState(outState)
}
As an activity begins to stop, the OS calls onSaveInstanceState()
for the activity to save any state information to an instance state bundle. Some Android views handle that by default — EditText
for the text and ListView
for the scroll position.
onSaveInstanceState()
isn’t called when the user explicitly closes the activity or in cases when finish()
is called.
To save dogCount
‘s state, you’ve overridden onSaveInstanceState()
and you stored the state to Bundle
as a key-value pair using outState.putParcelable()
. For the key, you used STATE_DOG_COUNT
, which was already defined in the class.
Check DogCount
class. You’ll notice that it implements Parcelable
. In case you’re not familiar with Parcelable
, it’s an interface conceptually similar to Java Serializable
. Classes that implement Parcelable
can be written to and restored from a Parcel
, which is designed for high-performance IPC transport. To put it simply, it allows you to store simple data structures in Bundle
.
Restoring Instance State
Good! You now have the logic for storing the state, but that isn’t so useful unless you have the logic for retrieving it. In MainActivity.kt, add the following code below onSaveInstanceState()
:
override fun onRestoreInstanceState(savedInstanceState: Bundle) {
Timber.i("PuppyCounter - MainActivity - onRestoreInstanceState()")
// Always call the superclass so it can restore the view hierarchy
super.onRestoreInstanceState(savedInstanceState)
dogCount = savedInstanceState.getParcelable(STATE_DOG_COUNT) ?: DogCount()
}
Any state that you save in onSaveInstanceState()
, you can restore in onRestoreInstanceState()
. onRestoreInstanceState()
receives Bundle
that contains key-value pairs that you can read. Here, you used savedInstanceState.getParcelable()
to retrieve DogCount
‘s state. Notice that you used the same key that you used for saving the state: STATE_DOG_COUNT
.
onRestoreInstanceState()
after onStart()
callback only if it has a saved state to restore. You can also restore the state in onCreate()
because that callback receives the same Bundle
.
Build and run your app. Increase the counters and change the orientation:
Also check the logs if you’re interested in when each callback invocation occurred:
onSaveInstanceState()
and onRestoreInstanceState()
with activity lifecycle callbacks. The OS invokes these methods only if there’s a case for it.
Excellent! Now that you’ve fixed one bug in the app, it’s time to move to the next one. :]
Passing Data Between Screens
Increase the counters in the Main screen and then open the Share screen. You’ll notice that the Share screen’s values don’t match those in the Main screen.
In MainActivity.kt, modify showShareScreen()
like this:
private fun showShareScreen() {
Timber.i("PuppyCounter - MainActivity - start ShareActivity Intent")
val intent = ShareActivity.createIntent(this)
// Store DogCount state to the intent
intent.putExtra(ShareActivity.EXTRA_DOG_COUNT, dogCount)
startActivity(intent)
}
With this code, you store DogCount
‘s state in Intent
. Here, you’re using an approach similar to what you saw in the previous section. OK, this will pass the data to the ShareActivity, but you still have to add the logic to retrieve it.
In ShareActivity.kt, add the following method:
private fun readExtras() = intent.extras?.run {
Timber.i("PuppyCounter - ShareActivity - readExtras()")
dogCount = getParcelable(EXTRA_DOG_COUNT) ?: DogCount()
}
This method retrieves the Intent
object that started this activity and tries to get the extra data that was passed with it. In this particular case, it’ll try to retrieve DogCount
‘s state.
To complete the retrieving logic, invoke this method in onCreate()
in ShareActivity.kt:
override fun onCreate(savedInstanceState: Bundle?) {
Timber.i("PuppyCounter - ShareActivity - onCreate()")
super.onCreate(savedInstanceState)
setContentView(R.layout.layout_share)
findViews()
// Read extra data from the Intent
readExtras()
setOnShareBtnClickListener()
}
When retrieving the data from the intent, the best place to do it is in onCreate()
. That way, you have the time to set up the state before the activity resumes and the user starts interacting with it.
Great! Build and run your app. Increase the counters and then open the Share screen. You’ll see something like this:
Check the logs to see the lifecycles of both activities as you move from one screen to another.
Notice how the OS creates ShareActivity just after MainActivity‘s onPause()
call. As mentioned before, the app calls onStop()
when activity is no longer visible to the user. After MainActivity‘s onPause()
call, you can see the series of ShareActivity‘s lifecycle callbacks that include reading intent data. When resumed, ShareActivity is completely visible to the user and MainActivity‘s onStop()
can be called, followed by onSaveInstanceState()
.
Understanding Intent Data
Change the screen orientation on the Share screen and notice what happens. You’ll see that the app preserved dogCount
‘s state. How’s that possible if you haven’t implemented the logic to save and retrieve the instance state?
Check the logs! :]
You’re already familiar with how the state can be lost during a configuration change. In this case, notice how the readExtras() log is again present when the app creates the new ShareActivity. But if you check the code, you see that you print that log only if intent.extras
is different than null — or in other words, the intent contains some data.
The data that you pass with Intent
when starting a new activity is preserved when the activity is being recreated.
To wrap up this section, tap back while the screen is in landscape orientation and observe the logs once again.
ShareActivity is paused and old portrait MainActivity is destroyed. Then, new landscape MainActivity is created and resumed. Finally, the app calls ShareActivity‘s onStop()
and onDestroy()
.
Great job getting to this point! Now that you understand the activity lifecycle and how to correctly manage the activity state, it’s time to move on to fragments. :]