# <center> Habit tracker - aplikacija za vođenje svakodnevnih aktivnosti </center> ##### Prirodno-matematički fakultet ##### Odsjek za matematiku ##### Teorijska kompjuterska nauka ##### Razvoj mobilnih aplikacija <hr> Projekat radile: Adna Hrnjić Šejla Dizdarić Predmetni profesor: Prof. Dr. Elmedin Selmanović Predmetni asistent: Eldina Maslo, MA <hr> <center> <img src =" https://imgur.com/WkpZeqs.png" height = 500px> </center> # O aplikaciji <div style="text-align: justify"> Da li želite voditi računa o svojim dnevnim aktivnostima, svojim dobrim i zdravim navikama, ali i onim ne tako dobrim. Aplikacija HabitTracker je aplikacija koja prati Vaše životne navike. Osmišljena je da pomaže svima koji vode brz život i nemaju vremena voditi računa o sitnim stvarima. Ako ste i Vi jedna od takvih osoba ili vam jednostavno treba aplikacija koja će na jednom mjestu čuvati sve Vama bitne podatke, o Vašem načinu života, onda je ovo prava alikacija za Vas. </div> <br><br> <center> <img src ="https://imgur.com/3ikaMKh.png" height = 500px> </center> <hr> <center> <img src ="https://imgur.com/eBi1lnq.png" width = 500px> </center> <hr> <center> <img src = "https://imgur.com/sRVWEeJ.png" heigth = 500px> </center> <br><br> <div style="text-align: justify"> Sama aplikacija čuva podatke, računa prosječne vrijednosti. Sve što je potrebno jeste u aplikaciji kreirati sve aktivosti koje želite pratiti klikom na dugme „DODAJ NOVU AKTIVNOST“ . Potom pritiskom na dugme „POGLEDAJ SVOJE AKTIVNOST“ odvodi vas na liste vaših kreiranih aktivnosti razvrstanih u tri kategorije: Vremenske aktivnosti, Inkrementalne aktivnosti i Količinske aktivnosti. Vremenske aktivnosti su one aktivnosti koje mjere vrijeme trajanja zadane aktivnosti npr. spavanja, učenja, itd. Klikom na dugme start pokrećete timer, a kada ga želite zaustaviti, kliknite na isto dugme. U aplikaciji može biti samo za jedna aktivnost pokrenut timer u jednom trenutku vremena. Inkrementalne aktivnosti predstavljaju one aktivnosti za koje se prati njihovo povećavanje ili smanjenje u toku jednog dana. Prilikom kreiranja aktivnosti ovog tipa sami izaberete veličinu inkrementa. Količinske aktivnosti su posljednja vrsta aktivnosti koje bilježe dnevne količine neke mjerne jedinici koju, također, možete birati na početku, prilikom kreiranja aktivnosti. Dugmić „O APLIKACIJI“ prebacuje Vas na stranicu koja daje više informacija o aplikacijim (opis, kad je kreirana, developeri...). </div> <hr> <center> <img src = "https://imgur.com/pZRhFfD.png" heigth = 500px> </center> <br><br> <div style="text-align: justify"> Nakon što smo kliknuli na „DODAJ NOVU AKTIVNOST“ otvara nam se stranica prikazana na prethodoj slici. Na ovoj stranici biramo boju aktivnosti, kako bi razvrstali sve aktivnosti u neke skupine po kriteriju koji nama najviše odgovara. Potom biranjem vrste aktivnosti, te tako odlazimo na stranicu za unos podataka koji su prilagođeni toj vrsti aktivnosti. </div> <br><br> <hr> <center> <img src = "https://imgur.com/ZpJrjYb.png"> </center> <br><br> <div style="text-align: justify"> U zavisnosti na koju aktivnost kliknemo, na stranici će nam se prikazivati lista tih aktivnosti koje ste do sada kreirali. Na kartici jedne aktivnosti, kao što je prikazano na slici, prikazuje se ime i boja aktivnosti, te njena srednja vrijedost. Pritiskom na neku od aktivnosti prikazat će se nova lista dnevnih informacija te aktivnosti. Klikom na jednu instancu, otvara nam se stranica sa detaljima o toj dnevnoj kategoriji (podkategoriji). Unutar svih listi podaci su sortirani po datumu, tako da se kategorije koje kreirate i ažurirate posljednje prikazuju prve. Klikom na <img src ="https://imgur.com/BUgJbuj.png"> prikazuje nam se menu sa mogućnostima da dodamo novi aktivnost ili da odemo na stranicu O aplikaciji. </div> <br><br> <img "details fragment"> <div style="text-align: justify"> Na ovom stranici su prikazani detalji o jednoj instanci aplikacije. Na kaledaru je ucrtan datum te aktivnosti. Pored ovog tu su još i dugmići za brisanje, dodavanje ili ažuriranje ove dnevne kategorije. </div> <hr> # Arhitektura Aplikacije <div style="text-align: justify"> Arhitektura aplikacije predstavlja način na koji dizajnirate raspored klasa aplikacije. Naša aplikacija je podijeljena u sljedeće foldere: Activities (klase aktivnosti), Adapter (klase adaptera za kreiranje recycleView-a), Classes (pomoćne klase), DAO (klase Data Access Objects), DB (klasa koja predstavlja bazu podataka), Entitiy (klase entiteta potrebnih za rad aplikacije), Fragments (fragments klase), Repository (reposytori klasa), ViewModels (viewModel potreban za rad na aplikaciji). O svim file-ovima i klasama bit će riječi u nastavku teksta. </div> ## UI Controler <div style="text-align: justify"> Ovaj dio se sastoji od klasa vezane za aktivnosti i fragmente. On treba da sadrži samo logiku koja će upravljati korisničkim inteface-om i operativnim sistemom. </div> # Aktivnosti <div style="text-align: justify"> Klasa aktivnost omogućava kreiranje interface-a (korisničkog sučelja), kojim omogućavamo interakciju sa korisnicima. Ona može biti predstavljena preko cijelog prozora ili popup prozor ili pak ugrađena u druge prozore Jako važna metoda koju je potrebno implementirati u klasu koja nasljeđuje aktivnost je onCreate(Bundle). Pomoću nje inicijaliziramo našu aktinost i pri tom koristimo još jednu jako bitnu funkciju setContentView(int) koja služi za postavljanja layout-a (xml file koji definiše izgled korisničkog interface-a). Aktivnosti koje su bile potrebne za kreiranje ove aplikacije su: </div> ### Welcome Activity <div style="text-align: justify"> To je prva aktivnost koja se pojavljuje na aplikaciji prilikom pokretanja iste. Da bismo obezbijedili da nam neka aplikacija bude početna, u manifestu file je potrebno dodati nekoliko linija koda (postavlja se intent-filter tag unutar kojeg postavimo da je ovo main aktivnost) unutar te aktivnosti. U ovoj klasi samo je dodana funkcija koja vrši funkciju onClick() na dodatni dugmić i pomoću kojeg prelazimo dalje na aplikaciju (otvaramo novu aktivnost). To vršimo pomoću Intenta. Intent je u osnovi pasivna struktura podataka koja sadrži opis akcije koju mora izvšiti. Najviše se upotrebljava kao „ljepilo“ između aktinosti, iz jedne aktivnosti pokrećemo drugu, a pritom možemo slati i informacije. Dugme koje smo koristi je ImageButton pomoću kojeg postavimo sličicu koja ima funkciju dugmića sa onClick() metodom. </div> ### Main Activity <div style="text-align: justify"> Aktivnost koja se druga prikazuje na aplikaciji. Ona se sastoji od 3 dugmica (button-a). Prvi button nas vodi na drugu akivnost ActivitiesActivity, drugi na AddActivity i a posljednji na AboutActvity. Ovdje je prikazano 2 načina kako možemo preći iz jedne aktivnosti u drugu. Prvi način jeste u funkciji onCreate() instancirati dugme na kojem radimo. Potom na to dugme postavimo setOnClickListener{}, a unutar ove funkcije instanciramo varijablu tipa Intent i na klasičan način prelazimo na drugu aktivnost. Drugi način je postaviti parametar onClick na button u xml file-u, zatim implementaciju te funkcije pišemo u MainActivity. Posljednja funkcija koja se pojavljuje unutar ovog file-a je onConfigurationChanged(). Pomenuta funkcija će se pojavljivati u svim aktivnostima fragmentima (a izmjenama u fragmentima), jer nam ista obezbijeđuje da rotacijom telefona budu sačuvani podaci na stranici i da se prikaže onaj izgled stranice koji trenutno odgovara poziciji uređaja. </div> ### AboutApp Activity <div style="text-align: justify"> Jako je jednostavna aktivnost sa dva TextView taga koja su zaokružena ScrollView konteinerom. </div> ### Add Activity <div style="text-align: justify"> Ova aktinost služi za kreiranje nove kategorije. Korisnik prvo bira boju, a potom vrstu aktivnosti (klikom na jednu od tri aktivnosti otvara se input forma za dodavanje informacija o željenoj novoj kategoriji. U pozadini boje su dugmići, tako da klikom na jednu od njih kupi se int vrijednost odabrane boje i preko intent.putExtra() se šalje u novu aktivnost sa formom za daljni unos. Za izbor aktivnosti korišten je spinner. Spinner nam omogučava padajući meni. U aktivnosti instanciramo spinner i ako je on različit od nule (tačnije null) kreiramo adapter. Kao i svaki drugi adapter i ovaj nam služi za konvertovanje tj. za prilagođavanje podataka izvora i utora. Tako smo pomoću njega popunili spinner sa onoliko elemenata koliko smo kreirali u file-u res\values\strings u tagu <string-array>. Postavimo adapter na spinner, te postavimo setItemSelected{}, unutar koje provjeravamo trenutnu poziciji. U zavisnoti čemu je jednak izabrani string, otvara se aktivnost za kreiranje nove željene kategorije. </div> ### AddNewTime Activity <div style="text-align: justify"> Ova aktivnost predstavlja input formu za unos podataka za kreiranje nove kategorije, koja je vremenska aktivnost. Za unos dodatnih osobina koristi se dinamičko iscrtavanje EditText-a. Na stranici su ponuđena već 3 input polja. U slučaju da korinik za trenutnu kategoriju ima više osobina, klikom na dugme „+“ iscrtava se još jedno input polje. Dugmićem „ZAVRSI UNOS OSOBINA“ korisnik može provjeriti da li je dobre osobine upisao, jer će mu se svi uneseni podaci ispisati u TextView polju. Potom na dugme „DODAJ AKTIVNOST“, on šalje upit na bazu. Funkcija Add_Line() instancira layout (LinearLayout) u kojem iscrtavamo novi element i pomoću LayoutParametar i EditText postavlja parametre novog EditText-a, te funkcijom addView dodaje novi EditText element u, na početku instancirani, layout. Funkcija get_Answer() kupi text sa svih TextEdit polja, dodaje ih u listu, iz liste ih pretvara u string (funkcija join.ToString()). Nakon toga ispisuje ih u predviđeni TextView. Pomoću funkcije addToDB unosimo u novu kategoriju u bazu, koju kreiramo od prikupljnih unesenih podataka. Pomocu runBlocking omogućavamo čekanje unosa podataka, da bismo olakšali asinhronost funkcije za unošenje podataka. Za unos u bazu koristi se funkcija insert iz repository-ja. </div> ### AddNewQuan Activity <div style="text-align: justify"> Identična je prethodnoj aktivnosti uz mali dodatak. Implementiran je još jedan spinner koji radi na identičan način kao i spinner opisan u prethodnoj aktivnosti, koji nam omogućava izbor mjerne jedinice. Za mjerne jedinice smo kreirali data class (klasu koja se sastoji samo od atributa). U ovom slučaju klasa je čuva samo unitsName, polje tipa String, što predstavlja ime mjerne jednice. Potom je kreirana singleton klasa. Singleton je software design pattern (obrazac dizajna softvera) koji je obezbijeđuje i garantuje da klasa ima samo jednu instancu, te globalnu tačku pristupa pruža joj ova klasa. Singleton treba imati globalni pristup tako da mu svaka klasa može pristupiti. Da bismo implementirali singleton, potrebno je kreirati singleton class (klasu). Unutar singleton-a kreirana je funkcija init unutar koje je kreirana lista mjernih jedinica. A funkcija getUnits() omogućava vraćanje ove liste. U AddNewQuanActivity potrebno je instancirati singleton, potom pomoću instance pristupiti listi. I sada iz ove liste preko adaptera podtavljamo mjerne jedinice u spinner. Klikom na neku jednicu pomoću funkcije onItemSelected() spašavamo podatak o kojoj mjernoj jednici je riječ, da bismo to proslijedili u funkciju addToDB. </div> ### AddNewInc Activity <div style="text-align: justify"> Slična prethodnim dvjema aktivnostima </div> ### Activities Activity <center> <img src = "https://imgur.com/xnUP1oS.png"> </center> <div style="text-align: justify"> Ova aktivnost je znantno složenija aktivnost unutar koje se smjenjuju svi fragmenti koji se pojavljuju u projektu. Na vrhu stranice imamo TabLayout sa tri TabItem elementa (što su 3 vrste aktivnosti). TabLayout je Layout koji kreiran od vodoravnih kartica. Klikom na svaku od njih pojavljuje se fragment sa listom kategorija tih aktivnosti (TimeListFragment, ColListFrgament i IncListFragment). Lista je implementirana pomoću RecycleView konteinera. <b><i>RecyclerView</i></b> je napredna i fleksibilna verzija ListVIew-a. Služi nam za prikaz pokretne liste čiji su elementi kartice na kojima se prikazuju razni podaci (razni tipovi podataka). Ovaj widget koristimo kada kolekciju koju prikazujemo čine elementi koji se mijenjaju za vrijeme izvršavanja aplikacije od strane korisnika ili mrežnih događaja. U RecycleView modelu nekoliko različitih komponenti rade zajedno na prikazivanju prosljeđenih podataka. Izgled liste se kreira pomoću ViewHolder objekta. Ovaj objekat je instaca klase koju kreirate proširivanjem RecyclerView.ViewHolder. View holder objektom upravlja adapter kojeg kreiramo proširivanjem RecyclerView.Adapter. Adapter stvara view holder po potrebi. Instanciranje view holdera vršimo u override metodi onCreateViewHolder(). Adapter također povezuje view holder sa njegovim podacima, postavlja holder na trenutnu poziciju i poziva metodu onBindViewHolder(). Ova metoda prema položaju holdera određuje kakav sadržaj će biti prikazan. Dakle adapter nam služi za prilagođavanje podataka na kartici i postavljanje izgleda iste baš onako kako mi to želimo ili trebamo. Na ovoj aktivnosti imamo dodan options menu (menu koji se prikazuje u gornjem desnom uglu u idu 3 vertikalne tačkice). Otvaranjem ove ikonice pojavljuje se dva subItema, a klikom na njih otvaraju se AddActivity i AboutAppActivity redom. Implementacija options menu-a se ogleda u tome da dodamo menu layout file u res folder, dodamo onoliko subItema koliko želimo da se prikazuju na aplikaciji i svakom od njih dodijelimo id. Potom u aktivnosti u kojoj se pojavljuje ovaj menu implementiramo funkcije onCreateOpstionsMenu(Menu) koji kreira menu i povezuje ga sa tačno određenim (zadanim) menu-om, te onOptionsItemSelected (MenuItem) unutar koje se specificira koju će funkcionalnost imati koji item. </div> ### DayCategories Activity <br> <center> <img src = "https://imgur.com/yq1VoLI.png" height = 500px> <img src = "https://imgur.com/SvkJN8q.png" height = 500px> </center> <div style="text-align: justify"> <br><br> Ovo je posljednja aktivnost. Ova aktivnost na aplikaciji se otvara klikom na neki element liste recycle view-a i služi za rad sa fragmentima koji su vezani za dnevne aktivnosti (njihovo izlistavanje, brisanje, dodavanje, ažuriranje...). Pošto je ovo aktivnost koja nam služi kao podloga za rad sa fragmentima, bilo je potrebno implementirati navigaciju za navigiranje fragmentima. Bilo je potrebno u res folderu kreirati navigation layout i u njemu učitati one fragmente sa kojima će se raditi u okviru ove aktivnosti. A u klasi DayCategoriesActivity gleda se koja je aktivnost trenutne dnevne kategorije, te se za myNavHostFragment postavlja jedan od sljedećih fragmenata: TimeDayCat, QuanDayCat ili IncDayCat u zavisnosti da li je vremenskog, količinskog ili inkrementalnog tipa kategorija, a postavljanje se vrši preko supportFragmentMenager-a. U nav_graph.xml file se dalje dodaju ostali fragmenti koji su potrebni na ovom dijelu aplikacije, te veze izmedju njih. </div> <hr> <div style="text-align: justify-all"> Sve aktivnosti u sebi imaju implementiranu funkciju onConfigurationChanged(Configuration) koja služi za omogućavanje rotacije ekrana tačnije da se prikazuje tačno definisan layout za trenutni položaj mobilnog uređaja. </div> <br><br><hr> # Fragment ### TimeList Fragment <div style="text-align: justify"> Fragment koji je recycle view lista sa svim kategorijama koje se odnose na vremenske aktvnosti. Dakle imamo instancirane sve potrebne liste, adapter, uisope, viewModel. U adapteru je implementiran interface funkcije onItemClick, a funkcija je implementirana u ovom fragmentu i ovo nam omogućava klik na jedan član liste. Klikom na element liste otvara se aktivnost DayCategoriesActivity unutar kojeg se smjenjuju fragmenti koji daju detaljnije infromacije i više mogućosti za rad sa odabranom (klikutom) kategorijom. Funkcija onDeleteClicked vrši brisanje te kategorije iz baze. </div> ### IncList Fragment <div style="text-align: justify"> Potpuno identičan prethodnom fragmentu. </div> ### ColList Fragment <div style="text-align: justify"> Također analogan prvom fragmentu. </div> ### ColList Fragment <div style="text-align: justify"> Također analogan prvom fragmentu. </div> ### TimeDayCat Fragment <div style="text-align: justify"> Također, je fragment koji u sebi ima implementiran recycleView sa listom svih dnevnih kategorija odabrane kategorije vrmenske aktivnosti. Pored toga, dodana je ponovo onItemClick funkcija koja omogućava da klikom na neku odabranu dnevnu kategoriju otvra se fragment sa detaljima o toj dnevnoj aktivnosti. Za prenošenje infromacija iz TmeDayCat u novi ActivityDetailsFragment fragment korištena je klasa koja ima polja tipa @JwmFielde. Ova klasa nam služi za prenos podataka. Za svaki podatak koji želimo prenijeti postavimo po jedan polje u ovoj klasi. Tokom fragmenta popunimo polja sa tačnim informacijama. U sljedećem fragmentu samo pozovemo ime klase i parametar kojem želimo pristupiti. Na ovom fragmentu je implementiran i onSwipeTouchListener (tačnije dvije funkcija onSwipeRight() i onSwipeLight()). One nam služe za podršku, da kada korisnik swipe (pomjeri) cijelu stranicu, otvorit će nam se novi fragment. Da bi ovo bio omogućeno, prethodno je bilo potrebno implementirati open class OnSwipeTouchListener(Context) koja nasljeđuje klasu view.OnTouchListener unutar koje se implemetiraju funkcije onSwipeLeft, onSwipeRight, onSwipeUp, onSwipeDown, onClick, onDoubleClick, onLongClick. Zatim u aktvnosti ili fragmentu u kojem želimo postići neku akciju na pomjeranje stranice, na neki layout (npr RelativeLayout) postavimo onSwipeTouchListener i override-amo (preklopimo) funkcije koje su nam potrebne, te dodamo dodatne željene metode. U našem slučaju otvara se fragment ChartFragment. Na fragmentu je postaljen i floatingActionButton + koji prelazi na novi fragment (AddDayActivityFragment). Floating button se razlikuje od ostalih dugmica jer njegova pozicija je fiksna, uvije se prikazuje u istom dijelu stranice (kod nas donji desni ugao). </div> ### QuanDayCat Fragment <div style="text-align: justify"> Analogan prethodnom adapteru. </div> ### IncDayCat Fragment <div style="text-align: justify"> Također analogan prethodnom adapteru, samo se mijenjaju liste sa infromacijama </div> ### AddDayActivity Fragment <center> <img src = "https://imgur.com/Pp12Zbo.png" height = 500px> <img src = "https://imgur.com/QtavmHU.png" height = 500px></center> <br><br> <div style="text-align: justify"> je fragment na kojem se vrši dodavanje nove dnevne aktivnosti, dakle potrebno je dodati novi datum (kojeg kupimo sa DatePickera). U zavisnoti o kojem tipu kategorije je riječ prikazuje se jedan od layouta, dok su ostali skriveni, unutar kojeg se unose sve potrebne informacije ili se pokreće timer (korišten chronometar). Na button „DODAJ“ sve prikupljene informacije dodaju se u bazu i vraćamo se u listu tih dnevnih kategorija </div> ### ActivityDetails Fragment <br> <center> <img src = "https://imgur.com/uw3Vv0A.png" height = 500px> </center> <br><br> <div style="text-align: justify"> Prikazuje informacije o jednoj dnevnoj kategoriji. Prikazan je kalendar sa ucrtanim datumom. Tu se nalaze i 3 dugmića za brisanje, ažuriranje i dodavanje nove dnevne aktivnosti. Dugmici su okrugli, ali nisu korišteni floatingActionButton kao tip dugmica, već ImageButton, a za oblikovanje kreiran je u drawable folderu file koji postavlja border i radius na dugmic tako da na kraju on je okrugao a ne četvrtast, kakvog smo ga naviknuli viđati. </div> ### Update Fragment <br> <center> <img src = "https://imgur.com/yXssYVr.png" height = 500px> </center> <br><br> <div style="text-align: justify"> Otvara se kada korisnik klikne na dugme za ažuriranje dnevne kategorije. Unutar njega su smještena 3 layouta na početku skrivena. Potom fragment provjerava koje tipa je dnevna kategorija za koji je dobio podatke i shodno tome prikazuje odgovarajući layout. Na kraju na button submit unesene promjena se postavljaju u bazu i vraćamo se na stranicu o detaljima. </div> <hr> <div style="text-align: justify-all"> Unutar svakog fragmenta implementirana je funkcija onConfigurationChanged(Configuration) koja nam omogućava rotaciju mobilnog uređaja, a da se pri tom prikazuje odgovarajući layout. </div> <br><br> <hr> # ViewModel i ViewModelFactory <div style="text-align: justify"> <b><i>ViewModel i LiveData</i></b> su dio arhitekture androida, predstavljaju biblioteke i komponente koje nam omogućavaju pisanje aplikacija koje su kompaktne, povjerljive i održive. ViewModel je klasa koja je zadužena za upravljanjem (prikupljanjem i čuvanjem) podataka potrebnih za aktivnosti ili fragmente, a oni trebaju moći pratiti promjene u ViewModelu. Pored ovog ona obezbjeđuje komunikaciju fragmenata/aktivnosti sa ostatkom aplikacije. ViewModel se kreira zajedno sa scope-om (fragment ili aktivnost) i živi dok god živi i sam scope. ViewModelFactory intscira ViewModel objekat, sa ili bez parametara konstruktora. Factory pattern (obrazac) je design pattern (obrazac dizajna) koji se koristi za kreiranje objakata. Ovo je metoda koja vraća instancu iste klase. U ovom projektu implmentirana su dva ViewModel-a, ponovo, jedan za kategorije, drugi za dnevne kategorije. Oni nam služe za prenos liste ili funkcije iz repositorija na UI i na taj način obezbijeđuju da informacije budu prenesene u adaptere. </div> # LiveData i LiveData Observer <div style="text-align: justify"> LiveDate je klasa koja sadrži podatke. Ona je svjesna lifecycle-a (životnog vijeka ciklusa) (poštuje životni ciklus ostalih komponenti aplikacije). LiveData smatra da je observer (posmatrač), kojeg predstavlja observer klasa, u aktivnom stranju ako je njegov lifecycle nalazi u stanju STARTED (početak) ili RESUME (ponovo), stoga samo aktivne observere LiveData obavještava promjenama i ažuriranjima. LiveData je posmatrana i observer je obaviješten kada se neka promjena unutar LiveData objekta. Ona sadrži podatke. Enkapsulacija je način izravnog pristupa nekim poljima objekata. Pomoću enkapsulacije kontolirate kako druge klase mogu manipulisati tim unutarnjim poljima. Samo ViewModel treba da upravlja podacima aplikacije, a UI da ih čita. Observer pattern je softverski dizajnerski obrazac i zadatak mu je specificirati komunikaciju između objekata: predmet obzervacije (posmatranja) i obzervere (posmatrače). Predmet posmatranja obavještava observere o promjenama. U ovom slučaju predmet posmatranja je LiveDate, a observeri su UI kontoleri (fragmenti i aktivnosti). </div> # Room Database <div style="text-align: justify"> Room je postojana biblioteka koja pruža sloj apstrakcije preko SQLite-a kako bi se omogućio sigurniji pristup bazi podataka uz istovremeno korištenje pune snage SQLite-a. Room je dio Android arhitekturni komponenti. Ona nam dozvoljava kreiranja i manipulaciju SQLite bazom podataka na puno jednostavniji način. Ona automatski prevodi anotacije u SQLite upite kako bi obavila željene instrukcije. Pomenuta biblioteka pomaže nam pri kreiranju predmemorije podataka aplikacije na uređaju koji pokreće aplikaciju. Tri (velike) najvažnije komponente Room baze su: </div> <br> <ul> <li><b>Database (baza podataka):</b> ovo je klasa koja sadrži anotaciju @Database. Ona predstavlja objekt koji sadži konekciju sa SQLite bazom i sve funkcije koje su potrebne za njeno pokretanje i upravljanje. </li> <br> <li><b>Entiteti (Entity):</b> je tabela u Room bazi. Ovdje pohranjujemo sve podatke potrebne za rad jedne tabele. Raspoznajemo ih po anotaciji @Entity. </li> <br> <li><b>DAO (Database Acces Object):</b> je interface koji u sebi sadrži metode potrebne za pristup bazi i njenim upravljanjem. Analogno, posjeduje anotaciju @Dao. </li> </ul> <br> <div style="text-align: justify"> Za potrebe ove aplikacije, kreirali smo 5 entiteta: Activity, Category, DayCategory, ActivtyWithCategories, CategoryWithDayCategories. Entitet odnosno u duhu baza podataka tabela Activity čuva sve aktivnosti. Sastoji je od jednog polja „aname“ koji je tipa String i on ujedno predstavlja i primarni ključ ove tabele. Sljedeći entitet je Category, on u sebi pohranjuje sve informacije o jednoj kategoriji, npr. ime, kojeg je tipa, inkrement ako je u pitanju inkrementalna kategorija, mjernu jedinicu za količinske, dodatne osobine te kategorije, boju pomoću koje se grupišu aktivnosti, itd. Za primarni ključ postavljena je brojčana vrijednost tip Int koja je autoGenerate. Za svaku kategoriju potrebno je voditi evindenciju o dnevnoj aktivnosti korisnika. Radi lakšeg i preglednijeg rada i same baze, uvodimo novi entitet: DayCategory. Unutar ove tabele smješten su podaci datum na koju se odnosi ova kategorija, infromacija o količini za taj datum, ime i id kategorije na koju se odnosi ovaj tzv. podkategorija i string u kojem je smještena dodatna osobina procijenjena za ovaj dan. Ovo su bile 3 glavne tabele. Pošto tabele nisu međusobno nezavisne, to je onda trebalo implementirati tabele koje će ih povezivati. ActivtyWithCategories spaja tabelu Category sa tabelom Activity, tj. ova tabela „glumi“ strani ključ tabeli Category a tabelu Activity. Analogna je priča za tabelu CategoryWithDayCategories, ova tabela povezuje tabelu DayCategory sa tabelom Category. <br> Nešto više o entitet klasama: Anotacijom<b>@PrimaryKey </b> postavljamo tu kolonu za primarni ključ tabele. <b>@ColumnInfo</b> nam daje mogućnost da ime ove kolone preimenujemo i da joj možemo u ostatku aplikacije pristupati preko nekog drugog imena, onog kojeg postavimo kao parametar name = „...“ ) Anotacija <b>@Embedded</b> koristimo u slučajevima kada kao tip jedne kolone želimo postaviti neku složenu strukturu, a u ovoj aplikaciji smo koristili kada želimo da kolona predstavlja cijeli drugi entitet. </div> <br><br> <div style="text-align: justify"> <b>Data Access Objects ili DAOs</b> (kako im samo ime kaže) upotrebljavamo za pristup podacima kada implementiramo Room. Svaki DAO treba da sadrži set metoda za manipulaciju podataka (insert/umenatnje, update/ažuriranje, delete/brisanje, get/prikupljanje). Zbog činjenice da su 3 glavna entiteta, a dva su samo pomoćna, stoga pravimo samo 3 DAO objekta: ActivityDAO, CategoryDAO, DayCategoryDAO. Sve tri klase su slične, sadrže osnovne upite get, update, insert, delete, zbog toga detaljnije ćemo objasniti samo jednu npr. CategoryDAO. Unutar ove klase imamo: getAllCategories (upit koji vraća sve kategorije), getOneTypeCategories (upit koji vraća samo jedan tip kategorija npr samo kategorije koje su tipa vremenske aktnosti), update, insert i delete su funkcije koje prime kao parametar kategoriju i rade sa njoj ono što mu je naređeno, a posljednja funkcija deleteAllCategories briše sve aktivnosti, tačnije briše sve podatke iz tabele. Kao i sve do sada i samu bazu predstavljamo kao abstraktnu klasu koju je potrebno naslijediti od RoomDatabase. Anotacija @Database prima parametar entities u kojem postavljamo svoje entitete. Potrebno je kreirati i apstraktne promijenljive ili funkcije koje će instancirati DAO objekte. Neophodna stavka ove funkcije jeste i companion object {}. Unutar ovog objekta dozvoljavamo klijentima da pristupaju metodama za kreiranje i prstup bazi bez istanciranja klase. Varijabla instance se u početku postavi na nulu. Potom ona kada se baza jednom kreira, varijabla instace čuva referencu na bazu. Ovo nam omogućava da ponovno otvaramo konekciju na bazu, a istoremeno smanjimo vrijeme ove akcije. </div> # Korutine ( Coroutines ) <div style="text-align: justify"> <b>Korutine </b>su način upravljanja zadacima koji se dugo odrađuju. Kotlin korutine konvertuju callback bazirane kodove u sekvencijalne kodove, jer su sekvencijalni kodovi čitljiviji, lakši za upravljanje te nude više mogućnosti. Korutine su asinhrone, ne podržavaju blokove i koriste suspend funkcije (da bi pretvorili asinhroni kod u sinhroni). Asinhronost korutina se izražava u činjenici da se one izvršavaju nezavisno od koraka izvršavanja programa. Mogu raditi dok je program još u fazi čekanja na neki event. Suspend je kotlinova ključna riječ i služi za pravljenje funkcije ili tipa funkcje da bude vidljiv korutinama. Kada neka korutina pozove funkciju, ona vrši suspenziju sve dok ne dobije rezultat, potom korutina nastavlja tamo gdje je stala. Da bi mogli implementirali korutine u Kotlinu moramo se upoznati sa sljedeće tri stvari: job (zadataka), dispatcher i scope (oblast vidljivosti). <b>Job </b>je sve ono što može biti zaustavljeno. Svaka korutina posjeduje job i upravo se koristi za zaustavljanje korutine. Job može biti raspoređen u roditelj-dijete hijerarhiju, i zaustavljanjem roditeljskoj zadataka, instantno se zaustavljaju svi zadaci djece. <b>Dispatcher </b>šalje korake za pokretanje na različitim nitima. <b>Korutinski scope </b> ili oblast vidljivosti korutine definišu kontext u kojem korutine se pokreću. Scope povezuje tj. kombinuje informacije job-a (zadataka) i dispatcher. </div> <hr> # Repository <div style="text-align: justify"> <b>Reopository</b> u android aplikacijama nam omogućava podršku izvanmrežog predmemoriranja. Ako se ne implentira respository, aplikacije koje dohvataju podatke s poslužitelja (tj. servera) korisnik bi prilikom svakog pokretanja vidio zasloz za učitavanje, što nije dobra praksa i a sam klijent neće biti zadovoljan aplikacijom. Izvanmrežno programiranje se ogleda u tome da sama aplikacija sprema podatke preuzete sa mreže u svoju lokalnu memoriju uređaja, za brži pristup. Dakle podaci se neće preuzimati sa mreže svaki put, već samo jednom a potom će se isti prikupljati iz baze. Naravno, kada se primi novi mrežni rezultat, lokalna baza se ažurira i na zaslonu aplikacije se prikazuje novi sadržaj. Kao i sve do sada, repository se implemetira u vidu repozitorijske klase. U arhitekturi aplikacije ona bi trebala biti smještena izmedju izvora podataka i ostatka aplikacije, jer repository pattern (repository obrazac) izoluje podatke od ostatka aplikacije. U našoj aplikaciji implementirana su dvije repositoryjske klase: CategoryRepository i DayCategoryRepository, koji se odnose na kategorije i dnevne kategorije redom. U njima su instancirane liste, za kategorije to su 3 različite liste zavisno od aktivnosti na koju se odnose, dok u drugom repositoryju, za dnevne aktivnosti, samo je jedna lista kojom pristupamo svim dnevnim kategorijama određene kategorije. Unuta klase kreirane su high order funkcije, koje pozivaju funkcije kreirane u DAO interface-ima. One su kreirane tako da jedna funkcija čeka drugu sa radom tj. može se pauzirati neka od funkcija u svom scope-u, ali će njeno izvršavanje nastavljeno, jer se ne može desiti da funkcija ostane nedovršena. Npr. select kategorija je funkcija koja će „sačekati“ dok funkcija insert ne doda novu kategorijuu bazu. Nakon toga select funkcija nastavlja sa izvršavanjem. </div> # Classes ### Converters.kt <div style="text-align: justify"> Kao što ste primijetili, za implementaciju i rad ove aplikacije bilo je potrebno raditi sa podacima koji su datumi. Zbog specifičnosti baze SQLite, koja ne podržava tip Date ili neki sličan tip podataka, bilo je potrebno implementirati klasu Converters koja će nam omogućavati spašavanje datuma u bazu. Klasa se sastoji of dvije funkcije fromTimestamp(Long) i dateToTimestamp(Date). Ova klasa nam omogućava da kada pokupimo neki datum sa front-a, kao tip Date, da ga prebacimo u tip Long i kao takvog ga čuvamo u bazi. Suprotan proces se dešava kada kupimo podatke iz baze, tada Long tip pretvaramo u Datum i vraćamo isti iz funkcije.</div> ### Unit.kt <div style="text-align: justify"> Ovo je klasična data class koja ima ulogu struktura u drugim programskim jezicima. Ona nam omogućava kreiranje novog složenog tipa podataka, radi lakšeg rada. Klasa Unit služi za pohranu mjernih jedinica. Rad sa ovom klasom obezbijeđuje nam singleton klasa. </div> ### UnitSingleton.kt <div style="text-align: justify"> Ova klasa predstavlja klasičnu singleton klasu (o singleton klasama i njihovoj funkciji bilo je već govora u ovom tekstu). Kao i svaki singleton i ovaj nam vraća listu mjesnih jedinica. U fukciji su init inicijalizirane su sve jedinice koje se pojavljuju u aplikaciji. </div>