Spostamento app aziendale
Nota
Questo eBook è stato pubblicato nella primavera del 2017 e non è stato aggiornato da allora. C'è molto nel libro che rimane prezioso, ma alcuni dei materiali sono obsoleti.
Xamarin.Forms include il supporto per lo spostamento delle pagine, che in genere deriva dall'interazione dell'utente con l'interfaccia utente o dall'app stessa in seguito a modifiche dello stato basate sulla logica interne. Tuttavia, la navigazione può essere difficile da implementare nelle app che usano il modello Model-View-ViewModel (MVVM), perché è necessario affrontare i problemi seguenti:
- Come identificare la visualizzazione a cui passare, usando un approccio che non introduce un accoppiamento stretto e dipendenze tra le visualizzazioni.
- Come coordinare il processo in base al quale viene creata un'istanza e inizializzata la visualizzazione da esplorare. Quando si usa MVVM, è necessario creare un'istanza del modello di visualizzazione e visualizzazione e associarsi tra loro tramite il contesto di associazione della visualizzazione. Quando un'app usa un contenitore di inserimento delle dipendenze, la creazione di istanze di visualizzazioni e modelli di visualizzazione potrebbe richiedere un meccanismo di costruzione specifico.
- Se eseguire lo spostamento in primo luogo o visualizzare la navigazione model-first. Con la navigazione view-first, la pagina da esplorare si riferisce al nome del tipo di vista. Durante la navigazione, viene creata un'istanza della visualizzazione specificata, insieme al modello di visualizzazione corrispondente e ad altri servizi dipendenti. Un approccio alternativo consiste nell'usare lo spostamento modello di visualizzazione, in cui la pagina da passare a fa riferimento al nome del tipo di modello di visualizzazione.
- Come separare in modo pulito il comportamento di spostamento dell'app tra le visualizzazioni e i modelli di visualizzazione. Il modello MVVM offre una separazione tra l'interfaccia utente dell'app e la relativa presentazione e logica di business. Tuttavia, il comportamento di spostamento di un'app si estende spesso sulle parti dell'interfaccia utente e delle presentazioni dell'app. Spesso l'utente avvia la navigazione da una vista e questa viene sostituita come risultato della navigazione. Tuttavia, lo spostamento potrebbe spesso dover essere avviato o coordinato anche dall'interno del modello di visualizzazione.
- Come passare i parametri durante la navigazione per scopi di inizializzazione. Ad esempio, se l'utente naviga in una vista per aggiornare i dettagli dell'ordine, i dati dell'ordine dovranno essere passati alla vista, in modo che possa visualizzare i dati corretti.
- Come coordinare la navigazione per garantire che determinate regole business siano rispettate. Ad esempio, agli utenti potrebbe essere richiesta una conferma prima di uscire da una vista, in modo da correggere eventuali dati non validi, oppure una richiesta a inviare o scartare le modifiche apportate ai dati all'interno della vista.
Questo capitolo risolve queste sfide presentando una NavigationService
classe usata per eseguire lo spostamento di pagina model-first.
Nota
L'oggetto NavigationService
usato dall'app è progettato solo per eseguire lo spostamento gerarchico tra le istanze di ContentPage. L'uso del servizio per spostarsi tra altri tipi di pagina potrebbe comportare un comportamento imprevisto.
Spostamento tra pagine
La logica di spostamento può risiedere nel code-behind di una vista o in un modello di visualizzazione associato a dati. Anche se l'inserimento della logica di spostamento in una visualizzazione potrebbe essere l'approccio più semplice, non è facilmente testabile tramite unit test. L'inserimento della logica di spostamento nelle classi del modello di visualizzazione significa che la logica può essere esercitata tramite unit test. Inoltre, il modello di visualizzazione può implementare la logica per controllare la navigazione per garantire che vengano applicate determinate regole business. Ad esempio, un'app potrebbe non consentire all'utente di spostarsi da una pagina senza prima assicurarsi che i dati immessi siano validi.
Una NavigationService
classe viene in genere richiamata dai modelli di visualizzazione per promuovere la testabilità. Tuttavia, per passare alle visualizzazioni dai modelli di visualizzazione è necessario che i modelli di visualizzazione facciano riferimento alle visualizzazioni e in particolare le visualizzazioni a cui il modello di visualizzazione attivo non sia associato, che non è consigliato. Di conseguenza, l'oggetto NavigationService
presentato qui specifica il tipo di modello di visualizzazione come destinazione a cui passare.
L'app per dispositivi mobili eShopOnContainers usa la NavigationService
classe per fornire la navigazione model-first di visualizzazione. Questa classe implementa l'interfaccia INavigationService
, illustrata nell'esempio di codice seguente:
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();
}
Questa interfaccia specifica che una classe di implementazione deve fornire i metodi seguenti:
metodo | Scopo |
---|---|
InitializeAsync |
Esegue la navigazione su una delle due pagine all'avvio dell'app. |
NavigateToAsync |
Esegue lo spostamento gerarchico in una pagina specificata. |
NavigateToAsync(parameter) |
Esegue lo spostamento gerarchico in una pagina specificata, passando un parametro. |
RemoveLastFromBackStackAsync |
Rimuove la pagina precedente dallo stack di navigazione. |
RemoveBackStackAsync |
Rimuove tutte le pagine precedenti dallo stack di navigazione. |
Inoltre, l'interfaccia INavigationService
specifica che una classe di implementazione deve fornire una PreviousPageViewModel
proprietà . Questa proprietà restituisce il tipo di modello di visualizzazione associato alla pagina precedente nello stack di navigazione.
Nota
Un'interfaccia INavigationService
di solito specifica anche un metodo GoBackAsync
, che viene usato per tornare a livello di programmazione alla pagina precedente nello stack di navigazione. Tuttavia, questo metodo non è presente nell'app per dispositivi mobili eShopOnContainers perché non è obbligatorio.
Creazione dell'istanza navigationService
La NavigationService
classe , che implementa l'interfaccia INavigationService
, viene registrata come singleton con il contenitore di inserimento delle dipendenze Autofac, come illustrato nell'esempio di codice seguente:
builder.RegisterType<NavigationService>().As<INavigationService>().SingleInstance();
L'interfaccia INavigationService
viene risolta nel costruttore della ViewModelBase
classe, come illustrato nell'esempio di codice seguente:
NavigationService = ViewModelLocator.Resolve<INavigationService>();
Viene restituito un riferimento all'oggetto NavigationService
archiviato nel contenitore di inserimento delle dipendenze Autofac, creato dal InitNavigation
metodo nella App
classe . Per altre informazioni, vedere Navigazione all'avvio dell'app.
La classe ViewModelBase
archivia l'istanza di NavigationService
in una proprietà NavigationService
di tipo INavigationService
. Pertanto, tutte le classi del modello di visualizzazione, che derivano dalla ViewModelBase
classe , possono utilizzare la NavigationService
proprietà per accedere ai metodi specificati dall'interfaccia INavigationService
. In questo modo si evita il sovraccarico dell'inserimento dell'oggetto NavigationService
dal contenitore di inserimento delle dipendenze Autofac in ogni classe del modello di visualizzazione.
Gestione delle richieste di spostamento
Xamarin.Forms fornisce la NavigationPage
classe , che implementa un'esperienza di navigazione gerarchica in cui l'utente è in grado di spostarsi tra pagine, avanti e indietro, come desiderato. Per altre informazioni sulla navigazione gerarchica, vedere Navigazione gerarchica.
Anziché usare direttamente la NavigationPage
classe , l'app eShopOnContainers esegue il wrapping della NavigationPage
CustomNavigationView
classe nella classe , come illustrato nell'esempio di codice seguente:
public partial class CustomNavigationView : NavigationPage
{
public CustomNavigationView() : base()
{
InitializeComponent();
}
public CustomNavigationView(Page root) : base(root)
{
InitializeComponent();
}
}
Lo scopo di questo wrapping è semplificare lo stile dell'istanza NavigationPage
all'interno del file XAML per la classe .
Lo spostamento viene eseguito all'interno delle classi del modello di visualizzazione richiamando uno dei NavigateToAsync
metodi , specificando il tipo di modello di visualizzazione per la pagina a cui si passa, come illustrato nell'esempio di codice seguente:
await NavigationService.NavigateToAsync<MainViewModel>();
L'esempio di codice seguente illustra i NavigateToAsync
metodi forniti dalla 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);
}
Ogni metodo consente a qualsiasi classe del modello di visualizzazione che deriva dalla ViewModelBase
classe di eseguire lo spostamento gerarchico richiamando il InternalNavigateToAsync
metodo . Inoltre, il secondo NavigateToAsync
metodo consente di specificare i dati di navigazione come argomento passato al modello di visualizzazione a cui si passa, in cui viene in genere usato per eseguire l'inizializzazione. Per altre informazioni, vedere Passaggio di parametri durante la navigazione.
Il InternalNavigateToAsync
metodo esegue la richiesta di navigazione ed è illustrato nell'esempio di codice seguente:
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;
}
Il metodo esegue lo InternalNavigateToAsync
spostamento a un modello di visualizzazione chiamando prima il CreatePage
metodo . Questo metodo individua la vista corrispondente al tipo di modello di visualizzazione specificato e crea e restituisce un'istanza di questo tipo di visualizzazione. L'individuazione della vista corrispondente al tipo di modello di visualizzazione usa un approccio basato su convenzioni, che presuppone che:
- Le viste si trovano nello stesso assembly dei tipi di modello di visualizzazione.
- Le visualizzazioni si trovano in un oggetto . Visualizza lo spazio dei nomi figlio.
- I modelli di visualizzazione si trovano in un oggetto . Spazio dei nomi figlio ViewModels.
- I nomi delle visualizzazioni corrispondono ai nomi dei modelli di visualizzazione, con "Modello" rimosso.
Quando viene creata un'istanza di una visualizzazione, è associata al modello di visualizzazione corrispondente. Per altre informazioni su come si verifica questa situazione, vedere Creazione automatica di un modello di visualizzazione con un localizzatore di modelli di visualizzazione.
Se la visualizzazione creata è , LoginView
viene eseguito il wrapping all'interno di una nuova istanza della CustomNavigationView
classe e assegnata alla Application.Current.MainPage
proprietà . In caso contrario, l'istanza CustomNavigationView
viene recuperata e, purché non sia Null, viene richiamato il metodo per eseguire il PushAsync
push della visualizzazione creata nello stack di navigazione. Tuttavia, se l'istanza recuperata CustomNavigationView
è null
, la visualizzazione creata viene sottoposta a wrapping all'interno di una nuova istanza della CustomNavigationView
classe e assegnata alla Application.Current.MainPage
proprietà . Questo meccanismo garantisce che durante lo spostamento le pagine vengano aggiunte correttamente allo stack di navigazione sia quando sono vuote che quando contengono dati.
Suggerimento
Prendere in considerazione la memorizzazione nella cache delle pagine. La memorizzazione nella cache delle pagine comporta un consumo di memoria per le visualizzazioni che non sono attualmente visualizzate. Tuttavia, senza memorizzazione nella cache delle pagine significa che l'analisi e la costruzione xaml della pagina e il relativo modello di visualizzazione si verificheranno ogni volta che viene spostata una nuova pagina, che può avere un impatto sulle prestazioni per una pagina complessa. Per una pagina ben progettata che non usa un numero eccessivo di controlli, le prestazioni devono essere sufficienti. Tuttavia, la memorizzazione nella cache delle pagine potrebbe risultare utile se vengono rilevati tempi di caricamento delle pagine lenti.
Dopo aver creato e spostato la vista, viene eseguito il InitializeAsync
metodo del modello di visualizzazione associato alla visualizzazione. Per altre informazioni, vedere Passaggio di parametri durante la navigazione.
Navigazione all'avvio dell'app
Quando l'app viene avviata, viene richiamato il InitNavigation
metodo nella App
classe . L'esempio di codice seguente illustra il metodo:
private Task InitNavigation()
{
var navigationService = ViewModelLocator.Resolve<INavigationService>();
return navigationService.InitializeAsync();
}
Il metodo crea un nuovo NavigationService
oggetto nel contenitore di inserimento delle dipendenze Autofac e restituisce un riferimento, prima di richiamare il InitializeAsync
relativo metodo.
Nota
Quando l'interfaccia INavigationService
viene risolta dalla ViewModelBase
classe , il contenitore restituisce un riferimento all'oggetto NavigationService
creato quando viene richiamato il metodo InitNavigation.
L'esempio di codice seguente illustra il NavigationService
InitializeAsync
metodo :
public Task InitializeAsync()
{
if (string.IsNullOrEmpty(Settings.AuthAccessToken))
return NavigateToAsync<LoginViewModel>();
else
return NavigateToAsync<MainViewModel>();
}
Si MainView
passa a se l'app dispone di un token di accesso memorizzato nella cache, che viene usato per l'autenticazione. In caso contrario, l'oggetto LoginView
viene spostato su .
Per altre informazioni sul contenitore di inserimento delle dipendenze Autofac, vedere Introduzione all'inserimento delle dipendenze.
Passaggio di parametri durante la navigazione
Uno dei NavigateToAsync
metodi specificati dall'interfaccia INavigationService
consente di specificare i dati di navigazione come argomento passato al modello di visualizzazione a cui si passa, in cui viene in genere usato per eseguire l'inizializzazione.
Ad esempio, la classe ProfileViewModel
contiene un OrderDetailCommand
eseguito quando l'utente seleziona un ordine nella pagina ProfileView
. A sua volta, esegue il metodo OrderDetailAsync
, illustrato nell'esempio di codice seguente:
private async Task OrderDetailAsync(Order order)
{
await NavigationService.NavigateToAsync<OrderDetailViewModel>(order);
}
Questo metodo richiama lo spostamento all'oggetto OrderDetailViewModel
, passando un'istanza Order
che rappresenta l'ordine selezionato dall'utente nella ProfileView
pagina. Quando la NavigationService
classe crea , OrderDetailView
viene creata un'istanza della OrderDetailViewModel
classe e assegnata all'oggetto della BindingContext
visualizzazione. Dopo aver eseguito il passaggio a OrderDetailView
, il InternalNavigateToAsync
metodo esegue il InitializeAsync
metodo del modello di visualizzazione associato alla visualizzazione.
Il InitializeAsync
metodo viene definito nella ViewModelBase
classe come metodo che può essere sottoposto a override. Questo metodo specifica un object
argomento che rappresenta i dati da passare a un modello di visualizzazione durante un'operazione di navigazione. Pertanto, visualizzare le classi del modello che vogliono ricevere dati da un'operazione di navigazione forniscono la propria implementazione del InitializeAsync
metodo per eseguire l'inizializzazione richiesta. Nell'esempio di codice seguente viene illustrato il metodo InitializeAsync
della classe OrderDetailViewModel
:
public override async Task InitializeAsync(object navigationData)
{
if (navigationData is Order)
{
...
Order = await _ordersService.GetOrderAsync(
Convert.ToInt32(order.OrderNumber), authToken);
...
}
}
Questo metodo recupera l'istanza Order
passata al modello di visualizzazione durante l'operazione di spostamento e la usa per recuperare i dettagli completi dell'ordine dall'istanza OrderService
.
Richiamo dello spostamento tramite comportamenti
La navigazione viene in genere attivata da una vista tramite un'interazione utente. Ad esempio, LoginView
esegue la navigazione dopo un'autenticazione riuscita. L'esempio di codice seguente mostra come la navigazione viene chiamata da un comportamento:
<WebView ...>
<WebView.Behaviors>
<behaviors:EventToCommandBehavior
EventName="Navigating"
EventArgsConverter="{StaticResource WebNavigatingEventArgsConverter}"
Command="{Binding NavigateCommand}" />
</WebView.Behaviors>
</WebView>
In fase di esecuzione, EventToCommandBehavior
risponderà all'interazione con WebView
. WebView
Quando si passa a una pagina Web, verrà generato l'evento Navigating
, che eseguirà NavigateCommand
in LoginViewModel
. Per impostazione predefinita, gli argomenti dell'evento vengono passati al comando. Questi dati vengono convertiti durante il passaggio tra l'origine e la destinazione dal convertitore specificato nella proprietà EventArgsConverter
, che restituisce Url
da WebNavigatingEventArgs
. Pertanto, quando NavigationCommand
viene eseguito , l'URL della pagina Web viene passato come parametro all'oggetto registrato Action
.
A sua volta, NavigationCommand
esegue il metodo NavigateAsync
, illustrato nell'esempio di codice seguente:
private async Task NavigateAsync(string url)
{
...
await NavigationService.NavigateToAsync<MainViewModel>();
await NavigationService.RemoveLastFromBackStackAsync();
...
}
Questo metodo richiama lo spostamento all'oggetto MainViewModel
e, dopo la navigazione, rimuove la LoginView
pagina dallo stack di spostamento.
Conferma o annullamento della navigazione
Un'app potrebbe avere bisogno di interagire con l'utente durante un'operazione di navigazione, in modo che l'utente possa confermare o annullare la navigazione. Potrebbe essere necessario, ad esempio, quando l'utente tenta di spostarsi prima di aver completato una pagina di inserimento dati. In questo caso, un'app dovrebbe fornire una notifica che consenta all'utente di uscire dalla pagina o di annullare l'operazione di navigazione prima che avvenga. Questa operazione può essere ottenuta in una classe del modello di visualizzazione usando la risposta di una notifica per controllare se la navigazione viene richiamata o meno.
Riepilogo
Xamarin.Forms include il supporto per lo spostamento delle pagine, che in genere deriva dall'interazione dell'utente con l'interfaccia utente o dall'app stessa, in seguito a modifiche dello stato interno basate sulla logica. Tuttavia, la navigazione può essere complessa da implementare nelle applicazioni che usano il modello MVVM.
In questo capitolo è stata presentata una NavigationService
classe usata per eseguire lo spostamento modello-primo visualizzazione dai modelli di visualizzazione. L'inserimento della logica di spostamento nelle classi del modello di visualizzazione significa che la logica può essere esercitata tramite test automatizzati. Inoltre, il modello di visualizzazione può implementare la logica per controllare la navigazione per garantire che vengano applicate determinate regole business.