CameraX + Compose

Author: Adam Powell

Sample implementation of CameraX integration in a Composable

suspend fun <R> withChildLifecycleOwner(
    parentLifecycle: Lifecycle,
    block: suspend CoroutineScope.(LifecycleOwner) -> R
): R {
    val childOwner = object : LifecycleOwner {
        val lifecycle = LifecycleRegistry(this)
        override fun getLifecycle(): Lifecycle = lifecycle
    }
    val registry = childOwner.lifecycle
    return coroutineScope {
        val myJob = coroutineContext.job
        val parentObserver = LifecycleEventObserver { _, event ->
            registry.currentState = event.targetState
            if (event == Lifecycle.Event.ON_DESTROY) {
                myJob.cancel("parent lifecycle was destroyed")
            }
        }
        parentLifecycle.addObserver(parentObserver)
        try {
            block(childOwner)
        } finally {
            parentLifecycle.removeObserver(parentObserver)
            registry.currentState = Lifecycle.State.DESTROYED
        }
    }
}

class CameraState {
    private val bindingMutex = MutatorMutex()
    var camera: Camera? by mutableStateOf(null)
        private set

    suspend fun <R> bind(
        context: Context,
        lifecycleOwner: LifecycleOwner,
        selector: CameraSelector,
        vararg useCases: UseCase,
        block: suspend (Camera) -> R
    ): R {
        val provider = ProcessCameraProvider.getInstance(context).await()
        return bindingMutex.mutate {
            withChildLifecycleOwner(lifecycleOwner.lifecycle) { childLifecycleOwner ->
                try {
                    block(
                        provider.bindToLifecycle(childLifecycleOwner, selector, *useCases)
                            .also { this@CameraState.camera = it }
                    )
                } finally {
                    camera = null
                }
            }
        }
    }
}

@Composable
fun CameraBinding(
    state: CameraState,
    vararg useCases: UseCase,
    selectorBuilder: CameraSelector.Builder.() -> CameraSelector.Builder = { this }
) {
    // Make selectorBuilder observe snapshot changes and only rebuild the selector
    // when the builder changes. We do this since selectors don't implement equals
    // and this makes the API a little nicer since the caller doesn't need
    // to remember to remember {}.
    val currentSelectorBuilder by rememberUpdatedState(selectorBuilder)
    val selector by remember {
        derivedStateOf {
            CameraSelector.Builder().run(currentSelectorBuilder).build()
        }
    }
    val lifecycleOwner = LocalLifecycleOwner.current
    val context = LocalContext.current
    // useCases.toList() since arrays don't implement structural equality
    LaunchedEffect(state, selector, lifecycleOwner, useCases.toList()) {
        state.bind(context, lifecycleOwner, selector, *useCases) {
            awaitCancellation()
        }
    }
}

Have a project you'd like to submit? Fill this form, will ya!

If you like this snippet, you might also like:

Maker OS is an all-in-one productivity system for developers

I built Maker OS to track, manage & organize my life. Now you can do it too!