Using coroutines scoped to ViewModel for IO

So far as I can make out the following is a very common pattern for enabling coroutines in Android ViewModels.

class CoroutineScopedViewModel : ViewModel(), CoroutineScope {
    private val _job = Job()
    override val coroutineContext: CoroutineContext
        get() = Dispatchers.Main + _job

    override fun onCleared() {
        super.onCleared()
        _job.cancel()
    }

    fun thisIsCalledFromDataBoundView() = launch {
        /* do some UI */
        withContext(Dispatchers.IO) {
            try {
                /* do some IO */
            } catch (error: IOException) {
                /* do some exception handling */
            }
        }
    }
}

Before I go about refactoring my project from the experimental (pre-1.0.0) version, I need to just have something clarified:

If the activity or fragment attached to the viewmodel is closed before the withContext(Dispatchers.IO) block is completed will that child job be canceled before completing? I.e. can this model be used for inserting data into my DB, or could some strange timing issue arise where the user hits back or otherwise causes the ViewModel owner to close that ends up losing the data?

To be clear I currently use GlobalScope.launch(Dispatchers.IO) (quickest conversion from 1.2.7x to 1.3.x) to write user entries into the SQLite DB (that I later sync with a server in the background), I just don;t want to inadvertently introduce a timing bug because I misunderstood something and converted to the model shown above.

1 Like

You’re correct, the withContext will be cancelled if the ViewModel goes away. You could take advantage of the fact that cancellation is cooperative, but that seems ugly to me.

I think if you want the DB write to succeed then it needs to be done in a different scope. GlobalScope is probably fine. So instead of withContext, use GlobablScope.launch(Dispatchers.IO). Actually I’d move that into a separate class, like some kind of repository class.

That’s how I have it currently. I just see so many examples to the contrary (using a form similar to what is shown in my initial post) that it had me wondering if I’m doing something wrong.

Jumping a little bit late to the party, but here are my 2 cents:
In our code all of the view models inherit from a base view model who implements CoroutineScope
Some of the view models are only there to get a nice proccessed data to the UI, smoe are there to actualy manipulate data and do actual work.

In a typical view model class,
whenever there is some data to be retrieved, we would use this.launch { ... } to get it
whenever there is some work to be done (your example with DB insertions is good here) we use GlobalScope.launch { ... }

1 Like