# Dokumentacija - Razvoj mobilnih aplikacija - Projekat 2 Tema i zadatak projekta je prezentacija podataka dobivenih kroz Web API. Izabrala sam OMDb APi (The Open Movie Database, link: [https://www.omdbapi.com/](https://www.omdbapi.com/)) koji nam pruža informacije o raznim filmovima, serijama i slično. # OPŠTI KONCEPTI ANDROID FRAMEWORKA # JETPACK COMPOSE Jetpack Compose je moderni set alata za jednostavnije kreiranje Android UI-a. Komponente pojednostavljuju i ubrzavaju razvoj UI-a na Androidu sa manje koda, moćnijim alatima, i intuitivnim Kotlin mogućnostima. Sa komponentama, možemo kreirati svoj UI tako što definišemo set funkcija, koje zovemo composable funkcije, koje uzimaju podatke i prikazuju UI elemente. # FRAGMENT Fragment nastaje sa razvojem tableta. Dio interfejsa koji se može ponovo koristiti i svaki fragment ima svoj layout, lifecycle i sam se bavi svojim elementima. Svaki fragment mora biti hostiran na jednoj aktivnosti. U principu fragmenti i aktivnosti su dosta slični, samo što fragmente mi više kontrolišemo. # UI APLIKACIJE UI aplikacije je sve ono što vidimo na ekranu: tekst, slike, dugmiće/buttone, i razne druge elemente, i kako su oni raspoređeni po ekranu. To je ustvari način na koji aplikacija pokazuje stvari korisniku i način na koji korisnik ima interakciju sa aplikacijom. Svaki od navedenih elemenata se nazivaju UI komponente. Skoro sve što vidimo na ekranu naše aplikacije je UI element tj. komponenta. Mogu biti interaktivne, mogu imati mogućnost da se klikne na njih kao što dugmići, ili ih možemo uređivati kao što su input polja. # COMPOSABLE FUNKCIJE Composable funkcije su osnovni gradivni blok UI. Composable funkcije: * opisuju jedan dio našeg UI, * ne vraćaju ništa, zato što opisuju željeno stanje našeg ekrana umjesto da konstruišu UI widgete, * uzimaju neki input i generišu šta se treba prikazati na ekranu, te mogu prikazat više UI elemenata. Što znači da za kreiranje UI pišemo composable funkcije. One su samo obične funkcije sa anotacijom @Composable, koja govori Composu da doda posebnu podršku funkciju za updatovanje i održavanje našeg UI tokom vremena. Također, informiše Compose kompajler da funkcija namjerava data (podatke) pretvoriti u UI. Anotacija znači da dodajemo neke dodatne informacije kodu. Ta informacija pomaže alatima kao što Jetpack Compose compiler, i drugim developerima da razumiju kod. Composable funkcije se ne izvršavaju redoslijedom ako ih ima više. Odnosno, ne mora značiti da će se izvršiti onim redoslijedom kojim smo ih mi napisali. Npr, ako imamo tri funkcije naziva StartScreen, MiddleScreen i EndScreen i sve to u funkciji MyHomeScreen, ne znači da će se izvršiti respektivno kako su navedene. Može se desiti prvo da se izvrši MiddleScreen pa ostale. Možemo izvršavati više composable funkcija paralelno. # UI HIJERARHIJA UI Hijerarhija je bazirana na tome da jedna komponenta može sadržavati jednu ili više drugih komponenati, pa tako ponekad koristimo izraze roditelj i dijete. Što znači da roditeljski UI element može imati jedno ili više djece odnosno UI elemenata. Tri glavna standardna layout elementa su Column, Row i Box, koji se mogu ponašati kao roditeljski UI elementi. Column će sve svoje elemente poravnati odnosno poredati vertikalno, dok će Row sve svoje elmente poravnati odnosno poredati horizontalno. # LAYOUT MODIFAJERI Modifajeri se koriste da uredimo i dodamo neke osobine kako će se Jetpack Compose UI elemet ponašati. Naprimjer pomoću njih možemo dodati pozadinu, padding, ili neke akcije na redove, tekst ili dugmiće. Elementi mogu postati klikabilni, skrolabilni, draggable i zoomable. Modifajeri su standardni Kotlin objekti. Da bi uopšte mogli postaviti i koristiti Modifajere, ta komponenta ili layout moraju prihvatiti modifajer kao svoj parametar. Redoslijed modifajera je značajan iz razloga što svaka funkcija pravi promjene u odnosu na to šta su primile od prethodne funkcije i njenog Modifajera. # LAZY COLUMN Sa dolaskom Jetpack Composa, došla je i lazy column, koja je deklarativni UI alat. LazyColumn je komponenta vertikalne liste koju možemo skrolati. Napravljena je tako da efikasno rukuje velikim listama (listama koje imaju mnogo stavki/item-a) tako što renda/iscrtava samo vidljive stavke/item-e na ekranu, što pomaže u optimizaciji memorije i performansi. Koristeći LazyColumn, možetemo efikasno prikazati duge liste podataka bez potrebe da učitavate i prikazujete sve stavke odjednom. # RECYCLER VIEW To je view u kojem se prikazuju elementi određene liste. Da bi koristili Recycler View potreban je i LayoutManager koji upravlja tim elementima. Recycler View prikazuje samo onoliko elemenata liste koliko se može prikazati u jednom momentu na ekranu, a mi kada skrolamo po ekranu, onaj view koji izlazi iz vidnog polja se uništi a kreira se novi view (list item). Layout Manager definira kako ćemo predstaviti listu, hoće li to biti horizontalno, vertikalno, ili kao grid. Da bi ovo sve radilo potreban nam je Adapter koji služi da bi radili sa listama. Adapter je klasa koja upravlja sa elementima liste. # DATABINDING Koristimo ga da bi povezali komponente iz layouta. Kako bi bi izbjegli stalno korištenje findViewById, imamo opciju da uključimo “binding” u gradle fajlu – buildFeatures { databinding true }. Ono što još moramo uraditi da bi mogli koristiti “binding” jeste da cijeli layout ubacimo u tag “<layout>”. Nakon toga u “Mainu” napravimo objekat koji će nam predstavljati binding. Npr. “private lateinit var binding: ActivityMainBinding” – korisitmo camel case notaciju i komponentama iz layouta pristupamo na način “binding.idKomponente”. # CONSTRAINT LAYOUT Omogućava nam stvaranje velikih i složenih izgleda s ravnom hijerarhijom pogleda (bez ugniježđenih grupa pogleda). Sličan je RelativeLayoutu po tome što su svi pogledi raspoređeni prema odnosima između srodnih pogleda i roditeljskog izgleda, ali je fleksibilniji od RelativeLayouta i lakši za korištenje s Android Studioovim uređivačom izgleda. # VIEWBINDING **ViewBinding (Povezivanje View-ova)** je feature koja omogućuje lakše pisanje koda koji je u interakciji s pogledima (view-ovima). Jednom kada je povezivanje view-a omogućeno u modulu, on generira klasu vezanja za svaku XML datoteku izgleda koja je prisutna u tom modulu. Instanca klase povezivanja sadrži direktne reference na sve poglede koji imaju ID u odgovarajućem izgledu. U većini slučajeva, vezanje pogleda zamjenjuje findViewById. findViewById izvor je mnogih bugova s kojima se suočavaju korisnici u Androidu. Lako je proslijediti id koji nije u trenutnom izgledu - stvarajući null i rušenje. A budući da nema ugrađenu sigurnost tipa, lako je poslati kod koji poziva findViewById<TextView>(R.id.image). View binding zamjenjuje findViewById sažetom, sigurnom alternativom. Prednosti View Bindinga nad findViewById izvorom su: Sigurno za tip jer su svojstva uvijek ispravno upisana na temelju pogleda u izgledu. Dakle, ako stavite TextView u izgled, vezanje pogleda će izložiti svojstvo TextView. Null-safe za izglede definirane u više konfiguracija. Povezivanje pogleda će otkriti je li pogled prisutan samo u nekim konfiguracijama i stvoriti svojstvo @Nullable. A budući da su generirane klase vezanja redovite Java klase s Kotlin-prijateljskim bilješkama, možemo koristiti povezivanje pogleda iz programskog jezika Java i Kotlina. # JETPACK COMPOSE PODRŽAVA MVVM MVVM je skraćenica za **Model-View-ViewModel**. To je softverski patern koji se obično koristi u razvoju user interface (UI), posebno u kontekstu grafičkih user interface (GUI) i web aplikacija. ![](https://hackmd.io/_uploads/S1BJsEUPh.png) U MVVM paternu, aplikacija je podijeljena u tri glavne komponente: * **Model** ~ Model predstavlja podatke i poslovnu logiku aplikacije. On enkapsulira podatke i operacije/akcije koje se izvode nad podacima. Drugim riječima, model definira strukturu i ponašanje podataka koji se koriste u aplikaciji. * **View** ~ View je odgovoran za predstavljanje user interface-a korisniku. Definiše vizualne elemente i izgled user interface-a. View je određen u ViewModelu, a View uzima od click, submit i slične radnje od korisnika i šalje ih ka ViewModelu. * **ViewModel** ~ ViewModel djeluje kao posrednik između view-a i modela. Sadrži logiku prezentacije i izlaže podatke iz modela na način koji je lako iskoristiti u pogledu. ViewModel također upravlja interakcijama korisnika i u zavisnoti od tih interkacija ažurira model. Pruža mehanizme povezivanja podataka kako bi view/pogled i model bili sinhronizirani. Jedna od glavnih prednosti MVVM-a je njegova mogućnost da povezuje podatke, što omogućava da se promjene u ViewModelu automatski reflektuju u view-u, i obrnuto, bez eksplicitnih poziva ažuriranja. Ovo pojednostavljuje razvoj user interface-a i smanjuje količinu standardnog koda (boiler plate code-a) potrebnog za sinhronizaciju. Imamo tri sloja: 1. **UI Layer**, 2. **Domain Layer**(on je opcionalan), 1. **Data layer** ## UI LAYER Uloga UI Layera je da prikaže podatke aplikacije na ekranu. Kada god se podaci promjene, prilikom korisnikove interakcije sa aplikacijom, kao što je naprimjer klik na dugme UI treba da update-a te promjene. UI je ustvari vizuelna reprezentacija stanja aplikacije koju dobije od data layera/sloja. UI layer/sloj se sastoji od: * **UI elemenata** to su komponente koje rendaju/iscrtavaju podatke na ekran, a mi pravimo ove komponente koristeći Jetpack compose. * **State holders** to je komponenta koja “drži” podatke i prikazuje ih na UI te se bavi logikom aplikacije to je naprimjer ViewModel. * **ViewModel** pohranjuje podatke u vezi aplikacije koje se ne uništavaju kada se aktivnost uništi i ponovo kreira od strane Android frameworka. ViewModel objekti se ne uništavaju. Aplikacija automatski zadržava ViewModel objekte tokom promjena konfiguracije, tako da su podaci koje oni drže dostupni odmah nakon rekompozicije. ## UI ELEMENTI ZAJEDNO SA UI STATEOM ČINE UI ![](https://hackmd.io/_uploads/S1PlCV8P3.png) UI je ono što korisnik vidi, a UI State je ono što aplikacija kaže da trebaju vidjeti UI je ustvari vizuelni prikaz UI Statea. Bilo koja promjena UI statea odmah se reflektuje na UI. UI State je immutable, a bilo koje kršenje ovog pravila dovodi do toga da imamo više izvora podataka (odnosno truth) za jednu te istu informaciju i to uzorkuje nedosljednost podataka i grešaka. ![](https://hackmd.io/_uploads/HkVtwBIPn.png) ![](https://hackmd.io/_uploads/S1WiwH8D2.png) StateFlow je data holder(“drži podatke”) obervable flow koji prikazuje trenutne i nove update. Vrijednost njegovog propertija se reflektuje na vrijednost trenutnog stanja, da bi updateovali stanje i poslali ga u flow koristimo klasu Mutablestate flow. UDF undirectional data flow: state flow down, event flow up # LIFECYCLE AKTIVNOSTI Lifecycle aktivnost je skup stanja kroz koje prolazi aktivnost. Životni ciklus aktivnost počinje kada Android OS prvi put kreira aktivnost, a završava kada OS uništi aktivnost. Kada user navigira između aktivnosti, unutar ili van naše aplikacije, svaka aktivnost se pomjera iz stanja u stanje lifecycle. Svako stanje lifecyle ima odgovarajuću callback metodu, koju možemo i override u Activity klasi. Osnovni skup lifecyle metoda su: * onCreate(), * onRestart(), * onStart(), * onResume(), * onPause(), * onStop(), * onDestroy(). Da bi dodali ponašanje koje će se desiti kada aktivnost pređe u drugo stanje, trebamo override callback metodu. ## OPIS METODA * **onCreate()** ~ Ova metoda se poziva kada se aktivnost prvi put kreira. Metoda koja se dešava samo jednom za čitav život aktivnosti. * **onRestart()** ~ Ova metoda se poziva kada se aktivnost ponovo pokreće nakon što je zaustavljena. Slijedi onStop() i poziva se prije onStart(). Omogućava nam da izvršimo sve potrebne radnje za ponovno pokretanje aktivnosti, kao što je osvježavanje podataka ili ponovno pokretanje komponenti. * **onStart()** ~ Ova metoda se poziva kada aktivnost postane vidljiva korisniku. Priprema aktivnost za ulazak u prvi plan. * **onResume()** ~ Ova metoda se poziva kada aktivnost započne interakciju s korisnikom. To je posljednja metoda koja se poziva prije nego što aktivnost postane aktivna u prvom planu. Koristi se za nastavak svih operacija/akcija ili resursa koji su pauzirani ili zaustavljeni u onPause(). * **onPause()** ~ Ova metoda se poziva kada aktivnost više nije u prvom planu i izgubi fokus. Obično se koristi za pauziranje ili oslobađanje resursa, spremanje korisničkih podataka ili obavljanje bilo kojeg drugog potrebnog čišćenja prije nego što aktivnost pređe u pozadinu. * **onStop()** ~ Ova metoda se poziva kada aktivnost više nije vidljiva korisniku. Obično se koristi za oslobađanje resursa koji nisu potrebni dok aktivnost nije vidljiva. * **onDestroy()** ~ Ova metoda se poziva kada se aktivnost uništava ili zatvara. To je konačna metoda koja se pozove u životnom ciklusu aktivnosti. Obično se koristi za oslobađanje svih resursa koji se tiču aktivnosti. Ove metode nam omogućavaju upravljanje životnim ciklusom i stanjem naše aktivnosti, omogućavaju nam da kontrolišemo njeno ponašanje u različitim fazama. Implementacijom ovih metoda možemo osigurati da se naša aktivnost ponaša ispravno i efikasno tokom svog životnog ciklusa. # LIFECYCLE OF COMPOSABLES U ovom slučaju imamo dva bitna pojma Initial/početna Composition i Recomposition.Kada Jetpack Compose run/pokrene naše komponente prvi put tokom inicijalne/početne kompozicije,i vodit će računa o komponentama koje smo pozvali da opišemo naš user interface. Kada se stanje naše aplikacije promjeni Jetpack Compose će odraditi rekompoziciju, rekompozicija je kada Jetpack Compose ponovo pokrene composable funkcije sa novim podacima i onda će se updateovati podaci i te promjene će se odraziti na UI. Bitno je napomenuti da je Jetpack Compose pametan i da recompose samo funkcije koje su se promijenile. Dakle, u rekompoziciji se neće ponovo pozivati funkcije koje se nisu promijenile. # NAVIGACIJA Navigaciona komponenta ima tri glavna dijela: 1. navController, 1. navHost, 1. navGraph **navController** je zadužen za navigaciju između destinacija odnosno ekrana u našoj aplikaciji, te da se brine o backstacku i state/stanju composable ekrana. ``` val navController = rememberNavController() ``` Ovo je način kako pozivamo/kreiramo navController, njega je potrebno kreirati na mjestu gdje mu mogu pristupiti sve composable funkcije koje ga trebaju referencirati kako bi imale pristup navControlleru. Sljedeći dio je **NavGraph**, **NavHost** on djeluje kao kontejener za prikaz trenutnog odredišta NavGrapha,njegova uloga je da definiše navigacioni graf, drugim riječima u navHostu ćemo definisati sve naše ekrane, rute, argumente i sve ostalo što je potrebno da bi navigacija radila kako treba. U NavHostu navodimo navController koji ćemo koristit i početnu destinaciju odnosno početni ekran. Jedan od osnovnih koncepata navigacije u Compose aplikaciji je ruta. Ruta odgovara odredištu. Ova ideja je slična konceptu URL-a. Baš kao što različiti URL-ovi mapiraju tj. odvode nas na drugu stranicu na web aplikaciji, i u ovom slučaju ruta nas odvodi na destianciju i služi kao jedinstveni indetifikator. Destinacija/odredište je obično jedan Composable ili grupa više Composable koji odogovara onom što korisnik vidi. U aplikaciji postoji konačan broj ekrana/screenova, tako da postoji i konačan broj ruta. Rute možemo i zapravo ih definiramo u enum klasi ili sealed klasi. Enum klase u Kotlinu imaju name property koji vraća string sa tim name propertijem. U Kotlinu,sealed klasa je posebna vrsta klase koja predstavlja ograničenu hijerarhiju klasa ili stanja. Koristi se za definiranje zatvorenog skupa potklasa koje su poznate u vrijeme kompajliranja. NavHost je composable koji prikazuje druge composable destinacije/odredišta, na osnovu date rute. navController je instanca NavHostController klase, onda možemo koristit ovaj objekat da navigiramo to jeste da se krećemo između ekrana, tako što pozovemo navigate() metodu i time navigiramo na drugu destinaciju/odredište. Možemo dobiti NavHostController tako što pozovemo rememberNavController() iz composable funkcije. startDestination to je string ruta koja definira destinaciju koja se prikazuje po defaultu kada aplikacija prvi put prikaže NavHost. Unutar sadržaja funkcije NavHost-a, pozivamo composable() funkciju. Composable funkcija prima dva parametra. routa - string koji je korespondan sa imenom rute, ovo može biti bilo koji jedinstveni string. Koristit ćemo one name propertije iz enum klase. content- ovdje mmožemo pozvati composable koje želimo prikazati na datoj ruti. composable() funkciju pozivamo za svaku rutu. remeberNavController() je odgovoran za navigiranje između ruta. ## SAFEARGS Služi za prosljeđivanje podataka između fragmenata. Navigacija omogućava da priložimo podatke operaciji navigacije definiranjem argumenata za odredište. Na primjer, odredište korisničkog profila može uzeti argument korisničkog ID-a da odredi kojeg će korisnika prikazati. Općenito, treba se snažno preferirati prosljeđivanje samo minimalne količine podataka između fragmenata. Na primjer, trebali biste proslijediti ključ za dohvaćanje objekta umjesto samog objekta, jer je ukupan prostor za sva sačuvana stanja ograničen na Androidu. Ako trebate proslijediti velike količine podataka, koristimo ViewModel. # MODEL I BAZA PODATAKA Model predstavlja komponentu koja je odgovorna za podatke koji se koriste untar aplikacije. Ovo je 'data access layer' koji je skriven od korisnika. On sadrži enitete koji upravljaju bazom podataka, mrežne zahtjeve i pretvaraju podatke iz jednog u drugi tip kako bi se mogli prikazati krajnjem korisniku. Za lokalnu bazu podataka koristimo Room biblioteku koja koristi SQLite jezik. Postoje tri komponente u ROOM-u: * database klasa koja čuva bazu podataka i služi kao glavna pristupna tačka sa trajnim podacima iz aplikacije, * data entiteti koji predstavljaju tabele u bazi podataka, * DAO(Data Access Objects) koji daju metode tako da aplikacija može da koristi query, operacije kao što su update, insert i delete sa podacima iz baze podataka. ![](https://hackmd.io/_uploads/HJmluVoFn.jpg) Database klasa mora da prati sljedeće uslove: * Klasa mora biti označena sa @Database anotacijom koja uključuje entities niz koji je lista svih data entities u bazi podataka, * Ova klasa mora biti apstraktna klasa koja nasljeđuje RoomDatabase * Za svaki DAO koji je povezan sa bazom podataka, baza podataka mora da definiše apstraktnu metodu koja ima 0 argumenata i vraća instancu DAO klase # REPOSITORY Repozitorij se koristi za skrivanje više izvora podataka koje možemo imati u našoj aplikaciji, podaci u aplikaciji mogu doću iz lokalne baze podataka ili sa eksternog servisa kao što je WEB API, kao i keširani podaci. Repozitorijum izolira data sloj od ostatka aplikacije. Repozitorijum rješava konflikte koji se dešavaju kada imamo više izvora podataka i centralizira promjene na ovim podacima. # CACHE Keš se odnosi na prostor koji koriste podaci u našoj aplikaciji. Naprimjer, ako želite da privremeno spasite podatke sa mreže, u slučaju da internet konekcija bude ugrožena, koristimo keš. Također, čak i kada nam mreža nije dostupna aplikacija se može osloniti na keširane podatke. Može biti korisna za čuvanje privremenih podataka za aktivnosti koje nisu više na ekranu ili čak i trajne podaci in between app launches. Keš može biti u više formi, jednostavan ili kompleksan, a to sve ovisi od specifičnog zadatka. Jedna od tehnika je Retrofit. Retrofit je network biblioteka koja implementira type-safe REST klijenta za Android. Retrofit dohvaća podatke sa WEB APija i čuva kopiju svakog mrežnog rezultata u lokalnoj pohrani. # COROUTINES Do sada smo se uglavnom susretali sa single path izvršavanjem, odnosno da se izvršava jedan po jedan task. Međutim, dolazimo do problema kada je potrebno izvršavanje više taskova paralelno. I da se ti taskovi izvršavaju bez blokiranja i bez rušenja aplikacije. Tako prvo dolazimo do Threads ali kreiranje i switching i managing thread troši resurse sistema i ograničava nam broj threadsa koji se mogu pratiti/izvršavat istovremeno. Kada pokrećemo aplikaciju imamo nekoliko threads, svaka aplikacija ima jedan glavni thread koji se zove main thread ili UI thread. U nekim slučajevima ta dva threada ne moraju predstavljati isto. Ovaj thread je odgovoran za pokretanje našeg UI-a i zato je važno da ovaj glavni thread ima dobre preformanse kako bi se aplikacija dobro pokretala i ne bi zaustavljala. Coroutines dozvoljavaju multitasking ali omogućavaju drugačiji level apstrakcije u odnosu na rad sa threadsima. Jedan ključni feature coroutines je mogućnost da čuva state, tako ta mogu biti zaustavljene i ponovo pokrenute. Coroutine may or may not izvršiti se. **State** koje je predstavljeno continuationsima, uvijek dozvoljava dijelovima koda da signaliziraju kada trebaju predati kontrolu ili kada trebaju čekati na drugu coroutines da završe posao prije nego što se nastave. Ovaj tok se naziva kooperativni multitasking. Kotlinova implementacija coroutina dodaje brojne karakteristike koje pomažu pri obavljanju multitaskinga. U smislu continuations, kreiranje coroutine obuhvata taj rad u Job, jedinici koja može otkazati rad sa lifecycle untar CoroutineScope-a. **CoroutineScope** je kontekst koji rekurzivno nameće otkazivanje i ostala pravila djeci i njihovoj djeci. **Dispatcher** upravlja koju će pozadinsku nit coroutine koristit za izvršavanje, uklanjajući odgovornost o tome kada i gdje koristiti novu nit sa programera. **launch()** kreira coroutines i koristi se kada povratna vrijednost nije neophodna izvan confines coroutine. **suspend **je ključna riječ koja signalizira da kod u bloku može biti pauziran ili ponovo pokrenut. # OPIS RADA APLIKACIJE SA SLIKAMA Aplikacija je napravljena u Android Studiu koristeći Fragmente, Retrofit biblioteku, Room bazu podataka, Coroutines i uz pomoć navigacione komponente. Za početak, upoznajmo se sa navigacionim grafom. ![](https://hackmd.io/_uploads/r1RHRZiFn.jpg) Iz ovoga možemo zaključiti da aplikacija ima 6 fragmenata. Redom to su `fragment_home, apiCall, fragment_selectedmovielist,fragment_details, fragment_watchlistcall i fragment_watchlist.` Kao što se može primjetit postoji nekoliko putanja, a o njima ću detaljnije kako budem opisivala svaki fragment. # HOME FRAGMENT Home fragment, odnosno njegov UI se sastoji od Top app bar, pozadinske slike i dva buttona, a to su **Browse i My watchlist.** Browse nas vodi do fragmenta koji vrši API poziv i preuzima podatke sa API-ja. Kada kliknemo na My watchlist dolazimo do podataka koji su spašeni u bazi podataka, odnosno koje je korisnik sam dodao u bazu podataka. Na oba dugmića je postavljen OnClickListener koji nas treba odvesti na odgovarajuću destinaciju. Na početku sam definisala i inicijalizirala varijablu **hasInternetAccess**, to je varijabla koja nam govori da li aplikacija ima pristup internetu i ako ima, njena vrijednost se postavlja na true, a inače je false. Za provjeru sam koristila funkciju istoimenog naziva koja koristi context(koji pruža pristup resursima na nivou aplikacije) i conectivityManager koji je odgovoran za upravljanjem mrežom. Metoda getNetworkCapabilities() nam vraća podatke o trenutno aktivnoj mreži. U ovoj funkciji također saznajemo i kakvu vezu aplikacija sa internetom ima, da li je to preko WiFi, Etherneta ili preko mobilnih podataka. Napomena, da bi aplikacija uopšte imala bilo kakav pristup internetu potrebno je u AndroidManifestu dati dopuštenje da pristupa internetu. Nakon što provjeri da li ima pristup internetu ili ne imamo jednu if-else strukturu koja nas vodi na ApiCallFragment (ako ima internet) ili na WatchlistCallFragment (ako nema internet). Kod watchlist nema potrebe provjeravati da li ima internet ili ne, direktno nas vodi na WatchlistCallFragment. Što se samog izgleda tiče, sastoji se od ConstraintLayout, ImageView i LinearLayouta koji u sebi sadrži dva Button. <br> ![](https://hackmd.io/_uploads/ryeiHkzsY2.jpg) # API CALL ApiCall je fragment koji se sastoji od jednog Search polja (to je EditText koji je postavljen da u njega se može samo tekst unijeti),RadioGroup koji se sastoji od tri RadioButtona i na kraju jedan Button. Ponovo imamo ConstraintLayout, ImageView (predstavlja pozadinu) i LinearLayout sa ostalim navedenim elementima. Ovo je fragment koji nam služi da pristupimo podacima sa Web Api-ja. Korisnik treba prvo da unese pojam koji želi da pretražuje, to hvata varijabla **searchQuery**. Da bi smo osigurali da korisnik unese pojam za pretragu provjeravamo da li je searchQuery prazan string, a ako nije izaći će mu poruka da unese pojam. Za to koristimo objekat tipa Toast i izabrali smo da se poruka prikazuje duže vremena. Nakon toga korisnik bira za koju kategoriju želi da pretražuje, a nude mu se filmovi, serije ili da vidi sve ponuđeno iz API-ja koje sadrži taj pojam. Kada izabere, klikće Search i navigira na sljedeći fragment - **SelectedMovieListFragment**, a njemu šalje searchQuery i contentType (izabrani Radio Button) i true, kao znak da ima pristup internetu. Napomena, izabrala sam ovo kao filtere za pretragu jer je API tako definisan. ![](https://hackmd.io/_uploads/BkKH8foFn.jpg) # FILMAPI I RETROFITINSTANCE Što se tiče FilmAPI i Retrofitinstance, to je biblioteka koju koristimo za pristup podacima. Stavila sam ih u folder api i on se sastoji od FilmAPI i RetrofitInstance. FilmAPI je interfejs koji opisuje metode za komunikaciju sa API-jem. Koristi HTTP GET metodu. Prva metoda daje listu filmova koja ima parametre koje je OMDb API definisao. **apiKey** - to je api ključ koji je potreban za autentifikaciju pri korištenju API-ja (svaki korisnik ima svoj API). Prva metoda vraća Response<FilmResponse>, Response sadrži informacije o odgovoru API-ja, a FilmResponse je model koji odgovara tome. Iduća metoda je dajFilm koja ustvari uzima samo jedan podatak sa API-ja i to preko imbdID. Metoda vraća Response<FilmVise>, a FilmVise je model koji sadrži detaljnije informacije o filmu. Obje metode koriste oznaku suspend kako bi se mogle izvršavati asinhrono koristeći korutine. Retrofit objekat koristi za stvaranje instanci Retrofit klijenta za komunikaciju sa API-jem. Stvara se retrofit, a by lazy znači da će se istancirati samo jednom. Zatim kofiguriramo Retrofit klijent, dajemo URL WEB API-ja. Nakon toga JSON podatke pretvaramo u odgovarajuće objekte modela koristeći GSON biblioteku. Nakon toga .build() stvara Retrofit instancu. U nastavku kreiramo FilmAPI kako bi mogao izvršavati API pozive i tu smo se osigurali da se kreira samo jednom. # SELECTEDMOVIELISTFRAGMENT Ovaj fragment prikazuje u vidu liste dobivene podatke kroz API. Jedan element liste se sastoji od naziva filma/serije, godine kada je izašao i strelice koja nas vodi na detalje o izabranom elementu. Za prikaz smo koristili Recycler View. Ovo je momenat kada na scenu stupa MVVM arhitektura. Deklarisali i inicijalizirali varijable viewModel, adapter, apiCallMovies (koja predstavlja listu filmova) i apiCallSucessfull (koju sam postavila na true). Potreban nam je adapter jer prikazujemo listu u Recycler View-u, a adapter upravlja elementima liste. O adapteru više u sljedećem segmentu. Tu je postavljen i layoutManager kako bi smo mogli raditi sa listama. Varijabla **args** uzima argumente koji su koj proslijeđeni iz prethodnog fragmenta. Pozvali smo i repository, kako bi se mogle koristit i metode koje se nalaze u njemu, kao i ViewModelFactory. U if-else strukturi imamo: * Ako args.hasInternetAccess pristupamo metodi iz viewModel, a ona je dajFilmove(args.searchQuery,args.type). Ovo je metoda koja vrši API poziv i dohvaća podatke sa API-ja. Ako je odgovor API-ja (odnosno ako postoji taj rezultat pretrage), proslijeđujemo ga adapteru kako bi adapter mogao da prikaže podatke, a ako ne postoji ispisuje nam se poruka "Empty list", odnosno da ne postoje podaci i vrši se navigacija nazad. * Ako je pristup API-ju neuspješan, ispisujemo log u konzolu. Također, registrira se observer na viewModel.watchLista (LiveData objekt) kako bi se pratila promjena podataka u lokalnoj bazi i ažurirao adapter s tim podacima. U funkciji onStop(), provjeravamo da li je adapter online i da li je poziv bio uspješan, ako jeste, dodajemo ga u bazu podataka kako bi mogli da mu pristupimo i kada bude offline. ![](https://hackmd.io/_uploads/Bk1TZmjKn.jpg) <br> # CINESCOPEADAPTER **CinescopeAdapter** prima dva parametra, odnosno da li je lista filmova dobivena iz baze podataka ili putem API-ja, a drugi parametar nam govori da li uređaj ima pristup internetu. Na početku imamo praznu listu filmova i to tipa Film. Imamo klasu **MyViewHolder** koja nasljeđuje RecyclerView.viewHolder. Ova klasa nam čuva podatke koji se nalaze u jednom elementu liste u RecyclerView (odnosno podatke koje sadrži jedan list item) Metoda **onCreateViewHolder** vraća **MyViewHolder** koji prima view, a to je ustvari jedan te isti element liste. Metoda onBindViewHolder tu postavljamo vrijednosti na elemente iz listaFilmova, dakle za svaki element pojedinačno. Pronalazimo i dva elementa u jednom elementu liste i postavljamo ih na zadate vrijednosti. Stavili smo i setOnClickListener. Tu provjeravamo da li je povezano na internet ili nije, ako nije povezano ponovo nam izlazi poruka da nema interneta i zaustavlja se izvršavanje. Ako podatak dolazi iz baze podataka, onda navigiramo iz watchListe na DetailsFragment, a ako nije onda iz SelectedMOvieListFragmenta na DetailsFragment. Funkcija **getItemCount(**) vraća koliko elemenenata ima u listaFilmova. Funkcija **setData(film: List<Film>)** postavlja novu listu filmova i obavještava adapter da je došlo do promjene podataka. Koristimo ga za ažuriranje liste filmova pri promjeni podataka izvan adaptera. # DETALISFRAGMENT Do DetailsFragmenta možemo stići na dva načina: iz WatchlistFragment i iz SelectedMovieListFragment, dakle kada iz elementa liste izaberemo jedan element o kojem želimo znati više. Stoga DetailsFragment prima dva argumenta, a to je imdbID koji nam daje ID filma i fromWatchList, koji je Boolean tipa i koji nam upravo govori da li smo stigli preko API-ja ili preko lokalne baze podataka. Imamo if-else strukturu. If nam provjerava ako je iz lokalne baze da dugme removeFromWatchList stavimo na vidljivo i addToWatch dugme sakrijemo. Nakon toga pozivamo iz viewModela funkciju koja vraća film iz watchliste i postavlja podatke. Else dio dugme removeFromWatchList sakriva i stavlja addToWatchList na vidljivo jer smo došli preko API poziva. Pozivamo funkciju dajFilm() koja se poziva preko API. Registrira se observer na viewModel.film ili viewModel.viseOFilmu (LiveData objekti) kako bi se pratili odgovori API poziva ili podaci iz lokalne baze o detaljima filma. U ovisnosti o uspješnosti odgovora, poziva se metoda setData(binding, it) za postavljanje podataka u view. Definiše ponašanja dugmadi, omogućeno je i slanje predefinisane poruke drugom korisniku. setData na kraju postavlja podatke o odgovarajućem filmu u view, a podaci se prenose iz modela FilmVise. Također, klikom za navigiranje nazad, odlazimo na HomeFragment, odnosno vraćamo se na početak, kako se ne bi vraćali kroz listu, pa search, pa ponovo na početnu stranicu. ![](https://hackmd.io/_uploads/rJsiCmsKn.jpg) # WATCHLISTCALLFRAGMENT Ovo je fragment koji služi za filtiranje podataka koje smo sačuvali u bazi podataka, dakle watchlista. Sastoji se od tri dijela. Prvi dio po čemu ćemo filtirati podatke - nudi nam se opcija po žarnu i po godinama. Možemo izabrati konkretne vrijednosti, a možemo izabrati i bilo koju vrijednost (dakle sve). Drugi dio je kako ćemo te dobivene podatke sortirati, da li po defaultu, po žarnu ili po godinama. Treći dio je dugme koje izabrane opcije šalje narednom fragmentu. Dakle, uzimamo koje žarnove imamo iz resursa i stvara se ArrayAdapter za prikazivanje žarnova u spinnerGenre. Tu koristimo zaseban layout za prikazivanje stavki u spinneru, postavljamo ga kao dropdown listu. Nakon toga se stvara lista godina koja uključuje sve godine od 1900 pa do danas. Stvara se ArrayAdapter za prikazivanje godina u spinnerYear i koristimo isti layout kao i za žarnove. Dodjeljuju se adapteri spinnerima spinnerGenres i spinnerYears kako bi se prikazali žanrovi i godine u odgovarajućim spinnerima. Klikom na dugme filterButton (koji ima onClickListener), uzimamo koji je to žarn izabran i koja je godina izabrana i kako ćemo ih sortirati. Nakon toga nastavljamo ka WatchListFragmentu i njemu proslijeđujemo ove podatke kako bi znao da nam prikaže odgovarajuću listu. Njega neću posebno opisivati jer je izveden isto kao i lista za prikaz liste dobivenih filmova preko API-ja. O njemu vrijedi reći da koristi funkciju filtirajFilmove() iz CinescopeRepository kako bi izbacio filtiranu listu. Također, koristi Parse kako bi dobivene podatke pretvorio iz FilmVise u listu tipa Film. WatchlistFragment je fragment koji ima portrait i landscape orijentaciju. Možemo kliknut na element liste i doći do detalja o izabranom filmu. ![](https://hackmd.io/_uploads/HJZbXEoFh.jpg) ![](https://hackmd.io/_uploads/HJaW7VsK3.jpg)