Observando LiveData desde ViewModel

90

Tengo una clase separada en la que manejo la obtención de datos (específicamente Firebase) y generalmente devuelvo objetos LiveData y los actualizo de forma asincrónica. Ahora quiero tener los datos devueltos almacenados en un ViewModel, pero el problema es que para obtener dicho valor, necesito observar el objeto LiveData devuelto por mi clase de obtención de datos. El método de observación requería un objeto LifecycleOwner como primer parámetro, pero obviamente no lo tengo dentro de mi ViewModel y sé que se supone que no debo mantener una referencia a la Actividad / Fragmento dentro de ViewModel. ¿Qué tengo que hacer?

Vuk Bibic
fuente

Respuestas:

38

En esta publicación de blog del desarrollador de Google, José Alcérreca, se recomienda usar una transformación en este caso (ver el párrafo "LiveData en repositorios") porque ViewModel no debería contener ninguna referencia relacionada con View(Actividad, Contexto, etc.) porque lo hacía difícil. Probar.

guglhupf
fuente
¿Ha logrado que Transformation funcione para usted? Mis eventos no funcionan
romaneso
23
Las transformaciones por sí solas no funcionan, ya que cualquier código que escriba en la transformación solo se adjunta para ejecutarse cuando alguna entidad observa la transformación .
orbitbot
5
No sé por qué esta es la respuesta recomendada, no tiene nada que ver con la pregunta. 2 años después, y todavía no sabemos cómo observar los cambios en los datos del repositorio en nuestro modelo de vista.
Andrew
24

En la documentación de ViewModel

Sin embargo, los objetos ViewModel nunca deben observar cambios en los observables conscientes del ciclo de vida, como los objetos LiveData.

Otra forma es que los datos implementen RxJava en lugar de LiveData, entonces no tendrá el beneficio de ser consciente del ciclo de vida.

En la muestra de Google de todo-mvvm-live-kotlin , usa una devolución de llamada sin LiveData en ViewModel.

Supongo que si desea cumplir con la idea completa de ser un ciclo de vida, debemos mover el código de observación en Actividad / Fragmento. De lo contrario, podemos usar callback o RxJava en ViewModel.

Otro compromiso es implementar MediatorLiveData (o Transformations) y observar (ponga su lógica aquí) en ViewModel. Tenga en cuenta que el observador MediatorLiveData no se activará (igual que Transformaciones) a menos que se observe en Actividad / Fragmento. Lo que hacemos es poner una observación en blanco en Activity / Fragment, donde el trabajo real se realiza en ViewModel.

// ViewModel
fun start(id : Long) : LiveData<User>? {
    val liveData = MediatorLiveData<User>()
    liveData.addSource(dataSource.getById(id), Observer {
        if (it != null) {
            // put your logic here
        }
    })
}

// Activity/Fragment
viewModel.start(id)?.observe(this, Observer {
    // blank observe here
})

PD: leí ViewModels y LiveData: Patterns + AntiPatterns que sugirieron que Transformations. No creo que funcione a menos que se observe LiveData (lo que probablemente requiera que se haga en Activity / Fragment).

Desmond Lua
fuente
2
¿Cambió algo al respecto? ¿O RX, devolución de llamada o observación en blanco son solo soluciones?
qbait
2
¿Alguna solución para deshacerse de estas observaciones en blanco?
Ehsan Mashhadi
1
Quizás usando Flow ( mLiveData.asFlow()) o observeForever.
Machado
La solución de flujo parece funcionar si no desea tener / no necesita ninguna lógica de observador en Fragmento
adek111
14

Creo que puede usar observeForever, que no requiere la interfaz del propietario del ciclo de vida y puede observar los resultados del modelo de vista

Siddharth
fuente
2
esta me parece la respuesta correcta, especialmente que en los documentos sobre ViewModel.onCleared () se dice: "Es útil cuando ViewModel observa algunos datos y necesita borrar esta suscripción para evitar una fuga de este ViewModel".
Yosef
2
Lo siento, peroCannot invoke observeForever on a background thread
Boken
1
Eso parece bastante legítimo. Aunque uno tiene que guardar observadores en los campos viewModel y darse de baja en onCleared. En cuanto al hilo de fondo, observe desde el hilo principal, eso es todo.
Kirill Starostin
@Boken Puede forzar observeForevera ser invocado desde la vía principalGlobalScope.launch(Dispatchers.Main) { myvm.observeForever() }
rmirabelle
4

Utilice corrutinas de Kotlin con componentes de Arquitectura.

Puede utilizar la liveDatafunción del constructor para llamar a una suspendfunción, sirviendo el resultado como un LiveDataobjeto.

val user: LiveData<User> = liveData {
    val data = database.loadUser() // loadUser is a suspend function.
    emit(data)
}

También puede emitir varios valores desde el bloque. Cada emit()llamada suspende la ejecución del bloque hasta que el LiveDatavalor se establece en el hilo principal.

val user: LiveData<Result> = liveData {
    emit(Result.loading())
    try {
        emit(Result.success(fetchUser()))
    } catch(ioException: Exception) {
        emit(Result.error(ioException))
    }
}

En su configuración de gradle, use androidx.lifecycle:lifecycle-livedata-ktx:2.2.0o superior.

También hay un artículo al respecto.

Actualización : también es posible cambiar LiveData<YourData>en el Dao interface. Debe agregar la suspendpalabra clave a la función:

@Query("SELECT * FROM the_table")
suspend fun getAll(): List<YourData>

y en el ViewModelnecesita obtenerlo de forma asincrónica así:

viewModelScope.launch(Dispatchers.IO) {
    allData = dao.getAll()
    // It's also possible to sync other data here
}
Psijic
fuente