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
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)
TrackingMapViewModel con Hilt y StateFlow
Resumen
Conectar la lógica de rastreo con la interfaz de un mapa requiere un ViewModel bien estructurado que orqueste permisos, estados y emisiones de localización. Aquí aprenderás a construir un TrackingMapViewModel con Hilt, StateFlow e intents para controlar cuándo iniciar, pausar o reanudar el seguimiento en una app Android, ideal si trabajas con Jetpack Compose y arquitectura limpia.
¿Cómo se estructura un TrackingMapViewModel con Hilt?
El punto de partida es crear el archivo dentro del paquete de mapas y anotarlo con @HiltViewModel para que la inyección de dependencias funcione automáticamente [01:00].
La única dependencia que necesitas inyectar es el location tracker, que ya contiene la lógica de observación de ubicación. A partir de ahí, defines tres piezas internas:
- Un
MutableStateFlow<Boolean>llamadohasLocationPermission, inicializado enfalse, que cambia atruecuando el usuario concede permisos. - Un
MutableStateFlow<Boolean>llamadoshouldTrack, que reacciona a las acciones de start, pause o resume. - Un
MutableStateFlow<TrackingLocationState>privado que representa el estado completo de la pantalla.
¿Qué es un MutableStateFlow en un ViewModel? Es un contenedor reactivo que guarda un valor mutable y emite cambios a quien lo observe. Sirve para exponer estado a la UI sin perder consistencia entre recomposiciones.
¿Cómo combinar permisos y estado de tracking en un solo flow?
La magia ocurre cuando combinas hasLocationPermission y shouldTrack dentro de un flow llamado isAllowedToTrack [02:10]. Si ambos son true, activas la observabilidad del location tracker; si no, la detienes.
Esto se logra con el operador onEach, que reacciona a cada emisión: cuando el booleano resultante es true, llamas a startObservingLocation(); cuando es false, ejecutas stopObservingLocation(). Así evitas consumir batería innecesariamente y respetas el ciclo de vida del usuario.
¿Cómo manejar intents para start, pause y resume tracking?
Los intents son la forma limpia de comunicar acciones desde la UI hacia el ViewModel sin acoplar lógica. Defines una función onAction(intent: TrackingIntent) que se ejecuta dentro de viewModelScope para mantener todo en el ámbito correcto del ciclo de vida [03:30].
Dentro de un bloque when, mapeas cada caso:
StartTracking: poneshouldTrack.value = truey llama alocationTracker.setIsTracking(true).ResumeTracking: replica la misma lógica que start para reanudar el flujo.PauseTracking: cambiashouldTrack.valueafalsey detiene la observación.
Recuerda extender la clase de ViewModel; sin esa herencia no tendrás acceso a viewModelScope y el compilador te lo recordará.
¿Cómo se actualiza el estado del ViewModel de forma limpia?
Dentro del bloque init, se inicia la observabilidad de tres fuentes paralelas que alimentan el estado expuesto a la UI:
isAllowedToTrackactualiza el campoisPausedcon el valor inverso (si está habilitado, no está pausado).isTrackingdel location tracker se refleja directamente en el estado.- Las emisiones de localización y los
trackingDataSegmentsse mapean uno a uno al estado del ViewModel.
Cada observación se lanza con launchIn(viewModelScope) para garantizar que se cancele cuando el ViewModel deje de existir.
¿Cómo conectar el ViewModel con la pantalla del mapa?
En MapScreen, el floating action button cambia su comportamiento según el estado [05:40]. Si state.isPaused es true, la acción dispara TrackingIntent.ResumeTracking; en caso contrario, ejecuta TrackingIntent.PauseTracking.
Luego creas un composable llamado MapScreenRoute que actúa como puente: recibe el TrackingMapViewModel vía hiltViewModel(), expone el estado con state.collectAsState() y pasa el método onAction a la pantalla. También deja preparado un parámetro navigateToCamera para una sección posterior.
¿Por qué usar collectAsState en Compose? Convierte un StateFlow en un
State<T>que Compose observa de forma nativa, disparando recomposiciones solo cuando el valor cambia.
En MainActivity, registras la ruta dentro del grafo de navegación inyectando el ViewModel con hiltViewModel<TrackingMapViewModel>().
¿Por qué la polilínea se dibuja antes de iniciar el recorrido?
Al probar la app en el emulador con una ruta simulada (Lucky Location → Routes → Play Route), aparece un detalle: la polilínea se traza incluso sin presionar play [08:20]. El problema viene de código heredado en MainActivity que activaba startObservingLocation automáticamente tras dos segundos.
La solución tiene dos pasos:
- Eliminar esas líneas residuales de ejercicios anteriores en
MainActivity. - Asegurar que
hasLocationPermissionse ponga entruesolo cuando los permisos estén realmente concedidos, verificándolos en App Info del dispositivo.
Tras relanzar la app, el comportamiento es el esperado: el marcador salta al punto A, la cámara sigue en perspectiva al usuario y la polilínea se dibuja en tiempo real conforme avanza la simulación. Al pulsar pausa se detiene; al pulsar play, se reanuda el seguimiento desde donde quedó.
Con esto tienes un sistema de rastreo de localización reutilizable, listo para integrarse en cualquier proyecto que requiera geolocalización continua. ¿Cómo planeas adaptarlo a tu propia app? Cuéntame en los comentarios qué retos has tenido al manejar permisos dinámicos en Android.