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.

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 NavigationPageCustomNavigationView 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 è , LoginViewviene 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.

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 metodo NavigationServiceInitializeAsync:

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 , OrderDetailViewviene creata un'istanza della OrderDetailViewModel classe e assegnata all'oggetto della BindingContextvisualizzazione. 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 MainViewModele, 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.