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