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
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
Por ejemplo, queremos mostrar un diálogo ofreciendo un
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
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?
- Fideos de dependencias estatales de elementos yuanes
- La lógica de las transiciones de un estado de visualización a otro se difuminará en el
presentador. - 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
MVI
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
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.
- El espectáculo habitual de brindis nos rompe
- 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
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:
- Un punto de entrada a la vista.
- Siempre tenemos el estado actual de la pantalla a mano
- 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. - Flujo de datos unidireccional
¡Ama a Android y nunca pierdas la motivación!
Lista de mis inspiradores
- www.youtube.com/watch?v=VsStyq4Lzxo&t=592s - Patrones declarativos de IU (Google
I / O'19) - www.youtube.com/watch?v=pXw6r2kAvq8&t=2s - Viaje arquitectónico por Zsolt
Kocsi, Badoo EN
- www.youtube.com/watch?v=hBkQkjWnAjg&t=318s - Cómo cocinar un
MVI bien hecho para Android - www.youtube.com/watch?v=0IKHxjkgop4 - Gestión del estado con RxJava por Jake
Wharton - hannesdorfmann.com/android/model-view-intent - Artículo de Hannes Doorfmann