Saltar al contenido

Android – ¿Mejores prácticas para el estado de ViewModel en MVVM?

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.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *