Easy ViewModel & ViewModelScope

KH Rafiqul Islam (Alik)
3 min readNov 21, 2019

--

A ViewModelScope is defined for each ViewModel in your app. Any coroutine launched in this scope is automatically cancelled if the ViewModel is cleared. Coroutines are useful here for when you have work that needs to be done only if the ViewModel is active. If your ViewModel is destroyed, all the asynchronous task might be doing must be stopped, to avoid unnecessary resource consume.

Scopes in ViewModel

You need to create a coroutine scope by creating SupervisorJob(). you need to cancel it in the onCleared method. This coroutine scope will live as long as ViewModel is being used. Here is the code...

class UpdatePersonalDataViewModel @Inject
internal constructor(private val mRepository: Repository) : BaseViewModel<ProfileNavigator>() {

private val viewModelJob = SupervisorJob()
private val viewModelScope = CoroutineScope(Dispatchers.Main + viewModelJob)

fun launchGetProfile(token : String){
viewModelScope.launch {
getProfileData(token)
}
}

private suspend fun getProfileData(token : String){
mRepository.callDriverProfile(token)
}

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

Dispatchers.Main is a natural fit here, as ViewModel is often related to UI which if often involved in updating it. So launching on another dispatcher will cost at least two extra thread Switch.

Any heavy work occurring in the background will terminate immediately after the ViewModel gets destroyed. This is not a ton of code to write for achieving this behaviour. But Imagine you have lots of ViewModel inside your project. Then surely there are lots of code to write every time and that also the same code. So to rescue this here comes ViewModelScope

ViewModelScope

AndroidX lifecycle v2.1.0 introduced the extension property of ViewModelScope . It works the same way we demonstrate above. The new code looks like this

class UpdatePersonalDataViewModel @Inject
internal constructor(private val mRepository: Repository) : BaseViewModel<ProfileNavigator>() {

fun launchGetProfile(token : String){
viewModelScope.launch {
getProfileData(token)
}
}

private suspend fun getProfileData(token : String){
mRepository.callDriverProfile(token)
}
}

ViewModelScope means less code

To get this we need to implement below dependency in your Gradle file

implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0-rc02'

Digging deep into ViewModelScope

Let’s take a look into ViewModelScope below…

private const val JOB_KEY = "androidx.lifecycle.ViewModelCoroutineScope.JOB_KEY"

/**
*
[CoroutineScope] tied to this [ViewModel].
* This scope will be canceled when ViewModel will be cleared, i.e
[ViewModel.onCleared] is called
*
* This scope is bound to
* [Dispatchers.Main.immediate]
[kotlinx.coroutines.MainCoroutineDispatcher.immediate]
*/
val ViewModel.viewModelScope: CoroutineScope
get() {
val scope: CoroutineScope? = this.getTag(JOB_KEY)
if (scope != null) {
return scope
}
return setTagIfAbsent(JOB_KEY,
CloseableCoroutineScope(SupervisorJob() + Dispatchers.Main.immediate))
}

internal class CloseableCoroutineScope(context: CoroutineContext) : Closeable, CoroutineScope {
override val coroutineContext: CoroutineContext = context

override fun close() {
coroutineContext.cancel()
}
}

ViewModel has a concurrentHashSetwhich can save any object. The scopes are saved here. Scopes are stored here. If we take a look atgetTag(JOB_KEY) , this method tries to retrieve the coroutineScope from here. If there is no scope it creates a new scope the way we created earlier and stores the TAG into the bag.

Before destroying ViewModel it calls clear() before onCleared() . In clear() ViewModel cancel the job of ViewModelScope .

@MainThread
final void clear() {
mCleared = true;
// Since clear() is final, this method is still called on mock objects
// and in those cases, mBagOfTags is null. It'll always be empty though
// because setTagIfAbsent and getTag are not final so we can skip
// clearing it
if (mBagOfTags != null) {
synchronized (mBagOfTags) {
for (Object value : mBagOfTags.values()) {
// see comment for the similar call in setTagIfAbsent
closeWithRuntimeException
(value);
}
}
}
onCleared();
}

Another thing is to notice that, The method goes through all the items in the bag and apply closeWithRuntimeException which checks if the object is of type of Closeable if so, it closes it. So the ViewModelScope is the type of CloseableCoroutineScope which extends Coroutinecope

Source

--

--