Componenti di architettura Android LiveData

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.

1. Il componente LiveData

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:

  • impedisce la perdita di memoria quando l'osservatore è legato a a Ciclo vitale
  • previene arresti anomali a causa di attività interrotte 
  • i dati sono sempre aggiornati
  • gestisce senza problemi le modifiche di configurazione
  • rende possibile condividere le risorse
  • gestisce automaticamente il ciclo di vita

2. Osservando 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 LiveDataLo 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)

3. Implementazione 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.

Esempio di implementazione

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)

4. Il MutableLiveData Helper Class

Il 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.

5. Trasformazioni di 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.

Trasformazioni della mappa

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)

Trasformazioni di SwitchMap

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

  1. Nostro Transformation.switchMap osserva LocationLiveData i cambiamenti.
  2. Il LocationLiveData il valore aggiornato viene utilizzato per chiamare il MainRepository per ottenere il tempo per la posizione specificata.
  3. Il repository chiama il OpenWeatherService che produce a LiveData> di conseguenza.
  4. Quindi, il ritorno 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.

6. La 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 LiveDatas in una volta.

Per osservare a LiveData, chiamata addsource (LiveData, Osservatore), facendo reagire l'osservatore al 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.

7. Conclusione

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!