Android-architectuurcomponenten LiveData

We hebben al heel veel aandacht besteed aan onze reeks Android-architectuurcomponenten. We begonnen met praten over het idee achter de nieuwe architectuur en kijken naar de belangrijkste componenten gepresenteerd bij Google I / O. In de tweede post zijn we begonnen met een grondige verkenning van de belangrijkste componenten van het pakket, waarbij we de Levenscyclus en LiveModel componenten. In dit bericht gaan we door met het verkennen van de architectuurcomponenten, deze keer met het analyseren van het geweldige Actuele gegevens bestanddeel.

Ik ga ervan uit dat je bekend bent met de concepten en componenten die in de laatste tutorials worden behandeld, zoals Levenscyclus, LifecycleOwner, en LifecycleObserver. Als je dat niet bent, bekijk dan het eerste bericht in deze serie, waarin ik het algemene idee achter de nieuwe Android-architectuur en zijn componenten bespreek. 

We blijven bouwen aan de voorbeeldapplicatie die we in het laatste deel van deze serie hebben gestart. Je kunt het vinden in de tutorial GitHub repo.

1. De LiveData-component

Actuele gegevens is een gegevenshouder. Het kan worden geobserveerd, het kan elke soort gegevens bevatten en bovendien is het dat ook lifecycle-aware. In praktische termen, Actuele gegevens kan worden geconfigureerd om alleen gegevensupdates te verzenden wanneer de waarnemer actief is. Dankzij zijn levenscyclusbewustzijn, waargenomen door een LifecycleOwner de Actuele gegevens component stuurt alleen updates wanneer de waarnemer Levenscyclus is nog steeds actief en het zal de geobserveerde relatie verwijderen zodra die van de waarnemer is Levenscyclus is vernietigd.

De Actuele gegevens component heeft veel interessante kenmerken:

  • voorkomt geheugenlekken wanneer de waarnemer gebonden is aan a Levenscyclus
  • voorkomt crashes als gevolg van stopgezette activiteiten 
  • data is altijd up-to-date
  • handelt configuratiewijzigingen soepel af
  • maakt het mogelijk om bronnen te delen
  • handelt automatisch de levenscyclus af

2. Waarnemen Actuele gegevens

EEN Actuele gegevens component verzendt alleen gegevensupdates wanneer de waarnemer "actief" is. Wanneer waargenomen door een LifecycleOwner, de Actuele gegevens component beschouwt de waarnemer om alleen actief te zijn terwijl het Levenscyclus is op de staten BEGONNEN of HERVAT, anders beschouwt het de waarnemer als inactief. Tijdens de inactieve toestand van de waarnemer, Actuele gegevens stopt de stroom van de gegevensupdate totdat de waarnemer weer actief wordt. Als de waarnemer is vernietigd, Actuele gegevens zal zijn verwijzing naar de waarnemer verwijderen.

Om dit gedrag te bereiken, Actuele gegevens creëert een nauwe relatie met de waarnemer Levenscyclus indien waargenomen door een LifecycleOwner. Deze capaciteit maakt het gemakkelijker om geheugenlekken te voorkomen bij observatie Actuele gegevens. Echter, als het waarnemingsproces zonder een wordt genoemd LifecycleOwner, de Actuele gegevens component reageert niet op Levenscyclus toestand, en de status van de waarnemer moet handmatig worden afgehandeld.

Om een ​​te observeren Actuele gegevens, telefoontje observeren (LifecycleOwner, Observer) of observeForever (Observer)

  • observeren (LifecycleOwner, Observer): Dit is de standaardmanier om een ​​te observeren Actuele gegevens. Het verbindt de waarnemer met een Levenscyclus, veranderen Actuele gegevensactieve en inactieve toestand volgens de LifecycleOwner huidige toestand.
  • observeForever (Observer): Deze methode gebruikt geen a LifecycleOwner, dus de Actuele gegevens kan niet reageren op Levenscyclus evenementen. Bij gebruik van deze methode is het erg belangrijk om te bellen removeObserver (Observer), anders wordt de waarnemer mogelijk niet verzameld, waardoor een geheugenlek ontstaat.
// called vanuit een LifecycleOwner location.observe (// LifecycleOwner this, // create an observer Observer location -> info ("location: $ location !!. latitude, $ location.longitude")) // Waarnemen zonder LifecycleOwner val observer = Observer location -> info ("location: $ location !!. Latitude, $ location.longitude")) location.observeForever (observer) // when waarnemer zonder een LivecyleOwner // het is noodzakelijk om de waarnemers op een bepaald punt te verwijderen location.removeObserver (observer)

3. Uitvoeringsbesluit Actuele gegevens

Het generieke type in de Actuele gegevens klasse definieert het type gegevens dat zal worden bewaard. Bijvoorbeeld, Actuele gegevens houdt Plaats gegevens. Of Actuele gegevens houdt een Draad

Er zijn twee hoofdmethoden die in aanmerking moeten worden genomen bij de implementatie van de component: onActive () en onInactive (). Beide methoden reageren op de toestand van de waarnemer.

Voorbeeldimplementatie

In ons voorbeeldproject hebben we veel gebruikt Actuele gegevens objecten, maar we hebben er slechts één geïmplementeerd: LocationLiveData. De klas gaat met GPS Plaats, de huidige positie alleen doorgeven aan een actieve waarnemer. Merk op dat de klasse zijn waarde op de update onLocationChanged methode, doorgeven aan de huidige actieve waarnemer de vernieuwde Plaats gegevens.

class LocationLiveData @Inject Constructor (context: Context): LiveData(), LocationListener, AnkoLogger private val locationManager: LocationManager = context.getSystemService (Context.LOCATION_SERVICE) als LocationManager @SuppressLint ("MissingPermission") negeer fun onInactive () info ("onInactive") locationManager.removeUpdates (this) @ SuppressLint ("MissingPermission") leuk vernieuwenLocatie () info ("refreshLocation") locationManager.requestSingleUpdate (LocationManager.GPS_PROVIDER, this, null)

4. Het MutableLiveData Helper Class

De MutableLiveData is een helperklasse die zich uitstrekt Actuele gegevens, en blootstelt postValue en setValue methoden. Anders dan dat, gedraagt ​​het zich precies als zijn ouder. Om het te gebruiken, definieer het type gegevens dat het bevat, zoals MutableLiveData om een ​​te houden Draad, en maak een nieuw exemplaar.

val myData: MutableLiveData = MutableLiveData ()

Als u updates naar een waarnemer wilt verzenden, belt u postValue of setValue. Het gedrag van deze methoden is redelijk vergelijkbaar; echter, setValue zal direct een nieuwe waarde instellen en kan alleen worden aangeroepen vanuit de hoofdthread, terwijl postValue maakt een nieuwe taak op de hoofdthread om de nieuwe waarde in te stellen en kan worden aangeroepen vanuit een achtergrondthread.

fun updateData () // moet worden aangeroepen vanuit de hoofdthread myData.value = api.getUpdate fun updateDataFromBG () // kan worden gebeld vanuit bg thread myData.postValue (api.getUpdate)

Het is belangrijk om te overwegen dat, sinds de postValue methode maakt een nieuw Taak en berichten op de hoofdthread, het zal langzamer zijn dan directe oproepen naar setValue.

5. Transformaties van Actuele gegevens

Uiteindelijk moet u een wijzigen Actuele gegevens en propageer zijn nieuwe waarde aan zijn waarnemer. Of misschien moet je een kettingreactie tussen twee maken Actuele gegevens objecten, waardoor men reageert op veranderingen in een ander. Om beide situaties te behandelen, kunt u de transformaties klasse.

Kaarttransformaties

Transformations.map past een functie toe op a Actuele gegevens bijvoorbeeld en verzendt het resultaat naar zijn waarnemers, waardoor u de mogelijkheid krijgt om de gegevenswaarde te manipuleren.

Het is heel gemakkelijk te implementeren Transformations.map. Het enige wat u hoeft te doen is een Actuele gegevens in acht te nemen en a Functie te worden genoemd als het waargenomene Actuele gegevens veranderingen, herinnerend dat het Functie moet de nieuwe waarde van de getransformeerde retourneren Actuele gegevens.

Stel dat je een hebt Actuele gegevens dat moet een API bellen als een Draad waarde, zoals een zoekveld, verandert.

// LiveData die api // aanroept wanneer 'searchLive' de waarde ervan aanpast: LiveData = Transformations.map (searchLive, query -> return @ map api.call (query)) // Telkens wanneer 'searchLive' // is bijgewerkt, roept het // 'apiLive' Transformation.map fun updateSearch (query: String) searchLive.postValue (query)

SwitchMap-transformaties

De Transformations.switchMap is vrij gelijkaardig aan Transformations.map, maar het moet terugkeren Actuele gegevens object als resultaat. Het is een beetje moeilijker te gebruiken, maar het stelt je in staat om krachtige kettingreacties te bouwen. 

In ons project hebben we gebruikt Transformations.switchMap om een ​​reactie tussen te maken LocationLiveData en ApiResponse

  1. Onze Transformation.switchMap observeert LocationLiveData veranderingen.
  2. De LocationLiveData bijgewerkte waarde wordt gebruikt om de MainRepository om het weer voor de opgegeven locatie te krijgen.
  3. De repository roept de OpenWeatherService dat produceert een Actuele gegevens> als gevolg.
  4. Vervolgens worden de geretourneerde Actuele gegevens wordt waargenomen door een MediatorLiveData, die verantwoordelijk is voor het wijzigen van de ontvangen waarde en het bijwerken van het weer dat wordt weergegeven in de weergavelaag.
class MainViewModel @Inject-constructor (privévalorisatiebron: MainRepository): ViewModel (), AnkoLogger // Locatie private val location: LocationLiveData = repository.locationLiveDa () private var weatherByLocationResponse: LiveData> = Transformations.switchMap (locatie, l -> info ("weatherByLocation: \ nlocation: $ l") return @ switchMap repository.getWeatherByLocation (l))

Pas op voor tijdrovende operaties in uw Actuele gegevens transformaties. In de bovenstaande code, beide transformaties methoden lopen op de hoofdthread.

6. De MediatorLiveData

MediatorLiveData is een meer geavanceerd type Actuele gegevens. Het heeft mogelijkheden die erg lijken op die van de transformaties klasse: het is in staat om op anderen te reageren Actuele gegevens objecten, bellen een Functie wanneer de waargenomen gegevens veranderen. Het heeft echter veel voordelen in vergelijking met transformaties, omdat het niet op de hoofdthread hoeft te draaien en meerdere waarnemingen kan waarnemen Actuele gegevenss tegelijk.

Om een ​​te observeren Actuele gegevens, telefoontje addsource (LiveData, Waarnemer), de waarnemer laten reageren op de onChanged methode van de gegeven Actuele gegevens. Als u de waarneming wilt stoppen, belt u removeSource (LiveData).

val mediatorData: MediatorLiveData = MediatorLiveData () mediatorData.addSource (dataA, value -> // reageer op waarde-info ("mijn waarde $ waarde")) mediatorData.addSource (dataB, value -> // reageer op waarde-info ("mijn waarde $ value ") // we kunnen de bron verwijderen zodra gebruikte mediatorData.removeSource (dataB))

In ons project zijn de gegevens die worden waargenomen door de weergavelaag die het weer bevat om te exposeren een MediatorLiveData. Het onderdeel observeert twee andere Actuele gegevens voorwerpen: weatherByLocationResponse, welke weersupdates ontvangt op locatie, en weatherByCityResponse, welke weerupdates ontvangt op naam van de stad. Elke keer dat deze objecten worden bijgewerkt, weatherByCityResponse zal de weergavelaag bijwerken met het huidige aangevraagde weer.

In de MainViewModel, we observeren de Actuele gegevens en bied de weer object om te bekijken.

class MainViewModel @Inject constructor (private val repository: MainRepository): ViewModel (), AnkoLogger // ... // Waarde waargenomen door weergave. // Het transformeert een WeatherResponse in een WeatherMain. privé valweer: MediatorLiveData> = MediatorLiveData () // weer ophalen LiveData leuk getWeather (): LiveData> info ("getWeather") return weather private fun addWeatherSources () info ("addWeatherSources") weather.addSource (weatherByCityResponse, w -> info ("addWeatherSources: \ nweather: $ w !!. data !!  ") updateWeather (w.data !!)) weather.addSource (weatherByLocationResponse, w -> info (" addWeatherSources: weatherByLocationResponse: \ n $ w !!. data !! ") updateWeather (w.data! !)) private fun updateWeather (w: WeatherResponse) info ("updateWeather") // weer ophalen vanaf vandaag val weatherMain = WeatherMain.factory (w) // opslaan op gedeelde voorkeuren repository.saveWeatherMainOnPrefs (weatherHain) // update weerwaarde weather.postValue (ApiResponse (data = weatherMain)) init // ... addWeatherSources ()

In de Hoofdactiviteit, het weer wordt waargenomen en het resultaat wordt getoond aan de gebruiker.

 private fun initModel () // Get ViewModel viewModel = ViewModelProviders.of (this, viewModelFactory) .get (MainViewModel :: class.java) if (viewModel! = null) // observeer weerweergave Model !!. getWeather (). observeren (this @ MainActivity, Observer r -> if (r! = null) info ("Weer ontvangen op MainActivity: \ n $ r") if (! r.hasError ()) // Heeft geen errors info ("weather: $ r.data") if (r.data! = null) setUI (r.data) else // error error ("error: $ r.error") isLoading ( false) if (r.error !!. statusCode! = 0) if (r.error !!. message! = null) toast (r.error.message !!) else toast ("Er is een fout opgetreden") )

De MediatorLiveData werd ook gebruikt als basis voor een object dat oproepen naar de OpenWeatherMap-API afhandelt. Bekijk deze implementatie; het is geavanceerder dan die hierboven, en het is echt de moeite van het bestuderen waard. Als je geïnteresseerd bent, kijk dan eens naar OpenWeatherService, speciale aandacht schenken aan de Bemiddelaar klasse.

7. Conclusie

We zijn bijna aan het einde van onze verkenning van de architectuurcomponenten van Android. Inmiddels zou je genoeg moeten begrijpen om enkele krachtige apps te maken. In de volgende post zullen we verkennen Kamer, een ORM die zich omwikkelt SQLite en kan produceren Actuele gegevens resultaten. De Kamer component past perfect in deze architectuur, en het is het laatste stukje van de puzzel.

Tot ziens! En bekijk in de tussentijd een aantal van onze andere berichten over de ontwikkeling van Android-apps!