The lifecycle demo app
In this section, we will do a quick experiment that will help to familiarize us with the lifecycle functions that our app uses and give us a chance to play around with a bit more Kotlin code.
Follow these steps to start a new project and then we can add some code:
- Start a new project and choose the Basic Activity project template; this is because during this project, we will also look at the functions that control the app menu and the Empty Activity option doesn't generate a menu.
- Call it Lifecycle Demo. The code is in the download bundle in the
Chapter06/Lifecycle Demo
folder, should you wish to refer to it or copy and paste it. - Keep the other settings as they have been in all our example apps so far.
- Wait for Android Studio to generate the project files and then open the
MainActivity.kt
file in the code editor (if it is not opened for you by default) by left-clicking on the MainActivity tab above the editor.
We will only need the MainActivity.kt
file for this demonstration as we will not be building a UI.
Coding the lifecycle demo app
In the MainActivity.kt
file, find the onCreate
function and add two lines of code just before the closing curly brace (}
), which marks the end of the onCreate
function:
Toast.makeText(this, "In onCreate", Toast.LENGTH_SHORT).show() Log.i("info", "In onCreate")
Tip
Remember that you will need to use the Alt + Enter keyboard combination twice to import the classes needed for Toast
and Log
.
After the closing curly brace (}
) of the onCreate
function, leave one clear line and add the following five lifecycle functions and their contained code. Note that it doesn't matter in what order we add our overridden functions; Android will call them in the correct order, regardless of the order in which we type them:
override fun onStart() { // First call the "official" version of this function super.onStart() Toast.makeText(this, "In onStart", Toast.LENGTH_SHORT).show() Log.i("info", "In onStart") } override fun onResume() { // First call the "official" version of this function super.onResume() Toast.makeText(this, "In onResume", Toast.LENGTH_SHORT).show() Log.i("info", "In onResume") } override fun onPause() { // First call the "official" version of this function super.onPause() Toast.makeText(this, "In onPause", Toast.LENGTH_SHORT).show() Log.i("info", "In onPause") } override fun onStop() { // First call the "official" version of this function super.onStop() Toast.makeText(this, "In onStop", Toast.LENGTH_SHORT).show() Log.i("info", "In onStop") } override fun onDestroy() { // First call the "official" version of this function super.onDestroy() Toast.makeText(this, "In onDestroy", Toast.LENGTH_SHORT).show() Log.i("info", "In onDestroy") }
First, let's talk about the code itself. Notice that the function names all correspond to the lifecycle functions and their related phases, which we discussed earlier in this chapter. Notice that all the function declarations are preceded by the override
keyword. Also, see that the first line of code inside each function is super.on...
.
The following explains exactly what is going on:
- Android calls our functions at the various times that we have already discussed.
- The
override
keyword shows that these functions replace or override the original version of the function that is provided as part of the Android API. Note that we don't see these replaced functions, but they are there, and if we didn't override them, these original versions will be called by Android instead of ours. - The
super.on...
code, which is the first line of code within each of the overridden functions, then calls these original versions. So, we don't simply override these original functions in order to add our own code; we also call them, and their code is executed too.
Note
For the eager reader, the super
keyword is for super-class. We will explore function overriding and super-classes more as we progress through this book.
Finally, the code that you added will make each of the functions output one Toast
message and one Log
message. However, the messages that are output vary, as can be seen with the text that is in between the double speech marks (""
). The messages that are output will make it clear which function produced them.
Running the lifecycle demo app
Now that we have looked at the code, we can play with our app and learn about the lifecycle from what happens:
- Run the app on either a device or an emulator.
- Watch the screen of the emulator and you will see the following appear one after the other as
Toast
messages: In onCreate, In onStart, and In onResume. - Notice the following messages in the logcat window; if there are too many messages, remember that you can filter them by setting the Log level dropdown to Info:
info:in onCreate info:in onStart info:in onResume
- Now tap the back button on the emulator or the device. Notice that you get the following three
Toast
messages in this exact order: In onPause, In onStop, and In onDestroy. Verify that we have matching output in the logcat window. - Next, run a different app. Perhaps run the Hello World app from Chapter 1, Getting Started with Android and Kotlin (but any app will do), by tapping its icon on the emulator or device screen.
- Now try opening the task manager on the emulator.
Tip
You can refer to Chapter 3, Exploring Android Studio and the Project Structure, and the Using the emulator as a real device section for how to do this on the emulator if you are unsure.
- You should now see all the recently run apps on the device.
- Tap on the lifecycle demo app and notice that the usual three starting messages are shown; this is because our app was previously destroyed.
- Now tap the task manager button again and switch to the Hello World app. Notice that this time, only the In onPause and In onStop messages are shown. Verify that we have matching output in the logcat window; this should tell us that the app has not been destroyed.
- Now, again using the task manager button, switch to the lifecycle demo app. You will see that only the In onStart and In onResume messages are shown, indicating that
onCreate
was not required to get the app running again. This is as expected because the app was not previously destroyed, but merely stopped.
Next, let's talk about what we saw when we ran the app.
Examining the lifecycle demo app output
When we started the lifecycle demo app for the first time, we saw that the onCreate
, onStart
, and onResume
functions were called. Then, when we closed the app using the back button, the onPause
, onStop
, and onDestroy
functions were called.
Furthermore, we know from our code that the original versions of all these functions are also called because we are calling them ourselves with the super.on...
code, which is the first thing we do in each of our overridden functions.
The quirk in our app's behavior came when we used the task manager to switch between apps and, when switching away from the lifecycle demo app, it was not destroyed and, subsequently, when switching back, it was not necessary to run onCreate
.
Note
Where's my Toast?
The opening three and closing three Toast
messages are queued by the operating system and the functions have already completed by the time they are shown. You can verify this by running the experiments again and see that all three starting and closing log messages are output before the second Toast
message is even shown. However, the Toast
messages do reinforce our knowledge about the order, if not the timing.
It is entirely possible (but not that likely) that you got slightly different results when you followed the preceding steps. What is for sure is that when our apps are run on thousands of different devices by millions of different users who have different preferences for interacting with their devices, Android will be calling the lifecycle functions at unpredictable times.
For example, what happens when the user exits the app by pressing the home button? If we open two apps one after the other, and then use the back button to switch to the earlier app, will that destroy or just stop the app? What happens when the user has a dozen apps in their task manager and the operating system needs to destroy some apps that were previously only stopped; will our app be one of the victims?
You can, of course, test out all the preceding scenarios on the emulator. But the results will only be true for the one time you test it. It is not guaranteed that the same behavior will be exhibited every time, and certainly not on every different Android device.
At last, there is some good news; the solution to all of this complexity is to follow a few simple rules:
- Set up your app so that it is ready to run in the
onCreate
function. - Load your user's data in the
onResume
function. - Save your user's data in the
onPause
function. - Tidy up your app and make it a good Android citizen in the
onDestroy
function. - Watch out for a couple of occasions in this book where we might like to use
onStart
andonStop
.
If we follow the preceding rules, we will see that, over the course of the book, we can simply stop worrying about the lifecycle and let Android handle it.
There are a few more functions that we can override as well; so, let's take a look at them.