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.
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:
Levenscyclus
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 gegevens
actieve 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)
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.
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)
MutableLiveData
Helper ClassDe 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
.
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.
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)
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
.
Transformation.switchMap
observeert LocationLiveData
veranderingen.LocationLiveData
bijgewerkte waarde wordt gebruikt om de MainRepository
om het weer voor de opgegeven locatie te krijgen.OpenWeatherService
dat produceert een Actuele gegevens>
als gevolg.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.
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 gegevens
s tegelijk.
Om een te observeren Actuele gegevens
, telefoontje addsource (LiveData
, de waarnemer laten reageren op de , Waarnemer)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.
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!