***Osnovni podaci o projektu*** - **predmet**: Razvoj mobilnih aplikacija - **predmetni profesor**: Prof. dr Elmedin Selmanović - **autori**: Elma Turčinović, Sara-Farah Materne - **studijska godina**: 2020/2021 ***O aplikaciji*** Aplikacija **CovApp** razvijana je kao semestralni projekat iz predmeta *Razvoj mobilnih aplikacija*. Pred nama je bio zadatak da razvijemo aplikaciju koja služi kao simulacija registracije za vakcinu protiv Covid-19. Za razvoj aplikacije korišten je programski jezik Kotlin. Vizuali (ikonice) vezani za Covid-19 preuzeti su sa stranice https://www.flaticon.com, iz paketa **Virus transmission**, a njihov autor je korisnik *mavadee*. <br> <div style="text-align:center"><img src="https://i.imgur.com/x3Pd1d9.png" width = 400/></div> <br> ***Struktura aplikacije*** **CovApp** je podijeljena u tri smislene cjeline: - registracija, - statistika - odjeljak o aplikaciji, te sadrži i početni (**Home**) dio koji je početna tačka za pristup svakoj od njih. **Registracija** je glavni i najkompleksniji dio aplikacije. Podrazumijeva registraciju korisnika kroz više faza, koje zahtijevaju unos podataka kao što su lične informacije, radno mjesto, podaci o zdravstvenom stanju, a naposlijetku i tip željene vakcine i lokacija vakcinacije. Ukoliko se prilikom procesa registracije detektuje da neki od uslova nije ispunjen (korisnik je strani državljanin, boluje/nedavno je bolovao od Covid-19, ili je maloljetan), prikazuje se ekran sa prikladnom porukom, te se korisnik vraća na početnu stranicu. Ukoliko je registracija uspješna, tada se korisniku na osnovu zaključenog prioriteta (dob, nepovoljno zdravstveno stanje, visok rizik na radnom mjestu) dodjeljuje odgovarajući termin na odabranoj lokaciji. **Statistika** simulira broj registrovanih korisnika te vakcina koje su odabrali putem aplikacije **CovApp**. Kako za ovaj projekat nije korištena baza podataka, to simulacija predstavlja jednostavnu situaciju - svake sekunde bilježi se po jedan novi korisnik sa slučajno dodijeljenom vakcinom. U odjeljku **o aplikaciji** moguće je pročitati najosnovnije informacije o aplikaciji. ***Arhitektura aplikacije*** Budući da se radi o poprilično jednostavnoj aplikaciji koja ne koristi bazu podataka, te je rad sa podacima sveden na minimum (neophodno je bilo implementirati samo njihov prenos kroz tok registracije), **CovApp** ne posjeduje specifičnu arhitekturu kao npr. MVVM. Ipak, gdje je to bilo moguće, nastojale smo razdvojiti logiku i lokalno čuvanje podataka od user interface-a (ViewModel i View). Aplikacija sadrži jednu glavnu aktivnost, te više fragmenata koji su korišteni za prikaz manjih dijelova aplikacije. Fragmenti su raspoređeni u direktorije na osnovu gore spomenutih smislenih cjelina, uz dodatak odvojenog direktorija za Home fragment. <br> <div style="text-align:center"><img src="https://scontent.fsjj1-1.fna.fbcdn.net/v/t1.15752-9/184919427_738815376798053_8052523866402512059_n.png?_nc_cat=102&ccb=1-3&_nc_sid=ae9488&_nc_ohc=QsFTQJT6hEgAX_eoELh&_nc_ht=scontent.fsjj1-1.fna&oh=8e17467b3b32c5b88a787eda2824f8c2&oe=60C23A5F" /></div> <br> Budući da aplikacija sadrži relativno veliki broj fragmenata, te su njihove funkcionalnosti u većini slučajeva slične, oni će samo biti navedeni sa kratkim opisima: U aboutApp direktoriju nalazi se AboutAppFragment koji daje pregled osnovnih informacija o aplikaciji. U home direktoriju nalazi se HomeFragment koji predstavlja prvi fragment sa kojim se korisnik susreće pri pokretanju aplikacije. U statistics direktoriju je StatisticsFragment za prikaz registrovanih korisnika, te SharedPreferences klasa koju koristimo za čuvanje podataka. U registration direktoriju nalaze se sljedeći fragmenti: - BadAgeFragment - BadCitizenshipFragment - BadCovidFragment - CitizenshipAgeFragment - CovidFragment - HealthConditionsFragment - PersonalInfoFragment - ProfessionFragment - SummaryFragment - VaccineFragment Fragmenti sa prefiksom *Bad* prikazuju se korisniku ukoliko podaci koje je unio ne ispunjavaju već navedene kriterije za vakcinaciju. Ostali fragmenti, osim summary fragmenta, od korisnika zahtijevaju unos traženih podataka. Zajedničko svim fragmentima koji zahtijevaju unos jeste to što, ukoliko neko od traženih polja nije popunjeno, korisniku je onemogućen prelazak na naredni fragment. Redoslijed kojim se korisnik može kretati kroz fragmente opisan je u nastavku, u odjeljku o *navigaciji*. Prelasci sa fragmenta na fragment odvijaju se pritiskom na odgovarajuću dugmad. *****Dizajn aplikacije***** Sami dizajn aplikacije je prilično jednostavan i prilagođen većini korisnika. Kroz cijelu aplikaciju provlači se tema nijansi plave boje, koja psihološki dokazano utiče na čovjeka stvarajući mu osjećaj mira i pouzdanosti, zbog čega je često možemo vidjeti u zdravstvenim ili sličnim ustanovama. Kako je i tematika aplikacije zdravlje i njegova zaštita, to se plava boja pokazala pogodnom za većinu dijelova aplikacije. Sve boje korištene u dizajnu aplikacije mogu se naći u folderu **values**. Na dizajn aplikacije najvećim dijelom utiče raspored njenih elemenata koji je definisan u fajlovima u folderu **layout**. Bitno je napomenuti da u folderu sa resursima aplikacije imamo dva foldera sa layoutima, pri čemu su u jednom definisani prikazi fragmenata u **portrait**, a u drugom u **landscape** orijentaciji. Radi konzistentnosti, svi fragmenti za registraciju korisnika u sebi sadrže ****constraint layout,**** u kojemu se nalaze jedan layout za elemente tog fragmenta i dugme za prelazak na naredni fragment. Kod prikaza fragmenata za landscape orijentaciju, svi fragmenti dijela za registraciju sadrže **scroll view**, kako bi se nesmetano, bez preklapanja elemenata i narušavanja izgleda svi elementi fragmenata mogli biti prikazani. Također imamo i fragmente koji se prikazuju kada neki od uslova za registraciju nije ispunjen, a koji se razlikuju i dizajnu odnosu na fragmente za registraciju korisnika, Takvi fragmenti imaju pozadinu roze/svijetlo-crvene boje, koja nam govori da je došlo do neke greške. Pored toga, u zavisnosti od uslova za prijavu koji nije ispunjen (starost ispod 18 godina, državljanstvo ili simptomi zaraze virusom Covid-19), u pozadini se prikazuje slika, te poruka koja ukazuje na neispunjeni uslov. <div style="text-align:center"><img src="https://i.imgur.com/Jb0Tv3Q.png" /></div> Aplikacija posjeduje i svoj App bar koji prikazuje odgovarajuće naslove, a na Home fragmentu omogućava i pristup Navigation Draweru. Obje ove funkcionalnosti podešavaju se unutar navigacijske komponente o kojoj će biti riječi kasnije. Pomoću navigacijske komponente također se vodi računa i o postojanju **up button**-a na App baru u odgovarajućim situacijama. Za Summary fragment, u app baru se dodaje još jedan item - **share button**, koji korisniku omogućava slanje predefinirane poruke koja služi kao potvrda zakazanog termina za vakcinaciju. Za slanje podataka na share button postavljen je onClickListener, te implementirana metoda koja koristi **Intent** za slanje poruke. Intent omogućava da se započne aktivnost u nekoj drugoj aplikaciji, opisujući akciju koju želimo izvesti. Ovakav tip intenta naziva se *implicitni*, jer ne specificira aplikaciju u kojoj aktivnost treba da počne, već specificira akciju, i podatke koje će akcija koristiti. <div style="text-align:center"><img src="https://i.imgur.com/JoG8nV1.jpg" /></div> <br> <div style="text-align:center" ><img src="https://i.imgur.com/7vXzCsQ.jpg" width = 300/></div> <br> ***Prevod fragmenata na engleski jezik*** Aplikacija CovApp kao svoj feature ima i dvojezičnost. Defaultni jezik je B/H/S, a dostupna je i u varijanti na engleskom jeziku. Ovo je implementirano koristeći Androidov **Translations Editor**. Budući da tekst aplikacije nije hardkodiran, već su svi stringovi spašeni kao **values**, pomoću Translations Editora moguće je dodijeliti im odgovarajući prevod. Time je omogućeno i lagano proširivanje aplikacije za šire tržište, budući da je u tu svrhu potrebno samo unijeti odgovarajuće vrijednosti na odabranom jeziku. Da bi se pristupilo engleskoj verziji aplikacije, neophodno je u postavkama telefona odabrati engleski jezik kao primarni. U suprotnom, bit će prikazana B/H/S verzija. <div style="text-align:center"><img src="https://i.imgur.com/MdyO8DX.png" /></div> <br> <div style="text-align:center"><img src="https://i.imgur.com/ZCpdy1d.png" /></div> <br> ***Navigacija kroz aplikaciju*** Mobilne aplikacije uglavnom zahtijevaju da korisnik izvrši niz akcija kako bi kompletirao određeni zadatak. U kontekstu Android aplikacija, ovaj proces naziva se navigacija, te u slučaju **CovApp**, odnosi se na navigaciju kroz fragmente. ***Navigation component*** Navigacijska komponenta sastoji se od tri dijela - navigacijskog grafa, navigacijskog hosta, te navigacijskog kontrolera. Na slici je prikazan navigacijski graf naše aplikacije: ![slika:navigacijski graf](https://i.imgur.com/eV82A9G.png) Navigacijskim grafom opisane su "putanje" dostupne korisniku pri korištenju aplikacije. Sa slike se jasno vidi kako teče tok registracije, te da se akcija definisana za prelazak na idući fragment razlikuje u ovisnosti od korisnikovih odgovora (kao što je ranije već opisano). Fragmenti lijevo, koji su izolovani od ostatka grafa, su Statistics i About fragmenti, te je njima moguće pristupiti samo pomoću drawer menu-ja. Po završetku registracije korisnik se automatski vraća na Home Fragment, a ukoliko u bilo kojem koraku registracije pokuša da se vrati unatrag (up strelica na App baru), bit će vraćen na Home Fragment. Ovo je postignuto pomoću uklanjanja fragmenata sa back stacka. Navigacijska komponenta sadrži **NavigationUI** klasu. Ova klasa sadrži metode kojima se upravlja navigacijom pomoću top (app) bara, Navigation drawera, i bottom Navigation drawer je UI panel koji prikazuje glavni navigacijski meni aplikacije. On se prikazuje kada korisnik pritisne odgovarajuću ikonicu (3 horizontalne crte), ili kada slijeva prstom swipe-a preko ekrana. U našem Navigation draweru obezbijeđen je prelazak na Home, Statistics i About fragment. ***Uklanjanje fragmenata sa back stacka*** Po defaultu, prelaskom na sljedeći fragment, prethodni se dodaje na tzv. ***Back Stack*** aplikacije. Nakon što pritisnemo back dugme, trenutni se pop-a (uklanja) sa stacka, pa pristupamo prethodnom. Ovo ponašanje nekada može biti poželjno, ali smatrale smo da je prilikom registracije ono bespotrebno jer je proces kratak i jednostavan, a aplikacija posjeduje i neka stanja iz kojih ne bi imalo smisla da je omogućeno ići natrag ( "dead ends" - fragmenti koji označavaju loš unos te fragment sa pregledom unešenih podataka). Stoga, pritiskom na back dugme, dok se korisnik nalazi na bilo kojem od koraka registracije, sa stacka se uklanjaju svi fragmenti do početnog - Home fragmenta. Generalno je uklanjanje sa back stacka dobra praksa ukoliko imamo cirkularnu navigaciju kroz fragmente (npr. A->B->C->A) jer većinom nema smisla da se po završetku zadatka možemo vratiti do neke njegove međufaze. ***Safe Args plugin*** Prilikom navigiranja kroz fragmente potrebno je spašavati do tada unešene korisničke podatke. Pomoću navigacijskog grafa lako je definisati akcije za prelazak sa jednog fragmenta na drugi, kao i parametre (argumente) koji se trebaju prenijeti prilikom tog prelaska, kao i njihove tipove. Za navigacijsku komponentu postoji **Safe Args plugin** koji generiše klase za prenos podataka (*Directions* za fragment iz kojeg se podaci prenose, *Args* da se dohvate podaci preneseni do trenutnog fragmenta, te *Action* koja predstavlja akciju prelaska od trenutnog fragmenta do idućeg). Prenosom podataka koristeći safe args obezbjeđuje se type-safety i lagan pristup. Prije nego što podatke počnemo prenositi kroz aplikaciju, neophodno je ostvariti vezu između logike aplikacije i korisničkog interfejsa. ***View Binding i Data Binding*** Kako bismo povezali layout file-ove (dakle, definisani izgled) sa UI-orijentisanim strukturama podataka kao što su aktivnosti i fragmenti, te omogućili funkcionalnosti aplikacije poput korištenja unešenih podataka ili izmjene nekih njenih dijelova, neophodno je da obezbijedimo način pristupanja manjim segmentima aplikacije. U Androidu, oni se nazivaju **View**-ovi, i predstavljaju osnovnu gradivnu jedinicu za UI komponente unutar layouta aplikacije. Tipični primjeri View-ova su Button (dugme), EditText (izmjenjivo tekstualno polje), TextView (tekstualno polje), i sl. View-ovi mogu imati različita svojstva, a jedno od njih je i **id**. View-u je iz "logike" moguće pristupiti (to jeste, uvezati ga sa logikom), upravo koristeći atribut id, pomoću **findViewById** (*public final T **findViewById** (int id)*). Ovo je uglavnom prvi način vezivanja View-ova sa kojim se novi Android developeri susreću prilikom razvijanja Android aplikacije. Ovakav pristup uveden je u API level 1 (2008. godine), te se danas smatra zastarjelim. Neke od njegovih mana su: - za svaki od View-ova iz layouta moramo vršiti zasebnu pretragu preko id-a - nemamo tzv "Null safety" budući da moramo vršiti eksplicitne provjere o tome da li je neki view null prije nego mu pristupimo. Da bi se prevazišli ovi problemi uvedeni su sljedeći pristupi: ***View binding*** Pomoću View bindinga, nakon što se on enable-uje u gradle file-u, automatski se generišu *binding* klase za svaki XML layout. Na ovaj način, nakon što napravimo instancu date klase prilikom poziva metode kojom se fragment kreira, svakom view-u iz tog layout-a možemo pristupiti na način kao da je njen atribut/metoda. U **CovApp** projektu je za većinu fragmenata kao i za glavnu aktivnost korišten upravo View binding zbog toga što je jednostavan za korištenje - nije potrebno uvoditi dodatne postavke kao što je to slučaj sa Data bindingom, te se on brže kompajlira. ***Data binding*** Data binding također generiše svoje binding klase koje omogućavaju direktno povezivanje varijabli komponente sa view-ovima u layout-u. Koristeći Data binding moguće je pratiti promjene (observe), te vršiti direktne izmjene prikazanog sadržaja (npr. pomoću Binding adaptera). U **CovApp aplikaciji**, data binding korišten je unutar fragmenta koji je zadužen za prikaz statistike osoba registrovanih za vakcinu - *StatisticsFragment.* U tu svrhu, u *fragment_statistics.xml*-u bilo je neophodno unutar *data* taga specificirati varijablu pomoću koje će se pristupati podacima iz fragmenta. Ostatak korištenja Data bindinga, u našem slučaju, nije se mnogo razlikovao od view bindinga budući da nismo koristile Observere niti dvosmjerni Data binding. Korištenje bindinga omogućava već rečeno povezivanje UI-a sa logikom. Međutim, te postavke nisu dovoljne kako bi se omogućilo očekivano funkcionisanje aplikacije. ***Spašavanje podataka prilikom promjene konfiguracije*** Naprimjer, ako ne vodimo računa o promjeni konfiguracije, u **CovApp** se može desiti sljedeća situacija: korisnik unese svoje ime, zatim položi telefon (tzv. *landscape mode*), te unese ostale podatke. Ukoliko pokuša da se prebaci na idući fragment, iz aplikacije će dobiti poruku da ime nije unešeno. Ovo se dešava jer se promjenom orijentacije uređaja fragment uništava, a zatim ponovo rekreira u odabranoj orijentaciji, U ovoj aplikaciji korištena su dva pristupa za očuvanje konzistentnosti podataka prilikom promjene konfiguracije - **ViewModel**i i ****savedInstanceState****. Oba pristupa imaju svoje prednosti i mane. **View model** je pomoćna klasa za UI kontroler i zadužena je za pripremu podataka za korisnički interfejs. ViewModel objekti se automatski očuvaju prilikom promjene konfiguracije, tako da su podaci koje oni čuvaju dostupni sljedećoj instanci aktivnosti ili fragmenta. U sklopu **CovApp**, većina fragmenata (svi osim posljednjeg), a koji sadrže View-ove čiji sadržaj ovisi o korisničkom inputu (gdje samim tim potencijalno možemo očekivati gubitak podataka) imaju odgovarajuće ViewModel klase u kojima se čuvaju podaci. **SavedInstanceState** se odnosi na **Bundle** objekat koji se prosljeđuje u onCreate metode aktivnosti i fragmenata. Na ovaj način oni se mogu restore-ati u prethodno stanje koristeći podatke koji su spašeni u Bundle. Bundle će uvijek biti prazan, pa samim tim i null prvi put kad se fragment ili aktivnost pokrene, dok, ukoliko je objekat uništen npr. prilikom rotacije ekrana, tada on čuva podatke. SavedInstanceState u odnosu na ViewModel bolje reaguje u slučaju neočekivanog uništenja aktivnosti (death), ali može čuvati dosta manju količinu podataka. U sklopu **CovApp** ovaj pristup je korišten unutar fragmenta koji služi za simuliranje statistike, kako bismo isprobale i drugi način čuvanja podataka. SavedInstanceState može se koristiti i za trajnije čuvanje podataka unutar aplikacije (nakon njenog gašenja), ali u **CovApp** smo u tu svrhu koristile **SharedPreferences**. Na ovaj način moguće je spašavati i modifikovati podatke u obliku ključ-vrijednost među *preference* podatke. Ovakva implementacija je zahtjevna (teška) za uređaj pa ga može usporiti. U našem slučaju, ovaj pristup je korišten da se sačuva stanje brojača simulacije ukupno registrovanih korisnika, te korisnika prema različitim tipovima vakcina, pa kako se radi o brojevnim (cjelobrojnim) podacima, oni ne opterećuju značajno uređaj. ***Timer i lifecycle*** U kontekstu Android aplikacija, aktivnosti i fragmenti imaju svoje **životne cikluse**. Prilikom navigiranja kroz aplikaciju (izlazak iz nje, ponovni povratak, itd.), fragmenti odnosno aktivnosti prolaze kroz različita stanja. Za navigaciju kroz prelaske iz jednog stanja u drugo, postoji 6 callback funkcija, a one su: onCreate(), onResume(), onPause(), onStop(), i onDestroy(). Sistem poziva svaki od callback-ova kad fragment, odnosno aktivnost uđe u novo stanje. ![](https://i.imgur.com/45HD1Np.jpg) Za simulaciju prijavljenih korisnika neophodno je bilo prikazati (simuliranu) statistiku u realnom vremenu, to jeste u određenim intervalima prikazivati trenutne brojke. U ovu svrhu napravile smo instancu klase **Timer**, te njoj schedule-ale jedan TimerTask. Unutar njega izvršile smo override funkcije run, te uz uslov da je trenutna aktivnost pokrenuta na UI thread-u (jer u suprotnom dolazi do problema sa threadovima), izvršavamo vakcinaciju jednog zamišljenog korisnika svake sekunde. Za ovu implementaciju bilo je potrebno voditi računa o **životnom ciklusu** StatisticsFragment-a, te o spašavanju do tada zabilježenog broja korisnika. Kao što je već rečeno, za spašavanje smo koristile **SharedPreferences**, spašavajući u preference trenutno stanje do tada dostignute brojke vakcinisanih korisnika. U overrideane funkcije onDestroy(), onPause() i onStop() pozvale smo spašavanje podataka u SharedPreferences, te uništenje Timer-a. U funkciji onStart() i onResume() pristupamo spašenim podacima, te ih učitavamo kako bi se brojanje nastavilo, umjesto da se ponovo započne. Smatrale smo da je Timer najpogodnije postaviti unutar onResume() metode kako bismo omogućile da se, npr. po ponovnoj maksimizaciji aplikacije, brojanje nastavi od spašenog stanja. Također, na ovaj način ćemo imati Timer i ako je on prije toga postojao, a zatim je bio uništen. ***Zaključak*** CovApp je dobra osnova za razvoj aplikacije koja bi doista mogla služiti za registraciju za vakcinaciju. U tu svrhu bilo bi potrebno dodati bazu podataka za spašavanje registrovanih korisnika, ali ostatak aplikacije daje dobru osnovu za njeno proširenje. Također, vrlo je lako proširiti aplikaciju novim jezikom, novim uslovima i sl.