Cookies

We use cookies to improve your browsing experience on our site, show personalized content and analyze site traffic. By choosing "I Accept", you consent to our use of cookies and other tracking technologies.

To the top
Close
Zengo - Flutter mobil projekt alkalmazása web projektként
2022. 08. 20.

Flutter mobil projekt alkalmazása web projektként

Zengo - reading time8 minutes reading time

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.