Usando Flow en un proyecto Android
Antonio Leiva

En el artículo anterior veíamos un ejemplo sobre Flow, pero eran los típicos ejemplos que en el día a día no nos llevan a ninguna parte.

¿Cómo encaja todo esto dentro de una aplicación Android?

Hoy vamos a ver un ejemplo

La App

Vamos a usar de base una aplicación relativamente sencilla, un listado de películas que se recuperan de un servidor y se almacenan en una base de datos.

Esto usando una arquitectura también muy sencilla con un patrón de presentación MVVM y un repository que se encarga de los datos. Si quieres saber más, echa un vistazo a mi artículo sobre Clean Architecture, aunque en este caso me he saltado alguna capa para simplificar el ejemplo.

El repository tiene dos fuentes de datos: el servidor, y la base de datos. Si encuentra datos en la base de datos, devolverá estos, y si no se irá al servidor para buscarlos.

    suspend fun getMovies(): List<Movie> {
        if (localDataSource.isEmpty()) {
            val movies =remoteDataSource.getMovies()
            localDataSource.saveMovies(movies)
        }

        return localDataSource.getMovies()
    }

Todo el código está montado con corrutinas, de ahí que el repositorio sea una función suspend. También se está usando la compatibilidad con corrutinas de Room y de Retrofit.

Creando un Flow

Tal y como está montado el proyecto, no tiene mucho sentido usar un Flow, porque solo vamos a recibir datos una única vez del repositorio.

Flow merece la pena usarlo cuando la fuente de datos está “viva”, en el sentido de que va variando en el tiempo y nosotros queremos escuchar esos cambios.

Así que vamos a hacer un pequeño hack. Vamos a simular con un aleatorio que los valores cambian cada cierto tiempo.

Empezamos creando el Flow. Para devolver un Flow no hace falta usar una función suspend, ya que el Flow no se ejecuta hasta que lo recolectemos, y ahí es donde sí que necesitamos una corrutina.

fun getMovies(): Flow<List<Movie>> = flow {
    ...
}

Ahora tenemos que decirle qué valores emitir. Volvemos a poner el código anterior:

fun getMovies(): Flow<List<Movie>> = flow {
    if (localDataSource.isEmpty()) {
        val movies =
            remoteDataSource.getMovies()
        localDataSource.saveMovies(movies)
    }
    ...
}

Pero en vez del de devolver el resultado, lo emitimos:

fun getMovies(): Flow<List<Movie>> = flow {
    if (localDataSource.isEmpty()) {
        val movies =
            remoteDataSource.getMovies()
        localDataSource.saveMovies(movies)
    }

    emit(localDataSource.getMovies())
}

Ya estaría transformado. Lo único que este Flow solo emite un valor, así que no nos aporta demasiado. Para darle un poco de vidilla, haríamos:

while (true) {
    delay(2000)
    emit(localDataSource.getMovies().shuffled())
}

Y así emitimos valores de forma infinita. No nos tenemos por qué preocupar porque el Flow acabe. Cuando el recolector se desconecte o el scope de la corrutina desaparezca, esta corrutina también se cancelará, ya que delay comprueba si la corrutina está finalizada antes de ejecutarse.

Cómo recolectar los datos del Flow

Ahora tienes dos maneras de hacer esto. La más directa es usar el collect que ya vimos en el artículo anterior:

viewModelScope.launch {
    repository.getMovies().collect { list ->
        _movies.value = list
    }
}

Pero con Android-KTX, tenemos una función que nos permite convertir un Flow en un LiveData. En el build.gradle añade la siguiente dependencia:

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

Y a partir de aquí podrás utilizar la función asLiveData() sobre cualquier Flow:

val movies: LiveData<List<Movie>> 
    get() = repository.getMovies().asLiveData()

Ejecuta el proyecto, y mira cómo la App baila 💃

Conclusión

Si te fijas, usar Flow es muy sencillo una vez que tienes todos los conceptos de corrutinas asimilados. El API es tan simple que en unos minutos puedes estar creando tus propios Flows y comunicándote de forma asíncrona con la interfaz de usuario.

Además con las utilidades que incluyen las librerías de Android, trabajar con Flow cada día es más y más fácil.

Pero esto no se queda aquí. En el próximo artículo vamos a ver cómo integrar Room con Flow y haremos un ejemplo sencillo de paginación, para que veas un caso aún más real.

Quizá también te interese…

Temas, colores, tipografías y formas en Jetpack Compose

Temas, colores, tipografías y formas en Jetpack Compose

Si vienes del sistema clásico de vistas, recordarás que toda la definición de temas se hacía de una forma bastante tediosa a través de styles en XML. Si odias tu vida (o estás en una App donde mezclas XMLs y Jetpack Compose), aún puedes seguir usando esos temas...

Usando Cards de Material Design en Jetpack Compose

Usando Cards de Material Design en Jetpack Compose

Tenemos ya una App de lo más resultona después de todos los artículos que hemos visto hasta ahora. Puedes encontrarlos todos bien ordenaditos de forma gratuita en el área privada de Compose Expert. https://youtu.be/iZiXpWRIl3U Pero antes de finalizar nuestro camino,...

Navegación en Jetpack Compose con Navigation Compose

Navegación en Jetpack Compose con Navigation Compose

Jetpack Compose es un cambio de paradigma enorme en muchos aspectos. Cambia la forma de pensar en casi todos los puntos involucrados en el desarrollo de una App Android. Y la navegación no iba a ser menos. ¿Cómo se navega en Jetpack Compose? ¿Qué opciones tenemos?...

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 *