-
Notifications
You must be signed in to change notification settings - Fork 23
How to run the coroutine even if the user leaves the screen
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()
}
}
}