Skip to content

Latest commit

 

History

History
138 lines (93 loc) · 19.3 KB

homework.md

File metadata and controls

138 lines (93 loc) · 19.3 KB

Házi feladat

A házi feladat kötelező része a tárgynak. Összesen 60 pontot lehet szerezni. Két módon lehetséges a teljesítése:

Egyéni feladat

Lehetőség van egyéni feladat elkészítésére, melyben ti egy saját ötletet valósítottok meg, és azt adjátok be elkészült feladatnak. Mielőtt belekezdenétek, mindenképp egyeztessétek le (személyesen vagy Teamsen keresztül), hogy milyen alkalmazást akartok megvalósítani. Alapvetően egyéni feladatmegoldást várunk el, de van lehetőség csapatban elkészített alkalmazásra is, ameddig jól elkülöníthető, hogy ki melyik részein dolgozott az alkalamzásnak. Pár példa alkalmazásötletet találhattok az alábbi linkeken:

https://docs.google.com/document/d/1ra0RvqVrHObpckcLPVOH_YXnq2yvysvT/edit?usp=sharing&ouid=107826910246798864586&rtpof=true&sd=true

https://www.figma.com/file/Bs9PgPtAeRSkwCs7Q8ByrH/BME-Flutter-Kurzus

Értékelés

Minden feladat egyénileg kerül értékelésre. A szempontok közé tartozik a feladat komplexitása, a megvalósított alkalmazás kinézete, illetve a kód minősége. Igény esetén van lehetőség javítani is az eredményen. A leadási határidő a 14. hét vége, tehát december 6.. A Moodle rendszerén keresztül kell feltölteni az elkészült megoldást ZIP archívumban. A feltöltendő archívum nevében szerepeljen az egyeni szó is!

Specifikált alkalmazás

A másik lehetőségben egy megadott alkalmazást kell elkészítenetek. Ehhez egy kiinduló projektből kell egy, az alább megadott alkalmazást elkészítenetek. Az ellenőrzés tesztek segítségével fog megtörténni, mely része a kiinduló projektnek, így ti is nyomon tudjátok követni, hogy hány pontot kapnátok az aktuális állapotában. A kiinduló projektet innen tudjátok letölteni

Az alkalmazás

A feladatban egy egyszerű, felhasználok megtekintését megvalósító alkalmazást kell elkészítenetek. Két fő oldalból fog állni: egy bejelentkező, illetve egy listázó felületből. Az alkalmazásban a dio könyvtárat kell használni a hálózati kérések indításáért, ezeket azonban egy Interceptoron alapuló megoldással még a Dart kódból lekezelésre kerülnek, tehát nincs szükség egy külső szerverre való csatlakozáshoz. A projektben az állapotkezelésnél választhattok a provider vagy bloc alapú megoldások között. Elég egy állapotkezelési megoldással elkészíteni az alkalmazást.

Fő lépések

Mivel a kiértékelés a tesztek alapján történik, fontos, hogy azok sikeresen le tudjanak futni. Ehhez szükséges betartani pár alapvető szabályt:

  • A main.dart fájlban található DO NOT MODIFY kommenttel ellátott függvényeket, illetve paramétereket nem szabad módosítani. A configureDependencies() metódusba vehettek fel tetszőleges függőséget a get_it könyvtárhoz, illetve a MaterialApp egyéb paramétereit módosíthatjátok.
  • Minden, a projektben mellékelt osztálynál TILOS az osztálynevek, megadott függvények vagy mezők módosítása.
  • bloc esetében nem érdemes új állapotokat felvenni, mert azok a tesztelés során hibához vezethetnek.
  • Bármelyik állapotkezelési megoldást választva az ahhoz szükséges Provider osztályt (ChangeNotifierProvider vagy BlocProvider) az adott oldal elé vegyük fel. Ezt úgy tudjuk a legkönyebben elérni, ha a MaterialApp-on belül az oldal létrehozásánál wrappeljük az oldalt a megfelelő widgettel. TILOS az adott oldalon belül létrehozni az adott állapotkezelést végző objektumot (ilyenkor az oldalnak a tesztek nem tudnák átadni a mockolt objektumokat).
  • Új fájlokat, osztályokat nyugodtan fel lehet venni.
  • A projekt során szükséges lesz használni a Dio, illetve a SharedPreferences osztályokat. Ezeket a kód tetszőleges helyéről a GetIt.I<Dio>() és GetIt.I<SharedPreferences>() hívásokkal tudjuk elérni. TILOS ezeket az objektumokat magunknak létrehozni.

A Dio objetkum úgy lett konfigurálva, hogy minden kommunikációt kiírjon a konzolba, így könnyen tudjuk ellenőrizni, hogy milyen kérések indulnak el, illetve milyen válaszokat kapunk.

Ha kiválasztottuk az állapotkezelési megoldást, amit használni szeretnénk, akkor elég csak az adott mappára (lib/ui/bloc vagy lib/ui/provider) fókuszálni.

Specifikáció

A fő irányelv, hogy az alkalmazásnak teljesítenie kell a teszteket Értékelés fejezetben leírt módon. Ha eltérés található a szöveges specifikáció és a tesztek között, akkor ezt kérlek jelezzétek, illetve a tesztek élveznek elsőbbséget. Az alkalmazás készítése során nyugodtan lehet a teszteket is vizsgálni, de ne csak ezek alapján készüljön el az.

Login oldal

Az alkalmazás egy bejelentkezési felülettel kell induljon. Itt található egy email és egy jelszó beviteli mező, egy 'Jegyezz meg' checkbox, illetve egy bejelentkezési gomb. Ezek mellett az oldal tetszőlegesen egyedivé tehető. A bejelentkezés gomb megnyomására az alábbi folyamatnak kell megtörténnie:

  1. Ellenőrizzük, hogy érvényesek-e a beírt értékek. Az email mezőnek megfelelő formátumban kell lennie, a jelszónak pedig legalább 6 karakter hosszúnak kell lennie (ezek ellenőrzésére használható például a validators könyvtár). Ha valamelyik nem felel meg, akkor az adott mező alá ki kell írni egy hibaüzenetet (InputDecoration.errorText). Amint megváltozik valamelyik mező tartalma, akkor a mezőhöz tartozó hibaüzenetet egyből törölni kell.
  2. Ha érvényesek a mezők, el kell indítani a bejelentkezési folyamatot. provider esetében a login() metódust kell meghívni, illetve az ezáltal visszaadott Future eredményét megfelelően lekezelni. bloc esetében egy LoginSubmitEvent eseményt kell elküldeni a BLoC felé. Ezeknek csak akkor legyen hatása, ha még nem lett elindítva bejelentkezési folyamat.
  3. A bejelentkezés során töltőállapotba kell jutni. provider esetén ezt az isLoading változó jelzi, míg bloc-nál a LoginLoading állapotot kell kiadni. Töltőállapotban minden bemeneti mezőt le kell tiltani (szöveges mezők esetén az enabled változó, a checkbox és gomb esetén pedig a megfelelő callback paramétereket null értékre állításával).
  4. Ha a hálózati kérés hibával zárul (pl. hibás felhasználónév/jelszót ad meg a felhasználó), akkor a hiba szövegét egy Snackbar-ban kell megjeleníteni. Ehhez provider esetén a login() metódusban egy LoginException hibát dobjunk, amit a login oldalon el tudunk kapni (ekkor a login() által visszaadott Future hibával fog végződni). bloc esetében egy LoginError állapotot küldjünk ki, amit egy BlocListener vagy BlocConsumer segítségével tudunk megfigyelni és lekezelni.
  5. Ha sikeres a kérés, akkor kapni fogunk egy tokent, amivel tudjuk magunkat azonosítani a szerver felé. Ha a felhasználó bepipálta a 'Jegyezz meg' checkboxot, akkor ezt a tokent mentsük el tetszőleges kulccsal a SharedPreferences objektumunkba. Erre a tokenre a későbbiekben még szükség lesz.
  6. Sikeres kérés esetén lépjünk át a lista oldalra. Ehhez nevesített útvonalat használjunk, a /list útvonalra navigáljunk át. provider esetében ezt a login() által visszaadott Future hiba nélküli lefutásával tudjuk detektálni, míg bloc esetén egy LoginSuccess állapotot kell kiküldeni.
  7. Végül ne felejtsük el a töltőállapotot megszüntetni az isLoading hamisra állításával, vagy a LoginForm állapot elküldésével.

A bejelentkezéshez a Dio objektumon egy POST kérést kell indítani a /login útvonalra. A kérés body részében (data változó) egy Map objektumot kell küldeni, melyben az email kulccsal a felhasználó által beírt email címet, a password kulccsal pedig a jelszót küldjük. !!!FONTOS!!! A tesztek sikeres működése érdekében a kérésben mindenképp egy Map objektumot kell átadni, nem működik, hogy egy JSON sorosítható objektumot küldünk át. Ha külön osztályt hozunk létre a kommunikáció elősegítéséhez, akkor manuálisan hívjuk meg a toJson() metódust az objektumon, mellyel egy Map objektumot fogunk megkapni. !!!FONTOS!!!

A beépített Interceptor csak akkor fogja elfogadni a kérést, ha az email cím login@gmail.com, a jelszó pedig password. Ekkor egy Map objektumot kapunk vissza, amiben a token kulccsal találjuk meg a tokent. Egyéb esetben hibával végződik a kérés, a válaszban pedig egy Map-et találunk, melyben message kulccsal találjuk meg a hibaüzenetet, amit meg kell jeleníteni.

Ha a felhasználó valamikor már bejelentkezett a 'Jegyezz meg' funkcióval, akkor az alkalmazás indulásakor egyből be kell léptetni. Ehhez az oldal indulásakor (initState() metóduson belül) provider esetében mindig hívjuk meg a tryAutoLogin() metódust, mely igazzal tér vissza, ha sikerült a bejelentkeztetés. bloc esetében ugyanezet egy LoginAutoLoginEvent esemény kiküldésével, illetve LoginSuccess állapottal tudjuk elérni. Ilyenkor a SharedPreferences objektumon ellenőrizzük, hogy van-e mentett token, és ha igen, jelentkeztessük be a felhasználót és navigáljunk át a lista oldalra.

Lista oldal

Sikeres bejelentkezés esetén egy listázó oldalra kell elnavigálni. A navigáció során figyeljünk arra, hogy a login oldal kikerüljön a navigációs előzményekből (pushReplacementNamed() használatával). A lista oldal tetején egy AppBar legyen, mely tartalmaz egy IconButton gombot. Erre kattintva jelentkeztessük ki a felhasználót (navigáljunk vissza a bejelentkező oldalra, illetve töröljük a tokent mindenhonnan)! Az oldal fő részén jelenítsük meg a betöltött felhasználókat egy függőlegesen tekerhető felületen. Jelenítsük meg a listában a felhasználók profilképét, illetve nevét. A betöltés folyamata az alábbiak alapján működjön:

  1. Az oldalra indulásakor egyből indítsuk el a töltést a loadUsers() meghívásával vagy ListLoadEvent kiküldésével.
  2. A kérés közben a loginhoz hasonlóan töltőállapotban legyen az oldalunk (jelenítsünk meg egy ProgressIndicator-t az oldalon közben).
  3. Hiba esetén a loginhoz hasonlóan egy Snackbar-ban jelenítsük meg a hiba okát.
  4. Sikeres válasz esetén provider esetében a users listában, míg bloc esetében egy ListLoaded állapotban adjuk vissza a betöltött felhasználókat.

A felhasználói adatok betöltéséhez egy GET kérést kell a /users útvonalra küldeni. Az Interceptor csak akkor fog válaszolni, ha egy bejelentkezett felhasználó küldi a kérést, ehhez a headerben el kell küldeni Authorization kulccsal a Bearer <LOGIN TOKEN> sztringet. Ennek hiányában hibával válaszol a szerver. Sikeres kérés esetén egy felhasználói listát fogunk visszakapni, ami Map objektumokat fog tartalmazni, melyekben az avatarUrl kulccsal érjük el a felhasználó profilképének URL címét, illetve name kullcsal kapjuk a megjelenítendő felhasználó nevet.

Értékelés

A pontozást az automatizált tesztek fogják végezni. A test mappán belül található az összes teszt. Minden teszt tartalmaz egy rövid leírást, illetve a leírás végén lévő [x] jelöli, hogy hány pontot ér az adott teszt sikeres teljesítése. A tesztek a következő részekből állnak:

  • common: Itt találhatóak az általános tesztek, melyeknek állapotkezelési megoldástól függetlenül teljesíteni kell. Összesen 25 pont szerezhető ezekből.
  • bloc / provider: Itt találhatóak az állapotkezelési megoldásoktól függő tesztek. Ezek közül a választott állapotkezelési könyvtár tesztjeit kell csak futtatni. Ezekből a tesztekből is összesen 25 pont szerezhető.
  • screenshots: Egy speciális teszt, mely bejárja a teljes alkalmazást, és közben összesen 8 képernyőképet készít. A sikeres futtatás 2 pontot ér, míg minden jó képernyőkép 1 pontot, összesen 10 pontért. Ahhoz, hogy elkészüljenek a képek, a tesztelés során az --update-goldens argumentumot meg kell adni.

A képernyőképek készítését én Windows platformon tudtam kipróbálni. Ha valakinek bármilyen problémája adódna ezzel a résszel, keress meg Teams-en.

Android Studio esetében a tesztek helyesen felkonfigurálva megtalálhatóak a konfigurációk között.

!!FIGYELEM!! A tesztek módosítása szigorúan TILOS. A kiértékelés során felül lesznek írva az eredeti tesztekkel, ezért ezen belül bármi nemű módosítás el fog veszni, és az eredeti kiértékelés szerint lesz a pontozás.

A kiértékelés automatizálásához szükséges még egy segéd fájl kitöltése, mely a projekt gyökerében megtalálható TEST_INFO.txt fájlban kell megtenni. Ebben soronként a következő információkat kell beírni

  • Neptun kód
  • Választott állapotkezelési megoldás (bloc vagy provider)
  • Opcionális: Szöveges értékelés az automatizált házi feladatról

Az elkészült projektet becsomagolva a Moodle-ön keresztül kell beadni (elég csak a lib mappát és pubspec.yaml fájlt becsomagolni). A házi feladat sikeres teljesítéséhez legalább 24 pontot el kell érni. A beadási határidő a 14. hét vége, tehát december 6...

Beadás

A beadás a Moodle felületén keresztül történik. Ide az elkészült projektet kell feltölteni becsomagolt verzióban (.zip kiterjesztéssel). Figyeljetek rá, hogy az archívum ne legyen túl nagy méretű (nagyobb, mint 10 MB). Ehhez érdemes lefuttatni a flutter clean utasítást a projektmappán belül az archívum létrehozása előtt.

Tippek a megoldáshoz

Az automatizált tesztek miatt van pár rész, amire érdemes még odafigyelni a megoldás során, illetve pár hiba, amit nem könnyű elsőre értelmezni.

  • type 'Null' is not a subtype of type '***': Ilyen hibákat tipikusan akkor dob a teszt, ha nem találja a mockolt objektum azt a függvényhívást, amit a kód hívna. Ilyenkor érdemes megnézni a tesztet, hogy pontosan milyen hívások vannak értelmezve a mockolt objektumon (ezeket a when() hívásokon belül láthatjátok). Tipikus problémát szokott okozni, hogy a lista lekérésénél az autentikációs token a Dio get() hívásának az options paraméterében kerül átadásra. Ez bár működő megoldás, de minden kérésben meg kellene adni, ezért inkább egy interceptort szoktak használni. A mostani projektben arra kérnélek titeket, hogy a Dio objektum options változójában lévő headert állítsátok, az itt megadott értékek minden kéréshez hozzá fognak adódni.
  • type 'List<dynamic>' is not a subtype of type 'List<***>': Ilyen hibákat akkor szokott dobni, ha a hálózati kommunikáció során olyan típust feltételeztek egy gyűjteményről, amit a Dart futás közben nem lát. Figyeljetek arra, hogy például egy .map() hívás nem fogja mindig átkasztolni a gyűjteményt az új típusra, ilyenkor az explicit cast() függvényhívással tudtok olyan típusú gyűjteményt készíteni, amire szükségetek van (tehát pl. result.map(...).cast<UserItem>.toList()).
  • Hiányzó dependenciák: Előfordulhat, hogy bizonyos tesztek hibát dobnak arra, hogy nincsen a GetIt-be beregisztrálva objektum. Ilyenkor ellenőrizzétek, hogy minden saját osztályt a configureCustomDependencies() vettetek fel. Ezek mellett a *_page_*_test teszteknél nincsen szükség a GetIt-re a sikeres lefutáshoz. Ha mégis hibát dobna, az valószínűleg azért történik, mert az oldal létrehozásakor egyből megpróbáljátok kiolvasni valamelyik függőséget. Mivel magának az oldalnak nincsen szüksége közvetlenül az adott függőségekre, érdemes azokat azokba az eseménykezelő függvényekbe mozgatni, ahol tényleg aktuálisan szükség van rá.