Univerzita Karlova v Praze Matematicko-fyzikální fakulta
Diplomová práce
Ondřej Kunc Multiplatformní mobilní aplikace databázového systému Matylda Katedra distribuovaných a spolehlivých systémů
Vedoucí diplomové práce: RNDr. Jan Kofroň, Ph.D.
Studijní program: Informatika Studijní obor: Softwarové systémy Praha rok 2015
Na tomto místě bych rád poděkoval mému vedoucímu práce RNDr. Janu Kofroňovi, Ph.D., který mi byl vždy k dispozici a poskytnul velmi cenné rady a zkušenosti. Dále bych chtěl poděkovat své rodině a přítelkyni za podporu a všem členům týmu Matylda za skvělou spolupráci na projektu.
Prohlašuji, že jsem tuto diplomovou práci vypracoval samostatně a výhradně s použitím citovaných pramenů, literatury a dalších odborných zdrojů.
Beru na vědomí, že se na moji práci vztahují práva a povinnost vyplývající ze zákona č. 121/2000 Sb., autorského zákona v platném znění, zejména skutečnost, že Univerzita Karlova v Praze má právo na uzavření licenční smlouvy o užití této práce jako školního díla podle § 60 odst. 1 autorského zákona.
V ….……. dne …….….
podpis
Název práce: Multiplatformní mobilní aplikace databázového systému Matylda Autor: Ondřej Kunc Katedra / Ústav: Katedra distribuovaných a spolehlivých systémů Vedoucí diplomové práce: RNDr. Jan Kofroň, Ph.D. Abstrakt: Systém Matylda vznikl za účelem poskytnutí detailní databáze a webového rozhraní pro vyhledávání, filtrování a porovnávání zboží z oblasti potravin a drogerie. Cílem práce je k tomuto systému vytvořit uživatelsky přívětivou mobilní aplikaci, která bude dostupná na všech hlavních mobilních platformách dneška. Výsledná mobilní aplikace zpřístupňuje hlavní funkcionality již existující webové aplikace a využívá předností mobilních zařízení, jako je použití map nebo zjišťování polohy uživatele. Mezi hlavní funkcionality patří vyhledání detailní informace o produktech, procházení slevových letáků, porovnávání slev, vytváření nákupních seznamů, přihlašování a další. Aplikace tedy může být využita jako chytrý pomocník při nákupu potravin a drogerie, který ušetří uživatelům čas i peníze. Klíčová slova: vývoj mobilní aplikace, Android, iOS, Windows Phone, webové služby Title: Multi-platform mobile application of database system Matylda Author: Ondřej Kunc Department / Institute: Department of Distributed and Dependable Systems Supervisor of the master thesis: RNDr. Jan Kofroň, Ph.D. Abstract: System Matylda was created in order to provide a database and web interface which allows sorting, filtering and comparing of products from food and drugstore industry. The goal of this thesis is to create a user friendly mobile application for this system. The application will be available for all today’s major mobile platforms. The final application uses main features of the existing web application and also takes advantage of capabilities of modern mobile devices such as maps and user location. Some of the main features are searching for detailed product information, browsing discount leaflets, comparing discounts, creating shopping lists, authentication and more. The application can be used as a smart assistant which helps with shopping for food and drugstore products and can also save both time and money.
Keywords: mobile application development, Android, iOS, Windows Phone, web services
Obsah 1
2
Úvod ................................................................................................................ 1 1.1
O projektu Matylda.................................................................................... 1
1.2
Požadované cíle ......................................................................................... 1
1.3
Struktura diplomové práce ......................................................................... 2
Analýza............................................................................................................ 3 2.1
Popis existujícího systému ......................................................................... 3
2.2
Specifikace ................................................................................................ 4
2.3
Popis jednotlivých platforem...................................................................... 5
2.3.1
Android .............................................................................................. 6
2.3.2
iOS ..................................................................................................... 8
2.3.3
Windows ............................................................................................ 9
2.4
Srovnání technologií pro vývoj mobilní aplikace ......................................10
2.4.1
Nativní vývoj.....................................................................................12
2.4.2
PhoneGap ..........................................................................................12
2.4.3
Appcelerator Titanium .......................................................................13
2.4.4
Xamarin.............................................................................................14
2.4.5
Celkové srovnání ...............................................................................15
2.5
Xamarin technologie.................................................................................16
2.6
Návrhový vzor MVVM a MvvmCross Framework ...................................20
2.7
Podpora různých verzí cílových platforem ................................................23
2.8
Volba lokálního úložiště ...........................................................................26
2.9
Možnosti zobrazení map ...........................................................................27
2.10
Návrh implementace serverové části......................................................30
2.10.1 Začlenění do projektu Matylda...........................................................30 2.10.2 Volba technologie..............................................................................31 3
Implementace..................................................................................................33 3.1
Základní architektura ................................................................................34
3.1.1
Vrstvy webového klienta ...................................................................35
3.1.2
Databázová vrstva..............................................................................36
3.1.3
Vrstva služeb .....................................................................................37
3.1.4
ViewModely......................................................................................39
3.1.5
Android projekt .................................................................................42
3.1.6
iOS projekt ........................................................................................44
3.1.7
Windows projekt ...............................................................................46
3.2
Platformě specifické UI komponenty ........................................................47
3.3
Navigace aplikací......................................................................................52
3.4
Procházení letáků......................................................................................54
3.5
Geolokace.................................................................................................57
3.6
Implementace map....................................................................................57
3.7
Nákupní seznamy a jejich synchronizace ..................................................60
3.8
Chybové stavy ..........................................................................................62
3.9
Přihlašování..............................................................................................63
3.9.1
Serverová část....................................................................................64
3.9.2
Klientská část ....................................................................................66
3.10 4
Testování ..............................................................................................67
Uživatelská dokumentace................................................................................69 4.1
Instalace ...................................................................................................69
4.2
Průchod aplikací .......................................................................................71
4.3
Rozbor případů užití .................................................................................77
5
Podobné aplikace ............................................................................................79
6
Závěr...............................................................................................................81
7
Zdroje .............................................................................................................82
8
Seznam obrázků ..............................................................................................85
9
Seznam zkratek ...............................................................................................86
10
Přílohy ............................................................................................................87
1 Úvod 1.1 O projektu Matylda Projekt Matylda vznikl v rámci předmětu Softwarový projekt na MFF UK. Hlavním cílem projektu bylo vytvořit univerzální databázový systém pro ukládání produktů z oblasti potravin a drogerie prodávaných na území ČR. K tomuto účelu byla vyvinuta webová aplikace, která uživateli poskytuje široké možnosti filtrování, třídění a preferencí, se zachováním maximální jednoduchosti. Projekt byl v prosinci 2014 úspěšně obhájen a všichni autoři se rozhodli na projektu dále pokračovat a uvést ho do ostrého provozu. Další práce přinesla požadavky na nové funkcionality a vylepšení. Aby bylo dosaženo co největšího počtu uživatelů, byl jeden z těchto požadavků zpřístupnit funkce Matyldy na tzv. chytrých mobilních telefonech, které používá stále více lidí.
1.2 Požadované cíle
Hlavním cílem této práce je vyvinout mobilní aplikaci, která zpřístupní funkce webové aplikace a bude maximálně uživatelsky přívětivá.
Aplikace bude využívat předností chytrých telefonů, jako jsou mapy, zjišťování polohy uživatele a dotykového ovládání.
Bude analyzován trh s chytrými telefony, jejich operačními systémy a podíly jednotlivých platforem na trhu. Výstupem této analýzy bude seznam platforem, pro které se vyplatí vyvinout mobilní aplikaci pro projekt Matylda.
Proběhne analýza technologií pro vývoj mobilních aplikací. Dá se očekávat, že cílových platforem bude více, proto je třeba zvážit i různá multiplatformní řešení, kdy lze sdílet kód mezi jednotlivými platformami a tím ušetřit čas při vývoji.
Bude navržen a implementován způsob jakým bude komunikovat mobilní aplikace se systémem Matylda včetně popisu napojení do existující architektury systému. 1
1.3 Struktura diplomové práce Diplomová práce začíná popisem již vyvinutého systému a specifikací samotné aplikace. Poté následuje analýza platforem a srovnání technologií pro jejich vývoj. Součástí analýzy je také popis vybrané technologie a analýza řešení samotné aplikace. V kapitole 3, která se zabývá implementací, bude nejprve popsána základní architektura aplikace a poté bude následovat popis řešení jednotlivých problémů jako je např. navigace aplikací, implementace map nebo realizace přihlašování. Součásti kapitoly je i popis způsobu testování aplikace. Kapitola 4 obsahuje uživatelskou dokumentaci, kde je popsán průchod aplikací, realizace různých případů užití a popis instalace. Další kapitola srovná podobné aplikace na trhu a v závěru budou popsána možná rozšíření aplikace a zhodnocení celého řešení.
2
2 Analýza 2.1 Popis existujícího systému Nejprve stručně popíšeme softwarové dílo, které bylo vyvinuto v rámci softwarového projektu, na který tato práce navazuje. Detailní popis tohoto díla byl přiložen ve formě dokumentace při obhajobě projektu a zde bude následovat jen krátký popis pro uvedení čtenáře do problematiky. Systém Matylda je webový projekt založený na technologii ASP.NET MVC, který používá relační databázi MSSQL a NoSQL databázi Apache Solr pro ukládání dat. Hlavním účelem projektu je poskytnout uživatelům detailní databázi potravinářských a drogistických výrobků prodávaných v ČR. Důraz je kladen na ceny produktů a produkty ve slevě, které mohou zajímat velký počet lidí. Pro náročnější uživatele systém navíc umožňuje široké možnosti filtrování produktů. Je možné např. hledat pečivo prodávané v Bille a Albertu v Praze, které neobsahuje lepek a výsledky seřadit podle ceny. Mezi další funkcionality patří procházení slevových letáků, přihlašování, vytváření nákupních košíků, procházení obchodních řetězců, jejich prodejen a další. Web je rozdělen na veřejnou část obsahující výše popsanou funkcionalitu a administrativní část. V této části je možno spravovat téměř veškerá data pomocí webového rozhraní, přičemž největší důraz je kladen na manuální zadávání produktů z letáků. Z každého letáku se získávají údaje o produktech, jejich cenách, hmotnosti a další. Zpracování letáků je časově náročná operace, proto byly implementovány funkcionality, které tento proces urychlí. Jedná se např. o analýzu stránek metodou digitálního zpracování obrazu, které na základě heuristik zkusí rozpoznat opakující se prvky na stránce, jako je název produktu, stará cena a cena ve slevě. Tyto údaje jsou poté předvyplněné a zadávající je pouze kontroluje a upravuje. Databáze produktů v současné době obsahuje přes 20000 unikátních položek a je plněna ručním zpracováním slevových letáků a také importem produktů ze serveru http://www.zdravapotravina.cz/, se kterým máme smlouvu. Zároveň probíhají 3
jednání se samotnými řetězci o poskytování dat ve strojově čitelné formě. Projekt běží v testovacím režimu na adrese http://projekt-matylda.cz/.
2.2 Specifikace Hlavním požadavkem na mobilní aplikaci je zpřístupnit funkce webového portálu s využitím výhod mobilních zařízení. Protože se na vývoji webové části pracuje paralelně s mobilní, mohou vznikat požadavky na nové funkce či změnu stávající funkcionality i v průběhu vývoje, avšak všechny důležité funkcionality jsou definovány již před začátkem implementace. Nyní bude následovat jejich výčet:
Prohlížení nejzajímavějších nabídek – Uživatel si bude moci zobrazit seznam populárních produktů, které jsou aktuálně ve slevě, seřazené podle velikosti slevy. Tyto produkty by měly být zobrazeny na úvodní stránce.
Procházení aktuálních letáků – Uživatel bude mít možnost listovat aktuálními slevovými letáky řetězců.
Zobrazení informací o produktu – U každého produktu bude zobrazena informace o aktuální, průměrné a minimální ceně, složení, ingredience, seznam prodejen kde lze produkt koupit řazeno od nejbližší a poloha těchto prodejen na mapě.
Procházení produktů podle kategorií – Produkty jsou zařazeny do stromu kategorií a aplikace umožní průchod tímto stromem.
Procházení obchodníků – Aplikace umožní průchod přes jednotlivé prodejce. U každého prodejce se eviduje seznam jeho produktů, aktuálních letáků, nejbližších prodejen a jejich podčástí (např. Tesco Express je podčástí Tesco). U jednotlivých prodejen bude zobrazena adresa, poloha na mapě a možnost spuštění navigace k dané prodejně.
Vyhledávání – Uživatel bude moci vyhledávat produkty, kategorie a obchodníky.
Volba polohy – Aplikace bude používat aktuální polohu telefonu a navíc umožní nastavit aktuální polohu na libovolné město v ČR. 4
Přihlašování – Aby mohl uživatel používat nákupní seznamy, musí se nejprve přihlásit. Bude umožněno stejné přihlašování jako ve webové verzi, tedy jménem a heslem nebo přes služby externího poskytovatele, což bude Facebook a Google.
Nákupní seznamy – Přihlášený uživatel bude mít možnost zakládat nákupní seznamy, přidávat produkty a kategorie do seznamu, editovat seznamy a sdílet seznamy pomocí emailu s jinými uživateli. Seznamy budou synchronizovány s jeho Matylda účtem a musí částečně fungovat i bez připojení k internetu.
Tutoriál – Při prvním spuštění bude uživatel seznámen s hlavními funkcemi aplikace formou jednoduchého tutoriálu.
2.3 Popis jednotlivých platforem Před vývojem aplikace běžících na chytrých mobilních zařízeních je nejprve nutné zvážit, pro které zařízení je aplikace určená. Dnešní chytré telefony jsou vybaveny různými operačními systémy, které se od sebe můžou poměrně lišit, a pokud se rozhodneme podporovat jeden z těchto systémů, pak port na jiný systém může obnášet vývoj celé aplikace znovu od začátku. Pokud se podíváme na podíly různých operačních systémů na trhu, nejčastěji zde figurují tyto systémy: Android, iOS, Windows Phone, Symbian a BlackBerry OS. Celosvětové podíly systémů ve druhém čtvrtletí 2015 srovnává například server IDC [1]. Tabulka 1: Celosvětové podíly - druhé čtvrtletí 2015 [1]
Android
iOS
Windows Phone
BlackBerry OS
Ostatní
82.8 %
13.9 %
2.6 %
0.3 %
0.4 %
Server mobilenet.cz [2] přidává srovnání z českého trhu, kde srovnává data od tuzemských operátorů, konkrétně T-Mobile, O2 a Vodafone. Data jsou z ledna 2015 a vypadají následovně: 5
Tabulka 2: Podíly na českém trhu - leden 2015 [2]
Android
iOS
Symbian
Windows Phone
BlackBerry OS
Ostatní
71.7 %
10.63 %
9.32 %
6.16 %
1.2 %
0.99 %
Kromě procentuálního podílu operačních systémů tu jsou i další faktory pro komerční úspěch aplikace. Např. na Androidu může být těžší zviditelnit aplikaci než např. na Windows Phone. Jiné průzkumy zase ukázaly, že iOS uživatelé utratí za aplikace nejvíce peněz. Jenom z tržních podílů můžeme však bezpečně říci, že pokud chceme dosáhnout co největšího počtu uživatelů, měli bychom cílit naši aplikaci minimálně na platformu Android a iOS. Protože cílíme na české uživatele, mohl by připadat v úvahu i systém Symbian, avšak telefony s tímto systémem se již nevyrábějí a jejich podíl tedy bude postupně klesat. Posledním systémem, který má nezanedbatelný podíl a vyplatí se pro něj vyvíjet, je systém Windows Phone. Nyní bude následovat stručný popis vybraných systémů.
2.3.1 Android Operační systém Android je založený na jádře Linuxu. Původně byl vyvíjen společností Android Inc., kterou v roce 2005 koupil Google. V roce 2007 bylo vytvořeno uskupení konsorcium Open Handset Alliance, která měla za cíl vytvořit otevřený standard pro mobilní zařízení a jejíž první produkt byl právě Android [3]. Od vydání první verze vychází aktualizace systému, které přidávají do systému nové funkce. Např. verze 3.0 přinesla optimalizaci pro obrazovky tabletů. Poslední vydaná verze 5.1 (Lollipop) vyšla v březnu 2015. Architektura systému se skládá z několika vrstev. Kromě jádra systému a knihoven jádra je zde vrstva Android Runtime. Ta obsahuje virtuální stroj Dalvik, který vytváří běhové prostředí pro Java aplikace. Aplikace se překládá ze zdrojových souborů napsaných v Jave do Java byte kódu. Ten je pomocí Dalvik kompilátoru překompilován do výsledného Dalvik byte kódu, který běží na virtuálním stroji Dalvik. Od verze Androidu 4.4 se ke kompilaci používá mechanismus ART – Android Runtime. Jedná se o princip dopředné kompilace a funguje tak, že byte kód
6
Dalviku se při instalaci aplikace přeloží přímo do nativního kódu zařízení. To má za důsledek rychlejší běh aplikací a delší výdrž telefonu. Nejdůležitější vrstvou pro vývojáře aplikací je vrstva Application Framework. Ta zpřístupňuje velké množství služeb, které mohou být použité v samotné aplikaci. Jedná se např. o:
View komponenty - Tlačítka, textové pole, atd.
Activity manager – Stará se o životní cyklus aplikace.
Location manager – Umožní aplikaci získávat informaci o poloze.
Notification manager – Umožňuje zobrazit upozornění.
Obrázek 1: Architektura Androidu [4]
Oficiální vývojové prostředí pro Android je Eclipse, avšak Google prosazuje i Android studio, které koupil v roce 2014. Hlavním aplikačním obchodem je Google Play, ale aplikace mohou být nainstalovány i z jiných zdrojů. Dnes běží Android na více než 20000 zařízeních od 1300 různých značek.
7
2.3.2 iOS Systém iOS je odlehčenou verzí systému Mac OS X, kterou používá Apple ve svých počítačích. Je to tedy systém UNIXového typu. Hlavní odlišnosti iOS oproti Mac OS X je podpora dotykového ovládání. První verze systému vyšla v roce 2007 spolu s vydáním prvního iPhonu. Systém byl nejprve znám pod jménem iPhone OS. Až roku 2010 byl systém přejmenován na dnešní iOS. Aktuální verze je iOS 8. Podobně jako Android i iOS se dělí na několik vrstev, které poskytují vývojářům základní API a funkcionality. Nejnižší vrstva, na které jsou postaveny ostatní technologie se nazývá Core OS vrstva. Nad touto vrstvou je postavena vrstva Core Services. Ta poskytuje přístup ke službám jako je ochrana dat, SQLite databáze, úložiště
iCloud a další. Pro vytváření grafických a zvukově
propracovaných aplikací slouží vrstva Media. Tu vývojář typicky použije, pokud mu nestačí standartní komponenty systému, které jsou součástí nejvyšší vrstvy a to vrstvy Cocoa Touch [5]. Vrstva Cocoa Touch obsahuje většinu funkcionality, kterou vývojář potřebuje pro vývoj klasické aplikace pro iOS. Zahrnuje například:
UIKit framework – Framework poskytující infrastrukturu pro vývoj standartní grafické aplikace. Obsahuje např. View Controllery (spravují obsah uživatelského rozhraní) nebo standartní UI komponenty.
Notifiaction center Framework – Umožní přidat notifikace do notifikačního centra.
MapKit – Poskytuje přístup k mapovým komponentám, které mohou být součástí aplikace.
8
Obrázek 2: Architektura iOS [5]
Vývoj pro platformu iOS probíhá v aplikaci XCode, která je ale dostupná pouze na Mac OS X. Aplikace jsou psané v jazyce C, pokročilejším Objective C nebo nejnovějším jazyku Swift. Kód na iOS je překládán do nativního kódu zařízení a není interpretovaný, jako např. bytecode Dalviku na Androidu. Systém iOS běží na 12 různých modelech iPhonů a 6 modelech tabletů iPadů (novější verze systému nejsou podporovány na starších zařízeních). Výrobce telefonů je jediný a to firma Apple. Aplikace jsou distribuovány pomocí obchodu App Store.
2.3.3 Windows Windows Phone je systém vyvíjený společností Microsoft a je nástupcem systému Windows Mobile. Systém vyšel v roce 2010 a nesl označení Windows Phone 7. Později v roce 2012 vyšel systém Windows Phone 8 založený na architektuře Windows NT, který ale není zpětně kompatibilní s Windows Phone 7 a který používal architekturu Windows CE. Aktuální verze je Windows Phone 8.1 na jejíž popis se zaměříme. Architektura Windows Phone 8.1 se skládá z několika částí. Obsahuje sdílené jádro dělící se na Windows Core a Mobile Core. Nad jádrem je postavené WinRT rozhraní poskytující běhové prostředí Windows Runtime XAML Framework, grafické knihovny, rozhraní pro živé dlaždice, mapy, geolokace a další.
9
Až do verze Windows Phone 8.0 se aplikace pro telefon psaly v tzv. Silverlight API. S verzí 8.1 Microsoft představil rozhraní WinRT, které bylo dříve používáno pro psaní tabletových aplikací ve Windows 8, dříve známé jako metro aplikace. Nad rozhraním WinRT lze psát aplikace v jazyce C# nebo VB.NET s použitím jazyka XAML. Grafické aplikace je možné psát pomocí DirectX v C++. Stejně je možné pro vývoj používat HTML/Javascript [6]. Aplikace pro WinRT lze téměř s minimem změn pustit i na platformě Windows 8.1. Hlavní vývojové prostředí je Visual Studio.
Obrázek 3: Windows Runtime Execution model [6]
Hlavním dodavatelem telefonů pro systém Windows Phone byla firma Nokia, kterou Microsoft v roce 2013 koupil a nově již telefony s tímto systémem vychází pod značkou Microsoft. Z dalších výrobců je to např. HTC, Samsung, LG a další. Aplikace jsou distribuovány do obchodu Windows Phone Store.
2.4 Srovnání technologií pro vývoj mobilní aplikace V této podkapitole srovnáme technologie, pomocí kterých lze vyvíjet aplikace pro výše uvedené platformy. První z možností je pouze optimalizovat webovou aplikaci pro mobilní zařízení s využitím responsivního designu nebo specializovaného mobilní webu, ke kterému by se přistupovalo na telefonu pomocí webového prohlížeče. Toto řešení má však hned několik nedostatků, jsou jimi například tyto: 10
Aplikace nebude mít platformně specifický vzhled.
Aplikace nebude fungovat bez připojení k internetu.
Máme přístup jen k těm funkcím telefonu, které zpřístupňuje prohlížeč.
Z těchto důvodů nám toto řešení nevyhovuje. Navíc aplikace Matylda již částečně responsivní design používá, a proto se s ním v této práci nebudeme dále zabývat. V úvahu tedy přicházejí dva hlavní způsoby vývoje – nativní a multiplatformní. Nativní vývoj zahrnuje použití platformě specifických nástrojů a jazyků pro každý systém. Výsledkem je aplikace, která využívá nativních komponent a tedy má na každé platformě jiný vzhled. Pro lepší názornost platformě specifického vzhledu jsme srovnali oficiální náhledy obrázků z aplikačních obchodů všech uvažovaných systémů aplikace Facebook Messenger.
Obrázek 4: Aplikace Facebook Messenger (zleva Android, iOS, Windows Phone)
Hlavní myšlenkou multiplatformních technologií je to, že většinu nebo celý kód napíšeme jednou a poběží na všech platformách. Podle četnosti výskytu různých technologií jsme vybrali tři nejpopulárnější - Xamarin, PhoneGap, Appcelerator Titanium. Nyní podrobněji o jednotlivých technologiích. 11
2.4.1 Nativní vývoj Pokud bychom se rozhodli použít nativní vývoj, museli bychom pro každou platformu vyvinout aplikaci zvlášť pomocí nativních prostředků. Následující tabulka shrnuje hlavní jazyky a vývojová prostředí: Tabulka 3: Srovnání platforem a prostředí
Platforma iOS Android WP8
Jazyky Swift, Objective-C Java C#
Vývojová prostředí Xcode Android Studio, Eclipse Visual Studio
Výhody:
Jednoduché využití platformně specifických funkcionalit (GPS, telefonní seznam, atd.).
Jednoduché dosažení platformně specifického vzhledu.
Nevýhody:
Pro každý systém potřebujeme jednu aplikaci.
Horší údržba a implementace nových požadavků.
Vývojář musí mít znalosti všech platforem, jazyků, prostředí…
2.4.2 PhoneGap PhoneGap umožňuje psát aplikace založené na HTML technologiích, které jsou nasazeny a nainstalovány jako nativní aplikace. Celý kód je tedy psán v HTML + JavaScript + CSS a na každé platformě běží ve webové komponentě, která je zabalena do nativní aplikace. PhoneGap zároveň poskytuje společné API, přes které lze přistupovat k mobilním funkcionalitám jako je kamera, adresář atd. [7] PhoneGap aplikace tedy nemají defaultně platformně specifický vzhled a na každé platformě vypadají stejně.
12
Pro vývoj si stačí zvolit jedno prostředí (např. Visual Studio, Eclipse, Xcode) a poté použít online nástroj Adobe PhoneGap Build, kam se pouze nahraje kód, a pro každou platformu dostaneme zkompilovaný balíček. Abychom se zaregistrovali a nastavili certifikát pro vývoj na iOS, potřebujeme MAC. Nástroj Adobe PhoneGap Build je zpoplatně částkou $9,99 / měsíc. Pro jednu aplikaci do 50 MB je však zdarma. Výhody:
Sdílení až 100 % kódu.
Jednoduché nasazení přes PhoneGap Build.
Podpora dalších platforem – Blackberry, Firefox OS.
Teoretické znovupoužití stylů z původního webového projektu.
Nevýhody:
Ne všechny funkcionality jsou stejně podporovány na všech platformách.
Veškerá logika aplikace psaná v JavaScriptu, což se nemusí hodit na větší projekty.
Aplikace mají jednotný, ale ne nativní vzhled.
Aplikace jsou pomalejší ve srovnání s nativními (běží v prohlížeči).
Horší dokumentace
2.4.3 Appcelerator Titanium Appcelerator Titanium poskytuje multiplatformní JavaScriptové prostředí a rozhraní pro mobilní zařízení. Vzniklý kód využívající rozhraní mobilního zařízení je poté přeložen do nativního kódu platformy a business logika zůstane jako JavaScript a za běhu je interpretována platformě specifickým JavaScript enginem. Výsledkem je aplikace, která je hybridem nativní a JavaScriptové aplikace [8].
13
Aplikace je tedy rychlejší než PhoneGap a má nativní vzhled. Existují však nativní komponenty, které jsou specifické pro systém a nelze je jednoduše sdílet mezi platformami. Nelze tedy sdílet celých 100 % kódu. Aplikace lze vyvíjet v nástroji Titanium Studio, které je založené na prostředí Eclipse, nebo přes nástroje příkazové řádky. Titanium Studio bohužel nemá podporu pro vývoj Windows Phone 8, ale k vývoji lze využít Visual Studio. Pro systém iOS je možné v Titanium Studiu vyvíjet pouze na přístrojích s Mac OS od Applu. Základní nástroje jsou zdarma, platí se pouze dodatečné cloudové služby. Výhody:
Běží rychle díky částečnému překladu do nativní aplikace.
Nativní vzhled.
Sdílení velké části kódu.
Nevýhody:
Řešení platformě specifických komponent.
Nelze pro všechny platformy psát v jednom IDE.
Slabší dokumentace.
Veškerá logika psaná v JavaScriptu, což se nemusí hodit na větší projekty
2.4.4 Xamarin Xamarin [9] je placený nástroj, který umožňuje psát aplikace pro Android, iOS a Mac v C# a překládá je do nativního kódu pro danou platformu (aplikace platforem Windows a Windows Phone jsou sami o sobě psané v C#, proto je Xamarin neobsahuje). Udává se, že při použití tohoto nástroje lze bez velkého snažení sdílet 60-70 % kódu mezi platformami. Nadstavbou nad technologií Xamarin je technologie Xamarin.Forms. Jedná se o poměrně nový nástroj, kde se i UI kód napíše v C# a přeloží se do platformě specifických komponent. 14
Xamarin aplikace lze psát v nástroji Xamarin Studio nebo přímo ve Visual Studiu za použití Xamarin pluginu. Pro nasazení aplikace na iPhone je třeba mít připojený MAC, sloužící jako build host. Pro vývoj je potřeba jedna licence Xamarin.Android a jedna licence Xamarin.iOS, dohromady za $1998 / rok. Výhody:
Sdílení velkého množství kódu mezi platformami, což vede k velké udržovatelnosti projektu.
Výsledkem jsou plně nativní aplikace, které běží téměř stejně rychle jako nativní.
Výborná podpora a dokumentace.
Lze psát ve Visual Studiu jako původní webový projekt a částečně sdílet kód mezi webovým a mobilním projektem.
Nevýhody:
Cena.
Podobně jako u nativního vývoje musí vývojář znát nástroje pro vytváření UI na každé platformě.
2.4.5 Celkové srovnání Následující tabulka shrnuje některé ze základních aspektů:
15
Tabulka 4: Srovnání multiplatformních technologií
Technologie
Nativní aplikace
Cena navíc
Dokumen Prog. jazyk tace
Ano
% sdílení kódu mezi platformami 0
Nativní
Zdarma
N/A
Phonegap
Ne
100
Zdarma vs. $120 / rok
Horší
Xamarin Appcelerator Titanium
Ano Napůl
90 90
$1998 / rok Zdarma
Výborná Horší
Závisí na plat. HTML + CSS + Javascript C# JavaScript
Na základě analýzy těchto technologií a práci na základní úrovni s každou z nich, jsme dospěli k závěru, že jednoznačně nejvhodnější technologií pro naše účely je Xamarin. Zde jsou hlavní důvody pro tuto volbu:
Nativní vývoj nepřichází v úvahu, protože nemáme znalosti všech platforem a ukazuje se drahý. PhoneGap může být pomalý a nedosáhneme nativního vzhledu a Appcelerator Titanium je psán v JavasSriptu, což může být časově náročné.
Ze všech technologií se při osobním vyzkoušení Xamarin jevil nejjednodušší, s nejlepší dokumentací, podporou a s nejkratším časem potřebného k rozběhnutí jednoduché aplikace.
Tím, že většina aplikace je psaná v C#, bude potencionálně jednoduché zapojit i ostatní členy týmu na dílčí úkoly, aniž by museli vědět něco o mobilním vývoji.
2.5 Xamarin technologie Současně s volbou Xamarinu jako hlavního nástroje pro vývoj aplikace musíme zvolit vhodné technologie související s Xamarin vývojem. Xamarin sám o sobě umožňuje psát aplikace pro Android a iOS v jazyce C#. Nyní stručně popíšeme, jak je tohoto dosaženo na každé z těchto platforem. Xamarin.Android aplikace běží uvnitř prostředí Mono. To je postaveno nad Linuxovým jádrem a zpřístupňuje implementaci standartních .NET knihoven jako je 16
System, System.IO nebo System.Net. Naproti tomu Android knihovny jako Graphics, Audio nebo Telephony jsou přístupné pouze přes prostředí ART. Výsledná aplikace tedy používá obě běhová prostředí a Xamarin poskytuje engine na jejich propojení. Výsledkem překladu je balíček s příponou .apk, který má stejnou strukturu jako balíček přeložený z Javy. Pouze má navíc assembly knihovny aplikace a nativní knihovny obsahující prostředí Mono.
Obrázek 5: Xamarin.Android architektura
Oproti tomu Xamarin.iOS aplikace je kompilována pomocí dopředné kompilace, která je jednou z funkcionalit prostředí Mono a výsledkem je nativní kód cílového procesoru. Během překladu je použit nástroj zvaný Linker, který má za úkol odstranit nepoužívané části kódu. Samotný .NET Framework je součástí této aplikace, avšak nepoužívaný kód je během kompilace díky nástroji Linker odstraněn. Protože výsledný kód již není interpretovaný, přináší to s sebou několik omezení, např. nemožnost generování kódu za běhu. Všechny limitace lze najít na stránkách Xamarinu [10]. Obě technologie zpřístupňují kompletní rozhraní dané platformy a díky způsobu jejich fungování umožňují, aby výsledná aplikace běžela zhruba stejně rychle jako aplikace napsaná nativně. Např. přeložená Xamarin.iOS aplikace je nerozeznatelná od nativní napsané v Objective C, protože používá stejné nativní rozhraní, ale může mít větší velikost. Zajímavé srovnání můžeme najít např. na serveru magenic.com
17
[11], kde testovaná Xamarin aplikace běžela dokonce rychleji na iOS i na Androidu než nativní aplikace. Samotné použití Xamarinu pro vývoj aplikace na Android a iOS implicitně nezaručuje, že mezi platformami budeme sdílet nějaký kód. K tomu však slouží další dva koncepty a to Shared Project a Portable Class Library (PCL):
Shared Project – Umožňuje psát kód, na který se odkazují aplikační projekty.
Tento
kód
je při překladu
aplikačního
projektu
pouze
přikompilován k tomuto projektu. Sám o sobě tedy Shared Project nemá žádný výstup ve formě DLL souboru, ale stane se součástí projektu, který se na něj odkazuje. Tím může být např. Xamarin.Android, Xamarin.iOS nebo Windows projekt. Pokud potřebujeme použít kód, který je dostupný jen na jedné z platforem, použijeme direktivy kompilátoru. Např. pokud část kódu je dostupná jen na Androidu, můžeme to vyjádřit takto: #if __ANDROID__ //Android code #endif
Obrázek 6: Shared project a dirketivy kompilátoru
Portable Class Library – Na rozdíl od Shared Project je výstupem přeložený DLL soubor. Problém ale je ten, že různé platformy používají různou podmnožinu .NET Base Class Library (BCL). Při vytváření tohoto projektu je proto nutné vybrat platformy, které chceme podporovat. Tato volba se poté přeloží do tzv. identifikátoru profilu, který popisuje, jaké platformy podporujeme. V tomto projektu pak lze používat největší podmnožinu BCL, která je dostupná na všech zvolených platformách. Pro použití platformě specifické funkcionality je zapotřebí jistá míra abstrakce. Např. v PCL projektu definujeme interface, s kterým tu dále pracujeme, a jeho implementace bude definována v aplikačních projektech.
18
Obrázek 7: PCL a výběr platforem
Použití těchto konceptů může zaručit, aby velká část kódu byla sdílená. Minimálně však UI kód musíme napsat pro každou platformu zvlášť. Proto Xamarin přišel s další technologií zvanou Xamarin.Forms, která poskytuje abstrakci nad UI komponentami. Vzhled aplikace se tedy definuje pouze jednou a to pomocí předpřipravených komponent a píše se buďto přímo v C# kódu nebo pomocí jazyka XAML. Komponenty jsou poté při překladu namapovány na komponenty cílové platformy a tím je zaručen nativní vzhled i výkon. Hlavní otázka nyní je, zda pro naši aplikaci použít Xamarin.Forms. Na stránkách Xamarinu o Xamarin.Forms [12] se uvádí, kdy je vhodné tuto technologii použít. Podle těchto doporučení jsou Xamarin.Forms vhodné na jednoduché, případně prototypové aplikace, kdy není dáván důraz na platformě specifickou funkcionalitu. Naše aplikace však může být komplexnější a potenciálně může velmi využívat nativní rozhraní, proto se jako bezpečnější volba jeví Xamarin.Forms nepoužívat. Dalším argumentem může být např. fakt, že se jedná o poměrně novou technologii, která není zatím tolik rozšířená. Rozhodnutí tedy je, že Xamarin.Forms nepoužijeme. Přestože nebudeme sdílet UI kód pomocí Xamarin.Forms, stále můžeme sdílet velké množství kódu. K tomu by se nám hodila abstrakce nad rozhraními jednotlivých platforem. Jako příklad uvedeme získávání aktuální pozice uživatele, s kterou chceme dále pracovat (např. spočítat vzdálenost k obchodu). Na každé platformě se tento úkol řeší jinak. Na androidu používáme k získání lokace 19
LocationClient,
který udržuje souřadnice v objektu
CLLocationManager
najdeme
CustomLocation.
Na iOS je
se souřadnicemi uložených v CLLocation a podobně na Windows
Geolocator
s Geocoordinate. Nám by se však hodilo pracovat jen s jedním
typem objektů, který by tyto zastřešoval. Toho můžeme docílit tak, že si nad těmito objekty vytvoříme interface, jehož implementace bude v aplikačních projektech. Protože tato funkcionalita je poměrně častá, můžeme využít již existující řešení. Nejrozšířenější knihovnou je open source framework MvvmCross [13]. Ten nejenže zastřešuje funkcionality jako geolokace, práce s obrázky nebo s barvami, ale i navigaci mezi obrazovkami. Je založen na návrhovém vzoru MVVM, umožňující oddělit business logiku aplikace od vzhledu. Rozhodli jsme se tedy použít MvvmCross Framework využívající návrhový vzor MVVM, který bude popsán v následující kapitole.
2.6 Návrhový vzor MVVM a MvvmCross Framework MVVM (Model View ViewModel) [14] je návrhorvý vzor pocházející od firmy Microsoft. Má za cíl lépe oddělit vzhled aplikace od ostatních vrstev. Vznikl jako variace na vzor Presentation Model [15] od Martina Fowlera a je inspirován vzorem MVC (Model View Controller) [16]. Vzor MVVM rozděluje architekturu aplikace na tyto části:
Model – Označuje doménový model, který obsahuje data i tzv. business logiku aplikace.
View – Odpovídá uživatelskému rozhraní.
ViewModel – Vrstva abstrahující uživatelské rozhraní pomocí výčtu vlastností a akcí. Poskytuje přístup k datům z modelu a zajišťuje interakční logiku aplikace.
Binder – Spojuje data a akce z View s ViewModelem pomocí tzv. Data Bindingu. V kontextu Microsoft technologií tomu odpovídá jazyk XAML a je tedy implicitní součástí aplikace.
20
Obrázek 8: Návrhový vzor MVVM
Pokud bychom srovnávali tento vzor se vzorem MVC, pak vrstvy View a Model jsou totožné. Místo kontroleru, který řídí komunikaci mezi View a Modelem, je zde ViewModel a Data Binding. Hlavním přínosem MVVM je způsob oddělení UI kódu takovým způsobem, že není třeba psát explicitní kód na propojení View s daty ve ViewModelu – o to se stará Data Binding. ViewModel tedy neví nic o View který ho používá, ale změny v něm jsou propagovány pomocí bindingu do View. Stejně jsou změny ve View přeneseny do ViewModelu. Původně byl tento návrhový vzor využíván pouze v technologii WPF, později se začal používat v dalších technologiích od Microsoftu využívajících XAML jako např. Silverlight nebo WinRT (včetně vývoje pro Windows Phone). Nyní se používá např. i v JavaScript frameworku KnockoutJS. Na iOS je doporučeno vyvíjet aplikace pomocí vzoru MVC, který má širokou podporu v systému a jeho komponentách. Na Androidu není přesně dané, o jaký návrhový vzor se jedná a záleží, jak budeme chápat koncept tzv. aktivity, který má odpovídat jedné aktivitě uživatele. Z
návrhového
vzoru
MVVM
vychází
i
námi
zvolený
MvvmCross
Framework [13]. Cílem tohoto frameworku je umožnit psát pomocí MVVM i na platformách
Xamarin.Android
a
Xamarin.iOS,
které
MVVM
standardně
nepodporují. Jedná se o open source Framework vyvíjený především vývojářem jménem Stuart Lodge. Využívá techniku sdílených PCL projektů a umožňuje strukturovat aplikaci takovým způsobem, aby velká část logiky aplikace byla v tomto projektu. Hlavní myšlenka je taková, že obrazovky jednotlivých platforem si víceméně odpovídají obsahem a logikou, která je s nimi spojená a liší se hlavně ve vzhledu. Budeme mít tedy pro každou obrazovku jeden ViewModel, který může být součástí PCL. Na každé plaformě pak pouze naimplementujeme odpovídající View a 21
pomocí bindingu, který taktéž implementuje MvvmCross, napojíme tyto View do ViewModelu. Sám autor MvvmCross o tomto konceptu mluví jako o vylepšeném vzoru MVVM a pojmenovává ho zkratkou MVX – Mvvm Cross Platform.
Obrázek 9: Vzor MVX
MvvmCross tedy nabízí nástroje pro psaní multiplatformních ViewModelů. Dále poskytuje nástroje pro vytvoření základní infrastruktury aplikace jako je společná inicializace
při
startu
aplikace
nebo
možnost
používat
princip
tzv.
Dependency Injection. Jedná se o způsob programování, kdy si objekty sami nevytvářejí závislosti mezi sebou, ale předávají si je (např. pomocí konstruktoru). S tím souvisí pojem IoC Container, což je objekt, který se stará právě o vytváření objektů a jehož vlastní implementaci MvvmCross poskytuje. Důležitou součástí MvvmCross je mechanismus pluginů. Myšlenka je taková, že uživatel nemusí v každé aplikaci chtít pracovat např. s geolokací nebo obrázky. Proto jsou tyto jednotlivé funkcionality oddělené od hlavního projektu pomocí pluginové architektury. Tato architektura je podobná jako architektura samotného projektu a skládá se ze sdílených PCL projektů obsahující abstrakce a knihoven implementující tyto abstrakce na jednotlivých platformách. Pokud bychom chtěli naimplementovat vlastní plugin, musíme dodat PCL, která abstrahuje funkcionalitu tohoto pluginu pomocí interfaců a abstraktních tříd a pro každou platformu musíme dodat jednu knihovnu implementující tuto funkcionalitu na dané platformě. Uživatelova aplikace 22
se typicky bude skládat také z jednoho PCL projektu a několika projektů cílových platforem odkazujících se na jeho PCL. Použití je pak takové, že uživatel si přidá odkaz na PCL pluginu obsahující abstrakce do svého PCL projektu a navíc si přidá odkaz z každého projektu platformy na platformě specifickou knihovnu pluginu. Výhodou je, že uživatel pak s touto funkcionalitou pracuje ve formě abstrakce již jen ve svém PCL projektu a dosáhne většího množství sdílení kódu. Kromě Xamarin.Android a Xamarin.iOS podporuje MvvmCross i platformu Mac, Windows Phone, Windows a částečně i WPF. Framework není přímo závislý na technologii Xamarin, která je potřeba, jen pokud chceme podporovat Android nebo iOS.
2.7 Podpora různých verzí cílových platforem Dalším faktorem ovlivňující vývoj aplikace je volba verzí operačních systémů, které budeme podporovat. Novější verze operačních systémů přinášejí nová rozhraní, která nemusí být podporovaná na starých verzích. Další problém, který je třeba vzít v úvahu, je podpora tabletů. Naše aplikace je primárně určena pro chytré telefony, ale rozdíly mezi tablety a telefony se postupně smazávají a jediným rozdílem může být velikost displeje. Naše rozhodnutí je podporovat aplikaci i na tabletových zařízeních, ale nebudeme věnovat větší snahu jejich optimalizaci. Co se týče verzí, nejhorší je situace na Androidu. Různí výrobci telefonů používají vlastní upravené verze Androidu, a pokud vyjde nový Android, jsou uživatelé závislí na výrobci, zda jim doručí update s touto verzí, což může být se zpožděním nebo vůbec. Na stránkách developer.android.com [17] je tabulka ukazující rozložení verzí v dubnu 2015 z dat získaných z obchodu Google Play.
23
Obrázek 10: Rozložení Android verzí (duben 2015) [17]
Z těchto dat je patrné, že nejnižší verze, kterou má smysl podporovat je Gingerbread. Avšak i podíl této verze každým měsícem klesá. Proto jsme se rozhodli podporovat primárně verzi IceCream a výše, přičemž po uvedení aplikace do obchodu Google Play můžeme znovu zvážit pracnost podpory verze Gingerbread a případně ji doplnit. Android má podporu tabletů již od API level 11, tudíž naše aplikace by měla běžet i na všech tabletech. Na Androidu je zpětná kompatibilita řešena především pomocí tzv. Support Libraries. Ty umožňují používat funkcionalitu dostupnou v novějších verzích Androidu i ve starších verzích. Navíc se můžeme za běhu zeptat na aktuální verzi Androidu a podle toho volat kód specifický pro danou verzi. Na iOS je situace jednodušší. Apple umožňuje většině svých modelů přechod na nejnovější verzi. Nejnižší iPhone podporující nejnovější verzi 8 je iPhone 4S a co se týče tabletů, podporu nejnovější verze dostaly všechny tablety iPad 2 a výše. Přikládáme graf opět z dubna znázorňující rozložení verzí iOS z dat v App Store na stránkách Applu [18].
24
Obrázek 11 - Rozložení iOS verzí (duben 2015) [18]
Z grafu nám vyplývá, že má smysl podporovat verzi iOS 7 a výše. Opět máme možnost v kódu kontrolovat aktuální verzi. Při vývoji aplikace navíc máme možnost určit, zda je aplikace určená i pro tablety. My tuto možnost povolíme. Na Windows je díky volbě WinRT rozhraní volba jednoduchá. Jediná verze mobilního systému, který běží na tomto rozhraní je Windows Phone 8.1. Ten díky automatickému upgradu z Windows Phone 8.0 běží na většině telefonů. Komplikovanější je situace s tablety. Tabletům odpovídají zařízení běžící na Windows 8.1, aplikace tedy půjde pustit i na desktopových Windows. Tam ale nemusí být podpora pro dotykové ovládání, avšak většina komponent umí fungovat i bez toho. Pokud chceme vyvíjet i pro Windows 8.1, Visual Studio nabízí způsob, jak jednoduše sdílet většinu kódu aplikace. Jedná se o použití sdíleného projektu popsaném v kapitole 2.5. Ten umožňuje sdílet i View ve formě XAML kódu. Bohužel ne všechny komponenty existují na obou platformách. Protože na tablety je kladen menší důraz, budeme kód psát do sdíleného projektu, ale View s platformě specifickými komponentami budeme definovat jenom ve Windows Phone 8.1. Takto bez jakékoliv námahy získáme projekt spustitelný i na klasickém PC, který ale nebude kompletní a může sloužit k rychlému testování aplikace. Dalším důvodem, proč neudržovat kompletní Windows 8.1 aplikaci, je způsob, jakým bude řešen přechod na budoucí Windows 10. Tam bude celá aplikace jen v jednom projektu, jehož rozhraní má být více podobné současnému Windows Phone 8.1 a tedy bychom stejně komponenty z Windows 8.1 projektu nepoužili.
25
Rozhodnutí tedy je podporovat primárně Windows Phone 8.1, udržovat projekt Windows 8.1 jen pro testování a v budoucnu podporovat tablety až s příchodem Windows 10.
2.8 Volba lokálního úložiště Jedním z typických požadavků na aplikaci je persistence lokálních dat při vypnutí aplikace. Tento problém se týká i naší aplikace a přímo ze specifikace vyplývá, že budeme muset uchovávat alespoň tyto data:
Nákupní seznamy – Musí fungovat částečně bez připojení, tedy musíme si ukládat celé seznamy se všemi položkami včetně informací jako je počet kusů, zda je položka koupená atd.
Zobrazení tutoriálu – Tutoriál se má ukázat při prvním spuštění, proto si musíme někde pamatovat, zda již uživateli tutoriál příště neukazovat.
Přihlašovací údaje – Pokud bychom nutili uživatele přihlašovat se při každém spuštění aplikace, pak by používání aplikace bylo značně nepohodlné.
Lokace a filtry – Uživatel si může zvolit, kde nakupuje a jaké položky ho zajímají (např. jen ve slevě). Toto nastavení musí být také uloženo lokálně na telefonu. Každá z platforem má přístup k lokálnímu souborovému systému, který
přidělí aplikaci místo, kam může číst a zapisovat. Protože ukládání dat je častá operace, poskytuje každá platforma vestavěný mechanismus na ukládání dat ve formě
klíč-hodnota.
Konkrétně
na
Androidu
jsou
k dispozici
tzv.
SharedPreferences, na iOS existuje NSUserDefaults a na Windows je to ApplicationDataContainer. Druhou možností jak ukládat data je použít lokální SQLite databázi [19]. Jedná se o minimalistickou knihovnu ukládající data do jednoho souboru. Vyžaduje nulovou kofiguraci, implementuje téměř celý standard SQL-92 a je implementovaná na všech platformách.
26
Další možností je ukládat si data přímo do souboru a spravovat je ručně. To se může hodit např. pro ukládání strukturovaných dat ve formě XML nebo JSON nebo pro ukládání obrázků. Při volbě první varianty bychom se nechtěli zabývat implementačními detaily vyjmenovaných úložišť a můžeme například použít Settings Plugin pro Xamarin [20] zastřešující implementace na všech platformách pod jedním rozhraním. Při použití SQLite můžeme použít knihovnu sqlite-net [21], která taktéž poskytuje multiplatformní rozhraní. Pro jednotnou práci se se soubory můžeme použít File Plugin pro MvvmCross nad kterým je postaven DownloadCache plugin, který poskytuje logiku cachování stažených dat, hlavně obrázků. Ke každé volbě úložiště tedy existuje multiplatformní řešení, a proto by mělo být jejich použití jednoduché. Otázkou zůstává, který typ úložiště je nejvhodnější pro naši aplikaci. Pro jednoduchá data, jako právě informace, zda uživateli ukazovat tutoriál, by bylo nejvhodnější vestavěné úložiště. Na druhou stranu potřebujeme ukládat nákupní seznamy spolu s jejich položkami a na to nám struktura klíč-hodnota nestačí a pro tento účel by byla nejvhodnější SQLite databáze. Ukládání do souboru se jeví jako velmi pracné, může být však vhodné pro cachování obrázků. Bylo by však nepraktické, ukládat část informací do databáze a část do nastavení, proto jsme se rozhodli, ukládat veškerá data kromě obrázků do SQLite databáze a tedy spravovat pouze jedno úložiště. Pro cachování obrázků použijeme DownloadCache MvvmCross plugin, které používá ukládání do souborů, avšak správa těchto souborů je součástí implementace pluginu, proto nebudeme muset se soubory explicitně pracovat.
2.9 Možnosti zobrazení map Další z požadavků je dát uživateli možnost zobrazit si nejbližší prodejny na mapě a spustit navigaci k dané prodejně. Právě použití map je jednou z klíčových funkcionalit, které odlišují mobilní zařízení od desktopových počítačů, kde není 27
kladen takový důraz na aktuální polohu uživatele. Z tohoto důvodu má každý ze systémů v sobě zabudovaný mechanismus pro práci s mapami. V případě map musíme také učinit několik rozhodnutí. První otázkou je, zda pro zobrazení prodejen na mapě použijeme externí aplikaci nebo přidáme mapy přímo do aplikace. Na každé platformě však nemusí být dostupné jenom jedny mapy, např na iOS můžeme použít vestavěné mapy od Applu nebo použít Google mapy. Druhou otázkou tedy je, jaké mapy použijeme na konkrétních systémech. Nyní podrobněji probereme jednotlivé možnosti. Pokud bychom chtěli mapy zobrazit v externí aplikaci, pak všechny tři systémy tuto funkcionalitu poskytují. Typicky stačí zavolat systémovou metodu se specifickým URL obsahující informace, které chceme zobrazit v mapové aplikaci. Tento přístup má však několik nevýhod. Jedním problémem je malá možnost konfigurace map. Externí aplikace umí často zobrazit jen jeden bod nebo naplánovat cestu mezi dvěma body. Dalším problémem je samotný fakt, že uživatel je přesměrován do externí aplikace a to může odvést jeho pozornost od naší aplikace. Naopak vestavěné mapy můžeme umístit přímo do naší aplikace a pomocí mapových rozhraní máme možnost jejich široké konfigurace. Můžeme např. přepínat mezi nočním a denním režimem, přepnout na satelitní mapu a především můžeme vykreslit libovolný počet bodů na mapě. Pro naši aplikaci je tedy výhodné, pokud zobrazíme obchody spolu s naší pozicí na mapě, která bude uvnitř naší aplikace. Samotnou navigaci k prodejně ovšem provedeme pomocí externí aplikace, která má již navigaci mezi dvěma body implementovanou. Co se týče dostupnosti jednotlivých mapových technologií na jednotlivých platformách, situace je následující. Na Androidu se nejčastěji používají Google Maps. Ty jsou předinstalované jako aplikace na téměř všech Android zařízeních. Mapy můžeme používat pomocí rozhraní, které je dostupné pomocí platformy Google Play Services. Tato platforma je také dostupná na téměř všech telefonech a stačí ji přilinkovat do naší aplikace. Poté máme Google Mapy dostupné jako komponentu v naší aplikaci. Na stránkách 28
s dokumentací k Xamarinu [22] je detailně popsané, jak používat Google Maps na platformě Xamarin.Android. Popis zahrnuje například získání unikátního klíče pro fungování map, který se získá registrací na Googlu. Na serveru Android Central [23] jsou vypsané alternativy ke Google Maps, mezi něž patří např. HERE mapy s vlastním SDK pro Android. Avšak uživatelé Androidu jsou nejvíce zvyklí na Google Maps a díky jejich velké integraci do systému nemá smysl uvažovat nad alternativou. Na iOS se až do roku 2012 používaly výhradně Google Maps. S příchodem iOS 6 ovšem Apple představil vlastní technologii Apple Maps. Na tu se zpočátku snesla vlna kritiky kvůli nepřesnostem, pomalé odezvě nebo špatné navigaci. Tyto problémy však již nejsou v novějších verzích tak časté a Apple Maps jsou plnohodnotnou alternativou k mapám od Googlu. Výhodou Apple Maps je především fakt, jsou dostupné pomocí knihovny Map Kit, která je přímo součástí systému. Navíc Apple mapy jsou předinstalované na nových zařízeních a uživatelé jsou na ně více zvyklí. Z tohoto důvodu a kvůli lepší integraci do systému jsme se rozhodli na iOS použít mapy přímo od Applu. Při použití map na Windows Phone 8.1 jsou mapy dostupné pomocí komponenty Map Control, která odpovídá Bing mapám. Ty jsou zabudované v samotném SDK pro Windows Phone 8.1. Je zde i možnost použít např. Google mapy, které se ovšem musí používat přes komponentu prohlížeče a kód na konfiguraci map psát v JavaScriptu. Větší smysl dává tedy použít nativní komponentu Map Control. Tato komponenta je jednou z komponent, které nejsou dostupné na desktopových Windows 8.1, proto je budeme implementovat pouze ve verzi pro Windows Phone 8.1. Na každé platformě jsme tedy zvolili nativní mapy. Problém je, že rozhraní těchto map jsou tak rozdílné, že nelze jednoduše vytvořit abstrakci, která zastřeší celkovou práci s mapami. Pokud budeme chtít tedy na mapě zobrazit kolekci několika obchodů, pak si budeme muset napsat vlastní mechanismus, který se bude starat o zobrazování těchto bodů na každé platformě. To bude odpovídat implementaci Binderu popsaném v kapitole 2.6 o MVVM.
29
2.10 Návrh implementace serverové části Stěžejní funkcionalitou aplikace je komunikace se serverovou částí. Musíme vymyslet a naimplementovat způsob, jakým bude serverová část předávat data mobilnímu klientu. V této kapitole bude nejprve popsána architektura existujícího webového projektu a bude popsán způsob, jakým začleníme tuto část do existující architektury projektu. Následně vybereme technologii, pomocí které budeme novou část projektu implementovat. Samotná implementace většiny serverové části však bude ponechána na jiného člena týmu a nebude součástí odevzdaného řešení.
2.10.1 Začlenění do projektu Matylda Nejprve stručně popíšeme architekturu původního webového projektu, která se od odevzdání projektu téměř nezměnila a je zobrazena na následujícím diagramu.
Obrázek 12: Architektura systému Matylda
Architektura se dělí na několik vrstev, přičemž každá vrstva může komunikovat s vrstvami pod sebou, jak znázorňují šipky. Nejnižší vrstva obsahuje relační databázi MS SQL a NoSQL databázi Solr. Nad databází je vrstva Domain zajišťující mapování databázových entit z relační databáze do doménových modelů. Podobně SolrLib zajišťuje komunikaci s databázi Solr. Nad těmito vrstvami je postavená 30
nejdůležitější vrstva z hlediska fungování a to vrstva Business Services. Ta zajišťuje veškerou logiku aplikace, pracuje s modely z obou databází a poskytuje rozhraní skrývající implementační detaily nižších vrstev. Až nad touto vrstvou je samotná webová aplikace psaná v ASP.NET MVC. Kromě webové aplikace využívá vrstvu Buisness Services např. služba Job Scheduler, která plánuje úkoly na pozadí a běží odděleně od webové aplikace. Naším cílem je poskytnout přístup k aplikaci pomocí rozhraní, které bude možné konzumovat mobilním klientem. Současná implementace to sama o sobě neumožňuje, protože webová aplikace vrací data pouze ve formě HTML pro webové prohlížeče. Nyní máme dvě možnosti, jak tuto funkcionalitu doplnit. První možnost je rozšířit existující webovou aplikaci tak, aby poskytla navíc rozhraní, které půjde snadno zpracovat mobilním klientem. Druhou možností je vytvořit samostatnou webovou službu oddělenou od webového projektu a pracující nad vrstvou Buisness Services. Výhodou implementace již do existujícího projektu je především nulová konfigurace a jednoduchost implementace, kdy místo dat ve fromě HTML budeme vracet data ve formátu JSON nebo XML. Naopak nevýhodou je závislost na webovém projektu. To může být problém, pokud např. opravíme malou chybu a budeme muset nasazovat celý webový projekt. Tuto nevýhodu odstraňuje řešení samostatného projektu. Ten může fungovat nezávisle na webovém projektu, ale za cenu počáteční investice do konfigurace projektu. My dáme přednost škálovatelnějšímu řešení a to samostatné webové službě. Z logického hlediska se jedná o službu, která má trochu jinou funkci než webová aplikace, a proto jsme se rozhodli ji oddělit.
2.10.2 Volba technologie Nyní vybereme vhodnou technologii, pomocí které naimplementujeme webovou službu. Abychom mohli používat vrstvu Business Services, měla by být zvolená technologie postavená nad .NET frameworkem. V úvahu přicházejí dvě technologie a těmi jsou WCF [24] a Web API [25].
31
WCF je unifikovaný model pro implementaci aplikací orientovaných na služby. Podporuje různé protokoly jako HTTP, TCP nebo UDP. Je založený na protokolu SOAP, ale podporuje také REST služby. Při použití SOAP protokolu můžeme popsat webovou službu pomocí jazyku WSDL z něhož si klientská aplikace může vygenerovat proxy rozhraní, které může být snadno konzumováno. Web API je novější framework, který je ideální pro implementaci REST služeb. Podporuje pouze HTTP protokol, je vhodný pro konzumaci z různých prohlížečů nebo mobilních klientů. Umožňuje vybudovat službu podporující různé typy internetového média včetně XML nebo JSON. Na rozdíl od WCF je Web API open source. Pro naše účely se zdá být vhodnější technologie Web API. Nechceme budovat architekturu orientovanou na služby a ani nevyužijeme podporu jiných protokolů než HTTP. Výhodou SOAP služeb by mohlo být vygenerování proxy rozhraní na klientské aplikaci, avšak tuto možnost Windows Phone 8.1 aplikace nepodporují. Naší volbou je tedy použít pro implementaci technologii Web API, která bude oddělená od existující webové aplikace a bude postavená nad vrstvou Buisness Services existujícího projektu, jako je znázorněno na následujícím diagramu:
32
Obrázek 13: Upravená Architektura Matyldy
Do architektury webového projektu jsme se rozhodli zařadit ještě jeden projekt, který bude využívat přímo náš Web API projekt. Je jím projekt Contracts a je typu Portable Class Library. Díky tomu může být tento projekt používán jak z WebApi projektu, tak přímo z mobilní aplikace, a získáme možnost sdílet kód mezi mobilní a serverovou částí.
3 Implementace V minulé kapitole jsme analyzovali hlavní technologie, které použijeme při implementaci mobilní aplikace. V této kapitole nejprve popíšeme architekturu výsledné aplikace a poté popíšeme řešení dílčích problémů jako je např. implementace procházení letáků nebo přihlašování.
33
3.1 Základní architektura Na základě použitých technologií byla navržena architektura mobilní aplikace. Zde je diagram znázorňující její hlavní části, které budou následně popsány.
Obrázek 14: Celková architektura aplikace
Architektura se skládá z několika vrstev. Každému tmavě modrému obdélníku odpovídá jeden projekt. Šipky znázorňují, které vrstvy spolu přímo komunikují. Protože jsme se rozhodli použít MvvmCross Framework, vychází naše architektura z návrhového vzoru MVVM aplikovaného na více platforem. Všechny projekty od Core dolů (včetně Core) jsou sdílené mezi platformami. Jednotlivé části budeme popisovat od těch nejnižších, což jsou vrstvy na komunikaci se serverovou částí až po samotné projekty platforem. 34
3.1.1 Vrstvy webového klienta O komunikaci se serverem se starají tři projekty – WebApiClient, DataClient a AccountClient. Nejprve jsme uvažovali, že celá komunikace se serverem by mohla být v hlavním sdíleném Core projektu. Pokud bychom se však rozhodli napsat další aplikaci, která bude komunikovat s naším serverem, nemohli bychom tuto část jednoduše použít. Z tohoto důvodu jsme se rozhodli ji oddělit. Samotné dotazování na server řeší vrstva WebApiClient. Tato vrstva je teoreticky znovupoužitelná v jakémkoliv projektu, kterým se budeme dotazovat na náš server nezávisle na typu dat, které si posíláme. Nejdůležitější třída je MatyldaHttpClient,
která umožňuje posílat HTTP dotazy a to pomocí metod jako
jsou GET a POST. Data posíláme jako JSON a tato třída se stará zároveň o jejich serializaci a deserializaci, aniž bychom však znali typy objektů, které posíláme. To je umožněno tím, že tyto metody jsou psané genericky a díky knihovně Newtonsoft.Json, která umí serializovat .NET typy do JSON. Nad touto vrstvou jsou postavené dvě vrstvy a to AccountClient a DataClient. AccountClient se stará o autentizaci a autorizaci uživatele a všechny operace s tím související, jako je odhlašování, změna hesla nebo získávání údajů o uživateli. Oproti tomu DataClient se stará o veškerou ostatní komunikaci, která je specifická pro doménu aplikace. Tyto dvě vrstvy jsme se opět rozhodli od sebe oddělit, protože samotná autentizace a autorizace může být častým požadavkem, který by se mohl hodit i v jiných potencionálních aplikacích. Pokud bychom se naopak přihlašovat nechtěli, bude nám stačit používat pouze projekt DataClient pro získávání různých dat ze serveru, ke kterým má přístup nepřihlášený uživatel. Obě tyto vrstvy mají za úkol sestavit URL cílového dotazu (včetně tzv. query stringu obsahující parametry dotazu), zvolit HTTP metodu (POST nebo GET) a zavolat vrstu WebApiClient s cílovým typem, do kterého se mají data deserializovat. Návratovým typem volání z těchto vrstev je typ
HttpResult
obsahující výsledný
objekt získaný ze serveru, případně seznam chyb. Zbývá zajistit, aby data odeslaná ze serveru odpovídala typům, do kterých deserializujeme na klientovi. Toho dosáhneme tak, že veškerá data, která si 35
vyměňujeme, umístíme do projektu Contracts. Tento projekt je dostupný ve webové i mobilní části. Tyto data odpovídají objektům DTO (Data transfer objects), které slouží právě k přenosu dat mezi aplikacemi. Velkou výhodou tohoto přístupu je, že tyto modely spravujeme jen na jednom místě a máme jistotu, že jsou vždy synchronizované. Protože je naše webová služba založená na architektuře REST, najdeme různé zdroje (např. produkty nebo letáky) pod různými URL adresami. Každou tuto adresu musíme explicitně uvést na serveru i na klientovi, proto i tyto adresy budou součástí projektu Contracts a budou tedy sdílené. Protože implementace velké části serverového Web API projektu byla přenechána jiným členům týmu, rozhodli jsme se, že bude efektivnější, když i samotná implementace části tříd datových klientů bude implementována stejným člověkem. Tato implementace se skládá pouze z vytvoření cílového URL a předání správných typů pro serializaci a deserializaci. Díky tomuto způsobu vývoje odpadne velké množství komunikace, kdy by si členové týmu předávali informace, jako např. které URL patří ke které metodě. Navíc člověk implementující obě části bude mít možnost si výsledný kód ihned otestovat. Konkrétně se jedná o implementaci devíti tříd v projektu Data.Client, které mají na prvním řádku komentář “//Implemented by {Jméno autora}” a o třídu
ReflectionUtility
v projektu WebApi.Client, která
obsahuje pomocnou logiku na vytvoření URL. Na zbytku implementace aplikace se již žádná další osoba mimo autora aplikace přímo nepodílela.
3.1.2 Databázová vrstva V části 2.8 o lokálním úložišti jsme se rozhodli, že budeme používat databázi SQLite. Nyní si popíšeme, jak budeme tuto databázi používat v našem projektu a jak zapadá do celkové architektury systému. Nejprve musíme vyřešit, jakým způsobem budeme k databázi přistupovat. Jednou z možností je použít technologii ADO.NET. V tom případě bychom museli ručně psát SQL dotazy, které jsou zpracovány SQLite databází. My však použijeme robustnější řešení a to již zmíněnou knihovnu sqlite-net [21]. Ta obsahuje mimo jiné ORM Framework, který umožňuje namapovat tabulky v databázi na objekty našich modelů. Dále obsahuje metody na vykonávání jednoduchých CRUD operací. Stejně 36
je možné dotazovat se do databáze za použití LINQ dotazů a to vše s typovou kontrolou. Pro vytvoření spojení do databáze slouží třída
DbInitializerBase.
Ta má mimo
jiné abstraktní metodu GetDbPath pro získání cesty do databáze. Tato cesta je však platformě závislá, proto každá platforma obsahuje vlastní implementaci této metody. O správu připojení do databáze a vytváření tabulek se stará třída kromě
DbInitializerBase
používá třídu
DbDataInitializer
DbContext,
která
pro naplnění dat po
vytvoření. Abychom od aplikace ještě více odstínili přístup k databázi, rozhodli jsme se veškeré interakce s databází zabalit do tzv. repositářů. Konkrétně se jedná o implementace
rozhraní
IRepository
TProperty>,
implementace nám zpřístupní základní operace s entitou typu klíčem typu
TProperty.
jejíž
TModel,
generická a primárním
Stejně pro asynchronní (neblokující) dotazy do databáze
slouží podobné rozhraní IAsyncRepository
TProperty>.
Samotná databázová vrstva není přímo závislá na dalších částech aplikace, přesto jsme se ji pro jednoduchost rozhodli nechat v projektu Core sdružující veškerou sdílenou logiku aplikace. Pokud by vznikla potřeba používat tuto část i v jiné aplikaci, pak by oddělení databázové vrstvy do jiného projektu mělo být jen o přesunutí výše popsaných typů.
3.1.3 Vrstva služeb Tato vrstva obsahuje hlavní logiku aplikace. Hlavní částí této vrstvy je sada rozhraní. Ty můžou poskytovat nějakou obecnou funkcionalitu, jako např. získávat aktuální verzi aplikace, nebo můžou zastřešovat práci s entitami typu produkt nebo kategorie. Fakt, že se jedná o rozhraní a ne o implementaci, je tady zvlášť důležitý a to především ze dvou důvodů. Pokud potřebujeme službu např. na zjištění aktuální verze aplikace nebo pro otevření externích map, pak implementace těchto vrstev musí být přímo na projektu platforem a nemůžeme tedy v Core projektu pracovat přímo s implementací. Druhý důvod souvisí s životním cyklem vývoje. Pokud implementujeme novou obrazovku, pak potřebujeme zobrazit data, která získávám právě z této vrstvy. Problém je, že často tyto data ještě serverová část neposkytuje. 37
V tom případě naimplementujeme tzv. demo službu, která vrací ručně vytvořená falešná data a funkcionalitu reálné služby pouze simuluje. Tímto způsobem získáme rychlý zdroj dat, který je odstíněn od nižších vrstev a případných chyb vzniklých při komunikaci mezi klientem a serverem. Dalším pojmem úzce související s touto vrstvou jsou tzv. doménové entity. Jedná se o veškeré modely, s kterými tato vrstva pracuje a které zpřístupňuje vyšším vrstvám. Tyto modely jsou však oddělené od DTO modelů přicházejících ze serveru, přestože jsou často totožné. Toto rozhodnutí bylo učiněno z několika důvodů:
Ne všechny modely pochází ze serveru a bylo by nepraktické mít část modelů definovanou ve vrstvě Contracts a část ve vrstvě služeb.
Modely se můžou lišit. Ze serveru nám může přijít geografická poloha obchodu, ale v aplikaci chceme pracovat již jen se vzdáleností od uživatele.
Vrstvy můžou být implementovány a spravovány nezávisle na sobě. Pokud bychom používali DTO modely i ve vrstvě služeb, pak každá změna těchto modelů by se propagovala až na server, což by mělo za následek velkou závislost těchto částí na sobě.
Toto oddělení vrstev se velmi osvědčilo při vývoji, kdy jsme mohli mít kompletně hotovou danou funkcionalitu, jejíž data zajišťovaly demo implementace služeb pracujících pouze s doménovými entitami. Poté jsme mohli naimplementovat napojení těchto dat na server, které ale již pracovalo s jinými modely a mohlo být vytvořeno naprosto nezávisle. Závislost mezi těmito vrstvami vzniká v momentě, kdy začneme modely jedné vrstvy
mapovat na druhé.
K tomuto
účelu byl původně použit
nástroj
Automapper [26], který ulehčuje mapování entit mezi sebou na základě jmenných konvencí. V průběhu vývoje se bohužel ukázalo, že inicializace mapování při použití tohoto nástroje trvá např. na telefonu iPhone 4S až 2,5 vteřiny, což bylo velmi znát na době startu aplikace. Rozhodli jsme se proto mapování naimplementovat ručně. Pro převod mezi doménovými entitami a DTO modely proto slouží třída DtoToEntityMappingEngine.
38
Protože jsme databázovou vrstvu neoddělovali od vrstvy Core, rozhodli jsme se pro ORM mapování použít taktéž vrstvu doménových entit. Modely, které zároveň používáme pro mapování do databázových tabulek, vždy implementují rozhraní IEntityWithTypedId<TId>,
které jako generický parametr dostávají datový typ
primárního klíče. Typická metoda vrstvy služeb tedy může fungovat následovně: 1. Získá data z databázové vrstvy pomocí repositářů. 2. Převede modely, které chce poslat na server do vrstvy DTO pomocí mapování. 3. Zavolá vrstvu datových klientů a asynchronně počká na odpověď ze serveru, která obsahuje opět DTO. 4. Převede získané DTO opět na doménové entity, které vrátí volajícímu.
3.1.4 ViewModely Všechny doposud popsané vrstvy zapadají v rámci vzoru MVVM do části model. Celá tato část by stejně tak mohla být použitá i v rámci jiného vzoru jako je MVC. Navíc bychom mohli používat volání vrstvy služeb již přímo na každé platformě. V tom případě by pak na každé platformě muselo být velké množství kódu, které plní komponenty na jednotlivých obrazovkách, reaguje přitom na vstupy od uživatele a při tom se volá do vrstvy služeb. Velkou nevýhodou je fakt, že tento kód by byl na jednotlivých obrazovkách každé platformy velmi podobný a opakující se. My ale díky použití vzoru MVVM a MvvmCross frameworku máme možnost tento problém odstranit definováním ViewModelů, které jsou součástí sdíleného Core projektu. Každý ViewModel tedy logicky odpovídá jedné obrazovce. Toto dělení nelze vždy přímo uplatnit, protože na jedné obrazovce můžou být komponenty jako dialogy nebo tzv. taby, které rozdělují obrazovky na několik podčástí. Pak každá tato podčást může mít take svůj ViewModel. Dalším problémem je fakt, že na každé platformě mohou být obrazovky trochu odlišné. ViewModely tedy musí být 39
dostatečně univerzální, aby pokryly odlišnosti platforem. V případě velkých odlišností definujeme na různých platformách různé ViewModely. Hlavním úkolem ViewModelu je zprostředkovat interakční logiku a poskytnout data na dané obrazovce. Každý ViewModel obsahuje sadu vlastností odpovídající položkám
a
akcím
INotifyPropertyChanged,
ve
View
a
implementuje
standartní
rozhraní
pomocí kterého může vyvolat událost, která notifikuje View
o změně stavu dané vlastnosti. Naše ViewModely navíc dědí od typu
MvxViewModel,
který poskytuje MvvmCross a stará se o životní cyklus ViewModelu. Podobně jako u vrstvy služeb máme doménové entity, tak na vrstvě ViewModelů máme tzv. observable objects. Jedná se o všechny modely, které chceme zobrazovat ve View a neodpovídají celému ViewModelu. Typickým příkladem je jedna položka nějakého listu, např. položka nákupního seznamu. Tu chceme např. umět zaškrtávat jako koupenou přímo z vrstvy ViewModelu, tedy musí implementovat
INotifyPropertyChanged,
ale zároveň neodpovídá celé obrazovce. Ve
ViewModelu odpovídajícímu obrazovce nákupního seznamu potom budeme držet kolekci těchto objektů. Hlavním důvodem proč jsme pro tyto typy objektů nepoužili přímo doménové entity je ten, že z principu by doménové entity neměli řešit notifikaci do View a tedy implementovat
INotifyPropertyChanged.
Dalším důvodem
je fakt, že ve View občas chceme zobrazovat data trochu odlišné od doménových entit. Tyto objekty jsou mapovány z doménových a mapování je definováno ve třídě EntityToObservableMappingEngine.
Následující obrázek ilustruje mapování mezi
všemi vrstvami modelů definovaných v naší aplikaci.
40
Obrázek 15: Mapování modelů
V téměř každém ViewModelu pracujeme s dvěma důležitými systémovými typy. Tím prvním je
ObservableCollection.
Jedná se o typ kolekce, který
vyvolává události při akcích jako je přidání do kolekce nebo odebrání. Tyto události může poslouchat View a reagovat na ně. Druhým typem je implementace z MvvmCross
MvxCommand.
Tento typ
ICommand
zpřístupňuje
a jeho
akce
na
ViewModelu. Pro každou akci, kterou chceme používat na View definujeme vlastnost typu
MvxCommand,
které předáme samotnou akci jako delegát. Hlavní důvod,
proč nezveřejňujeme přímo samotné metody je ten, že s rozhraním
ICommand
umí
pracovat Data Binder. S touto vrstvou částečně souvisí ještě jeden pojem a tím jsou tzv. konvertory. Jedná se o třídu, která implementuje metody Convert a ConvertBack. Hlavním účelem konvertorů je transformace dat na ViewModelu do dat zobrazitelných na View a zpět. Existují dva typy konvertorů. Prvním jsou konvertory, které pomáhají transformovat data do typů specifických pro cílovou platformu a jsou tedy implementované
až
na
MvxVisibilityValueConverter.
platformě.
Příkladem
Ten převádí hodnotu typu
může bool
být
např.
na platformě
specifickou hodnotu odpovídající typu viditelnosti. Dalším typem jsou konvertory, které používáme nezávisle na typu platformy. Příkladem může být náš vlastní konvertor typu
CountToQuantityTextValueConverter,
počtu kusů na string ve formátu "5
ks".
který převádí číslo odpovídající
ViewModel tedy nemusí řešit, jak přesně se
data zobrazí. 41
Cílem je, aby ViewModel byl co nejjednodušší a tedy pro získání dat používá přímo vrstvu služeb a nikdy ne vrstvu serverových klientů nebo databázových repositářů. Typický ViewModel může mít např. tyto úkoly:
Získává data při inicializaci z vrstvy služeb.
Mapuje získaná data na vrstvu observable objects a zobrazuje je uživateli.
Reaguje na události od uživatele pomocí vlastností typu ICommand.
V reakci na události namapuje objekty zpátky do doménových entit a zavolá se do vrstvy služeb.
V reakci na události řeší navigaci na další ViewModely.
3.1.5 Android projekt Díky volbě Xamarinu má náš Android projekt podobnou strukturu, jako bychom ho psali nativně. Popíšeme tedy stručně jen hlavní části, které hrají důležitou roli z hlediska naší architektury. Centrálním bodem jsou tzv. aktivity odpovídající uživatelským akcím. Dále jsou zde tzv. App Resources. Ty se skládají z dodatečných souborů, které obsahují položky jako obrázky, řetězce, animace a především definice layoutu aplikace. Layout lze psát v kódu nebo právě pomocí XML, jehož elementy a atributy odpovídají View komponentám. K aktivitě se potom pomocí metody SetContentView přiřadí tento soubor, v kterém je definovaný vzhled. Komponentám v layout souboru můžeme přiřadit unikátní identifikátor. Po přiřazení layoutu do aktivity můžeme tuto komponentu pomocí metody FindViewById najít a manipulovat s ní. Částí View v návrhovém vzoru MVVM tedy pro nás na Androidu bude aktivita spolu s jejím layout souborem. Hlavní otázkou nyní je, jak spojit data z ViewModelu s komponentami na View. Přímočarým řešením by bylo v každé aktivitě získat instanci odpovídajícího ViewModelu
a
reagovat
INotifyPropertyChanged
na
jeho
událost
PropertyChanged
z rozhraní
a podle toho plnit data komponent. Tento způsob by však
byl velmi pracný s velkým množstvím opakujícího se kódu. K tomuto účelu by však 42
měla sloužit komponenta vzoru MVVM DataBinder, jehož implementaci MvvmCross poskytuje. Mámě na výběr dva způsoby, jak definovat data binding. Prvním způsob je použít fluent API přímo v kódu aktivity jak ukazuje následující obrázek: var passwordEditText = FindViewById<EditText>(Resource.Id.PasswordEditText); var emailEditText = FindViewById<EditText>(Resource.Id.EmailEditText); var confirmButton = FindViewById<Button>(Resource.Id.ConfirmButton); var set = this.CreateBindingSet(); set.Bind(passwordEditText).To(viewModel => viewModel.Password); set.Bind(emailEditText).For(view => view.Text).To(viewModel => viewModel.UserEmail); set.Bind(confirmButton).To(viewModel => viewModel.LoginCommand); set.Apply(); Obrázek 16: Fluent API binding
Tato část kódu nám zaručí, že obě komponenty typu EditText budou propagovat změny do odpovídajících vlastností na ViewModelu a opačně. Navíc po kliknutí na tlačítko se spustí akce definována vlastností LoginCommand. Všimněme si, že jsme nemuseli vždy uvádět pro jakou vlastnost na View se binding definuje. Každý typ má ale zaregistrovanou defaultní vlastnost, konkrétně pro pro
Button
je to vlastnost typu
ICommand,
EditText
je to vlastnost Text a
která se spustí na událost Click. Pokud
bychom navíc chtěli data dosazované do View nějak transformovat, můžeme využít metodu WithConversion, která dostane instanci konvertoru. Druhým způsobem, jak definovat data binding, je použít speciální atribut v XML souboru layoutu. Např. EditText odpovídající heslu v předchozím případě by byl definován takto: <EditText android:id="@+id/PasswordEditText" android:layout_width="match_parent" android:layout_height="wrap_content" android:password="true" android:inputType="textPassword" local:MvxBind="Text Password" /> Obrázek 17: Binding XML layout
Nejdůležitější je poslední řádek, kde definujeme dvojici vlastnost na View a vlastnost na ViewModelu, mezi kterými se má vytvořit binding. Pokud bychom chtěli pomocí bindingu spojit více vlastností, oddělíme je středníkem. MvvmCross 43
navíc nabízí mechanismus, pomocí kterého si můžeme vytvořit na komponentě vlastní binding a přiřadit mu libovolný řetězec. Problém druhého způsobu bindingu je ten, že ztrácíme typovou kontrolu a o špatně definovaném bindingu se dozvíme až za běhu. Na druhou stranu je tento způsob zápisu mnohem úspornější a přehlednější, proto se budeme snažit definovat binding primárně v layout souboru. Více informací o bindingu na všech platformách lze nalézt na stránkách MvvmCross [27]. Další důležitou částí z hlediska architektury jsou implementace služeb specifických pro Android. Ty jsou ve složce Services a poskytují implementace rozhraní definovaných v Core projektu. Registrace těchto služeb do IoC Containeru probíhá ve třídě Setup. Jedním z důležitých mechanismů na Androidu je používání tzv. adaptérů. Ty spojují data aplikace spolu s jednotlivými View a zároveň jsou zodpovědné za tvorbu těchto View [28]. Typickým příkladem je komponenta
ListView,
která vykresluje
kolekci elementů. Abychom jí dodali data, musíme naimplementovat vlastní adaptér, který v sobě bude mít logiku získávání dat a vytváření View pro jednotlivé položky. Výhodou tohoto přístupu je, že bázový systémový adaptér, od kterého můžeme náš adaptér dědit, v sobě má již logiku např. na stránkování a správu paměti při skrolování. MvvmCross dodává vlastní adaptéry, které navíc dokážou např. reagovat na události z kolekce
ObservableCollection,
jako je přidání nebo odebrání
položky a vykreslovat View, do kterých se opět můžeme bindovat. Za zmínku ještě stojí tzv. fragmenty, které představují část aktivity. Byly představeny ve verzi Honeycomb a umožňují vytvářet dynamičtější vzhled právě rozdělením aktivit do fragmentů. Stejně jako aktivita, i fragment může mít svůj vzhled definovaný v layout souboru a proto i tuto část budeme z hlediska MVVM chápat jako View. MvvmCross poskytuje bázovou třídu pro fragmenty MvxFragment, do které se můžeme bindovat.
3.1.6 iOS projekt Podobně jako na Androidu jsou aktivity, na iOS pracujeme s tzv. View Controllery. Jejich hlavním úkolem je spravovat hierarchii View aplikace, kterou 44
musí prezentovat uživateli. Dále zajišťují např. adaptivitu komponent pro různé velikosti displeje. Naproti tomu View komponentám odpovídají třídy poděděné od
UIView.
Ty
představují hlavní objekty, s kterými uživatel komunikuje. Mezi jejich zodpovědnosti patří např. definice chování změny své velikosti vůči svému rodičovskému View, správa svých podčástí, získávání dotykových událostí, kreslení svého obsahu do obdélníkové oblasti a další. I zde máme dvě hlavní možnosti jako definovat View aplikace. První je vytvořit view hierarchii přímo v kódu Controlleru. Častější variantou je však definice vzhledu v nástroji Xcode Interface Builder [29]. Jedná se o editor, kde si pomocí metody drag and drop vytvoříme vzhled celé komponenty. Výsledkem je potom soubor s příponou NIB, v kterém je definován celý layout. Na rozdíl od Android layout souboru, však nelze tento soubor jednoduše ručně editovat. Pomocí nástroje Assistant, lze v Interface Builderu vytvářet tzv. outlety, které odpovídají proměnným v daném layoutu. Xamarin potom z každého outletu vygeneruje C# vlastnost do partial class dané komponenty, na kterou se můžeme v kódu odkazovat. Vrstvou odpovídající View v návrhovém vzoru MVVM pro nás na iOS bude Controller spolu se svým View definovaným přímo v kódu nebo v NIB souboru. Data Binding zde MvvmCross řeší stejně jako na Androidu, jen s tím rozdílem, že na iOS můžeme použít pouze Fluent API přímo v kódu. Na Androidu jsme si odkaz na danou komponentu získali pomocí
FindViewById
a na iOS můžeme použít přímo
instanci definovanou v Controlleru nebo můžeme vzít vlastnost, vygenerovanou z outletu v NIB souboru. Zbývá rozhodnout, zda budeme definovat View v kódu nebo pomocí Interface Builderu. Hlavní problém s Interface Builderem je ten, že k jeho používání potřebujeme MAC. To může být nepraktické, pokud chceme udělat drobnou úpravu a nechceme kvůli tomu MAC připojovat. Naopak psaní vzhledu v kódu má hlavní nevýhodu v tom, že po každé úpravě musíme aplikaci spustit, abychom viděli změny. My se rozhodli definovat vzhled všech View přímo v kódu a jenom vzhled položek kolekcí definujeme v Interface Builderu. Důvod tohoto rozhodnutí je ten, že hlavní
45
View se můžou často měnit, proto nám nevadí počáteční investice při vytváření vzhledu v kódu. Naopak položky kolekcí by měly zůstat většinu času neměnné. Stejně jako na Androidu definujeme platformě specifické služby ve složce Services a jejich registraci ve třídě Setup.
3.1.7 Windows projekt Hlavní komponentou na platformě Windows jsou tzv. stránky (Pages), kde každá odpovídá jedné obrazovce a skládá se ze třídy poděděné od
Page
a ze souboru
s příponou XAML, kde je definován vzhled v jazyce XAML. I na Windows máme možnost definovat vzhled přímo v kódu, ale protože komponenty definované v XAML přímo odpovídají těm v kódu a můžeme se na ně v kódu jednoduše odkazovat, pak nemá smysl psát layout jinak než v jazyce XAML. Na Windows se jako hlavní návrhový vzor používá MVVM a jako View je chápána celá stránka včetně XAML souboru a stejně to bude platit i pro naši aplikaci. Pro napojení dat do ViewModelu již existuje vestavěná implementace data bindingu, tedy nemusíme používat implementaci z MvvmCross. Používají se zde vlastnosti typu
DependencyProperty,
které si drží hodnotu proměnné a poslouchají na změnu
hodnot ve View, které lze propagovat do ViewModelu. Binding je nejjednodušší definovat přímo v jazyce XAML. Např. pro komponentu s heslem vypadá kód takto: Obrázek 18: XAML binding
Dalším důležitým bodem z hlediska architektury je uspořádání Windows projektů. Jak jsme již zmínili v kapitole 2.7, na platformě Windows používáme sdílený projekt, do kterého vkládáme veškerý kód spustitelný na platformách Windows 8.1 i Windows Phone 8.1, který se při kompilaci přilinkuje k danému projektu. Tím získáme projekt spustitelný i na platformě Windows 8.1, který ale nebude obsahovat ty View, které obsahují komponenty dostupné jen na telefonech. Tento projekt odevzdáváme jako součást řešení, avšak slouží jen pro testovací účely a ne všechny obrazovky jsou funkční.
46
Windows poskytuje vlastní mechanismus konvertorů, které implementují rozhraní
IValueConverter.
Toto rozhraní však není dostupné v Core projektu, proto
pokud chceme využívat konvertory definované v Core, musíme vytvořit jejich nativní wrapper poděděním od generické třídy
MvxNativeValueConverter,
kde T
je typ konvertoru z Core. Služby projektu jsou ve složce Services jako na ostatních platformách, avšak zde můžeme rozlišovat i služby pro Windows 8.1 a WindowsPhone 8.1. Třída
Setup
je
však pouze jedna ve sdíleném projektu, a pokud chceme pro každou platformu zaregistrovat jinou službu, použijeme direktivy kompilátoru jako na Obrázek 6. Autor projektu je zároveň autorem všech odevzdaných zdrojových souborů, které jsou součástí popsaných projektů. Výjimku tvoří již zmiňované soubory ve vrstvě webového klienta. Dále to jsou všechny třídy, které mají v Summary komentáři řádek “Taken From {URL}”. Jedná se především o několik komponent na Androidu. Všechny přeložené knihovny, na které se v projektech odkazujeme, jsou knihovny třetích stran.
3.2 Platformě specifické UI komponenty Dosažení platformě specifického vzhledu na všech platformách sebou nese jeden větší problém. Tím je fakt, že všude musíme vybrat vhodné komponenty, které si víceméně odpovídají. Pokud například chceme zobrazit obyčejný text pro čtení, tak na Androidu použijeme
TextView,
na iOS
UILabel
a na Windows Phone
TextBlock.
Podobně najdeme velké množství těchto elementárních komponent, které si odpovídají svojí funkcionalitou. Volbu komponent, které řeší konkrétní problém (např. komponenta na prohlížení letáků), budeme řešit až v rozboru těchto problémů. V této části se zaměříme na řešení dvou komponent, které prostupují celou aplikací a výrazně ovlivňují vzhled. Jedná se především o zobrazení záložek a dialogů. Uvažujme následující problém, který si ilustrujeme na příkladu obchodů. Chtěli bychom mít obrazovku, kde bude rozcestník všech řetězců, zároveň seznam nejbližších prodejen a mapa těchto prodejen. Z hlediska sdíleného Core projektu tedy budeme mít jeden rodičkovský ViewModel, který má tři ViewModely jako podčásti. Otázkou nyní je, jak tyto data zobrazíme na jednotlivých platformách. Každá 47
platforma však poskytuje komponentu, která umožňuje horizontálně přecházet mezi jednotlivými podčástmi. Nyní si jednotlivé komponenty rozebereme. Na Androidu existuje komponenta
ViewPager,
která si drží kolekci fragmentů.
Tato komponenta umožňuje přecházet mezi fragmenty pomocí gesta horizontálního přejetí prstem (horizontal swipe). Aby bylo ihned zřejmé, že lze použít toto gesto, musíme zároveň vykreslit pro každý fragment jednu záložku s titulkem tohoto fragmentu. K tomu lze použít komponentu třetí strany jako např. ViewPagerIndicator [30] nebo přímo zabudované záložky na systémové komponentě zvané ActionBar. Ta zobrazuje horní pruh s nadpisem, tlačítkem zpět a s uživatelskými akcemi. My tuto komponentu používáme, proto záložky implementujeme přímo na ní a to pomocí metody AddTab. Nyní musíme vyřešit, jak nabindovat data z jednotlivých ViewModelů na fragmenty v komponentě ViewPager. MvvmCross poskytuje bázovou třídu pro fragmenty, do níž se můžeme bindovat, zvanou
MvxFragment.
Ta má mimo
jiné vlastnost DataContext, která očekává instanci ViewModelu. Abychom naplnili ViewPager,
předáme mu adaptér typu
kolekci typu
FragmentInfo,
MvxViewPagerFragmentAdapter,
který v sobě drží
jenž obsahuje typ fragmentu dané položky a instanci
ViewModelu. Samotný adaptér pak pouze v metodě GetItem vytvoří tento fragment a přiřadí mu ViewModel. Toto řešení je dostačující, pokud máme daný neměnný počet záložek. Na stránce s nákupními seznamy potřebujeme dynamický počet záložek, kdy chceme mít záložku pro každý nákupní seznam a to tento adaptér neumožňuje a ani jsme nenašli existující implementaci. Proto jsme si tento adaptér naimplementovali
sami
a
Hlavním rozdílem oproti
pojmenovali
MvxBindableViewPagerFragmentAdapter.
MvxViewPagerFragmentAdapter
událostí jako je přidání nebo odebrání v kolekci ViewModelu.
Abychom
měli
synchronizovány
naimplementovali jsme potomka tohoto adaptéru
je možnost poslouchání
ObservableCollection
záložky
na
na
obrazovce,
MvxTabViewPagerFragmentAdapter,
který zároveň s přidáváním nebo odebíráním fragmentů mění i jejich odpovídající záložky. Použití je pak velmi jednoduché. Stačí si v aktivitě vytvořit instanci adaptéru, předat ji do
ViewPager
a pomocí fluent API nabindovat vlastnost
ItemSource adaptéru na kolekci ViewModelů, které zobrazujeme. Na iOS existuje k tomuto účelu tzv. Tab Bar Controller, který drží kolekci View Controllerů, kde každý odpovídá jedné záložce. MvvmCross i zde obsahuje 48
implementaci tohoto Controlleru zvanou
MvxTabBarViewController,
která umožňuje
binding na ViewModel. Vytvoření záložek pak odpovídá vytvoření View Controllerů typu
IMvxTouchView,
které se dosadí do
MvxTabBarViewController.
Tím získáme
obrazovku se záložkami, do kterých se lze bindovat. Z důvodu odlišných UI principů jsme se rozhodli neimplementovat nákupní seznamy pomocí záložek, proto nemusíme řešit situaci s jejich dynamickým počtem. Hlavním rozdílem oproti mechanismu na Androidu je ten, že na iOS se Tab Bar Controller chápe jako rozcestník pro celou aplikaci. Bývá často součásti úvodní obrazovky aplikace, která umožňuje navigaci v rámci jedné záložky, kdy jsou úvodní záložky stále viditelné, ale můžeme se zanořovat v rámci kterékoliv z nich. Pokud bychom přistoupili na tento koncept, pak bychom museli navrhnout iOS aplikaci naprosto jinak než na ostatních platformách, proto budeme tyto záložky chápat stejně jako ty na Androidu. Na Windows Phone 8.1 je situace nejjednodušší. Existuje zde komponenta Pivot,
která sama o sobě podporuje binding. Ta obsahuje kolekci typu
PivotItem,
kam přímo pomocí bindingu dosadíme jednotlivé ViewModely. Celá tato část může být psána přímo v jazyce XAML. počtem položek typu
PivotItem,
Pivot
umí sám o sobě pracovat s dynamickým
proto můžeme tuto komponentu použít i na nákupní
seznamy. Jediným problémem je, že Pivot je jedna z komponent, která není dostupná na Windows 8.1, proto všechny obrazovky s touto komponentou nebudou na této platformě implementovány. Na následujícím obrázku obrazovky s obchody a letáky je výsledný vzhled na všech platformách.
49
Obrázek 19: Záložky - zleva Android, iOS, WP 8.1
Dialogy budeme chápat obrazovky, které se zobrazí jen v kontextu dané stránky a nenaruší uživatelskou zkušenost z průchodu aplikací. Dialogy budeme dělit do dvou typů. Prvním je dialog, který má za cíl získat od uživatele data a druhý pouze zobrazuje jednoduchá upozornění, případně dává na výběr ze dvou možností. Nejprve probereme dialog, který získává data od uživatele, a tedy může mít složitější vzhled a vlastní ViewModel. Na Androidu existuje třída AlertDialog, která umožňuje zobrazit dialog přes část obrazovky. Tento dialog spravuje třída
DialogFragment,
což je fragment na dané
aktivitě. Abychom v dialogu mohli bindovat data do ViewModelu, existuje v MvvmCross implementace MvxDialogFragment. Na iOS se spíše než dialog zakrývající část obrazovky k tomuto účelu používá tzv. Modal View. Od klasického View se liší např. animací při navigaci, kdy vyjíždí odspodu a ne ze stran, jako ostatní obrazovky. V implementaci dialogu tedy není žádný rozdíl jako při implementaci jiného View, pouze Modal View musíme správně zobrazit a to pomocí metody PresentViewController na Controlleru. Na Windows existuje třída
Flyout,
která má podobné využití jako Modal View
na iOS a my ji využijeme pro zobrazení dialogů. Pokud chceme vyvolat
Flyout,
musíme uvést existující komponentu, nad kterou se má zobrazit. Teoreticky totiž 50
můžeme zobrazit
Flyout
nad více komponentami najednou, což se dá lépe využít na
Windows 8.1, kde je tato komponenta také dostupná. Zde je příklad výsledných dialogů pro nastavení filtrů na všech platformách.
Obrázek 20: Dialogy - zleva Android, iOS, WP 8.1
Pro zobrazení jednoduchých upozornění nebo potvrzovacích dialogů jsme se rozhodli využít plugin pro MvvmCross UserInteraction [31]. Protože každá platforma má standartní mechanismus na zobrazení upozornění, může být tato funkcionalita abstrahována, což přesně dělá tento plugin. Upozornění pak můžeme používat přímo v kódu ViewModelu a máme zaručeno, že se objeví na obrazovce každé platformy. Bohužel se v průběhu vývoje ukázalo, že defaultní implementace na platformách není příliš modifikovatelná a tedy jsme na Androidu a na Windows Phone 8.1 poskytli vlastní implementaci rozhraní, které poskytuje plugin. Na Androidu jsme totiž potřebovali změnit defaultní barvu dialogu a na Windows Phone 8.1 chyběla implementace metody, která očekávala vstup od uživatele, kterou jsme dodali. Zde je ukázka tohoto dialogu na všech platformách.
51
Obrázek 21: Alert Dialogy - zleva Android, iOS, WP 8.1
3.3 Navigace aplikací Každý systém má svůj mechanismus umožňující navigaci mezi obrazovkami. Např. na Androidu existuje tzv. Intent, který spouští jednotlivé aktivity. Na iOS je zásobník View Controllerů a na Windows je komponenta Frame, která spravuje objekty typu Page. Naším cílem je, aby navigace aplikací mohla být řízena z Core projektu a tedy byla na jednom místě. MvvmCross poskytuje základní abstrakci ve formě metody ShowViewModel. Při jejím zavolání a předání cílového typu ViewModelu v generickém parametru se zajistí, že se na každé platformě vytvoří nové View, kterému se přiřadí daný ViewModel. Např. na Androidu se vytvoří nová aktivita. Druhou metodou je metoda
Close(IMvxViewModel viewModel),
která dostane
instanci ViewModelu a způsobí, že se aktuální obrazovka zavře. Na Androidu by to např. znamenalo ukončení aktuální aktivity. Tyto dvě metody však pokrývají jen základní případy užití. V průběhu vývoje se objevili případy, které nešlo naimplementovat pomocí těchto metod. Jednalo se např. o tyto problémy:
Navigace na dialog a zavření dialogu.
52
Odstranění první obrazovky z historie. To potřebujeme při stisknutí tlačítka zpět na hlavní obrazovce, kdy se nechceme dostat zpět na tutoriál.
Návrat o dvě obrazovky zpět.
Návrat na hlavní obrazovku a smazání všech ostatních v historii.
Implementace některých komponent souvisejících s navigací se můžou v různých aplikacích lišit. Příkladem může být implementace dialogů, kdy záleží na vývojáři, co bude vlastně považovat za dialog. Z tohoto důvodu nelze ani abstrahovat všechny výše popsané případy, proto se je ani MvvmCross nesnaží implementovat. Nabízí však mechanismus, pomocí kterého můžeme vstoupit do procesu navigace a provést vlastní logiku, která bude již závislá na platformě. Jedná se o tzv. View Presentery.
Metoda
ShowViewModel
obsahuje
několik
přetížení
s parametrem typu IMvxBundle. Do tohoto objektu si můžeme uložit libovolná data ve formě klíč-hodnota. Na každé platformě pak ve třídě
Setup
zaregistrujeme vlastní
implementaci třídy poděděné od platformě specifického View Presenteru. Např. na Androidu je to
MvxAndroidViewPresenter.
V této třídě je metoda
Show,
v které si
můžeme získat uložený objekt IMvxBundle a provést vlastní navigační logiku. Uvedeme si to na příkladu. Na ViewModelu chceme metodu, která na každé platformě zobrazí dialog. Naimplementujeme proto v Core projektu metodu ShowDialogViewModel,
přidá do
IMvxBundle
která zavolá
ShowViewModel a
navíc
příznak, že navigujeme na dialog. Potom na Androidu ve třídě
MatyldaAndroidViewPresenter
kontrolujeme, zda nám přišel tento příznak. Pokud ano,
pak si získáme instanci ViewModelu, vytvoříme DialogFragment, kterému předáme instanci ViewModelu a dialog zobrazíme. Na Windows postupujeme obdobně, jenom vytvoříme objekt typu
Flyout
a na iOS zajistíme, že se View patřící
k předanému ViewModelu zobrazí jako Modal View. Stejně postupujeme i u ostatních případů kdy manipulujeme z historií View na dané platformě. Pokud např. chceme z historie odstranit všechny View kromě hlavní stránky, pak na Androidu musíme nastavit komponentě Intent správné příznaky, na iOS manipulujeme se zásobníkem Controllerů a na Windows se zásobníkem na objektu Frame. 53
Pokud navíc všechny tyto akce přesuneme do bázového ViewModelu, od kterého dědí všechny naše ViewModely, pak získáme opětovně použitelný navigační mechanismus dostupný v celé aplikaci.
3.4 Procházení letáků Procházení letáků daných obchodníků musí být maximálně uživatelsky přívětivé, proto na tuto funkcionalitu máme následující požadavky:
U každého letáku chceme vidět seznam miniatur všech jeho stránek.
Po stisknutí libovolné miniatury se dostaneme na detail stránky letáku.
Stránku chceme umět přibližovat a oddalovat.
Pohybem prstu do stran se chceme dostat na předchozí a následující stránku.
Z těchto požadavků vyplývá, že chceme naimplementovat funkcionalitu odpovídající procházení fotek v galerii. Budeme tedy potřebovat dva ViewModely. První bude držet seznam miniatur a akcí na proklik a druhý bude držet seznam stránek ve velkém měřítku. Druhý ViewModel (LeafletPageDetailListViewModel) navíc musí řešit logiku stahování jednotlivých stránek, protože si nemůžeme dovolit stáhnout např. 40 stránek letáku najednou. Uživatel si totiž může prohlédnout pouze první dvě stránky a navigovat se zpět. Tento problém jsme vyřešili tím způsobem, že pomocí bindingu získáváme aktuální index prohlížené stránky a stahujeme nejbližší dvě stránky s vyšším a dvě s nižším indexem. Tím jsme přemístili veškerou logiku stahování do sdíleného projektu. Nyní si popíšeme, jaké komponenty jsme zvolili na jednotlivých systémech. Na Androidu existuje komponenta
Gallery,
která je však od API level 16
označená jako zastaralá a nemusela by být v budoucnu podporována. Jako alternativu lze
opět
použít
BindableViewPager,
ViewPager.
která používá
My
použijeme
již
existující
MvxBindablePagerAdapter,
implementaci
což je podobný typ
adaptéru jaký jsme implementovali pro nákupní seznamy, ale nepracuje s fragmenty. Tím získáme možnost nabindovat stránky letáku v kterých můžeme listovat. Zbývá dořešit layout stránky, včetně její přibližování a oddalování. Každá stránka se bude 54
bindovat na vlastnost ViewModelu
IsLoaded
toho bude ukazovat
Na Androidu bohužel není žádná systémová
ProgressBar.
určující zda je leták načtený a podle
komponenta, která by implementovala přibližování a oddalování obrázků. Na serveru GitHub [32] však existují implementace rozšiřující
ImageView
pro Android. My
budeme používat komponentu TouchImageView [33] přepsanou pro Xamarin.Android. Jedinou drobnou úpravu, kterou jsme provedli, je zakázání přechodu na další stránku letáku, pokud má uživatel aktuální stránku přiblíženou. Na Windows je řešení podobné. Seznam stránek letáků, mezi kterými lze listovat drží komponenta
FlipView.
Ani zde není komponenta pro přibližování
obrázků, proto si ji musíme naimplementovat sami. Existuje však komponenta ScrollViewer,
do které když umístíme obrázek, pak budeme mít přibližování a
oddalování zajištěné. Jediné co nám zbývá, je implementace gesta dvojitého poklepání na obrázek. To způsobí, že se obrázek maximálně přiblíží nebo oddálí. Toho dosáhneme vytvořením třídy IBehavior,
ZoomableScrollViewerBehavior
implementující
kterou použijeme na náš obrázek. Toto rozhraní pochází z Behaviors SDK
(XAML) a umožňuje nám vytvářet znovupoužitelný kód aplikovatelný na naše komponenty přímo v jazyce XAML. Na závěr probereme řešení procházení letáků na iOS. Problém implementace této komponenty na iOS se ukázal jako jeden z nejtěžších problémů v rámci celé aplikace. Ani zde neexistuje jednoduchá systémová komponenta. Nabízí se proto možnost využít komponentu
UIScrollView
zobrazující data větší než jedna
obrazovka. Hlavní problém této komopnenty je, že nepodporuje binding a ani MvvmCross neposkytuje žádnou vhodnou implementaci. Navíc většina návodů jak dosáhnout podobné funkcionality je demonstrována v jazyce Objective-C nebo Swift, tedy bychom museli řešit další problémy související s převodem do C# kódu. Na iOS však existuje v Objective-C několik plně funkčních implementací galerií ve formě knihoven. Nejvhodnější funkcionalitu poskytuje knihovna MWPhotoBrowser [34]. Ta se stará o samotné stahování, cachování a především procházení fotek, přičemž volitelně umožňuje zobrazit obrazovku s náhledy, přesně jako naše řešení na
55
iOS a Windows. Bohužel v době hledání knihovny1 jsme nenašli žádnou její konverzi pro Xamarin a proto jsme se rozhodli udělat tuto konverzi sami. Stáhli jsme si tedy knihovnu v jazyce Objective-C, provedli lokalizace několika textů do češtiny a změnili barvu pozadí. Poté jsme podle návodu na stránkách Xamarinu [35] provedli konverzi této knihovny do knihovny pro Xamarin.iOS, kterou můžeme používat v naší aplikaci. Stručně se jedná o tyto kroky: 1) Vytvořili jsme Makefile nad původní knihovnou, jehož výstupem byl tzv. Fat Binary soubor, který obsahuje kód pro x86 i ARM procesory. 2) V Xamarin studiu jsme vytvořili Xamarin.iOS Binding projekt. Ten dostane soubor z minulého kroku a obsahuje např. definice rozhraní, pomocí kterého budeme ke knihovně přistupovat, a poté vygeneruje výsledné DLL. 3) Použili jsme nástroj příkazové řádky Objective Sharpie od Xamarinu, který z hlavičkových souborů původního projektu vygeneruje C# soubor obsahující definici rozhraní knihovny. 4) Nakopírovali jsme soubor z minulého kroku do souboru ApiDefinition.cs v Binding projektu. Tento soubor obsahoval duplikáty a špatné definice, takže ho bylo nutné dodatečně upravit. 5) Xamarin.iOS Binding projekt po přeložení vygeneroval DLL soubor, na který se již odkazujeme v našem projektu. Problém tohoto řešení je ten, že pro procházení stránek letáků nemůžeme použít naše existující ViewModely, protože knihovna si řeší jejich funkcionalitu sama a ani nemůžeme použít
binding.
Vytvořili
LeafletSimplePageListViewModel,
jsme
proto
další ViewModel typu
který má za úkol pouze získat URL k obrázkům
všech náhledů stránek letáků a k samotným obrázkům stránek. V odpovídajícím View na iOS potom pouze předáme tyto URL naší komponentě, která se o jejich zobrazování postará sama.
1
Před odevzdáním práce jsme zjistili, že Xamarin vytvořil 15. 11. 2015 konverzi této
komponenty a poskytl ji ke stažení. V tuto chvíli jsme již nestihli řešení předělat, navíc tato knihovna pravděpodobně nebude lokalizovatelná.
56
3.5 Geolokace O získávání geologačních dat se stará služba
LocationService
implementovaná
v Core projektu. Ta využívá MvvmCross objekt typu IMvxLocationWatcher, který umí poslouchat na změnu lokace. Tento objekt je součástí Location pluginu MvvmCross a je implementován na každé platformě, kde používá rozhraní pro práci s geolokací. Třída
LocationService
navíc periodicky posílá na server pozici a server jí vrací
nejbližší město odpovídající této pozici. Toto město se pak ukládá do lokální databáze. Uživatel si navíc může zvolit vlastní město, kde nakupuje. Údaje o aktuální pozici a o nejbližším nebo zvoleném městu se posílají s většinou dotazů na server, který si již sám vyhodnotí, jaká data má uživateli poslat. Např. uživateli v Praze nebude posílat slevy platné jen v Liberci.
3.6 Implementace map V kapitole 2.9 jsme provedli volbu jednotlivých map. Nyní se zaměříme na konkrétní implementaci, především na způsob bindování dat z ViewModelu do map. První problém, který potřebujeme vyřešit je následující. Na ViewModelu chceme držet kolekci obchodů, které se budou zobrazovat na mapě. Chceme zařídit, aby při manipulací s touto kolekcí (např. přidání nebo odebrání položek), se tato změna projevila i na mapě cílové platformy. Ke každému obchodu navíc chceme na mapě zobrazit informace jako název, logo nebo vzdálenost. K tomuto účelu jsme na Androidu vytvořili třídu MarkerSet, která dostane instanci mapy a obsahuje vlastnost
IEnumerable ItemsSource,
do které
můžeme nabindovat kolekci z ViewModelu. Zároveň tato třída poslouchá na změny této kolekce a podle toho přidává nebo odebírá body z mapy. Abychom mohli na mapě rozkliknout položku, potřebujeme mapě předat adaptér implementující GoogleMap.IInfoWindowAdapter.
Z tohoto důvodu jsme pro obchody vytvořili adaptér
RetailerMarkerInfoWindowAdapter,
který pro každý model obchodu umí vykreslit
správné okno se všemi informacemi z ViewModelu. Na iOS postupujeme obdobně. Android třídě opovídá naše třída
MarkerSet
MvxMarkerModelAnnotationManager.
57
zde
Ta také dostává
instanci mapy, lze se do ní bindovat a reaguje na události na ViewModelu přidáváním a odebíráním bodů na mapě. Pro zobrazení popisku jednotlivých obchodů jsme na Androidu museli dodat adaptér. Na iOS se však předávání zodpovědností řeší přes tzv. delegáty, konkrétně komponenta mapy očekává delegát typu
MKMapViewDelegate.
MKPinAnnotationView,
Zde v metodě
GetViewForAnnotation
vrátíme objekt typu
obsahující layout daného obchodu na mapě, který se objeví po
rozkliknutí. MKPinAnnotationView však nemá příliš široké možnosti konfigurace, proto na iOS budeme zobrazovat pouze obrázek, titulek a tlačítko na navigaci na detail. Abychom tyto vlastnosti nabindovali na ViewModel, naimplementovali jsme potomka této třídy pojmenovaného
MatyldaBindableMkPinAnnotationView,
kterému
předáme řetězec vyjadřující binding stejně jako v layout souboru na Androidu. Na Windows Phone 8.1 se mapy zobrazují v komponentě jednotlivé body lze zobrazit přidáním MapControl
vlastnost
MapItemsControl
MapControl
do kolekce
Childs
a na
nebo přímo v jazyce XAML jako zanořený element. Ten obsahuje
ItemsSource,
ItemTemplate,
do které můžeme přímo bindovat kolekci bodů a zároveň
do které dosadíme vzhled jednotlivých bodů. Na rozdíl od Androidu a
iOS, kde jsme měli možnost zobrazit dodatečný layout po kliku na špendlík, si tuto logiku musíme naimplementovat sami v rámci layoutu špendlíku. To vyžaduje dodatečnou podporu na ViewModelu, kde si u špendlíku pamatujeme příznak, zda je viditelný a do toho se bindujeme a měníme tuto viditelnost po kliku na špendlík. Po čase se bohužel ukázalo, že MapItemsControl způsobuje úniky paměti, které vedou až k pádu aplikace. Proto jsme použili behavior
PushpinCollectionBehavior,
který
emuluje původní chování. Následující obrázek zobrazuje, jak vypadá detail prodejny na mapě na každém systému.
58
Obrázek 22: Detail prodejny na mapě – zleva Android, iOS, WP 8.1
Druhým problémem je zobrazení aktuální pozice uživatele. Na ViewModelu jsme definovali model, který získává pozici pomocí zpráv z LocationService. Na Androidu a Windows Phone 8.1 jsme tento bond nabindovali podobně jako celou kolekci bodů popsanou výše. Na iOS jsme využili systémové funkcionality, kdy jsme nastavili vlastnost mapy ShowsUserLocation na true a tedy jsme měli ušetřenou práci se zobrazováním layoutu pro pozici uživatele. Posledním problémem je přiblížení mapy po inicializaci. Např. na seznamu prodejen zobrazíme 10 nejbližších prodejen včetně pozice uživatele a potřebujeme, aby se mapa přiblížila tak, že všechny tyto body budou viditelné. Na ViewModelu si proto udržujeme seznam bodů, na které je potřeba se přiblížit. Na každé platformě potom naimplementujeme konvertor, který tyto body převádí do typu, pomocí kterého se umí cílová mapa přiblížit. Na Androidu je to obdélníková oblast LatLngBounds,
na iOS je to
MKCoordinateRegion
určený středem a poloměrem a na
Windows Phone 8.1 je to obdélník typu GeoboundingBox.
59
3.7 Nákupní seznamy a jejich synchronizace Většina entit používaných v aplikaci je pouze pro čtení a jejich získání a práce s nimi je přímočará. Jinak je tomu však s nákupními seznamy. Ty chceme ukládat do databáze, aby mohly fungovat bez připojení. Každý uživatel může mít více nákupních seznamů a každý seznam může obsahovat libovolný počet položek. Uživatel může být vlastník seznamu, mít ho jen pro čtení, nebo pro čtení a modifikaci. U položek rozlišujeme, zda se jedná o produkt, kategorii nebo vlastní textovou položku. Dále si pamatujeme, zda je položka koupená a počet kusů. Seznamy i položky lze přidávat, mazat a dále lze měnit počty položek a názvy celých seznamů. Během práce se seznamy se může uživateli stát, že ztratí připojení, a poté provede akci typu smazání položky, která se smaže pouze z lokální databáze a následně se zase připojí. V tuto chvíli však může být databáze v mobilu nekonzistentní s databází na serveru. Hlavním problémem, který budeme řešit v této kapitole, je způsob, jak udržovat obě databáze synchronizované. Položky i seznamy jsou tedy ve dvou databázích, proto mají dva unikátní identifikátory, jeden pro lokální databázi, druhý pro databázi na server. Při práci s těmito entitami ve vrstvě ViewModelů se od tohoto problému odstíníme a budeme pracovat vždy jen s naším lokálním identifikátorem. Hlavní zodpovědnost tedy bude na vrstvě služeb. Abychom si zjednodušili práci, vybereme operace, které je možno provádět jenom pokud je uživatel připojený. Bude se jednat o přidání, mazání a přejmenování celého seznamu. Nejčastější situací, kvůli které potřebujeme fungování nákupních seznamů bez připojení je ta, kdy uživatel jde do obchodu a pouze potřebuje zaškrtávat koupené položky, případně je mazat nebo upravovat počty kusů. Operace typu přidávání celého seznamu by tedy neměly být tak časté. Logika těchto operací vyžadující připojení bude taková, že nejprve zkusíme provést změnu na serveru a pokud selže, pak nebudeme změnu propagovat ani do lokání databáze a uživateli zobrazíme chybu.
60
Pro ostatní operace si nejprve zavedeme speciální stav u položky nákupního seznamu pojmenovaný ShoppingListItemState, který je třístavový. Položka může být synchronizována, změněna nebo smazána. U položky dále evidujeme informaci, zda se již dopropagovala na server. To zjistíme podle toho, zda má nastavený identifikátor ze serverové databáze. Hlavní myšlenka operací fungujících i bez připojení je taková, že všechny změny budeme nejprve provádět v lokální databázi a poté se je pokusíme provést na serveru. Pokud se operace na serveru nepovede, označíme si tuto položku podle typu operace jako modifikovanou nebo smazanou. V takovém případě by ale uživatel neměl poznat, že došlo k nějakému problému. Samotná synchronizace bude na jednom místě a zajišťovat ji bude metoda SynchronizeAllAsync
třídy
ShoppingListsService.
Její hlavní zodpovědností je uvést
lokální i serverovou databázi do stejného stavu. Tento proces se skládá z těchto kroků: 1) Stáhneme všechny nákupní seznamy s položkami ze serveru i z databáze. 2) Z lokální databáze smažeme celé seznamy, které již nejsou na serveru, a přidáme seznamy, které naopak nemáme lokálně. 3) U seznamů, které jsme již měli lokálně uložené, zkontrolujeme, zda se nezměnilo jejich jméno nebo práva na serveru a případně si je lokálně upravíme. 4) Dále z lokální databáze smažeme všechny položky seznamů, které již neexistují na serveru, ale existují lokálně. Stejně tak smažeme položky, které ještě neexistují na serveru, ale jenom lokálně a jsou ve stavu smazané. 5) Přidáme do lokální databáze položky ze serveru, které nemámě lokálně uložené. 6) Upravíme počty lokálních položek, které jsou synchronizované a liší se od těch serverových. 7) Podle
ShoppingListItemState
rozdělíme nesynchronizované lokální položky
na přidané, modifikované a smazané a najednou je pošleme na server, který si je sám zpracuje. 61
8) Jako odpověď nám ze serveru přijde seznam dvojic, které nově přidaným položkám přiřazují identifikátor do databáze na serveru. Přiřadíme tedy tyto identifikátory a všem položkám nastavíme synchronizovaný stav. Tuto metodu voláme vždy, když uživatel přejde na obrazovku s nákupními seznamy. Pokud pak kdekoliv v aplikaci potřebujeme data jen pro čtení týkajících se seznamů, již je získáme jenom voláním do lokální databáze, nikoliv na server. Pokud by mezitím došlo ke změně na serveru, tak se to aplikace dozví při příští synchronizaci. Veškerá tato netriviální logika je součástí vrstvy služeb a je tedy implicitně sdílená mezi všemi platformami.
3.8 Chybové stavy Nyní popíšeme, jak naše aplikace řeší chybové stavy. Během používání aplikace se může vyskytnout několik druhů chyb. Tou nejčastější je chyba, která nastane při komunikaci se serverem. To může mít hned několik důvodů. Uživatel může náhle ztratit připojení k internetu, server nemusí právě běžet, na serveru nastala chyba nebo může dostat chybovou odpověď (např. při zadání špatného hesla). Všechny tyto stavy musíme umět vyřešit, dát uživateli o chybě vědět a případně dovolit uživateli opakovat akci. V této podkapitole popíšeme, jak se propaguje chyba od nejnižších vrstev komunikujících se serverem až do uživatelského rozhraní. Vrstvy webového klienta by neměly vyhazovat žádné výjimky. Místo toho vrací generický objekt
HttpResult,
který si drží výsledek volání a informace o chybách
a stavový kód HTTP odpovědi. Rozlišujeme obecné chyby ContentErrors,
které lze prezentovat uživateli.
MatyldaHttpClient,
Errors
a chyby
Chyby zachytává přímo třída
která posílá HTTP dotazy na server a to buď z těla odpovědi,
nebo ze stavového kódu HTTP. Vrstva služeb už naopak nebude zveřejňovat chyby jako stavové kódy, ale bude vyhazovat výjimky. Každá tato výjimka dědí od naší bázové výjimky typu ServiceException,
která obsahuje kolekci chyb, které můžeme potenciálně zobrazit
uživateli. Vrstva služeb má tedy za úkol vzít
HttpResult
a na základě jeho
stavového kódu nebo chyb musí vyhodit správnou výjimku poděděnou od ServiceException,
které předá tyto chyby. 62
Výjimky se poté odchytávají až na vrstvě ViewModelů. Pokud by totiž vrstva služeb řešila tyto chybové stavy sama, pak by musela vracet dodatečnou informaci o chybě, aby ViewModel věděl, jak se má zachovat, což by mohlo velmi znepřehlednit kód. Kód ViewModelu, který získává data z vrstvy služeb, musí být uvnitř try bloku a v catch blocích se poté podle typu výjimky rozhodneme, jakou akci provedeme. Nejčastěji však chceme pouze chyby zobrazit uživateli, proto na bázovém ViewModelu máme vlastnost
ObservableCollection<string> Errors.
Díky tomu
můžeme ve View kódu reagovat na změny této kolekce. Další reakcí na chyby ve ViewModelu může být např. navigace na jinou obrazovku, zobrazení upozornění nebo vynulování řetězce do kterého uživatel zadával heslo. Téměř na každé obrazovce může nastat chyba při stahování dat, proto jsme vymysleli opětovně použitelný způsob, kterým je vytvoření speciální komponenty na každé platformě. Ta se bude bindovat přímo do vlastnosti
Errors
na ViewModelu.
Tato komponenta se zobrazí, jen pokud bude alespoň jedna chyba. Bude obsahovat popis chyby, tlačítko „Zkusit znovu“ a volitelně tlačítko, které naviguje uživatele na nákupní seznamy. Toto tlačítko se zobrazí, jen pokud to má v daném kontextu smysl. Navigaci na nákupní seznamy jsme zvolili proto, že nejčastější chybou je výpadek připojení a nákupní seznamy fungují i bez něj. Důsledek těchto tlačítek je ten, že na každém ViewModelu musíme naimplementovat mechanismus, který zkusí načíst data znovu. To je umožněno bindovatelnou vlastností ViewModelu,
který
volá
virtuální
metodu
RefreshCommand
RefreshAsync.
na bázovém
Tu
musíme
naimplementovat na každém ViewModelu, který má požadavek na obnovení dat. Kód této metody je však ve většině případů identický s kódem inicializace ViewModelu.
3.9 Přihlašování Abychom mohli rozlišovat přihlášené uživatele a zpřístupnit jim například jejich nákupní seznamy, musíme být schopni uživatele autentizovat a autorizovat. Na webovém projektu to řešíme přes autentizaci založenou na cookies, která je vhodná pro webové aplikace. Pro naši aplikaci je však vhodnější autentizace založená na tokenech. Naše aplikace navíc bude umět spravovat účet uživatele, stejně jako na 63
webovém projektu. Zde je seznam hlavních požadavků týkajících se autentizace a správy účtů, které bude aplikace umět.
Registrace a přihlášení pomocí emailu a hesla.
Registrace a přihlášení pomocí Facebook nebo Google účtů.
Možnost změnit si heslo.
Obnovení zapomenutého hesla.
Možnost nastavit si více účtů, přes které se uživatel bude přihlašovat.
Nyní stručně popíšeme, jak docílíme především prvních dvou bodů na serverové části a poté rozebereme implementační detaily v naší aplikaci.
3.9.1 Serverová část Hlavní princip autentizace založené na tokenech je ten, že máme autorizační server, kterému pošleme přihlašovací údaje, a on nám zpátky vystaví tzv. access token. Dále máme tzv. Resource Server, kterému posíláme tento token s každým dotazem a on nám zpřístupňuje požadované zdroje [36].
Obrázek 23: Přihlašování pomocí tokenu [36]
Pro nás jsou oba tyto servery obsaženy v našem Web Api projektu. Implementace tohoto mechanismu je především otázkou konfigurace. To obnáší např. definovat koncový bod na serveru, který bude generovat tokeny. Dále musíme vytvořit třídu implementující rozhraní v metodě
IOAuthAuthorizationServerProvider,
GrantResourceOwnerCredentials
vytvoří token. 64
která
na základě obdrženého jména a hesla
Vygenerovaný token je textový řetězec, který má již v sobě zakódované informace o uživateli a jeho právech. Každému tokenu můžeme nastavit dobu platnosti. Pokud nastavíme tuto dobu příliš dlouhou, např. na rok, pak uživatel získá přístup na náš server na celý rok. Problém nastane, kdybychom v průběhu této doby změnili uživatelovi práva, anebo mu znemožnili přístup na server. V tom případě se to server z tokenu nedozví a musel by při každém dotazu dělat dodatečnou kontrolu do databáze. Když naopak zvolíme platnost tokenu příliš krátkou, např. na dvě hodiny, pak máme problém po vypršení. Ve výsledku by to znamenalo, že uživatel by se musel přihlásit znovu, což by zcela jistě odradilo uživatele mobilní aplikace. Tento problém by se dal vyřešit např. zapamatováním si hesla uživatele a opětovným automatickým přihlášením, ale heslo si může změnit i přes webové rozhraní a naše aplikace se o tom nemusí dozvědět. Z tohoto důvodu jsme se rozhodli používat mechanismus tzv. refresh tokenů. Hlavní myšlenkou je, že budeme mít dlouho žijící refresh token a krátce žijící access token. Při přihlášení získáme oba tyto tokeny a těsně než access token vyprší, pošleme na server refresh token a získáme nový access token. K tomuto účelu jsme museli na serveru naimplementovat rozhraní IAuthenticationTokenProvider, které se stará právě o vytváření refresh tokenů a jejich přijímání. Dalším požadavkem je přihlašování přes externího poskytovatele, kterým je v našem případě Facebook nebo Google. Návod na implementaci je uveden např. na serveru Bit Of Technology [37]. Princip je zhruba následující: 1) Klientská aplikace se přes webový prohlížeč naviguje na náš server na speciální koncový bod. 2) Náš server přesměruje uživatele na externího poskytovatele. 3) Uživatel zadá na stránce Google nebo Facebook svoje přihlašovací údaje. 4) Poskytovatel nastaví do cookies token, který obsahuje výstup přihlášení, včetně údajů o uživateli a vyvolá přesměrování zpět na náš server. 5) Na serveru je logika, která podle tokenu od poskytovatele vygeneruje lokální access token a refresh token a vyvolá redirect na URI obsahující tyto údaje. 65
6) Klient detekuje cílové URI a z jeho parametrů si vytáhne oba tokeny a uloží si je. Pokud uživatel ještě nemá účet, bude mu vygenerován jenom dočasný access token. Po dodání svého emailu, který se může lišit od toho získaného od externího poskytovatele, mu bude vytvořen účet. Poté se může opět přihlásit použitím výše uvedených kroků. Avšak v tu chvíli budou v cookies již uloženy údaje od externího poskytovatele a tedy nemusí znovu zadávat přihlašovací údaje a oba tokeny mu budou přímo vráceny.
3.9.2 Klientská část Implementace přihlašování a registrace pomocí jména a hesla je poměrně přímočará a celá je součástí sdíleného kódu. Stačí provést POST na server a v odpovědi se vrátí typ
TokensModel,
který obsahuje oba tokeny a datum expirace
access tokenu. Co se týče obnovování access tokenu, naše řešení před každým dotazem na server ověří, zda již má token vypršet. Pokud ano, tak se klient zavolá na server a pomocí refresh tokenu získá nový token. Tato logika je implementována ve třídě ThreadSafeTokenHandler
a používá zámek, který zajistí, že při více dotazech na server
se nepokusí více vláken zároveň získat nový access token. Abychom nemuseli ručně před každým voláním na server psát kód s touto logikou, implementovali jsme třídu OAuth2BearerTokenHandler.
Tu předáme jako handler třídě
MatyldaHttpClient,
která
implementuje samotné GET a POST dotazy. Díky tomu získáme možnost v metodě SendAsync
handleru vložit kód, který se zavolá před každým dotazem. Tento handler
navíc slouží k tomu, aby před každým dotazem zkusil vytvořit hlavičku HTTP požadavku, do které vloží access token, který nás autorizuje na serveru. Posledním problémem je externí přihlašování. Tam už nám pouze sdílený kód stačit nebude, protože k tomu budeme potřebovat komponentu prohlížeče. Od této komponenty požadujeme následující:
Musí se umět navigovat na dané URL.
Musí umět poslouchat na změny URL. 66
Musí umět pracovat s cookie.
Na každé platformě taková komponenta existuje, na Androidu se jmenuje WebView,
na iOS UIWebView, a na Windows také
WebView.
Abychom přesto převedli co
nejvíce logiky do sdíleného projektu, naimplementovali jsme ViewModel pojmenovaný
ExternalLoginViewModel.
Ten obsahuje počáteční URL, na které se
musí komponenta navigovat. Dále obsahuje vlastnost typu parametr
dostane
aktuální
NavigationChangedCommand.
URL
prohlížeče
a
kterou
ICommand,
jsme
která jako
pojmenovali
Ta zavolá metodu, která kontroluje, zda jsme již obdrželi
tokeny. V tom případě oba tokeny vytáhne z URL a naviguje se na předchozí obrazovku. Na každé platformě jsme tedy jen vytvořili View, ve kterém je komponenta prohlížeče. Tu při inicializaci navigujeme na počáteční URL a při změně URL voláme NavigationChangedCommand.
3.10 Testování Aplikaci jsme v průběhu vývoje testovali na telefonech Lumia 920, iPhone 4s, iPhone 5s, Gigabyte GSmart Roma R2 a na spoustě druhů emulátorů všech platforem. Nyní se však zaměříme na automatické testování pomocí unit testingu. Cílem je vytvářet takové testy, abychom testovali jednotlivé třídy odděleně. Pokud bychom chtěli např. otestovat nějaký ViewModel, tak nechceme kvůli tomu vytvářet celou hierarchii objektů od všech služeb, které tento ViewModel používá, až do objektů z vrstvy klientů, které používají tyto služby. Nejprve si rozebereme, které části lze takto testovat. Všechny hlavní třídy ve vrstvách klientů a služeb jsou psány tak, že veškeré závislosti dostávají do konstruktorů a to ve formě rozhraní, nikoliv implementací. Díky tomu jsou všechny tyto třídy jednoduše testovatelné pomocí testovacích implementací těchto závislostí, nebo použitím knihovny jako je Moq [38], která umí vytvořit tyto implementace přímo v testu. Stejně tak jsou řešené i všechny ViewModely. U těch je ale problém, že nemáme jak otestovat části kódu používající metody typu
ShowViewModel.
Ty totiž pocházejí z předka, definovaném
v MvvmCross knihovně, kde jsou použity další závislosti. I tento problém se dá 67
vyřešit a to stáhnutím knihovny Test od MvvmCross a podědit samotný unit test od třídy z této knihovny [39]. Nad vytvářením View na platformách však nemáme kontrolu, protože si je vytváří samotný systém a tedy nemáme možnost jednoduše předávat do View závislosti. Další problém je ten, že View neobsahuje téměř žádnou logiku, pouze vytváří komponenty, což se testuje nejlépe okem. Vrstvu View tedy nemá cenu automaticky testovat. Ideální by tedy bylo mít na veškerou funkcionalitu ve vrstvách klientů, služeb a ViewModelů napsané testy. Pak by jakýkoliv zásah do kódu, který by nějakým způsobem rozbil otestovanou funkcionalitu, byl odhalen. Pokud se však zaměříme na velkou část funkcionality v těchto vrstvách, tak zjistíme, že se často jedná o jednoduchý kód typu zavolání správné metody na nižší vrstvě, namapování mezivýsledku na správné objekty a vrácení výsledku. Navíc vzhledem k tomu, že na téměř celé aplikaci pracoval jeden člověk, není taková nutnost mít celý kód otestovaný proti dalším úpravám. Proto jsme došli k názoru, že psát na všechno testy by bylo velmi pracné. Jedno kritické místo však v projektu existuje a tím je třída ShoppingListsService
operující s nákupními seznamy. Tato třída obsahuje velké
množství logiky typu volání do databáze, volání na server, vyhazování výjimek a logiku samotné synchronizace. Navíc si nemůžeme dovolit, aby uživatelé měli své nákupní seznamy v nekonzistentním nebo chybném stavu. Rozhodli jsme se tedy otestovat primárně tuto třídu, přičemž pokud by se další místo v aplikaci ukázalo takto kritické, neměl být problém testy dopsat. Výsledkem tedy je, že máme 121 automatických testů, z nichž 114 je ve třídě ShoppingListsServiceSpecs
a testuje třídu ShoppingListsService. Testy jsme se navíc
snažili psát tak, aby slovně popisovali funkcionalitu, kterou testujeme. Toho je docíleno umístěním testů do zanořených tříd. Uveďme si to na příkladu. Máme třídu Given_SynchronizeAllAsync_is_called,
která obsahuje všechny testy týkající se
synchronizace. Ta bude mít několik podtříd, přičemž každá bude popisovat podmínky
testu.
Jedna
When_error_occurs_during_obtaining_server_shopping_lists,
podtřída
je
která řeší situaci, kdy
na serveru nastala chyba, když jsme získávali nákupní seznamy. Tato třída již obsahuje samotné metody, kde každá testuje jednu věc, která nastane. V tomto 68
případě nastane pouze jedna věc a to vyhození výjimky, proto se tato testovací metoda jmenuje Then_ShoppingListsServerSynchronizationException_is_thrown.
4 Uživatelská dokumentace 4.1 Instalace Pro nasazení aplikace na emulátor nebo zařízení jsou potřeba následující prerekvizity:
Windows 8.1 Pro nebo Windows 10 Pro.
Microsoft Visual Studio 2013 nebo 2015 (Community Edition a vyšší) https://www.visualstudio.com/.
Xamarin buissness nebo trial verze, která je na jeden měsíc https://xamarin.com/download. 2
Xamarin platform for Mac - https://xamarin.com/download
Extension SQLite for Windows Runtime do Visual Studia – nutné stáhnout sqlite-wp81-winrt
http://sqlite.org/2015/sqlite-wp81-winrt-3090100.vsix
i
sqlite-winrt81 http://sqlite.org/2015/sqlite-winrt81-3090200.vsix.3
Lokálně zkopírované zdrojové soubory z příloh.
Android i Windows projekty jsou spustitelné přímo z Visual Studia, iOS projekt je nejjednodušší pustit na Macu z Xamarin Studia nebo si nastavit Mac jako build host. To lze provést ve Visual Studiu v menu Tools -> Options -> Xamarin -> iOS Settings -> Find Mac Build Host. Program Build Host musí zároveň běžet na Macu a
2
Xamarin Installer sám stáhne Javu a Android SDK.
3
Při stažení novější verze než 3.9.1 je pro správný chod aplikace nutné updatovat všechny
SQLiteNet balčíky na nejnovější verzi. To se týká balíčků v projektu Core a všech platformě specifických projektů. Nejjednodušší způsob je kliknout pravým tlačítkem myši na projekt, zvolit volbu Managet Nuget Packages a u každého balíčku dát Update.
69
být ve stejné lokální síti. Více informací o nastavení Build Hostu lze najít na http://developer.xamarin.com/guides/ios/getting_started/installation/windows/. Android by měl být také spustitelný z Xamarin Studia na Macu, ale netestovali jsme to. K otevření projektu je nutné otevřít soubor Matylda.sln, který je ve složce Source. V podsložce Presentation jsou všechny projekty platforem a ve složce PortableLibraries jsou všechny ostatní knihovny. Pro spuštění projektu je nutné na něj kliknout pravým tlačítkem myši a zvolit volbu “Set as StartUp Project“. Externí knihovny by se měli stáhnout při prvním přeložení aplikace. Pro nasazení aplikace na Android je nutné zvolit konfiguraci „Debug | Any CPU“ nebo „Release | Any CPU“. Aplikaci je možné nasadit na Android emulátory, které jsou součástí Android SDK nebo na reálná zařízení, která musí mít povolený režim vývojáře. S Visual Studio 2015 je navíc k dispozici program Visual Studio Emulator for Android, který poskytuje Android Emulátory běžící na Hyper-V. Android aplikaci lze také nasadit přímo z přeloženého balíčku s příponou APK ve složce Compiled Packages v příloze. Obě Windows aplikace jsou v podsložce WindowsCommon. Windows Phone 8.1 aplikaci lze opět nasadit na emulátory nebo registrované reálné zařízení. Při nasazení na zařízení lze použít konfiguraci Debug nebo Release, ale cílová platforma musí být ARM. Any CPU není podporováno, protože knihovna SQLite je nativní. Při nasazení na emulátory jsou podporovány platformy Any CPU a x86 nebo x64. Testovací Windows 8.1 aplikace může být také nasazena v konfiguraci Debug nebo Release, ale platforma musí být kvůli nativní SQLite nastavená na x86 nebo x64. Aplikace pro iOS může být také nasazena na emulátor nebo reálně zařízení. Pro nasazení na emulátor je nutné mít konfiguraci “Debug | iPhone Simulator” nebo “Release | iPhone Simulator”. Mezi emulátory lze ve Visual Studiu volit až po připojení Build Hostu. Pro připojení reálného zařízení je nutné být součástí placeného Apple Developer programu a mít telefon zaregistrovaný jako vývojářský (http://developer.xamarin.com/guides/ios/getting_started/installation/device_provisio ning/). Konfigurace při nasazení na zařízení musí být “Debug | iPhone” nebo “Release | iPhone”. 70
Pokud by z nějakého důvodu byl server nedostupný, pak ve třídě projektu stačí nastavit položku
_isDemo
App
v Core
na true. Aplikace pak bude fungovat v demo
režimu.
4.2 Průchod aplikací Nyní popíšeme, jaké všechny obrazovky aplikace obsahuje a jak se mezi nimi pohybovat. Pokud mezi ně počítáme i jednotlivé záložky nebo dialogy, pak má naše aplikace přibližně 48 obrazovek na každé platformě. Proto jejich popis nebude příliš detailní a nebude zahrnovat všechny screenshoty. Pokud by se na nějaké platformě průchod lišil, explicitně to uvedeme. Tento popis neplatí pro desktopový Windows 8.1, protože tam několik obrazovek chybí a je jenom pro testovací účely. Následující obrázek zobrazuje základní obrazovky (bez dialogů) a průchod mezi nimi. Samotné obrazovky odpovídají vnějším obdélníkům, záložky nebo tlačítka odpovídají vnitřním obdélníkům a šipky znázorňují směr navigace.
Obrázek 24: Obrazovky aplikace a průchod mezi nimi
Po prvním spuštění aplikace se objeví tutoriál se třemi obrazovkami, mezi kterými lze přecházet posouváním doprava. Na všech třech obrazovkách jsou přehledně uvedeny hlavní funkcionality aplikace. Na poslední stránce si uživatel může zvolit, zda chce příště tento tutoriál ještě zobrazovat. Tlačítkem “Přejít do aplikace“ se dostane na hlavní obrazovku. 71
Hlavní obrazovka slouží jako hlavní rozcestník aplikace a zároveň obsahuje seznam produktů s nejvýhodnějšími slevami. Jsou zde tři hlavní tlačítka, kterými jsou “Produkty“, “Obchody a letáky“ a “Nákupní seznamy“, pomocí kterých se lze navigovat do dalších sekcí. Na Androidu lze navíc pomocí tlačítka ☰ otevřít tzv. Hamburger Menu. To obsahuje rozcestník do všech hlavních částí aplikace a je dostupné z většiny obrazovek přetažením prstu z levého okraje obrazovky doprava. Všechny tři platformy mají na úvodní obrazovce tlačítka pro volbu aktuální polohy, tlačítko pro hledání v aplikaci a tlačítko pro přihlášení. Na Windows Phone 8.1 je navíc hlavní obrazovka rozdělena na tři podčásti, jak ukazuje následující obrázek:
Obrázek 25: WP 8.1 - hlavní obrazovka
Následující obrázek ukazuje hlavní obrazovku na zbylých dvou platformách, přičemž na Androidu je verze s otevřeným a zavřeným Hamburger Menu.
72
Obrázek 26: Hlavní obrazovka - zleva Android, Android Hamburger Menu, iOS
Po kliknutí na tlačítko polohy se zobrazí dialog, který umožňuje zvolit lokaci, kde uživatel nakupuje. Základní dvě volby jsou “Aktuální pozice“ a “Celá ČR”. Po stísknutí tlačítka “Přidat místo“ se otevře obrazovka, kde můžeme vybrat další město v ČR pomocí textového pole, které napovídá ze serveru. Po výběru města se toto město automaticky nastaví jako aktuální. Zároveň se přidá do seznamu měst na předchozí obrazovce, odkud ho můžeme smazat. Po stisknutí tlačítka s lupou se otevře dialog, pomocí kterého můžeme hledat produkt, kategorii nebo obchod. Je zde opět textové pole, které napovídá ze serveru a po potvrzení volby nás aplikace naviguje na obrazovku odpovídající cílovému objektu. Po stisknutí tlačítka Produkty na hlavní obrazovce se dostaneme na seznam všech kategorií, u kterých je nastaven aktuální počet evidovaných slev. Po stisku libovolné kategorie se dostaneme na její detail. Detail kategorie obsahuje komponentu držící všechny podkategorie a seznam všech produktů v této kategorii. Např. pro kategorii alkohol jsou podkategorie Destiláty, Pivo, Víno a další. Po stisknutí tlačítka podkategorie se dostaneme na stejný typ obrazovky pouze s daty podkategorie. Seznam produktů můžeme po stisknutí tlačítka nastavení filtrovat a řadit, jak je znázorněno na Obrázek 20. Každý 73
produkt má u sebe ikonu košíku, pomocí kterého vyvoláme dialog na přidání položky do seznamu. Stejně tak je zde tlačítko, které vyvolá ten stejný dialog, který ale do seznamu přidá celou kategorii. Dále je zde tlačítko s lupou, pomocí kterého můžeme v aktuální kategorii hledat. Po rozkliku produktu se dostaneme na detail produktu. Detail produktu se skládá ze tří záložek. Na první je obrázek, základní informace o produktu a jeho cenách a tabulka vlastností jako objem nebo hmotnost a tabulka složení. Zároveň je zde tlačítko na přidání položky do seznamu, které opět vyvolá dialog na přidání do seznamu. Tato záložka je zobrazena na následujícím obrázku.
Obrázek 27: Detail Produktu - zleva Android, iOS, WP 8.1
Záložka “Kde Koupit“ obsahuje seznam nejbližších prodejen, kde se dá produkt koupit, včetně jeho ceny v tomto obchodě. Po rozkliku obchodu se dostaneme na detail obchodu. Poslední záložka je mapa, kde se zobrazí nejbližších 10 prodejen na mapě, kde se daný produkt prodává, včetně cen produktu. Proklik každé prodejny na mapě způsobí navigaci na detail prodejny. Detail prodejny obsahuje dvě záložky. Na první je jméno prodejny, její adresa, otevírací doba a tlačítko na navigaci na prodejnu. Toto tlačítko spustí externí aplikaci na navigaci. Druhá záložka zobrazuje tuto prodejnu na mapě spolu s uživatelovou polohou. 74
Po kliku na tlačítko “Obchody” na hlavní stránce se dostaneme na obrazovku s přehledem obchodníků. Ten má tři záložky. Na první je seznam samotných řetězců, na druhé je seznam nejbližších prodejen všech řetězců a na třetí je mapa s 10 nejbližšími prodejnami. Po rozkliknutí prodejen se dostaneme na detail prodejny a po rozkliknutí řetězce se dostaneme na detail řetězce. Detail řetězce má 5 záložek. Na první jsou aktuálně platné letáky, na druhé jsou produkty, které se zde prodávají, na třetí jsou nejbližší prodejny tohoto řetězce, na čtvrté jsou tyto prodejny na mapě a na páté jsou podčásti řetězce. Poslední záložka se skryje, pokud zde nejsou žádné podřětězce. Produkty na obchodníkovi můžeme filtrovat stejným filtrem jako na detailu kategorie a tlačítkem s lupou můžeme hledat produkty prodávané jen daným obchodníkem. Proklikem na produkt se dostaneme na detail produktu, při kliknutí na prodejnu se dostaneme na detail prodejny a proklikem na podčást (např. v Tescu je Tesco Express) se dostaneme na stejnou obrazovku, jen pro Tesco Express. Po kliknutí na leták se dostaneme na obrazovku s náhledem stránek letáku. Z náhledu stránek letáku se dostaneme na detaily jednotlivých stránek. Pohybem prstem doleva se dostaneme na předchozí stránku a pohybem doprava na následující stránku. Každou stránku můžeme přibližovat a posouvat. Na hlavní obrazovce se přes tlačítko přihlášení můžeme dostat na rozcestník s přihlášením. Tento rozcestník se pustí také při prvním startu aplikace po tutoriálu. Na tomto rozcestníku jsou uvedeny důvody, proč mít účet a tlačítko na navigaci, na přihlášení a na registraci. Obrazovka s přihlášením obsahuje formulář na vyplnění emailu, hesla a potvrzovací tlačítko. Dále jsou zde tlačítka na přihlášení přes Facebook a Google a tlačítko na zapomenuté heslo. Tlačítko zapomenuté heslo si řekne o email uživatele, na který přijde odkaz na změnu hesla. Tlačítko přihlásit se pod emailem a heslem se pokusí přihlásit uživatele, nebo ukáže seznam chyb. Facebook nebo Google tlačítko zobrazí uživateli stránku tohoto poskytovatele ve webovém prohlížeči, kde je uživatel vyzván pro zadání jména a hesla. Po zadání údajů je prohlížeč zavřen, a pokud uživatel ještě nemá účet, je přesměrován na stránku se zadáním emailu. Poté je uživatel přihlášen. 75
Obrazovka s registrací obsahuje formulář se jménem, heslem, potvrzením hesla a potvrzovacím tlačítkem. Dále jsou zde tlačítka Facebook a Google, které mají stejnou funkcionalitu jako na stránce s přihlášením. Po úspěšné registraci je uživatel přihlášen. Uživateli je však po registraci zobrazeno upozornění, že mu byl poslán aktivační odkaz, který musí aktivovat do určité doby, jinak bude odhlášen a účet nebude moci používat. Pokud je uživatel přihlášen, může jít do sekce nákupních seznamů. Pokud by do této sekce chtěl jít nepřihlášený uživatel, pak bude vyzván dialogem, aby se nejprve přihlásil. Uživateli je při registraci automaticky založen nákupní seznam. Samotné obrazovky se seznamy si na Androidu a Windows Phone 8.1 odpovídají, ale na iOS se liší. Na iOS máme samostatnou obrazovku s listem nákupních seznamů, které můžeme gestem přejetí prstem doleva přejmenovat nebo smazat. Na tomto místě můžeme také zakládat nový seznam. Pokud máme jen jeden seznam, je tato obrazovka přeskočena a jsme navigování přímo na její detail. Na zbylých dvou platformách jsou seznamy a jejich detaily na jedné obrazovce, přičemž jednomu seznamu odpovídá jedna záložka. Na detailu seznamu máme tlačítka na přejmenování seznamu, smazání seznamu, smazání všech položek seznamu a přidání položky do seznamu. Dále je zde tlačítko na sdílení seznamu a tlačítko “Kde koupit”. Především zde ale máme seznam všech položek, které můžeme zaškrtávat, mazat a upravovat jejich počty. Po kliknutí na tlačítko s přidáním položky, se nám zobrazí dialog, v kterém můžeme vyhledávat položky a zvolit si počet kusů. Po kliknutí na úpravu položky se zobrazí dialog, v kterém můžeme měnit počty kusů, nebo přejít na detail položky, což může být produkt nebo kategorie. Některé akce na těchto obrazovkách nebudou dostupné, pokud uživatel nemá dostatečná práva k seznamu. Obrazovka “Kde koupit” dává uživateli možnost zvolit si obchod a poté mu ukáže, kolik je v tomto obchodě slev na zboží v jeho seznamu, kolik ušetří a výčet všech položek včetně jejich ceny a případné slevy.
76
Obrázek 28: Kde koupit nákupní seznam - zleva Android, iOS, WP8.1
Obrazovka se sdílením nákupního seznamu dovoluje pozvat uživatele ke sdílení seznamu. V tu samou chvíli se mu přidělí práva, která můžou být “Jen pro čtení” nebo “Čtení a úprava”. Pozvánka se pošle do emailu uživatele, kde ji musí proklikem na odkaz potvrdit. V tu chvíli uvidí uživatel tento sdílený seznam mezi svými seznami. Na této obrazovce vlastník seznamu vidí všechny pozvané uživatele a poslané pozvánky. Pokud je uživatel přihlášený, může přes tlačítko nastavení z hlavní obrazovky přejít do správy účtu. Zde se může odhlásit a uvidí seznam účtů, do kterých se přihlašuje. Má také možnost kliknout na tlačítko „Přidat účet“. V tom případě bude navigován na obrazovku, kde si může nastavit další účet, přes který se bude přihlašovat. Může se pak přihlašovat např. přes Facebook a zároveň emailem a heslem. Má zde i možnost, změnit si u lokálního účtu svoje heslo.
4.3 Rozbor případů užití Nyní si probereme několik typických situací, kvůli kterým by uživatel mohl chtít používat naší aplikaci, a rozebereme si, jak tyto situace vyřešit. 1) Uživatel chce zjistit, kde mají nejlevnější pivo – Uživatel zapne aplikaci, přejde na produkty, zde zvolí kategorii Alkohol a podkategorii Pivo. Dále 77
nastaví filtr na řazení podle ceny a uvidí nejlevnější pivo. Po rozkliku na detail se navíc dozví, kde toto pivo koupí a za jakou cenu. Alternativní cestou by bylo ihned po spuštění aplikace začít vyhledávat řetězec “Pivo“, kde bychom se dostali přímo na kategorii “Pivo“. 2) Uživatel chce zjistit aktuální slevy v Bille – Uživatel zapne aplikaci, přejde na obchody a zvolí Billu. Zde má na výběr projít si leták Billy nebo přejít na záložku Produkty, kde si zvolí filtr “Jen ve slevě”. 3) Uživatel je v cizím městě a potřebuje co nejrychleji koupit sýr romadúr – Uživatel zapne aplikaci a vyhledá sýr romadúr. Díky našeptávání nemusí psát celý tento řetězec, pouze si ho rozklikne z našeptaných položek. Na detailu sýru se přepne na záložku “Mapa“, kde ihned uvidí nejbližší prodejny, kde se tento sýr prodává. 4) Uživatel chce dojet autem do nejbližšího Tesca Express – Uživatel zapne aplikaci, přejde na obchody a zvolí Tesco. Dále zvolí záložku “Podčásti“ a vybere Tesco Express. Zde zvolí záložku “Nejbližší prodejny” a vybere první prodejnu, popřípadě zkontroluje na mapě její pozici a klikne na tlačítko s navigací. To ho přesměruje do externí aplikace, která ihned začne navigovat na tuto prodejnu. 5) Uživatel si chce vytvořit nákupní seznam a zjistit, kde ho koupí nejlevněji – Uživatel zapne aplikaci a přihlásí se. Pokud uživatele zajímá zboží v konkrétní kategorii nebo u konkrétního prodejce, pak si přidá zboží do nákupního seznamu z obrazovky kategorie nebo prodejce. Jinak může přejít na svůj nákupní seznam přes tlačítko “Nákupní seznamy” na hlavní obrazovce. Poté pomocí tlačítka přidat přejde na dialog s vyhledávacím políčkem. Zde pomocí našeptávání bude přidávat jednotlivé položky a jejich počty. Po naplnění seznamu klikne na tlačítko “Kde koupit” a ze seznamu prodejců vybere toho, u které ušetří nejvíce na slevách. 6) Uživatel chce nasdílet svůj seznam jinému uživateli – Uživatel zapne aplikaci a přejde na nákupní seznam, který chce sdílet. U tohoto seznamu stiskne tlačítko sdílet a přejde na obrazovku se sdílením. Pokud chce poslat 78
seznam jenom jako výčet položek do emailu, pak stiskne tlačítko “Odeslat seznam e-mailem” a v dialogu zadá emailovou adresu příjemce. Pokud však chce, aby příjemce viděl tento seznam vždy aktualizovaný, pak ho může sdílet přímo v systému Matylda pomocí tlačítka “Pozvat uživatele ke sdílení”. V dialogu zadá emailovou adresu, zvolí možnost “jen pro čtení“ a odešle pozvánku. Příjemce aktivuje pozvánku kliknutím na odkaz v emailu a přihlášením se do systému Matylda. Nyní má příjemce tento nákupní seznam svázaný s jeho účtem a může si ho prohlížet ve webové i mobilní aplikaci.
5 Podobné aplikace Na českém trhu existuje několik serverů, které se zabývají slevami. Nejznámější je server http://www.kupi.cz/
a poté server http://www.akcniceny.cz/. Oba tyto
servery pracují s produkty ve slevě, letáky, obchody a nákupními seznamy. Oba servery jsme již zmiňovali jako potenciální konkurenci při tvorbě webové aplikace. Server Kupi má svoji mobilní aplikaci, která je však jen pro Android. Většina funkcionalit v této aplikaci je totožná s našimi. Pokud bychom měli uvést nějaké funkcionality, které máme navíc, pak je to třeba podrobnější detail produktu, mapy zabudované v aplikaci, funkcionalita kde koupit na nákupních seznamech nebo přihlašování přes externího poskytovatele. Aplikace Kupi má naopak
navíc
funkcionalitu na hlídání zboží a skenování pomocí čárového kódu. Server Akční Ceny má dvě aplikace, které jsou rozdělené podle funkcionalit. První aplikace je pojmenovaná Akční Ceny. Ta má také funkcionality na procházení letáků, produktů ve slevě nebo prodejen. I tato aplikace postrádá některé naše funkcionality jako podrobný detail produktu, práce s geolokací, procházení produktů po kategoriích (má pouze vyhledávání po kategoriích) a další. Navíc má např. možnost uložit si do telefonu letáky a prohlížet si je bez připojení. Druhou aplikací je Nákupní Seznam, jenž má podobné možnosti nákupních seznamů jako naše aplikace. Při přidávání produktů do seznamu však nelze navázat položky na reálné produkty, místo toho je zde seznam obecných produktů typu rohlík nebo banán. Je zde však možnost u přidaných položek zvolit volbu „Zobrazit zboží v akci“, které ukáže aktuální slevy těchto produktů. Obě aplikace jsou pro Android a iOS. 79
80
6 Závěr Hlavním cílem práce bylo vytvořit multiplatformní mobilní aplikaci pro databázový systém Matylda. Byly uvedeny možné způsoby vývoje aplikace na více platforem a na základě jejich analýzy byla zvolena technologie Xamarin. Součástí diskuze byl také návrhový vzor MVVM a knihovna MvvmCross, kterou aplikace intenzivně využívá. Popsali jsme si hlavní rozdíly jednotlivých systémů a učinili několik rozhodnutí o použitých technologiích a knihovnách, které významně ovlivnily vývoj. Výsledkem je nativní aplikace, která běží na platformách Android, iOS a Windows Phone a poskytuje velké množství funkcionalit původního webového projektu. Při řešení jednotlivých problémů bylo ilustrováno, jak jsme se vypořádali s rozdílnými rozhraními systémů, které se liší jak po vzhledové tak po funkční stránce. Díky volbě vhodných technologií jsme dosáhli takové struktury projektu, kdy téměř celá logika aplikace je sdílená mezi platformami. Tím jsme si ušetřili spoustu práce při vývoji a dosáhli velmi dobře udržovatelného řešení. Protože původní webový projekt se stále vyvíjí, je zde velký prostor pro přidání dalších funkcionalit. Těmi může být např. hlídání zboží a notifikace pří slevách, stahování letáků do telefonu nebo pokročilý filtr produktů. Abychom zjistili, na jakou funkcionalitu se do budoucna zaměřit, a která může být naopak přebytečná, musela by být aplikace používána. V době odevzdání však aplikace nebyla ještě ani v jednom obchodě s aplikacemi, proto zatím nemáme žádnou odezvu od uživatelů. Cílová aplikace splňuje všechny naše požadavky a může sloužit uživatelům jako chytrý pomocník při nakupování.
81
7 Zdroje 1. IDC: Smartphone OS Market Share 2015, 2014, 2013, and 2012. IDC: The premier global market intelligence firm. [Online] [Citace: 15. 10 2015.] http://www.idc.com/prodserv/smartphone-os-market-share.jsp. 2. Pravda o podílu iOS a Windows Phone v Česku (statistiky) | mobilenet.cz. mobilenet.cz. [Online] [Citace: 2015. 9 2015.] http://mobilenet.cz/clanky/pravda-opodilu-ios-a-windows-phone-v-cesku-statistiky-19696. 3. Industry Leaders Announce Open Platform for Mobile Devices. Open Handset Alliance. [Online] 5. 11 2007. [Citace: 3. 11 2015.] http://www.openhandsetalliance.com/press_110507.html. 4. Android Application Security Part 2-Understanding Android Operating System. Aditya Agrawal. [Online] 10. 1 2015. [Citace: 3. 11 2015.] https://manifestsecurity.com/android-application-security-part-2/. 5. About the iOS technologies. Apple Developer. [Online] [Citace: 3. 11 2015.] https://developer.apple.com/library/ios/documentation/Miscellaneous/Conceptual/iPh oneOSTechOverview/Introduction/Introduction.html. 6. Windows Phone 8.1 for Developers–Converging the App Models. MSDN Blogs. [Online] 31. 3 2014. [Citace: 3. 11 2015.] http://blogs.msdn.com/b/thunbrynt/archive/2014/03/31/windows-phone-8-1-fordevelopers-converging-the-app-models.aspx. 7. PhoneGap Explained Visually. PhoneGap. [Online] 2. 5 2012. [Citace: 3. 11 2015.] http://phonegap.com/2012/05/02/phonegap-explained-visually/. 8. Titanium Platform Overview - Appcelerator Platform - Appcelerator Docs. Appcelerator Documentation. [Online] [Citace: 6. 11 2015.] http://docs.appcelerator.com/platform/latest/#!/guide/Titanium_Platform_Overview. 9. Mobile App Development & App Creation Software - Xamarin. [Online] [Citace: 6. 11 2015.] https://xamarin.com/. 10. Limitations - Xamarin. [Online] [Citace: 6. 11 2015.] http://developer.xamarin.com/guides/ios/advanced_topics/limitations/. 11. MOBILE DEVELOPMENT PLATFORM PERFORMANCE. [Online] 29. 12 2014. [Citace: 6. 11 2015.] http://magenic.com/Blog/Post/4/Mobile-DevelopmentPlatform-Performance. 12. Build a Native Android UI & iOS UI with Xamarin.Forms - Xamarin. [Online] [Citace: 6. 11 2015.] https://xamarin.com/forms. 82
13. MvvmCross/MvvmCross · GitHub. GitHub · Where software is built. [Online] [Citace: 6. 11 2015.] https://github.com/MvvmCross/MvvmCross. 14. Patterns - WPF Apps With The Model-View-ViewModel Design Pattern. MSDN. [Online] 2 2009. [Citace: 6. 11 2015.] https://msdn.microsoft.com/enus/magazine/dd419663.aspx. 15. Fowler, Martin. Presentation Model. [Online] 19. 7 2004. [Citace: 6. 11 2015.] http://martinfowler.com/eaaDev/PresentationModel.html. 16. GUI Architectures. Martin Fowler. [Online] 18. 7 2006. [Citace: 6. 11 2015.] http://martinfowler.com/eaaDev/uiArchs.html. 17. Dashboards. Android Developers. [Online] [Citace: 15. 4 2015.] https://developer.android.com/about/dashboards/index.html. 18. Apple Developer. App Store - Support - Apple Developer. [Online] [Citace: 15. 4 2015.] https://developer.apple.com/support/app-store/. 19. SQLite. [Online] [Citace: 6. 11 2015.] https://www.sqlite.org/. 20. Settings Plugin for Xamarin And Windows. GitHub. [Online] [Citace: 6. 11 2015.] https://github.com/jamesmontemagno/Xamarin.Plugins/tree/master/Settings. 21. sqlite-net. GitHub. [Online] [Citace: 6. 11 2015.] https://github.com/praeclarum/sqlite-net. 22. Part 2 - Maps API - Xamarin. Developer Center - Xamarin. [Online] [Citace: 6. 11 2015.] https://developer.xamarin.com/guides/android/platform_features/maps_and_location/ maps/part_2_-_maps_api/. 23. Alternatives to Google Maps on Android. Android Central. [Online] [Citace: 6. 11 2015.] http://www.androidcentral.com/alternatives-google-maps-android. 24. Windows Communication Foundation. MSDN. [Online] [Citace: 6. 11 2015.] https://msdn.microsoft.com/en-us/library/dd456779(v=vs.110).aspx. 25. ASP.NET Web API. MSDN. [Online] [Citace: 6. 11 2015.] https://msdn.microsoft.com/en-us/library/hh833994(v=vs.108).aspx. 26. Automapper. GitHub. [Online] [Citace: 6. 11 2015.] https://github.com/AutoMapper/AutoMapper. 27. Databinding · MvvmCross/MvvmCross Wiki. [Online] [Citace: 17. 11 2015.] https://github.com/MvvmCross/MvvmCross/wiki/Databinding.
83
28. Adapter. Android Developers. [Online] [Citace: 7. 11 2015.] http://developer.android.com/reference/android/widget/Adapter.html. 29. Xcode - Interface Builder. Apple Developer. [Online] [Citace: 6. 11 2015.] https://developer.apple.com/xcode/interface-builder/. 30. ViewPagerIndicator. [Online] [Citace: 6. 11 2015.] http://viewpagerindicator.com/. 31. MvvmCross-UserInteraction. GitHub. [Online] [Citace: 6. 11 2015.] https://github.com/brianchance/MvvmCross-UserInteraction. 32. GitHub. [Online] [Citace: 6. 11 2015.] https://github.com/. 33. TouchImageView. GitHub. [Online] [Citace: 6. 11 2015.] https://gist.github.com/justintoth/10663869. 34. MWPhotoBrowser. GitHub. [Online] [Citace: 6. 11 2015.] https://github.com/mwaterfall/MWPhotoBrowser. 35. Walkthrough: Binding an Objective-C Library. Xamarin. [Online] [Citace: 6. 11 2015.] https://developer.xamarin.com/guides/ios/advanced_topics/binding_objectivec/Walkthrough_Binding_objective-c_library/. 36. Mike Wasson. Secure a Web API with Individual Accounts and Local Login in ASP.NET Web API 2.2. ASP.NET. [Online] 15. 10 2014. [Citace: 10. 11 2015.] http://www.asp.net/web-api/overview/security/individual-accounts-in-web-api. 37. Taiseer Joudeh. ASP.NET Web API 2 external logins with Facebook and Google in AngularJS app. Bit Of Technology. [Online] 11. 8 2014. [Citace: 10. 10 2015.] http://bitoftech.net/2014/08/11/asp-net-web-api-2-external-logins-sociallogins-facebook-google-angularjs-app/. 38. Moq. GitHub. [Online] [Citace: 6. 11 2015.] https://github.com/Moq/moq4. 39. Testing · MvvmCross/MvvmCross Wiki. GitHub. [Online] [Citace: 10. 10 2015.] https://github.com/MvvmCross/MvvmCross/wiki/Testing.
84
8 Seznam obrázků Obrázek 1: Architektura Androidu (4)...................................................................... 7 Obrázek 2: Architektura iOS (5) .............................................................................. 9 Obrázek 3: Windows Runtime Execution model (6)................................................10 Obrázek 4: Aplikace Facebook Messenger (zleva Android, iOS, Windows Phone) .11 Obrázek 5: Xamarin.Android architektura...............................................................17 Obrázek 6: Shared project a dirketivy kompilátoru..................................................18 Obrázek 7: PCL a výběr platforem..........................................................................19 Obrázek 8: Návrhový vzor MVVM.........................................................................21 Obrázek 9: Vzor MVX............................................................................................22 Obrázek 10: Rozložení Android verzí (duben 2015) (17) ........................................24 Obrázek 11 - Rozložení iOS verzí (duben 2015) (18) ..............................................25 Obrázek 12: Architektura systému Matylda.............................................................30 Obrázek 13: Upravená Architektura Matyldy ..........................................................33 Obrázek 14: Celková architektura aplikace .............................................................34 Obrázek 15: Mapování modelů ...............................................................................41 Obrázek 16: Fluent API binding..............................................................................43 Obrázek 17: Binding XML layout ...........................................................................43 Obrázek 18: XAML binding ...................................................................................46 Obrázek 19: Záložky - zleva Android, iOS, WP 8.1 ................................................50 Obrázek 20: Dialogy - zleva Android, iOS, WP 8.1.................................................51 Obrázek 21: Alert Dialogy - zleva Android, iOS, WP 8.1........................................52 Obrázek 22: Detail prodejny na mapě – zleva Android, iOS, WP 8.1 ......................59 Obrázek 23: Přihlašování pomocí tokenu (36).........................................................64
85
9 Seznam zkratek MVVM – Model View ViewModel MVC – Model View Controller IoC – Inversion of Control ART – Android Runtime WinRT – Windows Runtime XAML – Extensible Application Markup Language PCL – Portable Class Library UI – User Interface WPF – Windows Presentation Foundation IDE – Integrated Development Enviroment
86
10 Přílohy Na přiloženém DVD souboru jsou soubory s následující strukturou:
Obsah.txt
/Documentation – Obsahuje dokumentaci v souboru Documentation.chma 4 a text práce v Diplomova_prace.pdf.
/Source – Obsahuje zdrojové soubory, které se dělí do následujících částí o /Components – komponenty Xamarinu. o /NativeLibraries – MWPhotoBrowser knihovna s binding projektem. o /PortableLibraries – Sdílené knihovny. o /Presentation – Aplikační projekty. o /Tests – Testy. o /ThirdPartyLibraries – Přeložené DLL soubory.
4
/Compiled Packages – Přeložený balíček pro Android.
Dokumentace je vytvořená v nástroji SandCastle http://sandcastle.codeplex.com/. Pokud se
nezobrazuje obsah v pravé části, je soubor zablokovaný. Odblokuje se přes Vlastnosti -> Odblokovat.
87