Compartilhar via


Navegação de aplicativos empresariais

Observação

Este eBook foi publicado na primavera de 2017 e não foi atualizado desde então. Há muito no livro que permanece valioso, mas parte do material está desatualizado.

Xamarin.Forms inclui suporte para navegação de página, que normalmente resulta da interação do usuário com a interface do usuário ou do próprio aplicativo como resultado de alterações de estado internas controladas pela 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:

  • Como identificar a exibição a ser navegada, usando uma abordagem que não introduz acoplamento apertado e dependências entre exibições.
  • Como coordenar o processo pelo qual a exibição a ser navegada é instanciada e inicializada. Ao usar o MVVM, o modelo de exibição e exibição precisa ser instanciado e associado uns aos outros por meio do contexto de associação do modo de 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 deseja executar a navegação do primeiro modo de exibição ou exibir a navegação de primeiro modelo. 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 seu modelo de exibição correspondente e outros serviços dependentes. Uma abordagem alternativa é usar a navegação do modelo de exibição primeiro, em que a página para navegar se refere ao nome do tipo de modelo de exibição.
  • Como separar de forma limpa o comportamento de navegação do aplicativo entre os modos de exibição e os modelos de exibição. O padrão MVVM fornece uma separação entre a interface do usuário do aplicativo e sua apresentação e lógica de negócios. No entanto, o comportamento de navegação de um aplicativo geralmente abrangerá as partes da interface do usuário e das apresentações 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.
  • 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.
  • Como coordenar a navegação para garantir que determinadas regras de negócios 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 NavigationService classe usada para executar a navegação de primeira página do modelo de exibição.

Observação

O NavigationService usado pelo aplicativo foi projetado apenas para executar a navegação hierárquica entre instâncias do ContentPage. Usar o serviço para navegar entre outros tipos de página pode resultar em um comportamento inesperado.

A lógica de navegação pode residir no code-behind de uma exibição ou em um modelo de exibição associada a dados. 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 exercida por meio de testes de unidade. Além disso, o modelo de exibição pode implementar a lógica para controlar a navegação para 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.

Normalmente NavigationService , uma classe é invocada de modelos de exibição para promover a capacidade de teste. No entanto, navegar para exibições de modelos de exibição exigiria que os modelos de exibição referenciam exibições e, particularmente, exibições às quais o modelo de exibição ativa não está associado, o que não é recomendado. Portanto, o NavigationService apresentado aqui especifica o tipo de modelo de exibição como o destino para o qual navegar.

O aplicativo móvel eShopOnContainers usa a NavigationService classe para fornecer navegação do modelo de exibição primeiro. Essa classe implementa a interface INavigationService, que é mostrada no seguinte exemplo de código:

public interface INavigationService  
{  
    ViewModelBase PreviousPageViewModel { get; }  
    Task InitializeAsync();  
    Task NavigateToAsync<TViewModel>() where TViewModel : ViewModelBase;  
    Task NavigateToAsync<TViewModel>(object parameter) where TViewModel : ViewModelBase;  
    Task RemoveLastFromBackStackAsync();  
    Task RemoveBackStackAsync();  
}

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 Executa a navegação hierárquica em uma página especificada.
NavigateToAsync(parameter) Executa a navegação hierárquica para uma página especificada, passando um parâmetro.
RemoveLastFromBackStackAsync Remove a página anterior da pilha de navegação.
RemoveBackStackAsync Remove todas as páginas anteriores da pilha de navegação.

Além disso, a INavigationService interface especifica que uma classe de implementação deve fornecer uma PreviousPageViewModel propriedade . Essa propriedade retorna o tipo de modelo de exibição associado à página anterior na 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 do aplicativo móvel eShopOnContainers porque ele não é necessário.

Criando a instância navigationservice

A NavigationService classe , que implementa a INavigationService interface , é registrada como um singleton com o contêiner de injeção de dependência autofac, conforme demonstrado no exemplo de código a seguir:

builder.RegisterType<NavigationService>().As<INavigationService>().SingleInstance();

A INavigationService interface é resolvida no construtor de ViewModelBase classe, conforme demonstrado no exemplo de código a seguir:

NavigationService = ViewModelLocator.Resolve<INavigationService>();

Isso retorna uma referência ao NavigationService objeto armazenado no contêiner de injeção de dependência autofac, que é criado pelo InitNavigation método na App classe . Para obter mais informações, consulte Navegando quando o aplicativo é iniciado.

A classe ViewModelBase armazena a instância NavigationService em uma propriedade NavigationService, do tipo INavigationService. Portanto, todas as classes de modelo de exibição, derivadas da ViewModelBase classe , podem usar a NavigationService propriedade para acessar os métodos especificados pela INavigationService interface. Isso evita a sobrecarga de injetar o NavigationService objeto do contêiner de injeção de dependência de autofac em cada classe de modelo de exibição.

Manipulando solicitações de navegação

Xamarin.Forms fornece a NavigationPage classe , que implementa uma experiência de navegação hierárquica na qual o usuário é capaz de navegar por páginas, encaminhamentos e versões anteriores, conforme desejado. Para obter mais informações sobre navegação hierárquica, veja Navegação hierárquica.

Em vez de usar a NavigationPage classe diretamente, o aplicativo eShopOnContainers encapsula a NavigationPage classe na CustomNavigationView classe , conforme mostrado no exemplo de código a seguir:

public partial class CustomNavigationView : NavigationPage  
{  
    public CustomNavigationView() : base()  
    {  
        InitializeComponent();  
    }  

    public CustomNavigationView(Page root) : base(root)  
    {  
        InitializeComponent();  
    }  
}

A finalidade desse encapsulamento é para facilitar o NavigationPage estilo da instância dentro do arquivo XAML para a classe .

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

await NavigationService.NavigateToAsync<MainViewModel>();

O exemplo de código a seguir mostra os NavigateToAsync métodos fornecidos pela NavigationService classe :

public Task NavigateToAsync<TViewModel>() where TViewModel : ViewModelBase  
{  
    return InternalNavigateToAsync(typeof(TViewModel), null);  
}  

public Task NavigateToAsync<TViewModel>(object parameter) where TViewModel : ViewModelBase  
{  
    return InternalNavigateToAsync(typeof(TViewModel), parameter);  
}

Cada método permite que qualquer classe de modelo de exibição derivada da ViewModelBase classe execute navegação hierárquica invocando o InternalNavigateToAsync método . Além disso, o segundo NavigateToAsync método permite que os dados de navegação sejam especificados como um argumento que é passado para o modelo de exibição que está sendo navegado, no qual normalmente é usado para executar a inicialização. Para obter mais informações, consulte Passando parâmetros durante a navegação.

O InternalNavigateToAsync método executa a solicitação de navegação e é mostrado no seguinte exemplo de código:

private async Task InternalNavigateToAsync(Type viewModelType, object parameter)  
{  
    Page page = CreatePage(viewModelType, parameter);  

    if (page is LoginView)  
    {  
        Application.Current.MainPage = new CustomNavigationView(page);  
    }  
    else  
    {  
        var navigationPage = Application.Current.MainPage as CustomNavigationView;  
        if (navigationPage != null)  
        {  
            await navigationPage.PushAsync(page);  
        }  
        else  
        {  
            Application.Current.MainPage = new CustomNavigationView(page);  
        }  
    }  

    await (page.BindingContext as ViewModelBase).InitializeAsync(parameter);  
}  

private Type GetPageTypeForViewModel(Type viewModelType)  
{  
    var viewName = viewModelType.FullName.Replace("Model", string.Empty);  
    var viewModelAssemblyName = viewModelType.GetTypeInfo().Assembly.FullName;  
    var viewAssemblyName = string.Format(  
                CultureInfo.InvariantCulture, "{0}, {1}", viewName, viewModelAssemblyName);  
    var viewType = Type.GetType(viewAssemblyName);  
    return viewType;  
}  

private Page CreatePage(Type viewModelType, object parameter)  
{  
    Type pageType = GetPageTypeForViewModel(viewModelType);  
    if (pageType == null)  
    {  
        throw new Exception($"Cannot locate page type for {viewModelType}");  
    }  

    Page page = Activator.CreateInstance(pageType) as Page;  
    return page;  
}

O InternalNavigateToAsync método executa a navegação para um modelo de exibição chamando primeiro o CreatePage método . Esse método localiza a exibição que corresponde ao tipo de modelo de exibição especificado e cria e retorna uma instância desse tipo de exibição. Localizar a exibição que corresponde ao tipo de modelo de exibição usa uma abordagem baseada em convenção, que pressupõe que:

  • As exibições estão no mesmo assembly que os tipos de modelo de exibição.
  • Os modos de exibição estão em um . Exibe o namespace filho.
  • Os modelos de exibição estão em um . Namespace filho ViewModels.
  • Os nomes de exibição correspondem aos nomes do modelo de exibição, com "Modelo" removido.

Quando uma exibição é instanciada, ela é associada ao modelo de exibição correspondente. Para obter mais informações sobre como isso ocorre, consulte Criar automaticamente um modelo de exibição com um localizador de modelo de exibição.

Se o modo de exibição que está sendo criado for um LoginView, ele será encapsulado dentro de uma nova instância da CustomNavigationView classe e atribuído à Application.Current.MainPage propriedade . Caso contrário, a CustomNavigationView instância será recuperada e, desde que não seja nula, o PushAsync método será invocado para efetuar push da exibição que está sendo criada na pilha de navegação. No entanto, se a instância recuperada CustomNavigationView for , a exibição que está nullsendo criada será encapsulada dentro de uma nova instância da CustomNavigationView classe e atribuída à Application.Current.MainPage propriedade . Esse mecanismo garante que, durante a navegação, as páginas sejam adicionadas corretamente à pilha de navegação quando ela estiver vazia e quando ela contiver dados.

Dica

Considere armazenar em cache páginas. O cache de páginas resulta no consumo de memória para exibições que não são exibidas no momento. No entanto, sem o cache de páginas, isso significa que a análise e a construção XAML da página e seu modelo de exibição ocorrerão sempre que uma nova página for navegada, o que pode ter um impacto no desempenho de uma página complexa. Para uma página bem projetada que não usa um número excessivo de controles, o desempenho deve ser suficiente. No entanto, o cache de página pode ajudar se os tempos de carregamento de página lentos forem encontrados.

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

Quando o aplicativo é iniciado, o InitNavigation método na App classe é invocado. O seguinte exemplo de código mostra esse método:

private Task InitNavigation()  
{  
    var navigationService = ViewModelLocator.Resolve<INavigationService>();  
    return navigationService.InitializeAsync();  
}

O método cria um novo NavigationService objeto no contêiner de injeção de dependência autofac e retorna uma referência a ele, antes de invocar seu InitializeAsync método.

Observação

Quando a INavigationService interface é resolvida pela ViewModelBase classe , o contêiner retorna uma referência ao NavigationService objeto que foi criado quando o método InitNavigation é invocado.

O exemplo de código a seguir mostra o NavigationServiceInitializeAsync método :

public Task InitializeAsync()  
{  
    if (string.IsNullOrEmpty(Settings.AuthAccessToken))  
        return NavigateToAsync<LoginViewModel>();  
    else  
        return NavigateToAsync<MainViewModel>();  
}

O MainView será navegado para se o aplicativo tiver um token de acesso armazenado em cache, que é usado para autenticação. Caso contrário, o LoginView será navegado para.

Para obter mais informações sobre o contêiner de injeção de dependência do Autofac, consulte Introdução à injeção de dependência.

Passando parâmetros durante a navegação

Um dos NavigateToAsync métodos, especificados pela interface , permite que os INavigationService dados de navegação sejam especificados como um argumento que é passado para o modelo de exibição que está sendo navegado, para o qual 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)  
{  
    await NavigationService.NavigateToAsync<OrderDetailViewModel>(order);  
}

Esse método invoca a navegação para o OrderDetailViewModel, passando uma Order instância que representa a ordem que o usuário selecionou na ProfileView página. Quando a NavigationService classe cria o OrderDetailView, a OrderDetailViewModel classe é instanciada e atribuída ao da BindingContextexibição. Depois de navegar até o OrderDetailView, o InternalNavigateToAsync método executa o InitializeAsync método do modelo de exibição associado da exibição.

O InitializeAsync método é definido na ViewModelBase classe como um método que pode ser substituído. Esse método especifica um object argumento que representa os dados a serem passados para um modelo de exibição durante uma operação de navegação. Portanto, exibir classes de modelo que desejam receber dados de uma operação de navegação fornecem sua própria implementação do InitializeAsync método para executar a inicialização necessária. O exemplo de código a seguir mostra o método InitializeAsync da classe OrderDetailViewModel:

public override async Task InitializeAsync(object navigationData)  
{  
    if (navigationData is Order)  
    {  
        ...  
        Order = await _ordersService.GetOrderAsync(  
                        Convert.ToInt32(order.OrderNumber), authToken);  
        ...  
    }  
}

Esse método recupera a Order instância que foi passada para o modelo de exibição durante a operação de navegação e a usa para recuperar os detalhes completos do pedido da OrderService instância.

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 Navigating evento 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, a URL da página da Web é passada como um parâmetro para o registrado Action.

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

private async Task NavigateAsync(string url)  
{  
    ...          
    await NavigationService.NavigateToAsync<MainViewModel>();  
    await NavigationService.RemoveLastFromBackStackAsync();  
    ...  
}

Esse método invoca a navegação para o e, após a MainViewModelnavegação, remove a LoginView página da pilha de navegação.

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 obtido 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

Xamarin.Forms inclui suporte para navegação de página, que normalmente resulta da interação do usuário com a interface do usuário ou do 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 NavigationService classe , que é usada para executar a navegação do modelo de exibição primeiro a partir de 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 a lógica para controlar a navegação para garantir que determinadas regras de negócios sejam impostas.