Aplikace pro více zařízení (2.) – MVVM

Vítejte v miniseriálu, který vám má pomoci psát kód tak, aby byl, aby byl co nejvíce znovupoužitelný v různých typech aplikací. V prvním díle jsme se zabývali potřebným nástrojem, což jsou zejména portable libraries. V dnešním druhém díle se budeme věnovat základnímu návrhovému vzoru - MVVM.

Co je to MVVM a proč ho použít?

MVVM je návrhový vzor z rodiny MV*, exaktnější definici najdete například na Wikipedii. Není nutné se pouštět do rozsáhnlých akademických debat. Postačí asi říct, že MVVM se (podobně jako ostatní MV* schémata) pokouší oddělit stav a chování uživatelského rozhraní (ViewModel) od vzhledu uživatelského rozhraní (View).

Oddělení “stavu a chování” od “vzhledu” uživatelského rozhraní přináší řadu výhod:

  • Nezávislost na realizaci uživatelského rozhraní – pokud se ho později rozhodnu změnit jeho vzhled (View), je to velmi snadné (ViewModel nemá žádnou závislost na View)
  • Nezávislost na platformě – stejný ViewModel může být použit na více platformách (pokud podporují XAML), neboť ViewModel nemá žádnou závislost na konkrétní platformě.
  • Testovatelnost – mohu si snadno vytvořit instanci ViewModelu (bez vytvoření skutečného uživatelského rozhraní) a spouštět proti němu unit testy.

Části MVVM anebo M-V-VM

Popišme si nyní tři základní části návrhového vzoru, tedy Model (M), View (V) a ViewModel (VM). Jejich funkce a vztah je popsaný na následujícím obrázku:

image

Nejnižší vrstvou je model a je zároveň vrstvou nejméně zajímavou. Jako model označujeme cokoliv co je “pod úrovní” uživatelského rozhraní tedy např. přístup k datům, volání back-endu, herní algoritmus, Tato vrstva funguje a vytváří se obvyklým způsobem a není důvod se jí dále věnovat.

Naopak nejvyšší vrstvou je View, které má na starost vzhled uživatelského rozhraní. Prakticky vzato je View tvořeno popisem rozhraní pomocí XAML jazyka a minimální množstvím kódu s ním spojeným (code-behind). View interaguje s ViewModelem prostřednictvím data-bindingu, což je nejsilnější stránka uživatelského rozhraní napsaného v XAMLu. Více se mu budeme věnovat ve třetím díle seriálu.

Nejdůležitější vrstvou modelu je vrstva prostřední, tedy ViewModel. K ní se váže anglická slovní hříčka, zde je to spíše “view of a model” anebo “model of a view” – s trochou nadsázky se dá říct, že obě varianty mají svoje racionální jádro. Rozhodně se však jedná o vrstvu nejzajímavější. Její úlohou je:

  • Udržovat informaci o stavu uživatelského rozhraní
  • Zpřístupňovat tento stav formou vhodnou pro data binding
  • Reagovat na příkazy z uživatelského rozhraní (např. stisk tlačítka)

ViewModel je samostatná třída bez znalosti View, často umístěná v samostatném projektu (není podmínkou). Pojďme se nyní podívat, jak prakticky ViewModel realizovat.

Praktická realizace ViewModelu

Při vytváření ViewModelu si sice vystačíme s tím, co nabízí platforma, nicméně je dobré si ušetřit práci a nepsat zbytečně kód, který už napsal někdo jiný. Pomocných frameworků pro MVVM je celá řada. Nemám k dispozici žádné statistiky, ale na první pohled se zdá, že nejčastěji používaným je MVVMLight. Přiznám se, že ho mám rovněž v oblibě, neboť je velmi jednoduchý a elegantní, a co víc – je k dispozici  též ve formě Portable Library (PL), takže je možné vytvářet celý ViewModel též jako PL (pamatujete si na povídání o možných závislostech v prvním díle?).

Takže jak takový typický ViewModel vypadá:

1. Využívá třídy z nižší vrstvy (Model), ale nezpřístupňuje je navenek. Drží si je typicky jako privátní členy třídy. Používá je pro získání dat, která zpřístupňuje pro View, případně provádí operace nad daty na základě příkazů přijatých z View.

2. Implementuje rozhraní INotifyPropertyChanged, takže při každé změně vlastnosti se vyšší vrstva (View) pomocí data bindingu dozví o této změně a může se jí přizpůsobit. Pokud použijete MVVMLight, nemusíte toto rozhraní implementovat, namísto toho odvodíte svůj ViewModel ze třídy ViewModelBase. Zpřístupněná vlastnost ViewModelu pak vypadá např. takto:

private string _userId;
public string UserId
{
get
{
return _userId;
}
private set
{
this.Set(ref _userId, value);
}
}

3. Pokud zpřístupňuje kolekce, které se mění, měly by tyto kolekce implementovat rozhraní INotifyCollectionChanged, aby se vyšší vrstva (View) dozvěděla o změně obsahu kolekce. Typicky ovšem toto rozhraní nemusíte implementovat, místo toho použijete třídu ObservableCollection<T> , která umí vše potřebné, např. takto:

private ObservableCollection<ShoppingListItem> _shoppingList;
public ObservableCollection<ShoppingListItem> ShoppingList
{
get
    {
        return _shoppingList;
}
private set
{
this.Set(ref this._shoppingList, value);
this.DeleteItemCommand.RaiseCanExecuteChanged();
}
}

4. Operace na datech se provádí prostřednictvím příkazů (objektů), zpřístupňujících ICommand, které nabízí vyšší vrstvě možnost zjistit, zda je příkaz povolen a případně ho vykonat. Pokud použijete framework MVVMLight, nebudete muset toto rozhraní nikdy implementovat – místo toho použijete připravenou třídu RelayCommand a předáte jí dva potřebné lambda výrazy, např.:

private RelayCommand _loginCommand;
public RelayCommand LoginCommand
{
get
{
if (_loginCommand == null)
_loginCommand = new RelayCommand<string>(
async () => await LoginAsync(),
() => (this.UserId == null) && !_autoLoginInProgress);
return _loginCommand;
}
}

5. Pokud třída zpřístupňuje jiné objekty (ať už jednotlivě nebo jako kolekce), měly by pro ně rovněž platit výše uvedená pravidla.

Vzorové řešení

Během celého miniseriálu používám pro ukázky jednoduchou, ale zcela funkční aplikaci, jejíž kód dám později k dispozici ke stažení. V tuto chvíli je zajímavé především to, kam umístit jednotlivé vrstvy. Zvolil jsem následující řešení:

 

image

ViewModel a Model jsem tedy umístil do jednoho portable library (PL) projektu. Stejně tak bylo možné umístit každou vrstvu do svého projektu, s tím, že  ViewModel projekt bude referencovat Model projekt (oba by byly PL, tak by v tom nebyl problém). Toto uspořádání zaručuje, že obě vrstvy bude možné použít z obou zamýšlených klientů – Windows Phone i Windows Store.

Model je sada pomocných tříd – definice entit (Channel, ShoppingListItem), a třídy pro manipulaci s daty (ChannelManager, ShoppingListManager, AuthenticationManager). Model používá Azure Mobile Services pro autentizaci uživatelů a uložení dat - položek nákupního košíku a notifikačních kanálů. K tomu referencuje knihovnu Azure Mobile Services SDK (v nové verzi též jako PL).

ViewModel tvoří dvě třídy – ShoppingListViewModel pro vlastní práci s nákupním seznamem a LoginViewModel pro přihlášení uživatele.

Závěr a pozvánka

Pokud se chcete o výše uvedeném dozvědět více, doporučuji následující 2 odkazy:

Dne 9.4. od 10:00 hodin pořádáme na výše uvedené téma hodinový online seminář, kde se o problematice můžete dozvědět více. Využijte příležitosti položit konkrétní dotazy, které vás zajímají. Zaregistrovat se můžete zde.

V dalších dílech nás čeká jazyk XAML pro vytvoření View a možnosti abstrakce platformy.

   Michael