Navegação de aplicativos corporativos
Observação
Este e-book 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 controladas por lógica interna. 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 introduza acoplamento rígido e dependências entre as exibições.
- Como coordenar o processo pelo 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 entre si 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 a navegação de exibição inicial deve ser executada ou a navegação de exibição de modelo inicial. 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 de exibição que prioriza o modelo, 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 as exibições 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 abrange 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 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 certas 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 página do modelo de exibição primeiro.
Observação
O NavigationService
usado pelo aplicativo foi projetado apenas para executar a navegação hierárquica entre instâncias de ContentPage. Usar o serviço para navegar entre outros tipos de página pode resultar em um comportamento inesperado.
Navegando entre páginas
A lógica de navegação pode residir no code-behind de uma exibição ou em um modelo de exibição associado 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 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, uma NavigationService
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 fizessem referência a 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 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 de exibição no modelo 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 para 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 não é necessário.
Criando a instância do 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 do 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, que derivam 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 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 pode navegar pelas páginas, para frente e para trás, 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
CustomNavigationView
classe na 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 é 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, onde 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 exemplo de código a seguir:
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 vistas estão na mesma montagem que os tipos de modelo de vista.
- As visualizações estão em um arquivo . Exibe o namespace filho.
- Os modelos de vista estão em um arquivo . Namespace filho ViewModels.
- Os nomes de vista correspondem aos nomes dos modelos de vista, com "Modelo" removido.
Quando uma exibição é instanciada, ela é associada ao seu modelo de exibição correspondente. Para obter mais informações sobre como isso ocorre, consulte Criando automaticamente um modelo de vista com um localizador de modelo de vista.
Se a exibição que está sendo criada for um LoginView
, ela será encapsulada em uma nova instância da CustomNavigationView
classe e atribuída à 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 enviar a exibição que está sendo criada para a pilha de navegação. No entanto, se a instância recuperada CustomNavigationView
for null
, a exibição que está sendo criada será encapsulada em 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 contiver dados.
Dica
Considere armazenar páginas em cache. O cache de página resulta no consumo de memória para exibições que não são exibidas no momento. No entanto, sem o cache de página, isso significa que a análise XAML e a construção 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 forem encontrados tempos de carregamento de página lentos.
Depois que a exibição é criada e navegada, o InitializeAsync
método do modelo de exibição associado à exibição é executado. Para obter mais informações, consulte Passando parâmetros durante a navegação.
Navegando quando o aplicativo é iniciado
Quando o aplicativo é iniciado, o InitNavigation
App
método na 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 NavigationService
InitializeAsync
método:
public Task InitializeAsync()
{
if (string.IsNullOrEmpty(Settings.AuthAccessToken))
return NavigateToAsync<LoginViewModel>();
else
return NavigateToAsync<MainViewModel>();
}
O MainView
é navegado para se o aplicativo tiver um token de acesso armazenado em cache, que é usado para autenticação. Caso contrário, o LoginView
é 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, especificado 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, 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)
{
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 .BindingContext
Depois de navegar até o OrderDetailView
, o InternalNavigateToAsync
método executa o InitializeAsync
método do modelo de exibição associado à 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, as classes de modelo de exibição 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
navega para uma página da Web, o Navigating
evento é acionado, o que executa o NavigateCommand
arquivo .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 .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 a MainViewModel
navegação a seguir, 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 controladas por lógica interna. 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 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 lógica para controlar a navegação para garantir que determinadas regras de negócios sejam impostas.