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.
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 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á null
sendo 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.
Navegando quando o aplicativo é iniciado
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 NavigationService
InitializeAsync
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 BindingContext
exibiçã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 MainViewModel
navegaçã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.