Sütik

Sütiket használunk a tartalom személyre szabására és a forgalom elemzésére. Kérjük, határozza meg, hogy hajlandó-e elfogadni weboldalunkon a sütiket.

Oldal tetejére
Bezárás
Zengo - NKFIA pályázat Zengo - Széchenyi2020
Zengo - Flutter mobilprojekt alkalmazása web projektként
Kategória:

Flutter mobilprojekt alkalmazása web projektként

Zengo - óra8 perc olvasási idő
2022. 08. 20.

Cégünknél a közelmúltig túlnyomóan az Android és iOS rendszerekre külön történt a fejlesztés. A Flutter bevezetésével az egyszerűbb alkalmazások már cross-platform kerültek fejlesztésre, a hatékonyságot bizonyos esetekben ez nagyban növelte. A Flutter 3-as verziójában már széles körű webes támogatás is megjelent.

Kíváncsiak voltunk, hogy a már meglévő mobilos Flutter moduljainkat mekkora munkával tudnánk webes platformra is fordítani. Hol vannak a buktatók? Mi az, amit nyerünk és mi az, amit vesztünk a közös kódbázissal?

A továbbiakban élő példákon keresztül megpróbálunk választ adni ezekre a kérdésekre, és kipróbáljuk a Flutter webes részét is. Tartsatok velünk!

Mi a Flutter?

A Flutter egy, a Google által fejlesztett keretrendszer, melynek a programozási nyelve a szintén Google által fejlesztett objektumorientált Dart nyelv. Ez egy ingyenes és nyílt forráskódú mobil UI framework, mely 2017. májusában jelent meg. A Flutter egyik előnye a többi cross-platform nyelvhez képest, hogy natív kódra build-elődik, így a cross-platform sebesség problémái megoldottak.

A legfrissebb verziója a 3.0-ás, ami az Androidon és az iOS-en kívül már stabilan támogatja a Windows, Linux, macOS és WEB alkalmazásokat is. Ezzel a harmadik verzióval lett stabil, de beta-ként már a 2.10.5-ös, általunk használt verzióban is volt lehetőség ezekre a platformokra build-elni. Ezzel egy kódbázisból tudunk összesen 6 különböző platformra fejleszteni.

Miért érdemes a Fluttert választani?

A Flutter egy modern keretrendszer, amellyel sokkal egyszerűbb a mobilalkalmazások létrehozása, mintha Java, Swift vagy React Native-ban készülnének. Flutter kód írásakor valós időben tudjuk megtekinteni az eredményeket - ezt hívják Hot-reloadnak -, így csak jelentősebb módosítások esetén van szükség a készülő alkalmazás újraindítására.

A Flutter használatának előnyei:

  • ideális induló ötletek prototipizálásához,
  • ideális teljes rendszerek kivitelezéséhez,
  • olcsón és gyorsan lehet jó minőségű kódot előállítani, amely minden platformon használható,
  • jó dokumentációval rendelkezik,
  • sok külső féltől származó plugin elérhető,
  • az Android Studio és a VS Code is támogatja.

Flutter projekt buildelése webes környezetre

A Flutter kétfajta webes támogatással rendelkezik:

  • Single-Page apps (SPAs), melyek egyetlen oldalon futnak, és dinamikusan frissülnek új oldal betöltése nélkül, és
  • Progressive web apps (PWAs), melyek asztali alkalmazásként futtathatóak.

Ha egy mobilalkalmazást szeretnénk webalkalmazássá alakítani, első lépésként a Flutter főkönyvtárában ki kell adnunk a

flutter create .

parancsot. Ennek a parancsnak létre kell hoznunk a webes környezetre készülő verzió könyvtárát is az android és ios mappa mellett.

100%

Ezután a legfontosabb lépés, hogy a mobilalkalmazás által használt összes csomagot megvizsgáljuk, hogy létezik-e a webes változata. Ezt a pub.dev oldalon tudjuk megtenni, ahol a keresőbe bemásolva a csomag nevét az kiadja a csomagot, és tudjuk ellenőrizni, hogy a címkék között szerepel-e a web. Ha nem szerepel, kereshetünk alternatívát, vagy saját magunk elkészíthetjük, melyet utána publikálhatunk is a pub.dev-en. A képernyő arányok miatt előfordulhat, hogy ami mobilon jól nézett ki, az a weben nem fog. Ezt javíthatjuk a Layout Builder segítségével.

JavaScript támogatás

Mivel a Flutter legelőször az internetböngészők nyelve volt, tartalmaz egy js csomagot, mely natív átjárhatóságot biztosít a Dart és a JavaScript között. Ahhoz, hogy használni tudjuk a JavaScriptet, a pubspec.yaml-fájlba a dependecies alá fel kell vennünk a js-t.

@JS() annotáció segítségével tudjuk hívni a javaScript kódokat. Ha egyedi nevet szeretnénk adni neki Dart-ba, akkor azt a @JS(“név”) segítségével tehetjük meg.

@JS("Dog")
class DartDog {
  ...
}
JavaScript objektum paraméterként

Létre kell hoznunk egy osztályt, és meg kell jegyeznünk a @JS() és @anonymous karakterekkel. Az alábbi példa szerint az Options osztályt fogjuk használni.

@JS()
@anonymous
class Options {
 external bool get bed;
 external String get hardness;
 external factory Options({bool bed, String hardness});
}
Funkciók átadása JavaScriptnek

A függvényparaméterhez a függvényünket az allowInterop-pal kell csomagolnunk, így:

dog.jump(allowInterop((int height){
 print(height);
}));

Ahhoz, hogy ez működjön, másoljuk a generált Dog.js fájlt a TypeScript fájlból a Flutter projekt webmappájába, az index.html mellé. Ezután nyissuk meg az index.html fájlt, és adjuk hozzá ezt a sort az import main.dart.js szkriptcímke elé.

<script src="Dog.js" type="application/javascript"></script>

A mobil és a webalkalmazás közötti leglényegesebb különbség a navigálás lesz. Míg mobilon kötött útvonal van arra, hogy jutunk el egy-egy oldalra, addig a weben az url-t átírva is megtehejtük ezt, kihagyva már pár lépést és képernyőt. Ennek orvoslására használható a beamer csomag. A beamer csomag a Navigator 2.0 API-t használja és megvalósítja az összes mögöttes logikát.

Egy alap példakód, amely megnyitja az alkalmazás indításakor a HomePage()-et:

class HomeLocation extends BeamLocation {
  @override
  List<String> get pathBlueprints => ['/'];
  @override
  List<BeamPage> pagesBuilder(BuildContext context) => [
        BeamPage(
          key: ValueKey('home'),
          child: HomePage(),
        ),
      ];
}

Az alábbi példa megmutatja, hogyan lehet megnyitni egy terméket, hogy az url-t változtatjuk:

class ProductsLocation extends BeamLocation {
  @override
  List<String> get pathBlueprints => ['/products/:productId'];
  @override
  List<BeamPage> pagesBuilder(BuildContext context) => [
        ...HomeLocation().pagesBuilder(context),
        if (pathSegments.contains('products'))
          BeamPage(
            key: ValueKey('products-${queryParameters['title'] ?? ''}'),
            child: ProductsPage(),
          ),
        if (pathParameters.containsKey('productId'))
          BeamPage(
            key: ValueKey('product-${pathParameters['productId']}'),
            child: ProductDetailsPage(
              productId: pathParameters['productId'],
            ),
          ),
      ];
}

Ha a felhasználó megnyitja a /products oldalt, akkor megjelenít minden keresett elemet. Ha a /products:/productId eléréssel nyitja meg, akkor létrejön egy részletező oldal, melyben pathParameters-ként megkapja a productId-t, és kiírja azt például: “product 2”. Ennek a segítségével elkerülhetőek a 404-es hibák, ha olyat akarna felhasználó megnyitni, ami nem létezik.

Zengos Flutter modulok

Ebben a blogposztban 3 általunk készült mobilos modult vizsgáltunk meg, hogy mennyire lehetséges mininális hozzányúlással webes projektekben felhasználni. Ezek a modulok a Magyar Szürkék Útja projektünkhöz készültek.

Térkép modul

Nem lehetséges a közvetlen átállás. Ez a modul egy egyedi Google Maps, amelyet már a régebbi projektekben is használtunk, de most átkerült Flutteres verzióra. Magát a modult úgy alakítottuk ki, hogy támogassa a GMS szolgáltatások nélküli készülékeket is. A Huawei készülékeknél a Petal Map API-hoz kapcsolódik, ami a Huawei Google Mapset kiválató térképes szoftvercsomagja.

Maga a Flutteres Google Maps api nem támogatja natívan a weben történő feldolgozást. Alternatívaként lehetőség van a google_maps_flutter_web használatára, de a Web Maps JavaScriptes változata fizetős, melyet ez a Flutter csomag tartalmaz. A modul a felépítéséből adódóan nem futtatható, csak egy modulba importálva használható (nincsen main run függvénye).

Puzzle játék modul

Körülményes az átállás jelen állapotában, mivel a képvágáshoz használt Image osztály a dart.io-ból jön, de a webes részhez a dart.html-es Image és Picture-re van szükség, amely nem tudja kezelni a feldolgozáshoz és daraboláshoz szükséges dolgokat. A legnagyobb probléma, hogy a projekt kialakítása miatt lokálisan tárolva vannak a content-ek, hogy offline is lehessen használni, azonban a dart.html-el csak Image.asset() és Image.network() képfeldolgozást tud használni.

Ha beégetett vagy az interneten lévő képeket használjuk, akkor már futtatható az alkalmazás, de ezután egy újabb probléma került elő: a dart.html-es Image osztálynak nincs copyResized() funkciója, melynek a segítésével rajzoljuk ki a puzzle egyes elemeit. Maga a tábla és a háttérben lévő kép - amire ki kell rakni a darabokat - mérete a SizeConfig segítségével beállítható.

Véleményem szerint az átalakítás rövidebb időt venne igénybe, mivel a használt dependencies-eknek már van webes támogatása is, így magát a kép betöltéshez és kirajzoláshoz kapcsolodó feldolgozást kellene átalakítani. Lehetőség van konverter írására, mellyel áttudjuk alakítani a képet. Az újraírás több időt venne igénybe, mert akkor a Widget-eket és a puzzle darabolás logikáját is újra el kellene készíteni.

100% 100%

Memória játék

Gond nélkül fordult mobilos projektből webes projektre. Ami átdolgozandó rész, az a screen size config. Maga a modul paraméterként kapja meg az eszköztől a képernyő méreteket, melyeket a MediaQuery-ből kalkulál ki:

import 'package:flutter/widgets.dart';

class SizeConfig {
 static late double _screenWidth;
 static late double _screenHeight;

 static void init(BuildContext context) {
   final screenSize = MediaQuery.of(context).size;
   _screenWidth = screenSize.width;
   _screenHeight = screenSize.height;
 }

 static double getProportionateScreenWidth(double inputWidth) {
   return (inputWidth / 375.0) * _screenWidth;
 }

 static double getProportionateScreenHeight(double inputHeight) {
   return (inputHeight / 812.0) * _screenHeight;
 }
}

Ezt a függvényt kell beépíteni és egy kicsit átdolgozni, hogy tökéletes legyen minden képarányon. A modulok úgy lettek elkészítve, hogy paraméterben kapjanak minden értéket (cím, színek, méretek, callback események, képek listája), hogy minél több projektben újra felhasználhatóak legyenek, átalakítások nélkül.

100% 100%

Konklúzió

Maga a Flutter egyre jobban fejlődik és egyre nagyobb szeletet vesz ki a mobilos nyelvek tortájából. Jelen pillanatban, ha mobilon és weben is szeretnénk egy projektet készíteni egy kód bázisból, és úgy is indul a fejlesztés, hogy figyelembe vettük a webes részt is, úgy elég egyszerűen tudunk alkalmazásokat készíteni.

Ha egy már meglévő mobilos projektet szeretnénk webes projektté alakítani, adódhat egy pár döccenő. Van, ahol alaposabb átdolgozásokra is szükség lehet a platformok sajátossága miatt. Szerencsére egyszerűen le tudjuk kérdezni, hogy webes környezetben futunk-e a kIsWeb konstans segítségével, vagy mobilos környezetben (Platform.android || Plaftorm.ios).

A lokális assetek tárolása és elérése (pl.: képek, adatbázisok, szövegek, amelyeket az alkalmazásban használunk) platformonként változó. Így a webes feldolgozásnál más a fájl elérés módja, mint mobilos környezetekben. Emiatt meg kell vizsgálnunk az elérési útvonalakat is, és platform specifikusan módosítani azokat.

else if (node.attributes['src'].startsWith('asset:')) 
{ final assetPath =
node.attributes['src'].replaceFirst('asset:', '');
};

Előfordulhat, hogy tesztelés során még a cache-ből olvassa az adatokat, és az új javításokat, változásokat nem lehet még tesztelni. Ennek megoldását az alábbiakban részletezzük.

A projekt létrehozása után az index.html fájl így fog kinézni:

<!DOCTYPE html>
<html>
 <head>
   <meta charset="UTF-8">
   <title>to_cache_or_not_to_cache</title>
 </head>
 <body>
   <script src="main.dart.js"
type="application/javascript"></script>
 </body>
</html>

Ha szeretnénk, hogy hibajavítás után ne a cache-elt verziót használjuk, annyi a teendőnk, hogy a main.dart.js importálásánál egy verziót is adunk meg neki:

<!DOCTYPE html>
<html>
 <head>
   <meta charset="UTF-8">
   <title>to_cache_or_not_to_cache</title>
 </head>
 <body>
   <script src="main.dart.js?version=1"
type="application/javascript"></script>
 </body>
</html>

Ez a megoldás garantálja az alkalmazás újratöltődését, és továbbra is a gyorsítótárban tárolja az adatokat, míg úgy nem döntünk, hogy új frissítést kell végrehajtani. Ekkor csak a verzió számot kell emelnünk. Az az egy kis hátránya van, hogy egy ideig még a felhasználónak az előző verzió maradhat a böngészőjében. Ennek a kiküszöböléséhez szerver-oldali megoldásoknál a Cache-Control-t kell kikapcsolnunk.