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 oggetti Observer. 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'oggetto LiveData si connette al servizio di sistema una volta, quindi qualsiasi osservatore che ha bisogno della risorsa può semplicemente monitorare l'oggetto LiveData. Per ulteriori informazioni, vedi Estendere LiveData.

Lavorare con gli oggetti LiveData

Per lavorare con gli oggetti LiveData:

  1. Crea un'istanza di LiveData per contenere un determinato tipo di dati. Di solito questo accade all'interno del corso ViewModel.
  2. Crea un oggetto Observer che definisce il metodo onChanged() che controlla cosa accade quando i dati trattenuti dell'oggetto LiveData subiscono variazioni. In genere, crei un oggetto Observer in un controller dell'interfaccia utente, ad esempio un'attività o un frammento.
  3. Collega l'oggetto Observer all'oggetto LiveData utilizzando il metodo observe(). Il metodo observe() accetta un oggetto LifecycleOwner. In questo modo, l'oggetto Observer viene iscritto all'oggetto LiveData in modo da ricevere una notifica delle modifiche. In genere, l'oggetto Observer 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 oggetti LiveData che sta osservando. Ciò si verifica solo se è stato impostato l'oggetto LiveData 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'oggetto LiveData 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'oggetto LiveData 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'istanza LiveData 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'oggetto LiveData e scompatta e invia il risultato a valle. La funzione passata a switchMap() deve restituire un oggetto LiveData, 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

Blog

Video