How to pause/stop collecting/emitting data in a Flow while app minimised?

Viewed 2017

I have a UseCase and remote repository that return Flow in a loop and I collect the result of UseCase in the ViewModel like this:

viewModelScope.launch {
    useCase.updatePeriodically().collect { result ->
        when (result.status) {
            Result.Status.ERROR -> {
                errorModel.value = result.errorModel
            }
            Result.Status.SUCCESS -> {
                items.value = result.data
            }
            Result.Status.LOADING -> {
                loading.value = true
            }
        }
    }
}

the problem is when the app is in the background (minimized) flow continues working. so can I pause it when the app is in the background and resume it when the app comes back to the foreground?

and also I don't want to observe the data in my view (fragment or activity).

2 Answers

I'd play around with the stateIn operator and the way I'm currently consuming the flow in the view.

Something like:

val state = useCase.updatePeriodically().map { ... }
    .stateIn(viewModelScope, SharingStarted.WhileSubscribed, initialValue)

And consume it from the View like:

viewModel.flowWithLifecycle(this, Lifecycle.State.STARTED)
            .onEach {
                
            }
            .launchIn(lifecycleScope) 

For other potential ways on how to collect flows from the UI: https://medium.com/androiddevelopers/a-safer-way-to-collect-flows-from-android-uis-23080b1f8bda

EDIT:

If you don't want to consume it from the view, you still have to signal for the VM that your View is in the background currently. Something like:

private var job: Job? = null

fun start(){
    job = viewModelScope.launch {
        state.collect { ... }
    }
}

fun stop(){
    job?.cancel()
}



Even if the viewModelScope is cancelled, the flow will continue to collect because it is not cooperative to cancellation.

To make a flow cancellable, you can do one of the following things:

  1. In the collect lambda, call currentCoroutineContext().ensureActive() to make sure the context in which the flow is being collected is still active. This will however throw a CancellableException, which you will need to catch, if the coroutine scope was cancelled already (viewModel scope for your case.)

  2. You can use cancellable() operator as follows:

    myFlow.cancellable().collect { //do stuff here.. } And you can call cancel() whenever you want to cancel the flow.

For official documentation on cancelling the flow see:

https://kotlinlang.org/docs/flow.html#flow-cancellation-checks

Related