Esercizio - Usare lo schema Factory per accedere a una dipendenza dal codice condiviso

Completato

In questo esercizio si partirà da un progetto Xamarin.Forms esistente e si estrarrà il codice condivisibile nella libreria .NET Standard del progetto. Si userà lo schema Factory per creare istanze delle dipendenze. Questo consente di isolare il codice specifico della piattaforma per leggere e scrivere il file di archiviazione che visualizza le citazioni.

Esplorare l'applicazione

Aprire la soluzione iniziale dalla cartella exercise1>start nella copia del repository dell'esercizio clonato o scaricato.

Nota

Se si prevede di eseguire il debug delle app Xamarin in Android da Windows, è consigliabile clonare o scaricare il contenuto dell'esercizio in un percorso di cartella breve, come ad esempio C:\dev\, per evitare che il percorso dei file generati dalla compilazione superi la lunghezza massima consentita.

Esaminiamo ora il funzionamento dell'applicazione Great Quotes.

  1. Eseguire l'applicazione in qualsiasi piattaforma. L'applicazione mostra un elenco di citazioni.
  2. Selezionare una voce dall'elenco per visualizzare la citazione completa in una nuova pagina dei dettagli.
  3. Modificare la citazione in una pagina di modifica modale selezionando l'icona di modifica sulla barra degli strumenti. Al termine delle modifiche, chiudere la pagina modale. Le informazioni aggiornate compariranno nella pagina dei dettagli.
  4. Chiudere l'applicazione e riaprirla. Tutte le modifiche dovrebbero essere disponibili.

Ora che si è appreso il funzionamento dell'applicazione, esaminare il codice.

  1. La soluzione è una tipica applicazione Xamarin.Forms per Android e iOS. Condivide il codice usando una libreria .NET Standard.
  2. Le citazioni sono salvate in un file XML archiviato in una posizione specifica della piattaforma.
  3. La classe QuoteLoader nel progetto principale di ciascuna piattaforma è responsabile del caricamento e del salvataggio delle citazioni.
  4. Nel codice condiviso l'applicazione usa tre oggetti ContentPage: QuoteListPage, QuoteDetailPage e EditQuotePage.
  5. La classe MainViewModel nel codice condiviso è responsabile del supporto degli oggetti ContentPage. MainViewModel è impostato come BindingContext per ogni pagina.
  6. QuoteDetailPage usa un metodo SaveQuotes sulla classe MainViewModel.
  7. MainViewModel richiama un delegato action che viene passato al costruttore di MainViewModel. Il delegato viene richiamato nel metodo SaveQuotes.
  8. MainViewModel ha una dipendenza da QuoteLoader. Si basa sul delegato passato al costruttore che punta al metodo Save dell'istanza QuoteLoader.
  9. Poiché l'istanza della classe QuoteLoader può essere creata solo nel codice specifico della piattaforma, le istanze sia di QuoteLoader che di MainViewModel vengono create rispettivamente nella classe Android MainActivity e nella classe iOS UIApplicationDelegate.
  10. L'istanza di MainViewModel viene quindi passata come parametro del costruttore alla classe Xamarin.Forms App.
  11. La classe App imposta un static MainViewModel GreatQuotesViewModel dal parametro del costruttore di MainViewModel. In ogni ContentPage viene fatto riferimento a GreatQuotesViewModel come l'elemento BindingContext descritto in precedenza.

Esaminando la struttura dell'applicazione, si può notare che è possibile migliorare sia la struttura che la condivisione del codice. Nelle sezioni seguenti si modificherà la struttura dell'applicazione.

Aggiungere un'astrazione

Il primo passaggio consiste nel creare un'astrazione per il codice QuoteLoader in uso. Se si prova ad aggiungere direttamente una delle classi di piattaforma alla libreria .NET Standard, la compilazione avrà esito negativo. Le classi hanno dipendenze da specifiche API di file che non sono disponibili nel profilo condiviso. Le piattaforme presentano lievi differenze rispetto alle modalità di gestione dei file locali e alle posizioni di archiviazione dei file nel dispositivo. Per ogni piattaforma occorrerà un approccio unico per caricare le citazioni.

  1. Aprire il file QuoteLoader.cs nel progetto specifico della piattaforma (iOS o Android). In ogni progetto questo file è disponibile nella cartella Data. Esaminare i metodi e l'implementazione:

    • Load viene usato per caricare le citazioni da un file.
    • Save viene usato per salvare una raccolta di citazioni esistente nello stesso file.
  2. Creare una nuova interfaccia per rappresentare il file QuoteLoader indipendente dalla piattaforma. In questo esempio si userà un'interfaccia, ma è anche possibile usare una classe di base astratta. Inserire l'interfaccia nella libreria .NET Standard, perché verrà condivisa tra tutti i progetti.

    • Denominare l'interfaccia IQuoteLoader.
    • Definire i metodi Load e Save così come sono definiti nel progetto della piattaforma.
    using GreatQuotes.ViewModels;
    ...
    public interface IQuoteLoader
    {
        IEnumerable<GreatQuoteViewModel> Load();
        void Save(IEnumerable<GreatQuoteViewModel> quotes);
    }
    
  3. Fare in modo che l'implementazione di QuoteLoader specifica della piattaforma implementi l'interfaccia IQuoteLoader. Non è necessario apportare modifiche al codice, in quanto le firme per i metodi Load e Save dovrebbero essere già presenti nella classe esistente.

    public class QuoteLoader : IQuoteLoader
    
  4. Compilare ed eseguire l'applicazione e verificare che funzioni ancora correttamente.

Aggiungere una factory

Si userà ora lo schema Factory per creare l'implementazione della proprietà dell'oggetto IQuoteLoader che verrà usata nel codice della libreria .NET Standard.

  1. Nella libreria .NET Standard creare una nuova classe statica denominata QuoteLoaderFactory. Questa classe statica rappresenterà la factory che si userà per creare l'implementazione specifica della piattaforma di un IQuoteLoader.
  2. Aggiungere una singola proprietà statica denominata Create, di tipo Func<IQuoteLoader>. Questa proprietà verrà impostata in seguito per creare un nuovo IQuoteLoader.

Il codice per la factory dovrebbe essere simile al seguente:

public static class QuoteLoaderFactory
{
    // This must be assigned to a method which creates a new quote loader.
    public static Func<IQuoteLoader> Create { get; set; }
}

Aggiungere una classe QuoteManager

In entrambi i progetti specifici della piattaforma è presente codice quasi identico per caricare e salvare le citazioni. Attualmente, i progetti specifici della piattaforma usano le classi QuoteLoader in modo diretto, tuttavia si vuole eseguire il push di questo codice comune nel codice condiviso (la libreria .NET Standard). Spostare la gestione della raccolta GreatQuote nel codice condiviso, in modo che tutte le piattaforme possano individuare i dati allo stesso modo.

  1. Creare una nuova classe QuoteManager nella libreria .NET Standard.

  2. Aggiungere un costruttore privato.

  3. Creare una proprietà statica Instance per esporre una singola copia di QuoteManager. È possibile usare il tipo predefinito Lazy<T> per implementare questo modello. In alternativa, creare l'oggetto la prima volta che si accede alla proprietà. L'obiettivo è disporre di una proprietà statica pubblica per ottenere una singola istanza nota dell'oggetto.

    public class QuoteManager
    {
       static readonly Lazy<QuoteManager> instance = new Lazy<QuoteManager>(() => new QuoteManager());
    
       public static QuoteManager Instance { get => instance.Value; }
    
       private QuoteManager()
       {
       }
    }
    
  4. Ottenere un oggetto IQuoteLoader usando il delegato QuoteLoaderFactory.Create. Assegnarlo a un campo della classe. Ricordare che si ha un delegato assegnato a una proprietà e che è possibile chiamarlo come un metodo.

    IQuoteLoader loader;
    ...
    private QuoteManager()
    {
       loader = QuoteLoaderFactory.Create();
    }
    
  5. Aggiungere una proprietà IList<GreatQuoteViewModel> pubblica denominata Quotes per esporre le citazioni caricate.

  6. Nel costruttore assegnare la proprietà a una nuova istanza di ObservableCollection<GreatQuoteViewModel>, usando il metodo Load dal campo IQuoteLoader. Ricordare di includere using System.Collections.ObjectModel per fare riferimento al tipo ObservableCollection.

  7. Infine, aggiungere un nuovo metodo di istanza pubblica denominato Save. Questo metodo di istanza pubblico usa il metodo loader.Save() per salvare la raccolta di citazioni.

La versione finale di QuoteManager dovrebbe essere simile al codice seguente:

using System.Collections.ObjectModel;
using GreatQuotes.ViewModels;
...
public class QuoteManager
{
    static readonly Lazy<QuoteManager> instance = new Lazy<QuoteManager>(() => new QuoteManager());

    private IQuoteLoader loader;

    public static QuoteManager Instance { get => instance.Value; }

    public IList<GreatQuoteViewModel> Quotes { get; set; }

    private QuoteManager()
    {
        loader = QuoteLoaderFactory.Create();
        Quotes = new ObservableCollection<GreatQuoteViewModel>(loader.Load());
    }

    public void Save()
    {
        loader.Save(Quotes);
    }
}

Usare la classe QuoteManager

Il passaggio successivo consiste nell'usare la nuova classe condivisa QuoteManager e assegnare la factory. QuoteManager sarà l'oggetto responsabile per la gestione di tutti gli elementi correlati alle citazioni. Tuttavia, si vogliono comunque salvare le citazioni da MainViewModel. Ecco perché QuoteManager è una dipendenza di MainViewModel.

  1. Aprire MainViewModel.
  2. Eliminare il campo saveQuotes e rimuovere il parametro Action dal costruttore della classe.
  3. Rimuovere il codice che ha richiamato Action nel metodo SaveQuotes.
  4. Aggiungere alla classe un campo di sola lettura di tipo QuoteManager. Assegnare all'elemento il nome quoteManager.
  5. All'interno del costruttore della classe usare la proprietà QuoteManager.Instance per assegnare un valore al campo quoteManager.
  6. Nel costruttore della classe usare quoteManager.Quotes per assegnare un valore alla proprietà Quotes. Perché l'assegnazione funzioni, sarà necessario convertire la raccolta in un ObservableCollection<GreatQuoteViewModel>.
  7. Per salvare tutte le citazioni, usare il metodo quoteManger.Save() nel metodo SaveQuotes.

Il codice della classe MainViewModel dovrebbe essere simile al seguente:

public class MainViewModel : BaseViewModel
{
    private readonly QuoteManager quoteManager;

    public MainViewModel()
    {
        quoteManager = QuoteManager.Instance;
        Quotes = quoteManager.Quotes as ObservableCollection<GreatQuoteViewModel>;
    }

    public ObservableCollection<GreatQuoteViewModel> Quotes { get; set; }

    public GreatQuoteViewModel ItemSelected { get; set; }

    public void SaveQuotes()
    {
        quoteManager.Save();
    }
}

Aggiornare il codice specifico della piattaforma

L'ultimo passaggio consiste nel pulire le classi specifiche della piattaforma e la classe dell'applicazione Xamarin.Forms.

  1. Aprire la classe a livello di applicazione che caricava le citazioni. Questa classe è AppDelegate.cs per iOS o MainActivity.cs per Android.

  2. Rimuovere dal codice specifico della piattaforma la proprietà GreateQuotesViewModel e il codice che crea l'istanza di QuoteLoader e GreateQuotesViewModel. Rimuovere la riga di codice simile alla seguente:

    public MainViewModel GreateQuotesViewModel { get; private set; }
    

    Rimuovere anche queste righe di codice:

    var quoteLoader = new QuoteLoader();
    GreateQuotesViewModel = new MainViewModel(() => quoteLoader.Save(GreateQuotesViewModel.Quotes))
    {
        Quotes = new ObservableCollection<GreatQuoteViewModel>(quoteLoader.Load())
    };
    

    Tenere presente che in ogni progetto contiene questo codice in un file diverso:

    • iOS: AppDelegate.cs
    • Android: MainActivity.cs
  3. Assegnare la proprietà QuoteLoaderFactory.Create della classe factory a un metodo che crei una nuova classe QuoteLoader specifica della piattaforma, ad esempio un elemento che implementi IQuoteLoader.

    • È possibile usare qualsiasi stile di assegnazione del delegato, come lambda, metodo anonimo o un normale metodo C# definito nella classe.
    • Nel progetto iOS aggiungere questo codice all'override FinishedLaunching.
    • Nel progetto Android aggiungere questo codice all'override OnCreate.

    Di seguito è riportato un esempio che usa un delegato:

    QuoteLoaderFactory.Create = () => new QuoteLoader();
    
  4. Pulire la creazione dell'istanza della classe dell'applicazione Xamarin.Forms. Non è più necessario creare un'istanza di MainViewModel.

    Il codice ora dovrebbe essere simile al seguente:

    LoadApplication(new App());
    
  5. Aggiornare il costruttore della classe App e rimuovere il parametro MainViewModel.

  6. Impostare la proprietà GreatQuotesViewModel su una nuova istanza di MainViewModel.

    Il codice finale del costruttore dovrebbe essere simile al seguente:

    public App()
    {
        InitializeComponent();
        GreatQuotesViewModel = new MainViewModel();
        MainPage = new NavigationPage (new QuoteListPage());
    }
    
  7. Aggiornare il metodo OnSleep in modo da usare direttamente il metodo di salvataggio QuoteManager. La classe dell'applicazione non dovrebbe avere una dipendenza da MainViewModel.

    Il metodo dovrebbe essere simile al seguente:

    protected override void OnSleep()
    {
        QuoteManager.Instance.Save();
    }
    

Eseguire l'applicazione

Compilare ed eseguire l'applicazione per verificare che carichi e salvi correttamente le citazioni. Provare ad aggiungere un punto di interruzione nella classe QuoteManager dove ottiene il QuoteLoader. Tracciare il codice per vederlo passare dal codice multipiattaforma (condiviso) al progetto specifico della piattaforma.

È possibile visualizzare la soluzione completata nella cartella exercise1>final della copia del repository dell'esercizio clonato o scaricato.