StateFlow, el sustituto natural de LiveData
Antonio Leiva

Llevamos una serie de capítulos dedicados a Flow, donde hemos visto desde los conceptos básicos de Flow hasta cómo usar Flow en un ejemplo real.

Incluso hemos visto cómo Flow se integra con Room.

Pero justo en este artículo anterior nos encontramos un problema: no podemos lanzar corrutinas a lo loco cuando se actualiza el scroll del adapter, porque nos podemos encontrar con que se lanza la misma petición a la API antes de que la anterior haya acabado.

Lo ideal sería tener una cola de peticiones, donde hasta que no acabe la anterior no se procese la siguiente. Pero… ¡esto es justo un Flow!

Si recuerdas, hablamos de que los flows emiten sus valores en secuencia, y por tanto la recolección también se hace en secuencia: hasta que no acaba de recolectarse un valor, no se genera el siguiente.

Aquí es donde entra en juego StateFlow, un Flow dedicado que te va a resultar familiar

¿Qué es StateFlow?

StateFlow es un Flow muy particular, porque:

  • Maneja un único valor en su campo value
  • Cada vez que se modifica, los recolectores asociados a él reciben una actualización
  • Nada más suscribirse, recibe el último valor asignado a value
  • Existen 2 variantes, StateFlow, que es inmutable (no se puede modificar value, y MutableStateFlow

En general, StateFlow sirve para almacenar un estado, y que los cambios en ese estado puedan ser escuchados de forma reactiva.

¿Te suena esto de algo? Es exactamente la definición de LiveData, pero aplicado a los Flows.

Sustituyendo LiveData por StateFlow

Antes de solucionar nuestro problema, te voy a mostrar cómo puedes usar StateFlow en lugar de LiveData.

En el ViewModel teníamos este código:

private val _spinner = MutableLiveData<Boolean>()
val spinner: LiveData<Boolean> get() = _spinner

Aquí es fácil. Solo tenemos que cambiar los LiveData por StateFlow:

private val _spinner = MutableStateFlow(true)
val spinner: StateFlow<Boolean> get() = _spinner

Solo hay un peuqeño cambio, y es que se nos pide un valor inicial, que le vamos a asignar true, y así nos olvidamos de hacerlo en el init.

Si ves que no te encuentra estas clases, es porque necesitas añadir una versión posterior de las corrutinas. En el momento de escribir este artículo, esta era la última versión existente:

implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.1'

En la activity también hay que cambar cosas. Antes teníamos:

viewModel.spinner.observe(this@MainActivity, { progress.visible = it })

Y ahora tendremos:

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

Si no consigues importar lifecycleScope es porque necesitas la siguiente dependencia:

implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.2.0'

Además, cuando lo único que queremos es escuchar los cambios de un Flow, tenemos una opción más sencilla que abrir un bloque de launch. Podemos indicarle sencillamente en qué scope queremos que se lance:

viewModel.spinner
    .onEach { progress.visible = it }
    .launchIn(lifecycleScope)

En este caso no te lo recomiendo, porque queremos que se lance cuando la activity se inicia y que pare de escucharse cuando la activity para.

Solucionando nuestro problema con StateFlow

En vez de lanzar una corrutina cada vez que informamos de una nueva actualización del estado del scroll, lo podemos hacer mediante un StateFlow editable que dejemos público en el ViewModel:

val lastVisible = MutableStateFlow(0)

Desde la Activity solo tendríamos que hacer:

recycler.addOnScrollListener(object : RecyclerView.OnScrollListener() {
    override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
        viewModel.lastVisible.value = layoutManager.findLastVisibleItemPosition()
    }
})

Y desde el ViewModel, observar los cambios de este StateFlow:

init {
    viewModelScope.launch {
        lastVisible.collect { notifyLastVisible(it) }
    }
}

private suspend fun notifyLastVisible(lastVisible: Int) {
    repository.checkRequireNewPage(lastVisible)
    _spinner.value = false
}

Conclusión

¡Pues ya está! Con esto ya hemos dado nuestros primeros pasos para entender cómo funciona Flow integrado con el framework de Android, y cómo algunas librerías ya lo soportan.

Si te interesa este tema y quieres ver otros ejemplos más particulares, déjame en los comentarios sugerencias y me plantearé hacerlo para siguientes vídeos.

Quizá también te interese…

¿Qué es Kotlin Multiplataforma?

¿Qué es Kotlin Multiplataforma?

En el mundo actual, donde los dispositivos móviles están presentes en nuestra vida diaria, es fundamental para los desarrolladores crear aplicaciones que se adapten a diferentes sistemas operativos. Kotlin Multiplataforma es una herramienta que facilita la creación de...

5 trucos de Kotlin para escribir código más eficiente en Android

5 trucos de Kotlin para escribir código más eficiente en Android

El lenguaje de programación Kotlin se ha convertido en el más popular para el desarrollo de aplicaciones de Android en los últimos años. Su sintaxis concisa y moderna, junto con su capacidad para mejorar la eficiencia de código, lo convierten en una opción atractiva...

Cómo crear un backend en Kotlin usando Ktor

Cómo crear un backend en Kotlin usando Ktor

Ktor es un framework de servidor web ligero y rápido para Kotlin, desarrollado por JetBrains. Es ideal para crear aplicaciones web y servicios RESTful, y es muy fácil de usar y configurar. En este artículo, vamos a ver cómo crear un backend para una aplicación de...

6 Comentarios

  1. Alfredo

    En el caso de que quisiésemos realizar una llamada a una API solo una vez, StateFlow y Flow sería demasiado? Porque no nos hace falta escuchar cambios continuamente, no?

    Responder
    • Antonio Leiva

      No, pero aún así necesitas algún tipo de componente observable para escuchar desde la UI si usas el patrón MVVM, así que necesitas StateFlow o LiveData.

      Responder
  2. Manuel castillo

    Y si tuvieramos que elegir entre FLOW y LIVEDATA…. en que deberiamos poner atencion?

    Responder
    • Antonio Leiva

      Si ya estás usando corrutinas, usa StateFlow. Si no, deberías usar corrutinas 😄

      Ya en serio, LiveData tiene sentido si tu código aún es Java o si no usas corrutinas. Para el resto de casos, merece la pena utilizar StateFlow.

      Responder
  3. Carlos Daniel Rodríguez Roche

    Cómo puedo modificar un StateFlow desde la UI y que vuelva a escuchar los cambios desde la UI ?? … por ejemplo, la llamada al API depende de un ID que se debe modificar desde la UI

    Responder
    • Antonio Leiva

      La UI llamará al ViewModel, que se comunicará con la capa de datos para devolver los cambios a la UI. En ese momento, el ViewModel actualiza el StateFlow, y eso actualiza la interfaz. No sé si te sigo exactamente con la pregunta, pero ese es el proceso.

      Responder

Enviar un comentario

Los datos personales que proporciones a través de este formulario quedarán registrados en un fichero de DevExpert, S.L.U., 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 *