Really neat solution π.
I just didn't understood one thing here that why we need both postAtFrontOfQueue() & post()? Wouldn't just post() do the trick for us like shown below? π€ With or without postAtFrontOfQueue() we essentially want to call posted.complete(Unit) at the end of the main thread queue.
Choreographer.getInstance().postFrameCallback {
handler.post {
posted.complete(Unit)
}
}
Thanks, it's really great writeup π.
By the way, instead of CompletableDeferred, why don't you use suspendCancellableCoroutine by which you can also support cancelling registered callback if Flow is by chance terminated/cancelled/?
Let's say, this is how it looks if implemented with suspendCancellableCoroutine:
object WhileSubscribedOrRetained : SharingStarted {
private val handler = Handler(Looper.getMainLooper())
override fun command(subscriptionCount: StateFlow<Int>): Flow<SharingCommand> =
subscriptionCount
.transformLatest { count ->
if (count > 0) {
emit(SharingCommand.START)
} else {
awaitChoregrapherFramePostFrontOfQueue()
emit(SharingCommand.STOP)
}
}
.dropWhile { it != SharingCommand.START }
.distinctUntilChanged()
override fun toString(): String = "WhileSubscribedOrRetained"
@OptIn(InternalCoroutinesApi::class)
private suspend fun awaitChoregrapherFramePostFrontOfQueue() {
suspendCancellableCoroutine<Unit> { cont ->
val frameCallback = Choreographer.FrameCallback {
handler.postAtFrontOfQueue {
handler.post {
if (!cont.isCompleted) {
cont.resume(Unit)
}
}
}
}
Choreographer.getInstance().postFrameCallback(frameCallback)
cont.invokeOnCancellation {
Choreographer.getInstance().removeFrameCallback(frameCallback)
}
}
}
}
Sebastian Lobato Genco
Android dev
Great article and discovery! Thanks a lot.
One thing I discovered testing this extensively is that Espresso and Robolectric automated tests tend to kick the count = 0 + your post() tricks earlier than Compose runs the first composition. Since Compose is ~100ms late to the party, tests fail. I solved it waiting (with a 1sec timeout) for a RESUME callback from a Compose listener.