Usando Corrutinas con Firebase en Android MVVM

Estoy desarrollando un proyecto alterno que utiliza la arquitectura MVVM y Firebase/Firestore, para todo el manejo de los hilos y tareas en segundo plano estoy usando corrutinas (coroutines).

Me acabo de topar con una situación que no me había sucedido, al usar Firebase, este trae sus propios listeners para saber cuando termina un proceso, el cual regresa un DocumentSnapshot que es el que necesitas para extraer los datos. Algo así:

class MyRepository(private val firestoreDb: FirestoreDb) {
    
    suspend fun downloadSomething(listener: (DocumentSnapshot) -> Unit){
        firestoreDb.collection(collectionName).get()
            .addOnCompleteListener {
                task ->
            if (task.isSuccessful) {
               // Do something ...
               listener(myDocumentSnapshot)
            } else {
               // Show error
            }
        }
    }
}

Esto es llamado desde el ViewModel el cual se vería así.

viewModelScope.launch {
    repository.downloadSomething {
        documentSnapshot ->
        // Do Something
    }
}

Pero uno de los puntos principales de las corrutinas es que no necesitemos integrar interfaces y/o listeners para devolver los datos, sino que los podamos devolver en la misma linea para continuar con nuestro código, es decir, en vez de esto:

fun downloadSomething() {
repository.downloadSomething {
result ->
processResult(result)
}
}

Con las corrutinas podemos hacer esto:

suspend fun downloadSomething() {
val result = repository.downloadSomething()
processResult(result)
}

Pero tal y como funciona normalmente Firebase no se puede porque como dije antes, ya trae ese listener .addOnCompleteListener integrado, entonces ¿Cómo lo manejamos para poder usar el resultado con corrutinas? La respuesta que encontré fue usar .await()

await() nos ayuda a esperar a que un proceso se complete sin bloquear otros hilos (Como el Main Thread), haciendo que evitemos tener que llamar un listener una vez que se complete, ya que al terminar se ejecutará la siguiente linea de código del hilo. Pero await() no viene con Firebase sino con las Kotlin Coroutines, por lo que tenemos que agregarlo como dependencia:

implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.1.1'

Ahora que ya podemos usar await() el código de Firebase quedaría así:

class MyRepository(private val firestoreDb: FirestoreDb) {

    suspend fun downloadSomething(): DocumentSnapshot {
        return withContext(Dispatchers.IO) {
            firestoreDb.collection(collectionName).get().await()
        }
    }
}

Con esto podemos regresar el DocumentSnapshot y continuar con nuestro trabajo de forma secuencial en el ViewModel:

viewModelScope.launch {
    val documentSnapshot = repository.downloadSomething()
    // Continuar secuencialmente
}

Esto trae ventajas para hacer testing, para manejar excepciones, para cumplir con el MVVM y para manejar los LiveData.

NOTA: Esto solo funciona para cuando usas get().addOnCompleteListener(), todavía no he encontrado la manera de hacerlo funcionar con cambios en tiempo real con addSnapshotListener(), buscaré la manera para postear.

Como siempre todos los comentarios y dudas son más que bienvenidos.

Publicado por Jesus Almaral

Soy ingeniero en Mecatrónica con maestría en Machine Learning, tengo experiencia en lenguajes como Java, Kotlin, Matlab, Android, Python, etc. Actualmente soy desarrollador de aplicaciones móviles, me gusta la música y toco la guitarra, me gusta mucho saber cosas sobre el universo, leer y comer tacos. También me apasiona enseñar.

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión /  Cambiar )

Google photo

Estás comentando usando tu cuenta de Google. Cerrar sesión /  Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión /  Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión /  Cambiar )

Conectando a %s

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios .

A %d blogueros les gusta esto: