Panoramica di LiveData Componente di Android Jetpack.
LiveData
è una classe di contenitore di dati osservabili. A differenza di un normale Observable, LiveData è consapevole del ciclo di vita,
ovvero rispetta il ciclo di vita di altri componenti dell'app, come attività,
fragment o servizi. Questa consapevolezza garantisce che LiveData aggiorni solo gli osservatori dei componenti dell'app in uno stato del ciclo di vita attivo.
LiveData considera un osservatore, rappresentato dalla classe
Observer
, in stato attivo se il suo ciclo di vita è nello stato
STARTED
o
RESUMED
. LiveData invia notifiche sugli aggiornamenti solo agli osservatori attivi. Gli osservatori inattivi registrati per guardare gli oggetti LiveData
non vengono informati delle modifiche.
Puoi registrare un osservatore accoppiato a un oggetto che implementa l'interfaccia
LifecycleOwner
. Questa relazione consente di rimuovere l'osservatore quando lo stato dell'oggetto corrispondente Lifecycle
cambia in DESTROYED
.
Questo è particolarmente utile per le attività e i frammenti perché possono osservare in sicurezza gli oggetti LiveData
e non preoccuparsi di perdite: le attività e i frammenti vengono immediatamente annullati quando i relativi cicli di vita vengono distrutti.
Per saperne di più su come utilizzare LiveData, consulta Utilizzare gli oggetti LiveData.
Vantaggi dell'utilizzo di LiveData
L'utilizzo di LiveData offre i seguenti vantaggi:
- Assicurati che l'interfaccia utente corrisponda allo stato dei dati
- LiveData segue il pattern observer. LiveData invia notifiche agli oggetti
Observer
quando i dati sottostanti cambiano. Puoi unificare il codice per aggiornare l'interfaccia utente in questi oggettiObserver
. In questo modo, non devi aggiornare l'interfaccia utente ogni volta che i dati dell'app cambiano, perché lo fa l'osservatore. - Nessuna perdita di memoria
- Gli osservatori sono associati agli oggetti
Lifecycle
e si occupano della pulizia al termine del ciclo di vita associato. - Nessun arresto anomalo dovuto ad attività interrotte
- Se il ciclo di vita dell'osservatore è inattivo, ad esempio nel caso di un'attività nella pila di fondo, non riceve eventi LiveData.
- Niente più gestione manuale del ciclo di vita
- I componenti dell'interfaccia utente osservano solo i dati pertinenti e non interrompono o riprendono l'osservazione. LiveData gestisce automaticamente tutto questo, poiché è a conoscenza delle modifiche dello stato del ciclo di vita pertinenti durante l'osservazione.
- Dati sempre aggiornati
- Se un ciclo di vita diventa inattivo, riceve i dati più recenti quando diventa nuovamente attivo. Ad esempio, un'attività in background riceve i dati più recenti subito dopo essere tornata in primo piano.
- Modifiche di configurazione appropriate
- Se un'attività o un frammento viene ricreato a causa di una modifica della configurazione, come la rotazione del dispositivo, riceve immediatamente i dati più recenti disponibili.
- Condividere risorse
- Puoi estendere un oggetto
LiveData
utilizzando il pattern singleton per incapsulare i servizi di sistema in modo che possano essere condivisi nella tua app. L'oggettoLiveData
si connette al servizio di sistema una volta, quindi qualsiasi osservatore che ha bisogno della risorsa può semplicemente monitorare l'oggettoLiveData
. Per ulteriori informazioni, vedi Estendere LiveData.
Lavorare con gli oggetti LiveData
Per lavorare con gli oggetti
LiveData
:
- Crea un'istanza di
LiveData
per contenere un determinato tipo di dati. Di solito questo accade all'interno del corsoViewModel
. - Crea un oggetto
Observer
che definisce il metodoonChanged()
che controlla cosa accade quando i dati trattenuti dell'oggettoLiveData
subiscono variazioni. In genere, crei un oggettoObserver
in un controller dell'interfaccia utente, ad esempio un'attività o un frammento. Collega l'oggetto
Observer
all'oggettoLiveData
utilizzando il metodoobserve()
. Il metodoobserve()
accetta un oggettoLifecycleOwner
. In questo modo, l'oggettoObserver
viene iscritto all'oggettoLiveData
in modo da ricevere una notifica delle modifiche. In genere, l'oggettoObserver
viene collegato a un controllore dell'interfaccia utente, ad esempio un'attività o un frammento.
Quando aggiorni il valore memorizzato nell'oggetto LiveData
, vengono attivati tutti gli osservatori registrati, a condizione che LifecycleOwner
collegato sia attivo.
LiveData consente agli osservatori del controller dell'interfaccia utente di iscriversi agli aggiornamenti. Quando i dati trattenuti dall'oggetto LiveData
cambiano, l'interfaccia utente si aggiorna automaticamente in risposta.
Creare oggetti LiveData
LiveData è un wrapper che può essere utilizzato con qualsiasi dato, inclusi gli oggetti che implementano Collections
, come List
. Un oggetto LiveData
viene solitamente memorizzato all'interno di un oggetto ViewModel
e vi si accede tramite un metodo getter, come mostrato nell'esempio seguente:
Kotlin
class NameViewModel : ViewModel() { // Create a LiveData with a String val currentName: MutableLiveData<String> by lazy { MutableLiveData<String>() } // Rest of the ViewModel... }
Java
public class NameViewModel extends ViewModel { // Create a LiveData with a String private MutableLiveData<String> currentName; public MutableLiveData<String> getCurrentName() { if (currentName == null) { currentName = new MutableLiveData<String>(); } return currentName; } // Rest of the ViewModel... }
Inizialmente, i dati in un oggetto LiveData
non sono impostati.
Per saperne di più sui vantaggi e sull'utilizzo della classe ViewModel
, consulta la guida a ViewModel.
Osserva gli oggetti LiveData
Nella maggior parte dei casi, il metodo onCreate()
di un componente dell'app è il punto di partenza giusto per iniziare a osservare un oggetto
LiveData
per i seguenti motivi:
- Per assicurarti che il sistema non effettui chiamate ridondanti dal metodo
onResume()
di un'attività o di un frammento. - Per assicurarti che l'attività o il frammento dispongano di dati che possono essere visualizzati non appena diventano attivi. Non appena un componente dell'app è nello stato
STARTED
, riceve il valore più recente dagli oggettiLiveData
che sta osservando. Ciò si verifica solo se è stato impostato l'oggettoLiveData
da osservare.
In genere, LiveData invia aggiornamenti solo quando i dati cambiano e solo agli osservatori attivi. Un'eccezione a questo comportamento è che gli osservatori ricevono anche un update quando passano da uno stato inattivo a uno attivo. Inoltre, se l'osservatore passa da inattivo ad attivo una seconda volta, riceve un aggiornamento solo se il valore è cambiato dall'ultima volta che è diventato attivo.
Il seguente codice di esempio illustra come iniziare a osservare un oggetto LiveData
:
Kotlin
class NameActivity : AppCompatActivity() { // Use the 'by viewModels()' Kotlin property delegate // from the activity-ktx artifact private val model: NameViewModel by viewModels() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // Other code to setup the activity... // Create the observer which updates the UI. val nameObserver = Observer<String> { newName -> // Update the UI, in this case, a TextView. nameTextView.text = newName } // Observe the LiveData, passing in this activity as the LifecycleOwner and the observer. model.currentName.observe(this, nameObserver) } }
Java
public class NameActivity extends AppCompatActivity { private NameViewModel model; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Other code to setup the activity... // Get the ViewModel. model = new ViewModelProvider(this).get(NameViewModel.class); // Create the observer which updates the UI. final Observer<String> nameObserver = new Observer<String>() { @Override public void onChanged(@Nullable final String newName) { // Update the UI, in this case, a TextView. nameTextView.setText(newName); } }; // Observe the LiveData, passing in this activity as the LifecycleOwner and the observer. model.getCurrentName().observe(this, nameObserver); } }
Dopo che è stato chiamato observe()
con nameObserver
passato come parametro, viene invocato immediatamente onChanged()
fornendo il valore più recente memorizzato in mCurrentName
.
Se l'oggetto LiveData
non ha impostato un valore in mCurrentName
, onChanged()
non viene chiamato.
Aggiornare gli oggetti LiveData
LiveData non ha metodi disponibili pubblicamente per aggiornare i dati archiviati. La classe
MutableLiveData
espone i metodi
setValue(T)
e
postValue(T)
pubblicamente e devi utilizzarli se devi modificare il valore memorizzato in un oggetto
LiveData
. In genere,
MutableLiveData
viene utilizzato in
ViewModel
e poi
ViewModel
espone agli osservatori solo oggetti LiveData
immutabili.
Dopo aver configurato la relazione di osservatore, puoi aggiornare il valore
dell'oggetto LiveData
, come illustrato nell'esempio seguente, che attiva tutti gli osservatori quando l'utente tocca un pulsante:
Kotlin
button.setOnClickListener { val anotherName = "John Doe" model.currentName.setValue(anotherName) }
Java
button.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { String anotherName = "John Doe"; model.getCurrentName().setValue(anotherName); } });
La chiamata a setValue(T)
nell'esempio fa sì che gli osservatori chiamino i propri metodi
onChanged()
con il valore John Doe
. L'esempio mostra la pressione di un pulsante, ma setValue()
o postValue()
potrebbero essere chiamati per aggiornare mName
per una serie di motivi, ad esempio in risposta a una richiesta di rete o al completamento del caricamento di un database. In tutti i casi, la chiamata a setValue()
o postValue()
attiva gli osservatori e aggiorna l'interfaccia utente.
Utilizzare LiveData con Room
La libreria di persistenza Room supporta le query osservabili, che restituiscono oggetti LiveData
.
Le query osservabili vengono scritte nell'ambito di un oggetto di accesso al database (DAO).
Room genera tutto il codice necessario per aggiornare l'oggetto LiveData
quando viene aggiornato un database. Il codice generato esegue la query in modo asincrono su un THREAD in background, se necessario. Questo pattern è utile per mantenere sincronizzati i dati visualizzati in un'interfaccia utente con i dati archiviati in un database. Puoi scoprire di più su Room e sulle DAO nella guida alla libreria di persistenza Room.
Utilizzare le coroutine con LiveData
LiveData
include il supporto per le coroutine Kotlin. Per ulteriori informazioni, consulta
Utilizzare le coroutine Kotlin con i componenti dell'architettura Android.
LiveData nell'architettura di un'app
LiveData
è consapevole del ciclo di vita, poiché segue il ciclo di vita di entità come attività e frammenti. Utilizza LiveData
per comunicare tra questi proprietari del ciclo di vita e altri oggetti con una durata diversa, ad esempio gli oggetti ViewModel
.
La responsabilità principale di ViewModel
è caricare e gestire i dati relativi all'interfaccia utente, il che lo rende un'ottima scelta per contenere oggetti LiveData
. Crea oggetti LiveData
in ViewModel
e utilizzali per esporre lo stato al livello UI.
Le attività e i frammenti non devono contenere istanze LiveData
perché il loro ruolo è visualizzare i dati, non mantenere lo stato. Inoltre, mantenere le attività e i frammenti liberi
dall'archiviazione dei dati semplifica la scrittura dei test di unità.
Potrebbe essere allettante utilizzare oggetti LiveData
nella classe del livello dati, ma
LiveData
non è progettato per gestire stream di dati asincroni. Anche se
puoi utilizzare le trasformazioni LiveData
e MediatorLiveData
per ottenere questo risultato, questo approccio presenta degli svantaggi: la capacità di combinare gli stream
di dati è molto limitata e tutti gli oggetti LiveData
(inclusi quelli creati
tramite trasformazioni) vengono osservati nel thread principale. Il codice seguente è un
esempio di come l'attesa di un LiveData
in Repository
può bloccare il
thread principale:
Kotlin
class UserRepository { // DON'T DO THIS! LiveData objects should not live in the repository. fun getUsers(): LiveData<List<User>> { ... } fun getNewPremiumUsers(): LiveData<List<User>> { return getUsers().map { users -> // This is an expensive call being made on the main thread and may // cause noticeable jank in the UI! users .filter { user -> user.isPremium } .filter { user -> val lastSyncedTime = dao.getLastSyncedTime() user.timeCreated > lastSyncedTime } } }
Java
class UserRepository { // DON'T DO THIS! LiveData objects should not live in the repository. LiveData<List<User>> getUsers() { ... } LiveData<List<User>> getNewPremiumUsers() { return Transformations.map(getUsers(), // This is an expensive call being made on the main thread and may cause // noticeable jank in the UI! users -> users.stream() .filter(User::isPremium) .filter(user -> user.getTimeCreated() > dao.getLastSyncedTime()) .collect(Collectors.toList())); } }
Se devi utilizzare stream di dati in altri livelli della tua app, ti consigliamo di utilizzare Kotlin Flows e poi di convertirli in LiveData
in ViewModel
utilizzando asLiveData()
.
Scopri di più sull'utilizzo di Kotlin Flow
con LiveData
in questo codelab.
Per le basi di codice create con Java, ti consigliamo di utilizzare gli esecutori insieme ai callback o a RxJava
.
Estendere LiveData
LiveData considera un osservatore in stato attivo se il suo ciclo di vita è negli stati STARTED
o RESUMED
. Il seguente esempio di codice illustra come estendere la classe
LiveData
:
Kotlin
class StockLiveData(symbol: String) : LiveData<BigDecimal>() { private val stockManager = StockManager(symbol) private val listener = { price: BigDecimal -> value = price } override fun onActive() { stockManager.requestPriceUpdates(listener) } override fun onInactive() { stockManager.removeUpdates(listener) } }
Java
public class StockLiveData extends LiveData<BigDecimal> { private StockManager stockManager; private SimplePriceListener listener = new SimplePriceListener() { @Override public void onPriceChanged(BigDecimal price) { setValue(price); } }; public StockLiveData(String symbol) { stockManager = new StockManager(symbol); } @Override protected void onActive() { stockManager.requestPriceUpdates(listener); } @Override protected void onInactive() { stockManager.removeUpdates(listener); } }
L'implementazione dell'ascoltatore dei prezzi in questo esempio include i seguenti metodi importanti:
- Il metodo
onActive()
viene chiamato quando l'oggettoLiveData
ha un osservatore attivo. Ciò significa che devi iniziare a osservare gli aggiornamenti del prezzo delle azioni da questo metodo. - Il metodo
onInactive()
viene chiamato quando l'oggettoLiveData
non ha osservatori attivi. Poiché non ci sono osservatori in ascolto, non c'è motivo di rimanere connessi al servizioStockManager
. - Il metodo
setValue(T)
aggiorna il valore dell'istanzaLiveData
e notifica la modifica a tutti gli osservatori attivi.
Puoi utilizzare la classe StockLiveData
come segue:
Kotlin
public class MyFragment : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) val myPriceListener: LiveData<BigDecimal> = ... myPriceListener.observe(viewLifecycleOwner, Observer<BigDecimal> { price: BigDecimal? -> // Update the UI. }) } }
Java
public class MyFragment extends Fragment { @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); LiveData<BigDecimal> myPriceListener = ...; myPriceListener.observe(getViewLifecycleOwner(), price -> { // Update the UI. }); } }
Il metodo
observe()
trasmette il LifecycleOwner
associato alla visualizzazione del frammento come primo argomento. In questo modo, questo osservatore è associato all'oggetto Lifecycle
associato al proprietario, il che significa che:
- Se l'oggetto
Lifecycle
non è in uno stato attivo, l'osservatore non viene chiamato anche se il valore cambia. - Dopo l'eliminazione dell'oggetto
Lifecycle
, l'osservatore viene rimosso automaticamente.
Il fatto che gli oggetti LiveData
siano consapevoli del ciclo di vita significa che puoi condividerli tra più attività, frammenti e servizi. Per mantenere semplice l'esempio, puoi implementare la classe LiveData
come singleton nel seguente modo:
Kotlin
class StockLiveData(symbol: String) : LiveData<BigDecimal>() { private val stockManager: StockManager = StockManager(symbol) private val listener = { price: BigDecimal -> value = price } override fun onActive() { stockManager.requestPriceUpdates(listener) } override fun onInactive() { stockManager.removeUpdates(listener) } companion object { private lateinit var sInstance: StockLiveData @MainThread fun get(symbol: String): StockLiveData { sInstance = if (::sInstance.isInitialized) sInstance else StockLiveData(symbol) return sInstance } } }
Java
public class StockLiveData extends LiveData<BigDecimal> { private static StockLiveData sInstance; private StockManager stockManager; private SimplePriceListener listener = new SimplePriceListener() { @Override public void onPriceChanged(BigDecimal price) { setValue(price); } }; @MainThread public static StockLiveData get(String symbol) { if (sInstance == null) { sInstance = new StockLiveData(symbol); } return sInstance; } private StockLiveData(String symbol) { stockManager = new StockManager(symbol); } @Override protected void onActive() { stockManager.requestPriceUpdates(listener); } @Override protected void onInactive() { stockManager.removeUpdates(listener); } }
Puoi utilizzarlo nel frammento come segue:
Kotlin
class MyFragment : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) StockLiveData.get(symbol).observe(viewLifecycleOwner, Observer<BigDecimal> { price: BigDecimal? -> // Update the UI. }) }
Java
public class MyFragment extends Fragment { @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); StockLiveData.get(symbol).observe(getViewLifecycleOwner(), price -> { // Update the UI. }); } }
Più frammenti e attività possono osservare l'istanza MyPriceListener
.
LiveData si connette al servizio di sistema solo se uno o più di questi sono visibili
e attivi.
Trasformare LiveData
Potresti voler apportare modifiche al valore memorizzato in un oggetto
LiveData
prima di
inviarlo agli osservatori oppure potresti dover restituire un'istanza distinta di
LiveData
in base al valore di un altro. Il pacchetto
Lifecycle
fornisce la classe
Transformations
che include metodi di assistenza che supportano questi scenari.
Transformations.map()
- Applica una funzione al valore memorizzato nell'oggetto
LiveData
e propaga il risultato a valle.
Kotlin
val userLiveData: LiveData<User> = UserLiveData() val userName: LiveData<String> = userLiveData.map { user -> "${user.name} ${user.lastName}" }
Java
LiveData<User> userLiveData = ...; LiveData<String> userName = Transformations.map(userLiveData, user -> { user.name + " " + user.lastName });
Transformations.switchMap()
- Analogamente a
map()
, applica una funzione al valore memorizzato nell'oggettoLiveData
e scompatta e invia il risultato a valle. La funzione passata aswitchMap()
deve restituire un oggettoLiveData
, come illustrato dall'esempio seguente:
Kotlin
private fun getUser(id: String): LiveData<User> { ... } val userId: LiveData<String> = ... val user = userId.switchMap { id -> getUser(id) }
Java
private LiveData<User> getUser(String id) { ...; } LiveData<String> userId = ...; LiveData<User> user = Transformations.switchMap(userId, id -> getUser(id) );
Puoi utilizzare i metodi di trasformazione per trasmettere informazioni durante il ciclo di vita dell'osservatore. Le trasformazioni non vengono calcolate a meno che un osservatore non stia osservando
l'oggetto LiveData
restituito. Poiché le trasformazioni vengono calcolate in modo lazy, il comportamento relativo al ciclo di vita viene trasmesso implicitamente senza richiedere chiamate o dipendenze esplicite aggiuntive.
Se ritieni di aver bisogno di un oggetto Lifecycle
all'interno di un oggetto
ViewModel
, una
trasformazione è probabilmente una soluzione migliore. Ad esempio, supponiamo di avere un componente dell'interfaccia utente che accetta un indirizzo e restituisce il codice postale corrispondente. Puoi implementare il modello ViewModel
ingenuo per questo componente come illustrato dal seguente codice di esempio:
Kotlin
class MyViewModel(private val repository: PostalCodeRepository) : ViewModel() { private fun getPostalCode(address: String): LiveData<String> { // DON'T DO THIS return repository.getPostCode(address) } }
Java
class MyViewModel extends ViewModel { private final PostalCodeRepository repository; public MyViewModel(PostalCodeRepository repository) { this.repository = repository; } private LiveData<String> getPostalCode(String address) { // DON'T DO THIS return repository.getPostCode(address); } }
Il componente dell'interfaccia utente deve quindi annullare la registrazione dall'oggetto LiveData
precedente
e registrarsi alla nuova istanza ogni volta che chiama getPostalCode()
. Inoltre, se il componente dell'interfaccia utente viene ricreato, viene attivata un'altra chiamata al metodo repository.getPostCode()
anziché utilizzare il risultato della chiamata precedente.
In alternativa, puoi implementare la ricerca del codice postale come trasformazione dell'input dell'indirizzo, come mostrato nell'esempio seguente:
Kotlin
class MyViewModel(private val repository: PostalCodeRepository) : ViewModel() { private val addressInput = MutableLiveData<String>() val postalCode: LiveData<String> = addressInput.switchMap { address -> repository.getPostCode(address) } private fun setInput(address: String) { addressInput.value = address } }
Java
class MyViewModel extends ViewModel { private final PostalCodeRepository repository; private final MutableLiveData<String> addressInput = new MutableLiveData(); public final LiveData<String> postalCode = Transformations.switchMap(addressInput, (address) -> { return repository.getPostCode(address); }); public MyViewModel(PostalCodeRepository repository) { this.repository = repository } private void setInput(String address) { addressInput.setValue(address); } }
In questo caso, il campo postalCode
è definito come una trasformazione del
addressInput
. Se la tua app ha un osservatore attivo associato al campopostalCode
, il valore del campo viene ricalcolato e recuperato ogni volta chepostalCode
cambia.addressInput
Questo meccanismo consente ai livelli inferiori dell'app di creare oggetti LiveData
che vengono calcolati in modo lazy su richiesta. Un oggetto ViewModel
può ottenere facilmente riferimenti agli oggetti LiveData
e poi definire regole di trasformazione su di essi.
Creare nuove trasformazioni
Esistono una dozzina di trasformazioni specifiche diverse che potrebbero essere utili nella tua app, ma non sono fornite per impostazione predefinita. Per implementare la tua trasformazione, puoi utilizzare la classe MediatorLiveData
, che ascolta altri oggetti LiveData
e elabora gli eventi emessi. MediatorLiveData
propaga correttamente il suo stato all'oggetto LiveData
di origine. Per scoprire di più su questo pattern, consulta la documentazione di riferimento della classe Transformations
.
Unire più origini LiveData
MediatorLiveData
è
una sottoclasse di LiveData
che consente di fondere più origini LiveData. Gli osservatori degli oggetti MediatorLiveData
vengono attivati ogni volta che uno degli oggetti di origine LiveData
originali cambia.
Ad esempio, se nella tua UI hai un oggetto LiveData
che può essere aggiornato da un database locale o da una rete, puoi aggiungere le seguenti origini all'oggetto LiveData
:MediatorLiveData
- Un oggetto
LiveData
associato ai dati archiviati nel database. - Un oggetto
LiveData
associato ai dati a cui è stato eseguito l'accesso dalla rete.
La tua attività deve solo osservare l'oggetto MediatorLiveData
per ricevere aggiornamenti da entrambe le origini. Per un esempio dettagliato, consulta la sezione Appendice: esposizione dello stato della rete della Guida all'architettura delle app.
Risorse aggiuntive
Per scoprire di più sul corso
LiveData
, consulta
le seguenti risorse.
Campioni
- Sunflower, un'app di dimostrazione che mostra le best practice con i componenti dell'architettura
Codelab
- Android Room with a View (Java) (Kotlin)
- Scopri le coroutine avanzate con Kotlin Flow e LiveData
Blog
- ViewModel e LiveData: pattern e antipattern
- LiveData oltre ViewModel: pattern reattivi che utilizzano Transformations e MediatorLiveData
- LiveData con SnackBar, navigazione e altri eventi (caso SingleLiveEvent)
Video
Consigliati per te
- Nota: il testo del link viene visualizzato quando JavaScript è disattivato
- Utilizzare le coroutine Kotlin con componenti attenti al ciclo di vita
- Gestione dei cicli di vita con componenti consapevoli del ciclo di vita
- Testare l'implementazione della paginazione