So far you have implemented the core logic for MediaPipe’s face and gesture task results and photo capture execution.
However, the view controller is not communicating its execution results back to the view model, which raises the risk for photo capture failures, frequent or duplicate requests, and other potential issues.
It is best practice to complete the data flow cycle by providing callbacks for the states of the view controller. This ensures that the view model does not emit values in undesired states, such as when the camera is busy or unavailable.
MainViewModel
and add a MutableStateFlow
named _isCameraReady
as a private member variable. This tracks whether the camera is busy or unavailable.Copy in the following:
private val _isCameraReady = MutableStateFlow(true)
captureEvents
by combining _bothOk
and _isCameraReady
. This ensures that whenever a capture event is dispatched, the camera readiness state is set to false, which prevents the next capture event from being dispatched again.
val captureEvents: SharedFlow<Unit> =
combine(_bothOk, _isCameraReady) { bothOk, cameraReady -> bothOk to cameraReady}
.debounce(CONDITION_CHECK_STABILITY_THRESHOLD)
.filter { (bothOK, cameraReady) -> bothOK && cameraReady }
.onEach { _isCameraReady.emit(false) }
.map {}
.shareIn(viewModelScope, SharingStarted.WhileSubscribed())
onPhotoCaptureComplete()
, which restores the camera readiness state back to true, so that the captureEvents
resumes emitting once the conditions are met again:
fun onPhotoCaptureComplete() {
viewModelScope.launch {
_isCameraReady.emit(true)
}
}
MainActivity
and invoke the onPhotoCaptureComplete()
method inside the onImageSaved
callback:
override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) {
val savedUri = outputFileResults.savedUri
Log.i(TAG, "Photo capture succeeded: $savedUri")
viewModel.onPhotoCaptureComplete()
}
The differences in hardware mean that the duration of an image capture varies across Android devices. Additionally, consecutive image captures place a heavy load on the CPU, GPU, camera, and flash memory buffer.
To address this, you can implement a simple cooldown mechanism after each photo capture that can both enhance the user experience whilst also conserving computing resources.
MainViewModel
’s companion object. This defines a three-second cooldown before making the camera available again.
private const val IMAGE_CAPTURE_DEFAULT_COUNTDOWN = 3000L
delay(IMAGE_CAPTURE_DEFAULT_COUNTDOWN)
before updating _isCameraBusy
state.
fun onPhotoCaptureComplete() {
viewModelScope.launch {
delay(IMAGE_CAPTURE_DEFAULT_COUNTDOWN)
_isCameraBusy.emit(false)
}
}
You might need to import the kotlinx.coroutines.delay
function.
Furthermore, if you remove the viewModel.onPhotoCaptureComplete()
to simulate something going wrong during photo capture, the camera will not become available again.
However, silently failing without notifying the user is not a good practice for app development. Error handling is omitted in this learning path only for the sake of simplicity.
If you run into any difficulties completing this Learning Path, you can check out the complete sample code on GitHub and import it into Android Studio.
If you discover a bug, encounter an issue, or have suggestions for improvement, please feel free to open an issue with detailed information.