Panoramica di LiveData Componente di Android Jetpack.
LiveData
è una classe
osservabile del titolare di dati. A differenza di un'app osservabile standard, LiveData è sensibile al ciclo di vita, ovvero rispetta il ciclo di vita di altri componenti dell'app, come attività, frammenti o servizi. Questa consapevolezza garantisce che LiveData aggiorni solo gli osservatori dei componenti dell'app in uno stato attivo del ciclo di vita.
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 gli oggetti LiveData
non ricevono notifiche relative alle 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 Lifecycle
corrispondente diventa DESTROYED
.
Ciò è particolarmente utile per le attività e i frammenti perché possono osservare
in sicurezza gli oggetti LiveData
senza preoccuparsi di perdite: le attività e i frammenti vengono annullati
immediatamente quando i loro cicli di vita vengono eliminati.
Per ulteriori informazioni su come utilizzare LiveData, vedi Utilizzare gli oggetti LiveData.
I vantaggi dell'utilizzo di LiveData
L'utilizzo di LiveData offre i seguenti vantaggi:
- Assicura che la UI corrisponda allo stato dei dati
- LiveData segue il modello di osservazione. LiveData invia una notifica agli oggetti
Observer
in caso di modifiche ai dati sottostanti. Puoi consolidare il codice per aggiornare l'interfaccia utente in questiObserver
oggetti. In questo modo, non è necessario aggiornare l'interfaccia utente ogni volta che i dati dell'app cambiano, perché l'osservatore lo fa per te. - Nessuna perdita di memoria
- Quando il loro ciclo di vita associato viene distrutto, gli osservatori sono vincolati agli oggetti
Lifecycle
e si puliscono. - Nessun arresto anomalo a causa di attività interrotte
- Se il ciclo di vita dell'osservatore non è attivo, come nel caso di un'attività nello stack di back, l'osservatore non riceve alcun evento LiveData.
- Niente più gestione manuale del ciclo di vita
- I componenti UI si limitano a osservare i dati pertinenti e non interrompono o riprendono l'osservazione. LiveData gestisce automaticamente tutto questo perché, durante l'osservazione, è a conoscenza delle modifiche pertinenti allo stato del ciclo di vita.
- Dati sempre aggiornati
- Se un ciclo di vita diventa inattivo, riceve i dati più recenti quando diventa di nuovo attivo. Ad esempio, un'attività in background riceve gli ultimi dati subito dopo essere tornata in primo piano.
- Modifiche corrette alla configurazione
- Se un'attività o un frammento vengono ricreati a causa di una modifica alla configurazione, come la rotazione del dispositivo, riceve immediatamente i dati più recenti disponibili.
- Condivisione delle risorse
- Puoi estendere un oggetto
LiveData
utilizzando il pattern singleton per includere i servizi di sistema in modo che possano essere condivisi nella tua app. L'oggettoLiveData
si connette una volta al servizio di sistema e qualsiasi osservatore che necessita della risorsa può semplicemente controllare l'oggettoLiveData
. Per ulteriori informazioni, vedi Extend LiveData.
Utilizzo degli oggetti LiveData
Segui questi passaggi per lavorare con gli oggetti LiveData
:
- Crea un'istanza di
LiveData
per contenere un determinato tipo di dati. Di solito, questa operazione viene eseguita all'interno del corsoViewModel
. - Crea un oggetto
Observer
che definisce il metodoonChanged()
, che controlla cosa succede quando i dati bloccati dell'oggettoLiveData
cambiano. Solitamente si crea un oggettoObserver
in un controller UI, ad esempio un'attività o un frammento. Associa l'oggetto
Observer
all'oggettoLiveData
utilizzando il metodoobserve()
. Il metodoobserve()
accetta un oggettoLifecycleOwner
. Sottoscrive l'oggettoObserver
all'oggettoLiveData
per ricevere una notifica sulle modifiche. In genere l'oggettoObserver
viene collegato in un controller UI, ad esempio un'attività o un frammento.
Quando aggiorni il valore archiviato nell'oggetto LiveData
, vengono attivati tutti gli osservatori registrati purché l'elemento LifecycleOwner
collegato sia nello stato attivo.
LiveData consente agli osservatori del controller UI di iscriversi agli aggiornamenti. Quando i dati conservati dall'oggetto LiveData
cambiano, l'interfaccia utente si aggiorna automaticamente in risposta.
Creazione di oggetti LiveData
LiveData è un wrapper che può essere utilizzato con qualsiasi dato, inclusi gli oggetti che implementano Collections
, come List
. Un oggetto LiveData
è solitamente archiviato all'interno di un oggetto ViewModel
ed è accessibile tramite un metodo getter, come dimostrato 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 ulteriori informazioni sui vantaggi e sull'utilizzo della classe ViewModel
, consulta la guida di ViewModel.
Osservazione degli oggetti LiveData
Nella maggior parte dei casi, il metodo onCreate()
di un componente dell'app è il posto giusto per iniziare a osservare un oggetto LiveData
per i seguenti motivi:
- Per garantire che il sistema non effettui chiamate ridondanti dal metodo
onResume()
di un'attività o di un frammento. - Per garantire che l'attività o il frammento contenga 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. Questo si verifica solo se l'oggettoLiveData
da osservare è stato impostato.
In genere, LiveData fornisce aggiornamenti solo quando i dati cambiano e solo agli osservatori attivi. Un'eccezione a questo comportamento è che gli osservatori ricevono un aggiornamento anche 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 in cui è stato attivato.
Il seguente codice campione 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
observe()
è stata chiamata con nameObserver
passato come
parametro,
onChanged()
viene richiamato immediatamente fornendo il valore più recente archiviato in mCurrentName
.
Se l'oggetto LiveData
non ha impostato un valore in mCurrentName
, onChanged()
non viene richiamato.
Aggiornamento oggetti LiveData
LiveData non dispone di metodi pubblicamente disponibili per aggiornare i dati archiviati. La classe MutableLiveData
espone pubblicamente i metodi setValue(T)
e postValue(T)
e devi utilizzarli se devi modificare il valore archiviato in un oggetto LiveData
. Solitamente
MutableLiveData
viene utilizzato nel
ViewModel
, quindi
ViewModel
espone solo oggetti LiveData
immutabili agli osservatori.
Dopo aver impostato la relazione 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 di setValue(T)
nell'esempio fa sì che gli osservatori chiamino i loro metodi
onChanged()
con il valore John Doe
. L'esempio mostra la pressione di un pulsante, ma
è possibile chiamare setValue()
o postValue()
per aggiornare mName
per diversi
motivi, ad esempio in risposta a una richiesta di rete o al completamento
del carico del database; in tutti i casi, la chiamata a setValue()
o postValue()
attiva
gli osservatori e aggiorna la UI.
Utilizza LiveData con la stanza virtuale
La libreria di persistenza Stanza supporta
query osservabili, che restituiscono oggetti
LiveData
.
Le query osservabili sono scritte come parte di un oggetto di accesso al database (DAO).
La stanza virtuale 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 quando necessario. Questo pattern è utile per mantenere sincronizzati i dati visualizzati in una UI con quelli archiviati in un database. Per saperne di più sulle stanze virtuali e su DAO, consulta la guida alla libreria permanente delle stanze.
Utilizzare le coroutine con LiveData
LiveData
include il supporto per le coroutine Kotlin. Per maggiori informazioni, consulta
Utilizzare le coroutine Kotlin con i componenti dell'architettura Android.
LiveData nell'architettura di un'app
LiveData
è sensibile al ciclo di vita e 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 ottimo candidato per l'archiviazione di oggetti LiveData
. Crea
LiveData
oggetti 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 conservare lo stato. Inoltre, mantenendo attività e frammenti liberi
di conservare i dati, semplifica la scrittura dei test delle unità.
La tentazione di lavorare LiveData
oggetti nella classe del livello dati può essere tentata, ma LiveData
non è progettato per gestire flussi 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 flussi 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'inserimento di un LiveData
in Repository
possa 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 per poi convertirli in LiveData
in ViewModel
utilizzando asLiveData()
.
Scopri di più sull'utilizzo di Kotlin Flow
con LiveData
in questo codelab.
Per i codebase creati con Java, valuta l'utilizzo di esecutori
in combinazione con callback o RxJava
.
Estendi LiveData
LiveData considera un osservatore in stato attivo se il ciclo di vita dell'osservatore è nello stato STARTED
o RESUMED
. Il seguente codice campione 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 del listener di prezzi in questo esempio include i seguenti metodi importanti:
- Il metodo
onActive()
viene richiamato quando l'oggettoLiveData
ha un osservatore attivo. Ciò significa che devi iniziare a osservare gli aggiornamenti del prezzo delle azioni con questo metodo. - Il metodo
onInactive()
viene richiamato quando l'oggettoLiveData
non ha osservatori attivi. Poiché nessun osservatore è in ascolto, non c'è motivo di rimanere connessi al servizioStockManager
. - Il metodo
setValue(T)
aggiorna il valore dell'istanzaLiveData
e informa tutti gli osservatori attivi della modifica.
Puoi utilizzare il corso StockLiveData
nel seguente modo:
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()
passa il LifecycleOwner
associato alla vista del frammento come primo argomento. In questo modo denota che
l'osservatore è vincolato all'oggetto Lifecycle
associato al proprietario, ovvero:
- Se l'oggetto
Lifecycle
non è in 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 sensibili al ciclo di vita significa che puoi condividerli
tra più attività, frammenti e servizi. Per mantenere l'esempio semplice, puoi implementare la classe LiveData
come singleton, come indicato di seguito:
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); } }
E 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.
Trasforma LiveData
Potresti voler modificare il valore archiviato in un oggetto LiveData
prima di inviarlo agli osservatori oppure potresti dover restituire un'istanza LiveData
diversa in base al valore di un'altra. Il pacchetto Lifecycle
fornisce la classe Transformations
che include metodi helper che supportano questi scenari.
Transformations.map()
- Applica una funzione al valore archiviato 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()
- Come per
map()
, applica una funzione al valore archiviato nell'oggettoLiveData
, annulla il wrapping e invia il risultato a valle. La funzione passata aswitchMap()
deve restituire un oggettoLiveData
, come illustrato nel seguente esempio:
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 usare i metodi di trasformazione per portare le informazioni lungo 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 pigro, il comportamento relativo al ciclo di vita viene trasmesso implicitamente senza richiedere
chiamate esplicite o dipendenze aggiuntive.
Se pensi di aver bisogno di un oggetto Lifecycle
all'interno di un oggetto
ViewModel
, probabilmente la
trasformazione è la soluzione migliore. Ad esempio, supponiamo di avere un componente dell'interfaccia utente che accetta un indirizzo e restituisce il codice postale di quell'indirizzo. Puoi implementare l'ingenuo ViewModel
per questo componente come illustrato nel 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 UI deve quindi annullare la registrazione dall'oggetto LiveData
precedente e registrarsi nella nuova istanza ogni volta che chiama getPostalCode()
. Inoltre, se il componente UI viene ricreato, attiva un'altra chiamata al metodo repository.getPostCode()
anziché utilizzare il risultato della chiamata precedente.
Puoi invece implementare la ricerca del codice postale come trasformazione dell'indirizzo di input, 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 della metrica addressInput
. Finché alla tua app è associato un osservatore attivo al campo postalCode
, il valore del campo viene ricalcolato e recuperato ogni volta che cambia addressInput
.
Questo meccanismo consente a livelli inferiori dell'app di creare oggetti LiveData
calcolati on demand. Un oggetto ViewModel
può ottenere facilmente i riferimenti agli oggetti LiveData
e poi definire le regole di trasformazione al loro interno.
Crea nuove trasformazioni
Esistono decine di trasformazioni specifiche 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
ed elabora gli eventi da loro emessi. MediatorLiveData
propaga correttamente
il proprio stato all'oggetto LiveData
di origine. Per saperne di più su questo pattern, consulta la documentazione di riferimento della classe Transformations
.
Unisci più origini LiveData
MediatorLiveData
è una sottoclasse di LiveData
che ti consente di unire più origini LiveData. Gli osservatori degli oggetti MediatorLiveData
vengono quindi attivati ogni volta che viene modificato uno qualsiasi degli oggetti dell'origine LiveData originale.
Ad esempio, se nella UI è presente un oggetto LiveData
che può essere aggiornato da un database locale o da una rete, puoi aggiungere le seguenti origini all'oggetto MediatorLiveData
:
- Un oggetto
LiveData
associato ai dati archiviati nel database. - Un oggetto
LiveData
associato ai dati a cui si accede dalla rete.
La tua attività deve osservare l'oggetto MediatorLiveData
solo per ricevere aggiornamenti da entrambe le origini. Per un esempio dettagliato, consulta la sezione Addendum: esposizione dello
stato di rete
della Guida all'architettura
delle app.
Risorse aggiuntive
Per scoprire di più sul corso LiveData
, consulta le seguenti risorse.
Samples
- Sunflower, un'app demo che mostra le best practice per i componenti dell'architettura
- Esempio di base dei componenti dell'architettura Android
Codelab
- Camera Android con vista (Java) (Kotlin)
- Scopri le coroutine avanzate con Kotlin Flow e LiveData
Blog
- ViewModels e LiveData: Patterns + AntiPattern
- LiveData oltre il ViewModel - Pattern reattivi con Transformations e MediatorLiveData
- LiveData con SnackBar, navigazione e altri eventi (caso SingleLiveEvent)
Video
Consigliato per te
- Nota: il testo del link viene visualizzato quando JavaScript è disattivato
- Utilizzare le coroutine Kotlin con componenti sensibili al ciclo di vita
- Gestione dei cicli di vita con componenti sensibili al ciclo di vita
- Testare l'implementazione di Paging