LiveData klase
LiveData
1 |
public abstract class LiveData extends Object |
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.
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
1 2 3 |
java.lang.Object ↳android.arch.lifecycle.LiveData<T> ↳android.arch.lifecycle.MutableLiveData<T> |
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
1 2 3 4 |
java.lang.Object ↳android.arch.lifecycle.LiveData<T> ↳android.arch.lifecycle.MutableLiveData<T> ↳android.arch.lifecycle.MediatorLiveData<T> |
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.
- 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)
123456789101112131415161718192021LiveData<Integer> berzaBeogradLiveData = ....;LiveData<Integer> berzaLondonLiveData = ....;final MediatorLiveData<String> resultMediatorLiveData = new MediatorLiveData<>();resultMediatorLiveData.addSource(berzaBeogradLiveData, new Observer<Integer>() {@Overridepublic void onChanged(@Nullable Integer value) {// Uradi nešto sa integer-om// npr. resultMediatorLiveData.setValue(value);}});resultMediatorLiveData.addSource(berzaLondonLiveData, new Observer<Integer>() {@Overridepublic void onChanged(@Nullable Integer value) {// Uradi nešto sa integer-om}}); - 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:
1234567891011liveDataMerger.addSource(liveData1, new Observer() {private int count = 1;@Override public void onChanged(@Nullable Integer s) {count++;liveDataMerger.setValue(s);if (count > 10) {liveDataMerger.removeSource(liveData1);}}});
Observe u View-u
Deo koda u okviru View-a je isti kao kod LiveData, pa je posmatranje MediatorLiveData može da izgleda ovako:
1 2 3 4 5 6 |
mediatorLiveData.observe(this, new Observer<Integer>() { @Override public void onChanged(@Nullable Integer integer) { //Use the value } }); |
View – ViewModel
U ovome primeru je prikazan postupak neophodan za komunikacija izmedju View-a (aktivnost) i njegovog ViewModel-a:
-
Implementacija biblioteke u projekat
1implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
1implementation "android.arch.lifecycle:extensions:1.1.0"
Takodje u okviru gradle staviti da google() bude u sekciji repository:
123456allprojects {repositories {jcenter()google()}} -
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:
123public class MainActivityViewModel extends ViewModel{}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.
12345public class MainActivityViewModel extends AndroidViewModel{public ManiActivityViewModel(@NonNull Application application) {super(application);}} -
Kreiranje MutableLiveData objekta
123public class MainActivityViewModel extends ViewModel{private MutableLiveData<Boolean> mHasSearchResults = new MutableLiveData<>();} -
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:
12345678public class MainActivityViewModel extends ViewModel{...public LiveData<Boolean> getSearchResultsState(){return mHasSearchResults;}} -
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):
1mHasSearchResults.setValue(true);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()
-
-
Praćenje promena MutableLiveData u View klasi
-
Referenciranje na ViewModel u View-u:
1234567891011private MainActivityViewModel mMainActivityViewModel;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);//Referenciranje na ViewModel iz View-a:mMainActivityViewModel = ViewModelProviders.of(this).get(MainActivityViewModel.class);} -
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:
1LiveData<boolean> liveDataObjekat = mMainActivityViewModel.getSearchResultsState(); -
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:
12345678910111213liveDataObjekat.observe(this, new Observer<Boolean>() {@Overridepublic void onChanged(@Nullable Boolean hasSearchResults) {// iskoristite bolean vrednost "hasSearchResults" koja je prosledjena i utičite na Viewif (hasSearchResults){clResultsContainer.setVisibility(View.VISIBLE);} else {clResultsContainer.setVisibility(View.GONE);}}});
-
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
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)
1 2 |
private AppDBHelper mHandler; List<BuyItem> listFromDB = mHandler.getAllItemsFromDB(); |
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:
1 2 3 4 5 |
/* get list directly from DB */ public List<BuyItem> getAllItems () { List<BuyItem> listFromDB = mHandler.getAllItemsFromDB(); return listFromDB; } |
Kao što smo do sada videli u radu sa LiveData, da bi se emitovala promena neke promenjive potrebne su tri stvari:
- Kreiranje MutableLiveData objekata (ovde sve započinje pa se kreira novi objekat)
12/* New LiveData */MutableLiveData <List<BuyItem>> allDataItems = new MutableLiveData<>(); - Getter tog MutableLiveData objekata
- 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:12345/* Getter and setValue (value "listFromDB") to emitting changes all in one */public MutableLiveData<List<BuyItem>> getAllItemsFromRepo() {allDataItems.setValue(listFromDB);return allDataItems;}
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:
1 |
private MutableLiveData<List<BuyItem>> allDataItems = repository.getAllItemsFromRepo(); |
Druga stavka je kreiranje gettera za taj LiveData objekat (koji će iskoristi View):
1 2 3 4 |
/* LiveData Getter from ViewModel*/ public LiveData<List<BuyItem>> getAllItemsFromViewModel() { return allDataItems; } |
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.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
/*Insert item to DB*/ public void insertItemDB(BuyItem buyItem) { repository.insertItem(buyItem); /*Set new value end emit that */ allDataItems.postValue(repository.getAllItems()); } /* Remove item from DB*/ public void removeItemFromDB(long id) { repository.removeItem(id); /*Set new value end emit that*/ allDataItems.postValue(repository.getAllItems()); } |
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
1 2 3 4 5 6 |
buttonAdd.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { addItem(); } }); |
U okviru ove metode se poziva metoda koja će na kraju da utiče na promenu baze podataka:
1 |
mMainViewModel.insertItemDB(newBuyItem); |
Ili
1 |
mMainViewModel.removeItemFromDB((long) viewHolder.itemView.getTag()); |
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:
1 2 3 4 5 6 7 8 |
/* Observe changes in list emitted from ViewModel*/ mMainViewModel.getAllItemsFromViewModel().observe(this, new Observer() { @Override public void onChanged(Object o) { /*React to changes in list*/ mMainViewModel.updateAdapter(); } }); |
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.
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:
- 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).
- Fragmenti ne moraju da znaju jedni o drugima, ako jedan od fragmenata nestane, drugi nastavi da radi kao i obično.
- 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); /*Adding fragments to container*/ getSupportFragmentManager().beginTransaction() .add(R.id.clContainerA, FragmentA.newInstance()) .add(R.id.clContainerB, FragmentB.newInstance()) .commit(); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
public class SharedViewModel extends ViewModel { /*Definisanje promenjivog objekta koji predstavlja tekst*/ private MutableLiveData<String> mText = new MutableLiveData<>(); /*dobijanje reference na promenjiv objekat*/ public LiveData<String> getTextLiveDataObject() { return mText; } /*definisanje vrednosti objekta*/ public void setUnos(String text) { this.mText.setValue(text); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
public class FragmentA extends Fragment { private SharedViewModel mViewModel; private EditText editText; private Button button; public static FragmentA newInstance() { return new FragmentA(); } @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { View root = inflater.inflate(R.layout.fragment_a_layout, container, false); editText = root.findViewById(R.id.etUnos); button = root.findViewById(R.id.btnAkcija); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { mViewModel.setUnos(editText.getText().toString()); } }); return root; } @Override public void onActivityCreated(@Nullable Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); mViewModel = new ViewModelProvider(getActivity()).get(SharedViewModel.class); mViewModel.getTextLiveDataObject().observe(getViewLifecycleOwner(), new Observer<String>() { /*Definisanje callback metode koja regauje na promenu LiveData objekta mText*/ @Override public void onChanged(String s) { editText.setText(s); } }); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
public class FragmentB extends Fragment { private SharedViewModel mViewModel; private EditText editText; private Button button; public static FragmentB newInstance() { return new FragmentB(); } @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { View root = inflater.inflate(R.layout.fragment_b_layout, container, false); editText = root.findViewById(R.id.etUnos); button = root.findViewById(R.id.btnAkcija); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { mViewModel.setUnos(editText.getText().toString()); } }); return root; } @Override public void onActivityCreated(@Nullable Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); mViewModel = new ViewModelProvider(getActivity()).get(SharedViewModel.class); mViewModel.getTextLiveDataObject().observe(getViewLifecycleOwner(), new Observer<String>() { /*Definisanje callback metode koja regauje na promenu LiveData objekta mText*/ @Override public void onChanged(String s) { editText.setText(s); } }); } } |
Pogledajte ceo primer projekta na GitHub-u u okviru repozitorijuma “FragmetComunication”.
LiveData klasa
1 |
public abstract class LiveData extends Object |
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.
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
1 2 3 |
java.lang.Object ↳android.arch.lifecycle.LiveData<T> ↳android.arch.lifecycle.MutableLiveData<T> |
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
1 2 3 4 |
java.lang.Object ↳android.arch.lifecycle.LiveData<T> ↳android.arch.lifecycle.MutableLiveData<T> ↳android.arch.lifecycle.MediatorLiveData<T> |
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.
- 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)
123456789101112131415161718192021LiveData<Integer> berzaBeogradLiveData = ....;LiveData<Integer> berzaLondonLiveData = ....;final MediatorLiveData<String> resultMediatorLiveData = new MediatorLiveData<>();resultMediatorLiveData.addSource(berzaBeogradLiveData, new Observer<Integer>() {@Overridepublic void onChanged(@Nullable Integer value) {// Uradi nešto sa integer-om// npr. resultMediatorLiveData.setValue(value);}});resultMediatorLiveData.addSource(berzaLondonLiveData, new Observer<Integer>() {@Overridepublic void onChanged(@Nullable Integer value) {// Uradi nešto sa integer-om}}); - 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:
1234567891011liveDataMerger.addSource(liveData1, new Observer() {private int count = 1;@Override public void onChanged(@Nullable Integer s) {count++;liveDataMerger.setValue(s);if (count > 10) {liveDataMerger.removeSource(liveData1);}}});
Observe u View-u
Deo koda u okviru View-a je isti kao kod LiveData, pa je posmatranje MediatorLiveData može da izgleda ovako:
1 2 3 4 5 6 |
mediatorLiveData.observe(this, new Observer<Integer>() { @Override public void onChanged(@Nullable Integer integer) { //Use the value } }); |
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.
-
Implementacija biblioteke u projekat
1implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
1implementation "android.arch.lifecycle:extensions:1.1.0"
Takodje u okviru gradle staviti da google() bude u sekciji repository:
123456allprojects {repositories {jcenter()google()}} -
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:
123public class MainActivityViewModel extends ViewModel{}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.
12345public class MainActivityViewModel extends AndroidViewModel{public ManiActivityViewModel(@NonNull Application application) {super(application);}} -
Kreiranje MutableLiveData objekta
123public class MainActivityViewModel extends ViewModel{private MutableLiveData<Boolean> mHasSearchResults = new MutableLiveData<>();} -
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:
12345678public class MainActivityViewModel extends ViewModel{...public LiveData<Boolean> getSearchResultsState(){return mHasSearchResults;}} -
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
1mHasSearchResults.setValue(true);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….
-
-
Praćenje promena MutableLiveData u View klasi
-
Referenciranje na ViewModel u View-u:
1234567891011private MainActivityViewModel mMainActivityViewModel;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);//Referenciranje na ViewModel iz View-a:mMainActivityViewModel = ViewModelProviders.of(this).get(MainActivityViewModel.class);} -
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:
1LiveData liveDataObjekat = mMainActivityViewModel.getSearchResultsState(); -
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:
12345678910111213liveDataObjekat.observe(this, new Observer<Boolean>() {@Overridepublic void onChanged(@Nullable Boolean hasSearchResults) {// iskoristite bolean vrednost "hasSearchResults" koja je prosledjena i utičite na Viewif (hasSearchResults){clResultsContainer.setVisibility(View.VISIBLE);} else {clResultsContainer.setVisibility(View.GONE);}}});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):1234567if (liveDataObjekat.getValue() != null) {boolean state = liveDataObjekat.getValue();// .....Uradi nešto sa tom dobijenom vrednosti}
-
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)
1 2 3 4 5 6 7 |
private AppDBHelper mHandler; /* get list directly from DB */ public List<BuyItem> getAllItems () { List<BuyItem> listFromDB = mHandler.getAllItemsFromDB(); return listFromDB; } |
Kao što smo do sada videli u radu sa LiveData u klasi koja emituje informacije potrebne su tri stvari:
- Kreiranje MutableLiveData objekata (ovde sve započinje pa se kreira novi objekat)
12/* New LiveData, setValue and Emit changes (which value acquired list "listFromDB") */MutableLiveData <List<BuyItem>> allDataItems = new MutableLiveData<>(); - Getter tog MutableLiveData objekata
- 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:12345/* Getter and setValue (value "listFromDB") to emitting changes all in one */public MutableLiveData<List<BuyItem>> getAllItemsFromRepo() {allDataItems.setValue(getAllItems());return allDataItems;}
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:
1 |
private MutableLiveData<List<BuyItem>> allDataItems = repository.getAllItemsFromRepo(); |
Druga stavka je kreiranje gettera za taj LiveData objekat (koji će iskoristi View):
1 2 3 4 |
/* LiveData Getter from ViewModel*/ public LiveData<List<BuyItem>> getAllItemsFromViewModel() { return allDataItems; } |
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.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
/*Insert item to DB*/ public void insertItemDB(BuyItem buyItem) { repository.insertItem(buyItem); /*Set new value end emit that */ allDataItems.postValue(repository.getAllItems()); } /* Remove item from DB*/ public void removeItemFromDB(long id) { repository.removeItem(id); /*Set new value end emit that*/ allDataItems.postValue(repository.getAllItems()); } |
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
1 2 3 4 5 6 |
buttonAdd.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { addItem(); } }); |
U okviru ove metode se poziva metoda koja će na kraju da utiče na promenu baze podataka:
1 |
mMainViewModel.insertItemDB(newBuyItem); |
Ili
1 |
mMainViewModel.removeItemFromDB((long) viewHolder.itemView.getTag()); |
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:
1 2 3 4 5 6 7 8 |
/* Observe changes in list emitted from ViewModel*/ mMainViewModel.getAllItemsFromViewModel().observe(this, new Observer() { @Override public void onChanged(Object o) { /*React to changes in list*/ mMainViewModel.updateAdapter(); } }); |
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.
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:
- 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).
- Fragmenti ne moraju da znaju jedni o drugima, ako jedan od fragmenata nestane, drugi nastavi da radi kao i obično.
- 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); /*Adding fragments to container*/ getSupportFragmentManager().beginTransaction() .add(R.id.clContainerA, FragmentA.newInstance()) .add(R.id.clContainerB, FragmentB.newInstance()) .commit(); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
public class SharedViewModel extends ViewModel { /*Definisanje promenjivog objekta koji predstavlja tekst*/ private MutableLiveData<String> mText = new MutableLiveData<>(); /*dobijanje reference na promenjiv objekat*/ public LiveData<String> getTextLiveDataObject() { return mText; } /*definisanje vrednosti objekta*/ public void setUnos(String text) { this.mText.setValue(text); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
public class FragmentA extends Fragment { private SharedViewModel mViewModel; private EditText editText; private Button button; public static FragmentA newInstance() { return new FragmentA(); } @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { View root = inflater.inflate(R.layout.fragment_a_layout, container, false); editText = root.findViewById(R.id.etUnos); button = root.findViewById(R.id.btnAkcija); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { mViewModel.setUnos(editText.getText().toString()); } }); return root; } @Override public void onActivityCreated(@Nullable Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); mViewModel = new ViewModelProvider(getActivity()).get(SharedViewModel.class); mViewModel.getTextLiveDataObject().observe(getViewLifecycleOwner(), new Observer<String>() { /*Definisanje callback metode koja regauje na promenu LiveData objekta mText*/ @Override public void onChanged(String s) { editText.setText(s); } }); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
public class FragmentB extends Fragment { private SharedViewModel mViewModel; private EditText editText; private Button button; public static FragmentB newInstance() { return new FragmentB(); } @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { View root = inflater.inflate(R.layout.fragment_b_layout, container, false); editText = root.findViewById(R.id.etUnos); button = root.findViewById(R.id.btnAkcija); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { mViewModel.setUnos(editText.getText().toString()); } }); return root; } @Override public void onActivityCreated(@Nullable Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); mViewModel = new ViewModelProvider(getActivity()).get(SharedViewModel.class); mViewModel.getTextLiveDataObject().observe(getViewLifecycleOwner(), new Observer<String>() { /*Definisanje callback metode koja regauje na promenu LiveData objekta mText*/ @Override public void onChanged(String s) { editText.setText(s); } }); } } |
Pogledajte ceo primer projekta na GitHub-u u okviru repozitorijuma “FragmetComunication”.