Componente pentru arhitectura Android LiveData

Am acoperit deja o mulțime de terenuri în seria noastră de componente Android Architecture Components. Am început să vorbim despre ideea din spatele noii arhitecturi și despre componentele cheie prezentate la Google I / O. În cel de-al doilea post, am început explorarea profundă a componentelor principale ale pachetului, urmărind cu atenție Ciclu de viață și LiveModel componente. În acest post, vom continua să explorăm Componentele de Arhitectură, de această dată analizând minunatul LiveData component.

Presupun că sunteți familiarizat cu conceptele și componentele acoperite în ultimele tutoriale, cum ar fi Ciclu de viață, LifecycleOwner, și LifecycleObserver. Dacă nu sunteți, aruncați o privire la primul post din această serie, unde discutăm ideea generală din spatele noii arhitecturi Android și a componentelor sale. 

Vom continua să construim pe exemplul aplicației pe care am început-o în ultima parte a acestei serii. O puteți găsi în reparația GitHub tutorial.

1. Componenta LiveData

LiveData este un deținător de date. Este capabil să fie observat, poate deține orice fel de date și, în plus, este, de asemenea ciclului de viață-conștient. În termeni practici, LiveData poate fi configurat să trimită actualizări de date doar atunci când observatorul său este activ. Datorită conștientizării pe durata ciclului de viață, atunci când este observată de către a LifecycleOwner  LiveData componenta va trimite actualizări numai atunci când este observatorul Ciclu de viață este încă activă și va elimina relația observată odată cu observarea observatorului Ciclu de viață este distrusă.

LiveData componenta are multe caracteristici interesante:

  • împiedică scurgeri de memorie atunci când observatorul este legat de un a Ciclu de viață
  • împiedică accidentele cauzate de activitățile oprite 
  • datele sunt întotdeauna actualizate
  • se ocupă de modificările configurației fără probleme
  • face posibilă partajarea resurselor
  • gestionează automat ciclul de viață

2. Observarea LiveData

A LiveData Componenta trimite actualizări de date numai atunci când observatorul său este "activ". Când se observă de către a LifecycleOwner,  LiveData componenta considera observatorul ca fiind activ numai in timpul lui Ciclu de viață este pe state ÎNCEPUT sau reluate, altfel va considera observatorul ca inactiv. În timpul stadiului inactiv al observatorului, LiveData va opri fluxul de actualizare a datelor, până când observatorul său va deveni activ din nou. Dacă observatorul este distrus, LiveData va elimina referința la observator.

Pentru a realiza acest comportament, LiveData creează o legătură strânsă cu observatorul Ciclu de viață când este observat de către a LifecycleOwner. Această capacitate facilitează evitarea scurgerilor de memorie la observare LiveData. Cu toate acestea, dacă procesul de observare este chemat fără a LifecycleOwner,  LiveData componenta nu va reacționa la Ciclu de viață stare, iar statutul observatorului trebuie manevrat manual.

Pentru a observa a LiveData, apel observa (LifecycleOwner, Observer) sau observeForever (Observer)

  • observa (LifecycleOwner, Observer): Acesta este modul standard de observare a LiveData. Legătura dintre observator și a Ciclu de viață, schimbare LiveData- starea activă și inactivă în conformitate cu LifecycleOwner starea curenta.
  • observeForever (Observer): Această metodă nu utilizează a LifecycleOwner, asa ca LiveData nu va putea răspunde Ciclu de viață evenimente. Atunci când utilizați această metodă, este foarte important să apelați removeObserver (Observer), în caz contrar, observatorul nu poate fi colectat de gunoi, provocând o scurgere de memorie.
// sunat de la o locație LifecycleOwner.observe (// LifecycleOwner this, // creând un observer Observer location -> info ("locație: $ location !!. latitude, $ location.longitude") // Observare fără LifecycleOwner val observer = Observer locație -> info ("locație: $ location !!. Latitude, $ location.longitude")) location.observeForever (observator) // when observer without a LivecyleOwner // este necesar să eliminați observatorii la un loc point.removeObserver (observator)

3. Implementarea LiveData

Tipul generic din LiveData clasa definește tipul de date care vor fi păstrate. De exemplu, LiveData deține Locație date. Sau LiveData deține a Şir

Există două metode principale care trebuie luate în considerare în implementarea componentei: onActive () și onInactive (). Ambele metode reacționează la starea observatorului.

Exemplu de implementare

În proba noastră de proiect am folosit o mulțime de LiveData obiecte, dar am implementat doar unul: LocationLiveData. Clasa se ocupă de GPS Locație, depășind poziția actuală numai pentru un observator activ. Observați că clasa își actualizează valoarea pe onLocationChanged metodă, trecând la observatorul activ actual actualizat Locație date.

clasa LocationLiveData @Inject constructor (context: Context): LiveData(), LocListener, AnkoLogger private val locationManager: LocationManager = context.getSystemService (Context.LOCATION_SERVICE) ca LocationManager @SuppressLint ("MissingPermission") suprascrie distracție onInactive () locationManager.removeUpdates (this) SuppressLint ("MissingPermission") distracție refreshLocation () info ("refreshLocation") locationManager.requestSingleUpdate (LocationManager.GPS_PROVIDER, this, null)

4. The MutableLiveData Clasa de ajutor

 MutableLiveData este o clasă de ajutor care se extinde LiveData, și expune postValue și SetValue metode. În afară de asta, se comportă exact ca părintele său. Pentru ao utiliza, definiți tipul de date pe care le deține, cum ar fi MutableLiveData să dețină o Şir, și să creați o instanță nouă.

val myData: MutableLiveData = MutableLiveData ()

Pentru a trimite actualizări unui observator, apelați postValue sau SetValue. Comportamentul acestor metode este destul de similar; in orice caz, SetValue va seta direct o nouă valoare și poate fi apelat numai din firul principal, în timp ce postValue creează o nouă sarcină pe firul principal pentru a seta noua valoare și poate fi apelată dintr-un fir de fundal.

distracție updateData () // trebuie să fie apelat din firul principal myData.value = api.getUpdate distracție updateDataFromBG () // poate fi sunat din bg thread myData.postValue (api.getUpdate)

Este important să se considere că, din moment ce postValue metoda creează un nou Sarcină și mesaje pe firul principal, acesta va fi mai lent decât apelurile directe către SetValue.

5. Transformări ale LiveData

În cele din urmă, va trebui să schimbați a LiveData și să-i difuzeze noua valoare observatorului său. Sau poate că trebuie să creați o reacție în lanț între două LiveData obiecte, făcând una să reacționeze la schimbările de pe altul. Pentru a face față ambelor situații, puteți utiliza funcția transformări clasă.

Transformări de hartă

Transformations.map aplică o funcție pe LiveData instanța și trimite rezultatul către observatorii săi, oferindu-vă posibilitatea de a manipula valoarea datelor.

Este foarte ușor de implementat Transformations.map. Tot ce trebuie să faceți este să oferiți o LiveData care trebuie respectate și a Funcţie pentru a fi numit atunci când observat LiveData modificări, amintiți - vă că Funcţie trebuie să returneze noua valoare a modelului transformat LiveData.

Să presupunem că aveți un a LiveData care trebuie să sune un API când a Şir valoare, ca un câmp de căutare, se modifică.

// LiveData care apelează api // când 'searchLive' își schimbă valoarea apiLive: LiveData = Transformations.map (căutareLive, interogare -> return @ map api.call (interogare)) // De fiecare dată când 'searchLive' are // valoarea lui actualizată, va apela // 'apiLive' Transformation.map fun update (interogare: String) searchLive.postValue (interogare)

SwitchMap Transformări

Transformations.switchMap este destul de similar cu Transformations.map, dar trebuie să returneze a LiveData obiect ca rezultat. Este ceva mai greu de folosit, dar vă permite să construiți reacții puternice în lanț. 

În proiectul nostru, am folosit Transformations.switchMap pentru a crea o reacție între LocationLiveData și ApiResponse

  1. Al nostru Transformation.switchMap observă LocationLiveData schimbări.
  2. LocationLiveData valoarea actualizată este utilizată pentru a apela MainRepository pentru a obține vremea pentru locația specificată.
  3. Depozitul solicită OpenWeatherService care produce o LiveData> ca rezultat.
  4. Apoi, cei întorși LiveData este observată de către a MediatorLiveData, care este responsabil pentru modificarea valorii primite și actualizarea vremii expuse în stratul de vizualizare.
class MainViewModel @ Constructor injectare (depozit valore privat: MainRepository): ViewModel (), AnkoLogger // Locația privată val: LocLiveData = repository.locationLiveDa () private var weatherByLocationResponse: LiveData> = Transformations.switchMap (locație, l -> info ("locationByLocation: \ nlocație: $ l") return @ switchMap repository.getWeatherByLocation (l)

Feriți-vă de operațiunile consumatoare de timp în dvs. LiveData transformări, totuși. În codul de mai sus, ambele transformări metodele rulează pe firul principal.

6. MediatorLiveData

MediatorLiveData este un tip mai avansat de LiveData. Are capabilități foarte asemănătoare cu cele ale transformări clasa: este capabil să reacționeze la altele LiveData obiecte, apelarea a Funcţie când datele observate se modifică. Cu toate acestea, are multe avantaje în comparație cu transformări, deoarece nu are nevoie să ruleze pe firul principal și poate observa mai multe LiveDatao dată.

Pentru a observa a LiveData, apel addSource (LiveData, Observator), ceea ce face ca observatorul să reacționeze la onChanged din metoda dată LiveData. Pentru a opri observarea, sunați removeSource (LiveData).

val mediatorData: MediatorLiveData = MediatorLiveData () mediatorData.addSource (dataA, value -> // reacționează la valoarea valorii ("valoarea mea valoarea $")) mediatorData.addSource (dataB, value - $ value ") // putem elimina sursa odată utilizată mediatorData.removeSource (dataB))

În proiectul nostru, datele observate de stratul de vizualizare care conține vremea de expunere sunt a MediatorLiveData. Componenta observă alte două LiveData obiecte: weatherByLocationResponse, care primește actualizări meteorologice după locație, și weatherByCityResponse, care primește actualizări meteorologice după numele orașului. De fiecare dată când aceste obiecte sunt actualizate, weatherByCityResponse va actualiza stratul de vizualizare cu vremea cerută.

În MainViewModel, observăm LiveData și să furnizeze vreme obiect de vedere.

class MainViewModel @ Inject constructor (depozit privat val: MainRepository): ViewModel (), AnkoLogger // ... // Valoarea observată de View. // Transformă WeatherResponse la WeatherMain. vreme privată de vale: MediatorLiveData> = MediatorLiveData () // recuperarea vremii LiveData distracție getWeather (): LiveData> info ("getWeather") întoarce vremea distracție privată addWeatherSources () info ("addWeatherSources") weather.addSource (weatherByCityResponse, w -> info ("addWeatherSources: \ nweather: $ w !! ) updateWeather (w.data !!)) weather.addSource (WeatherByLocationResponse, w -> info ("addWeatherSources: weatherByLocationResponse: \ n $ w !! !) distracție privată updateWeather (w: WeatherResponse) info ("updateWeather") // obținerea vremii de astăzi val weatherMain = WeatherMain.factory (w) // salva pe preferințele partajate repository.saveWeatherMainOnPrefs (weatherMain) valoare meteo weather.postValue (ApiResponse (data = weatherMain)) init // ... addWeatherSources ()

În Activitate principala, vremea este observată și rezultatul său este arătat utilizatorului.

 private fun initModel () // Obțineți ViewModel viewModel = ViewModelProviders.of (this, viewModelFactory) .get (MainViewModel :: class.java) dacă (viewModel! = null) // observați modul meteo viewModel !!. getWeather (). observați (această @ MainActivity, Observer r -> if (r! = null) informația ("Weather received on MainActivity: \ n $ r") if (! r.hasError eroare info ("vreme: $ r.data") dacă (r.data! = null) setUI (r.data) fals) dacă (r.error !. statusCode! = 0) if (r.error !, message! = null) toast (r.error.message !!) altceva toast ("A apărut o eroare") )

MediatorLiveData a fost de asemenea utilizat ca bază a unui obiect care gestionează apelurile către API-ul OpenWeatherMap. Aruncați o privire la această implementare; este mai avansată decât cele de mai sus și merită studiat. Dacă sunteți interesat, aruncați o privire la OpenWeatherService, acordând o atenție deosebită Mediator clasă.

7. Concluzie

Suntem aproape la sfârșitul explorării componentelor de arhitectură Android. Până acum, ar trebui să înțelegeți suficient pentru a crea unele aplicații puternice. În următorul post, vom explora Cameră, un ORM care se împachetează SQLite și poate produce LiveData rezultate. Cameră componentă se potrivește perfect în această arhitectură și este ultima piesă a puzzle-ului.

Ne vedem în curând! Între timp, verificați câteva dintre celelalte postări ale noastre privind dezvoltarea aplicațiilor Android!

Cod