Objetivo deste artigo
Não vou me aprofundar em como o MVI é tecnicamente implementado (há mais de uma maneira e cada uma tem suas próprias vantagens e desvantagens). Meu principal objetivo em um pequeno artigo é interessar você a estudar este tópico no futuro e possivelmente encorajá-lo a implementar esse padrão em seus projetos de combate, ou pelo menos verificá-lo em seu dever de casa.
Que problema você pode enfrentar
Meu caro amigo, vamos imaginar esta situação, temos uma interface de visualização com a qual podemos
trabalhar:
interface ComplexView {
fun showLoading()
fun hideLoading()
fun showBanner()
fun hideBanner()
fun dataLoaded(names: List<String>)
fun showTakeCreditDialog()
fun hideTakeCreditDialog()
}
À primeira vista, parece que nada é complicado. Basta selecionar uma entidade separada para trabalhar com essa visualização, chamá-la de apresentador (voila, aqui está o MVP), e esses são
E aqui está o próprio apresentador:
interface Presenter {
fun onLoadData(dataKey: String)
fun onLoadCredit()
}
É simples, a view puxa os métodos do apresentador quando é necessário carregar os dados, o apresentador, por sua vez, tem o direito de puxar a view para mostrar as informações carregadas, bem como exibir o andamento. Mas aqui aparece o
Por exemplo, queremos exibir um diálogo oferecendo um
view.hideTakeCreditDialog ()
Mas, ao mesmo tempo, você não deve esquecer que, ao exibir uma caixa de diálogo, é necessário ocultar o carregamento e não mostrá-lo enquanto estiver com uma caixa de diálogo na tela. Além disso, existe um método que exibe um banner, que não devemos chamar enquanto estamos exibindo uma caixa de diálogo (ou feche a caixa de diálogo e só depois exiba o banner, tudo depende dos requisitos). Você tem a seguinte imagem.
Em nenhum caso você deve ligar para:
view.showBanner ()
view.showLoading ()
Enquanto o diálogo está sendo exibido. Caso contrário,
Agora vamos pensar com você e supor que você ainda queira mostrar um banner (tal requisito de uma empresa). O que você precisa lembrar?
O fato é que, quando você chama este método:
view.showBanner ()
Certifique-se de ligar para:
view.hideLoading ()
view.hideTakeCreditDialog ()
Mais uma vez, para que nada saltasse sobre o resto dos elementos da tela, a notória consistência.
Então surge a pergunta: quem vai bater em suas mãos se você fizer algo errado? A resposta é simples - NINGUÉM . Em tal compreensão, você não tem absolutamente nenhum controle.
Talvez no futuro você precise adicionar mais algumas funcionalidades à visualização, que também estarão relacionadas ao que já existe. Quais são as desvantagens que obtemos disso?
- Macarrão de dependências estaduais de elementos yuan
- A lógica das transições de um estado de exibição para outro será manchada pelo
apresentador - É muito difícil adicionar um novo estado de tela, pois há um grande risco de
você se esquecer de ocultar algo antes de exibir um novo banner ou caixa de diálogo
E fomos você e eu que analisamos o caso quando havia apenas 7 métodos na exibição. E mesmo aqui acabou sendo um problema.
Mas existem essas visões:
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)
}
Vai ser muito difícil adicionar algo novo aqui ou editar o antigo, você tem que ver o que está conectado e como, e então começar a
MVI
O ponto inteiro
O resultado final é que temos uma entidade chamada estado. Com base neste estado, a visualização renderizará sua exibição. Não vou me aprofundar, então minha tarefa é despertar o seu interesse, então irei direto aos exemplos. E no final do artigo, haverá uma lista de fontes muito úteis, se você estiver interessado.
Vamos relembrar nossa posição no início do artigo, temos uma visualização onde mostramos diálogos, banners
data class UIState(
val loading: Boolean = false,
val names: List<String>? = null,
val isBannerShowing: Boolean = false,
val isCreditDialogShowing: Boolean = false
)
Vamos definir a regra, você e eu podemos mudar a visualização apenas com a ajuda deste estado, haverá essa interface:
interface ComplexView {
fun renderState(state: UIState)
}
Agora vamos definir mais uma regra. Podemos entrar em contato com o dono do estado (no nosso caso será o apresentador) apenas por meio de um ponto de entrada. Enviando eventos para ele. É uma boa ideia chamar esses eventos de ações.
sealed class UIAction {
class LoadNamesAction(dataKey: String) : UIAction()
object LoadBannerAction : UIAction()
object LoadCreditDialogInfo : UIAction()
}
Só não jogue tomates em mim para as aulas lacradas, eles simplificam a vida na situação atual, eliminando castas adicionais ao processar ações no apresentador, um exemplo ficará abaixo. A interface do apresentador será semelhante a esta:
interface Presenter {
fun processAction(action: UIAction)
}
Agora vamos pensar em como conectar tudo:
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
)
}
}
Se você prestou atenção, o fluxo de um estado de exibição para outro agora ocorre em um lugar e já é mais fácil montar uma imagem de como tudo funciona em sua cabeça.
Não é muito fácil, mas sua vida deveria ser mais fácil. Além disso, no meu exemplo, isso não é visível, mas podemos decidir como processar nosso novo estado com base no estado anterior (também existem várias noções para implementar isso). Sem falar na reutilização insana que os caras do badoo alcançaram, um de seus assistentes para atingir esse objetivo foi o MVI.
No entanto, você não deve se alegrar cedo, tudo neste mundo tem prós e contras, e aqui estão eles
- O show de brinde usual nos quebra
- Ao atualizar uma caixa de seleção, todo o estado será copiado novamente e enviado para a
visualização, ou seja, um redesenho desnecessário ocorrerá se nada for feito a respeito
Suponha que desejamos exibir um sistema de notificação normal do Android; de acordo com a lógica atual, definiremos um sinalizador em nosso estado para exibir nosso sistema de notificação.
data class UIState(
val showToast: Boolean = false,
)
O primeiro
Pegamos e alteramos o estado no apresentador, definimos showToast = true e a coisa mais simples que pode acontecer é a rotação da tela. Tudo é destruído,
Bem o segundo
Isso já é um problema de renderização desnecessária na visualização, que ocorrerá todas as vezes, mesmo quando apenas um dos campos no estado for alterado. E este problema é resolvido de várias maneiras às vezes não das mais bonitas (às vezes por uma verificação maçante antes de reclamar do novo significado, que é diferente do anterior). Mas com o lançamento do compose em uma versão estável, esse problema será resolvido, então meu amigo viverá com você em um mundo transformado e feliz!
Hora dos profissionais:
- Um ponto de entrada para a vista
- Sempre temos o estado atual da tela em mãos
- Mesmo na fase de implementação, você tem que pensar sobre como um estado irá fluir
para outro e qual é a conexão entre eles - Fluxo de Dados Unidirecional
Ame o Android e nunca perca a sua motivação!
Lista dos meus inspiradores
- www.youtube.com/watch?v=VsStyq4Lzxo&t=592s - Padrões de IU declarativos (Google
I / O'19) - www.youtube.com/watch?v=pXw6r2kAvq8&t=2s - Jornada arquitetônica de Zsolt
Kocsi, Badoo EN
- www.youtube.com/watch?v=hBkQkjWnAjg&t=318s - Como preparar um
MVI bem passado para Android - www.youtube.com/watch?v=0IKHxjkgop4 - Gerenciamento de estado com RxJava por Jake
Wharton - hannesdorfmann.com/android/model-view-intent - Artigo por Hannes Doorfmann