LiveData & MVVM

LiveData klase

LiveData

LiveData je apstraktna klasa tzv. observable data holder zadužen da čuva informacije o podacima i da obavesti sve zainteresovane posmatrače ako dodje do promena.

LiveData is actually just an Abstract Class. So it can’t be used as itself.

mediator_live_data_diagram

Najvažnija karakteristika LiveData je ta što je svestan životnog ciklusa drugih komponenti aplikacije (aktivnosti, fragmenti…). Upravo zbog te karakteristike LiveData prosledjuje podatke samo observers (posmatračima) koji su u aktivnom stanju (ako je životni ciklus u STARTED ili RESUMED stanju), dok neaktivni (destroyed) posmatrači iako registrovani nisu obavešteni vrednostima koje čuva LiveData. Kada koristimo LiveData ne treba brinemo kada se završava (destroy) životni ciklus aktivnosti/fragmenta jer se automatski odjavjljuju čim komponenta završi svoj životni ciklus.

MutableLiveData

Kod LiveData klase su setter metode privatne i ona se ne može se koristi ako je potrebno negde promeniti podatke tj. setovati ih. Iz tog razloga postoji njena podklasa MutableLiveData kod koje je setter metoda public setValue() (za background thread se koristi postValue() metoda).

MediatorLiveData klasa

LiveData subclass which may observe other LiveData objects and react on OnChanged events from them.

Ovo je još specifičnija metoda i ima dodatnu mogućnost da može pratiti rezultate iz različitih LiveData i spojiti ih u jedan MediatorLiveData.
Ako pretpostavim da imamo dva LiveData objekta koji emituju neku vrednost ali iz dva različita izvora (npr. vrednost nekog dobra sa dve različite berze) tada mi želimo da osluškujemo promene iz oba izvora.

mediator_live_data_diagram

  • addSource(LiveData source, Observer onChanged)
    Metoda omogućuje da se izabere koji LiveData objekat se osluškuje, i šta će da radi callback metoda onChanged kada dodje do promene.
    MediatorLiveData ima dva parametra, prvo je LiveData koju želite da primeti MediatorLiveData, a drugi je povratni poziv koji će se pokrenuti kada se promene podaci u LiveData (prosleđeni u prvom parametru)

  • removeSource(LiveData toRemove)
    Metoda omogućava da se prestane sa slušanjem promena LiveData. Kao u sledećem primeru kada se nakon 10 promena podatka više ne prate promene:

Observe u View-u

Deo koda u okviru View-a je isti kao kod LiveData, pa je posmatranje MediatorLiveData može da izgleda ovako:

View – ViewModel

U ovome primeru je prikazan postupak neophodan za komunikacija izmedju View-a (aktivnost) i njegovog ViewModel-a:

  1. Implementacija biblioteke u projekat



    Takodje u okviru gradle staviti da google() bude u sekciji repository:

  2. MutableLiveData u ViewModel klasi

    • Kreiranje ViewModel klase

      ekstendujući ViewModel (kada nam nije potreban Context) ili AndroidViewModel klase (kada nam je potreban Context):

      Ovu klasu koristimo ako nam nije potreban Context:

      AndroidVievModel se koristi ako je potreban context. Za dobijanje context-a u okviru klase se korist metoda getApplication () ili se kroz konstruktor klase prosledi Aplication.

    • Kreiranje MutableLiveData objekta

    • Kreiranje getter-a za MutableLiveData objekat

      Iako je sam objekat “mHasSearchResults” ima promenjive vrednosti pa je predstavljen kao MutableLiveData, mi sa getter-om uvek vraćamo taj isti objekat te se iz tog razloga ovde koristi LiveData objekat a ne MutableLiveData:

    • Emitovanje promena

      Sledeća stvar koju je potrebno uraditi u okviru ViewModel klase je emitovanje promena svim prijavljenim posmatračima. To se izvršava korišćenjem MutableLiveData metoda: setValue() (sa glavnog thread-a) ili postValue() (sa background thread-a):

      Pozivanje metode setValue(T) i prethodnom primeru rezultuje da se kod posmatrača (obično neki View) pozivaju metode onChanged() sa vrednostima koja je poslatim kroz parametar setValue() metode (u ovom slučaju “true”).

      NAPOMENA:
      setValue() se ne može pozivati sa background thread-a, u tom slučaju je potrebno koristi postValue()

  3. Praćenje promena MutableLiveData u View klasi

    • Referenciranje na ViewModel u View-u:

    • Kreiranje reference na LiveData objekat

      Sada kada imamo referencu na ViewModel možemo da pristupimo i njegovim metodama, što ćemo da iskoristimo i da pozovemo getter metodu sa kojom ćemo dobiti referencu i na LiveData objekat:

    • Observe (osluškivanje promena)

      Kada dobijemo referencu na LiveData objekat možemo da pozovemo njegovu metodu observe(). Ova metoda prihvata dva parametra:

      • “LifecycleOwner” – definišemo na koji View treba LiveData da pazi kada je u pitanju LifeCycle
      • “Observer” – kroz ovaj parametar kreirajući novi anonimni posmatrač (Observer) praktično definišemo šta će posmatrač da uradi nakon promene:

Repository – ViewModel – View

mvvm diagram

U MVVM arhitekturi svaki nivo ima direktni pristup samo jednom nivou ispod, dok ga ne interesuju oni iznad njega. Ovakva arhitektura nam omogućava da promenimo layer iznad (npr.View) bez ikakvog menjanja nivoa ispod tj. izvora informacija (npr.ViewModel).

Cela komunikacija izmedju ovih arhitektonskih layer-a ustvari je jedan zatvoreni krug. Početak komunijacije je najčešće sama interakcija korisnika sa interfejsom (View), koji prosledjuje informacije “na dole” sve do baze podataka (kroz setValue()). Promena u bazi je okidač za emitovanje promena u Repository klasi. Te promene osluškuje ViewModel, pa ih zatim koristi da bi emitovao svoje informacije View-u, koji ih zatim koristi za ažuriranje sadržaja. Ovaj slučaj je praktično produženi slučaj komunikacije izmedju View-a i ViewModel-a iz prethodne sekcije.

Repository

Repositor je klasa koja omogućava pristup podacima ostalim delovima programa, a ona jedino ima pristup različitim izvorima informacija (web ili kao u ovome slučaju baza podataka). Za dobijanje informoacija iz baze podataka repository pristupa klasi zaduženoj za direktan pristup bazi (u ovome primeru je “AppDBHelper” klasa)

Ne možemo pozivati ovu handler-ovu metodu i iz drugih delova koda jer ostatak programa ima pristup samo repository-jy, onda je potrebno napraviti metodu koja apstrahuje handler:

Kao što smo do sada videli u radu sa LiveData, da bi se emitovala promena neke promenjive potrebne su tri stvari:

  1. Kreiranje MutableLiveData objekata (ovde sve započinje pa se kreira novi objekat)
  2. Getter tog MutableLiveData objekata
  3. Setovanje nove vrednosti promenjive sa setValue() metodom (u ovome primeru je ta nova vrednost lista koju smo dobili direktno iz Db-a.
    Drugu i treću stavku ćemo definisati zajedno u okviru jedne metode:

ViewModel

U ViewModel klasi je takodje prva stavka kreiranje MutableLiveData objekata. Medjutim ovde postoji mala razlika u odnosu na prethodni primer (komunikacija samo izmedju View-a iViewModel-a), jer se ne kreira novi MutableLiveData objekat, već taj objekat ViewModel dobija iz Repository-ja preko getter-a:

Druga stavka je kreiranje gettera za taj LiveData objekat (koji će iskoristi View):

A treća stavka je emitovanje promena (setValue() ili postValue()). Obično se ove promene dešavaju kada se pozove neka od Repository metoda za insert, update ili remove data iz baze.

View

View je zadužen da pokrene ceo lanac dogadja tako što prihvata interakciju krajnjeg korisnika (koji može da izvrši neku promenu koja će da utiče na sadržaj baze podataka). Zatim se taj korisnički zahtev prosledjuje dalje “na dole” (View -> VieModel -> Repository -> DB). Prihvatanje

U okviru ove metode se poziva metoda koja će na kraju da utiče na promenu baze podataka:

Ili

Kada dodje do promena u bazi podataka View-u su potrebne te nove informacije. Iz tog razloga View prati i osluškuje promene LiveDate objekta (čiji počeci sežu do repa) i reaguje na njih ažuriranjem sadržaja:

Ceo kod projekta u ovim primerima možete videti na GitHub-u u okviru projekta “SQLdatabaseWithoutLibrary”.

Fragmenat – Fragment

Komunikacija se odvija preko zajedničkog ViewModela, tako što svi View-i (fragmenti i aktivnost) mogu da mu pristupe, i “osluškući” promene koje emituje ViewModel.

fragment and viewmodel

Kada fragmentA želi da pošalje neku poruku FragmentuB, dovoljno je da ažurira LiveData u ViewModel-u, a ViewModel će da “emituje” te promene u etar, pa će i FragmentB koji osluškuje to biti obavešten o poruci i moći da reagujue odgovarajuće na nju. Ukoliko Aktivnost osluškuje promene kod istog tog LiveData objekta, i ona će biti istovremeno obaveštena pa će i ona moći da adekvatno reaguje.

Prednosti ove komunikacije su sledeće:

  1. Aktivnost ne mora ništa da zna i da radi u vezi konverzacije izmedju dva fragmenta (koristeći listener patern kod vezan za konverzaciju se nalazi u okviru Aktivnosti).
  2. Fragmenti ne moraju da znaju jedni o drugima, ako jedan od fragmenata nestane, drugi nastavi da radi kao i obično.
  3. Svaki fragment ima svoj životni ciklus i na njega ne utiče životni ciklus drugog. Ako jedan fragment zameni drugi, UI nastavlja da radi bez problema.
Primer




Pogledajte ceo primer projekta na GitHub-u u okviru repozitorijuma “FragmetComunication”.

LiveData klasa

LiveData je apstraktna klasa tzv. observable data holder zadužen da čuva informacije o podacima i da obavesti sve zainteresovane posmatrače ako dodje do promena.

LiveData is actually just an Abstract Class. So it can’t be used as itself.

mediator_live_data_diagram

Najvažnija karakteristika LiveData je ta što je svestan životnog ciklusa drugih komponenti aplikacije (aktivnosti, fragmenti…). Upravo zbog te karakteristike LiveData prosledjuje podatke samo observers (posmatračima) koji su u aktivnom stanju (ako je životni ciklus u STARTED ili RESUMED stanju), dok neaktivni (destroyed) posmatrači iako registrovani nisu obavešteni vrednostima koje čuva LiveData. Kada koristimo LiveData ne treba brinemo kada se završava (destroy) životni ciklus aktivnosti/fragmenta jer se automatski odjavjljuju čim komponenta završi svoj životni ciklus.

MutableLiveData klasa

Pošto je LiveData abstraktna klasa ona ne može da se koristi samomstalno iz tog razloga postoji njena podklasa MutableLiveData koja ima public metode setValue() i postValue() za definisanje vrednosti koje posmatrači prate:

  • setValue() se poziva kada ažuriramo vrednost iz MainThread-a
  • postValue() se poziva kada ažuriramo vrednosti iz nekog drugog thread-a.

Tako da svaki View koji prati promene neke vrednosti setovane kros metode setValue()/postValue() može da ažurira UI kada se jedan LiveData objekat promeni.

MediatorLiveData klasa

LiveData subclass which may observe other LiveData objects and react on OnChanged events from them.

Ovo je još specifičnija metoda i ima dodatnu mogućnost da može pratiti rezultate iz različitih LiveData i spojiti ih u jedan MediatorLiveData.
Ako pretpostavim da imamo dva LiveData objekta koji emituju neku vrednost ali iz dva različita izvora (npr. vrednost nekog dobra sa dve različite berze) tada mi želimo da osluškujemo promene iz oba izvora.

mediator_live_data_diagram

  • addSource(LiveData source, Observer onChanged)
    Metoda omogućuje da se izabere koji LiveData objekat se osluškuje, i šta će da radi callback metoda onChanged kada dodje do promene.
    MediatorLiveData ima dva parametra, prvo je LiveData koju želite da primeti MediatorLiveData, a drugi je povratni poziv koji će se pokrenuti kada se promene podaci u LiveData (prosleđeni u prvom parametru)

  • removeSource(LiveData toRemove)
    Metoda omogućava da se prestane sa slušanjem promena LiveData. Kao u sledećem primeru kada se nakon 10 promena podatka više ne prate promene:

Observe u View-u

Deo koda u okviru View-a je isti kao kod LiveData, pa je posmatranje MediatorLiveData može da izgleda ovako:

View – ViewModel

U ovome primeru je prikazan postupak u kome ViewModel emituje promene stanja neke promenjive, dok te stanje te promenjive osluškuje View (u ovome slučaju aktivnost) i zatim reaguje na tu promenu.

  1. Implementacija biblioteke u projekat



    Takodje u okviru gradle staviti da google() bude u sekciji repository:

  2. MutableLiveData u ViewModel klasi

    • Kreiranje ViewModel klase

      ekstendujući ViewModel (kada nam nije potreban Context) ili AndroidViewModel klase (kada nam je potreban Context):

      Ovu klasu koristimo ako nam nije potreban Context:

      AndroidVievModel se koristi ako je potreban context. Za dobijanje context-a u okviru klase se korist metoda getApplication () ili se kroz konstruktor klase prosledi Aplication.

    • Kreiranje MutableLiveData objekta

    • Kreiranje getter-a za MutableLiveData objekat

      Iako je sam objekat “mHasSearchResults” ima promenjive vrednosti pa je predstavljen kao MutableLiveData, mi sa getter-om uvek vraćamo taj isti objekat te se iz tog razloga ovde koristi LiveData objekat a ne MutableLiveData:

    • Promene vrednosti promenjive koje izaziva emitovanje vesti o tome

      Da bi se emitovala vest iz viewModel-a o promeni vrednosti promenjive potrebno je i napraviti tu promenu. To se izvršava korišćenjem MutableLiveData metoda:

      • setValue() (sa glavnog thread-a)
      • postValue() (sa background thread-a)
      Primer

      NAPOMENA:
      setValue() se ne može pozivati sa background thread-a, u tom slučaju je potrebno koristi postValue()

      Nakon izvršene promene vrednosti promenjive, ViewModel emituje “vest” svim prijavljenim posmatračima da je došlo do promena, što zatim rezultuje da se kod posmatrača izvršava metoda onChanged() sa novim vrednostima koje su prosledjene (kroz parametar setValue() metode, u ovom primeru “true”).

      NAPOMENA:
      Metoda LiveData objekta setValue() sa kojom se menja vrednost promenjive, može da se pozove iz bilo kog fajla gde imamo referencu na taj LiveData objekat (u ovome primeru taj objekat je mHasSearchResults). To može biti neki View koji prihvata korisničku akciju i na osnovu nje setuje novu vrednost, ili fajl koji registruje promenu u bazi podataka nakon čega emituje te promene….

  3. Praćenje promena MutableLiveData u View klasi

    • Referenciranje na ViewModel u View-u:

    • Kreiranje reference na LiveData objekat

      Sada kada imamo referencu na ViewModel možemo da pristupimo i njegovim metodama, što ćemo da iskoristimo i da pozovemo getter metodu sa kojom ćemo dobiti referencu i na LiveData objekat:

    • Observe (osluškivanje promena)

      Kada dobijemo referencu na LiveData objekat možemo da pozovemo njegovu metodu observe(). Ova metoda prihvata dva parametra:

      • “LifecycleOwner” – definišemo na koji View treba LiveData da pazi kada je u pitanju LifeCycle
      • “Observer” – kroz ovaj parametar kreirajući novi anonimni posmatrač (Observer) praktično definišemo šta će posmatrač da uradi nakon promene:

      NAPOMENA:
      Sa prethodnim kodom u okviru onChanged() mi reagujemmo samo na promenu vrednosti koju nadgledamo, medjutim nekada je potrebno imati vrednost koju čuva live objekat i pre same promene (npr. setovati početnu vrednost pre nego što dodje do bilo kakve promene…). U takvom slučaju se dobijanje trenutne vrednosti koju čuva LiveData objekat vrši koristeći njegovu metodu getValue() (vraća trenutnu vrednost koju čuva taj objekat):

Repository – ViewModel – View

U MVVM arhitekturi svaki nivo ima direktni pristup samo jednom nivou ispod, dok ga ne interesuju oni iznad njega. Ovakva arhitektura nam omogućava da promenimo layer iznad (npr.View) bez ikakvog menjanja nivoa ispod tj. izvora informacija (npr.ViewModel).

Cela komunikacija izmedju ovih arhitektonskih layer-a ustvari je jedan zatvoreni krug. Početak komunijacije je najčešće sama interakcija korisnika sa interfejsom (View), koji prosledjuje informacije “na dole” sve do baze podataka (kroz setValue()). Promena u bazi je okidač za emitovanje promena u Repository klasi. Te promene osluškuje ViewModel, pa ih zatim koristi da bi emitovao svoje informacije View-u, koji ih zatim koristi za ažuriranje sadržaja. Ovaj slučaj je praktično produženi slučaj komunikacije izmedju View-a i ViewModel-a iz prethodne sekcije.

Repository

Pošto Repositoru jedini ima direktan pristup izvoru informacija (u ovome slučaju je to baza podataka), u njemu kreiramo metodu čiji cilj je da dobije te informoacije preko klasa zaduženih za pristup bazi (u ovome primeru je “AppDBHelper” klasa)

Kao što smo do sada videli u radu sa LiveData u klasi koja emituje informacije potrebne su tri stvari:

  1. Kreiranje MutableLiveData objekata (ovde sve započinje pa se kreira novi objekat)
  2. Getter tog MutableLiveData objekata
  3. Emitovanje informacija sa setValue() metodom (ove informacije se dobijaju iz Db-a prethodnom metodom “getAllItems()”
    Drugu i treću stavku ćemo definisati zajedno u okviru jedne metode:

ViewModel

U ViewModel klasi je takodje prva stavka kreiranje MutableLiveData objekata. Medjutim ovde postoji mala razlika u odnosu na prethodni primer (komunikacija samo izmedju View-a iViewModel-a), jer se ne kreira novi MutableLiveData objekat, već taj objekat ViewModel dobija iz Repository-ja preko getter-a:

Druga stavka je kreiranje gettera za taj LiveData objekat (koji će iskoristi View):

A treća stavka je emitovanje promena (setValue() ili postValue()). Obično se ove promene dešavaju kada se pozove neka od Repository metoda za insert, update ili remove data iz baze.

View

View je zadužen da pokrene ceo lanac dogadja tako što prihvata interakciju krajnjeg korisnika (koji može da izvrši neku promenu koja će da utiče na sadržaj baze podataka). Zatim se taj korisnički zahtev prosledjuje dalje “na dole” (View -> VieModel -> Repository -> DB). Prihvatanje

U okviru ove metode se poziva metoda koja će na kraju da utiče na promenu baze podataka:

Ili

Kada dodje do promena u bazi podataka View-u su potrebne te nove informacije. Iz tog razloga View prati i osluškuje promene LiveDate objekta (čiji počeci sežu do repa) i reaguje na njih ažuriranjem sadržaja:

Ceo kod projekta u ovim primerima možete videti na GitHub-u u okviru projekta “SQLdatabaseWithoutLibrary”.

Fragmenat – Fragment

Komunikacija se odvija preko zajedničkog ViewModela, tako što svi View-i (fragmenti i aktivnost) mogu da mu pristupe, i “osluškući” promene koje emituje ViewModel.

fragment and viewmodel

Kada fragmentA želi da pošalje neku poruku FragmentuB, dovoljno je da ažurira LiveData u ViewModel-u, a ViewModel će da “emituje” te promene u etar, pa će i FragmentB koji osluškuje to biti obavešten o poruci i moći da reagujue odgovarajuće na nju. Ukoliko Aktivnost osluškuje promene kod istog tog LiveData objekta, i ona će biti istovremeno obaveštena pa će i ona moći da adekvatno reaguje.

Prednosti ove komunikacije su sledeće:

  1. Aktivnost ne mora ništa da zna i da radi u vezi konverzacije izmedju dva fragmenta (koristeći listener patern kod vezan za konverzaciju se nalazi u okviru Aktivnosti).
  2. Fragmenti ne moraju da znaju jedni o drugima, ako jedan od fragmenata nestane, drugi nastavi da radi kao i obično.
  3. Svaki fragment ima svoj životni ciklus i na njega ne utiče životni ciklus drugog. Ako jedan fragment zameni drugi, UI nastavlja da radi bez problema.
Primer




Pogledajte ceo primer projekta na GitHub-u u okviru repozitorijuma “FragmetComunication”.