In the Android View system it is common to have ViewModels observe lifecycle events, generated by the Fragment or Activity hosting them, in order to start and stop certain tasks. Typically ViewModels will implement the DefaultLifecycleObserver interface and listen for lifecycle events like onStart and onStop. We can see an example below
This is all pretty straightforward — the Fragment adds the ViewModel as an observer on its lifecycle, and the ViewModel overrides the lifecycle events it is interested in. In this case, we want to run a task when the fragment is started (say, fetch some data from the network) so we override onStart, and we want to cancel that task if the fragment is backgrounded or destroyed, so we would cancel that task in the onStop callback.
The recommended approach for new apps using Jetpack Compose is to use a single Activity and no fragments, the Activity will host the composables and navigating from screen to screen simply translates into swapping one composable for another one. So with no fragments and with the Activity being longer lived that the composables it hosts, we can’t rely on the Activity lifecycle to start/stop tasks on our ViewModels that should be scoped to the composable.
I’ll pause here for a moment to note that if you are porting an existing app from the view system to Jetpack Compose you may still rely on this approach as you may be converting your XML layouts into composable functions, but still keeping the Fragments that host those views (now hosting composables). In this case, your ViewModels can still observe the lifecycle events of the hosting Fragment.
So, how can we listen for composable events in our ViewModel similar to the lifecycle events we have in the view system? The answer to this is Effects, in this case we are interested in DisposableEffect, as we want to listen to both the composable having successfully been added to the composition, to start a task, and leaving the composition, to cancel the task. Let’s see the code first
We can see that
- the ViewModel no longer implements DefautLifecycleObserver
- we have equivalent onStart() and onStop() methods in the ViewModel, but these take no arguments and do not override anything
- in the composable we are using a DisposableEffect to notify the ViewModel when the composable enters and exits the composition
How does DisposableEffect work? When this composable enters the composition the effect’s lambda runs, which calls the ViewModel onStart method and registers a disposable lambda to run when the composable leaves the composition. This way, when we navigate away from this screen, the ViewModel’s onStop will be called and any running tasks can be cancelled.
It’s important to note that DisposableEffect has constructors with different number of arguments, named keys. Whenever any of the keys passed as argument changes the disposable effect will call its onDispose lambda and then trigger the effect lambda again. In our case, we want to remember the ViewModel associated with the composable, so we use the ViewModel itself as the key — as long as the composable is supplied with the same ViewModel it will not run any of the effects’ lambdas when being recomposed, which is exactly how we intend this to work to achieve parity with the view system.