Difference between revisions of "Kotlin - Handling Android Activity State Changes"

From Techotopia
Jump to: navigation, search
(Activity Lifetimes)
Line 57: Line 57:
  
  
[[File:as3.0_kotlin_state_change_main.png]]
+
[[File:as_3.0_class_hierarchy.png]
  
  

Revision as of 16:08, 24 November 2017

Based on the information outlined in the chapter entitled “Understanding Android Application and Activity Lifecycles” it is now evident that the activities that make up an application pass through a variety of different states during the course of the application’s lifespan. The change from one state to the other is imposed by the Android runtime system and is, therefore, largely beyond the control of the activity itself. That said, in most instances the runtime will provide the activity in question with a notification of the impending state change, thereby giving it time to react accordingly. In most cases, this will involve saving or restoring data relating to the state of the activity and its user interface.

The primary objective of this chapter is to provide a high-level overview of the ways in which an activity may be notified of a state change and to outline the areas where it is advisable to save or restore state information. Having covered this information, the chapter will then touch briefly on the subject of activity lifetimes.


Contents


The Activity Class

With few exceptions, activities in an application are created as subclasses of either the Android Activity class, or another class that is, itself, subclassed from the Activity class (for example the AppCompatActivity or FragmentActivity classes).

Consider, for example, the simple AndroidSample project created in “Creating an Example Android App in Android Studio”. Load this project into the Android Studio environment and locate the AndroidSampleActvity.kt file (located in app -> java -> com.<your domain>.androidsample). Having located the file, double-click on it to load it into the editor where it should read as follows:

package com.ebookfrenzy.androidsample
 
import android.os.Bundle
import android.support.design.widget.Snackbar
import android.support.v7.app.AppCompatActivity
import android.view.Menu
import android.view.MenuItem
 
import kotlinx.android.synthetic.main.activity_android_sample.*
 
class AndroidSampleActivity : AppCompatActivity() {
 
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_android_sample)
        setSupportActionBar(toolbar)
 
        fab.setOnClickListener { view ->
            Snackbar.make(view, "Replace with your own action", 
                 Snackbar.LENGTH_LONG)
                    .setAction("Action", null).show()
        }
    }
 
    override fun onCreateOptionsMenu(menu: Menu): Boolean {
        // Inflate the menu; this adds items to the action bar if it is present.
        menuInflater.inflate(R.menu.menu_android_sample, menu)
        return true
    }
 
    override fun onOptionsItemSelected(item: MenuItem): Boolean {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        return when(item.itemId) {
            R.id.action_settings -> true
            else -> super.onOptionsItemSelected(item)
        }
    }
}

When the project was created, we instructed Android Studio also to create an initial activity named AndroidSampleActivity. As is evident from the above code, the AndroidSampleActivity class is a subclass of the AppCompatActivity class.

A review of the reference documentation for the AppCompatActivity class would reveal that it is itself a subclass of the Activity class. This can be verified within the Android Studio editor using the Hierarchy tool window. With the AndroidSampleActivity.kt file loaded into the editor, click on AppCompatActivity in the class declaration line and press the Ctrl-H keyboard shortcut. The hierarchy tool window will subsequently appear displaying the class hierarchy for the selected class. As illustrated in Figure 19-1, AppCompatActivity is clearly subclassed from the FragmentActivity class which is itself ultimately a subclass of the Activity class:


[[File:as_3.0_class_hierarchy.png]


The Activity class and its subclasses contain a range of methods that are intended to be called by the Android runtime to notify an activity that its state is changing. For the purposes of this chapter, we will refer to these as the activity lifecycle methods. An activity class simply needs to override these methods and implement the necessary functionality within them in order to react accordingly to state changes.

One such method is named onCreate() and, turning once again to the above code fragment, we can see that this method has already been overridden and implemented for us in the AndroidSampleActivity class. In a later section we will explore in detail both onCreate() and the other relevant lifecycle methods of the Activity class.

Dynamic State vs. Persistent State

A key objective of Activity lifecycle management is ensuring that the state of the activity is saved and restored at appropriate times. When talking about state in this context we mean the data that is currently being held within the activity and the appearance of the user interface. The activity might, for example, maintain a data model in memory that needs to be saved to a database, content provider or file. Such state information, because it persists from one invocation of the application to another, is referred to as the persistent state. The appearance of the user interface (such as text entered into a text field but not yet committed to the application’s internal data model) is referred to as the dynamic state, since it is typically only retained during a single invocation of the application (and also referred to as user interface state or instance state).

Understanding the differences between these two states is important because both the ways they are saved, and the reasons for doing so, differ.

The purpose of saving the persistent state is to avoid the loss of data that may result from an activity being killed by the runtime system while in the background. The dynamic state, on the other hand, is saved and restored for reasons that are slightly more complex.

Consider, for example, that an application contains an activity (which we will refer to as Activity A) containing a text field and some radio buttons. During the course of using the application, the user enters some text into the text field and makes a selection from the radio buttons. Before performing an action to save these changes, however, the user then switches to another activity causing Activity A to be pushed down the Activity Stack and placed into the background. After some time, the runtime system ascertains that memory is low and consequently kills Activity A to free up resources. As far as the user is concerned, however, Activity A was simply placed into the background and is ready to be moved to the foreground at any time. On returning Activity A to the foreground the user would, quite reasonably, expect the entered text and radio button selections to have been retained. In this scenario, however, a new instance of Activity A will have been created and, if the dynamic state was not saved and restored, the previous user input lost.

The main purpose of saving dynamic state, therefore, is to give the perception of seamless switching between foreground and background activities, regardless of the fact that activities may actually have been killed and restarted without the user’s knowledge.

The mechanisms for saving persistent and dynamic state will become clearer in the following sections of this chapter.


The Android Activity Lifecycle Methods

As previously explained, the Activity class contains a number of lifecycle methods which act as event handlers when the state of an Activity changes. The primary methods supported by the Android Activity class are as follows:

  • onCreate(savedInstanceState: Bundle?) – The method that is called when the activity is first created and the ideal location for most initialization tasks to be performed. The method is passed an argument in the form of a Bundle object that may contain dynamic state information (typically relating to the state of the user interface) from a prior invocation of the activity.
  • onRestart() – Called when the activity is about to restart after having previously been stopped by the runtime system.
  • onStart() – Always called immediately after the call to the onCreate() or onRestart() methods, this method indicates to the activity that it is about to become visible to the user. This call will be followed by a call to onResume() if the activity moves to the top of the activity stack, or onStop() in the event that it is pushed down the stack by another activity.
  • onResume() – Indicates that the activity is now at the top of the activity stack and is the activity with which the user is currently interacting.
  • onPause() – Indicates that a previous activity is about to become the foreground activity. This call will be followed by a call to either the onResume() or onStop() method depending on whether the activity moves back to the foreground or becomes invisible to the user. Steps may be taken within this method to store persistent state information not yet saved by the app. To avoid delays in switching between activities, time consuming operations such as storing data to a database or performing network operations should be avoided within this method. This method should also ensure that any CPU intensive tasks such as animation are stopped.
  • onStop() – The activity is now no longer visible to the user. The two possible scenarios that may follow this call are a call to onRestart() in the event that the activity moves to the foreground again, or onDestroy() if the activity is being terminated.
  • onDestroy() – The activity is about to be destroyed, either voluntarily because the activity has completed its tasks and has called the finish() method or because the runtime is terminating it either to release memory or due to a configuration change (such as the orientation of the device changing). It is important to note that a call will not always be made to onDestroy() when an activity is terminated.
  • onConfigurationChanged() – Called when a configuration change occurs for which the activity has indicated it is not to be restarted. The method is passed a Configuration object outlining the new device configuration and it is then the responsibility of the activity to react to the change.

In addition to the lifecycle methods outlined above, there are two methods intended specifically for saving and restoring the dynamic state of an activity:

  • onRestoreInstanceState(savedInstanceState: Bundle?) – This method is called immediately after a call to the onStart() method in the event that the activity is restarting from a previous invocation in which state was saved. As with onCreate(), this method is passed a Bundle object containing the previous state data. This method is typically used in situations where it makes more sense to restore a previous state after the initialization of the activity has been performed in onCreate() and onStart().
  • onSaveInstanceState(outState: Bundle?) – Called before an activity is destroyed so that the current dynamic state (usually relating to the user interface) can be saved. The method is passed the Bundle object into which the state should be saved and which is subsequently passed through to the onCreate() and onRestoreInstanceState() methods when the activity is restarted. Note that this method is only called in situations where the runtime ascertains that dynamic state needs to be saved.

When overriding the above methods in an activity, it is important to remember that, with the exception of onRestoreInstanceState() and onSaveInstanceState(), the method implementation must include a call to the corresponding method in the Activity super class. For example, the following method overrides the onRestart() method but also includes a call to the super class instance of the method:

override fun onRestart() {
    super.onRestart()
    Log.i(TAG, "onRestart")
}

Failure to make this super class call in method overrides will result in the runtime throwing an exception during execution of the activity. While calls to the super class in the onRestoreInstanceState() and onSaveInstanceState() methods are optional (they can, for example, be omitted when implementing custom save and restoration behavior) there are considerable benefits to using them, a subject that will be covered in the chapter entitled “Saving and Restoring the State of an Android Activity”.

Activity Lifetimes

The final topic to be covered involves an outline of the entire, visible and foreground lifetimes through which an activity will transition during execution:

  • Entire Lifetime –The term “entire lifetime” is used to describe everything that takes place within an activity between the initial call to the onCreate() method and the call to onDestroy() prior to the activity terminating.
  • Visible Lifetime – Covers the periods of execution of an activity between the call to onStart() and onStop(). During this period the activity is visible to the user though may not be the activity with which the user is currently interacting.
  • Foreground Lifetime – Refers to the periods of execution between calls to the onResume() and onPause() methods.

It is important to note that an activity may pass through the foreground and visible lifetimes multiple times during the course of the entire lifetime.

The concepts of lifetimes and lifecycle methods are illustrated in Figure 19-2:


The Android lifetime and lifecycle diagram

Disabling Configuration Change Restarts

As previously outlined, an activity may indicate that it is not to be restarted in the event of certain configuration changes. This is achieved by adding an android:configChanges directive to the activity element within the project manifest file. The following manifest file excerpt, for example, indicates that the activity should not be restarted in the event of configuration changes relating to orientation or device-wide font size:

<activity android:name=".DemoActivity"
          android:configChanges="orientation|fontScale"
          android:label="@string/app_name">

Summary

All activities are derived from the Android Activity class which, in turn, contains a number of event methods that are designed to be called by the runtime system when the state of an activity changes. By overriding these methods, an activity can respond to state changes and, where necessary, take steps to save and restore the current state of both the activity and the application. Activity state can be thought of as taking two forms. The persistent state refers to data that needs to be stored between application invocations (for example to a file or database). Dynamic state, on the other hand, relates instead to the current appearance of the user interface.

In this chapter, we have highlighted the lifecycle methods available to activities and covered the concept of activity lifetimes. In the next chapter, entitled “Android Activity State Changes by Example”, we will implement an example application that puts much of this theory into practice.