Nello sviluppo di un’app, è fondamentale strutturare opportunamente il codice per distinguere in modo netto i vari strati ed il relativo comportamento, specie con applicazioni dotate di interfaccia grafica. In tal caso, è fondamentale che l’interfaccia (o UI) sia ben separata dalla logica di business, affinché una modifica nella logica o nel modello di dominio non si rifletta sull’interfaccia.
A tal proposito, un pattern architetturale noto è Model View Controller (MVC), che separa il modello dall’interfaccia attraverso l’impiego di un controller. Da questo pattern sono stati poi derivati ulteriori "dialetti", tra cui Model-View-ViewModel (MVVM), oggetto di questa lezione.
MVVM è un pattern definito da Microsoft per lo sviluppo di applicazioni WPF, Windows Phone e Silverlight basate proprio sullo XAML, ed è stato quindi ripreso da Xamarin proprio per i suoi molteplici benefici. Tra questi, citiamo:
- la possibilità di creare test per il ViewModel e il Model senza usare la View;
- la possibilità di ridisegnare la UI dell’app senza dover apportare cambiamenti agli altri due layer;
- il ViewModel svolge la funzione di adapter per le classi del Model, per evitare di effettuare cambiamenti importanti al codice del Model.
MVVM
Come il nome stesso lascia intendere, MVVM suddivide l’applicazione in tre livelli (o layer):
Model | è il punto di accesso ai dati ed è costituito da un insieme di classi che modellano la realtà di interesse e permettono di gestire i dati impiegati nell’app. In Xamarin, è tipicamente una pagina dell’app |
View | è lo strato di presentazione dell’app, ossia la sua interfaccia grafica, e permette all’utente di interagire con i dati |
ViewModel | connette la View e il Model e permette di gestire i dati provenienti dal Model per mostrarli nella View una volta elaborati, e viceversa |
La modalità di comunicazione tra questi strati si può, quindi, riassumere secondo lo schema della figura seguente.
L’utente interagisce con la UI, che raccoglie gli input ricevuti e li inoltra al ViewModel. Quest’ultimo, sulla base dell’input ricevuto, effettua un cambio di stato accedendo, quando necessario, al Model. Inoltre, il ViewModel aggiorna il proprio stato affinché si rifletta sulla View. In questo modo, il ViewModel non solo disaccoppia View e Model ma mantiene anche nel suo stato corrente sia le informazioni del Model che della View.
Un aspetto fondamentale per poter applicare correttamente MVVM è che la classe ViewModel non abbia alcun riferimento a Xamarin.Forms e, quindi, ai suoi elementi tipici della View. Nonostante ciò, molteplici esempi della documentazione ufficiale violano questa regola a causa della bassa complessità delle app di esempio proposte. In questa lezione, rispetteremo questa esigenza ma, nonostante ciò, non avremo un Model a disposizione a cui legare i dati ricevuti attraverso la view.
Implementazione
Scopo di questa esercitazione è la creazione di una form composta da tre campi (Name, Nickname, e-mail) inseriti dall’utente e automaticamente mostrati a quest’ultimo in tempo reale.
Creiamo una nuova Forms Xaml Page chiamandola MVVMDataBinding. Lavoriamo ora sul codice XMAL inserendo tutti gli elementi di interesse rappresentati nel nostro schema, effettuando l’opportunamente il binding tra le risorse come segue in questo esempio.
<Label HorizontalOptions="Start" Text="Name"/>
<Entry Text="{Binding Name, Mode=TwoWay}" />
<!-- altro codice -->
<StackLayout Padding="10" Orientation="Horizontal">
<Label Text="Your name is:" />
<Label Text="{Binding Name}" />
</StackLayout>
È possibile notare una piccola modifica, ossia la presenza del campo Mode
, nel binding proposto con le proprietà che saranno impiegate nel ViewModel. Il markup Binding
permette di specificare diverse proprietà tra cui Mode
. Questa proprietà è usata per specificare la direzione in cui le modifiche dei valori delle proprietà si propagano nell'app:
OneWay |
Propagazione dei cambiamenti da un oggetto sorgente a un elemento target della UI (usata di default, non è necessario specificarla) |
TwoWay |
Propagazione dei cambiamenti bidirezionale |
OneWayToSource |
Propagazione dei cambiamenti dall’elemento target all’oggetto sorgente |
Definiamo adesso la classe FormViewModel
aggiungendo al progetto portable una nuova classe. Per poter applicare MVVM è necessario che la classe implementi l’interfaccia INotifyPropertyChanged
, che definisce l’evento PropertyChanged
di tipo PropertyChangedEventHandler
. Questo evento prende un’istanza della classe PropertyChangedEventArgs
che definisce la proprietà PropertyName
di tipo string
, attraverso la quale è possibile sapere quale proprietà nel ViewModel è cambiata (permettendo all’evento di accedere a quella proprietà). Di seguito, vediamo la definizione dell’evento e del metodo OnPropertyChanged
impiegato per l’aggiornamento di stato.
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler!= null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Fatto ciò, è possibile aggiungere alla nostra classe le proprietà che devono essere legate agli elementi della View. Ad esempio, definiamo la proprietà Name
:
string name;
public string Name
{
get { return name; }
set
{
if (name != value)
{
name = value;
OnPropertyChanged("Name");
}
}
}
Attraverso la proprietà pubblica Name
, offriamo la possibilità lato codice di ottenere le informazioni sul nome corrente attraverso il get
, e di impostare un nuovo valore per tale proprietà attraverso il set
ogni qualvolta il valore assunto dalla variabile name
differisce da quella corrente. Proprio in quest’ultima porzione viene richiamato il metodo OnPropertyChanged
, che prenderà in ingresso il nome della proprietà che deve essere aggiornata innescando il meccanismo sopra descritto.
Non resta a questo punto che legare la View con il ViewModel. Si potrebbe fare per ogni singolo componente, ma è possibile ottimizzare questo processo usando la proprietà BindingContex
. Aggiungiamo al costruttore presente nel code-behind la seguente riga:
BindingContext = new FormViewModel();
In questo modo, tutti gli elementi della UI hanno lo stesso BindingContex. Un’alternativa è effettuare il binding tramite XAML. A tal proposito, si rimanda a questo approfondimento specifico.
Il codice completo di questa lezione è reperibile ai seguenti link:
Eseguiamo l’applicazione sulle piattaforme di interesse ottenendo il risultato mostrato in Figura 56.