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 questi Observer 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'oggetto LiveData si connette una volta al servizio di sistema e qualsiasi osservatore che necessita della risorsa può semplicemente controllare l'oggetto LiveData. Per ulteriori informazioni, vedi Extend LiveData.

Utilizzo degli oggetti LiveData

Segui questi passaggi per lavorare con gli oggetti LiveData:

  1. Crea un'istanza di LiveData per contenere un determinato tipo di dati. Di solito, questa operazione viene eseguita all'interno del corso ViewModel.
  2. Crea un oggetto Observer che definisce il metodo onChanged(), che controlla cosa succede quando i dati bloccati dell'oggetto LiveData cambiano. Solitamente si crea un oggetto Observer in un controller UI, ad esempio un'attività o un frammento.
  3. Associa l'oggetto Observer all'oggetto LiveData utilizzando il metodo observe(). Il metodo observe() accetta un oggetto LifecycleOwner. Sottoscrive l'oggetto Observer all'oggetto LiveData per ricevere una notifica sulle modifiche. In genere l'oggetto Observer 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 oggetti LiveData che sta osservando. Questo si verifica solo se l'oggetto LiveData 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'oggetto LiveData 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'oggetto LiveData non ha osservatori attivi. Poiché nessun osservatore è in ascolto, non c'è motivo di rimanere connessi al servizio StockManager.
  • Il metodo setValue(T) aggiorna il valore dell'istanza LiveData 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'oggetto LiveData, annulla il wrapping e invia il risultato a valle. La funzione passata a switchMap() deve restituire un oggetto LiveData, 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

Codelab

Blog

Video