Nuestro grupo redactor ha estado mucho tiempo investigando la solución a tu interrogante, te dejamos la soluciones y esperamos servirte de mucha apoyo.
Solución:
Luché con el mismo problema en el trabajo y puedo compartir lo que nos está funcionando. Estamos desarrollando al 100% en Kotlin, por lo que los siguientes ejemplos de código también lo estarán.
Estado de la interfaz de usuario
Para prevenir el ViewModel
de hincharse con un montón de LiveData
propiedades, exponer una sola ViewState
para vistasActivity
o Fragment
) para observar. Puede contener los datos previamente expuestos por los múltiples LiveData
y cualquier otra información que la vista pueda necesitar para mostrarse correctamente:
data class LoginViewState (
val user: String = "",
val password: String = "",
val checking: Boolean = false
)
Tenga en cuenta que estoy usando una clase de datos con propiedades inmutables para el estado y deliberadamente no uso ningún recurso de Android. Esto no es algo específico de MVVM, pero un estado de vista inmutable evita inconsistencias en la interfaz de usuario y problemas de subprocesos.
Dentro de ViewModel
crear un LiveData
propiedad para exponer el estado e inicializarlo:
class LoginViewModel : ViewModel()
private val _state = MutableLiveData()
val state : LiveData get() = _state
init
_state.value = LoginViewState()
Para luego emitir un nuevo estado, use el copy
función proporcionada por la clase Data de Kotlin desde cualquier lugar dentro del ViewModel
:
_state.value = _state.value!!.copy(checking = true)
En la vista, observe el estado como lo haría con cualquier otro LiveData
y actualice el diseño en consecuencia. En la capa Vista, puede traducir las propiedades del estado a visibilidades de vista reales y usar recursos con acceso completo a la Context
:
viewModel.state.observe(this, Observer
it?.let
userTextView.text = it.user
passwordTextView.text = it.password
checkingImageView.setImageResource(
if (it.checking) R.drawable.checking else R.drawable.waiting
)
)
Combinar múltiples fuentes de datos
Dado que probablemente expuso anteriormente los resultados y los datos de la base de datos o las llamadas de red en el ViewModel
, puedes usar un MediatorLiveData
para combinarlos en un solo estado:
private val _state = MediatorLiveData()
val state : LiveData get() = _state
_state.addSource(databaseUserLiveData, name ->
_state.value = _state.value!!.copy(user = name)
)
...
El enlace de datos
Desde un unificado, inmutable ViewState
esencialmente rompe el mecanismo de notificación de la biblioteca de enlace de datos, estamos usando un mutable BindingState
que se extiende BaseObservable
para notificar selectivamente el diseño de cambios. Proporciona un refresh
función que recibe el correspondiente ViewState
:
Actualización: se eliminaron las declaraciones if que verifican los valores modificados, ya que la biblioteca de enlace de datos ya se encarga de representar solo los valores realmente modificados. Gracias a @CarsonHolzheimer
class LoginBindingState : BaseObservable()
@get:Bindable
var user = ""
private set(value)
field = value
notifyPropertyChanged(BR.user)
@get:Bindable
var password = ""
private set(value)
field = value
notifyPropertyChanged(BR.password)
@get:Bindable
var checkingResId = R.drawable.waiting
private set(value)
field = value
notifyPropertyChanged(BR.checking)
fun refresh(state: AngryCatViewState)
user = state.user
password = state.password
checking = if (it.checking) R.drawable.checking else R.drawable.waiting
Cree una propiedad en la vista de observación para el BindingState
y llama refresh
desde el Observer
:
private val state = LoginBindingState()
...
viewModel.state.observe(this, Observer it?.let state.refresh(it) )
binding.state = state
Luego, use el estado como cualquier otra variable en su diseño:
...
...
Información avanzada
Algunas de las plantillas estándar definitivamente se beneficiarían de las funciones de extensión y las propiedades delegadas, como actualizar el ViewState
y notificando cambios en el BindingState
.
Si desea más información sobre el estado y el manejo del estado con los componentes de la arquitectura utilizando una arquitectura “limpia”, puede consultar Eiffel en GitHub.
Es una biblioteca que creé específicamente para manejar estados de vista inmutables y enlace de datos con ViewModel
y LiveData
así como unirlo con las operaciones del sistema Android y los casos de uso empresarial. La documentación es más profunda de lo que puedo proporcionar aquí.
Flujo de datos unidireccional de Android (UDF) 2.0
Actualización 18/12/2019: Flujo de datos unidireccional de Android con LiveData – 2.0
He diseñado un patrón basado en el Flujo de datos unidireccional utilizando Kotlin con Datos en tiempo real.
UDF 1.0
Echa un vistazo al completo Medio publicar o YouTube hable para obtener una explicación en profundidad.
Medio: flujo de datos unidireccional de Android con LiveData
YouTube – Flujo de datos unidireccional – Adam Hurwitz – Medellín Android Meetup
Descripción general del código
Paso 1 de 6: definir modelos
ViewState.kt
// Immutable ViewState attributes.
data class ViewState(val contentList:LiveData>, ...)
// View sends to business logic.
sealed class ViewEvent
data class ScreenLoad(...) : ViewEvent()
...
// Business logic sends to UI.
sealed class ViewEffect
class UpdateAds : ViewEffect()
...
Paso 2 de 6: pasar eventos a ViewModel
Fragment.kt
private val viewEvent: LiveData> get() = _viewEvent
private val _viewEvent = MutableLiveData>()
override fun onCreate(savedInstanceState: Bundle?)
...
if (savedInstanceState == null)
_viewEvent.value = Event(ScreenLoad(...))
override fun onResume()
super.onResume()
viewEvent.observe(viewLifecycleOwner, EventObserver event ->
contentViewModel.processEvent(event)
)
Paso 3 de 6 – Procesar eventos
ViewModel.kt
val viewState: LiveData get() = _viewState
val viewEffect: LiveData> get() = _viewEffect
private val _viewState = MutableLiveData()
private val _viewEffect = MutableLiveData>()
fun processEvent(event: ViewEvent) {
when (event)
is ViewEvent.ScreenLoad ->
// Populate view state based on network request response.
_viewState.value = ContentViewState(getMainFeed(...),...)
_viewEffect.value = Event(UpdateAds())
...
Paso 4 de 6: gestione las solicitudes de red con el patrón LCE
LCE.kt
sealed class Lce
class Loading : Lce()
data class Content(val packet: T) : Lce()
data class Error(val packet: T) : Lce()
Result.kt
sealed class Result
data class PagedListResult(
val pagedList: LiveData>?,
val errorMessage: String): ContentResult()
...
Repository.kt
fun getMainFeed(...)= MutableLiveData>().also lce ->
lce.value = Lce.Loading()
/* Firestore request here. */.addOnCompleteListener
// Save data.
lce.value = Lce.Content(ContentResult.PagedListResult(...))
.addOnFailureListener
lce.value = Lce.Error(ContentResult.PagedListResult(...))
Paso 5 de 6: gestionar los estados de LCE
ViewModel.kt
private fun getMainFeed(...) = Transformations.switchMap(repository.getFeed(...)) {
lce -> when (lce) {
// SwitchMap must be observed for data to be emitted in ViewModel.
is Lce.Loading -> Transformations.switchMap(/*Get data from Room Db.*/)
pagedList -> MutableLiveData>().apply
this.value = pagedList
is Lce.Content -> Transformations.switchMap(lce.packet.pagedList!!)
pagedList -> MutableLiveData>().apply
this.value = pagedList
is Lce.Error ->
_viewEffect.value = Event(SnackBar(...))
Transformations.switchMap(/*Get data from Room Db.*/)
pagedList -> MutableLiveData>().apply
this.value = pagedList
Paso 6 de 6 – ¡Observe el cambio de estado!
Fragment.kt
contentViewModel.viewState.observe(viewLifecycleOwner, Observer viewState ->
viewState.contentList.observe(viewLifecycleOwner, Observer contentList ->
adapter.submitList(contentList)
)
...
Recuerda algo, que tienes la opción de añadir una estimación objetiva si diste con la respuesta.