Skip to content

How to run the coroutine even if the user leaves the screen

Devrath edited this page Jun 15, 2021 · 2 revisions

Usually, when the user leaves the screen, the ViewModel gets cleared and all the coroutines launched in viewModelScope get canceled. Sometimes, however, we want a certain coroutine operation to be continued when the user leaves the screen. In this use case, the network request keeps running and the results still get inserted into the database cache when the user leaves the screen. This makes sense in real-world applications as we don't want to cancel an already started background "cache sync".

You can test this behavior in the UI by clearing the database, then loading the Android version, and instantly close the screen. You will see in LogCat that the response still gets executed and the result still gets stored. The respective unit test AndroidVersionRepositoryTest also verifies this behavior.

class ContinueCoroutineWhenUserLeavesScreenViewModel(
    private var repository: AndroidVersionRepository
) : BaseViewModel<UiState>() {

    // more information in this blogpost about "Coroutines & Patterns for work that shouldn't
    // be cancelled" =>
    // https://medium.com/androiddevelopers/coroutines-patterns-for-work-that-shouldnt-be-cancelled-e26c40f142ad

    fun loadData() {
        uiState.value = UiState.Loading.LoadFromDb

        viewModelScope.launch {
            val localVersions = repository.getLocalAndroidVersions()
            if (localVersions.isNotEmpty()) {
                uiState.value =
                    UiState.Success(DataSource.Database, localVersions)
            } else {
                uiState.value =
                    UiState.Error(DataSource.Database, "Database empty!")
            }

            uiState.value = UiState.Loading.LoadFromNetwork

            try {
                uiState.value = UiState.Success(
                    DataSource.Network,
                    repository.loadAndStoreRemoteAndroidVersions()
                )
            } catch (exception: Exception) {
                uiState.value = UiState.Error(DataSource.Network, "Network Request failed")
            }
        }
    }

    fun clearDatabase() {
        repository.clearDatabase()
    }
}

sealed class DataSource(val name: String) {
    object Database : DataSource("Database")
    object Network : DataSource("Network")
}
class AndroidVersionRepository(
    private var database: AndroidVersionDao,
    private val scope: CoroutineScope,
    private val api: MockApi = mockApi()
) {

    suspend fun getLocalAndroidVersions(): List<AndroidVersion> {
        return database.getAndroidVersions().mapToUiModelList()
    }

    suspend fun loadAndStoreRemoteAndroidVersions(): List<AndroidVersion> {
        return scope.async {
            val recentVersions = api.getRecentAndroidVersions()
                Timber.d("Recent Android versions loaded")
                for (recentVersion in recentVersions) {
                    Timber.d("Insert $recentVersion to database")
                    database.insert(recentVersion.mapToEntity())
                }
                recentVersions
            }.await()
        }

    fun clearDatabase() {
        scope.launch {
            database.clear()
        }
    }
}
Clone this wiki locally