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

3

6

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).

Bonzer answered 30/5, 2021 at 15:46 Comment(5)
If you want something to be changed based on the lifecycle of your UI, why wouldn't it be tied to your UI? It seems like you are fighting against the exact thing you need.Arty
Collecting or observing remote data source in view is an anti-pattern, so we have to do that in the ViewModel. but we don't have ViewLifeCycle there so this is my issue I think. I tried MediatorLiveData and Stateflow but they didn't help me.Bonzer
I'd like to see what source you're referencing since being lifecycle aware is exactly what collecting in the UI is for and what the Guide to app architecture talks through. How does the data from this result feed into your UI? It sounds like what you're actually looking for is a map to transform your periodic update into what your UI needs.Arty
What is the implementation of useCase.updatePeriodically() function?Impressment
@YuriiKot it doesn't matter really, just it is returning flowBonzer
C
7

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

Chatwin answered 4/9, 2021 at 15:35 Comment(0)
P
4

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()
}



Proposal answered 30/5, 2021 at 16:25 Comment(1)
Exactly the problem is that don't want to collect flow in the view.Bonzer
B
3

I believe you want something like this

lifecycleScope.launch {
    repeatOnLifecycle(Lifecycle.State.STARTED) {
        state.collect { 
        }
    }
}

Here's an execellent article on repeatOnLifecyle: https://medium.com/androiddevelopers/repeatonlifecycle-api-design-story-8670d1a7d333

Bramwell answered 19/2, 2023 at 14:50 Comment(2)
Why does this stop the collection of flows?Amye
Read the article. Collect when the activity/fragment's state is STARTED or higher. Stop when its not. Why do you want you views collecting data when its not being viewed?Bramwell

© 2022 - 2024 — McMap. All rights reserved.