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.
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.
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.
[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 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
andRestarted
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 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.
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 whenViewModel
constructor is called. -
ViewModel
is destroyed. That is whenViewModel.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.
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.
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
Originally published at https://vtsen.hashnode.dev.
Top comments (0)