Activity and View Model Lifecycles Demo App
Tags: android, kotlin, beginners, jetpackcompose
Published: 2022-10-14 23:13:56

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

The app has 2 screens (First Screen and Second Screen) implemented using [simple compose navigation](https://vtsen.hashnode.dev/simple-jetpack-compose-navigation-example).  

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.

![](https://developer.android.com/guide/components/images/activity_lifecycle.png)

> 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.

```kotlin
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.
```kotlin
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.

```kotlin
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.

```kotlin
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"`:

```xml
<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.

| Scenario | Activity Lifecycle Event Callbacks | 
| --- | --- |
| Starts up | onCreate() → onStart() → onResume() |
| Navigate to different screens | No transition | 
| Starts second transparent activity | onPause() |
| Press back button (from the transparent activity) | onResume() |
| Rotate screen | onPause() → onStop() → onDestroy() → onCreate() → onStart() → onResume() | 
| Press home button | onPause() → onStop() |
| Press square button and select the app | onRestart() → 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](https://cdn.hashnode.com/res/hashnode/image/upload/v1657353776997/MSqHPLGcO.png)

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

- [How to Simulate Process Death in Android?](https://vtsen.hashnode.dev/how-to-simulate-process-death-in-android)

More detailed descriptions on each lifecycle state below:

| Activity Lifecycle State | Description |
| --- | --- |
| CREATED | Activity is created and NOT visible |
| STARTED | Activity is visible at background |
| RESUMED | Activity is visible at foreground |
| DESTROYED | Activity 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.

![](https://developer.android.com/static/images/topic/libraries/architecture/lifecycle-states.svg)

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

| Lifecycle Event | Lifecycle State | Description |
| --- | --- | --- |
| ON_PAUSE | STARTED  | Activity is paused and visible, still in foreground |
| ON_STOP | CREATED | Activity is NOT visible, move from foreground to background |
| N/A | RESTARTED | Intermediate state between Stopped and Started stages |

> For complete documentation, you can refer to this official documentation [here](https://developer.android.com/guide/components/activities/activity-lifecycle).

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](https://vtsen.hashnode.dev/understand-kotlin-multiple-constructors) is called.
- `ViewModel` is destroyed. That is when `ViewModel.onCleared()` is called.

```kotlin
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`
```kotlin
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()**

```kotlin
@Composable  
private fun MainScreen() {  
    //view model store owner belongs to the activity
    val viewModel: MainViewModel = viewModel(
        factory = MainViewModelFactory("MainScreen"))  
    /*...*/
}
```
**Create ViewModel in FirstScreen()**
```kotlin
@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()**

```kotlin
@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.

| Scenario | View Model Lifecycle State | 
| --- | --- |
| Starts up | After activity is resumed, main and first screen view models are created |
| Navigate to Second Screen | Second screen view model is created |
| Pop back to first Screen | Second screen view model is destroyed |
| Rotate the screen | No impact to view model lifecycle
| Press back and exit the app | After 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](https://cdn.hashnode.com/res/hashnode/image/upload/v1657353789634/jd37R29uy.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](https://github.com/vinchamp77/Demo_UnderstandLifecycles)

---

*Originally published at [https://vtsen.hashnode.dev](https://vtsen.hashnode.dev/activity-and-view-model-lifecycles-demo-app)*.