Online Android szakkör (DKRMG)
Elérkeztünk az Android szakkör utolsó leckéjéhez. A leírás végéig befejezzük az előző alkalommal megkezdett üzenőfal alkalmazásunkat: végre kilistázzuk a telefon kijelzőjén a közös üzeneteket, illetve kicsit kitupírozzuk a korábbi kódunkat, hogy jobban kezelje a hibákat. Az elkészült programmal már tényleg könnyen lehet használni a webes felület nélkül is a közös üzenőfalat.
Az előző lecke végén jogosan tehettük volna fel a kérdést: ha egyetlen paranccsal tudtunk üzenetet küldeni, akkor miért nem írtuk meg a lista lehívás kódját is? Azért használunk külső API-kat, hogy utána gyorsan és könnyedén tudjunk programokat írni! Miért nem fejeztük be ezt az egész Üzenőfal alkalmazást már akkor?
A válasz sajnos az, hogy a hosszan tartó műveletek kicsit megnehezítik a mai programozók életét. Mire is gondolunk itt egészen pontosan? Ha egy bonyolult matematikai problémát szeretnénk megoldani (például a π kiszámítása nagyon pontosan) vagy egy nagy méretű fájlt szeretnénk feldolgozni (például egy komplett nagyszótár), az bizony eltarthat jó pár másodpercig. Hasonlóképpen a hálózati kommunikáció (pl. az üzenőfal lehívása vagy bármilyen fájl letöltése) is eltarthat pár másodpercig, főként ha mondjuk akadozik az internet! Miért is gond ez?
Tegyünk egy rövid időutazást körülbelül húsz évvel ezelőttre, amikor a modern grafikus operációs rendszerek még gyerekcipőben jártak, és a felhasználók többnyire csak úgynevezett parancssorokat használtak a számítógépük működtetése közben. A parancssorok világában, mint azt az alábbi képek is mutatják, a számítógépünk egyszerre mindig egy dologgal foglalkozott. Ha elindítottunk egy játékot, vagy csak megnyitottunk egy dokumentumot, a képernyőről minden mást eltűnt. Nem volt még egér sem, nem hogy több érintést érzékelő LCD képernyő.
Ebben a világban teljesen normális dolog lett volna írni egy olyan programot, ami az elindulásától kezdve a felhasználó felé se nézve elvégzi a dolgát, majd végeredményként visszadob valami kedves üzenetet (az eredménnyel, vagy valami barátságtalan hibakóddal). Nem kellett más programokkal és a felhasználóval versenyezni; a számítógép, ha a mi programunkat futtatta, akkor csak ránk figyelt.
Nem csak a számítógépeink változtak az elmúlt évtizedek folyamán (mind méretben és tudásban), de a felhasználók elvárásai is módosultak. Nézz csak kicsit körül a számítógéped monitorján! Hány program is fut egymás mellett?
Sőt! Nem elég, hogy több programot futtatsz egyszerre, de a programok többségétől elvárod, hogy egyszerre több mindent is csináljon! Képzeld el például, mennyire felháborító volna, ha az egész számítógéped lefagyna, amíg a következő online videót be nem tölti a böngésződ!
Az egyik legelső leckében leszögeztük: az általunk írt Java program sorok mindig szépen szabályosan futnak le egymás után. Minden parancs megvárja, amíg az előző utasítás el nem végezte a feladatát. Viszont a fenti kitérőből megtanultuk: a mai világban egyszerűen nem szabad hosszú másodpercekig felelőtlenül feltartani a programunk / számítógép / okostelefon futását.
Utolsó próbatételként írjuk be az alábbi sorokat az üzenőfal programunkhoz, a küldés gomb lenyomását kezelő függvényének elejére:
try {
Thread.sleep(30000); // aludjunk most itt 30 másodpercet
} catch (InterruptedException e) {
}
Futtasd a programot, és nézd meg, hogy mi történik a gomb lenyomása után! Ha nem tapasztalsz semmi különöset, próbáld meg többször megnyomni a gombot, szerkeszteni az EditText szövegét, stb. Hogy viselkedik a program? Írd meg e-mailben, hogy mik a tapasztalataid!
Ha kipróbáltad, utána gyorsan töröld ki a fenti kódot! Nem szeretjük az alvó, lusta programokat!
Nem lelőve a poént a pontos viselkedésről annyit megsúghatunk, hogy se a programunk, se az Android rendszer nem nézi jó szemmel ezt a tíz másodperces akadást!
De várjunk csak! Hiszen
ebben a függvényben küldjük el az üzenetünket, ami természetesen hálózati kommunikációval
jár! Miért nem akadozik a kozosUzenetek.insert(uzenet);
parancs?
Az Azure API tudja, hogy a program futását nem szabad megakasztani, ezért a hálózati
kommunikációt a háttérben végzi. Az insert
utasításunk valójában csak egy
ígéretet tesz nekünk, hogy megpróbálja elküldeni az üzenetünket, és tovább engedi a program
futását. Az üzenet küldése ilyenkor egy másik program szálon a háttérben
történik.
És mindez úgy működött az előző leckében, hogy nem is tudtunk róla. Igen ám, de ha a háttérben futó művelet eredményről szeretnénk értesítést kapni, azért már dolgoznunk is kell egy keveset! Ez pedig főként az üzenetek lehívásánál elkerülhetetlen lesz.
Az előző leckében már megírtuk, hogy a telefonunkról tudjunk üzenetet küldeni a közös üzenőfalra. Ez jó esetben működött is, de elég bizonytalan megoldás volt: ha a szerver valamiért nem működik például, vagy csak ki volt kapcsolva a wifi, arról mi nem szerzünk tudomást, legfeljebb úgy, hogy nem találjuk az üzenetünket a webes változaton. Ezen jó lenne javítani!
Az előző leckében úgy küldtük el az üzenetet, hogy a közös üzenetek listáját jelképező
kozosUzenetek
változónak meghívtuk az insert
belső függvényét.
kozosUzenetek.insert(uzenet);
Ennek az eredményével nem foglalkoztunk. Pedig érdemes, ugyanis ez az insert
függvény egy ígéretet ad vissza (a változó típusa ListenableFuture<valami>
).
Az ígéretet pedig később számon kérhetjük! De mi is ez az ígéret? Egy sokáig tartó
művelet eredményét nem várhatjuk meg, hiszen fentebb megbeszéltük, hogy akkor lefagyna az
alkalmazás. Ez az ígéret megoldást nyújt erre, ugyanis rá lehet akasztani két
speciális függvényt, amik majd csak akkor fognak lefutni, ha a munka
befejeződött.
Mit értünk a munka befejeződésén? Két eshetőséggel kell számolnunk:
Siker (onSuccess): a telefon elküldte az üzenetet a szervernek, az megkapta, feldolgozta, hozzáadta a többihez, és ezt vissza is mondja a telefonnak. Vagyis a munka akkor ért véget, ha a telefon megbizonyosodott róla, hogy az üzenet célba ért.
Hiba (onFailure): valahol valami hiba miatt nem sikerül a megígért művelet.
Most mentsük el ezt az ígéretet, hogy később számon kérhessük. Ehhez kell egy változó a
küldés gomb lenyomását kezelő függvénybe, aminek a típusa ListenableFuture<Uzenet>
.
Hívjuk mondjuk kuldes
-nek. Tehát: a
kozosUzenetek.insert(uzenet);
sor a következőre változik:
ListenableFuture<Uzenet> kuldes = kozosUzenetek.insert(uzenet);
Most már tudjuk, hogy mit ígér nekünk az Azure szolgáltatás, kérjük is számon rajta, hiszen az ígéret szép szó, ha betartják, úgy jó!
Ezt úgy tudjuk elérni, ha az alábbi sorokat beillesztjük az insert parancs alá
(feltételezzük, hogy az ígéret a kuldes
nevű változóban van.):
// kuldes ígéret számonkérése a jövőben
Futures.addCallback(kuldes, new FutureCallback<Uzenet>() {
@Override
public void onSuccess(Uzenet result) {
// {1} Ez az onSuccess függvény fut,
// ha sikerült elvégezni a munkát (elküldeni az üzenetet)
}
@Override
public void onFailure(Throwable t) {
// {2} Ez az onFailure függvény fut, ha valami hiba történt
}
});
Nézzük meg részletesebben a két függvényt. Az {1} onSuccess
akkor fut le, ha a
szerver feldolgozta az üzenetünket, minden rendben volt, nem történt hiba, és ez a válasz
vissza is jutott hozzánk. Ide érdemes írni azokat a sorokat, amik pl. értesítik a
felhasználót, hogy sikerült elküldeni az üzenetet. Pl. egy kis rezgés
(bővebben: szorgalmi)
A {2} onFailure
akkor fut, ha a szerver valami miatt nem fogadta el az
üzenetünket, vagy el se jutott odáig (pl. nincs bekapcsolva a WiFi). Ilyenkor érdemes
megjeleníteni egy Toast* üzenetet.
*Itt most a Gyorssegélyben látottaktól eltérően a this
helyére
<activityneve>.this
-t kell írni. pl.: MainActivity.this
. Ennek okait nem
részleteznénk itt
a lecke közepén.
Próbáljuk ki, hogy továbbra is működőképes-e az alkalmazásunk. Teszteljük kikapcsolt WiFivel is (figyelj, hogy a mobilnet se legyen bekapcsolva!), hogy megjelenik-e a Toast.
Az üzenet küldésekor láthattuk, hogyan lehet a hosszan tartó műveletek ígéreteit elmenteni egy változóba, és rögtön megszabni azt is, hogy a sikeres/sikertelen befejeztük után mi történjen. A létező üzenetek lehívásakor gyakorlatilag ugyanezt fogjuk tenni.
Mielőtt megírjuk magát a letöltést végző kódunkat, gondoljuk át, hogy mikor, minek hatására szeretnénk majd letölteni az üzeneteket:
Induláskor jó lenne, ha rögtön megjelennének az üzenetek
Ha új üzenetet küldtünk, újra le kell tölteni az üzenőfal tartalmát, hogy lássuk, megjelent-e az új bejegyzés
Felmerülhet az igény manuális frissítésre is (pl. menüből)
Hogyan értesülünk a mások által írt új üzenetekről? Az ügyesebbek akár egy időzítőt is beállíthatnak, ami rendszeresen, pl. percenként frissíti az üzenetek listáját.
Láthatjuk, hogy akár négy különböző helyen is szükségünk van az üzenetek letöltésére és megjelenítésére. Elméletileg működne az is, hogy egy helyen megírjuk, majd az összes többi helyre átmásoljuk a kódot, de ez egyrészt egy nagyon csúnya, és nem hatékony megoldás, másrészt saját magunk ellen dolgoznánk, ugyanis ha kiderül, hogy valamit módosítani kell a letöltést végző kódon (pl. ilyenkor is szeretnénk egy rezgést), akkor azt mind a négy helyen módosítani kell. Szinte biztos, hogy valamelyiket el fogod felejteni, vagy csak simán elrontod… Sokkal előrelátóbb megoldás, ha az üzenetek szerverről történő letöltéséhez írunk egy külön függvényt, és ezt csak meg kell hívni ott, ahol le szeretnénk tölteni az üzeneteket.
Ennek a függvénynek nem lesz se paramétere, sem visszatérési értéke, így kezdetben nagyon egyszerű lesz.
public void letolt() {
}
Ezt a függvényt az onCreate
és a gomb onClick
függvényekkel egy
szintre rakd, figyelj, hogy még véletlenül se egy másik függvényen belülre!
Az üzenetek letöltése hálózati kommunikációval jár, tehát mindaz igaz rá, amit az üzenet küldésénél már láttunk, és ezt is egy ígéret segítségével fogjuk megoldani.
Először nézzük meg, hogyan tudjuk az összes üzenetet elkérni a szervertől:
kozosUzenetek.execute();
Ha jobban belegondolunk, ezzel nem biztos, hogy jól járnánk. Amikor ezt írom 40 üzenet van eltárolva, pedig eddig csak mi küldtünk be teszt-üzeneteket. Ha mindenki elkezdi próbálgatni az alkalmazását, több száz, akár több ezer üzenet is lehet majd a felhőben. Ezt mind letölteni sokáig tarthatna, és a telefon sem feltétlenül tudná hatékonyan megjeleníteni.
Jó lenne lekorlátozni, hogy csak néhány, pl. 15 üzenetet töltődjön le.
Erre való a .top(<szám>)
belső függvény. Ezt az .execute()
elé kell beszúrni, így:
kozosUzenetek.top(15).execute();
Most már kezelhető mennyiségű üzenet érkezik majd, de pontosan melyikek? Ezt az Azure felhő
dönti el, és nem biztos, hogy ő is arra a tizenöt üzenetre gondol, mint mi. Célszerű lenne a
15 legfrissebbet lekérni. Ehhez először meg kell mondani, hogy rendezetten kérjük
az üzeneteket, mégpedig a __createdAt
tulajdonságuk alapján csökkenő
sorrendben. Erre az .orderBy(<tulajdonság>,<irány>)
belső függvény használható, így:
kozosUzenetek.orderBy("__createdAt", QueryOrder.Descending).top(15).execute();
A fenti sor megkéri a szolgáltatást, hogy először rendezze az üzeneteket, majd az első tizenöt elemet küldje el. Fordítva is működne? Gondold végig!
Most már meghatároztuk, hogy melyik üzeneteket és milyen sorrendben kérjük. De fentebb már
említettük, az .execute() függvény nem várhatja meg az üzenetek lekérését. Inkább, a korábbi
.insert(uzenet)
-hez nagyon hasonló ígérettel tér vissza. Ezt mentsük
is el! Az ígéret típusa: ListenableFuture<MobileServiceList<Uzenet>>
, neve legyen letoltes
!
public void letolt() {
ListenableFuture<MobileServiceList<Uzenet>> letoltes = kozosUzenetek.orderBy("__createdAt",QueryOrder.Descending).top(15).execute();
}
A letöltés ígéretének beváltása nagyon fog hasonlítani az üzenetküldés ígéretének beváltásához:
Futures.addCallback(letoltes, new FutureCallback<MobileServiceList<Uzenet>>() {
@Override
public void onSuccess(MobileServiceList<Uzenet> result) {
// {1} ez fut, ha sikerült elvégezni a munkát (letölteni az üzeneteket)
}
@Override
public void onFailure(Throwable t) {
// {2} Ez fut, ha valami hiba történt
}
});
Nézzük meg, mit tudunk csinálni az {1} onSuccess
függvényben: Szemfülesebbek
észrevehették, hogy hogyan épül fel az ígéret típusa.
Az eleje kötelezően ListenableFuture
, ez jelzi, hogy egy ígérettel van
dolgunk.
Utána két kacsacsőr között található, hogy mire tesz ígéretet, azaz milyen típusú
"gyümölcse lesz a munkának". Ugyan ez a típus jelenik meg az {1} onSuccess
függvény egyetlen paraméterének típusában is.
Remélem senki nem lepődik meg, ha azt állítjuk, hogy az {1} onSuccess
függvény
paramétere a letöltött üzenetek listája, hiszen ez a "munka gyümölcse". Már csak egyetlen
zavaró tényező van: Mi az a MobileServiceList<Uzenet>
?
Miért nem kapunk egy jól ismert ArrayList<Uzenet>
listát?
Valójában az MobileServiceList
is egyfajta ArrayList
, csak az
Azure fejlesztői kiegészítették kicsit. A mi szempontunkból ugyan olyan a kettő.
Sikeres eredmény esetén töröljük az uzenetek lista jelenlegi tartalmát (uzenetek.clear();
)
Utána pedig rögtön hozzá is adhatjuk a letöltött üzenetek összes elemét a saját listánkhoz, így:
uzenetek.addAll(result);
Ezek után ne felejtsük el értesíteni az adaptert a változásról, írjunk valami
figyelmeztetést a {2} onFailure
függvénybe (ami ugye akkor fut le, ha valami
hiba történt) és készen is vagyunk az üzenetek letöltését végző függvényünk megírásával!
Reméljük, sikerült követni, de ha esetleg mégsem, vagy csak leellenőriznéd a munkádat, kattintás után láthatod egyben a letöltő függvényt.
public void letolt() {
ListenableFuture<MobileServiceList<Uzenet>> letoltes =
kozosUzenetek
.orderBy("__createdAt", QueryOrder.Descending)
.top(15)
.execute();
Futures.addCallback(letoltes, new FutureCallback<MobileServiceList<Uzenet>>() {
@Override
public void onSuccess(MobileServiceList<Uzenet> result) {
uzenetek.clear();
uzenetek.addAll(result);
adapter.notifyDataSetChanged();
}
@Override
public void onFailure(Throwable t) {
Toast.makeText(MainActivity.this, "Hiba az üzenetek letöltésénél!",
Toast.LENGTH_SHORT).show();
Log.e("MainActivity", "Hiba az üzenetek letöltésekor.", t);
}
});
}
Pár felesleges részt illene eltávolítani a programunkból. Jelenleg a Küldés gombra nyomáskor
létrehozunk egy új üzenetet, azt egyrészt elküldjük a szervernek (kozosUzenetek.insert(uzenet);
ígéret), másrészt hozzáadjuk a saját helyi listánkhoz is. Ha felidézzük az imént megírt
letöltő függvényünket, akkor láthatjuk, hogy ott azt írtuk, hogy amint megérkeznek a
szerverről a friss üzenetek, a saját listánkat teljesen kiürítjük
(uzenetek.clear();
), tehát feleslegesen adjuk hozzá. Töröljük ki azt az
utasítást, ami az uzenetek listához adja hozzá az üzenetet (uzenetek.add(uzenet);)
Szintén felesleges az előző lecke elején az onCreate
-be írt üdvözlő üzenet
hozzáadása is, ugyanis az is ki lesz törölve.
A lecke befejezéséhez már csak egy apró, ám annál fontosabb feladat maradt hátra: használjuk is az imént megírt függvényünket!
Ehhez annyit kell tennünk, hogy ahol szeretnénk, hogy letöltődjenek az üzenetek, ott
meghívjuk a saját letolt() függvényünket. Ilyen hely lehet például az onResume
függvény (meg kell írni!), hogy mindig, amikor a felhasználó megpillantja az alkalmazásunkat
letöltődjenek a legfrissebb üzenetek. Pl így:
@Override
protected void onResume() {
super.onResume();
letolt();
}
De érdemes az üzenet küldésekor (feltéve, hogy sikeres volt, tehát az
ottani {1} onSuccess
függvény végén) is frissíteni a listát,
hogy a felhasználó láthassa a frissen beküldött üzenetét.
A program elméletileg most már teljesen működőképes. Ellenőrizd, majd az elkészült projektet küldd el nekünk a feltöltő oldalon keresztül!
Jelenleg az üzenet küldésekor a következők történnek:
Elkészítjük az üzenet objektumot
Odaadjuk az Azure-nak, és kapunk cserébe egy ígéretet, ami majd egyszer később teljesül (vagy hibát jelez)
Töröljük az EditText
tartalmát.
Teljesül az ígéret
Sikerült: rezgés
Hiba történt: Toast
Remélhetőleg látható ennek a folyamatnak a hibája: Ha valami probléma adódott az üzenet elküldésével, akkor mielőtt erről tudomást szerzünk egy Toast formájában, már kitöröltük az EditText tartalmát. Vagyis teljesen és visszavonhatatlanul elveszett az üzenet tartalma! Reméljük, nem kell mondani, hogy ez milyen rossz fényt vetne az alkalmazásunkra és a programozóra… Javítsuk!
Több megoldás lehet a problémára, nézzük a legegyszerűbbet.
Az egész küldő függvény legelején tiltsuk le a küldés gombot (setEnabled(false)
belső függvénye). Ez azért kell, hogy amíg meg nem bizonyosodtunk az üzenetküldés
eredményéről, nem próbálja újraküldeni a felhasználó az üzenetet. (Lehet, hogy csak
lassú a net, de ha a türelmetlen felhasználó többször megnyomja a gombot, akkor
ugyan lassan, de több példányban lesz elküldve az üzenet.)
Hozzuk létre az üzenetet, adjuk oda az Azure-nak, és írjuk meg az ígéret beváltásához szükséges kód vázát, pont, mint eddig.
Töltsük ki a két függvényt:
Az {1}
-esben (azaz siker esetén) továbbra is jelezhetjük egy rövid
rezgéssel a sikert, viszont fontos, hogy itt töröljük ki az EditText tartalmát, és
ne felejtsük el újra engedélyezni a gombot (setEnabled(true)
)!
a {2}
-esben (azaz hiba esetén) lehet mondjuk egy hosszabbat rezegni,
Toast-ot megjeleníteni, viszont ami fontos, hogy itt ne töröljük ki az EditText
tartalmát, viszont itt is engedélyezzük a gombot!
Ez azért jó, mert így biztosan nem veszik el az elküldeni kívánt üzenet. Ha hiba történik (például nincs bekapcsolva a WiFi) van lehetősége korrigálni a felhasználónak (pl. bekapcsolni a WiFi-t) és újra megpróbálkozni a küldéssel, anélkül, hogy újra be kéne gépelnie az üzenetét.
Nem túlzás azt állítani, hogy az üzenőfal alkalmazásunk igen felhasználóbarát az üzenetek elküldésénél. Most javítsunk egy kicsit a letöltésen is!
Az egyik legszembetűnőbb hiányosság az, hogy az üzenetek listáját csupán két esetben töltjük le:
onResume
)Tételezzük fel egy pillanat erejéig, hogy a Aladár, a szakkör egy igen lelkes fiktív tagja az üzenőfalon szeretne hosszú társalgást folytatni a hozzá hasonlóan valós Bélával. Amikor Aladár feltesz egy kérdést, utána nincs más megoldás: újra kell indítania párszor a programot, hogy meglássa, válaszolt-e Béla.
Ezt a problémát szeretnénk megoldani, első lépésben egy igen egyszerű "frissítés" gomb elhelyezésével, aminek segítségével a felhasználó bármikor lehívhatja az üzenetek listáját!
id
tulajdonságáról!letoltes()
függvényünket.Gomb helyett használhatunk egy felugró menüt is. Ehhez segítséget itt találtok.
A gombnyomásos megoldás nagyban megkönnyíti Aladár és Béla dolgát. Ha még ennél is jobb megoldást szeretnénk, akkor érdemes lehet az üzeneteket egy időzítő segítségével pl. pár másodpercenként lehívni. Ezt az 5*. szorgalmi feladatban meg is tesszük.
Ha alaposan letesztelted a hibakezelést is, akkor észrevehetted, hogy amikor pl. nincs internet, akkor a listában nem jelenik meg egy elem sem. Ez nem túl informatív. Sokkal jobb felhasználói élményt nyújtana, ha ilyenkor valami szöveg látszódna, mutatva, hogy itt elvileg egy lista van, csak most épp üres.
Ez meg lehet oldani egy macerásabb, és egy egyszerűbb módon is, az előbbi kitalálását rátok bízzuk, de utóbbit megmutatjuk, mert egy hasznos kis funkció.
visibility
tulajdonságát gone
-ra, így nem fog bezavarni. Később
ne felejtsük el visszaállítani visible
-re!RelativeLayout
komponenst. Érdemes beállítani az layout:alignComponent
tulajdonságon belül
a bottom:top
sort mondjuk az alsó EditText-re, vagy a küdés gombra.bottom
) melyik
másik komponens tetejéhez (top
) igazodjon.ures
RelativeLayout
, neve lehet pl.: ures
), majd az onCreate
-ben
rendeljük is hozzá a findViewById
függvénnyel.onCreate
-ben) a ListView-nak, hogy ez jelenjen meg, ha nincsen egy üzenet sem:
list.setEmptyView(ures);
Figyelj oda, hogy a ListView típusú változódat használd, ne az adaptert, és ne is az ArrayList
-et!letoltes()
függvényedet. Jelenleg az alkalmazásunk mindig a 15 legfrissebb üzenetet jeleníti meg. Ez így is van rendjén, hiszen az üzenőfalak sajátossága, hogy többnyire csak a legújabb bejegyzések érdeklik a felhasználót. Néha azonban bizony előfordulhat, hogy vissza szeretnénk keresni egy régi beszélgetést, vagy csak gyönyörködni szeretnénk a többi szakkörös felhasználó szárnypróbálgató üzeneteiben. Viszont továbbra is igaz, hogy nem lenne bölcs döntés egyszerre lehívni a szolgáltatás összes korábbi üzentetét, hiszen az feleslegesen terhelné mind a telefont, mind a hálózatot.
Egy egyszerű megoldás lehet a problémára, ha elhelyezünk egy újabb gombot a felhasználói felületre, amivel jelezhetjük: nekünk ennél több üzenetre lesz szükségünk.
A pontos megvalósítást rátok bízzuk! Első megközelítésként érdemes lehet létrehozni egy int
típusú változót, ami a letöltendő
üzenetek számát tárolja (kezdetileg 15
, de gombnyomásra/menüből növelhető). Bátrabbak kísérletezhetnek a lekérdezésnél a skip
függvénnyel is!
A második szorgalmi végén utaltunk már erre a feladatra: jó lenne, ha a programunk futás közben (onResume
-tól kezdve
egészen onPause
-ig) folyamatosan hívná az üzeneteket, mondjuk három másodpercenként.
Első megközelítésben megnézzük, hogy miként tudunk az Android rendszerben ilyen időzítőt létrehozni. Azonban még most le kell szögezni: a megoldás igen naiv, hiszen pár másodpercenként frissíti a teljes lista tartalmát!
A probléma ennél a feladatnál is ugyanaz, mint korábban: a letolt();
függvényünket szeretnénk meghívni adott időközönként,
ez azonban csak a programunk fő száláról történhet meg. A program fő szálát pedig nem tarthatjuk fel sokáig (emlékszel még a lecke elején a
Thread.sleep(30000)
eredményére?). Ezért az Android rendszerben a következő megoldás vált elfogadottá:
Létre hozunk egy futtatható objektumot, ami a főszálon fut le, majd dolga végeztével megkéri a rendszert, hogy futtassa őt újra adott idő elteltével.
Nézzük ezt meg a gyakorlatban:
Elsőként hozzunk létre egy új változót az Activity
ben, ami segítségünkre lesz a futtatásoknál.
Ez a Handler
objektum képes futtatni az objektumunkat akár rögtön, akár adott idő elteltével.
android.os.Handler handler;
Adjunk értéket az új változónak az onCreate
függvény végén.
handler = new android.os.Handler();
Most hozzunk létre még egy változót, ami ezúttal magát a futásra váró függvényt csomagolja be.
Runnable refreshTask;
Amikor értéket adunk ennek a változónak, rögtön megadhatjuk azt a függvényt is, amit le szeretnénk majd futtatni
adott időközönként. A változónk run
belső kis függvénye az, ami az objektum futtatásánál elindul.
refreshTask = new Runnable() {
@Override
public void run() {
// TODO: hívd meg a letolt(); függvényt!
// vége a függvénynek. Most kérjük meg a rendszert, hogy futtasson minket újra 3000ms (3s) elteltével
handler.postDelayed(refreshTask, 3000);
}
};
Remélhetőleg eddig követhető volt: most már van egy Handler
változónk, aki segít
futtatni a függvényünket, illetve egy Runnable refreshTask;
, ami gyakorlatilag a frissítés
függvényt csomagolja be. Ha egyszer elindítjuk ezt a frissítés függvényt, akkor őt onnantól kézbe veszi az
irányítást, és gondoskodik róla, hogy három másodpercenként újra lefusson
(handler.postDelayed(refreshTask, 3000);
).
Tehát nincs más dolgunk, mint gondoskodni az első futásról. Például indíthatjuk a függvényt rögtön
az onResume
végén:
handler.post(refreshTask);
Érdemes még arról is gondoskodni, hogy a program leállításakor ez az ismétlődő refreshTask
is
legyen szíves megpihenni. Ehhez az onPause
függvény végére kell odabiggyesztenünk az alábbi kódot:
handler.removeCallbacks(refreshTask);
onResume
függvényünk kérvényezni fogja az első frissítést. Tehát az ottani
letolt();
sort akár törölhetjük is.Van még ötleted? Nem kell itt megállnod. A program rengeteg irányba fejleszthető! Például a lekérés kódja lehetne kicsit okosabb úgy, hogy csak bizonyos dátum utáni üzenetek iránt érdeklődjön, így elkerülve az üzenetek folyamatos újratöltését! De a lehetőségek tárháza végtelen. A feltöltő oldalra bármilyen plusz munkát feltölthetsz. Illetve ha kérdésed támad az ötleteddel kapcsolatban, írd meg bátran, hátha tudunk segíteni!