Olá!!! Esse é meu segundo artigo sobre como integrar SwiftUI às arquiteturas mais comuns. No artigo anterior, eu falei como integrar SwiftUI com MVC e agora vou falar sobre MVP.
Se você leu meu último artigo, fico feliz em falar com você de novo. Se não, fico feliz em falar com você agora, e sugiro que leia o artigo de MVC.
Eu vou continuar de onde parei e fazer algumas comparações, mas ainda será possível entender esse artigo separadamente.
Então, vamos começar.
- • •
MVP
A arquitetura MVP consiste em três componentes:
- Model: Serviços, provedores de dados, gerenciadores, e entidades que representam os dados da aplicação. Os elementos dessa camada são responsáveis por coisas como requisição de rede, persistência, parser de informações e assim por diante.
- View: Responsável por renderizar a tela, receber ações do usuário e informar o presenter sobre elas.
- Presenter: Recebe ações a partir da view, faz as requisições necessárias para a camada de modelo para lidar com as ações apropriadamente, e formata os dados para serem apresentados na view.
Se você não está familiarizado com MVP, possivelmente está dizendo:
Espera um minuto, isso é só MVC substituindo ViewController por Presenter. Isso está certo?
E, bem, realmente parece isso, mas tem algumas diferenças. No MVP com UIKit, o UIViewController é considerado como parte da view e a responsabilidade do Controller do MVC passa para o presenter.
Mas por quê? Como isso muda as coisas?
Lembre-se que UIViewControllers também lidam com o ciclo de vida da view, com métodos como viewDidLoad, viewWillAppear e viewDidAppear. Consequentemente, eles se tornam muito difíceis de testar, porque esses comportamentos não são fáceis de mockar.
A ideia com MVP é mover a lógica para fora do ViewController, e colocá-la no presenter, assim podemos testá-la sem nos preocuparmos com o ciclo de vida da view.
Na verdade, o presenter é totalmente independente da view e não deve nem saber se está sendo utilizado UIKit, SwiftUI ou qualquer outra coisa. O que também torna mais fácil alterar o framework de UI utilizado, como faremos nesse tutorial.
- • •
MVP com UIKit
Primeiro, eu vou mostrar uma aplicação com MVP e UIKit, e depois vou mudar ela para SwiftUI. O app de exemplo basicamente apresenta uma lista de usuários e tem a opção de ordená-los por nome ou idade.
Primeiro, precisamos definir um serviço para carregar os dados e os modelos usados pela view. No caso, User e UserViewModel. O primeiro é o modelo retornado pelo backend e deve ser utilizado apenas por componentes com lógica de negócios (camada de model e presenter).
E o segundo é criado pelo presenter a partir do primeiro para ser usado especificamente pela view para renderizar as informações. Essa é uma boa prática porque torna a view independente das respostas do servidor.
Em seguida, nós criamos o presenter, exibimos seus métodos através do UserPresenterProtocol, e delegamos alguns métodos de renderização para a view, usando o protocolo UserPresenterDelegate. Dessa forma, o presenter depende da interface, em vez de depender da implementação concreta da view (princípio da inversão de dependências), tornando mais fácil substituir a view e criar componentes mocks que implementam esses protocolos, para testar o presenter.
Note também que o presenter lida com como os objetos são apresentados, ordenando eles, criando o campo nome, e transformando a data de nascimento em uma string com a idade do usuário. E ele não possui nenhum elemento do UIKit, IBOutlets ou IBActions.
E, finalmente, nós fazemos o UsersViewController implementar o protocol PresenterDelegate.
Mas agora, a pergunta principal:
- • •
Como usar isso com SwiftUI?
Bom, nós seguiremos uma abordagem muito similar a que foi utilizada no tutorial de MVC. Iremos substituir o UIViewController por uma Store, e fazer a Store implementar o PresenterDelegate:
Então, nós criamos a view usando SwiftUI e a fazemos ter uma referência para a store e para o presenter. Desse jeito, ela pode renderizar o estado de acordo com a informação da store, e chamar o presenter para lidar com ações do usuário e carregar dados.
Basicamente, a view envia eventos para a store, que envia para o presenter, que então lida com elas da mesma forma que faria no MVC. Depois, chama a store utilizando os métodos do delegate para atualizar seu estado.
Uma vez que a store atualiza sua propriedade local que controla o estado, a mudança é publicada utilizando o framework Combine, informando a view para se recarregar, pois ela tem a store como um observed object (objeto observado).
Note também que no exemplo foi utilizada a mesma prática do artigo anterior, decompondo a view em várias views menores.
Então temos uma view para cada estado: LoadingView, ErrorView, EmptyStateView e UsersDisplayView. E uma outra view (UsersView) para controlar o estado a ser apresentado e se comunicar com o presenter.
Note também como cada componente é pequeno e tem uma responsabilidade bem clara, e como foi fácil substituir o UIKit por SwiftUI quando utilizamos MVP. O presenter e o model não tiveram nenhuma alteração.
. No próximo artigo eu vou falar sobre VIPER e Clean Swift, que são arquiteturas mais complexas e frequentemente utilizadas. Fique ligado(a)!