The previous chapters have discussed in detail the different states and lifecycles of the activities comprising an Android application. In this chapter, we will put the theory of handling activity state changes into practice by creating an example application. The purpose of this example application is to provide a real-world demonstration of an activity as it passes through various states within the Android runtime. In the next chapter, entitled Saving and Restoring the State of an Android Activity, the example project constructed in this chapter will be extended to demonstrate the saving and restoration of dynamic activity state.
Creating the State Change Example Project
The first step in this exercise is to create a new project. Launch Android Studio and, if necessary, close any currently open projects using the File -> Close Project menu option so that the Welcome screen appears.
Select the New Project option from the welcome screen and, within the resulting new project dialog, choose the Empty Views Activity template before clicking on the Next button.
Enter StateChange into the Name field and specify com.ebookfrenzy.statechange as the package name. Before clicking on the Finish button, change the Minimum API level setting to API 26: Android 8.0 (Oreo) and the Language menu to Kotlin. Upon completing the project creation process, the StateChange project should be listed in the Project tool window located along the left-hand edge of the Android Studio main window. Use the steps outlined in An Overview of Android View Binding to convert the project to use view binding.
The next action to take involves the design of the user interface for the activity. This is stored in a file named activity_main.xml which should already be loaded into the Layout Editor tool. If it is not, navigate to it in the Project tool window where it can be found in the app -> res -> layout folder. Once located, double-clicking on the file will load it into the Android Studio Layout Editor tool.
Designing the User Interface
With the user interface layout loaded into the Layout Editor tool, it is time to design the user interface for the example application. Instead of the “Hello World!” TextView currently in the user interface design, the activity requires an EditText view. Select the TextView object in the Component Tree panel and press the Delete key on the keyboard to remove it from the design.
From the Palette located on the left side of the Layout Editor, select the Text category and, from the list of text components, click and drag a Plain Text component over to the layout canvas. Move the component to the center of the display so that the center guidelines appear and drop it into place so that the layout resembles that of Figure 21-2.
When using the EditText widget, it is necessary to specify an input type for the view. This simply defines the type of text or data the user will enter. For example, if the input type is set to Phone, the user will be restricted to entering numerical digits into the view. Alternatively, if the input type is set to TextCapCharacters, the input will default to upper-case characters. Input type settings may also be combined.
For this example, we will use the default input type to support general text input. To choose a different setting in the future, select the EditText widget in the layout and locate the inputType entry within the Attributes tool window. Next, click the flag icon to the left of the current setting to open the list of options, as shown in Figure 21-3 below. The Type menu provides options to restrict the input to text, numbers, dates and times, and phone numbers. The Variations menu provides additional options for the currently selected input type. For example, a variation is available for the text input type for email addresses as input.
Once a type and variation have been chosen, the input type may be customized further using the list of flag checkboxes:
Remaining in the Attributes tool window, change the view’s id to editText and click on the Refactor button in the resulting dialog.
By default, the EditText displays text which reads “Name”. Remaining within the Attributes panel, delete this from the text property field so that the view is blank within the layout. Before continuing, click the Infer Constraints button in the layout editor toolbar to add any missing constraints.
Overriding the Activity Lifecycle Methods
At this point, the project contains a single activity named MainActivity, derived from the Android AppCompatActivity class. The source code for this activity is contained within the MainActivity.kt file, which should already be open in an editor session and represented by a tab in the editor tab bar. If the file is no longer open, navigate to it in the Project tool window panel (app -> kotlin+java -> com.ebookfrenzy.statechange -> MainActivity) and double-click on it to load the file into the editor.
So far the only lifecycle method overridden by the activity is the onCreate() method which has been implemented to call the superclass instance of the method before setting up the user interface for the activity. We will now modify this method to output a diagnostic message in the Android Studio Logcat panel each time it executes. For this, we will use the Log class, which requires that we import android.util.Log and declare a tag that will enable us to filter these messages in the log output:
package com.ebookfrenzy.statechange
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import com.ebookfrenzy.statechange.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private val TAG = "StateChange"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
Log.i(TAG, "onCreate")
}
}
.
.
Code language: Kotlin (kotlin)
The next task is to override more methods, each containing a corresponding log call. These override methods may be added manually or generated using the Alt-Insert keyboard shortcut as outlined in the chapter entitled The Basics of the Android Studio Code Editor. Note that the Log calls will still need to be added manually if the methods are being auto-generated:
override fun onStart() {
super.onStart()
Log.i(TAG, "onStart")
}
override fun onResume() {
super.onResume()
Log.i(TAG, "onResume")
}
override fun onPause() {
super.onPause()
Log.i(TAG, "onPause")
}
override fun onStop() {
super.onStop()
Log.i(TAG, "onStop")
}
override fun onRestart() {
super.onRestart()
Log.i(TAG, "onRestart")
}
override fun onDestroy() {
super.onDestroy()
Log.i(TAG, "onDestroy")
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
Log.i(TAG, "onSaveInstanceState")
}
override fun onRestoreInstanceState(savedInstanceState: Bundle) {
super.onRestoreInstanceState(savedInstanceState)
Log.i(TAG, "onRestoreInstanceState")
}
Code language: JavaScript (javascript)
Filtering the Logcat Panel
The purpose of the code added to the overridden methods in MainActivity.kt is to output logging information to the Logcat tool window, which is displayed using the button shown in Figure 21-4:
The Logcat tool window can be configured to display all events relating to the device or emulator session or restricted to those events that relate to the currently selected app. The output can also be restricted to only those log events that match a specified filter.
When displayed while the current app is running, the Logcat tool window will appear as shown in Figure 21-5 below:
The menu marked A in the above figure allows you to select the device or emulator for which log output will be displayed. This output appears in the output panel marked C. The log output can be filtered by entering options into the field marked B. The default key setting, package:mine, restricts the output to log messages generated by the current app package (in this case com.ebookfrenzy.statechange). Leaving this field blank will allow log output from the selected device or emulator to be displayed, including diagnostic messages generated by the operating system. Keys may also be combined to filter the output further. For example, we can configure the Logcat panel to display only messages associated with our StateChange tag as follows:
package:mine tag:StateChange
Code language: plaintext (plaintext)
We can exclude output by prefixing the key with a minus (-) sign. In addition to the StateChange tag, we might have diagnostic messages using a different tag. To filter the log so that output from this second tag is excluded, we could enter the following key options:
package:mine tag:StateChange -tag:OtherTag
Code language: plaintext (plaintext)
In addition to your own tag values, it is also possible to select from a range of predefined diagnostic tags built into Android. Logcat will display a list of matching tags as you type into the filter field, as shown in Figure 21-6:
Alternatively, use Ctrl-Space to access a complete list of filtering suggestions.
The level key may be used to control which messages are displayed based on severity. To filter out all messages except error messages, the following key would be used:
level:error
Code language: plaintext (plaintext)
In addition to error, the Logcat panel supports verbose, info, warn, and assert level settings.
Logcat also supports multiple log panels, each with its own filter settings. To add another panel, click on the + button marked D in Figure 21-5 above. Switch between different panels using the corresponding tabs, or display them side-by-side by right-clicking on the currently displayed panel and selecting either the Split-Right or SplitDown menu option to arrange the panels horizontally or vertically. To rename a panel, right-click on the tab and select the Rename Tab option. Before proceeding, close all but one Logcat panel and configure the filter as follows:
package:mine tag:StateChange
Code language: plaintext (plaintext)
Running the Application
For optimal results, the application should be run on a physical Android device or emulator. With the device configured and connected to the development computer, click on the run button in the Android Studio toolbar as shown in Figure 21-7 below:
Select the physical Android device or emulator from the Choose Device dialog if it appears (assuming you have not already configured it as the default target). After Android Studio has built the application and installed it on the device, it should start up and be running in the foreground.
A review of the Logcat panel should indicate which methods have so far been triggered:
Experimenting with the Activity
With the diagnostics working, it is time to exercise the application to understand the activity lifecycle state changes. To begin with, consider the initial sequence of log events in the Logcat panel:
onCreate
onStart
onResume
Code language: plaintext (plaintext)
Clearly, the initial state changes are exactly as outlined in Android App and Activity Lifecycles. Note, however, that a call was not made to onRestoreInstanceState() since the Android runtime detected that there was no state to restore in this situation.
Tap on the Home icon in the bottom status bar on the device display and note the sequence of method calls reported in the log as follows:
onPause
onStop
onSaveInstanceState
Code language: plaintext (plaintext)
In this case, the runtime has noticed that the activity is no longer in the foreground, is not visible to the user, and has stopped the activity, but not without providing an opportunity for the activity to save the dynamic state. Depending on whether the runtime ultimately destroyed the activity or restarted it, the activity will either be notified it has been restarted via a call to onRestart() or will go through the creation sequence again when the user returns to the activity.
As outlined in Android App and Activity Lifecycles, the destruction and recreation of an activity can be triggered by making a configuration change to the device, such as rotating from portrait to landscape. To see this in action, rotate the device while the StateChange application is in the foreground. When using the emulator, device rotation may be simulated using the rotation button located in the emulator toolbar. To complete the rotation, it may also be necessary to tap on the rotation button. This appears at the bottom of the device or emulator screen, as shown in Figure 21-9:
The resulting sequence of method calls in the log should read as follows:
onPause
onStop
onSaveInstanceState
onDestroy
onCreate
onStart
onRestoreInstanceState
onResume
Code language: plaintext (plaintext)
Clearly, the runtime system has allowed the activity to save the state before being destroyed and restarted.
Summary
The adage that a picture is worth a thousand words holds just as true for examples when learning a new programming paradigm. In this chapter, we created an example Android application to demonstrate the different lifecycle states an activity will likely pass through. While developing the project in this chapter, we also looked at a mechanism for generating diagnostic logging information from within an activity.
In the next chapter, we will extend the StateChange example project to demonstrate how to save and restore an activity’s dynamic state.