Build a Hands-Free Selfie Android Application with MediaPipe
Introduction
Set up the Development Environment
Manage Camera Permissions
Integrate MediaPipe solutions
Manage UI state with ViewModel
Use SharedFlow to View Events
Use StateFlow to View Controller States
Mediate flows to trigger photo capture
Avoid duplicate photo capture requests
Next Steps
Build a Hands-Free Selfie Android Application with MediaPipe
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.
Introduce camera readiness state
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.
- Navigate to
MainViewModeland add aMutableStateFlownamed_isCameraReadyas a private member variable. This tracks whether the camera is busy or unavailable.
Copy in the following:
private val _isCameraReady = MutableStateFlow(true)
- Update
captureEventsby combining_bothOkand_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())
- Add a photo capture callback named
onPhotoCaptureComplete(), which restores the camera readiness state back to true, so that thecaptureEventsresumes emitting once the conditions are met again:
fun onPhotoCaptureComplete() {
viewModelScope.launch {
_isCameraReady.emit(true)
}
}
- Navigate to
MainActivityand invoke theonPhotoCaptureComplete()method inside theonImageSavedcallback:
override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) {
val savedUri = outputFileResults.savedUri
Log.i(TAG, "Photo capture succeeded: $savedUri")
viewModel.onPhotoCaptureComplete()
}
Introduce a camera cooldown mechanism
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.
- Add the following constant value to
MainViewModel’s companion object. This defines a three-second cooldown before making the camera available again.
private const val IMAGE_CAPTURE_DEFAULT_COUNTDOWN = 3000L
- Add a
delay(IMAGE_CAPTURE_DEFAULT_COUNTDOWN)before updating_isCameraBusystate.
fun onPhotoCaptureComplete() {
viewModelScope.launch {
delay(IMAGE_CAPTURE_DEFAULT_COUNTDOWN)
_isCameraBusy.emit(false)
}
}
You might need to import the kotlinx.coroutines.delay function.
- Build and run the app again. You should notice that now the photo capture cannot be triggered as frequently as before. Good job!
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.
Further resource for support: complete sample code on GitHub
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.