Aplikace pro více zařízení (4.) – Abstrakce platformy

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. Ve druhém díle jsme se věnovali základnímu návrhovému vzoru – MVVM, zejména prostřední vrstvě ViewModel, ve třetím díle potom XAMLu používanému pro View. Dnes se budeme věnovat abstrakci platformy.

V čem je vlastně problém?

Při vývoji typické aplikace určitě narazíte na potřebu používat řadu systémových služeb jako např. kryptografii, síťovou komunikaci, uložení nastavení, notifikaci, autentizaci, ...

Tyto služby typicky potřebuje vrstva ViewModel anebo Model využívat, ale není možné je implementovat jako portable library (PL), neboť jsou implementovány na každé platformě různě – používají různé mechanismy, třídy, knihovny, … Navíc jsou implementovány v platformově závislých knihovnách, takže není možné je z PL referencovat.

Řešení = abstrakce platformy

Vyřešení tohoto problému není příliš obtížné – skládá se z následujících kroků.

  • Ve sdíleném kódu (PL) se definují rozhraní/abstraktní třídy pro jednotlivé systémové služby
  • Kód v PL (ViewModel, Model) znají a využívají pouze tato definovaná rozhraní, nikoliv konkrétní implementace.
  • Každá platforma definuje svoje konkrétní implementace těchto rozhraní/abstraktních tříd.
  • Při startu aplikace na dané platformě jsou konkrétní implementace zpřístupněny sdílenému kódu, který je pak volá.

Zpřístupnění zmíněné v posledním kroku může mít různou podobu. Například lze ve sdíleném kódu vytvořit statickou třídu Services, která má jako vlastnosti příslušné služby (např. vlastnost Storage typu IStorageService). Ve sdíleném kódu pak můžete používat tuto funkci pomocí Services.Storage, při startu každé platformy samozřejmě musíte přiřadit vlasnosti Services.Storage její konkrétní implementaci.

Inversion of Control

Populární technikou pro vytváření dobře testovatelných  aplikací s kontrolovanými závislostmi mezi komponentami je návrhový vzor Inversion of Control (IoC). Ten je často používán v kombinaci se vzorem Service Locator.

Oba tyto vzory jsou velmi jednoduchým způsobem implementovány i v knihovně MVVMLight, proč je tedy nevyužít pro zjednodušení a zefektivnění kódu. Implementace je založena na třídě ServiceLocator z Microsoft patterns & practices. Používá jednoduchý IoC kontejner jménem SimpleIoC, jehož hlavní devizou je – jak vyplývá i z názvu – jednoduchost.

Např. potřebujeme vytvořit službu pro abstrakci navigace mezi jednotlivými obrazkovkami, která je na každé platformě různá. Za tímto účelem definujeme v portable library (sdíleném kódu) rozhraní INavigationService:

public interface INavigationService
{
void Navigate(string type, object parameter = null);
}

Tuto třídu pak implementujeme pro každou konkrétní platformu. Např. pro Windows Store může být implementace následující:

class NavigationService : INavigationService
{
private PhoneApplicationFrame _rootFrame;
public NavigationService(PhoneApplicationFrame rootFrame)
{
_rootFrame = rootFrame;
}

public void Navigate(string type, object parameter = null)
{
switch (type)
{
case "MainPage":
_rootFrame.Navigate(new Uri("/MainPage.xaml", UriKind.Relative));
break;
}
}
}

Při startu aplikace na dané platformě pak zaregistrujeme tuto konkrétní implementaci rozhraní:

SimpleIoc.Default.Register<INavigationService>(
() => new NavigationService(Window.Current.Content as Frame));

Ve sdíleném kódu pak implementujeme statickou třídu Services pro snadný přístup k jednotlivým službám:

public static class Services
{
… kráceno …
public static INavigationService Navigation
{
get { return ServiceLocator.Current.GetInstance<INavigationService>(); }
}
}

Odměnou za tyto přípravné práce je nám velmi elegantní využívání této služby v kódu:

Services.Navigation.Navigate("MainPage");

Podobným způsobem snadno abstrahujeme i ostatní služby platformy.

Vzorové řešení

Opět jako příklad použiji jednoduchou, ale zcela funkční aplikaci, jejíž kód si dnes konečně můžete stáhnout. Kam umístit platformově závislé funkce by mělo být jasné z obrázku:

image

Veškerá rozhraní jsou implementována ve sdílené knihovně, jejich implementace je pak zvlášť pro každou platformu v platformově závislé knihovně, a to samozřejmě opakovaně (v tomto případě 2x).

Konkrétně během vývoje vyvstala potřeba pro abstrakci následujících funkcí:

  • Lokální úložiště (IStorageService) – je třeba ukládat některé údaje v lokálním úložišti (např. přihlašovací token pro mobilní službu
  • Notifikace (INotificationService) – pomocná služba pro získání informací k registraci notifikace pro zařízení a obsluhu příchozích notifikacích
  • Navigace (INavigationService) – přepínání mezi jednotlivými obrazovkami aplikace
  • Dialogy (IDialogService) – jednoduché dialogy, jako je zobrazení chybového hlášení nebo potvrzení otázky (např. při mazání)
  • Uživatelské rozhraní pro přihlášení do Azure Mobile Service (IMobileServiceUI) – poměrně speciální záležitost pro embedovaný prohlížeč sloužící k autentizaci uživatelů pro službu

Kód ke stažení

Jak jsem již třikrát slíbil, kód této vzorové aplikace je volně k dispozici. Dnes si ho konečně můžete stáhnout zde.

Kód využívá můj Azure Mobile Service. Klidně si jej můžete spustit proti mé službě, něco si do ní uložit a nechat posílat notifikace. Neručím ovšem za to, že služba bude vždy dostupná, že ji do budoucna nezměním tak, že nebude s tímto kódem kompatibilní anebo že nevymažu data v ní uložená.
Pokud byste si chtěli udělat vlastní backend, více informací o službě Azure Mobile Service naleznete např. v tomto miniseriálu.

Závěr a pozvánka

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.

Původně jsem myslel, že seriál tímto dílem ukončím. Při jeho psaní jsem však dostal chuť vylepšit svůj kód ještě o unit testy a vylepšit jeho testovatelnost na hranici možností dnešních nástrojů. Takže se můžete těšit ještě na pátý díl, byť se objeví s časovým odstupem.

Michael