Entonces, ¿por qué necesitamos MVI en el desarrollo móvil?

Ya se ha dicho mucho sobre MVI, sobre cómo freírlo y configurarlo correctamente. Sin embargo, no se dedica mucho tiempo a cómo este método simplifica la vida en determinadas situaciones, en comparación con otros enfoques.



Objeto de este artículo



No profundizaré en cómo se implementa técnicamente MVI (hay más de una forma y cada una tiene sus pros y sus contras). Mi objetivo principal en un artículo breve es interesarle para que estudie este tema en el futuro y posiblemente animarle a implementar este patrón en sus proyectos de combate, o al menos comprobarlo en su tarea.



¿Qué problema puedes enfrentar?



Mi querido amigo, imaginemos esta situación, tenemos una interfaz de vista con la que

trabajar:



interface ComplexView { 
   fun showLoading()   
   fun hideLoading()   
   fun showBanner()    
   fun hideBanner()    
   fun dataLoaded(names: List<String>)    
   fun showTakeCreditDialog()
   fun hideTakeCreditDialog()
}


A primera vista, parece que nada es complicado. Simplemente selecciona una entidad separada para trabajar con esta vista, llámala presentador (voilá, el MVP está listo), y estos son grandes problemas, pequeñas dificultades, y ahora intentaré explicar por qué.



Y aquí está el propio presentador:



interface Presenter {  
   fun onLoadData(dataKey: String)    
   fun onLoadCredit()
}


Es simple, la vista extrae los métodos del presentador cuando es necesario cargar datos, el presentador, a su vez, tiene derecho a extraer la vista para mostrar la información cargada, así como mostrar el progreso. Pero aquí aparece el problema de la complejidad: esta es la absoluta falta de control sobre la consistencia de su interfaz de usuario, amigo.



Por ejemplo, queremos mostrar un diálogo ofreciendo un préstamo una oferta favorable al usuario y realizar esta llamada desde el presentador, teniendo en nuestras manos un enlace a la interfaz de visualización:



view.hideTakeCreditDialog ()



Pero al mismo tiempo, no debe olvidar que al mostrar un cuadro de diálogo, debe ocultar la carga y no mostrarlo mientras tenga un cuadro de diálogo en la pantalla. Además, existe un método que muestra un banner, al que no debemos llamar mientras estamos mostrando un diálogo (o cerrar el diálogo y solo después de eso mostrar el banner, todo depende de los requisitos). Tienes la siguiente imagen.



En ningún caso debe llamar a:



view.showBanner ()



view.showLoading ()



Mientras se muestra el diálogo. De lo contrario, los gatos, probadores y usuarios, llorarán de dolor en los ojos de un vistazo a un banner sobre un diálogo importante con un préstamo con una oferta rentable.



Ahora pensemos con usted y supongamos que aún desea mostrar un banner (un requisito de este tipo para una empresa). ¿Qué necesitas recordar?

El hecho es que al llamar a este método:



view.showBanner ()



Asegúrate de llamar:



view.hideLoading ()



view.hideTakeCreditDialog ()



De nuevo, para que nada saltara sobre el resto de elementos de la pantalla, la notoria consistencia.



Entonces surge la pregunta, ¿quién te golpeará en las manos si haces algo mal? La respuesta es simple: NADIE . En tal realización, no tienes absolutamente ningún control.



Quizás en el futuro necesitará agregar más funcionalidad a la vista, que también estará relacionada con lo que ya está allí. ¿Cuáles son las desventajas que obtenemos de esto?



  1. Fideos de dependencias estatales de elementos yuanes
  2. La lógica de las transiciones de un estado de visualización a otro se difuminará en el

    presentador.
  3. Es bastante difícil agregar un nuevo estado de pantalla, ya que existe un alto riesgo de que

    olvide ocultar algo antes de mostrar un nuevo banner o diálogo.


Y fuimos tú y yo quienes analizamos el caso cuando solo hay 7 métodos en la vista. E incluso aquí resultó ser un problema.



Pero existen tales puntos de vista:



interface ChatView : IView<ChatPresenter> {  
    fun setMessage(message: String)      
    fun showFullScreenProgressBar()      
    fun updateExistingMessage(model: ChatMessageModel)      
    fun hideFullScreenProgressBar()      
    fun addNewMessage(localMessage: ChatMessageModel)      
    fun showErrorFromLoading(message: String)         
    fun moveChatToStart()      
    fun containsMessage(message: ChatMessageModel): Boolean      
    fun getChatMessagesSize(): Int      fun getLastMessage(): ChatMessageModel?      
    fun updateMessageStatus(messageId: String, status: ChatMessageStatus)      
    fun setAutoLoading(autoLoadingEnabled: Boolean) 
    fun initImageInChat(needImageInChat: Boolean)      
    fun enableNavigationButton()     
    fun hideKeyboard()     
    fun scrollToFirstMessage()      
    fun setTitle(@StringRes titleRes: Int)      
    fun setVisibleSendingError(isVisible: Boolean)      
    fun removeMessage(localId: String)      
    fun setBottomPadding(hasPadding: Boolean)      
    fun initMessagesList(pageSize: Int)      
    fun showToast(@StringRes textRes: Int)     
    fun openMessageDialog(message: String)     
    fun showSuccessRating()      
    fun setRatingAvailability(isEnabled: Boolean)      
    fun showSuccessRatingWithResult(ratingValue: String)
}


Será bastante difícil agregar algo nuevo aquí o editar lo antiguo, hay que ver qué está conectado y cómo, y luego comenzar a llorar para trabajar y rezar para que el probador no se pierda nada. Y en el momento de tu desesperación aparece.



MVI





imagen

El punto completo



La conclusión es que tenemos una entidad llamada estado. Según este estado, la vista representará su visualización. No voy a profundizar, por lo que mi tarea es despertar su interés, por lo que pasaré directamente a los ejemplos. Y al final del artículo habrá una lista de fuentes muy útiles, si está interesado.



Recordemos nuestra posición al inicio del artículo, tenemos una vista donde mostramos diálogos, banners y magia . Describiremos cómo tú y yo expresamos opiniones



data class UIState(
       val loading: Boolean = false, 
       val names: List<String>? = null,     
       val isBannerShowing: Boolean = false,     
       val isCreditDialogShowing: Boolean = false 
)


Establezcamos la regla, usted y yo podemos cambiar la vista solo con la ayuda de este estado, habrá una interfaz de este tipo:



interface ComplexView { 
   fun renderState(state: UIState)
}


Ahora establezcamos una regla más. Podemos contactar al titular del estado (en nuestro caso será un presentador) solo a través de un punto de entrada. Enviándole eventos. Es una buena idea llamar a estos eventos acciones.



sealed class UIAction {     
       class LoadNamesAction(dataKey: String) : UIAction()     
       object LoadBannerAction : UIAction()     
       object LoadCreditDialogInfo : UIAction()
 }


Simplemente no me arrojes tomates para las clases selladas, simplifican la vida en la situación actual, eliminando castas adicionales al procesar acciones en el presentador, un ejemplo será a continuación. La interfaz del presentador se verá así:



interface Presenter { 
   fun processAction(action: UIAction)
}


Ahora pensemos en cómo conectar todo:



fun processAction(action: UiAction): UIState {     
return when (action) {         
    is UiAction.LoadNamesAction -> state.copy(
                loading = true, 
                isBannerShowing = false,          
                isCreditDialogShowing = false
    )        
    is UiAction.LoadBannerAction -> state.copy(             
                loading = false,            
                isBannerShowing = true,             
                isCreditDialogShowing = false
    )         
    is UiAction.LoadCreditDialogInfo -> state.copy(     
                loading = false,             
                isBannerShowing = false,             
                isCreditDialogShowing = true
    )     
  } 
}


Si prestó atención, el flujo de un estado de visualización a otro ahora ocurre en un lugar y ya es más fácil armar una imagen de cómo funciona todo en su cabeza.



No es muy fácil, pero tu vida debería ser más fácil. Además, en mi ejemplo, esto no es visible, pero podemos decidir cómo procesar nuestro nuevo estado en función del estado anterior (también hay varias nociones para implementar esto). Sin mencionar la increíble capacidad de reutilización que lograron los chicos de badoo, uno de sus asistentes para lograr este objetivo fue MVI.



Sin embargo, no debes alegrarte temprano, todo en este mundo tiene pros y contras, y aquí están.



  1. El espectáculo habitual de brindis nos rompe
  2. Cuando actualice una casilla de verificación, todo el estado se copiará nuevamente y se enviará a la

    vista, es decir, se volverá a dibujar innecesario si no se hace nada al respecto.


Supongamos que queremos mostrar un brindis normal de Android, de acuerdo con la lógica actual, estableceremos una bandera en nuestro estado para mostrar nuestro brindis.



data class UIState(
       val showToast: Boolean = false, 
)


El primero



Tomamos y cambiamos el estado en el presentador, establecemos showToast = true y lo más simple que puede suceder es la rotación de la pantalla. Todo se destruye, las explosiones y la destrucción de la actividad se recrean, pero como eres un desarrollador genial, tu estado está pasando por todo esto. Y en el estado tenemos una bandera mágica que dice mostrar tostadas. Resultado: la tostada se muestra dos veces. Hay varias formas de resolver este problema y todas parecen muletas . Nuevamente, esto se escribirá en las fuentes adjuntas a este artículo.



Bueno, el segundo



Esto ya es un problema de renderizado innecesario en la vista, que ocurrirá cada vez incluso cuando solo cambie uno de los campos del estado. Y este problema se resuelve de varias maneras, a veces no de las más hermosas (a veces mediante una verificación aburrida antes de quejarse de un nuevo significado, que es diferente al anterior). Pero con el lanzamiento de compose en una versión estable, este problema se resolverá, ¡entonces mi amigo vivirá contigo en un mundo transformado y feliz!



Tiempo para los profesionales:



  1. Un punto de entrada a la vista.
  2. Siempre tenemos el estado actual de la pantalla a mano
  3. Incluso en la etapa de implementación, debe pensar en cómo un estado fluirá

    hacia otro y cuál es la conexión entre ellos.
  4. Flujo de datos unidireccional


¡Ama a Android y nunca pierdas la motivación!



Lista de mis inspiradores








All Articles