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.
Como siempre todos los comentarios y dudas son más que bienvenidos. Te invito a visitar mi blog hackaprende.com para ver más artículos y cursos sobre diseño, programación y emprendimiento tecnológico.
Top comments (1)
Hola tienes link al repositorio?
Por cierto como instancias el Firebase db?
Y donde se lo pasas al repositorio? El repositorio lo instancias dentro del view model y desde ahí pasas el Firebase db?