Vincent Tsen
Vincent Tsen - AndroidDev Blog

Follow

Vincent Tsen - AndroidDev Blog

Follow

Activity and View Model Lifecycles Demo App

Simple app to demonstrate Android lifecycle in Activity and View Model using debug logging

Vincent Tsen's photo
Vincent Tsen
·Sep 24, 2022·

7 min read

Activity and View Model Lifecycles Demo App

Table of contents

  • Implement DefaultLifecycleObserver
  • Simulate Activity Loses Focus
  • Activity Lifecycle Summary
  • When ViewModel is Created and Destroyed?
  • View Model Lifecycle Summary
  • Conclusion
  • Source Code

The app has 2 screens (First Screen and Second Screen) implemented using simple compose navigation.

Navigating between screens has no impact on the activity life cycles. However, it may impact View Model lifecycle depend on where you instantiate the ViewModel.

Let's first look at activity lifecycle first.

Implement DefaultLifecycleObserver

This is the official activity lifecycle diagram, indicating when these lifecycle event callbacks are called. For example, before activity goes into CREATED state, onCreate() event callback is called. This applies to the rest of the lifecycle states.

Please note that onCreate() and the rest are lifecycle events and not lifecycle states.

In order to demonstrate the activity lifecycle, you need to implement DefaultLifecycleObserver interface. Then, you override all the functions and print out the different lifecycle states.

class MyLifeCycleObserver(private val name: String) : DefaultLifecycleObserver {

    private val tag = "LifeCycleDebug"

    override fun onCreate(owner: LifecycleOwner) {
        Log.d(tag, "$name: onCreate()")
    }

    override fun onStart(owner: LifecycleOwner) {
        Log.d(tag, "$name: onStart()")
    }

    override fun onResume(owner: LifecycleOwner) {
        Log.d(tag, "$name: onResume()")
    }

    override fun onPause(owner: LifecycleOwner) {
        Log.d(tag, "$name: onPause()")
    }

    override fun onStop(owner: LifecycleOwner) {
        Log.d(tag, "$name: onStop()")
    }

    override fun onDestroy(owner: LifecycleOwner) {
        Log.d(tag, "$name: onDestroy()")
    }
}

In activity, you register this lifecycle observer in onCreate() function.

class MainActivity : ComponentActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {

        super.onCreate(savedInstanceState)

        val observer = MyLifeCycleObserver(MainActivity::class.simpleName!!)
        lifecycle.addObserver(observer)

        /*...*/
        }
    }
}

For some reasons, the onRestart() callback is not available in DefaultLifecycleObserver. So you need to also override the onRestart() in your activity.

class MainActivity : ComponentActivity() {
    /*...*/

    override fun onRestart() {
        super.onRestart()

        Log.d(
            "LifeCycleDebug",
            "${MainActivity::class.simpleName!!}: onRestart()")
    }
}

Simulate Activity Loses Focus

In order to demonstrate the current activity lifecycle is paused (loses focus), the current activity needs to go into background, but still visible. To do that, you start a second activity with transparent background.

First, you create this Loses Focus button to start the second activity.

val context = LocalContext.current
/*...*/
DefaultButton(
    text = "Loses Focus",
    onClick = {
        context.startActivity(Intent(context, SecondActivity::class.java))
    }
)

In AndroidManifest.xml, add the second activity with android:theme="@android:style/Theme.Translucent":

<activity
    android:name="com.example.understandlifecyclesdemo.ui.SecondActivity"
    android:exported="true"
    android:theme="@android:style/Theme.Translucent">
</activity>

When the button is clicked, the current activity loses focus. Thus, it goes into PAUSED state.

Activity Lifecycle Summary

Try to play around with different scenarios and investigate the output from Logcat.

Here is the summary of all the different scenarios.

ScenarioActivity Lifecycle Event Callbacks
Starts uponCreate() → onStart() → onResume()
Navigate to different screensNo transition
Starts second transparent activityonPause()
Press back button (from the transparent activity)onResume()
Rotate screenonPause() → onStop() → onDestroy() → onCreate() → onStart() → onResume()
Press home buttononPause() → onStop()
Press square button and select the apponRestart() → onStart() → onResume()
Shut down (press back button)onPause() → onStop() → onDestroy()
Simulate process death (press home button, kill the process manually)onPause() → onStop()

Please note that in process death, onDestroy() event callback is not fired.

To simulate process death, you first need to press the home button to move the activity into background. After that, you kill the process manually. The easiest way to kill the process is using the stop button in Logcat.

Activity_and_ViewModel_Lifecycle_Demo_App_01.png

[Updated - Oct 15, 2022]: The above method does NOT work anymore on Android Studio Dolphin version. See the following article for more details.

More detailed descriptions on each lifecycle state below:

Activity Lifecycle StateDescription
CREATEDActivity is created and NOT visible
STARTEDActivity is visible at background
RESUMEDActivity is visible at foreground
DESTROYEDActivity is exited / shut down by user

Please note that there are no Paused, Stopped and Restarted lifecycle states which I find it a bit confusing. See diagram below.

ON_PAUSE event sends the lifecyle state to STARTED. ON_STOP event sends the lifecyle state to CREATED.

Lifecycle EventLifecycle StateDescription
ON_PAUSESTARTEDActivity is paused and visible, still in foreground
ON_STOPCREATEDActivity is NOT visible, move from foreground to background
N/ARESTARTEDIntermediate state between Stopped and Started stages

For complete documentation, you can refer to this official documentation here.

Now, let's look at the View Model lifecycle.

When ViewModel is Created and Destroyed?

There are only 2 lifecycle stages in View Model:

  • ViewModel is created. That is when ViewModel constructor is called.
  • ViewModel is destroyed. That is when ViewModel.onCleared() is called.
class MainViewModel(private val name: String) : ViewModel() {  

    private val tag = "LifeCycleDebug"  

    init {  
        Log.d(tag, "${name}ViewModel: onCreated()")  
    }  

    override fun onCleared() {  
        super.onCleared()  

        Log.d(tag, "${name}ViewModel: onCleared()")  
    }  
}

Since MainViewModel takes in constructor parameter (to differentiate the ViewModel instances), you need to create the MainViewModelFactory

class MainViewModelFactory(private val name: String)  
    : ViewModelProvider.NewInstanceFactory() {  

    override fun <T : ViewModel> create(modelClass: Class<T>): T {  

        if (modelClass.isAssignableFrom(MainViewModel::class.java))  
            return MainViewModel(name) as T  

    throw IllegalArgumentException("Unknown ViewModel class")  
    }  
}

In order to demonstrate this View Model lifecycle, you create the ViewModel in 3 different places:

Create ViewModel in MainScreen()

@Composable  
private fun MainScreen() {  
    //view model store owner belongs to the activity
    val viewModel: MainViewModel = viewModel(
        factory = MainViewModelFactory("MainScreen"))  
    /*...*/
}

Create ViewModel in FirstScreen()

@Composable  
fun FirstScreen(  
    navigateToSecondScreen: () -> Unit  
) {  
    //compose navigation creates a new view model store owner for each destination  
  val viewModel: MainViewModel = viewModel(  
        factory = MainViewModelFactory("FirstScreen"))
  /*...*/
}

Create ViewModel in SecondScreen()

@Composable  
fun SecondScreen(  
    popBackStack: () -> Unit,  
) {  
    //compose navigation creates a new view model store owner for each destination  
  val viewModel: MainViewModel = viewModel(  
        factory = MainViewModelFactory("SecondScreen"))
}

View Model Lifecycle Summary

Let's investigate the Logcat and see what happens.

ScenarioView Model Lifecycle State
Starts upAfter activity is resumed, main and first screen view models are created
Navigate to Second ScreenSecond screen view model is created
Pop back to first ScreenSecond screen view model is destroyed
Rotate the screenNo impact to view model lifecycle
Press back and exit the appAfter activity is destroyed, main and first screen view models are destroyed

ViewModelStoreOwner determines the view model lifecycle. It is set to LocalViewModelStoreOwner.current depends on where you call the viewModel() composable function.

In MainScreen(), before the navigation graph is build, the ViewModelStoreOwner belongs to the activity. In FirstScreen() and SecondScreen(), compose navigation creates a new ViewModelStoreOwner for each screen destination. However, since First Screen is the root/start destination, its lifecycle very much the same as the Main Screen view model, which is tied to the activity lifecycle.

So, after the activity is resumed, both Main and First Screen view models are created. Second Screen view model is created when it is pushed to the stack. When it pops from stack, it is then destroyed.

When the app is shutdown, activity is destroyed. After that, Main and First Screen view models are destroyed. The diagram below summarizes what happens.

Activity_and_ViewModel_Lifecycle_Demo_App_02.png

Conclusion

Activity lifecycle is common and well documented. However, it is not completely clear on view model lifecycle, especially when it will be destroyed.

Before I ran the test on this simple app, I had an impression that when the composable screen is gone off-screen, its view model is destroyed. I thought the view model lifecycle is tied to composable screen.

Well, this is not the case. It is based on the back stack from the navigation. When the screen is added into the back stack, its view model is created. When it is removed from the back stack, its view model is destroyed.

Source Code

GitHub Repository: Demo_UnderstandLifecycles

Did you find this article valuable?

Support Vincent Tsen by becoming a sponsor. Any amount is appreciated!

See recent sponsors Learn more about Hashnode Sponsors
 
Share this