74 votes

Comment effacer la valeur stockée de LiveData ?

Selon Documentation LiveData :

La classe LiveData offre les avantages suivants :

...

Des données toujours à jour : Si un cycle de vie redémarre (comme une activité qui revient à l'état initial depuis la pile arrière), il reçoit les dernières données de localisation (si ce n'est pas déjà fait).

Mais parfois, je n'ai pas besoin de cette fonctionnalité.

Par exemple, j'ai les LiveData suivantes dans ViewModel et Observer dans Activity :

//LiveData
val showDialogLiveData = MutableLiveData<String>()

//Activity
viewModel.showMessageLiveData.observe(this, android.arch.lifecycle.Observer { message ->
        AlertDialog.Builder(this)
                .setMessage(message)
                .setPositiveButton("OK") { _, _ -> }
                .show()
    })

Maintenant, après chaque rotation, un vieux dialogue apparaîtra.

Existe-t-il un moyen d'effacer la valeur stockée après qu'elle ait été manipulée ou est-ce une mauvaise utilisation de LiveData ?

2 votes

0 votes

Est-ce que cela est lié au problème des données en direct ? L'activité sera recréée à chaque rotation, que vous ayez utilisé LiveData ou non. Le problème persiste même si vous le supprimez.

1 votes

@LongRanger cela peut être résolu en supprimant le message mis en cache dans LiveData après l'affichage du dialogue, afin que la nouvelle activité ne le reçoive pas. Le même principe a été utilisé pour l'application Moxy's OneExecutionStateStrategy

71voto

Lyla Points 1663

Mise à jour

Il existe en fait plusieurs façons de résoudre ce problème. Elles sont bien résumées dans cet article. LiveData avec SnackBar, Navigation et autres événements (le cas SingleLiveEvent) . Ce texte est écrit par un collègue Googler qui travaille dans l'équipe des composants d'architecture.

TL;DR Une approche plus robuste consiste à utiliser un Classe d'enveloppe d'événement dont vous pouvez voir un exemple au bas de l'écran. l'article .

Ce modèle a fait son chemin dans de nombreux échantillons Android, par exemple :

Pourquoi un wrapper d'événement est-il préféré à SingleLiveEvent ?

Un problème avec SingleLiveEvent est que s'il y a plusieurs observateurs d'un SingleLiveEvent, seul l'un d'entre eux sera notifié lorsque les données ont changé - cela peut introduire des bogues subtils et est difficile à contourner.

En utilisant une classe enveloppe d'événement, tous vos observateurs seront notifiés comme d'habitude. Vous pouvez alors choisir soit de "gérer" explicitement le contenu (le contenu n'est "géré" qu'une seule fois), soit de consulter le contenu, ce qui renvoie toujours le dernier "contenu". Dans l'exemple du dialogue, cela signifie que vous pouvez toujours voir quel était le dernier message avec peek mais assurez-vous que pour chaque nouveau message, la boîte de dialogue n'est déclenchée qu'une seule fois, en utilisant la fonction getContentIfNotHandled .

Ancienne réponse

La réponse d'Alex dans les commentaires est, je pense, exactement ce que vous recherchez. Il y a un exemple de code pour une classe appelée SingleLiveEvent . L'objectif de cette classe est décrit comme suit :

Un observable qui tient compte du cycle de vie et qui envoie seulement les nouvelles mises à jour après que abonnement, utilisé pour des événements tels que la navigation et les messages Snackbar.

Ceci permet d'éviter un problème courant avec les événements : lors d'un changement de configuration (comme la rotation), une mise à jour peut être émise si l'observateur est actif. Ce LiveData n'appelle l'observable que s'il y a un appel explicite à setValue() ou call().

8voto

Jurij Pitulja Points 840

Je ne suis pas sûr que cela fonctionnera dans votre cas, mais dans mon cas (augmenter/diminuer la quantité d'articles dans la salle en cliquant sur les vues), enlever l'observateur et vérifier s'il y a des observateurs actifs me permet de faire le travail :

LiveData<MenuItem> menuitem = mViewModel.getMenuItemById(menuid);
menuitem.observe(this, (MenuItem menuItemRoom) ->{
                menuitem.removeObservers(this);
                if(menuitem.hasObservers())return;

                // Do your single job here

                });
});  

MISE À JOUR 20/03/2019 :

Maintenant, je préfère ça : Classe EventWraper de Google Samples dans MutableLiveData

/**
 * Used as a wrapper for data that is exposed via a LiveData that represents an event.
 */
public class Event<T> {

    private T mContent;

    private boolean hasBeenHandled = false;

    public Event( T content) {
        if (content == null) {
            throw new IllegalArgumentException("null values in Event are not allowed.");
        }
        mContent = content;
    }

    @Nullable
    public T getContentIfNotHandled() {
        if (hasBeenHandled) {
            return null;
        } else {
            hasBeenHandled = true;
            return mContent;
        }
    }

    public boolean hasBeenHandled() {
        return hasBeenHandled;
    }
}

Dans ViewModel :

 /** expose Save LiveData Event */
 public void newSaveEvent() {
    saveEvent.setValue(new Event<>(true));
 }

 private final MutableLiveData<Event<Boolean>> saveEvent = new MutableLiveData<>();

 LiveData<Event<Boolean>> onSaveEvent() {
    return saveEvent;
 }

Dans Activité/Fragment

mViewModel
    .onSaveEvent()
    .observe(
        getViewLifecycleOwner(),
        booleanEvent -> {
          if (booleanEvent != null)
            final Boolean shouldSave = booleanEvent.getContentIfNotHandled();
            if (shouldSave != null && shouldSave) saveData();
          }
        });

7voto

Final12345 Points 51

Dans mon cas, SingleLiveEvent ne m'aide pas. J'utilise ce code :

private MutableLiveData<Boolean> someLiveData;
private final Observer<Boolean> someObserver = new Observer<Boolean>() {
    @Override
    public void onChanged(@Nullable Boolean aBoolean) {
        if (aBoolean != null) {
            // doing work
            ...

            // reset LiveData value  
            someLiveData.postValue(null);
        }
    }
};

0 votes

Ce n'est qu'une supposition, mais il se peut qu'il ne soit pas utile dans votre cas parce que vous utilisez postValue et la mise en œuvre de l'exemple SingleLiveEvent n'en tient pas compte. Une correction facile cependant, ajouter - ``` override fun postValue(value : T) { pending.set(true) super.postValue(value) } ```

6voto

Nynuzoud Points 21

Si vous avez besoin d'une solution simple, essayez celle-ci :

class SingleLiveData<T> : MutableLiveData<T?>() {

    override fun observe(owner: LifecycleOwner, observer: Observer<in T?>) {
        super.observe(owner, Observer { t ->
            if (t != null) {
                observer.onChanged(t)
                postValue(null)
            }
        })
    }
}

Utilisez-le comme une MutableLiveData ordinaire.

1 votes

Fonctionne comme un charme !

3voto

Ankit Bisht Points 161

Vous devez utiliser SingleLiveEvent dans ce cas.

class SingleLiveEvent<T> : MutableLiveData<T>() {

    private val pending = AtomicBoolean(false)

    @MainThread
    override fun observe(owner: LifecycleOwner, observer: Observer<T>) {

        if (hasActiveObservers()) {
            Log.w(TAG, "Multiple observers registered but only one will be notified of changes.")
        }

        // Observe the internal MutableLiveData
        super.observe(owner, Observer<T> { t ->
            if (pending.compareAndSet(true, false)) {
                observer.onChanged(t)
            }
        })
    }

    @MainThread
    override fun setValue(t: T?) {
        pending.set(true)
        super.setValue(t)
    }

    /**
     * Used for cases where T is Void, to make calls cleaner.
     */
    @MainThread
    fun call() {
        value = null
    }

    companion object {
        private const val TAG = "SingleLiveEvent"
    }
}

Et à l'intérieur de votre classe viewmodel créer un objet comme :

 val snackbarMessage = SingleLiveEvent<Int>()

1 votes

J'ai utilisé cette classe, mais elle se déclenche à nouveau.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X