Usar um viewmodel

Concluído

Depois de aprender sobre os componentes que compõem o padrão MVVM, você provavelmente descobriu que o modelo e a exibição eram fáceis de definir. Vamos explorar como usar o viewmodel para definir melhor sua função no padrão.

Expor propriedades à interface do usuário

Como no exemplo anterior, os viewmodels normalmente dependem dos modelos para a maioria dos seus dados e suas lógicas de negócios. Mas é o viewmodel que formata, converte e enriquece os dados da forma que a exibição atual exige.

Formatar usando um viewmodel

Você já viu um exemplo de formatação com período de férias. Serialização, codificações de caracteres e formatação de data são exemplos de como o viewmodel pode formatar os dados do modelo.

Converter usando um viewmodel

Muitas vezes, o modelo fornece informações de maneiras indiretas. Mas o viewmodel pode corrigir isso. Por exemplo, suponha que você deseja mostrar na tela se um funcionário é um supervisor. Mas nosso modelo Employee não nos diz isso diretamente. Em vez disso, você precisa inferir esse fato com base em se a pessoa tem outras pessoas relatando a elas. Suponha que o modelo tem essa propriedade:

public IList<Employee> DirectReports
{
    get
    {
        ...
    }
}

Se a lista estiver vazia, você poderá inferir que este Employee não é um supervisor. Nesse caso, EmployeeViewModel inclui a propriedade IsSupervisor que fornece essa lógica:

public bool IsSupervisor => _model.DirectReports.Any();

Enriquecer usando um viewmodel

Às vezes, um modelo pode fornecer apenas uma ID para dados relacionados. Ou talvez seja necessário acessar várias classes de modelo para correlacionar os dados necessários para uma única tela. O viewmodel também fornece um lugar ideal para executar essas tarefas. Suponha que você queira mostrar todos os projetos que um funcionário está gerenciando no momento. Esses dados não fazem parte da classe de modelo Employee. Eles podem ser acessados observando a classe de modelo CompanyProjects. Nosso EmployeeViewModel, como sempre, expõe seu trabalho como uma propriedade pública:

public IEnumerable<string> ActiveProjects => CompanyProjects.All
    .Where(p => p.Owner == _model.Id && p.IsActive)
    .Select(p => p.Name);

Use propriedades de passagem com um viewmodel

Com frequência, um viewmodel precisa exatamente da propriedade que o modelo fornece. Para essas propriedades, o viewmodel apenas passa os dados por meio de:

public string Name
{
    get => _model.Name;
    set => _model.Name = value;
}

Definir o escopo para o viewmodel

Você pode usar um viewmodel em qualquer nível em que há uma exibição. Uma página geralmente tem um viewmodel, mas também pode ter subexibições. Um motivo comum para viewmodels aninhados é quando a página exibe um ListView na página. A lista tem um viewmodel que representa a coleção, como EmployeeListViewModel. Cada elemento na lista é um EmployeeViewModel.

Diagrama de um EmployeeListViewModel com vários subobjetos EmployeeViewModel.

Também é comum ter um viewmodel de nível superior que contenha os dados e o estado de todo o aplicativo, mas que não esteja associado a nenhuma página específica. Tal viewmodel é comumente usado é na manutenção do item "ativo". Considere o exemplo ListView descrito. Quando o usuário seleciona uma linha de funcionário, esse funcionário representa o item atual. Se o usuário navegar para uma página de detalhes ou selecionar um botão de barra de ferramentas enquanto a linha estiver selecionada, a ação ou a exibição deverá ser para aquele funcionário. Uma maneira elegante de lidar com esse cenário é ter o limite de dados do ListView.SelectItem para uma propriedade que a página de detalhes ou a barra de ferramentas também possa acessar. Colocar essa propriedade em um viewmodel central funciona bem.

Identificar quando reutilizar os viewmodels com exibições

O modo como você define a relação entre o viewmodel e o modelo, bem como entre o viewmodel e a exibição, é determinado mais pelos requisitos de aplicativo do que por regras. A finalidade do viewmodel é fornecer à exibição a estrutura e os dados que ela precisa. Isso deve orientar decisões sobre "quão grande" o escopo de um viewmodel deve ser definido.

Os viewmodels geralmente refletem de perto a estrutura de uma classe de modelo e eles têm uma relação um-para-um com essa classe. Você viu um exemplo anteriormente com o EmployeeViewModel que encapsulou e aumentou uma única instância Employee. Mas nem sempre é uma relação um-para-um. Se o viewmodel for projetado para fornecer o que a exibição precisa, você poderá, em vez disso, acabar com algo como HRDashboardViewModel para dar uma visão geral de um departamento de RH, que não tem nenhuma relação explícita com qualquer modelo, mas pode usar dados de qualquer classe de modelo.

Da mesma forma, você pode descobrir que viewmodels e exibições geralmente têm uma relação um-para-um. Mas isso também não é necessariamente o caso. Vamos pensar novamente sobre um ListView que mostra uma linha para cada funcionário. Ao selecionar uma das linhas, você acessará uma página de detalhes do funcionário.

A página de lista tem o viewmodel com uma coleção. Como sugerido anteriormente, essa coleção pode ser uma coleção de objetos EmployeeViewModel. E quando o usuário seleciona uma linha, a instância EmployeeViewModel pode ser passada para o EmployeeDetailPage. E a página de detalhes pode usar EmployeeViewModel como seu BindingContext.

Este cenário talvez seja uma excelente oportunidade para reutilização do viewmodel. Mas lembre-se de que os viewmodels foram projetados para fornecer o que a exibição precisa. Em alguns casos, você poderá querer separar viewmodels, mesmo se eles forem baseados na mesma classe de modelo. Neste exemplo, as linhas ListView podem precisar de muito menos informações que a página de detalhes completa. Se recuperar os dados de que a página de detalhes precisa adicionar muito se sobrecarga, talvez você queira ter ambos modelos EmployeeListRowViewModel e EmployeeDetailViewModel que atendem a essas respectivas exibições.

Modelo de objeto Viewmodel

Usar uma classe base que implementa INotifyPropertyChanged significa que você não precisa reimplementar a interface em cada viewmodel. Considere o aplicativo de RH, conforme descrito na parte anterior deste módulo de treinamento. A classe EmployeeViewModel implementou a interface INotifyPropertyChanged e forneceu um método auxiliar chamado OnPropertyChanged para gerar o evento PropertyChanged. Outros viewmodels no projeto, como os que descrevem recursos atribuídos a um funcionário, também exigiriam que INotifyPropertyChanged se integrassem totalmente a um modo de exibição.

A biblioteca do Kit de Ferramentas do MVVM, parte do Kit de Ferramentas da Comunidade do .NET, é uma coleção de tipos leves padrão, independentes e que fornecem uma implementação inicial para a criação de aplicativos modernos usando o padrão MVVM.

Em vez de escrever sua própria classe base viewmodel, você herda da classe ObservableObject do kit de ferramentas, que fornece tudo o que você precisa para uma classe base viewmodel. O EmployeeViewModel pode ser simplificado de:

using System.ComponentModel;

public class EmployeeViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler? PropertyChanged;
    private Employee _model;

    public string Name
    {
        get {...}
        set
        {
            _model.Name = value;
            OnPropertyChanged(nameof(Name))
        }
    }

    protected void OnPropertyChanged(string propertyName) =>
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}

Para o seguinte código:

using Microsoft.Toolkit.Mvvm.ComponentModel;

public class EmployeeViewModel : ObservableObject
{
    private string _name;

    public string Name
    {
        get => _name;
        set => SetProperty(ref _name, value);
    }
}

O Kit de Ferramentas MVVM é distribuído por meio do pacote NuGet CommunityToolkit.Mvvm.

Verifique seu conhecimento

1.

Ao usar o padrão MVVM com o MAUI do .NET, seu modelo, exibição e viewmodel não são completamente separados um do outro. Qual opção descreve uma dependência comum entre as partes do MVVM?

2.

Que item tem a maior probabilidade de ser acoplado à plataforma e tem dificuldade para criar testes de unidade: o modelo, a exibição ou o viewmodel?