The Android Activity and Fragment lifecycles

Activities and fragments are core Android application components. An activity describes a unit of the application that is dedicated to a task, such as drafting an email. Meanwhile, a fragment is a module within an activity. For example, if you are designing a Music Player app, you might use an activity to coordinate the music library and have separate fragments within the activity for the Albums view, Artists view and Playlists view. A fragment must be tied to an activity, but an activity can exist without fragments. Activities and fragments also have lifecycles, which describe a sequence of state changes the components progress through from their creation until they are destroyed. An awareness of the lifecycle states for activities and fragments is critical for designing robust Android applications that perform effectively. In this tutorial, we will explore the purpose of each lifecycle stage, discuss how you can provide custom instructions for a given stage, and compare the relationship between the activity and fragment lifecycles.

The Android activity lifecycle

An Android activity progresses through various states from the moment it is opened until it is closed. The collection of states is called the activity lifecycle, and each state is associated with a different level of readiness for the activity and its components. Also, the lifecycle states are related to how much computational resources the device will allocate to the app, and hence the activity. For example, if the user switches to another app but leaves your app running in the background, then the activity from your app will enter the onPause and onStop stages of its lifecycle. In which case, the activity from your app will no longer be visible but could be resumed if the user returns to your app. However, imagine the user does not return for a while and other apps are required to perform more complex and urgent tasks. The device may determine that it does not have sufficient computational resources to keep idle apps running in the background. In which case, it may forcibly shut down apps such as yours and any activities that are in the onStop stage of their lifecycle. To better understand the Android activity lifecycle, a diagram and explanation of the different stages are included below:

android-activity-lifecycle-stages

Interacting with the Android activity lifecycle stages

In Android programming, you can interact with each stage of an activity's lifecycle and define additional instructions that should be carried out. To interact with a lifecycle stage, you simply need to override the Activity class method associated with the stage. Method overriding is a principle of object-oriented programming languages such as Kotlin and Java. In brief, method overriding allows you to customise the behaviour of a method that is inherited from another class or interface. You may notice the activities in your application often inherit data from an Activity subclass such as AppCompatActivity. The Activity class and its subclasses contain fundamental instructions that should be carried out at each activity lifecycle stage, but we can override these instructions using polymorphism. For example, you could override the onCreate stage for an activity by writing the following code:

private lateinit var binding: ActivityMainBinding

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    binding = ActivityMainBinding.inflate(layoutInflater)
    setContentView(binding.root)
}

In the above code, we use a super.onCreate call to run any processes that were defined in the superclass's onCreate method. In this way, we ensure that any critical lifecycle tasks that are defined in the Activity or AppCompatActivity classes continue to run as normal. Next, the above code initialises a variable called binding, which will hold a reference of a layout resource file's binding class. The layout is then set as the main content view for the activity, which is a common feature you will see when an activity class overrides the onCreate method because setting a content view is required for displaying the user interface.

You might also like to override other stages of the activity lifecycle to best fulfil the user's needs. For example, information about the application's state could be saved in the onPause stage. In the onPause stage, the activity is no longer in the foreground but the user could return shortly. For instance, imagine you were designing a music streaming app that allowed the user to toggle the repeat mode. You could store the user's repeat mode preference in the onPause stage as shown below so it can be retrieved when the user returns to the activity:

companion object {
    const val REPEAT_MODE_ALL = 1
    const val REPEAT_MODE_NONE = 0
    const val REPEAT_MODE_ONE = 2
}
    
private var activeRepeatMode = REPEAT_MODE_NONE

override fun onPause() {
    super.onPause()

    getSharedPreferences(localClassName, MODE_PRIVATE).edit().apply {
        putInt("repeat_mode", activeRepeatMode)
        apply()
    }
}

The Android fragment lifecycle

Activities can be divided into fragments. Each fragment represents a module within the activity, and each fragment can have a distinct lifecycle and user interface; however, the fragment's lifecycle is dependent on the parent activity's lifecycle. For example, if the parent activity enters the onDestroy stage and shuts down then the fragment will shut down also. A summary of the key fragment lifecycle stages is provided below. Some stages are similar to the activity lifecycle, while others are dedicated to the initialisation and destruction of the fragment's user interface view.

android-fragment-lifecycle.png

Interacting with the Android fragment lifecycle stages

The process for interacting with a stage in the fragment lifecycle is very similar to interacting with activity lifecycle stages. Simply override the method of the target lifecycle stage then add your custom code. For example, in a fragment, you could override the onCreateView stage to specify what layout resource you would like the fragment to use for its user interface:

private var _binding: FragmentFirstBinding? = null
private val binding get() = _binding!!

override fun onCreateView(
    inflater: LayoutInflater, container: ViewGroup?,
    savedInstanceState: Bundle?
): View {
    _binding = FragmentFirstBinding.inflate(inflater, container, false)
    return binding.root
}

In the above code, the onCreateView method initialises the binding class for a layout resource called fragment_first.xml. Next, the root element of the layout is set as the main View for the fragment, thereby rending the layout as the fragment's user interface.

User interactions with the layout should be handled in the onViewCreated stage. For example, if the fragment_first layout contained a button with the ID firstButton, then you could assign an onClick listener to the button using the following code:

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)

    binding.firstButton.setOnClickListener {
        findNavController().navigate(R.id.action_FirstFragment_to_SecondFragment)
    }
}

Finally, when the fragment's user interface is being destroyed, you should reset the binding variable to prevent interactions with a user interface that no longer exists. To handle this, you could override the onDestroyView stage as shown below:

override fun onDestroyView() {
    super.onDestroyView()
    _binding = null
}

<<< Previous

Next >>>