Navigation
Navigation within a DEM app can result from the user's interaction with the UI or from the app itself as a result of internal logic-driven state changes. Page navigation requests are usually triggered from a view, with the navigation logic either being in the view's code-behind, or in the data bound view model.
Navigating in a DEM application is conceptually different than standard navigation in Xamarin.Forms. While Xamarin.Forms navigation relies on a Page class instance to navigate, DEM removes all dependencies on Page types to achieve loosely coupled navigation from within a ViewModel. In DEM, the concept of navigating to a View does not exist. Instead, you simply navigate to an view model type, or a unique identifier URI, which represents the target view you wish to navigate to in your application.
Page navigation in DEM is accomplished by using the INavigationService.
One approach for specifying a navigation target is to use a navigation service, which would require the type of the view-model to navigate to. Because a navigation service is usually invoked from view models in order to promote testability, this approach would require view models to reference other view-model (and particularly views that the view model isn't associated with), which is not recommended. The recommended approach is to use a string to specify the navigation target that can be easily passed to a navigation service, and which is easily testable.
Creating pages
Pages in Xamarin.Forms are controls that support navigation and contain other controls. All page classes are subtypes of the Xamarin.Forms.ContentPage class, and represent content that can be navigated to by the user.
In apps that use DEM, each page should derive from the classes in the AssecoSEE.DEM.Core.Pages namespace.
- UsecaseContentPage - in-line master page, master page can navigate to other master pages or popup pages
- UsecaseSubPage - child page, this page can be call only from the master page in multi page view models
- ModalContentPage - modal popup page, can be called from master or child page
The following code example shows how the SamplePage derives from the UsecaseContentPage class.
<pages:UsecaseContentPage x:Class="AssecoSEE.DEMO.Customized.SamplePage"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:components="clr-namespace:AssecoSEE.DEM.Components.Components;assembly=AssecoSEE.DEM.Components"
xmlns:ctrl="clr-namespace:AssecoSEE.DEM.Components.Controls;assembly=AssecoSEE.DEM.Components"
xmlns:ext="clr-namespace:AssecoSEE.DEM.Components.XamlExtensions;assembly=AssecoSEE.DEM.Components"
xmlns:pages="clr-namespace:AssecoSEE.DEM.Core.Pages;assembly=AssecoSEE.DEM.Core">
<ContentPage.Content>
</ContentPage.Content>
</pages:UsecaseContentPage>
[Tip] DEM uses the MVVM pattern that abstracts the user interface for the app. With MVVM you rarely need to customize the code-behind files. Instead, the controls of the user interface are bound to properties of a view model object. If page-related code is required, it should be limited to conveying data to and from the page's view model object. More info about MVVM
Registering
Registering your Page for navigation is essentially mapping a unique identifier/key to the target view during the bootstrapping process. In order to register your Pages for Navigation, override the RegisterTypes method in your App.cs. App.cs:
protected override void RegisterTypes(ITypeCatalog typeCatalog)
{
typeCatalog.Add(
new ViewModelInfo(typeof(AssecoSEE.DEMO.Customized.SampleViewModel))
);
}
By default, DEM uses a convention to find the View(Page) for the ViewModel (PageName + ViewModel = PageNameViewModel). This process uses reflection which introduces a small performance hit. If you would like to avoid this performance hit, you can provide the Page types when registering your ViewModel in DI contener.
new ViewModelInfo(typeof(Usecases.Transfers.TransfersViewModel))
{
PageTypes = new Type[]{
typeof(Usecases.Transfers.TransfersPage),
typeof(Usecases.Transfers.TransferFilterPage),
typeof(Usecases.Transfers.TransferDetailsPage)
}
}
Navigating
The DEM typically triggers navigation requests from user interaction in the views. Beocuse that we implement navigation commands that call navigation service
Navigating between master-pages
protected virtual async Task GoToCardLimits(object arg)
{
var account = arg as DataObject<Account>;
await App.Navigation.NavigateToAsync<CardLimits.CardLimitsViewModel>(new AccountDetailsNavigationParams()
{
AccountNumber = account.Value.AccountNumber,
Data = account
});
}
Navigating with URI
protected virtual async Task<bool> GotoPageAsync(object parametar)
{
string query = parametar?.ToString() ?? String.Empty;
var result = await NavigationService.NavigateToAsync(NavigationUriParser.Parse(query));
return result.Success;
}
Navigating to the sub pages
Navigate to the sub-page using page name, becouse one view-model can have multiple pages. View model must be mult-page view model or derived class like page wizard or page list view model.
protected virtual async Task GoToAccountSettings(object arg)
{
SubPageTitle = "accountdata_card_settings";
SubPageSubtitle = Account.GetPropertyValue<string>("AccountName");
await App.Navigation.NavigateToAsync(this, "AccountSettingsPage");
}
Navigating to the modal page
await App.Device.DisplayModalAsync<string>(typeof(ChangePasswordViewModel));
Navigating in the XAML
DEM allows you to declare your navigation directly inside your Xaml. This approach really helps with basic navigation scenarios, and can also help clean up your ViewModels.
<ToolbarItem Name="RightMenuItem3" Order="Secondary" Priority="3" Text="{ext:Translate payment_standing_orders_new_balance_transfer}" Command="{Binding StartViewModelCommand}" CommandParameter="StandingOrderBalanceTransferViewModel" />
Getting the Navigation Service
Once your views are registered for navigation, you must use the INavigationService to perform navigation. To obtain the INavigationService in your ViewModels simply ask for it as a property.
App.Navigation
Once you have the INavigationService in your ViewModel, you can navigate to your target views by calling the INavigationService.NavigateToAsync
method and provide the unique identifier/key that represents the target View-Model.
Navigation extensions
Going back to the previous View is as simple calling the INavigationService.GoBackAsync
method.
await NavigationService.NavigateBackAsync(null);
DEM provide some commands for base navigation in ViewModel like
public Command GoToParentPageCommand { get; protected set; }
public Command GoToBackPageCommand { get; protected set; }
public Command GoToHomepageCommand { get; protected set; }
protected virtual async Task BackPageAsync()
{
await NavigationService.NavigateBackAsync(null);
}
protected virtual async Task ParentPageAsync()
{
await NavigationService.NavigateToParentAsync();
}
protected virtual async Task<NavigationResult> GoToHomepageAsync()
{
return await NavigationService.NavigateToRootAsync();
}
Passing parameters
The DEM navigation service also allows you to pass parameters to the target view during the navigation process. Passing parameters to the next ViewModel can be done using an overload of the INavigationService.NavigateToAsync method. This overload accepts a NavigationParameters object that can be used to supply data to the next ViewModel. The NavigationParameters object is in fact just a dictionary. It can accept any arbitrary object as a value.
Creating Parameters
Creating parameters can be done in a variety of ways.
var navigationParams = new NavigationParameters ();
navigationParams.Add("model", new Contact ());
App.NavigateToAsync(this, "MainPage", navigationParams);
You can also create an HTML query string to generate your parameter collection.
protected virtual async Task GoToTransactionsAsync(object parametar)
{
await App.Navigation.NavigateToAsync(NavigationUriParser.Parse("TransactionsViewModel?AccountNumber=AccountNumber&Kinds=fcx"));
}
Getting Parameters
Getting the parameters that were passed to the target ViewModel being navigated to can be achieved by using the INavigationAware
interface on the corresponding ViewModel.
public override Task<bool> InitializeAsync(NavigationParameters navigationParameters = null)
{
if (navigationParameters is ProductCalculatorNavigationParameter productCalculatorNavigationParameter)
{
CurrentProduct = productCalculatorNavigationParameter.Data;
SetSubTitle(CurrentProduct);
}
return Task.FromResult(SampleProperty);
}
Reading Parameters
Now that you have access to the parameters, you must read the parameters from the available NavigationParameters
instance. There are two ways to read parameters; by using the parameter key as an indexer on the parameters instance, or by using the GetValue
/GetValues
method on the parameters instance.
public override async Task<bool> InitializeAsync(NavigationParameters navigationParameters = null)
{
if (navigationParameters!=null && navigationParameters.ContainsKey("Campaignid"))
{
Campaignid = navigationParameters.GetValue<string>("Campaignid", String.Empty);
}
}
Validation for navigation
From now on the method is checked from view-model Initialize, so if the method brings back false navigations won’t be performed
var viewModel = await InternalNavigateToViewModelAsyncInner(useModalNavigation, info, parameter);
if (viewModel == null)
{
return null;
}
All this is so we wouldn’t have to write validations on all navigation commands that can be scattered around modules rather than the View-Models themselves which are the only ones responsible for the business logic of the screen. Here are the examples on the view-model CurrencyExchangeSellViewModel
public override async Task<bool> Initialize(NavigationParameters navigationParameters = null)
{
var output = await base.Initialize(navigationParameters);
if (!output)
{
return false;
}
if (AccountCollectionFrom != null || AccountCollectionFrom.Count() == 0)
{
await App.Device.DisplayMessage("User does not have options to sell in exchange. There is no accounts from!");
return false;
}
if (AccountCollectionTo != null || AccountCollectionTo.Count() == 0)
{
await App.Device.DisplayMessage("User does not have options to sell in exchange. There is no accounts to!");
return false;
}
return false;
}
It firstly checks it out from PaymentNavigationParams and afterwards logic for each view-model can be put separately. So wherever we navigate from, a message appears on this view-model if it doesn’t have all the necessary to show up.
In this way we centralize that all the logic stays with the elements that are made, and not where they are called, for example, from the review of the exchange rate list or tomorrow from push message, etc.