I want to register an observer to a lifecycleOwner...
# coroutines
s
I want to register an observer to a lifecycleOwner and remove it when this lifecycle is no longer resumed. I am currently doing:
Copy code
lifecycleOwner.repeatOnLifecycle(Lifecycle.State.RESUMED) {
    launch {
        val callback = LifecycleEventObserver { _: LifecycleOwner, event: Lifecycle.Event ->
            if (event == Lifecycle.Event.ON_PAUSE) {
                // do something on pause
            }
        }
        lifecycleOwner.lifecycle.addObserver(callback)
        try {
            awaitCancellation()
        } finally {
            lifecycleOwner.lifecycle.removeObserver(callback)
        }
    }
}
But this
try/finally
with the
awaitCancellation()
combination feels a bit weird. Is there a nicer way to do this in general?
a
you could get a little more tightly scoped by using
suspendCancellableCoroutine
directly, but since
Lifecycle
is so strictly thread-sensitive, doing it the way you have here will save you some headaches in terms of making sure you only add/remove the observer on the main thread. Probably create a
val job = Job()
and
job.complete()
in your listener, and then
job.join()
in your
try
block.
s
Hm I’m sorry, I got a bit confused. You said doing it the way I did it will save me headaches, so it’s the best way to do this? If not, the points you made about job, would that mean to replace what I got with:
Copy code
lifecycleOwner.repeatOnLifecycle(Lifecycle.State.RESUMED) {
    launch {
        val job = Job()
        val callback = LifecycleEventObserver { _: LifecycleOwner, event: Lifecycle.Event ->
            if (event == Lifecycle.Event.ON_PAUSE) {
                job.complete()
            }
        }
        lifecycleOwner.lifecycle.addObserver(callback)
        try {
            job.join()
        } finally {
            lifecycleOwner.lifecycle.removeObserver(callback)
        }
    }
And if so, how would this be different from what I had done?
a
it's not different, it's the same structure just with the TODO filled in 🙂
s
Oh with that comment I meant more like, I want to do something on Pause there, like in my case cancel a mediaPlayer for example, I thought my original implementation should work by itself already anyway 🤔
a
ah
now that I look closer, since you're doing this in a
repeatOnLifecycle
call the second observer is redundant
you can do this:
Copy code
repeatOnLifecycle(RESUMED) {
  try {
    awaitCancellation()
  } finally {
    // do a thing when you drop from resumed to a lower state
  }
}
repeatOnLifecycle
already cancels the block when you drop below the target state
so all you need to do is await the cancellation
s
You’re absolutely right, of course! I am now doing just
Copy code
coroutineScope.launch {
    lifecycleOwner.repeatOnLifecycle(Lifecycle.State.RESUMED) {
        launch {
            try {
                awaitCancellation()
            } finally {
                mediaPlayer?.pause()
            }
        }
        launch {
            // other stuff I'm doing after Resumed
        }
    }
}
And this should work perfectly fine. I have the extra launch there as I want to do more than just one thing (I’m collecting a flow) on the other launch, but I’ve seen that the
block
of
repeatOnLifecycle
is launched on a
coroutineScope{}
so all child coroutines should just cancel properly. I’m still trying to grasp this whole “Structured concurrency” concept but it’s looking really good and clean when I manage to understand how to use it!
👍 1
a
yeah it's an inspiring API design and it has applicability in so many places
it's heavily influencing where Android API designs are headed for sure
s
I am very happy that it is the case, I strongly believe it is only getting better by being influenced by it. Thanks for the help!
n
Or:
Copy code
lifecycleOwner.repeatOnLifecycle(Lifecycle.State.RESUMED) {
    coroutineContext.job.invokeOnCompletion { mediaPlayer.pause() }
}
The difference is that
invokeOnCompletion
will wait for the entire scope to finish (all the child jobs too).
s
Right that’s an option too. But in this case I’m not interested if it happens after all the children or just on cancellation. If anything, it’s better for it to happen asap so that any job that may drag on for too long doesn’t make the audio still play for more than needed, so I think I’ll stick with the cancellation approach. But thanks a lot for the idea, I had forgotten that this approach exists too.
n
Don’t forget that you don't have to use coroutine apis for everything. You can just listen for ON_PAUSE.
s
If I’d do that, I’d have to also then add and remove the observer myself but I’m letting repeatOnLifecycle be the replacement of that here right? Also I’m in that place also calling some suspending functions too that I didn’t mention here, so I was in coroutines land already anyway.
396 Views