Contenido del curso
Google Maps SDK
Servicios de Localización
- 7

Kotlin Flows para medir tiempo en Android
12:54 min - 8

Simulación de ubicación GPS en emulador y dispositivo Android
05:48 min - 9

Modelos de localización propios con Clean Architecture
08:36 min - 10

Callbacks de Android convertidos en Flows
14:50 min - 11

Inyección de dependencias para observar localización en Android
06:33 min - 12

LocationTracker con StateFlow para rastreo en Android
08:46 min - 13

State Flows para controlar localización y tiempo en Kotlin
10:00 min - 14

Configuración y pruebas de Location Tracker en Android
09:37 min
Integración Maps con Localización
Manejo de permisos
Integración cámara
- 23

Cómo guardar fotos en Android con PhotoHandler
Viendo ahora - 24

Conversión de Bitmaps a Byte Arrays con Extension Functions
05:58 min - 25

CameraViewModel con Hilt y StateFlow
08:40 min - 26

Configuración de métodos del ViewModel para gestión de cámara
09:40 min - 27

Integración de CameraX con Jetpack Compose en Android
14:23 min - 28

Creación de pantalla de previsualización de fotos con Jetpack Compose
08:44 min - 29

Galería de fotos en marcadores del mapa
11:55 min
Servicios en Android
Transmisiones en Android (Broadcast)
Cómo guardar fotos en Android con PhotoHandler
Resumen
Manejar la cámara en Android implica resolver dos retos clave: gestionar permisos y guardar fotos en el sistema local. Aquí aprenderás a construir un PhotoHandler en Kotlin que centraliza el flujo de captura, previsualización y almacenamiento de imágenes usando CameraX, Flow y coroutines.
Qué hace un PhotoHandler en una app Android
Un PhotoHandler funciona como contrato entre la cámara y el almacenamiento. Define qué operaciones expone tu feature de cámara y cómo se comunican entre sí.
En la capa de domain creas un paquete camera y dentro un archivo PhotoHandler con la interfaz de operaciones. Las funciones que necesitas son:
savePicturePreview(): suspend function que retorna unFile?con la foto confirmada.onCancelPreview(): suspend function que descarta la foto en preview.getPhotos(): expone unFlow<List<File>>con las fotos guardadas.getCurrentPreviewPhoto(): retorna unFlow<ByteArray?>con la foto temporal.clearPhotos(): suspend function que libera recursos y borra archivos.onPhotoPreview(photoBytes: ByteArray): envía la foto recién capturada al preview.
¿Qué es un PhotoHandler? Es una interfaz que define cómo capturar, previsualizar, guardar y eliminar fotos en una app Android, separando la lógica de cámara del almacenamiento local.
Cómo fluye una foto desde CameraX hasta el archivo guardado
El recorrido tiene una lógica clara. Cuando CameraX captura una imagen, llamas a onPhotoPreview con el byte array, que es básicamente un bitmap serializado. Ese byte array se expone vía getCurrentPreviewPhoto para que la UI lo muestre.
Si al usuario le gusta, ejecutas savePicturePreview y la foto se convierte en un File real en disco. Si no, onCancelPreview limpia el estado. Al final del recorrido, clearPhotos libera la memoria.
Cómo implementar PhotoHandlerImpl en la capa de data
En el módulo de data creas otro paquete camera con la clase PhotoHandlerImpl. Esta clase inyecta el Application Context como private val context: Context, porque necesitas acceder al sistema de archivos de la app.
Defines un photoDirectory inicializado con lazy, lo que significa que se crea solo cuando se usa por primera vez. La ruta apunta al directorio principal de archivos de la app concatenado con /fotos. Si la carpeta no existe, ejecutas mkdir() para crearla.
Luego declaras dos variables locales con MutableStateFlow:
currentPreviewPhoto: unMutableStateFlow<ByteArray?>que empieza ennull.photos: unMutableStateFlow<List<File>>que empieza conemptyList().
Cómo cargar fotos guardadas con loadSavedPhotos
La función privada loadSavedPhotos revisa si photoDirectory existe y trae todos los archivos con listFiles(). Aplica un filtro doble: que sea archivo y que tenga extensión válida.
Las extensiones aceptadas son jpg, jpeg y png, comparadas en lowercase. Después ordena los resultados de forma descendente por la última fecha de modificación. Si el directorio no existe, devuelve emptyList().
Esta función se ejecuta en el bloque init de la clase, así que apenas se invoca el PhotoHandlerImpl, ya tienes la lista de fotos cargada.
Por qué usar Dispatchers.IO para guardar y borrar fotos
Las operaciones de archivo bloquean el hilo principal y degradan la experiencia. Por eso, tanto clearPhotos como savePicturePreview usan withContext(Dispatchers.IO) para mover el trabajo a un hilo optimizado para entrada y salida.
En clearPhotos, recorres los archivos, llamas a delete() y luego loadSavedPhotos() refresca el Flow expuesto. En savePicturePreview, primero validas que exista una foto actual. Si la hay, generas un nombre con timestamp usando Locale y Date, creas el File dentro de photoDirectory y escribes el byte array con FileOutputStream.
¿Por qué se usa Dispatchers.IO? Porque las operaciones de lectura y escritura en disco son bloqueantes. Ejecutarlas en el hilo principal congela la UI;
Dispatchers.IOlas mueve a un pool de hilos diseñado para esto.
Qué pasa después de guardar una foto
Una vez escrito el archivo, llamas de nuevo a loadSavedPhotos() para que el Flow refleje la foto recién agregada. Después limpias currentPreviewPhoto.value = null para cerrar el ciclo de preview.
La función retorna el File guardado, cumpliendo el contrato de la interfaz. Si ocurre una excepción durante el proceso, devuelves null para que la capa superior maneje el error sin romper la app.
Cómo se exponen los Flows al resto de la app
La exposición de datos usa asStateFlow(), que convierte un MutableStateFlow en su versión inmutable. Esto es importante para que las capas externas solo puedan observar, nunca modificar el estado.
getPhotos()retornaphotos.asStateFlow().getCurrentPreviewPhoto()retornacurrentPreviewPhoto.asStateFlow().onPhotoPreviewsimplemente asignacurrentPreviewPhoto.value = photoBytes.onCancelPreviewasignacurrentPreviewPhoto.value = null.
Con esto, tu PhotoHandler queda listo para inyectarse en el árbol de dependencias. El siguiente paso es entender la traducción entre bitmaps y byte arrays, que es donde la cámara y el almacenamiento se encuentran de verdad.
¿Has implementado un sistema de cámara similar en tus proyectos? Cuéntame en los comentarios qué retos enfrentaste con permisos o almacenamiento.