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.

Leave a rating/review
Download materials
Save for later
Share
You are currently viewing page 3 of 4 of this article. Click here to view the first page.

Exploring Fragment Lifecycle

Like activities, fragments have their lifecycle. When the user navigates and interacts with your app, your fragments transition through various states in their lifecycle as they’re added, removed, and enter or exit the screen.

Fragment lifecycle and fragment lifecycle callbacks

In the figure above, you can see that fragment lifecycle is similar to the activity lifecycle but contains some additional fragment-specific methods. Before explaining each callback, check them in the app.

In the previous section, you played with two activities and saw how their lifecycle changed when you moved between screens. For this example, you’ll implement the same screens with fragments. You can find two fragments that represent each screen in the fragments package: MainFragment.kt and ShareFragment.kt. There’s also one container activity and viewmodels package. For now, ignore the viewmodels package. You’ll need that in the next section.

If you check MainFragment.kt, you’ll notice a lot of similarities with MainActivity.kt. They both share the same logic for managing states, but MainFragment.kt contains a few more lifecycle callbacks.

Before running the app, open SplashActivity.kt and update startFirstActivity() so that it starts ActivityWithFragments instead of MainActivity:

private fun startFirstActivity() {
  startActivity(Intent(this, ActivityWithFragments::class.java))
}

Excellent! Now, build and run the app. Then, inspect the logs.

Logs showing activity and fragment lifecycle when opening Main screen implemented with fragment

Notice how the fragment lifecycle is synchronized with the activity lifecycle. First, the app creates and starts ActivityWithFragments. After that, it creates and starts the fragment and its view. Finally, it resumes both activity and fragment.

Next, tap back and again observe the logs.

Logs showing activity and fragment lifecycle when pressing back button on Main screen

By closing the app, you started the process of destroying the activity. As before, fragment lifecycle events follow activity lifecycle events. Both activity and fragment are first paused, then stopped and finally destroyed.

A fragment’s lifecycle state can never be greater than its parent. For example, a parent fragment or activity must start before its child fragments. Likewise, child fragments must stop before their parent fragment or activity. From the logs above, you might think otherwise — that the activity is stopping first — but that’s just because you print the log as the first call in lifecycle callbacks. Internally, the OS makes sure that all the child fragments stop before stopping the activity.

Understanding Fragment Lifecycle Callbacks

You can now dive a bit deeper into every lifecycle event to get a better understanding of the fragment lifecycle:

  • onCreate(): The fragment reaches the Created state. Similar to the activity’s onCreate(), this callback receives Bundle, containing any state previously saved by onSaveInstanceState().
  • onCreateView(): It’s called to inflate or create the fragment’s view.
  • onViewCreated(): The fragment’s view is instantiated with a non-null View object. That view is set on the fragment and can be retrieved using getView().
  • onStart(): The fragment enters the Started state. In this state, it’s guaranteed that the fragment’s view is available and that it’s safe to perform FragmentTransaction on the child FragmentManager of the fragment.
  • onResumed(): The fragment enters the Resumed state. It becomes visible after all Animator and Transition effects have finished. The user is now able to interact with the fragment.
  • onPause(): The fragment goes back to the Started state. The OS invokes this callback as the user begins to leave the fragment while the fragment is still visible.
  • onStop(): The fragment goes back to the Created state and is no longer visible.
  • onDestroyView(): It’s triggered after all the exit animations and transitions have completed, when the fragment’s view has been detached from the window. At this point, all references to the fragment’s view should be removed, allowing the fragment’s view to be garbage collected.
  • onDestroy(): The fragment enters the Destroyed state. It happens when the fragment is removed or if FragmentManager is destroyed. At this point, the fragment has reached the end of its lifecycle.

Now that you understand better what goes under the hood, navigate between the Main and Share screens to see the dance of the fragment lifecycle. :]

Logs showing activity and fragment lifecycle when going from Main screen to Share screen

As you’ve seen in this section and the previous one, the Android lifecycle is quite complex. Managing states and interacting with the UI at the right time can be tricky for inexperienced developers. This led to some new Android APIs and components that should make life easier for all Android developers. One such component is the ViewModel.

Note:

Using ViewModel to Store UI Data

ViewModel is designed to store and manage UI-related data in a lifecycle-conscious way.

First, replace the logic for saving and restoring state in MainFragment.kt with the approach that uses ViewModel.

In the viewmodels package, create a new class called MainViewModel.kt.

import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.raywenderlich.android.puppycounter.model.DogCount

class MainViewModel : ViewModel() {

  private val _dogCount: MutableLiveData<DogCount> by lazy {
    MutableLiveData<DogCount>(DogCount())
  }
  val dogCount: LiveData<DogCount> = _dogCount

  fun setDogCount(dogCount: DogCount) {
    _dogCount.value = dogCount
  }
}

This will be ViewModel for your Main screen. You’ll use it to save DogCount‘s state. Use _dogCount for tracking the state and dogCount for exposing it to the observers. For updating the state, use setDogCount().

Note: If you want to learn more about LiveData, refer to the Android Developer documentation.

In the MainFragment.kt, add these imports:

import androidx.fragment.app.viewModels
import com.raywenderlich.android.puppycounter.fragments.viewmodels.MainViewModel

Add the following line above onCreate():

private val viewModel: MainViewModel by viewModels()

With this, you added the code to create MainViewModel for MainFragment.

Next, add the following method to MainFragment.kt:

private fun subscribeToViewModel() {
  viewModel.dogCount.observe(viewLifecycleOwner, { value ->
    dogCount = value
    renderDogCount(dogCount)
  })
}

This method allows you to subscribe to the observable state in MainViewModel. Whenever dogCount‘s state changes, the app pushes the new state to the view and renderDogCount() is called with the new state, resulting in the updated UI.

Then, modify onViewCreated() so you subscribe to MainViewModel after the super call:

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
  Timber.i("PuppyCounter - MainFragment - onViewCreated()")
  super.onViewCreated(view, savedInstanceState)
  subscribeToViewModel()
  findViews(view)
  setupSmallDogViewsClickListeners(view)
  setupMiddleDogViewsClickListeners(view)
  setupBigDogViewsClickListeners(view)
}

You also need the logic that will update the state in MainViewModel. Modify updateDogCount() like this:

private fun updateDogCount(newDogCount: DogCount) {
  viewModel.setDogCount(newDogCount)
}

This method is called whenever the user updates the counters. It’ll update MainViewModel with the new state. MainViewModel will push that state through dogCount and the MainFragment will be notified since it’s subscribed to the MainViewModel.

Finally, in MainFragment remove onSaveInstanceState(), renderDogCount(dogCount) call from the onResume() and the savedInstanceState?.run { ... } code in onCreate(). You no longer need that. :]

Build and run the app. Tap the counters a couple times and rotate the screen. You should see something like this:

Main screen in both orientations showing how state was preserved by using ViewModel

Check the illustration below to see ViewModel‘s lifetime next to the associated activity lifecycle.

ViewModel lifetime next to the associated activity lifecycle

ViewModel objects are scoped to the Lifecycle passed to the ViewModelProvider when getting ViewModel. It remains in memory until the Lifecycle that it’s scoped to goes away permanently. For an activity, that happens when it’s finished. For a fragment, that happens when it’s detached.