Contenido del curso

Servicios de Localización

Kotlin Flows para medir tiempo en Android

Resumen

Controlar el tiempo en proyectos de localización en Android es la diferencia entre una app que drena la batería y una que emite datos con precisión. Aquí construirás un timer con Kotlin Flows que emite intervalos cada 200 milisegundos, ideal para desarrolladores Android que quieren dominar programación reactiva aplicada a geolocalización.

¿Cómo se estructura una clase Timer con Flows en Kotlin?

La idea es crear un singleton sencillo dentro de una carpeta domain que centralice la emisión de tiempo para todo el proyecto.

El punto de partida es un archivo Timer.kt declarado como object Timer, lo que garantiza una única instancia accesible desde cualquier parte del código [01:00]. Dentro de ese objeto vive la función timeAndEmits(), que devuelve un Flow<Duration> y se apoya en la clase Duration de Kotlin para manejar tiempo de forma tipada.

¿Qué es un Flow en Kotlin? Es la API de Kotlin para programación reactiva. Te permite emitir múltiples valores a lo largo del tiempo y reaccionar a ellos con operadores como collect, onEach o combine.

¿Cómo funciona el flow builder con intervalos de 200 ms?

Dentro del flow builder se guarda un lastEmitTime con System.currentTimeMillis() para marcar el inicio. Luego entras a un while(true) que ejecuta un delay(200) y captura el currentTime en cada vuelta.

La diferencia entre currentTime y lastEmitTime se calcula como elapsedTime y se emite en formato milliseconds, que devuelve una instancia de Duration. Al final de cada iteración, lastEmitTime se actualiza con currentTime para que el siguiente ciclo mida el intervalo real transcurrido [02:30].

Este patrón es importante porque el operador delay no es exacto: en pruebas reales aparecen valores de 203 o 204 milisegundos en lugar de 200 clavados, y la lógica de elapsedTime te entrega la duración real, no la teórica [05:30].

¿Cómo activo un Flow desde la MainActivity?

Un Flow no emite nada hasta que alguien lo colecta. Eso se llama cold flow.

En la MainActivity usas lifecycleScope.launch { ... } para lanzar una corrutina ligada al ciclo de vida de la actividad. Dentro invocas Timer.timeAndEmits().collect { ... } y registras cada emisión con Log.d("FlowTimer", "Timer interval emit: $it").

¿Qué es un cold flow? Es un flow que solo empieza a producir valores cuando un operador terminal como collect lo activa. Sin collect, el código del builder nunca se ejecuta.

¿Qué diferencia hay entre collect y launchIn?

Kotlin Flows tiene tres tipos de piezas que conviene distinguir:

  • Builders: construyen la emisión, como flow { }.
  • Operadores intermedios: transforman datos en tránsito, como onEach, zip, combine o scan.
  • Operadores terminales: activan la emisión, como collect o launchIn.

La alternativa más limpia es encadenar Timer.timeAndEmits().onEach { Log.d(...) }.launchIn(lifecycleScope). Aquí onEach reacciona a cada emisión y launchIn actúa como terminal, evitando el bloque manual de launch { collect { } } [07:00].

¿Cómo combino dos Flows con scan y zip?

La parte interesante llega cuando necesitas mezclar tiempo con otra fuente de datos.

Imagina un segundo flow llamado randomFlow que emite un número aleatorio entre 0 y 100 cada segundo. Para combinarlo con el timer aplicas dos operadores en cadena:

  1. scan(Duration.ZERO) { acumulador, value -> acumulador + value }: parte de cero y va sumando cada emisión de 200 ms, dándote el tiempo acumulado.
  2. zip(Timer.randomFlow) { time, random -> time to random }: empareja cada valor de tiempo con un número aleatorio en un Pair.
  3. onEach { Log.d("zipFlow", "value: ${it.first}, random: ${it.second}") }: imprime ambos valores juntos.
  4. launchIn(lifecycleScope): activa la cadena.

Al ejecutar verás que las emisiones bajan de cada 200 ms a cada segundo. La razón es que zip espera a que ambos flows hayan emitido antes de combinar y entregar el par, así que la cadencia la marca el flow más lento [10:00].

kotlin object Timer { fun timeAndEmits(): Flow<Duration> = flow { var lastEmitTime = System.currentTimeMillis() while (true) { delay(200) val currentTime = System.currentTimeMillis() val elapsedTime = currentTime - lastEmitTime emit(elapsedTime.milliseconds) lastEmitTime = currentTime } } }

Con esta base ya puedes conectar el timer a los componentes de localización y controlar exactamente cuándo emitir coordenadas. ¿Qué cadencia usarías tú para tu app de tracking? Cuéntame en los comentarios.