I am currently developing a desktop application, a...
# coroutines
a
I am currently developing a desktop application, and I have taken an MVVM approach. A ViewModel has no lifecycle in the desktop world, which makes me wonder, if I should inject a coroutine scope, or dispatcher to it. What is the best way to go here?? Should I create a long lasting coroutine scope? or just inject a dispatcher and use withContex while invoke network requests when needed to?? N.B: I want to inject one of these so that I may be able to test it as well
c
The normal ViewModel class on Android really doesn't have a lifecycle either. It's created and starts immediately running, and it can be closed (which is done automatically by the Android lifecycle). To that point, a CoroutineScope does that same thing. I would recommend injecting a CoroutineScope into the constructor and using that to run all work, and use
.invokeOnCompletion
to do the cleanup stuff. I recently released [Ballast](https://github.com/copper-leaf/ballast) which does this, and also provides a robust MVI pattern on top and useful utilities like state restoration and time-travel debugging to make it easy to understand and work with on all Kotlin platforms, which you may be interested in
And we just got a new #ballast channel created for this library if you need any help with it!
a
VieeModels in android do have a life cycle. It is different from the Lifecylce owner (e.g activities's lifecyle) but it does relate to the life cycle owner. It is why there is a viewModelLifecylce coroutinescope that is automatically cancelled when the view model is being cancelled so to speak. Which bring me now to this question. A CoroutineScope is basically a primitive for structured concurrency, this is super useful for things that can be cleared/cancelled manually/automatically. However, this doesn't mean it should be used everywhere. Which begs the question what even is the purpose of injecting a CoroutineScope?
c
I wasn't implying that ViewModels aren't tied to the Android lifecycle, but rather that they're more accurately described as something that has a limited lifetime than as something intimately related to the Android Lifecycle. They already live longer than Activities or Fragments, so their "lifecycle" isnt as clear cut as the normal UI lifecycle, and once a VM is cleared it can't be used again. The ViewModel itself is a generic pattern and doesn't need the Android stuff to work similarly on other platforms, it just happens that the Android ViewModel is cleared on a particular callback of the Android lifecycle. The more generic usage of a "ViewModel" as you would use in a desktop app works the exact same way. It's just a class with a limited lifetime, and the CoroutineScope is one thing you can use to define that lifetime. The ViewModel is alive as long as a particular CoroutineScope is active. If you use
GlobalScope
, then it will live forever. But if you're using #compose-desktop you can get a CoroutineScope scoped to a particular Composable function and have it live for a shorter period of time, as long as that bit of UI is visible. By passing a CoroutineScope in through the constructor, you can choose exactly how long you want the ViewModel to live when you create it
a
So why inject a CorountineScope then? can't we just instantiate it inside the ViewModel itself with the proper dispatcher?
a
ViewModel in Android has the only lifecycle method - onCleared(). In both Android and desktop, the ViewModel is cleared when the corresponding screen is removed from the stack. In both Android and desktop, the ViewModel outlives the UI (the latter is destroyed when another screen is pushed to the stack, but the former is not). So, I don't see any difference in the ViewModel lifecycle. The fact that Android ViewModel is not destroyed on configuration changes is just because it's still in the back stack. From my point of view, the ViewModel is the scope. So just pass a dispatcher via the constructor and create a private CoroutineScope property. Cancel the scope when the ViewModel is cleared.