Would anybody be willing to do a quick code review...
# compose
c
Would anybody be willing to do a quick code review on this utility function?
Copy code
@Composable
fun rememberLifecycleEvents(
  lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
): Flow<Lifecycle.Event>
1
Full implementation:
Copy code
@Composable
fun rememberLifecycleEvents(
  lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
): Flow<Lifecycle.Event> {
  val scope = rememberCoroutineScope()
  val flow = remember(lifecycleOwner) { MutableSharedFlow<Lifecycle.Event>() }
  DisposableEffect(lifecycleOwner) {
    val observer = LifecycleEventObserver { _, event ->
      scope.launch {
        flow.emit(event)
      }
    }

    lifecycleOwner.lifecycle.addObserver(observer)

    onDispose {
      lifecycleOwner.lifecycle.removeObserver(observer)
    }
  }

  return remember(lifecycleOwner) { flow }
}
Just wondering if I missed anything. And/or if this is good idea / bad idea
It’s expected you would use it like this:
Copy code
val lifecycleEvents = rememberLifecycleEvents()
LaunchedEffect(lifecycleEvents) {
  lifecycleEvents.collect {
    // Handle lifecycle events
  }
}
k
This is my take:
Copy code
interface LifecycleEffectScope {
    /**
     * Register a [callback] which will be invoked on [Lifecycle.Event.ON_START].
     */
    fun onStart(callback: () -> Unit)

    /**
     * Register a [callback] which will be invoked on [Lifecycle.Event.ON_RESUME].
     */
    fun onResume(callback: () -> Unit)

    /**
     * Register a [callback] which will be invoked on [Lifecycle.Event.ON_PAUSE].
     */
    fun onPause(callback: () -> Unit)

    /**
     * Register a [callback] which will be invoked on [Lifecycle.Event.ON_STOP].
     */
    fun onStop(callback: () -> Unit)

    /**
     * Register a [callback] which will be invoked on [Lifecycle.Event.ON_DESTROY].
     */
    fun onDestroy(callback: () -> Unit)
}

private class LifecycleEffectScopeImpl : LifecycleEffectScope {
    private var onStartCallback: (() -> Unit)? = null
    private var onResumeCallback: (() -> Unit)? = null
    private var onPauseCallback: (() -> Unit)? = null
    private var onStopCallback: (() -> Unit)? = null
    private var onDestroyCallback: (() -> Unit)? = null

    override fun onStart(callback: () -> Unit) {
        onStartCallback = callback
    }

    override fun onResume(callback: () -> Unit) {
        onResumeCallback = callback
    }

    override fun onPause(callback: () -> Unit) {
        onPauseCallback = callback
    }

    override fun onStop(callback: () -> Unit) {
        onStopCallback = callback
    }

    override fun onDestroy(callback: () -> Unit) {
        onDestroyCallback = callback
    }

    val observer = LifecycleEventObserver { _, event ->
        when (event) {
            Lifecycle.Event.ON_START -> onStartCallback?.invoke()
            Lifecycle.Event.ON_RESUME -> onResumeCallback?.invoke()
            Lifecycle.Event.ON_PAUSE -> onPauseCallback?.invoke()
            Lifecycle.Event.ON_STOP -> onStopCallback?.invoke()
            Lifecycle.Event.ON_DESTROY -> onDestroyCallback?.invoke()
            else -> {
                /* no-op */
            }
        }
    }
}

/**
 * A side effect of a composition which registers the callbacks set on [block] using
 * [LifecycleEffectScope] setters, whenever [owner] changes the events observer is disposed in
 * the old [owner] and attached to the new [owner].
 */
@Composable
fun LifecycleEffect(
    owner: LifecycleOwner = LocalLifecycleOwner.current,
    block: LifecycleEffectScope.() -> Unit,
) {
    DisposableEffect(owner) {
        val scope = LifecycleEffectScopeImpl()
        scope.block()
        owner.lifecycle.addObserver(scope.observer)

        onDispose {
            owner.lifecycle.removeObserver(scope.observer)
        }
    }
}
You can use it like this:
Copy code
LifecycleEffect {
   onStart {
       // ...
   }

   onStop {
       // ...
   }

   // etc
}
c
Ah nice, that’s a good approach too. I like that it is defined as
LifecycleEffect
which is consistent with other effects. The reason I used a Flow is that it is easy to e.g.
drop(1)
if we want effects for every
ON_START
event after the first one.
k
True, each approach as its own benefits 👍