Abbiamo già parlato molto della nostra serie di componenti per l'architettura Android. Abbiamo iniziato a parlare dell'idea alla base della nuova architettura e dei componenti chiave presentati a I / O di Google. Nel secondo post, abbiamo iniziato la nostra profonda esplorazione dei componenti principali del pacchetto, osservando da vicino Ciclo vitale
e LiveModel
componenti. In questo post, continueremo a esplorare i componenti di architettura, stavolta analizzando il fantastico LiveData
componente.
Suppongo che tu abbia familiarità con i concetti e i componenti trattati nelle ultime esercitazioni, come Ciclo vitale
, LifecycleOwner
, e LifecycleObserver
. Se non lo sei, dai un'occhiata al primo post di questa serie, in cui discuto l'idea generale alla base della nuova architettura Android e dei suoi componenti.
Continueremo a costruire sull'applicazione di esempio che abbiamo iniziato nell'ultima parte di questa serie. Puoi trovarlo nel tutorial repo GitHub.
LiveData
è un titolare di dati. È in grado di essere osservato, può contenere qualsiasi tipo di dati e, inoltre, è anche questo ciclo di vita consapevoli. In termini pratici, LiveData
può essere configurato per inviare solo aggiornamenti di dati quando il suo osservatore è attivo. Grazie alla sua consapevolezza del ciclo di vita, quando osservato da a LifecycleOwner
il LiveData
componente invierà aggiornamenti solo quando l'osservatore Ciclo vitale
è ancora attivo e rimuoverà la relazione osservata una volta l'osservatore Ciclo vitale
è distrutto.
Il LiveData
componente ha molte caratteristiche interessanti:
Ciclo vitale
LiveData
UN LiveData
componente invia gli aggiornamenti dei dati solo quando il suo osservatore è "attivo". Quando osservato da a LifecycleOwner
, il LiveData
componente considera l'osservatore attivo solo durante il suo Ciclo vitale
è negli stati INIZIATO
o RIPRESO
, altrimenti considererà l'osservatore come inattivo. Durante lo stato inattivo dell'osservatore, LiveData
interromperà il flusso di aggiornamento dei dati, fino a quando il suo osservatore non sarà di nuovo attivo. Se l'osservatore è distrutto, LiveData
rimuoverà il suo riferimento all'osservatore.
Per raggiungere questo comportamento, LiveData
crea una stretta relazione con quella dell'osservatore Ciclo vitale
quando osservato da a LifecycleOwner
. Questa capacità rende più facile evitare perdite di memoria durante l'osservazione LiveData
. Tuttavia, se il processo di osservazione è chiamato senza a LifecycleOwner
, il LiveData
componente non reagirà a Ciclo vitale
stato e lo stato dell'osservatore deve essere gestito manualmente.
Per osservare a LiveData
, chiamata osservare (LifecycleOwner, Observer
o observeForever (Observer
.
osservare (LifecycleOwner, Observer)
: Questo è il modo standard per osservare a LiveData
. Lega l'osservatore a a Ciclo vitale
, mutevole LiveData
Lo stato attivo e inattivo secondo il LifecycleOwner
stato attuale.observeForever (Observer)
: Questo metodo non usa a LifecycleOwner
, così la LiveData
non sarà in grado di rispondere a Ciclo vitale
eventi. Quando si utilizza questo metodo, è molto importante chiamare removeObserver (Observer)
, in caso contrario, l'osservatore potrebbe non essere garbage collection, causando una perdita di memoria.// chiamato da LifecycleOwner location.observe (// LifecycleOwner this, // creazione di un Observer Observer location -> info ("location: $ location !!. latitude, $ location.longitude")) // Osservazione senza LifecycleOwner val observer = Observer location -> info ("location: $ location !!. Latitude, $ location.longitude")) location.observeForever (observer) // quando observer senza LivecyleOwner // è necessario rimuovere gli osservatori in qualche punto location.removeObserver (observer)
LiveData
Il tipo generico nel LiveData
class definisce il tipo di dati che verranno mantenuti. Per esempio, LiveData
detiene Posizione
dati. O LiveData
tiene a Stringa
.
Esistono due metodi principali che devono essere considerati nell'implementazione del componente: onActive ()
e onInactive ()
. Entrambi i metodi reagiscono allo stato dell'osservatore.
Nel nostro progetto di esempio abbiamo usato un sacco di LiveData
oggetti, ma ne abbiamo implementato solo uno: LocationLiveData
. La classe si occupa del GPS Posizione
, passando la posizione corrente solo per un osservatore attivo. Si noti che la classe aggiorna il suo valore su onLocationChanged
metodo, passando all'attuale osservatore attivo, aggiornato Posizione
dati.
class LocationLiveData @Inject constructor (context: Context): LiveData(), LocationListener, AnkoLogger private val locationManager: LocationManager = context.getSystemService (Context.LOCATION_SERVICE) come LocationManager @SuppressLint ("MissingPermission") sovrascrive fun onInactive () info ("onInactive") locationManager.removeUpdates (this) @ SuppressLint ("MissingPermission") fun refreshLocation () info ("refreshLocation") locationManager.requestSingleUpdate (LocationManager.GPS_PROVIDER, this, null)
MutableLiveData
Helper ClassIl MutableLiveData
è una classe di supporto che si estende LiveData
, ed espone postValue
e valore impostato
metodi. A parte questo, si comporta esattamente come il suo genitore. Per usarlo, definire il tipo di dati che contiene, come MutableLiveData
tenere un Stringa
, e creare una nuova istanza.
val myData: MutableLiveData= MutableLiveData ()
Per inviare aggiornamenti a un osservatore, chiama postValue
o valore impostato
. Il comportamento di questi metodi è abbastanza simile; però, valore impostato
imposterà direttamente un nuovo valore e può essere chiamato solo dal thread principale, mentre postValue
crea una nuova attività sul thread principale per impostare il nuovo valore e può essere chiamata da un thread in background.
fun updateData () // deve essere chiamato dal thread principale myData.value = api.getUpdate fun updateDataFromBG () // può essere chiamato da bg thread myData.postValue (api.getUpdate)
È importante considerare che, dal momento che il postValue
metodo crea un nuovo Compito
e post sul thread principale, sarà più lento delle chiamate dirette a valore impostato
.
LiveData
Alla fine, dovrai cambiare a LiveData
e propagare il suo nuovo valore al suo osservatore. O forse hai bisogno di creare una reazione a catena tra due LiveData
oggetti, facendo reagire ai cambiamenti su un altro. Per gestire entrambe le situazioni, puoi usare il trasformazioni
classe.
Transformations.map
applica una funzione su a LiveData
istanza e invia il risultato ai suoi osservatori, dandoti la possibilità di manipolare il valore dei dati.
È molto facile da implementare Transformations.map
. Tutto quello che devi fare è fornire un LiveData
da osservare e a Funzione
essere chiamato quando osservato LiveData
cambiamenti, ricordando che il Funzione
deve restituire il nuovo valore del trasformato LiveData
.
Supponiamo che tu abbia un LiveData
che deve chiamare un'API quando a Stringa
valore, come un campo di ricerca, cambia.
// LiveData che chiama api // quando 'searchLive' cambia il suo valore val apiLive: LiveData= Transformations.map (searchLive, query -> return @ map api.call (query)) // Ogni volta che 'searchLive' ha // il suo valore aggiornato, chiamerà // 'apiLive' Transformation.map fun updateSearch (query: String) searchLive.postValue (query)
Il Transformations.switchMap
è abbastanza simile a Transformations.map
, ma deve restituire a LiveData
oggetto come risultato. È un po 'più difficile da usare, ma ti permette di costruire potenti reazioni a catena.
Nel nostro progetto, abbiamo usato Transformations.switchMap
creare una reazione tra LocationLiveData
e ApiResponse
.
Transformation.switchMap
osserva LocationLiveData
i cambiamenti.LocationLiveData
il valore aggiornato viene utilizzato per chiamare il MainRepository
per ottenere il tempo per la posizione specificata.OpenWeatherService
che produce a LiveData>
di conseguenza.LiveData
è osservato da a MediatorLiveData
, che è responsabile della modifica del valore ricevuto e dell'aggiornamento del tempo esposto nel livello vista.class MainViewModel @Inject constructor (private val repository: MainRepository): ViewModel (), AnkoLogger // Posizione private val location: LocationLiveData = repository.locationLiveDa () private var weatherByLocationResponse: LiveData> = Transformations.switchMap (posizione, l -> informazioni ("weatherByLocation: \ nlocation: $ l") return @ switchMap repository.getWeatherByLocation (l))
Fai attenzione alle operazioni che richiedono tempo nel tuo LiveData
trasformazioni, comunque. Nel codice sopra, entrambi trasformazioni
i metodi vengono eseguiti sul thread principale.
MediatorLiveData
MediatorLiveData
è un tipo più avanzato di LiveData
. Ha capacità molto simili a quelle del trasformazioni
classe: è in grado di reagire agli altri LiveData
oggetti, chiamando a Funzione
quando i dati osservati cambiano. Tuttavia, ha molti vantaggi rispetto a trasformazioni
, dal momento che non ha bisogno di essere eseguito sul thread principale e può osservare multipli LiveData
s in una volta.
Per osservare a LiveData
, chiamata addsource (LiveData
, facendo reagire l'osservatore al , Osservatore)onChanged
metodo dal dato LiveData
. Per fermare l'osservazione, chiama removeSource (LiveData
.)
val mediatorData: MediatorLiveData= MediatorLiveData () mediatorData.addSource (dataA, valore -> // reagisce alle informazioni sul valore ("il mio valore $ valore")) mediatorData.addSource (dataB, valore -> // reagisce alle informazioni sul valore ("il mio valore $ value ") // possiamo rimuovere l'origine una volta utilizzato mediatorData.removeSource (dataB))
Nel nostro progetto, i dati osservati dal livello vista che contiene il tempo da esporre sono a MediatorLiveData
. Il componente ne osserva altri due LiveData
oggetti: weatherByLocationResponse
, che riceve gli aggiornamenti meteo per posizione, e weatherByCityResponse
, che riceve gli aggiornamenti meteo per nome della città. Ogni volta che questi oggetti vengono aggiornati, weatherByCityResponse
aggiornerà il livello di visualizzazione con il tempo corrente richiesto.
Nel MainViewModel
, osserviamo il LiveData
e fornire il tempo metereologico
oggetto da vedere.
class MainViewModel @Inject constructor (private val repository: MainRepository): ViewModel (), AnkoLogger // ... // Valore osservato da View. // Trasforma WeatherResponse in Weather Weather. private val weather: MediatorLiveData> = MediatorLiveData () // recupera il tempo LiveData fun getWeather (): LiveData > info ("getWeather") return weather divertimento privato addWeatherSources () info ("addWeatherSources") weather.addSource (weatherByCityResponse, w -> informazioni ("addWeatherSources: \ nweather: $ w !!. dati !! ") updateWeather (w.data !!)) weather.addSource (weatherByLocationResponse, w -> informazioni (" addWeatherSources: weatherByLocationResponse: \ n $ w !!. dati !! ") updateWeather (w.data! !)) divertimento divertente updateWeather (w: WeatherResponse) info ("updateWeather") // ottenere tempo da oggi val weatherMain = WeatherMain.factory (w) // salva sulle preferenze condivise repository.saveWeatherMainOnPrefs (weatherMain) // update valore meteo weather.postValue (ApiResponse (data = weatherMain)) init // ... addWeatherSources ()
Nel Attività principale
, il tempo è osservato e il suo risultato è mostrato all'utente.
private fun initModel () // Visualizza ViewModel viewModel = ViewModelProviders.of (this, viewModelFactory) .get (MainViewModel :: class.java) if (viewModel! = null) // osserva weather viewModel !!. getWeather (). osservare (questo @ MainActivity, Observer r -> if (r! = null) info ("Meteo ricevuto su MainActivity: \ n $ r") if (! r.hasError ()) // Non ha alcun informazioni sugli errori ("weather: $ r.data") if (r.data! = null) setUI (r.data) else // errore error ("error: $ r.error") isLoading ( false) if (r.error !!. statusCode! = 0) if (r.error !!. message! = null) toast (r.error.message !!) else toast ("Si è verificato un errore") )
Il MediatorLiveData
è stato utilizzato anche come base per un oggetto che gestisce le chiamate all'API OpenWeatherMap. Dai un'occhiata a questa implementazione; è più avanzato di quelli sopra, e vale davvero la pena studiare. Se sei interessato, dai un'occhiata OpenWeatherService
, prestando particolare attenzione al Mediatore
classe.
Siamo quasi alla fine della nostra esplorazione dei componenti di architettura di Android. Ormai, dovresti capire abbastanza per creare alcune potenti app. Nel prossimo post, esploreremo Camera
, un ORM che avvolge SQLite
e può produrre LiveData
risultati. Il Camera
componente si inserisce perfettamente in questa architettura, ed è il pezzo finale del puzzle.
A presto! E nel frattempo, dai uno sguardo ad alcuni dei nostri altri post sullo sviluppo di app per Android!