Vyvíjíme mobilní aplikaci ve Flutteru (2/3) | eMan

Vyvíjíme mobilní aplikaci ve Flutteru (2/3)

flutter app eMan

Jazyk Dart je nutné znát, když chceme ve Flutteru psát. Proto se mrkneme nejen na to, jak se ve Flutteru píše, ale taky si ho představíme z hlediska návrhu. Odpovíme si i na základní otázku: Proč to funguje multiplatformě? Nakonec zavoláme nativní službu konkrétní platformy.

Minule jsme si řekli něco o frameworku Flutter. Nakonfigurovali jsme si prostředí, abychom mohli začít vytvářet Flutter aplikace, popsali si, jak vypadá a co obsahuje projekt Flutter aplikace. Nakonec jsme si ukázali, jak vytvořit platformě závislý dialog.

Dnes si představíme Flutter techničtěji. Ukážeme si jazyk Dart tak, abychom věděli, jak se v tom Flutteru vlastně píše. Rozebereme Flutter z hlediska návrhu a vysvětlíme, proč to vlastně funguje multiplatformě. Nakonec si ukážeme, jak zavolat nativní službu konkrétní platformy. Tak pojďme na to.

 

Jazykové okénko

Dart je objektový programovací jazyk, který je syntaxí velmi podobný dnešním moderním jazykům, jako je Kotlin, C# nebo Java.

 

Proměnné

Dart je silně typový jazyk. To znamená, že každá proměnná má svůj datový typ, který je určen při vytvoření proměnné a nelze ho během chodu programu měnit. Při zakládání nové proměnné není nutné explicitně určit typ, ale můžeme pomocí klíčového slova var využít dedukci typu. I když se jedná o silně typový jazyk, umožňuje použitím klíčového slova dynamic, vytvoření proměnné bez „datového typu“.

 

 

Třídy a objekty

Všechno v Dartu je objekt, každý objekt je instancí nějaké třídy a všechny třídy dědí od třídy Object. Třídy se skládají z třídních proměnných a metod (statické metody), objekty pak z instančních proměnných a metod.

Jedním z konceptů objektově orientovaného programování je zapouzdření tříd. Například v Javě lze definovat několik úrovní viditelnosti, které jsou většinou řešené pomocí klíčových slov jazyka, jako private, protected, či public. V Dartu existuje jen public a private viditelnost, která není definovaná speciálními klíčovými slovy, ale pro private viditelnost stačí při deklaraci třídy, proměnné apod., použít v názvu prvku prefix _. Viditelnost se ovšem nevztahuje na úroveň tříd, ale na úroveň jednotlivých částí programu – knihoven (library).

 

 

Poznámka: Defaultně je každý vlastní dart soubor knihovnou. To znamená, že jednotlivé privátní komponenty nejsou mezi soubory viditelné.

Zajímavým konceptem jazyka jsou tzv. Named constructors. Jedná se o klasický konstruktor objektu, kterému lze ale definovat jméno. U vhodně pojmenovaného konstruktoru pak jeho název vystihuje způsob vzniku objektu. Například v Javě by šel podobný konstrukt vytvořit pomocí statické metody, která by prováděla vytvoření a inicializaci samotného objektu.

 

 

Funkce

Kromě metod podporuje Dart i klasické funkce, a jelikož všechno je objekt, jsou i ony objekt. Hlavní funkcí je funkce main(), která je vstupní funkcí programu, takže každý program bude začínat právě touto funkcí. Definice funkcí do sebe můžeme vnořovat a vytvářet tzv. Nested function. Každá vnořená funkce získává kontext funkce, do které byla vnořena – vidí její lokální proměnné.

 

Null-aware operátory

Podobně jako Kotlin i Dart nabízí několik operátorů, které zjednodušují zápis Null Pointer safety kódu.

  • If null operátorexpr1 ?? expr2 – pokud není výraz expr1 roven null, vyhodnotí se celý výraz jako expr1, jinak expr2
  • Null-aware assignmentvariable ??= expr – pokud je hodnota variable rovna null, přiřadí se do proměnné výraz expr
  • Null-aware accessvariable?.method – pokud není hodnota variable rovna null, provede se zavolání metody method a vrátí se výsledek volání, jinak se vrátí null

 

Kaskáda

Slouží k zavolání více metod na jednom objektu. Například v Kotlinu se docílí stejného chování pomocí apply.

 

 

Všechno je widget

„Všechno je widget“, to je věta, se kterou jste se už určitě setkali, pokud jste četli nějaký článek o Flutter. Ano, je tomu opravdu tak, všechno je widget. Všechno, i samotná aplikace, je widget. Díky tomu lze ke každému prvku UI přistupovat jednotně. Rozlišujeme dva druhy widgetů:

  • Stateless widget – Prvky, které reprezentují stateless widget, jsou neměnné, tzn. nemění svůj vnitřní stav. Stav prvku je nastaven při jeho konstrukci a nelze ho během života objektu měnit (všechny atributy třídy jsou final).
  • Stateful widget – Prvky, které mohou a také mění svůj vnitřní stav. Respektive stateful widgety se skládají ze dvou částí:
    • části, která reprezentuje samotný widget a zůstává celou dobu také neměnná
    • části, která reprezentuje stav widgetu, může se měnit a slouží k perzistenci stavu widgetu.

 

 

Každý widget je velice štíhlý, protože se stará pouze o tu činnost, pro kterou byl vytvořen, o žádnou jinou. Například widget Text, který umí zobrazit text, se stará pouze o samotné zobrazení a například vůbec neřeší svoji pozici vůči ostatním wigetům nebo schopnost sbírání událostí (například kliků). Pokud bychom ho chtěli o nějakou další funkcionalitu rozšířit, jednoduše ho obalíme widgetem, který danou funkcionalitu podporuje. V konečném výsledku pak vývoj takové Flutter aplikace vede ke skládání jednotlivých widgetů za docílením požadované funkcionality, no prostě takové lego. A lego má přece každý rád 😉

 

Technologie Flutteru

Flutter nevyužívá žádné nativní prvky cílové platformy. Všechny prvky napsali vývojáři Flutteru znovu, lépe a tak, aby vyhovovaly zvyklostem cílové platformy. Většina zdrojového kódu aplikace je překládána přímo do strojového kódu výsledného procesoru (na Androidu pomocí Android NDK, na iOS pak prostřednictvím LLVM). Díky tomu máme jen jeden zdrojový kód pro obě platformy. A taky by pak, alespoň na Androidu, měla být výsledná aplikace mnohem rychlejší.

Jednotlivé vrstvy frameworku jsou zobrazeny na schématu níže. Závislost jednotlivých vrstev je shora dolů – každá výše položená vrstva závisí na vrstvách níže. Zelená část zobrazuje hlavní komponenty frameworku Flutter. Jak vidíte, Material (Android) komponenta a Cupertino (iOS) komponenta se nacházejí na stejné úrovni, což značně komplikuje vývoj platformě závislé aplikace, jak jsme se přesvědčili v předchozím článku. V některých materiálech přímo na stránkách Flutteru můžete najít schéma, kde Material komponenta závisí na Cupertino komponentě. To by zmíněný problém řešilo, nicméně aktuální zdrojové kódy Flutteru spíše nasvědčují tomu, že se obě komponenty nacházejí na stejné úrovni.

V modré části se pak nacházejí komponenty jádra Flutteru, které se starají o samotný běh aplikace. Komponenta Skia se stará o rendrování 2D grafiky, komponenta Dart o AOT kompilaci částí kódu, které nemohly být zkompilovány přímo do strojového kódu a komponenta Text o rendrování textu.

 

 

Nativní rozhraní

Flutter umožňuje zavolat určitou službu nativního rozhraní platformy prostřednictvím tzv. Method channel. Flutter aplikace (klient) posílá zprávu s požadavkem nativní části aplikace (host). Pokud host zprávu zná, obslouží požadavek a vrátí zpět odpověď. Pro správné obsloužení musejí klient a host implementovat stejný protokol zprávy.

 

 

Ze schématu je patrné, že jsme schopni prostřednictvím Method channel zavolat libovolné služby nativní platformy, nebo služby, které nabízí knihovny třetích stran. Mimo to se jedná také o jeden ze způsobů, jak pomocí platformě-specifických služeb „naimplentovat“ službu, kterou Flutter neumožňuje. Toho lze využít například když nemáme knihovnu s požadovanou službou dostupnou přímo pro Flutter, ale existují knihovny jak pro Android, tak iOS.

Celý princip si ukážeme na konkrétním příkladě – zjištění stavu baterie našeho zařízení. Jelikož Flutter umožňuje psaní zdrojového kódu platformě-specifických částí i v Kotlinu pro Android část a Swiftu pro iOS část, zvolil jsem ukázky v těchto jazycích.

 

Klient

Klientská část aplikace odešle požadavek na získání úrovně baterie a po obdržení výsledku zobrazí naformátovaný výsledek uprostřed obrazovky. Zpracování zprávy je asynchronní, takže stav baterie můžeme zobrazit až po obdržení výsledku. V ukázce můžete vidět jeden ze způsobů, jak zpracovat takové asynchronní volání.

Důležitou částí je zde konstrukce objektu MethodChannel, který v parametru konstruktoru očekává název vytvářeného kanálu. Pomocí názvu pak na straně hosta provedeme spojení s klientem. Aby spolu mohli klient a host komunikovat, musejí nejen komunikovat přes stejný kanál, ale také si v tomto kanále posílat a očekávat stejné typy zpráv. K tomu slouží metoda invokeMethod(), kde v parametru metody definujeme typ zprávy.

Soubor main.dart v adresáři lib by měl pak vypadat následovně:

 

 

Host

Důležitou částí na straně hosta je zaregistrování handleru na stejném kanálu, který byl definován na straně klienta. Po obdržení konkrétního typu zprávy, který lze zjistit pomocí call.method, provedeme požadovanou operaci a výsledek uložíme do objektu result.

Soubor MainActivy.kt v adresáři android/app/src/main/kotlin/package_name by pak měl vypadat následovně:

 

 

Soubor AppDelegate.swift v adresáři ios/Runner by pak měl vypadat následovně:

 

 

 

Závěr

Dnes jsme si ukázali, v čem a jak psát Flutter aplikaci. Řekli jsme si, z jakých částí se samotná aplikace skládá a z jakých částí se skládá framework Flutter. Na závěr jsme se podívali, jak komunikovat s nativním rozhraním platformy za účelem volání platformě-specifických služeb. Celý dnešní projekt naleznete na eManím GitHubu.

Příště se konečně pustíme do vývoje větší aplikace. Tam zúročíme znalosti nabyté tímto trochu delším, ale snad zajímavým úvodem do Flutteru.

 

Zdroje

Filip Šmíd
Filip Šmíd
Android Developer
  • Petr Klein 15. 6. 2018, 16:11

    Ahoj, díky za nové info ohledně Flutteru, chtěl jsem zeptat jak to vypadá s posledním dílem a zda bys tam mohl trochu popsat restrikce layoutu (Container, Flex, …). Například se snažím udělat ListView widgetů Card kde je karta rozdělena na půl, jelikož jsem web developer zvyklý na bootstrap, tak jsem našel obdobný způsob, což je použití Flex widgetů, nicméně vzniká tam spoustu problému se zanořováním a automatickým roztahováním childrens. Takže za mě by to bylo určitě přínosné. Díky a budu se těšit. Petr

    • Filip Šmíd 22. 6. 2018, 12:11

      Ahoj,
      děkuji za reakci. Poslední měsíc jsem měl studijní pauzu, takže jsem se poslednímu článku nemohl naplno věnovat, ale od příštího týdne bych měl na článku opět začít pracovat, takže by měl být vypublikován nejdéle do měsíce. Poslední část by měla být mnohem více praktická, každopádně bych se na tvůj dotaz mohl blíže podívat, dokonce si myslím, že by se to k němu i tématicky hodilo. Aktuálně nemám koncept článku ještě vymyšlený, ale budu se snažit odpovědět na tvůj dotaz.

      Filip

    • Filip Šmíd 25. 6. 2018, 0:33

      Ahoj,

      děkuji za reakci. Poslední měsíc jsem měl studijní pauzu, takže jsem se
      poslednímu článku nemohl naplno věnovat, ale od příštího týdne bych měl
      na článku opět začít pracovat, takže by měl být vypublikován nejdéle do
      měsíce. Poslední část by měla být mnohem více praktická, každopádně bych
      se na tvůj dotaz mohl blíže podívat, dokonce si myslím, že by se to k
      němu i tématicky hodilo. Aktuálně nemám koncept článku ještě vymyšlený,
      ale budu se snažit odpovědět na tvůj dotaz.

      Filip

EMAN EUROPE

eMan s.r.o.
U Pergamenky 1145/12
170 00 Praha 7
Česká republika

Zobrazit na mapě

+420 222 202 222
info@eman.cz

EMAN NORTH AMERICA

eMan Solutions LLC
The Cannon Houston
1334 Brittmoore Rd
Houston, TX 77043
United States of America
Zobrazit na mapě

+1 (346) 232 2867
hello@emanglobal.com