Home > Software design >  How to launch lifecycle.whenResumed coroutine immediately so flow-collection starts before returning
How to launch lifecycle.whenResumed coroutine immediately so flow-collection starts before returning

Time:03-02

I listen to a flow in whenResumed, so events are only collected while app is resumed. However I would like to ensure that events sent immediately (ie. within the same non-async stack-execution) also gets collected.

    init {
        lifecycleScope.launchWhenResumed {
            stateUpdates.collect { onUpdate(it) }
        }
        // Now do stuff that might trigger event, which I want to collect above
    }
    

I know I can use a sharedFlow with a cache, and probably that is the recommended solution, but I'm think situations could occur where I don't want cached events to be sent to new collectors.

I know I can do the following, where line 1 is executed immediately, but here again line 2 is dispatched and thus executed later:

    CoroutineScope(Dispatchers.Main).launch(start = CoroutineStart.UNDISPATCHED) {
        // 1
        lifecycle.whenResumed {
            // 2
        }
    }

But if I add a `lifeCycle.when

CodePudding user response:

I think I found the solution. It requires yielding inside a coroutine, so other all other coroutines scheduled on the same Dispatcher (eg. main thread) has chance to run.

    runBlocking {
        lifecycleScope.launchWhenResumed {
            stateUpdates.collect { onUpdate(it) }
        }
        yield()
    }

And skip the runBlocking if already inside a coroutine. The above assumes we are already running on the main-thread, and also requires to be in state resumed for collect to run. So that hints at design issue, and what I really should have been doing is:

   lifecycleScope.launchWhenResumed {
        launch {
            stateUpdates.collect { onUpdate(it) }
        }
        yield()
        // Do my thing, that might result in state-update
    }

or equivalently

   lifecycleScope.launchWhenResumed {
        stateUpdates.collect { onUpdate(it) }
   }
   lifecycleScope.launchWhenResumed {
        yield()
        // Do my thing, that might result in state-update
    }

See Kotlin Coroutine Yield and this other question What is the purpose of coroutine yield()?

CodePudding user response:

Instead of doing this:

runBlocking {
    lifecycleScope.launchWhenResumed {
        stateUpdates.collect { onUpdate(it) }
    }
    yield()
}

Do this, which is less fragile:

lifecycleScope.launchWhenResumed {
    yield()
    stateUpdates.collect { onUpdate(it) }
}

It's still a bit fragile, though, because it relies on the order coroutines were submitted.

CodePudding user response:

lifecycleScope.launch {
    val mutex = Mutex(locked = true)
    lifecycleScope.launchWhenResumed {

       stateUpdates
            .onStart { mutex.unlock() }
            .collect { onUpdate(it) }
    }

    mutex.withLock {
        // Do my thing, that might result in state-update
    }
}
  • Related