Enterprise-App-Navigation
Hinweis
Dieses eBook wurde im Frühjahr 2017 veröffentlicht und wurde seitdem nicht aktualisiert. Es gibt viel in dem Buch, das wertvoll bleibt, aber einige der Materialien sind veraltet.
Xamarin.Forms enthält Unterstützung für die Seitennavigation, die in der Regel aus der Interaktion des Benutzers mit der Benutzeroberfläche oder aus der App selbst infolge von internen logikgesteuerten Zustandsänderungen resultiert. Die Implementierung der Navigation in Apps, die das Model-View-ViewModel-Muster (MVVM) verwenden, kann jedoch komplex sein, da die folgenden Herausforderungen zu bewältigen sind:
- Ermitteln der zu navigierenden Ansicht mithilfe eines Ansatzes, bei dem keine enge Kopplung und Abhängigkeiten zwischen Ansichten eingeführt werden.
- Koordinieren des Prozesses, zu dem die zu navigierende Ansicht instanziiert und initialisiert wird. Bei Verwendung von MVVM muss das Ansichts- und Ansichtsmodell über den Bindungskontext der Ansicht instanziiert und miteinander verknüpft werden. Wenn eine App einen Container zum Einfügen von Abhängigkeiten verwendet, ist möglicherweise die Instanziierung von Ansichten und Ansichtsmodellen ein bestimmter Konstruktionsmechanismus erforderlich.
- Unabhängig davon, ob die Ansichts-first-Navigation oder die Erste Ansichtsmodellnavigation durchgeführt werden soll. Wenn Ansichtsnavigation zuerst verwendet wird, verweist die Seite, zu der navigiert werden soll, auf den Namen des Ansichtstyps. Während der Navigation wird die angegebene Ansicht instanziiert, zusammen mit dem entsprechenden Ansichtsmodell und anderen abhängigen Diensten. Eine alternative Methode besteht darin, die Ansichtsmodell-erste Navigation zu verwenden, bei der die Seite, zu der navigiert werden soll, auf den Namen des Ansichtsmodelltyps verweist.
- Wie Sie das Navigationsverhalten der App bereinigt über die Ansichten und Ansichtsmodelle trennen. Das MVVM-Muster bietet eine Trennung zwischen der Benutzeroberfläche der App und der zugehörigen Präsentations- und Geschäftslogik. Das Navigationsverhalten einer App umfasst jedoch häufig die Ui- und Präsentationsteile der App. Der Benutzer initiiert die Navigation häufig über eine Ansicht, und die Ansicht wird als Ergebnis der Navigation ersetzt. Die Navigation muss jedoch häufig auch innerhalb des Ansichtsmodells initiiert oder koordiniert werden.
- Übergeben von Parametern während der Navigation für Initialisierungszwecke. Wenn der Benutzer beispielsweise zu einer Ansicht navigiert, um Bestelldetails zu aktualisieren, müssen die Bestelldaten an die Ansicht übergeben werden, damit sie die richtigen Daten anzeigen kann.
- Wie Sie die Navigation koordinieren, um sicherzustellen, dass bestimmte Geschäftsregeln eingehalten werden. So können Benutzer beispielsweise aufgefordert werden, ungültige Daten zu korrigieren, bevor sie eine Ansicht verlassen, oder sie werden aufgefordert, Datenänderungen, die in der Ansicht vorgenommen wurden, zu übermitteln oder zu verwerfen.
In diesem Kapitel werden diese Herausforderungen behandelt, indem eine NavigationService
Klasse vorgestellt wird, die zum Ausführen der Ansichtsmodell-first-Seitennavigation verwendet wird.
Hinweis
Die NavigationService
von der App verwendete App dient nur zum Ausführen der hierarchischen Navigation zwischen ContentPage-Instanzen. Die Verwendung des Diensts zum Navigieren zwischen anderen Seitentypen kann zu unerwartetem Verhalten führen.
Navigieren zwischen Seiten
Navigationslogik kann sich im CodeBehind einer Ansicht oder in einem datengebundenen Ansichtsmodell befinden. Während das Platzieren von Navigationslogik in einer Ansicht möglicherweise der einfachste Ansatz ist, ist sie nicht einfach durch Komponententests zu testen. Das Platzieren von Navigationslogik in Ansichtsmodellklassen bedeutet, dass die Logik über Komponententests ausgeübt werden kann. Darüber hinaus kann das Ansichtsmodell logik implementieren, um die Navigation zu steuern, um sicherzustellen, dass bestimmte Geschäftsregeln erzwungen werden. Beispielsweise erlaubt eine App dem Benutzer möglicherweise nicht, von einer Seite zu navigieren, ohne vorher sicherzustellen, dass die eingegebenen Daten gültig sind.
Eine NavigationService
Klasse wird in der Regel aus Ansichtsmodellen aufgerufen, um die Testbarkeit zu fördern. Das Navigieren zu Ansichten aus Ansichtsmodellen erfordert jedoch, dass die Ansichtsmodelle auf Ansichten verweisen, und insbesondere Ansichten, denen das aktive Ansichtsmodell nicht zugeordnet ist, was nicht empfohlen wird. Daher gibt die NavigationService
hier dargestellte Darstellung den Ansichtsmodelltyp als Ziel an, zu dem navigiert werden soll.
Die mobile eShopOnContainers-App verwendet die NavigationService
Klasse zum Bereitstellen der Ansichtsmodell-first-Navigation. Diese Klasse implementiert die INavigationService
-Schnittstelle, die im folgenden Codebeispiel gezeigt wird:
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();
}
Diese Schnittstelle gibt an, dass eine implementierende Klasse die folgenden Methoden bereitstellen muss:
Methode | Zweck |
---|---|
InitializeAsync |
Führt beim Starten der App die Navigation zu einer von zwei Seiten aus. |
NavigateToAsync |
Führt eine hierarchische Navigation zu einer angegebenen Seite aus. |
NavigateToAsync(parameter) |
Führt eine hierarchische Navigation zu einer angegebenen Seite durch, wobei ein Parameter übergeben wird. |
RemoveLastFromBackStackAsync |
Entfernt die vorherige Seite aus dem Navigationsstapel. |
RemoveBackStackAsync |
Entfernt alle vorherigen Seiten aus dem Navigationsstapel. |
Darüber hinaus gibt die INavigationService
Schnittstelle an, dass eine implementierungsklasse eine PreviousPageViewModel
Eigenschaft bereitstellen muss. Diese Eigenschaft gibt den Ansichtsmodelltyp zurück, der der vorherigen Seite im Navigationsstapel zugeordnet ist.
Hinweis
Eine INavigationService
-Schnittstelle gibt normalerweise auch eine GoBackAsync
-Methode an, die verwendet wird, um programmgesteuert zur vorherigen Seite im Navigationsstapel zurückzukehren. Diese Methode fehlt jedoch in der mobilen eShopOnContainers-App, da sie nicht erforderlich ist.
Erstellen der NavigationService-Instanz
Die NavigationService
Klasse, die die INavigationService
Schnittstelle implementiert, wird als Singleton mit dem Container für die Autofac-Abhängigkeitseinfügung registriert, wie im folgenden Codebeispiel veranschaulicht:
builder.RegisterType<NavigationService>().As<INavigationService>().SingleInstance();
Die INavigationService
Schnittstelle wird im ViewModelBase
Klassenkonstruktor aufgelöst, wie im folgenden Codebeispiel veranschaulicht:
NavigationService = ViewModelLocator.Resolve<INavigationService>();
Dadurch wird ein Verweis auf das NavigationService
Objekt zurückgegeben, das im Autofac-Abhängigkeitseinfügungscontainer gespeichert ist, der von der Methode in der App
InitNavigation
Klasse erstellt wird. Weitere Informationen finden Sie unter "Navigieren beim Starten der App".
Die ViewModelBase
-Klasse speichert die NavigationService
-Instanz in einer NavigationService
-Eigenschaft vom Typ INavigationService
. Daher können alle Ansichtsmodellklassen, die von der ViewModelBase
Klasse abgeleitet werden, die NavigationService
Eigenschaft verwenden, um auf die von der INavigationService
Schnittstelle angegebenen Methoden zuzugreifen. Dadurch wird verhindert, dass das NavigationService
Objekt aus dem Autofac-Abhängigkeitseinfügungscontainer in jede Ansichtsmodellklasse eingefügt wird.
Behandeln von Navigationsanforderungen
Xamarin.Forms stellt die NavigationPage
Klasse bereit, die eine hierarchische Navigationsoberfläche implementiert, in der der Benutzer nach Bedarf durch Seiten, Vorwärts und Rückwärts navigieren kann. Weitere Informationen zur hierarchischen Navigation finden Sie unter : Hierarchische Navigation.
Anstatt die NavigationPage
Klasse direkt zu verwenden, umschließt die eShopOnContainers-App die NavigationPage
Klasse in der CustomNavigationView
Klasse, wie im folgenden Codebeispiel gezeigt:
public partial class CustomNavigationView : NavigationPage
{
public CustomNavigationView() : base()
{
InitializeComponent();
}
public CustomNavigationView(Page root) : base(root)
{
InitializeComponent();
}
}
Der Zweck dieses Umbruchs ist die Einfache Formatierung der NavigationPage
Instanz innerhalb der XAML-Datei für die Klasse.
Die Navigation erfolgt innerhalb von Ansichtsmodellklassen, indem sie eine der NavigateToAsync
Methoden aufrufen und den Ansichtsmodelltyp für die Seite angeben, zu der navigiert wird, wie im folgenden Codebeispiel gezeigt:
await NavigationService.NavigateToAsync<MainViewModel>();
Das folgende Codebeispiel zeigt die NavigateToAsync
von der NavigationService
Klasse bereitgestellten Methoden:
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);
}
Jede Methode ermöglicht jede Ansichtsmodellklasse, die von der ViewModelBase
Klasse abgeleitet wird, die hierarchische Navigation durch Aufrufen der InternalNavigateToAsync
Methode auszuführen. Darüber hinaus ermöglicht die zweite NavigateToAsync
Methode die Angabe von Navigationsdaten als Argument, das an das Ansichtsmodell übergeben wird, zu dem navigiert wird, wo sie normalerweise zum Initialisieren verwendet wird. Weitere Informationen finden Sie unter Übergeben von Parametern während der Navigation.
Die InternalNavigateToAsync
Methode führt die Navigationsanforderung aus und wird im folgenden Codebeispiel gezeigt:
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;
}
Die InternalNavigateToAsync
Methode führt die Navigation zu einem Ansichtsmodell aus, indem zuerst die CreatePage
Methode aufgerufen wird. Diese Methode sucht die Ansicht, die dem angegebenen Ansichtsmodelltyp entspricht, und erstellt und gibt eine Instanz dieses Ansichtstyps zurück. Das Auffinden der Ansicht, die dem Ansichtsmodelltyp entspricht, verwendet einen konventionsbasierten Ansatz. Dabei wird davon ausgegangen, dass:
- Ansichten befinden sich in derselben Assembly wie Ansichtsmodelltypen.
- Ansichten befinden sich in einem . Zeigt den untergeordneten Namespace an.
- Ansichtsmodelle befinden sich in einem . ViewModels untergeordneter Namespace.
- Ansichtsnamen entsprechen Ansichtsmodellnamen, wobei "Modell" entfernt wurde.
Wenn eine Ansicht instanziiert wird, wird sie dem entsprechenden Ansichtsmodell zugeordnet. Weitere Informationen dazu, wie dies geschieht, finden Sie unter "Automatisches Erstellen eines Ansichtsmodells mit einem Ansichtsmodelllocator".
Wenn es sich bei der erstellten Ansicht um eine LoginView
umschlossene Ansicht handelt, wird sie in eine neue Instanz der CustomNavigationView
Klasse eingeschlossen und der Application.Current.MainPage
Eigenschaft zugewiesen. Andernfalls wird die CustomNavigationView
Instanz abgerufen und vorausgesetzt, dass sie nicht NULL ist, wird die PushAsync
Methode aufgerufen, um die ansicht zu übertragen, die auf dem Navigationsstapel erstellt wird. Wenn die abgerufene CustomNavigationView
Instanz jedoch lautet null
, wird die erstellte Ansicht in eine neue Instanz der CustomNavigationView
Klasse umschlossen und der Application.Current.MainPage
Eigenschaft zugewiesen. Mit diesem Mechanismus wird sichergestellt, dass Seiten während der Navigation sowohl bei leerer Als auch bei der Datenzuführung dem Navigationsstapel korrekt hinzugefügt werden.
Tipp
Erwägen Sie das Zwischenspeichern von Seiten. Das Zwischenspeichern von Seiten führt zu einer Speicherauslastung für Ansichten, die derzeit nicht angezeigt werden. Ohne Seitenzwischenspeicherung bedeutet dies jedoch, dass die XAML-Analyse und -Konstruktion der Seite und des Ansichtsmodells jedes Mal auftreten, wenn eine neue Seite navigiert wird. Dies kann sich auf eine komplexe Seite auswirken. Für eine gut gestaltete Seite, die keine übermäßige Anzahl von Steuerelementen verwendet, sollte die Leistung ausreichen. Das Zwischenspeichern von Seiten kann jedoch hilfreich sein, wenn langsame Seitenladezeiten auftreten.
Nachdem die Ansicht erstellt und navigiert wurde, wird die InitializeAsync
Methode des zugeordneten Ansichtsmodells der Ansicht ausgeführt. Weitere Informationen finden Sie unter Übergeben von Parametern während der Navigation.
Navigieren beim Starten der App
Wenn die App gestartet wird, wird die InitNavigation
Methode in der App
Klasse aufgerufen. Im folgenden Codebeispiel wird diese Methode veranschaulicht:
private Task InitNavigation()
{
var navigationService = ViewModelLocator.Resolve<INavigationService>();
return navigationService.InitializeAsync();
}
Die Methode erstellt ein neues NavigationService
Objekt im Autofac-Abhängigkeitseinfügungscontainer und gibt einen Verweis darauf zurück, bevor sie die InitializeAsync
Methode aufrufen.
Hinweis
Wenn die INavigationService
Schnittstelle von der ViewModelBase
Klasse aufgelöst wird, gibt der Container einen Verweis auf das NavigationService
Objekt zurück, das beim Aufrufen der InitNavigation-Methode erstellt wurde.
Das folgende Codebeispiel zeigt die NavigationService
InitializeAsync
Methode:
public Task InitializeAsync()
{
if (string.IsNullOrEmpty(Settings.AuthAccessToken))
return NavigateToAsync<LoginViewModel>();
else
return NavigateToAsync<MainViewModel>();
}
Die MainView
Navigation erfolgt, wenn die App über ein zwischengespeichertes Zugriffstoken verfügt, das für die Authentifizierung verwendet wird. Andernfalls wird die LoginView
Navigation erfolgt.
Weitere Informationen zum Autofac-Abhängigkeitseinfügungscontainer finden Sie in der Einführung in Abhängigkeitseinfügung.
Übergeben von Parametern während der Navigation
Eine der NavigateToAsync
methoden, die von der INavigationService
Schnittstelle angegeben werden, ermöglicht die Angabe von Navigationsdaten als Argument, das an das Ansichtsmodell übergeben wird, zu dem navigiert wird, wo es in der Regel zum Initialisieren verwendet wird.
Beispielsweise enthält die ProfileViewModel
-Klasse einen OrderDetailCommand
, der ausgeführt wird, wenn der Benutzer eine Bestellung auf der ProfileView
-Seite auswählt. Dadurch wird wiederum die OrderDetailAsync
-Methode ausgeführt, wie im folgenden Codebeispiel gezeigt:
private async Task OrderDetailAsync(Order order)
{
await NavigationService.NavigateToAsync<OrderDetailViewModel>(order);
}
Mit dieser Methode wird die Navigation auf der OrderDetailViewModel
Seite aufgerufen, wobei eine Order
Instanz übergeben wird, die die Reihenfolge darstellt, die der Benutzer auf der ProfileView
Seite ausgewählt hat. Wenn die NavigationService
Klasse erstellt OrderDetailView
wird, wird die OrderDetailViewModel
Klasse instanziiert und der Ansicht BindingContext
zugewiesen. Nach dem OrderDetailView
Navigieren zur Methode wird die InternalNavigateToAsync
InitializeAsync
Methode des zugeordneten Ansichtsmodells der Ansicht ausgeführt.
Die InitializeAsync
Methode wird in der ViewModelBase
Klasse als Methode definiert, die überschrieben werden kann. Diese Methode gibt ein object
Argument an, das die Daten darstellt, die während eines Navigationsvorgangs an ein Ansichtsmodell übergeben werden sollen. Daher stellen Ansichtsmodellklassen, die Daten aus einem Navigationsvorgang empfangen möchten, eine eigene Implementierung der InitializeAsync
Methode bereit, um die erforderliche Initialisierung durchzuführen. Im folgenden Codebeispiel wird die InitializeAsync
-Methode aus der OrderDetailViewModel
-Klasse gezeigt:
public override async Task InitializeAsync(object navigationData)
{
if (navigationData is Order)
{
...
Order = await _ordersService.GetOrderAsync(
Convert.ToInt32(order.OrderNumber), authToken);
...
}
}
Diese Methode ruft die Order
Instanz ab, die während des Navigationsvorgangs an das Ansichtsmodell übergeben wurde, und verwendet sie, um die vollständigen Bestelldetails aus der OrderService
Instanz abzurufen.
Aufrufen der Navigation mithilfe von Verhalten
Die Navigation wird normalerweise von einer Ansicht aus durch eine Benutzerinteraktion ausgelöst. Beispielsweise führt die LoginView
Navigation nach erfolgreicher Authentifizierung aus. Das folgende Codebeispiel zeigt, wie die Navigation durch Verhalten aufgerufen wird:
<WebView ...>
<WebView.Behaviors>
<behaviors:EventToCommandBehavior
EventName="Navigating"
EventArgsConverter="{StaticResource WebNavigatingEventArgsConverter}"
Command="{Binding NavigateCommand}" />
</WebView.Behaviors>
</WebView>
Zur Laufzeit reagiert das EventToCommandBehavior
auf die Interaktion mit der WebView
. Wenn die WebView
Navigation zu einer Webseite erfolgt, wird das Navigating
Ereignis ausgelöst, das in der NavigateCommand
LoginViewModel
Datei ausgeführt wird. Die Ereignisargumente für das Ereignis werden standardmäßig an den Befehl übergeben. Diese Daten werden konvertiert, während sie von dem in der EventArgsConverter
-Eigenschaft angegebenen Konverter zwischen Quelle und Ziel übergeben werden, der die Url
aus den WebNavigatingEventArgs
zurückgibt. Wenn die Webseite ausgeführt wird, wird die NavigationCommand
URL der Webseite daher als Parameter an die registrierte Action
Übergeben.
Dadurch führt NavigationCommand
wiederum die NavigateAsync
-Methode aus, wie im folgenden Codebeispiel gezeigt:
private async Task NavigateAsync(string url)
{
...
await NavigationService.NavigateToAsync<MainViewModel>();
await NavigationService.RemoveLastFromBackStackAsync();
...
}
Diese Methode ruft die Navigation auf, MainViewModel
und die folgende Navigation entfernt die LoginView
Seite aus dem Navigationsstapel.
Bestätigen oder Abbrechen der Navigation
Eine App muss möglicherweise während eines Navigationsvorgangs mit dem Benutzer interagieren, damit der Benutzer die Navigation bestätigen oder abbrechen kann. Dies kann beispielsweise erforderlich dann sein, wenn der Benutzer versucht, zu navigieren, bevor er eine Dateneingabeseite vollständig abgeschlossen hat. Unter diesen Umständen sollte eine App eine Benachrichtigung bereitstellen, die dem Benutzer erlaubt, von der Seite zu navigieren oder den Navigationsvorgang abzubrechen, bevor er auftritt. Dies kann in einer Ansichtsmodellklasse mithilfe der Antwort einer Benachrichtigung erreicht werden, um zu steuern, ob die Navigation aufgerufen wird.
Zusammenfassung
Xamarin.Forms enthält Unterstützung für die Seitennavigation, die in der Regel aus der Interaktion des Benutzers mit der Benutzeroberfläche oder aus der App selbst resultiert, aufgrund von internen logikgesteuerten Zustandsänderungen. Allerdings kann die Navigation in Anwendungen, die das MVVM-Muster verwenden, kompliziert zu implementieren sein.
In diesem Kapitel wurde eine NavigationService
Klasse vorgestellt, die zum Ausführen der Ansichtsmodell-ersten Navigation aus Ansichtsmodellen verwendet wird. Das Platzieren von Navigationslogik in Ansichtsmodellklassen bedeutet, dass die Logik über automatisierte Tests ausgeübt werden kann. Darüber hinaus kann das Ansichtsmodell logik implementieren, um die Navigation zu steuern, um sicherzustellen, dass bestimmte Geschäftsregeln erzwungen werden.