.NET MAUI - MVVM, DataBinding e a interface ICommand - IV
Vou continuar a apresentar os principais conceitos sobre MVVM, DataBinding e da interface ICommand : Usando o Mvvm Community ToolKit para simplificar a implementação do padrão MVVM. |
Continuando o artigo anterior veremos como simplificar a implementação do padrão MVVM, particularmente na implementação da interface INotifyPropertyChanged, usando o pacote MVVM Community TooloKit.
O padrão MVVM ajuda a separar as responsabilidades entre a View a lógica de negócio da aplicação mas sua implementação nos leva a ter que incluir manualmente na implementação da interface INotifyPropertyChanged muito código repetitivo que esta sujeito a erros.
Para cada propriedade e cada comando, precisamos implementar um backing field, setters e getters e gerar um evento para notificar a UI de que algo mudou e isso requer muito código clichê.
Por exemplo para uma única propriedade Nome definida na viewmodel teremos que criar algo como o código abaixo:
private string _nome; public string Nome { get => _nome; set { if (_nome.Equals(value)) { return; }
_nome = value; |
Isso pode se tornar caro rapidamente, porque além da lógica de negócios real, há muito código adicional que precisa ser mantido. Isso custa um tempo valioso de desenvolvimento e aumenta o risco de bugs.
Aqui entra o pacote MVVM Community ToolKit da Microsoft que é um pacote que oferece diversas ferramentas e utilitários para ajudar na implementação do padrão MVVM em projetos .NET.
Para evitar esses erros comuns e ajudar com o princípio Don't Repeat Yourself (DRY), o MVVM Community Toolkit nos ajuda a reduzir a quantidade desse código clichê. Ele faz isso de várias maneiras:
Isso reduz drasticamente o tempo de desenvolvimento e nossos arquivos de código parecem muito mais limpos. Então, usando os Source Generators, nosso código acima pode ser reduzido para escrever uma linha como esta:
[ObservableProperty] private string _nome;
Uma das funcionalidades do pacote é a classe ObservableObject, que simplifica a implementação da interface INotifyPropertyChanged e reduz a quantidade de código repetitivo necessário para criar objetos observáveis.
Para mostrar como o pacote MVVM atua, vamos dar uma olhada em um ViewModel comum que implementa a interface INotifyPropertyChanged e depois veremos como ela muda quando usamos o recurso do MVVM Source Generators
Para ilustrar vamos nos basear em uma aplicação .NET MAUI bem simples que vai permitir informar o nome, sobrenome, rua codigo postal e cidade e durante a entrada dos dados a aplicação vai exibir em um Frame o endereço montado e vai ter também um botão que permite imprimir o endereço, que no nosso exemplo, vai usar um DisplayAlert para exibir o endereço completo.
Abaixo temos a aplicação em execução :
Neste projeto temos uma view chamada EnderecoView.xaml com seguinte código:
<?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="MauiEnderecoCompleto.Mvvm.Views.EnderecoView" Title="Endereço"> <Grid> <VerticalStackLayout Padding="20" Spacing="20" VerticalOptions="Fill"> <Grid ColumnDefinitions="*,*" RowDefinitions="auto,auto,auto,auto,auto">
<Label Grid.Row="0" Grid.Column="0" <Label Grid.Row="1" Grid.Column="0" <Label Grid.Row="2" Grid.Column="0" <Label Grid.Row="3" Grid.Column="0" <Label Grid.Row="4" Grid.Column="0" </Grid> |
Temos também uma view model chamada EnderecoViewModel com o seguinte código :
using
System.ComponentModel; using System.Runtime.CompilerServices; using System.Text; using System.Windows.Input; namespace MauiEnderecoCompleto.Mvvm.ViewModels; public class
EnderecoViewModel :
INotifyPropertyChanged private string
_nome; private string
_sobrenome; private string
_rua; private string
_cep; private string
_cidade; public string
Endereco
stringBuilder
return stringBuilder.ToString(); private void
ImprimirEndereco(string endereco) public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName]
string propertyName = null)
private bool SetField<T>(ref T field, T value, [CallerMemberName] string
propertyName = null)
field = value; |
Observe a quantidade de código necessária na implementação da interface INotifyPropertyChanged.
Ufa !!!, é muito código para apenas algumas propriedades e um mísero comando ! Quanta repetição...
Nota: Poderia ser ainda pior, se a comparação de igualdade tivesse
sido implementada separadamente em cada propriedade também.
Tecnicamente, o código acima é bom e sólido, mas fazer a mesma coisa várias
vezes para cada ViewModel diferente usada em uma aplicação pode acabar se
tornando bastante tedioso e sujeito a erros.
Felizmente o pacote MVVM Community ToolKit vem em nosso auxílio e veremos que após a sua utilização o código vai ser reduzido drásticamente. Vamos entender como usar o MVVM Community ToolKit destacando seus principais recursos.
Usando o MVVM Community ToolKit
A primeira coisa a fazer é instalar o pacote nuger CommunityToolkit.MVVM no projeto .NET MAUI.
Atualmente a versão mais recente é a 8.2.0 (lançada em maio/2023).
Depois de instalado, podemos extrair as classes e atributos necessários adicionando a seguinte instrução using a nossa ViewModel EnderecoViewModel:
using
CommunityToolkit.Mvvm.ComponentModel;O MVVM Community Toolkit também fornece funcionalidade MVVM regular e classes base que podem ser usadas para simplificar ViewModels que você pode usar com ou sem os geradores de código-fonte.
Após instalar e referenciar o pacote em nossa view model podemos iniciar a utilização dos seus recursos.
Assim podemos usar a classe ObservableObject
como uma classe base ou usar o atributo
[INotifyPropertyChanged] em uma classe parcial para gerar automaticamente
a implementação INotifyPropertyChanged para nós.
Se a sua viewmodel não precisar herdar de outra classe, como é o nosso exemplo,
podemos usar a classe base ObservableObject :
... using CommunityToolkit.Mvvm.ComponentModel; namespace MauiEnderecoCompleto.Mvvm.ViewModels;
public
partial class
EnderecoViewModel
: ObservableObject |
Se a sua view model precisar herdar de outra classe base podemos usar o atributo[INotifyPropertyChanged] (e, opcionalmente, também o atributo [INotifyPropertyChanging]):
using
CommunityToolkit.Mvvm.ComponentModel; namespace MauiEnderecoCompleto.Mvvm.ViewModels;[INotifyPropertyChanged] public partial class EnderecoViewModel : OutraClasse { ... } |
Cabe destacar que ao usar o atributo [INotifyPropertyChanged] ou a classe base ObservableObject, sua classe deve ser declarada como partial, porque os geradores de código criam um código que complementa a classe.
Atributos
O MVVM Community Toolkit usa atributos para permitir que os MVVM
Source Generators saibam que algo deve ser gerado automaticamente. Os
atributos podem ser colocados acima ou na frente dos campos e assinaturas de
método.
Ao usar os geradores de código, você provavelmente usará os seguintes atributos
com mais frequência do que qualquer um dos outros:
1 - [ObservableProperty]
Este atributo é usado para gerar automaticamente uma propriedade para um
campo de apoio ou backing field. A propriedade resultante, por convenção,
sempre começa com letra maiúscula.
Os campos de apoio devem ser declarados privados e começar com uma letra minúscula ou com um sublinhado (_):
Para o nosso exemplo o código usado para cada uma das propriedades ficaria assim:
using
System.ComponentModel; using System.Runtime.CompilerServices; using System.Text; using System.Windows.Input; using CommunityToolkit.Mvvm.ComponentModel;namespace MauiEnderecoCompleto.Mvvm.ViewModels;public partial class EnderecoViewModel : ObservableObject{ [ObservableProperty] [ObservableProperty] private string _sobrenome; [ObservableProperty] private string _rua; [ObservableProperty] private string _cep; [ObservableProperty] private string _cidade; ... |
Observe que especificamos apenas os campos de apoio para as propriedades, mas para acessá-los, ainda precisamos usar os nomes das propriedades apropriados. Eles podem ser vinculados dentro do código XAML usando os nomes das propriedades:
... <Entry Grid.Row="0" Grid.Column="1" Text="{Binding Nome}" /> <Entry Grid.Row="1" Grid.Column="1" Text="{Binding Sobrenome}" /> <Entry Grid.Row="2" Grid.Column="1" Text="{Binding Rua}" /> ... |
2- [RelayCommand]
Para gerar automaticamente um comando, você pode simplesmente colocar esse
atributo acima ou na frente de um método. O comando resultante terá o mesmo nome
do método, mas com o sufixo Command:
[RelayCommand] private void MeuMetodo() { Console.WriteLine("Comando"); } |
Este atributo também pode ser usado em métodos assíncronos, que na verdade criarão uma instância de AsyncRelayCommand 'debaixo dos panos', o que é muito conveniente ao usar MVVM com .NET MAUI:
[RelayCommand] private async Task MeuMetodoAsync() { await MessageService.ShowMessageAsync("Mensagem"); } |
Os comandos resultantes podem ser vinculados acessando MeuMetodoCommand e MeuMetodoAsyncCommand do código XAML:
... <Button Command = "{Binding MeuMetodoCommand}" /> <Button Command="{Binding MeuMetodoAsyncCommand}" /> ... |
3- [NotifyPropertyChangedFor]
Às vezes, você também pode querer informar ao consumidor da propriedade que
outra propriedade também foi alterada e que todas as vinculações devem ser
atualizadas.
É para isso que serve o atributo [NotifyPropertyChangedFor] e só funciona em combinação com o atributo [ObservableProperty]. Enquanto os outros atributos não exigem um argumento, este requer o nome da propriedade para a qual um evento PropertyChanged deve ser gerado:
[ObservableProperty] [NotifyPropertyChangedFor(nameof(NomeCompleto))] private string _nome; [ObservableProperty] private string _sobrenome; //o evento PropertyChanged será disparado para esta propriedade public string NomeCompleto => $"{Nome} {Sobrenome}"; |
Reduzindo o código da view model
Agora vamos atualizar a nossa view model EnderecoViewModel usando o MVVM ToolKit para reduzir o código.
a- Vamos usar o atributo ObservableObject para implementar a interface INotifyProperyChanged
b- Vamos usar os atributos [ObservableProperty] e NotifyPropertyChanged(nameof(Endereco)] para as propriedades da view model
c- Vamos usar o atributo [RelayCommand] para o método ImprimirEndereco(string endereco)
Veja abaixo como ficou o código após a aplicação dos recursos do pacote MVVM Toolkit:
using
CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using System.Text; namespace MauiEnderecoCompleto.Mvvm.ViewModels;public partial class EnderecoViewModel : ObservableObject{ [ObservableProperty] [NotifyPropertyChangedFor(nameof(Endereco))] private string _nome; [ObservableProperty] [ObservableProperty] [ObservableProperty] [NotifyPropertyChangedFor(nameof(Endereco))] private string _cep; [ObservableProperty] public string Endereco { get { var stringBuilder = new StringBuilder(); stringBuilder.AppendLine($"{Nome} {Sobrenome}") .AppendLine(Rua) .AppendLine($"{Cep} - {Cidade}"); return stringBuilder.ToString(); } } [RelayCommand] private void ImprimirEndereco(string endereco) { App.Current.MainPage.DisplayAlert("Endereço", endereco, "Ok"); } } |
Uau !!!
Acabamos de economizar cerca de 50% das linhas de código em comparação com a versão inicial da nossa ViewModel.
Impressionante !!! não é ?
Ao executar o projeto teremos o mesmo resultado.
Espiando o código gerado (sob o capô)
Agora, o que realmente acontece quando as propriedades e os comandos são gerados ?
Como marcamos nossa ViewModel como sendo uma classe parcial, os
geradores de código-fonte podem criar mais partes para a nossa classe em
arquivos separados, como as propriedades e comandos baseados nos atributos que
fornecemos.
Como os geradores de código-fonte em C# são construídos sobre os analisadores de
código da plataforma do compilador .NET (Roslyn), eles são executados depois de
fazer uma edição em um arquivo C# aberto. Dessa forma, as fontes geradas estão
sempre disponíveis para o compilador C#.
Podemos encontrar os arquivos gerados automaticamente (que sempre terminam em .g.cs) para nosso projeto no Solution Explorer em Dependencies -> net7.0 -> Analyzers -> CommunityToolkit.Mvvm.SourceGenerators.
Se selecionarmos o ObservablePropertyGenerator,
podemos encontrar um arquivo que termina em
EnderecoViewModel.g.cs, que contém nossas propriedades geradas
automaticamente com seus setters e getters, por exemplo para nossa propriedade
Nome:
Como podemos ver, um EqualityComparer padrão é
usado para o campo de apoio _nome, que é do
tipo string.
Somente se as strings do campo de apoio e do objeto de valor forem diferentes, o
evento PropertyChanging será gerado antes de
atualizar o campo de apoio para o valor fornecido. Depois que o campo de apoio
foi atualizado, o evento PropertyChanged é gerado,
mas não apenas para nossa propriedade Nome, ele também é gerado para a
propriedade Endereco, porque adicionamos o atributo
[NotifyPropertyChangedFor(nameof(Endereco))] acima de nosso campo de
apoio _nome.
Se você olhar de perto, notará que não há apenas chamadas de método
OnPropertyChanging() e OnPropertyChanged(), mas
também chamadas para um método OnNomeChanging() e
um método OnNomeChanged().
Os dois primeiros métodos geram os eventos
PropertyChanging e PropertyChanged, respectivamente, enquanto os outros
dois são, na verdade, métodos parciais sem um corpo, que são declarados mais
abaixo no arquivo de origem gerado automaticamente:
Desta forma, reduzir o código clichê em seu aplicativo que usa o padrão MVVM
ficou ainda mais fácil e simples com os MVVM Source Generators do MVVM Community
Toolkit.
Como mostramos, eles são muito convenientes e fáceis de usar - desde que você
já esteja um pouco familiarizado com o padrão MVVM em aplicativos .NET e saiba o
que está fazendo.
Até agora, vimos os casos de uso mais diretos e simples com os atributos comuns
[ObservableProperty], [RelayCommand] e
[NotifyPropertyChangedFor], e, em breve vamos abordar outros recursos.
Pegue o código do projeto aqui: MauiEnderecoCompleto2.zip (sem as referências)
'Tendo sido, pois, justificados pela fé, temos paz com Deus,
por nosso Senhor Jesus Cristo;'
Romanos 5:1
Referências: