2 formas de recolectar Flows en la UI que SÍ funcionan
Antonio Leiva

En la serie de artículos sobre Programación Reactiva con Flow hemos visto muchos conceptos, y hemos aprendido cómo aplicarlos al desarrollo Android.

Pero hay algo que no hemos hecho del todo bien. Esto es la recolección de Flows desde la Activity (o el Fragment, en caso de que los uses).

Hemos llegado hasta donde las herramientas de Android hoy en días nos permiten, pero esto tiene un pequeño problema.

Veamos este caso:

lifecycleScope.launchWhenStarted {
    viewModel.spinner.collect { visible ->
        progress.visible = visible
    }
}

Esto está muy bien, porque nos estamos asegurando de que cuando la activity esté en el estado onStop, la interfaz no intente actualizarse con los resultados que se recolectan.

Pero seguimos teniendo un problema. Imagina que estamos recolectando datos de un Flow que cada x segundos (o basado en ciertas reglas) sigue haciendo peticiones a un servidor, siempre y cuando haya alguien recolectando.

En este caso, el Flow no va a dejar de trabajar, y por tanto estamos haciendo peticiones innecesarias. Sí, nuestra App no explota por intentar actualizarla en segundo plano, pero tenemos una App haciendo cosas que no hacen falta.

¿Cómo podemos mejorar esto? Hay 2 opciones

Opción 1: Crear un Scope propio de corrutinas

Todas estas cosas que nos ahorran código están muy bien, pero nos hacen perder control sobre lo que está ocurriendo.

Si somos explícitos sobre cuándo creamos un scope y cuándo lo cancelamos, entonces sabemos exactamente qué está pasando en cada momento.

Si no sabes sobre scopes, aquí tienes una pequeña explicación y en este otro artículo te lo explico más a fondo.

La forma más sencilla de hacerlo es que la Activity implemente CoroutineScope. Para ello. Esto nos obliga también a tener un CoroutineContext:

class MainActivity : AppCompatActivity(), CoroutineScope {

    override val coroutineContext: CoroutineContext
        get() = TODO("Not yet implemented")
}

Ese CoroutineScope, como ya explicaba en los otros recursos que te enlacé, requiere de un Job y un Dispatcher:

private lateinit var job: Job

override val coroutineContext: CoroutineContext
   get() = Dispatchers.Main + job

Este contexto va a ejecutar siempre por defecto su código en el Main dispatcher, y va a usar ese Job padre que controlará todas las corrutinas asociadas que se ejecuten.

¿Qué tenemos que hacer ahora? Simplemente crear y cancelar ese Job cuando lo necesitemos.

Si lo que queremos es que todo empiece en el onStart y acabe en el onStop, solo necesitamos este código:

override fun onStart() {
    super.onStart()
    job = SupervisorJob()
}

override fun onStop() {
    job.cancel()
    super.onStop()
}

Creamos el Job en el onStart y lo cancelamos en el onStop. Ya está, problema resuelto.

Es verdad que es un poco más de código, pero bien encapsulado no debería ser mayor problema.

Y ahora es muy sencillo empezar a recolectar un Flow:

launch {
    viewModel.spinner.collect { progress.visible = it }
}

Ahora, el equipo de Android siempre está tratando de evitarnos escribir código repetitivo, así que nos dan otra opción.

Opción 2: LifecycleOwner.addRepeatingJob

En las nuevas alphas de la librería lifecycle-runtime-ktx se ha incluido esta nueva función, que puede hacer lo mismo sin necesidad de crear el scope manualmente.

La función addRepeatingJob recibe como parámetro el momento en el que esa tarea debe empezar a ejecutarse (el job se crea), y por tanto lo cancelará en el estado opuesto.

Para ello, necesitamos añadir esta versión de la librería o superiores:

implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.4.0-alpha01'

Ahora, la única modificación que necesitaríamos en nuestro código original sería sustituir la llamada a launchWhenStarted con esta nueva función:

addRepeatingJob(Lifecycle.State.STARTED) {
    viewModel.spinner.collect { progress.visible = it }
}

Como ves, muy sencillo y además muy versátil, porque puedes definir distintos jobs que sobrevivan a distintos estados de la Activity o el Fragment.

Esto también nos da la opción de usar un equivalente a flowOn que ya vimos en el artículo de los conceptos básicos sobre Flows:

viewModel.movies.flowWithLifecycle(
    lifecycle,
    Lifecycle.State.STARTED
)

Como nota curiosa, aquí el estado STARTED es el valor por defecto, así que no haría falta ni ponerlo (en addRepeatingJob sí!):

viewModel.movies.flowWithLifecycle(lifecycle)

Google está a tope de Flow

Pues sí, está claro que la programación reactiva ha venido para quedarse (no hay más que ver la incursión de Jetpack Compose en nuestras vidas, o los ViewModels con LiveData), y es muy evidente que Flow es la solución natural.

La solución nativa del lenguaje, integrado con corrutinas, e independiente del framework de Android, han venido para quedarse, y con esta serie de artículos, espero que te quede más claro cómo utilizarlos.

Si te queda cualquier duda o quieres que amplíe algún tema, no dudes en usar los comentarios.

Quizá también te interese…

Kotlin 1.5.0 : Las 5 novedades que puedes empezar a usar hoy

Kotlin 1.5.0 : Las 5 novedades que puedes empezar a usar hoy

Kotlin 1.5.0 ya está aquí, y como siempre trae una serie de novedades que te van a interesar muchísimo. Cabe destacar que a partir de ahora, de acuerdo las nuevas versiones de Kotlin se lanzarán cada 6 meses, independientemente de las nuevas funcionalidades que...

¿Qué es Kotlin y para qué sirve?

¿Qué es Kotlin y para qué sirve?

Kotlin es un lenguaje de programación de código abierto creado por JetBrains que se ha popularizado gracias a que se puede utilizar para programar aplicaciones Android. Pero si has llegado hasta aquí pensando que Kotlin solo se puede usar en Android, lo que te voy a...

Cómo hacer tests de Corrutinas y Flows – Paso a Paso

Cómo hacer tests de Corrutinas y Flows – Paso a Paso

¡Vaya viaje por el que hemos pasado en estos artículos! Hace ya varios de ellos empezamos hablando sobre la programación reactiva con Flow, y hemos aprendido un montón de conceptos e ideas sobre cómo aplicarlos en el día a día. Pero nada de esto está completo si no...

0 comentarios

Enviar un comentario

Los datos personales que proporciones a través de este formulario quedarán registrados en un fichero de Antonio Leiva Gordillo, con el fin de gestionar los comentarios que realizas en este blog. La legitimación se realiza a través del consentimiento de la parte interesada. Si no se acepta, no podrás comentar en este blog. Los datos que proporciona solo se utilizan para evitar el correo no deseado y no se usarán para nada más. Puede ejercer los derechos de acceso, rectificación, cancelación y oposición en contacto@devexperto.com.

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Acepto la política de privacidad *