Compartilhar via


Navegação

Dica

Esse conteúdo é um trecho do livro eletrônico, Padrões de Aplicativo Empresarial Usando .NETMAUI, disponível em .NET Docs ou em PDF para download gratuito que pode ser lido off-line.

Miniatura da capa do livro eletrônico MAUI Padrões de aplicativo empresarial usando .NET.

O .NET MAUI inclui suporte para navegação de página, que normalmente resulta da interação do usuário com a interface do usuário ou com o próprio aplicativo como resultado de alterações de estado internas controladas por lógica. No entanto, a navegação pode ser complexa de implementar em aplicativos que usam o padrão MVVM (Model-View-ViewModel), pois os seguintes desafios devem ser atendidos:

  • Identificar a exibição a ser navegada usando uma abordagem que não introduz acoplamento rígido e dependências entre exibições.
  • Coordenar o processo segundo o qual a exibição a ser navegada é instanciada e inicializada. Ao usar o MVVM, a exibição e o modelo de exibição precisam ser instanciados e associados uns aos outros por meio do contexto de associação da exibição. Quando um aplicativo está usando um contêiner de injeção de dependência, a instanciação de exibições e modelos de exibição pode exigir um mecanismo de construção específico.
  • Se deve ser executada a navegação de exibição primeiro ou a navegação de modelo de exibição primeiro. Com a navegação de exibição primeiro, a página para a qual navegar se refere ao nome do tipo de exibição. Durante a navegação, a exibição especificada é instanciada, juntamente com o modelo de exibição correspondente e outros serviços dependentes. Uma abordagem alternativa é usar a navegação de modelo de exibição primeiro, em que a página para a qual navegar se refere ao nome do tipo de modelo de exibição.
  • Determinar como separar corretamente o comportamento de navegação do aplicativo entre as exibições e os modelos de exibição. O padrão MVVM separa a interface do usuário do aplicativo e sua lógica de apresentação e de negócios, mas não fornece um mecanismo direto para emparelhá-los. No entanto, o comportamento de navegação de um aplicativo muitas vezes abrange as partes de interface do usuário e de apresentação do aplicativo. O usuário geralmente iniciará a navegação de uma exibição e a exibição será substituída como resultado da navegação. No entanto, a navegação geralmente também pode precisar ser iniciada ou coordenada de dentro do modelo de exibição.
  • Determinando como passar parâmetros durante a navegação para fins de inicialização. Por exemplo, se o usuário navegar até uma exibição para atualizar os detalhes do pedido, os dados do pedido precisarão ser passados para a exibição para que possam exibir os dados corretos.
  • Coordenando a navegação para garantir que regras de negócios específicas sejam obedecidas. Por exemplo, os usuários podem ser solicitados antes de navegar para longe de uma exibição para que corrijam dados inválidos ou para enviar ou descartar alterações de dados que foram feitas dentro da exibição.

Este capítulo aborda esses desafios apresentando uma classe de serviço de navegação chamada MauiNavigationService que é usada para executar a navegação de página de modelo de exibição primeiro.

Observação

O MauiNavigationService usado pelo aplicativo é simplista e não abrange todos os tipos de navegação possíveis. Os tipos de navegação necessários para seu aplicativo podem exigir funcionalidade adicional.

A lógica de navegação pode residir no code-behind ou no modelo de exibição associado a dados de uma exibição. Embora colocar a lógica de navegação em uma exibição possa ser a abordagem mais simples, ela não é facilmente testável por meio de testes de unidade. Colocar a lógica de navegação em classes de modelo de exibição significa que a lógica pode ser verificada por meio de testes de unidade. Além disso, o modelo de exibição pode implementar lógica para controlar a navegação e garantir que determinadas regras de negócios sejam impostas. Por exemplo, um aplicativo pode não permitir que o usuário navegue para longe de uma página sem primeiro garantir que os dados inseridos sejam válidos.

Um serviço de navegação normalmente é invocado de modelos de exibição, a fim de promover a capacidade de teste. No entanto, navegar para exibições de modelos de exibição exigiria que os modelos de exibição referenciassem as exibições e, particularmente, exibições às quais o modelo de exibição ativo não está associado, o que não é recomendado. Portanto, o MauiNavigationService apresentado aqui especifica o tipo de modelo de exibição como o destino ao qual navegar.

O aplicativo multiplataforma eShop usa a classe MauiNavigationService para fornecer navegação de modelo de exibição primeiro. Essa classe implementa a interface INavigationService, que é mostrada no seguinte exemplo de código:

public interface INavigationService
{
    Task InitializeAsync();

    Task NavigateToAsync(string route, IDictionary<string, object> routeParameters = null);

    Task PopAsync();
}

Essa interface especifica que uma classe de implementação deve fornecer os seguintes métodos:

Método Finalidade
InitializeAsync Executa a navegação para uma das duas páginas quando o aplicativo é iniciado.
NavigateToAsync(string route, IDictionary<string, object> routeParameters = null) Executa a navegação hierárquica para uma página especificada usando uma rota de navegação registrada. Opcionalmente, pode passar parâmetros de rota nomeados a serem usados para processamento na página de destino
PopAsync Remove a página atual da pilha de navegação.

Observação

Uma interface INavigationService normalmente também especificaria um método GoBackAsync, que é usado para retornar programaticamente à página anterior na pilha de navegação. No entanto, esse método está ausente no aplicativo multiplataforma eShop porque não é necessário.

Criando a instância de MauiNavigationService

A classe MauiNavigationService, que implementa a interface INavigationService, é registrada como um singleton com o contêiner de injeção de dependência no método MauiProgram.CreateMauiApp(), conforme demonstrado no seguinte exemplo de código:

mauiAppBuilder.Services.AddSingleton<INavigationService, MauiNavigationService>();;

A interface INavigationService pode, então, ser resolvida adicionando-a ao construtor de nossas exibições e modelos de exibição, conforme demonstrado no seguinte exemplo de código:

public AppShell(INavigationService navigationService)

Isso retorna uma referência ao objeto MauiNavigationService armazenado no contêiner de injeção de dependência.

A classe ViewModelBase armazena a instância MauiNavigationService em uma propriedade NavigationService, do tipo INavigationService. Portanto, todas as classes de modelo de exibição, que derivam da classe ViewModelBase, podem usar a propriedade NavigationService para acessar os métodos especificados pela interface INavigationService.

Manipulando solicitações de navegação

O .NET MAUI fornece várias maneiras de navegar dentro de um aplicativo. A maneira tradicional de navegar é com a classe NavigationPage, que implementa uma experiência de navegação hierárquica na qual o usuário pode navegar pelas páginas, para frente e para trás, conforme desejado. O aplicativo eShop usa o componente Shell como o contêiner raiz para o aplicativo e como um host de navegação. Para obter mais informações sobre a navegação do Shell, consulte Navegação de Shell na Central de Desenvolvedores do Microsoft.

A navegação é executada dentro de classes de modelo de exibição invocando um dos métodos NavigateToAsync, especificando o caminho de rota para a página que está sendo navegada, conforme demonstrado no seguinte exemplo de código:

await NavigationService.NavigateToAsync("//Main");

O seguinte exemplo de código mostra o método NavigateToAsync fornecido pela classe MauiNavigationService:

public Task NavigateToAsync(string route, IDictionary<string, object> routeParameters = null)
{
    return
        routeParameters != null
            ? Shell.Current.GoToAsync(route, routeParameters)
            : Shell.Current.GoToAsync(route);
}

O controle Shell do .NET MAUI já está familiarizado com a navegação baseada em rota, portanto, o método NavigateToAsync funciona para mascarar essa funcionalidade. O método NavigateToAsync permite que os dados de navegação sejam especificados como um argumento que é passado para o modelo de exibição que está sendo navegado, onde normalmente é usado para executar a inicialização. Para obter mais informações, consulte Passando parâmetros durante a navegação.

Importante

Há várias maneiras de executar a navegação no .NET MAUI. O MauiNavigationService foi criado especificamente para trabalhar com Shell. Se você estiver usando um NavigationPage ou TabbedPage ou um mecanismo de navegação diferente, esse serviço de roteamento precisará ser atualizado para funcionar usando esses componentes.

Para registrar rotas para o MauiNavigationService, precisamos fornecer informações de rota do XAML ou no code-behind. O exemplo a seguir mostra o registro de rotas via XAML.

<?xml version="1.0" encoding="UTF-8" ?>
<Shell
    xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    xmlns:views="clr-namespace:eShop.Views"
    x:Class="eShop.AppShell">

    <!-- Omitted for brevity -->

    <FlyoutItem >
        <ShellContent x:Name="login" ContentTemplate="{DataTemplate views:LoginView}" Route="Login" />
    </FlyoutItem>

    <TabBar x:Name="main" Route="Main">
        <ShellContent Title="CATALOG" Route="Catalog" Icon="{StaticResource CatalogIconImageSource}" ContentTemplate="{DataTemplate views:CatalogView}" />
        <ShellContent Title="PROFILE" Route="Profile" Icon="{StaticResource ProfileIconImageSource}" ContentTemplate="{DataTemplate views:ProfileView}" />
    </TabBar>
</Shell>

Neste exemplo, os objetos de interface do usuário ShellContent e TabBar estão definindo sua propriedade Route. Esse é o método preferencial de registrar rotas para objetos de interface do usuário controlados por um Shell.

Se tivermos objetos que serão adicionados à pilha de navegação posteriormente, precisaremos adicioná-los por meio do code-behind. O exemplo a seguir mostra o registro de rotas no code-behind.

Routing.RegisterRoute("Filter", typeof(FiltersView));
Routing.RegisterRoute("Basket", typeof(BasketView));

No code-behind, chamaremos o método Routing.RegisterRoute que usa um nome de rota como o primeiro parâmetro e um tipo de exibição como o segundo parâmetro. Quando um modelo de exibição usa a propriedade NavigationService para navegar, o objeto Shell do aplicativo procura por rotas registradas e efetua push delas para a pilha de navegação.

Depois que a exibição é criada e navegada, os métodos ApplyQueryAttributes e InitializeAsync do modelo de exibição associado da exibição são executados. Para obter mais informações, consulte Passando parâmetros durante a navegação.

Quando o aplicativo é iniciado, um objeto Shell é definido como a exibição raiz do aplicativo. Depois de definido, o Shell será usado para controlar o registro de rota e estará presente na raiz do nosso aplicativo daqui para frente. Depois que o Shell tiver sido criado, poderemos aguardar que ele seja anexado ao aplicativo usando o método OnParentSet para inicializar a rota de navegação. O seguinte exemplo de código mostra esse método:

protected override async void OnParentSet()
{
    base.OnParentSet();

    if (Parent is not null)
    {
        await _navigationService.InitializeAsync();
    }
}

O método usa uma instância de INavigationService à qual é fornecida o construtor da injeção de dependência e invoca seu método InitializeAsync.

O seguinte exemplo de código mostra a implementação do método MauiNavigationService.InitializeAsync:

public Task InitializeAsync()
{
    return NavigateToAsync(string.IsNullOrEmpty(_settingsService.AuthAccessToken)
        ? "//Login"
        : "//Main/Catalog");
}

A rota //Main/Catalog será acessada se o aplicativo tiver um token de acesso armazenado em cache, que é usado para autenticação. Caso contrário, a rota //Login será acessada.

Passando parâmetros durante a navegação

O método NavigateToAsync, especificado pela interface INavigationService, permite que os dados de navegação sejam especificados como um IDictionary<string, object> dos dados que é passado para o modelo de exibição que está sendo navegado, onde normalmente é usado para executar a inicialização.

Por exemplo, a classe ProfileViewModel contém um OrderDetailCommand que é executado quando o usuário seleciona uma ordem na página ProfileView. Por sua vez, isso executa o método OrderDetailAsync, que é mostrado no seguinte exemplo de código:

private async Task OrderDetailAsync(Order order)
{
    if (order is null)
    {
        return;
    }

    await NavigationService.NavigateToAsync(
        "OrderDetail",
        new Dictionary<string, object>{ { "OrderNumber", order.OrderNumber } });
}

Esse método invoca a navegação para a rota OrderDetail, passando informações de número da ordem que o usuário selecionou. Quando a estrutura de injeção de dependência cria o OrderDetailView para a rota OrderDetail junto com a classe OrderDetailViewModel, que é atribuída ao BindingContextda exibição. O OrderDetailViewModel tem um atributo adicionado a ele que permite que receba dados do serviço de navegação, conforme mostrado no exemplo de código abaixo.

[QueryProperty(nameof(OrderNumber), "OrderNumber")]
public class OrderDetailViewModel : ViewModelBase
{
    public int OrderNumber { get; set; }
}

O atributo QueryProperty nos permite fornecer um parâmetro para uma propriedade para mapear valores e uma chave para localizar valores do dicionário de parâmetros de consulta. Neste exemplo, a chave "OrderNumber" e o valor do número da ordem foram fornecidos durante a chamada NavigateToAsync. O modelo de exibição encontrou a chave "OrderNumber" e mapeou o valor para a propriedade OrderNumber. Em seguida, a propriedade OrderNumber pode ser usada posteriormente para recuperar os detalhes completos do pedido da instância OrderService.

Invocando a navegação usando comportamentos

A navegação geralmente é disparada de uma exibição por uma interação do usuário. Por exemplo, o LoginView executa a navegação após a autenticação bem-sucedida. O seguinte exemplo de código mostra como a navegação é invocada por um comportamento:

<WebView>
    <WebView.Behaviors>
        <behaviors:EventToCommandBehavior
            EventName="Navigating"
            EventArgsConverter="{StaticResource WebNavigatingEventArgsConverter}"
            Command="{Binding NavigateCommand}" />
    </WebView.Behaviors>
</WebView>

Em runtime, EventToCommandBehavior responderá à interação com WebView. Quando o WebView navegar para uma página da Web, o evento Navigating será acionado, o que executará o NavigateCommand no LoginViewModel. Por padrão, os argumentos do evento para o evento serão passados para o comando. Esses dados são convertidos conforme são passados entre a origem e o destino pelo conversor especificado na propriedade EventArgsConverter, que retorna o Url do WebNavigatingEventArgs. Portanto, quando o NavigationCommand é executado, o Url da página da Web é passado como um parâmetro para a Ação registrada.

Por sua vez, o NavigationCommand executa o método NavigateAsync, que é mostrado no seguinte exemplo de código:

private async Task NavigateAsync(string url)
{
    // Omitted for brevity.
    if (!string.IsNullOrWhiteSpace(accessToken))
    {
        _settingsService.AuthAccessToken = accessToken;
        _settingsService.AuthIdToken = authResponse.IdentityToken;
        await NavigationService.NavigateToAsync("//Main/Catalog");
    }
}

Esse método invoca a rota NavigationService do aplicativo para a rota //Main/Catalog.

Confirmando ou cancelando a navegação

Um aplicativo pode precisar interagir com o usuário durante uma operação de navegação para que o usuário possa confirmar ou cancelar a navegação. Isso pode ser necessário, por exemplo, quando o usuário tenta navegar antes de preencher totalmente uma página de entrada de dados. Nessa situação, um aplicativo deve fornecer uma notificação que permita que o usuário navegue para longe da página ou cancele a operação de navegação antes que ela ocorra. Isso pode ser feito em uma classe de modelo de exibição usando a resposta de uma notificação para controlar se a navegação é invocada ou não.

Resumo

O .NET MAUI inclui suporte para navegação de página, que normalmente resulta da interação do usuário com a interface do usuário ou com o próprio aplicativo como resultado de alterações de estado internas controladas por lógica. No entanto, a navegação pode ser complexa de implementar em aplicativos que usam o padrão MVVM.

Este capítulo apresentou uma classe NavigationService, que é usada para executar a navegação de modelo de exibição primeiro usando modelos de exibição. Colocar a lógica de navegação em classes de modelo de exibição significa que a lógica pode ser exercida por meio de testes automatizados. Além disso, o modelo de exibição pode implementar lógica para controlar a navegação e garantir que determinadas regras de negócios sejam impostas.