Nemrégiben bevezető útmutatókat kellett készítenem a ClickUp újoncainak! Ez egy nagyon fontos feladat volt, mert sok új felhasználó készült felfedezni a platformot a Super Bowl-on bemutatott hihetetlenül vicces reklámunknak köszönhetően! ✨
via ClickUp
A bemutató segítségével számos új felhasználónk, akik talán még nem ismerik a ClickUp-ot, gyorsan megértheti az alkalmazás több funkciójának használatát. Ez egy folyamatos erőfeszítés, akárcsak az új ClickUp University erőforrás, amelyet jelenleg fejlesztünk! 🚀
Szerencsére a ClickUp Flutter mobilalkalmazás mögötti szoftverarchitektúra lehetővé tette, hogy ezt a funkciót meglehetősen gyorsan megvalósítsam, még az alkalmazás valódi widgetjeinek újrafelhasználásával is! Ez azt jelenti, hogy a bemutató dinamikus, reszponzív és pontosan megegyezik az alkalmazás valódi képernyőivel – és ez akkor is így marad, ha a widgetek továbbfejlődnek.
A funkciót a megfelelő feladatok szétválasztásának köszönhetően is sikerült megvalósítanom.
Nézzük meg, mire gondolok! 🤔
A feladatok szétválasztása
A szoftverarchitektúra tervezése az egyik legbonyolultabb feladat a mérnöki csapatok számára. Az összes feladat közül mindig nehéz előre látni a szoftverek jövőbeli fejlődését. Éppen ezért egy jól rétegzett és szétválasztott architektúra létrehozása sok mindenben segíthet Önnek és csapattársainak!
A kis, egymástól független rendszerek létrehozásának fő előnye kétségkívül a tesztelhetőség! És ez segített nekem az alkalmazás meglévő képernyőinek demo alternatívájának létrehozásában!
Lépésről lépésre
Hogyan alkalmazhatjuk ezeket az elveket egy Flutter alkalmazásra?
Néhány technikát, amelyet a ClickUp fejlesztéséhez használunk, egyszerű példákkal mutatunk be.
A példa annyira egyszerű, hogy talán nem világít rá minden előnyére, de higgye el, segítségével sokkal könnyebben karbantartható Flutter alkalmazásokat hozhat létre komplex kódbázisokkal. 💡
Az alkalmazás
Példaként létrehozunk egy alkalmazást, amely az Egyesült Államok népességét mutatja be évente.
via ClickUp
Két képernyőnk van itt:
- HomeScreen: egyszerűen felsorolja az összes évet 2000-től napjainkig. Amikor a felhasználó rákattint egy évre, a DetailScreen képernyőre navigál, ahol a navigációs argumentum a kiválasztott évre van beállítva.
- DetailScreen : lekérdezi az évet a navigációs argumentumból, meghívja a datausa.io API-t az adott évre, és elemezi a JSON adatokat, hogy kivonja a kapcsolódó népességi értéket. Ha adatok állnak rendelkezésre, megjelenik egy címke a népességszámmal.
A DetailScreen implementációjára fogunk összpontosítani, mivel az aszinkron hívásával a legérdekesebb.
1. lépés: Naiv megközelítés

Alkalmazásunk legkézenfekvőbb megvalósítása az, ha egyetlen StatefulWidget-et használunk az egész logikához.
Hozzáférés az év navigációs argumentumához
A kért év eléréséhez elolvassuk a RouteSettings-et a ModalRoute örökölt widgetből.
HTTP-hívás
Ez a példa a http csomag get funkcióját hívja meg, hogy lekérje az adatokat a datausa.io API-ból, a kapott JSON-t a dart:convert könyvtár jsonDecode metódusával elemzi, és a Future-t a _future nevű tulajdonsággal az állapot részeként tárolja.
Renderelés
A widgetfa létrehozásához FutureBuilder-t használunk, amely a _future aszinkron hívás aktuális állapotának megfelelően újjáépíti magát.
Áttekintés
Rendben, a megvalósítás rövid és csak beépített widgeteket használ, de most gondoljon vissza eredeti szándékunkra: demo alternatívák (vagy tesztek) készítése ehhez a képernyőhöz. Nagyon nehéz ellenőrizni a HTTP-hívás eredményét, hogy az alkalmazás egy bizonyos állapotban jelenjen meg.
Itt segít nekünk az irányítás megfordítása koncepciója. 🧐
2. lépés: A vezérlés megfordítása

Ez az elv új fejlesztők számára nehezen érthető (és nehezen magyarázható), de az általános elképzelés az, hogy a komponenseinken kívüli problémákat kivonjuk – hogy azok ne legyenek felelősek a viselkedés kiválasztásáért – és helyette delegáljuk azokat.
Egy általánosabb helyzetben ez egyszerűen abból áll, hogy absztrakciókat hozunk létre és implementációkat illesztünk be komponenseinkbe, hogy azok implementációja később szükség esetén megváltoztatható legyen.
De ne aggódjon, a következő példánk után minden világosabb lesz! 👀
API kliens objektum létrehozása
Az API-hoz intézett HTTP-hívások ellenőrzése érdekében a megvalósítást egy külön DataUsaApiClient osztályba különítettük el. Emellett létrehoztunk egy Measure osztályt is, hogy az adatok kezelése és karbantartása egyszerűbb legyen.
API-kliens biztosítása
Példánkban a jól ismert provider csomagot használjuk, hogy egy DataUsaApiClient példányt illesszünk be a fa gyökérkönyvtárába.
Az API kliens használata
A szolgáltató lehetővé teszi, hogy bármely leszármazott widget (például a DetailScreen) elolvassa a fa legközelebbi DataUsaApiClient felső elemét. Ezután a getMeasure metódusával elindíthatjuk a Future-t, a tényleges HTTP implementáció helyett.
Demo API kliens
Most már kihasználhatjuk ezt az előnyt!
Ha még nem tudta: a dart bármely osztálya implicit módon meghatározza a hozzá tartozó interfészt is. Ez lehetővé teszi számunkra, hogy a DataUsaApiClient alternatív implementációját biztosítsuk, amely mindig ugyanazt az instanciát adja vissza a getMeasure metódushívásokból.
Ez a módszer
Ez a módszer
Demo oldal megjelenítése
Most már minden kulcsunk megvan a DetailPage demo példájának megjelenítéséhez!
Egyszerűen felülírjuk a jelenleg biztosított DataUsaApiClient példányt azzal, hogy a DetailScreen-t egy olyan szolgáltatóba csomagoljuk, amely helyette egy DemoDataUsaApiClient példányt hoz létre!
Ennyi az egész – a DetailScreen ehelyett ezt a demo példát olvassa be, és HTTP-hívás helyett a demoMeasure adatainkat használja.
Áttekintés
Ez egy remek példa az inverziós vezérlésre. A DetailScreen widgetünk már nem felelős az adatok lekérésének logikájáért, hanem ezt egy dedikált kliens objektumra ruházza át. Most már képesek vagyunk létrehozni a képernyő demo példányait, vagy widget teszteket implementálni a képernyőnkre! Fantasztikus! 👏
De még ennél is jobbat tudunk!
Mivel például nem tudjuk szimulálni a betöltési állapotot, nem tudjuk teljes mértékben ellenőrizni az állapotváltozásokat a widget szintjén.
3. lépés: Állapotkezelés

Ez egy forró téma a Flutterben!
Biztosan már olvastál hosszú fórumszálakat azokról, akik megpróbálják kiválasztani a legjobb állapotkezelési megoldást a Flutterhez. Hogy egyértelmű legyek, ebben a cikkben nem ezt fogjuk tenni. Véleményünk szerint, amíg elkülöníti az üzleti logikát a vizuális logikától, addig nincs gond! Ezeknek a rétegeknek a létrehozása nagyon fontos a karbantarthatóság szempontjából. Példánk egyszerű, de a valós alkalmazásokban a logika gyorsan bonyolulttá válhat, és az ilyen elkülönítés sokkal könnyebbé teszi a tiszta logikai algoritmusok megtalálását. Ezt a témát gyakran állapotkezelésként foglalják össze.
Ebben a példában egy alapvető ValueNotifier-t használunk egy Provider mellett. De használhattunk volna flutter_bloc-ot vagy riverpod-ot (vagy más megoldást), és az is remekül működött volna. Az elvek ugyanazok maradnak, és ha elkülönítette az állapotokat és a logikát, akkor akár az egyik másik megoldásból is átviheti a kódbázisát.
Ez a szétválasztás segít nekünk a widgetjeink állapotának ellenőrzésében is, így minden lehetséges módon modellezhetjük azokat!
Dedikált állapot létrehozása
Ahelyett, hogy a keretrendszer AsyncSnapshot funkciójára támaszkodnánk, most a képernyő állapotát DetailState objektumként ábrázoljuk.
Fontos továbbá a hashCode és az operator == metódusok implementálása is, hogy objektumainkat érték szerint összehasonlíthatóvá tegyük. Ez lehetővé teszi számunkra, hogy felismerjük, ha két példányt különnek kell tekintenünk.
💡 Az egyenértékű vagy befagyasztott csomagok kiváló lehetőségek a hashCode és az operator == módszerek implementálásához!
💡 Az egyenértékű vagy befagyasztott csomagok kiváló lehetőségek a hashCode és az operator == módszerek implementálásához!
Állapotunk mindig egy évhez kapcsolódik, de négy különböző lehetséges állapotunk is van attól függően, hogy mit szeretnénk megmutatni a felhasználónak:
- NotLoadedDetailState: az adatok frissítése még nem kezdődött el
- LoadingDetailState: az adatok betöltése folyamatban van
- LoadedDetailState: az adatok sikeresen betöltődtek a kapcsolódó mérőszámmal
- NoDataDetailState: az adatok betöltődtek, de nincs elérhető adat
- UnknownErrorDetailState: a művelet ismeretlen hiba miatt sikertelen volt.
Ezek az állapotok egyértelműbbek, mint az AsyncSnapshot, mivel valóban a mi felhasználási eseteinket képviselik. És ez ismét javítja a kódunk karbantarthatóságát!
💡 Javasoljuk, hogy a logikai állapotok ábrázolásához használja a freezed csomag Union típusait! Ez számos hasznos funkciót kínál, például a copyWith vagy a map metódusokat.
💡 Javasoljuk, hogy a logikai állapotok ábrázolásához használja a freezed csomag Union típusait! Ez számos hasznos funkciót kínál, például a copyWith vagy a map metódusokat.
A logika beépítése a Notifierbe
Most, hogy már rendelkezünk az állapotunk ábrázolásával, el kell tárolnunk valahol annak egy példányát – ez a DetailNotifier célja. A DetailNotifier az aktuális DetailState példányt az érték tulajdonságában tárolja, és módszereket biztosít az állapot frissítéséhez.
NotLoadedDetailState kezdeti állapotot és frissítési módszert biztosítunk az adatok API-ból való betöltéséhez és az aktuális érték frissítéséhez.
Állapot megadása a nézethez
A képernyőnk állapotának instantiálásához és megfigyeléséhez a szolgáltatóra és annak ChangeNotifierProvider szolgáltatására is támaszkodunk. Ez a fajta szolgáltató automatikusan megkeresi az összes létrehozott ChangeListener-t, és minden alkalommal, amikor változásról értesítést kap (amikor értesítő értékünk eltér az előzőtől), a Consumer újjáépítését indítja el.
Áttekintés
Remek! Alkalmazásunk architektúrája kezd nagyon jól kinézni. Minden jól definiált rétegekre van felosztva, mindegyiknek megvan a maga feladata! 🤗
A tesztelhetőséghez azonban még egy dolog hiányzik: meg akarjuk határozni a jelenlegi DetailState-et, hogy ellenőrizzük a kapcsolódó DetailScreen állapotát.
4. lépés: Vizuális dedikált elrendezés widget

Az utolsó lépésben kissé túl sok felelősséget ruháztunk a DetailScreen widgetre: ez volt felelős a DetailNotifier példányosításáért. És ahogy azt korábban láttuk, igyekszünk elkerülni a logikai felelősséget a nézet rétegben!
Ezt könnyen megoldhatjuk úgy, hogy egy újabb réteget hozunk létre a képernyő widgetünkhöz: a DetailScreen widgetünket két részre osztjuk:
- A DetailScreen feladata a képernyő különböző függőségeinek beállítása a jelenlegi alkalmazás állapotából (navigáció, értesítők, állapot, szolgáltatások stb.).
- A DetailLayout egyszerűen átalakítja a DetailState-et egy dedikált widgetfa-struktúrává.
A kettő kombinálásával egyszerűen létrehozhatunk DetailLayout demo/teszt példákat, de a DetailScreen-t a valódi felhasználási esethez használhatjuk az alkalmazásunkban.
Dedikált elrendezés
A feladatok jobb szétválasztása érdekében a Consumer widget alatt található összes elemet egy külön DetailLayout widgetbe helyeztük át. Ez az új widget csak adatokat fogyaszt, és nem felelős semmilyen instantiálásért. Csak a read állapotot konvertálja egy adott widget fává.
A ModalRoute. call és a ChangeNotifierProvider példány a DetailScreen-ben marad, és ez a widget egyszerűen visszatér a DetailLayout-hoz egy előre konfigurált függőségi fával!
Ez a kisebb fejlesztés kifejezetten a szolgáltatók használatára vonatkozik, de észreveheti, hogy hozzáadtunk egy ProxyProvider-t is, így bármely leszármazott widget közvetlenül felhasználhatja a DetailState-et. Ez megkönnyíti az adatok modellezését.
Widgetek kivonása dedikált osztályokként
Ne habozzon widgetfát külön osztályba kivonni! Ez javítja a teljesítményt és könnyebbé teszi a kód karbantartását.
Példánkban minden egyes társított állapot típushoz egy vizuális elrendezési widgetet hoztunk létre:
Bemutató példák
Most már teljes ellenőrzésünk van felett, hogy mit tudunk modellezni és megjeleníteni a képernyőn!
Csak be kell burkolnunk a DetailLayout-ot egy Provider-rel
Következtetés
Kétségtelen, hogy nem könnyű fenntartható szoftverarchitektúrát létrehozni! A jövőbeli forgatókönyvek előrejelzése sok erőfeszítést igényelhet, de remélem, hogy az általam megosztott néhány tipp segít Önnek a jövőben!
A példák egyszerűnek tűnhetnek – akár úgy is tűnhet, hogy túlkomplikáljuk a dolgokat –, de az alkalmazás komplexitásának növekedésével ezek a szabványok nagy segítséget nyújtanak majd Önnek! 💪
Élvezze a Flutter használatát, és kövesse a blogot, hogy több ilyen technikai cikket olvashasson! Maradjon velünk!

