Implementácia aplikácie nSoric aAurela: Rozdiel medzi revíziami
Riadok 79: | Riadok 79: | ||
* Snackbar má byť zobrazený len po určitú dobu, bez nutnosti explicitného zrušenia používateľom. | * Snackbar má byť zobrazený len po určitú dobu, bez nutnosti explicitného zrušenia používateľom. | ||
− | [[File:snackbar.png|207px|Material Design komponenty použité v aplikácii: Snackbar (aktivita Login), AppBar, Card a FAB (aktivita Dashboard)]] [[File: | + | [[File:snackbar.png|207px|Material Design komponenty použité v aplikácii: Snackbar (aktivita Login), AppBar, Card a FAB (aktivita Dashboard)]] [[File:card_app_bar_fab.png|207px|Material Design komponenty použité v aplikácii: Snackbar (aktivita Login), AppBar, Card a FAB (aktivita Dashboard)]] |
== Práca s dátami v aplikácii == | == Práca s dátami v aplikácii == |
Verzia zo dňa a času 22:28, 3. september 2020
1. | Informačný systém Sensorical |
2. | Platforma Android |
3. | Návrh aplikácie nSoric aAurela |
4. | Implementácia aplikácie nSoric aAurela |
5. | Testovanie aplikácie nSoric aAurela
|
Obsah
Implementácia používateľského rozhrania a jednotlivých funkcionalít aplikácie vychádza zo špecifikácie požiadaviek a navrhnutej štruktúry aplikácie v kapitole [navrh]. V tejto kapitole sú postupne opísané: implementácia používateľského rozhrania aplikácie, práca s dátami, komunikácia so serverom a samotná vizualizácia dát v aplikácii.
Používateľské rozhranie aplikácie
Jednotlivé obrazovky tvoriace používateľské rozhranie aplikácie, prostredníctvom ktorých používateľ s aplikáciou interaguje, sú implementované podľa navrhnutej štruktúry aplikácie (časť [navrh_aktivity]) ako jednotlivé triedy Android aktivít. Tieto Android aktivity rozdeľujú aplikáciu na dve časti: primárnu a sekundárnu.
Aktivity primárnej časti poskytujú používateľovi priamy prístup k prehľadu aktuálneho stavu meracieho systému Sensorical. Aktivity sekundárnej časti predstavujú pomocné aktivity, ktoré sú však nevyhnutné pre prístup do primárnej časti, tj. nutnosť výberu adresy servera, ku ktorému sa chce používateľ pripojiť, ako aj samotné prihlásenie do aplikácie. Android aktivity primárnej časti
- Dashboard - aktivita, ktorá sprostredkováva prehľad všetkých aktuálne dostupných senzorov a ich údajov, kategorizovaných v rámci oblastí a sektorov
- Favourites - aktivita, ktorá zobrazuje prehľad vybraných, obľúbených senzorov a ich údaje
- GraphView - aktivita ktorá zobrazuje detailný prehľad konkrétneho senzora, históriu nameraných hodnôt (grafy a štatistiky)
Android aktivity sekundárnej časti
- ServerSelect - aktivita pre výber adresy servera
- Login - aktivita pre prihlásenie do aplikácie
- About - aktivita zobrazujúca doplňujúce informácie o aplikácií
Android a Material Design
Material Design predstavuje dizajnérsky jazyk vyvinutý spoločnosťou Google v roku 2014, ktorý priniesol množstvo inovácií a zmien do spôsobu tvorby grafických používateľských rozhraní. Filozofia konceptu Material Design vychádza z fyzikálnych vlastností reálnych objektov ako 3D kontúry, odraz svetla, tiene…
Medzi inovácie, ktoré Material Design prináša patria nové komponenty ako napr. karty (Cards), plávajúce tlačidlo (Floating action button), oznámenia (Snackbars), animácie, prechody a iné. Najväčšou zmenou, ktorú Material Design priniesol je tzv. efekt hĺbky resp. používanie osi z. Jednotlivé komponenty sú zobrazované v pomyselných vrstvách v rôznych hladinách (radené nad sebou). Najdôležitejší komponent, s ktorým môže požívateľ aktuálne pracovať je zobrazený ponad ostatné komponenty (vo vyššej hladine osi z). Výhodou používateľských rozhraní, využívajúcich princíp radenia komponentov do vrstiev, je intuitívne prostredie, v ktorom sa používateľ ľahko zorientuje, s ktorými komponentmi môže aktuálne pracovať.
Implementácia Material Design v aplikácii
Pri implementácii používateľského rozhrania aplikácie boli odporúčania Material Design aplikované priamo v jednotlivých grafických rozhraniach Android aktivít a ich komponentoch. Tieto odporúčania (napr. odsadenie, vyvýšenie, farby, tlačidlá a tvary) presne definujú prípady použitia - kedy a ako môže byť daný komponent použitý, ako má vyzerať a ako sa má správať.
Pri tvorbe grafických používateľských rozhraní a taktiež pre exaktný popis parametrov jednotlivých komponentov sú používané abstraktné jednotky - dp (density pixel). Jednotky dp sú prepočítavané k referenčnému rozlíšeniu v pixeloch: 1dp=1 pixel na obrazovke s rozlíšením 160dpi (bodov na palec).
Material Design komponenty využité v aplikácii
Cards
Funkcia: Cards (Karty) zobrazujú obsah a akcie súvisiace s jedným senzorom (). Každá karta senzora zobrazuje: názov senzora, tlačidlo pre akciu pridania/odstránenia do/zo zoznamu obľúbených senzorov, priemernú hodnotu, poslednú nameranú hodnotu a dátum kedy bola nameraná. Na karte senzora je taktiež vykresľovaný trend meraní daného senzora. Trend predstavuje smer rastu resp. poklesu nameraných hodnôt za poslednú hodinu.
Material Design odporúčania:
- Vyvýšenie karty od 1dp do 8dp, optimálne 2dp.
- Ak sú v karte použité oddeľovače obsahu, nemali by prechádzať cez celú kartu, ale len cez jej časť.
Floating Action Button
Funkcia: plávajúce tlačidlo (FAB) je používané na vyhľadávanie v aktivitách prehľadu (Dashboard, Favourites). Tlačidlo je štandardne viditeľné, avšak pri posunutí v zozname smerom dolu je skryté, z dôvodu úplnej viditeľnosti údajov poslednej karty zoznamu. Pri posunutí v zozname smerom hore sa stane tlačidlo znova viditeľné.
Material Design odporúčania:
- FAB je zobrazené ponad všetok ostatný obsah, štandardne v pravom dolnom rohu.
- Optimálne rodičovské rozloženie - CoordinatorLayout (umožňuje automatické posunutie FAB smerom hore, napr. v prípade zobrazenia Snackbar oznámenia).
- Odporúčaná veľkosť je 56x56dp pre zariadenia s rozlíšením nad 460dp, pre zariadenia s nižším rozlíšením 46x46dp.
- Farba plávajúceho tlačidla je rovnaká ako hlavná farebná schéma aplikácie.
- Odsadenie od okraja obrazovky 16dp, vyvýšenie ponad ostatný obsah do 6dp.
AppBar
Funkcia: horná nástrojová lišta sprostredkováva informácie a akcie súvisiace s aktuálnou obrazovkou. Zobrazuje názov aktuálnej obrazovky, rýchle akcie (napr. otvorenie zoznamu obľúbených) a kontextové menu.
Material Design odporúčania:
- Názov obrazovky je umiestnený na ľavej strane nástrojovej lišty.
- Kontextové akcie sú umiestnené na pravú stranu.
- Ak je použité kontextové menu, je umiestené úplne na pravej strane.
- Vyvýšenie nástrojovej lišty je štandardne 4dp.
Snackbar
Funkcia: rýchle oznámenia ukotvené k spodnej časti obrazovky, ktoré informujú o aktuálnom dianí v aplikácii.
Material Design odporúčania:
- Optimálne rodičovské rozloženie - CoordinatorLayout.
- Snackbar má byť zobrazený len po určitú dobu, bez nutnosti explicitného zrušenia používateľom.
Práca s dátami v aplikácii
Dáta, s ktorými aplikácia pracuje sú sťahované zo vzdialených serverov a po stiahnutí sú ukladané do SQLite databázy, z ktorej sú sprostredkovávané v aplikácii. Sťahovanie najaktuálnejších meraní prebieha automaticky vždy po spustení resp. prihlásení do aplikácie. V prípade potreby manuálnej aktualizácie nameraných hodnôt, prípadne synchronizácie všetkých dát s aktuálne dostupnými dátami daného servera sú v aplikácii implementované dva rôzne spôsoby manuálnej synchronizácie: aktualizácia len nameraných hodnôt alebo synchronizácia všetkých dát.
Pre zefektívnenie a zrýchlenie sťahovania nameraných hodnôt sa v aplikácii vytvárajú záznamy o dátumoch, za ktoré boli namerané hodnoty úspešne stiahnuté. Vždy pred každým ďalším sťahovaním nových meraní sú tieto záznamy prečítané a stiahnu sa len merania tých dátumov, ktoré nie sú evidované ako stiahnuté.
Keďže aplikácia slúži na monitorovanie aktuálneho stavu a krátkodobejších trendov, maximálny rozsah zobrazenia nameraných hodnôt bol stanovený na jeden týždeň. Aby sa predišlo prílišnému zväčšeniu veľkosti aplikácie dôsledkom sťahovania množstva údajov, sú po každom stiahnutí najnovších nameraných hodnôt vymazané nepotrebné záznamy (merania) staršie ako jeden týždeň.
Implementácia SQLite databázy
SQLite databáza, ktorá slúži na archiváciu stiahnutých dát a ich následnú prezentáciu v aplikácii, bola vytvorená pomocou knižnice Room (viď [room]), pričom je implementovaná tak, aby presne korešpondovala s dátovým modelom systému Sensorical (). Aplikácia je navrhnutá tak, že umožňuje vytvárať a pracovať s ľubovoľným počtom SQLite databáz, ktoré sú vytvárané na základe webovej adresy servera, ku ktorej je používateľ aktuálne prihlásený.
Každý server, ku ktorému sa používateľ prihlási, má v aplikácii vytvorenú vlastnú SQLite databázu. Vždy po úspešnom prihlásení k niektorému zo serverov sa aplikácia pokúsi vytvoriť novú databázu s názvom webovej adresy daného servera. Ak už databáza s takýmto názvom existuje, aplikácia ju použije a otvorí.
Pri samotnej implementácii SQLite databázy v aplikácii, boli ako prvé vytvorené jednotlivé triedy reprezentujúce entity databázy ([listing:entity]). Každá trieda entity obsahuje premenné, ktoré predstavujú jednotlivé atribúty danej entity. Každá premenná triedy entity je prístupná prostredníctvom metód get a set.
@Entity(tableName = "area")
public class Area {
@PrimaryKey
private int id;
private String name;
public void setId(int id) { this.id = id; }
public int getId() { return id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
}
Ako ďalšie boli vytvorené potrebné DAO rozhrania ([listing:dao] rozhranie AreaDao) a ich metódy pre prístup k dátam databázy. Návratové typy týchto metód boli zvolené na základe očakávaného výsledku SQL dotazu, ktorý daná metóda vykonáva. V prípade DAO metód, ktoré slúžia na výber údajov z SQLite databázy, sú návratové typy zabalené do generickej triedy Maybe, knižnice RxJava, ktorá umožňuje vykonávať prácu týchto metód asynchrónne v paralelných vláknach.
DAO metódy, ktoré do SQLite databázy vkladajú záznamy nameraných hodnôt, využívajú v prípade konfliktu hodnôt primárnych kľúčov parameter IGNORE (ignorovať). Parameter IGNORE zabezpečí neprepísanie existujúceho záznamu v SQLite databáze, pretože nameraná hodnota nemôže byť spätne modifikovaná a teda nie je nutné už existujúce záznamy prepisovať (skrátenie času vykonávania operácie).
DAO metódy zabezpečujúce vkladanie ostatných údajov, majú v prípade konfliktu hodnôt primárnych kľúčov nastavený parameter REPLACE (nahradiť). V prípade modifikácie záznamu na strane servera sa síce nemení hodnota jeho primárneho kľúča, ale hodnota niektorého z atribútov je zmenená. Parameter REPLACE v takomto prípade zabezpečí prepísanie záznamu uloženého v SQLite databáze modifikovaným záznamom stiahnutým zo servera.
@Dao
public interface AreaDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
void insertAllAreas(List<Area> areas);
@Query("select group_view.area_id from group_view
where group_view.user_id=:userID")
Maybe<List<Integer>> getAllAreaIDsforUser(int userID);
}
Ako posledná bola implementovaná abstraktná trieda databázy ([listing:database]), ktorá je potomkom triedy RoomDatabase. Trieda databázy v aplikácii využíva návrhový vzor Singleton, ktorý zabezpečí vytvorenie a dostupnosť jedinej inštancie triedy databázy počas behu aplikácie. Inštanciu triedy databázy je možné vytvoriť (získať) prostredníctvom statickej metódy getInstance().
@Database(entities = {Area.class, Sensor.class}, version = 1)
public abstract class AppDatabase extends RoomDatabase {
public abstract AreaDao areaDao();
private static AppDatabase INSTANCE;
public static AppDatabase getInstance(final Context context){
synchronized (AppDatabase.class) {
if (INSTANCE == null){
INSTANCE = buildDatabase(context.getApplicationContext()); }
} return INSTANCE; }
private static AppDatabase buildDatabase(final Context context){
String DATABASE_NAME = context
.getSharedPreferences("preferences", Context.MODE_PRIVATE)
.getString("server_name", "default_name");
return Room.databaseBuilder(
context, AppDatabase.class, DATABASE_NAME).build(); }
}
V triede databázy sú zadefinované všetky Java triedy jednotlivých entít, z ktorých SQLite databáza pozostáva a taktiež abstraktné metódy všetkých DAO rozhraní, prostredníctvom ktorých môžno vytvoriť inštancie rozhraní v iných triedach. V aplikácii je v triede databázy implementovaná metóda buildDatabase(), ktorá zisťuje názov aktuálneho webového servera (čítanie zo SharedPreferences - ukladanie menších dát formou kľúč - hodnota), ku ktorému je používateľ prihlásený a tento názov použije pre vytvorenie resp. otvorenie databázy s daným názvom, volaním metódy build().
Práca s SQLite databázou
Práca s SQLite databázou, konkrétne vykonávanie dotazov na výber údajov pre niektorú z aktivít prehľadu (Dashboard, Favourites, GraphView) je v aplikácii realizované sekvenčne. Najskôr sa vykoná referenčný výber, pričom všetky ostatné dotazy čakajú vo fronte. V momente, kedy sa aktuálny výber dokončí, začne sa vykonávať nasledujúci (čakajúci). Sekvenčný spôsob vykonávania práce je zvolený z dôvodu závislosti vstupných parametrov (podmienok pre vykonanie) čakajúceho dotazu, na výsledku jemu predchádzajúcemu výberu.
Aby aplikácia zobrazila len ten obsah, na ktorý má prihlásený používateľ pridelené práva je využitý jedinečný identifikátor každého používateľa aplikácie - ID. Hodnota ID daného používateľa je využitá práve pri referenčnom výbere, na základe ktorého sú vybrané len tie vstupné parametre, na ktoré má používateľ definované oprávnenia.
demonštruje postupnosť volania DAO metód, ktoré vyberajú z SQLite databázy údaje pre aktivitu Dashboard. Obdobným spôsobom sú riešené výbery pre ostatné dve aktivity prehľadov (Favourites a GraphView).
Referenčný výber pre aktivitu Dashboard je výber zoznamu ID všetkých oblastí na základe ID prihláseného používateľa. Následne sa sekvenčne vykonávajú ďalšie výbery, ktorých parametrami sú vždy výsledky predchádzajúceho výberu. V metóde createDashboardData() sa vytvára dátová štruktúra, ktorá obsahuje vlastnosti potrebné pre zobrazenie údajov v Dashboard aktivite: názvy oblastí, sektorov a senzorov, fyzikálne jednotky senzorov, namerané hodnoty a ďalšie. Naplnená dátová štruktúra je odoslaná triede adaptéra (viď 1.3), ktorá tieto dáta vizualizuje.
Vykonávanie výberov dát z SQLite databázy prostredníctvom DAO metód predstavuje časovo náročnejšie operácie, ktoré musia prebiehať asynchrónne, v paralelnom vlákne. Pre účel vykonávania DAO metód v paralelnom vlákne je v aplikácii využitá RxJava generická trieda Maybe. znázorňuje princíp návrhového vzoru Observer, ktorý je implementovaný knižnicou RxJava a ktorý je v aplikácii využívaný pre vykonávanie dotazov na výber dát z SQLite databázy použitím triedy Maybe. Tento návrhový vzor zabezpečuje sledovanie priebehu práce časovo náročných DAO metód vykonávaných v paralelných vláknach ([listing:rx_operators], metóda getAllAreaIDsForUser()). Po dokončení práce týchto metód dôjde k automatickej notifikácii vlákna špecifikovaného operátorom observeOn() ([listing:rx_operators] - hlavné vlákno), na ktoré sú publikované výsledky prostredníctvom rozhrania Consumer a jeho metódy accept(). Metóda accept() prijíma prostredníctvom parametra výsledky vykonaného SQL dotazu danej DAO metódy.
areaDao.getAllAreaIDsForUser(userID)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<List<Integer>>() {
@Override
public void accept(List<Integer> areaIDs) { }
});
Komunikácia so vzdialeným serverom
Pre komunikáciu aplikácie so serverovým API je využitý socketový dátový prenos. Požiadavky/odpovede sú odosielané/prijímané v textovom formáte JSON. Po prijatí odpovede sa prechádza tá časť odpovede, ktorá obsahuje relevantné dáta (hodnota kľúča JSON objektu “data”). Časť odpovede “data” a jej hodnoty sa prechádzajú a ukladajú do dátových štruktúr (triedy jednotlivých entít), ktoré sú po naplnení vložené do SQLite databázy.
Vytvorenie, odoslanie a spracovanie požiadavky
Vytvorenie a odoslanie požiadavky na API a následné spracovanie odpovede zabezpečujú v aplikácii tri triedy (ClientSocket, ApiResponses, ApiDataPresenter), ktorých zjednodušenú štruktúru znázorňuje diagram tried na obrázku 1.6.
- trieda ClientSocket (odoslanie požiadavky) - konvertuje požiadavku do formátu JSON, otvára spojenie so serverom, odosiela požiadavku, čaká na odpoveď a vracia ju späť metóde triedy ApiResponses, ktorá požiadavku vyvolala.
- trieda ApiResponses (vytvorenie požiadavky) - vytvára a posiela triede ClientSocket údaje potrebné pre odoslanie požiadavky prostredníctvom parameterov metódy sendRequest(). Trieda ApiResponses zároveň prijíma a vracia späť textovú odpoveď metóde triedy ApiDataPresenter, ktorá danú požiadavku vyvolala.
- trieda ApiDataPresenter (spracovanie odpovede) - inicializuje proces vytvárania požiadavky volaním príslušnej metódy triedy ApiResponses, transformuje prijatú odpoveď na JSON objekt, ktorého hodnoty ukladá do dátovej štruktúry.
Proces komunikácie so vzdialeným serverom je časovo náročná operácia, ktorá musí byť vykonávaná asynchrónne, v paralelnom vlákne. V aplikácii je pre tento účel využitá RxJava trieda Completable. znázorňuje návrhový vzor Observer, ktorý je implementovaný knižnicou RxJava a ktorý je v aplikácii využívaný pre komunikáciu so serverovým API použitím triedy Completable. Podstata tohoto návrhového vzoru spočíva v tom, že po ukončení časovo náročnej metódy ([listing:completable] riadok 3, metóda run()) dôjde k automatickej notifikácii vlákna špecifikovaného operátorom observeOn(). Notifikáciu špecifikovaného vlákna ([listing:completable] - hlavné vlákno) zabezpečuje rozhranie CompletableObserver a jeho metóda onComplete(), ktorá je zavolaná, ak komunikácia so serverom a spracovanie odpovede prebehli v poriadku. Samotná komunikácia so serverom prebieha v tele metódy run() volaním metódy apiDataPresenter.getAreas(), ktorá vyvolá konkrétnu požiadavku.
Completable.fromRunnable(new Runnable() {
@Override
public void run() {
areaDao.insertAllAreas(apiDataPresenter.getAreas());
}
}).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()
.subscribe(new CompletableObserver() {
@Override
public void onSubscribe(Disposable d) { }
@Override
public void onComplete() { }
@Override
public void onError(Throwable e) { } });
Textový formát odpovede z API je vrátený do triedy ApiDataPresenter, v ktorej sa transformuje na JSON objekt ([listing:api_data_presenter], metóda parseAreaResponse()). V tejto metóde sa prechádza pole hodnôt s hodnotou kľúča “data”, pričom jednotlivé hodnoty tohto poľa sú opäť polia, ktorých hodnoty sa ukladajú do dátovej štruktúry, ktorá je vrátená späť do miesta volania ([listing:completable], riadok 4). Spôsob vytvárania JSON objektu z prijatej odpovede, prechádzanie a ukladanie jeho hodnôt do dátovej štruktúry zobrazuje [listing:api_data_presenter]. Postupnosť prechodu medzi triedami a formát dát v jednotlivých triedach počas komunikácie s API znázorňuje .
public ArrayList<Area> getAreas() {
parseAreaResponse();
return this.areaList;
}
private void parseAreaResponse() {
String serverResponse = apiResponses.getAreaResponse();
if (!serverResponse.isEmpty()) {
JSONObject response = new JSONObject(serverResponse);
JSONArray array = response.getJSONArray("data");
for (int i = 0; i < array.length(); i++) {
Area area = new Area();
area.setId(array.getJSONArray(i).getInt(0));
area.setName(array.getJSONArray(i).getString(1));
this.areaList.add(area);
}
}
}
Vizualizácia dát v aplikácii
Prezentáciu dát v aktivitách prehľadu (Dashboard, Favourites) zabezpečujú komponenty - CardView, RecyclerView a Adaptér. V aktivite detailného prehľadu konkrétneho senzora (GraphView) sú dáta vizualizované prostredníctvom grafov - knižnica MPAndroidChart.
Vizualizácia dát v aktivitách Dashboard a Favourites
CardView
CardView je grafická reprezentácia karty (Card 1.1.2), predstavuje rodičovské rozloženie pre všetky v nej obsiahnuté View prvky (textové polia, tlačidlá, obrázky…).
RecyclerView
RecylerView predstavuje model, ktorý umožňuje zobraziť väčšie množstvo údajov vo forme zoznamu. V aplikácii sú ako jednotlivé položky zobrazené v RecyclerView použité karty. Výhodou používania RecyclerView je jeho rýchlosť, pretože sa nevykresľujú (nevytvárajú) naraz všetky karty, ale vytvárajú sa len tie, ktoré sa aktuálne zmestia na obrazovku. Pre naplnenie RecyclerView kartami a ich následné zobrazenie sa využívajú návrhové vzory (triedy) ViewHolder a Adaptér. Jednotlivé karty zobrazené v RecyclerView sú reprezentované objektmi nazývanými view holders. Tieto objekty sú inštanciami tried, ktoré sú potomkami triedy RecyclerView.ViewHolder.
ViewHolder
ViewHolder je návrhový vzor, ktorý zabezpečuje vytvorenie objektu daného CardView rozloženia spolu s jej View prvkami. Takto vytvorený objekt môže byť použitý viacnásobne, bez nutnosti opakovanej inicializácie View prvkov tohto objektu, volaním metódy findViewById(), čo znižuje pamäťovú náročnosť a zároveň výrazne zrýchľuje proces listovania v RecyclerView. Vytvorené view holder objekty reprezentujú karty, ktoré sú zobrazené v RecyclerView. Tento návrhový vzor možno použiť vytvorením vnútornej triedy, potomok triedy RecyclerView.ViewHolder, v triede adaptéra.
Adaptér
Adaptér je návrhový vzor, ktorý funguje ako most medzi používateľským rozhraním a dátami. V Androide predstavuje adaptér triedu, ktorá sprostredkováva dáta RecyclerView. Trieda adaptéra (potomok triedy RecyclerView.Adapter) zabezpečuje správu jednotlivých view holder objektov prostredníctvom preťažených metód rodičovskej triedy: vytvorenie view holder objektov - metóda onCreateViewHolder, pridávanie vytvorených view holder objektov do RecyclerView - metóda onBindViewHolder().
V aplikácii sú vytvorené dve triedy adaptérov, kde každá z nich sprostredkováva dáta konkrétnej aktivite: DashboardAdapter (Dashboard aktivita), FavouritesAdapter (Favourites aktivita). Triedy adaptérov v aplikácii využívajú návrhový vzor Singleton, pričom vytvorenie (získanie) inštancie triedy oboch adaptérov je možné prostredníctvom statickej metódy getInstance(). Spôsob implementácie triedy adaptéra v aplikácii je demonštrovaný na jednoduchšej triede FavouritesAdapter, ktorá vykresľuje len jeden typ view holder objektu (len karta senzora, vľavo). Trieda DashboardAdapter vykresľuje tri typy view holder objektov, kde každý z nich reprezentuje kartu: oblasti, sektoru, alebo senzora ( vpravo).
Vytvorenie inštancie triedy FavouritesAdapter a následné nastavenie tejto inštancie ako adaptér objektu recylerView demonštruje kód [listing:adapter].
RecyclerView recyclerView = findViewById(R.id.recycler_view);
FavouritesAdapter adapter = FavouritesAdapter.getInstance();
recyclerView.setAdapter(adapter);
Nasledujúca ukážka ([listing:viewHolder]) zobrazuje implementáciu návrhového vzoru ViewHolder v triede adaptéra (FavouritesAdapter), vytvorením vnútornej triedy DataViewHolder. Trieda DataViewHolder inicializuje jednotlivé View prvky CardView rozloženia karty senzora. Každý vytvorený objekt tejto triedy reprezentuje jednu kartu v RecyclerView.
public static class DataViewHolder extends RecyclerView.ViewHolder {
private TextView textViewSensorName;
// iné View prvky CardView rozloženia karty senzora...
public DataViewHolder(@NonNull final View itemView) {
super(itemView);
textViewSensorName =
itemView.findViewById(R.id.text_view_sensor_name);
}
}
Metóda triedy adaptéra - onCreateViewHolder() ([listing:onCreate]) slúži pre dynamické vytváranie view holder objektov. Táto metóda vytvára len taký počet view holder objektov (kariet), koľko sa aktuálne zmestí na obrazovku. Pri listovaní v RecyclerView sú dynamicky vytvárané ďalšie potrebné objekty, pričom nahrádzajú tie, ktoré už na obrazovke nie sú vidieť (recycling).
@NonNull
@override
public RecyclerView.ViewHolder onCreateViewHolder(
@NonNull ViewGroup viewGroup, int i) {
View v;
LayoutInflater mInflater =
LayoutInflater.from(viewGroup.getContext());
v = mInflater.inflate(
R.layout.card_view_sensor, viewGroup, false);
return new FavouritesAdapter.DataViewHolder(v); }
Samotné pridanie vytvoreného view holder objektu do RecyclerView zabezpečuje metóda - onBindViewHolder() ([listing:onBind]). Každý view holder objekt v RecyclerView má definovanú pozíciu, na ktorej sa nachádza. Hodnotu aktuálnej pozície, na ktorú má byť ďalší view holder objekt pridaný/vykreslený prijíma metóda onBindViewHolder() prostredníctvom parametra. Na základe pozície je možné naviazať konkrétny view holder objekt na dáta.
@override
public void onBindViewHolder(
@NonNull RecyclerView.ViewHolder viewHolder, int position) {
((DataViewHolder) viewHolder).
textViewSensorName.setText(
filteredList.get(position).getSensorName()); }
Vizualizácia dát v aktivite GraphView
Prezentáciu dát v aktivite GraphView zabezpečujú grafy, vytvárané pomocou knižnice MPAndroidChart. Táto knižnica umožňuje vytvárať rôzne typy grafov: koláčové, stĺpcové, čiarové a iné. V aplikácii je využitý čiarový graf - LineChart. V aplikácii sú v grafe vykresľované dva typy čiar: LimitLine - horizontálna čiara pre zobrazenie priemernej hodnoty a samotná čiara grafu LineChart pre vykreslenie priebehu meraných hodnôt. Hodnoty osi x sú reprezentované časovými značkami jednotlivých meraní, ktoré sú explicitne konvertované na textový formát dátumu, ktorý je v grafe zobrazený ako legenda osi x. Hodnoty osi y sú reprezentované nameranými hodnotami. Jednotlivé body grafu sú vytvárané ako usporiadaná dvojica [časová značka, hodnota] ([listing:graph_creation] trieda Entry). Tieto usporiadané dvojice hodnôt sú postupne pridávané do poľa ([listing:graph_creation] objekt xyValues), ktoré je po naplnení priradené vytváranému objektu triedy LineDataSet v jej konštruktore. Metódy triedy LineDataSet umožňujú nastaviť požadované parametre čiary grafu (hrúbka, farba a iné). Objekt triedy LineDataSet je nakoniec priradený samotnému objektu grafu inicializovaného prostredníctvom metódy findViewById(). V aplikácii je v grafoch implementovaná možnosť zoomovania a taktiež možnosť zobrazenia hodnoty konkrétneho bodu pri kliknutí na ľubovoľnú hodnotu v grafe ( vpravo).
ArrayList<Entry> xyValues = new ArrayList<>();
for (int i = 0; i < values.size; i++) {
Entry xy = new Entry(values.get(i).getDate().getTime(),
values.getValue());
xyValues.add(xy);
}
LineDataSet dataSet = new LineDataSet(xyValues, "hodnoty");
dataSet.setLineWidth(2f);
// iné nastavenia vlastností dataSetu...
LineData lineData = new LineData(dataSet);
LineChart chart = findViewById(R.id.chart);
chart.setData(lineData);