Assincronia de dados com ViewModel

Nos posts anteriores desta série a gente viu como representar dados assíncronos e como criar uma View genérica para exibir esses dados. O que a gente ainda não viu ainda é como é que a gente verdadeiramente usa na prática essas coisas nos nossos projetos.

Neste post eu vou mostrar os princípios de uso disso tudo dentro da arquitetura Model-View-ViewModel (MVVM). Meu objetivo não é o de explorar a arquitetura completa, e sim de me concentrar na separação de responsabilidades da View e da ViewModel. Fazendo um resumo bem simplificado:

  • Uma View é responsável pela exibição dos dados (UI), mas não tem ideia de onde esses dados vieram;
  • Uma ViewModel é responsável por preparar os dados para serem exibidos, mas não sabe nada sobre a forma de exibição desses dados (não trata da UI);
  • Uma View pode acessar a sua ViewModel, mas uma ViewModel nunca sabe (e nunca acessa) quais são as Views associadas a ela.

Construindo a ViewModel

Em geral a ViewModel é uma classe conformante com o protocolo ObservableObject e que expõe os dados que vão ser apresentados por meio de propriedades marcadas como @Published. A ideia é que uma View pode “observar” as mudanças da ViewModel de modo que a interface seja automaticamente reconstruída para acompanhar essas mudanças.

Para exemplificar esse processo, vou criar aqui uma ViewModel que muda o estado de uma instância de AsyncData a cada segundo. No fim deste artigo, o objetivo é ver essa atualização funcionando sem qualquer código-fonte de “checar por atualizações / mandar reconstruir a UI” feita pelo programador.

Vamos ao código-fonte da ViewModel:

class UserProfileViewModel: ObservableObject {
    @Published var asyncName: AsyncData<String> = .loading
    private var timer: Timer!

    init() {
        timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in
            switch self.asyncName {
            case .loading:
                self.asyncName = .failed
            case .failed:
                self.asyncName = .loaded("Fulano de Tal (atualizado em \(Date()))")
            case .loaded(_):
                self.asyncName = .loading
            }
        }
    }
}

Como se pode ver, a UserProfileViewModel não faz qualquer referência a “alguma view que pode estar exibindo estes dados”. Dentro do princípio de MVVM, as propriedades @Published (neste caso, somente asyncName) são inicializadas e atualizadas de acordo com qualquer lógica que faça sentido para o modelo de dados (poderia ser por uma chamada a uma API REST, CloudKit, etc).

Construindo a View

Neste ponto, a nossa já conhecida AsyncDataView passa a ser uma view utilitária na construção das nossas views reais do projeto: ela será usada para exibir uma informação quando ela estiver .loaded ou uma das versões preprogramadas quando estiver nos estados .loading ou .failed — e tudo isso será buscado diretamente da ViewModel.

Então vamos ao código. Da mesma maneira que nós declaramos UserProfileViewModel como sendo um ObservableObject (“objeto observável”), dentro da view nós declararemos uma propriedade marcada como @ObservedObject (“objeto observado”). Esse é justamente o contrato necessário para que as views “saibam” das atualizações ocorridas dentro de uma ViewModel: a view é capaz de observar o momento em que as informações publicadas são modificadas.

struct UserProfileView: View {
    @ObservedObject var viewModel: UserProfileViewModel

    var body: some View {
        VStack {
            Text("Dados do usuário")
                .font(.title)
            AsyncDataView(asyncData: viewModel.asyncName) { name in
                Text("Nome: \(name)")
            }
        }
    }
}

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *


Esse site utiliza o Akismet para reduzir spam. Aprenda como seus dados de comentários são processados.