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
11:59 min - 24

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

CameraViewModel con Hilt y StateFlow
Viendo ahora - 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)
CameraViewModel con Hilt y StateFlow
Resumen
Cuando trabajas con la cámara en Android, necesitas una arquitectura clara que separe las acciones del usuario de los estados de la interfaz. Aquí aprenderás a definir los intents, el estado de UI y el ViewModel que controlan la lógica de una pantalla de cámara con Kotlin, Hilt y StateFlow.
Cómo defines los intents de la cámara con sealed interface
Los intents representan cada acción que el usuario puede ejecutar sobre la cámara. Al modelarlos con una sealed interface, garantizas que el when del ViewModel contemple todos los casos posibles.
Dentro de la carpeta presentation/camera creas el archivo CameraIntent y declaras la sealed interface. A partir de ahí, defines cada acción como una data class o un data object, según necesite o no parámetros.
Qué acciones necesita una pantalla de cámara
Estas son las cuatro acciones que estructuran el flujo:
PhotoTaken: una data class que recibe la foto comoByteArray. Representa el momento en que se captura una imagen.SubmitCameraPermissionInfo: una data class conacceptedCameraPermission: BooleanyshouldShowCameraRationale: Booleanpara gestionar el permiso del sensor.PhotoSaved: un data object que indica que el usuario aceptó la foto.CancelPreview: un data object que descarta la foto en preview.
¿Por qué una sealed interface y no un enum? Porque cada acción puede llevar datos distintos. La sealed interface permite que
PhotoTakencargue unByteArraymientras quePhotoSavedno necesite parámetros. [02:10]
Por qué Kotlin pide equals y hashCode en un ByteArray
Cuando una data class contiene un ByteArray, el IDE sugiere generar equals y hashCode manualmente. La razón es técnica: un ByteArray compara referencias por defecto, no contenido.
Al dejar que Kotlin genere estas funciones según el contenido, obtienes un hash code que actúa como un serial único de la imagen. Si dos fotos tienen el mismo hash, tienen exactamente la misma información. Esto evita comparaciones erróneas y recomposiciones innecesarias en Compose. [01:30]
Cómo modelas el estado de UI de la cámara
El estado se concentra en una sola data class llamada CameraUIState. Tener una única fuente de verdad simplifica el render y evita estados inconsistentes entre el preview y la captura.
Los campos que componen el estado son:
isInPreviewMode: Boolean = false: arranca enfalseporque la pantalla inicia tomando una foto, no mostrándola.lastSavedPhoto: File? = null: guarda el archivo que la cámara entrega cuando se confirma la foto.showCameraRationale: Boolean = false: controla si debes mostrar la justificación del permiso.permissionGranted: Boolean = false: indica si el usuario ya concedió acceso al sensor.
¿Qué es el preview mode en una cámara? Es el estado en el que ya tomaste una foto y la estás mostrando al usuario para que decida si la guarda o la descarta. [04:05]
Cómo construyes el CameraViewModel con Hilt y StateFlow
El CameraViewModel se anota con @HiltViewModel y extiende de ViewModel. Hilt se encarga de inyectar las dependencias en el constructor, lo que mantiene la clase desacoplada y testeable.
Qué dependencias inyectas en el constructor
El @Inject constructor recibe dos colaboradores:
photoHandler: gestiona la foto actual en preview y expone un flow con elByteArrayde la imagen.locationTracker: asocia cada foto con la última ubicación conocida en el momento de la captura.
Esta combinación te permite enriquecer cada imagen con metadatos de geolocalización sin acoplar la lógica a la UI.
Cómo expones uiState y previewPhoto como flujos calientes
El uiState se declara como MutableStateFlow<CameraUIState > privado y se expone con asStateFlow() para que la UI no pueda mutarlo directamente.
El previewPhoto es más interesante: toma el flujo del photoHandler.getCurrentPreviewPhoto() y lo transforma con stateIn dentro del viewModelScope. La configuración usa SharingStarted.WhileSubscribed(5000), lo que significa que el flujo se mantiene activo cinco segundos después de que el último suscriptor se desconecta.
¿Para qué sirve stateIn con WhileSubscribed? Convierte un cold flow en un hot flow que retiene el último valor. Si el usuario rota la pantalla, no pierdes la foto en preview porque el flujo no se reinicia inmediatamente. [06:20]
Cómo manejas los intents dentro del ViewModel
La función pública del ViewModel recibe un CameraIntent y lo procesa con un when. Esta estructura te obliga a contemplar cada acción declarada en la sealed interface.
Por ahora solo se implementa el caso de permisos como plantilla:
kotlin when (cameraIntent) { is CameraIntent.SubmitCameraPermissionInfo -> { uiState.value = uiState.value.copy( showCameraRationale = cameraIntent.shouldShowCameraRationale, permissionGranted = cameraIntent.acceptedCameraPermission ) } }
Usar copy sobre el estado actual asegura que solo modificas los campos relevantes y mantienes la inmutabilidad de la data class. Los demás intents quedan listos como ramas vacías para implementarse cuando la UI empiece a emitir capturas y confirmaciones.
Con esta base ya tienes la columna vertebral de la pantalla: intents tipados, un estado único y un ViewModel preparado para recibir acciones desde Compose. ¿Cómo conectarías tu UI con estos intents? Cuéntame en los comentarios qué parte te gustaría profundizar.