web-dev-qa-db-de.com

LiveData entfernt Observer nach dem ersten Rückruf

Wie entferne ich den Beobachter, nachdem ich das erste Ergebnis erhalten habe? Im Folgenden sind zwei Codewege aufgeführt, die ich ausprobiert habe, aber beide erhalten Updates, obwohl ich den Beobachter entfernt habe.

Observer observer = new Observer<DownloadItem>() {
        @Override
        public void onChanged(@Nullable DownloadItem downloadItem) {
            if(downloadItem!= null) {
                DownloadManager.this.downloadManagerListener.onDownloadManagerFailed(null, "this item already exists");
                return;
            }
            startDownload();
            model.getDownloadByContentId(contentId).removeObservers((AppCompatActivity)context);
        }
    };
    model.getDownloadByContentId(contentId).observeForever(observer);

 model.getDownloadByContentId(contentId).observe((AppCompatActivity)context, downloadItem-> {
             if(downloadItem!= null) {
                this.downloadManagerListener.onDownloadManagerFailed(null, "this item already exists");
                return;
            }
            startDownload();
            model.getDownloadByContentId(contentId).removeObserver(downloadItem-> {});
        } );
11
galaxigirl

Ihre erste funktioniert nicht, da observeForever() nicht an LifecycleOwner gebunden ist.

Ihr zweiter wird nicht funktionieren, weil Sie den vorhandenen registrierten Beobachter nicht an removeObserver() übergeben.

Sie müssen zunächst entscheiden, ob Sie LiveData mit einer LifecycleOwner (Ihre Aktivität) verwenden oder nicht. Ich gehe davon aus, dass Sie eine LifecycleOwner verwenden sollten. In diesem Fall verwenden Sie:

Observer observer = new Observer<DownloadItem>() {
    @Override
    public void onChanged(@Nullable DownloadItem downloadItem) {
        if(downloadItem!= null) {
            DownloadManager.this.downloadManagerListener.onDownloadManagerFailed(null, "this item already exists");
            return;
        }
        startDownload();
        model.getDownloadByContentId(contentId).removeObservers((AppCompatActivity)context);
    }
};

model.getDownloadByContentId(contentId).observe((AppCompatActivity)context, observer);
17
CommonsWare

Nach dem Aufruf von CommonsWare answer können Sie anstelle von removeObservers(), der alle an LiveData angeschlossenen Beobachter entfernt, einfach removeObserver(this) aufrufen, um nur diesen Beobachter zu entfernen:

Observer observer = new Observer<DownloadItem>() {
    @Override
    public void onChanged(@Nullable DownloadItem downloadItem) {
        if(downloadItem!= null) {
            DownloadManager.this.downloadManagerListener.onDownloadManagerFailed(null, "this item already exists");
            return;
        }
        startDownload();
        model.getDownloadByContentId(contentId).removeObserver(this);
    }
};

model.getDownloadByContentId(contentId).observe((AppCompatActivity)context, observer);

Hinweis: in removeObserver(this), this bezieht sich auf die Observer-Instanz und funktioniert nur bei anonymen inneren Klassen. Wenn Sie ein Lambda verwenden, verweist this auf die Aktivitätsinstanz.

8
Toni Joe

Ich liebe die generischen Lösungen von @Vince und @Hakem Zaied, aber für mich scheint die Lambda-Version noch besser zu sein:

fun <T> LiveData<T>.observeOnce(observer: (T) -> Unit) {
    observeForever(object: Observer<T> {
        override fun onChanged(value: T) {
            removeObserver(this)
            observer(value)
        }
    })
}

fun <T> LiveData<T>.observeOnce(owner: LifecycleOwner, observer: (T) -> Unit) {
    observe(owner, object: Observer<T> {
        override fun onChanged(value: T) {
            removeObserver(this)
            observer(value)
        }
    })
}

So endest du mit:

    val livedata = model.getDownloadByContentId(contentId)
    livedata.observeOnce((AppCompatActivity) context) {
        if (it != null) {
            DownloadManager.this.downloadManagerListener.onDownloadManagerFailed(null, "this item already exists")
        }
        startDownload();
    }

Was ich sauberer finde.

Außerdem wird removeObserver() als erstes aufgerufen, wenn der Beobachter abgesetzt wird, wodurch es sicherer wird (d. H. Mit potenziellen Laufzeitfehlern umgeht, die aus dem Beobachtercode des Benutzers stammen).

2
d4vidi

Es gibt eine bequemere Lösung für Kotlin mit Erweiterungen:

fun <T> LiveData<T>.observeOnce(lifecycleOwner: LifecycleOwner, observer: Observer<T>) {
    observeForever(object : Observer<T> {
        override fun onChanged(t: T?) {
            observer.onChanged(t)
            removeObserver(this)
        }
    })
}

Diese Erweiterung erlaubt uns das zu tun:

liveData.observeOnce(this, Observer<Password> {
    if (it != null) {
        // do something
    }
})

Um Ihre ursprüngliche Frage zu beantworten, können wir das tun:

val livedata = model.getDownloadByContentId(contentId)
livedata.observeOnce((AppCompatActivity) context, Observer<T> {
    if (it != null) {
        DownloadManager.this.downloadManagerListener.onDownloadManagerFailed(null, "this item already exists");
    }
    startDownload();
})

Die ursprüngliche Quelle ist hier: https://code.luasoftware.com/tutorials/Android/android-livedata-observe-once-only-kotlin/

1
Vince

Die von @CommonsWare und @Toni Joe vorgeschlagene Lösung hat das Problem nicht gelöst, als ich die Beobachter entfernen musste, nachdem ich das erste Ergebnis einer DAO-Abfrage in meinem ViewModel erhalten hatte. Die folgende Lösung, die bei Livedata gefunden wurde, behält den Beobachter, nachdem sie removeObserer aufgerufen hat, hat den Trick für mich mit ein wenig meiner eigenen Intuition ausgeführt.

Gehen Sie wie folgt vor, erstellen Sie in Ihrem ViewModel eine Variable, in der die LiveData auf Anfrage gespeichert werden, rufen Sie sie in einem Aufruf der Funktion "Beobachter erstellen" in der Aktivität ab, nachdem Sie eine Nullprüfung durchgeführt haben, und rufen Sie eine Funktion zum Entfernen von Beobachtern auf, bevor Sie die Routine "flushToDB" in a aufrufen importierte Klasse. Das heißt, der Code in meinem ViewModel sieht folgendermaßen aus:

public class GameDataModel extends AndroidViewModel {
   private LiveData<Integer> lastMatchNum = null;
   .
   .
   .
   private void initLastMatchNum(Integer player1ID, Integer player2ID) {
       List<Integer> playerIDs = new ArrayList<>();
       playerIDs.add(player1ID);
       playerIDs.add(player2ID);

       lastMatchNum = mRepository.getLastMatchNum(playerIDs);
   }

 public LiveData<Integer> getLastMatchNum(Integer player1ID, Integer player2ID) {
       if (lastMatchNum == null) { initLastMatchNum(player1ID, player2ID); }
       return lastMatchNum;
   }

Wenn im obigen Beispiel die LiveData-Variable im ViewModel keine Daten enthält, rufe ich initLastMatchNum() auf, um die Daten von einer Funktion im View-Modell abzurufen. Die aus der Aktivität aufzurufende Funktion ist getLastMatchNum(). Diese Routine ruft die Daten in der Variablen im ViewModel ab (die über das Repository über das DAO abgerufen werden).

Den folgenden Code habe ich in meiner Aktivität

public class SomeActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
         .
         .
         .
        setupLastMatchNumObserver(); 
         .
         .
         .
    }

    private void setupLastMatchNumObserver() {
        if (mGameDataViewModel.getLastMatchNum(Player1ID, Player2ID).hasObservers()) {
            Log.v("Observers", "setupLastMatchNumObserver has observers...returning");
            return;
        }
        Log.v("Setting up Observers", "running mGameDataViewModel.get...observer()");
        mGameDataViewModel.getLastMatchNum(Player1ID, Player2ID).observe(this, new Observer<Integer>() {
            @Override
            public void onChanged(Integer MatchNumber) {
                if (MatchNumber == null ) {
                    matchNumber = 1;
                    Log.v( "null MatchNumber", "matchNumber: " + matchNumber.toString());
                }
                else {
                    matchNumber = MatchNumber; matchNumber++;
                    Log.v( "matchNumber", "Incrementing match number: " + matchNumber.toString());
                }
                MatchNumberText.setText(matchNumber.toString());
            }
        });
    }

    private void removeObservers() {
        final LiveData<Integer> observable = mGameDataViewModel.getLastMatchNum(Player1ID, Player2ID);
        if (observable != null && observable.hasObservers()) {
            Log.v("removeObserver", "Removing Observers");
            observable.removeObservers(this);
        }
    }

Was oben vor sich geht, ist 1.) Ich rufe die Routine setupLastMatchNumObserver() in der Methode onCreate der Aktivität auf, um die Variable matchNum der Klasse zu aktualisieren. Auf diese Weise werden die in einer Datenbank gespeicherten Matchnummern zwischen Spielern in meinem Spiel protokolliert. Jede Gruppe von Spielern hat eine andere Spielnummer in der Datenbank, je nachdem, wie oft sie neue Spiele miteinander spielen. Die ersten Lösungen in diesem Thread schienen mir etwas müde zu sein, da das Aufrufen von Beobachtern entfernen im onChanged für mich seltsam erscheint und das TextView -Objekt nach jedem Datenbank-Flush jedes Spielzuges ständig ändern würde . Also wurde matchNumber nach jedem Zug inkrementiert, weil es nach dem ersten Zug einen neuen Wert in der Datenbank gab (nämlich den einen matchNumber++ - Wert) und onChanged wurde weiterhin aufgerufen, weil removeObservers hat nicht wie vorgesehen funktioniert. setupLastMatchNumObserver() prüft, ob Beobachter der Live-Daten vorhanden sind und instanziiert in diesem Fall nicht bei jeder Runde einen neuen Anruf. Wie Sie sehen, setze ich ein TextView -Objekt, um die aktuelle Matchnummer der Spieler wiederzugeben.

Der nächste Teil ist ein kleiner Trick, wann removeObservers() aufgerufen werden soll. Zuerst dachte ich, wenn ich es direkt nach setupLastMatchNumObserver() im onCreate Override der Aktivität aufrufen würde, wäre alles in Ordnung. Der Beobachter wurde jedoch entfernt, bevor er die Daten abrufen konnte. Ich fand heraus, dass, wenn ich removeObservers() direkt vor dem Aufruf aufrief, um die in der Aktivität gesammelten neuen Daten in die Datenbank zu schreiben (in separaten Routinen während der Aktivität), das wie ein Zauber wirkte. d.h.

 public void addListenerOnButton() {
        .
        .
        .
            @Override
            public void onClick(View v) {
               .
               .
               .
               removeObservers();
               updateMatchData(data); 
            }
   }

Ich rufe auch removeObservers(); und updateMatchData(data) an anderen Stellen in meiner Aktivität auf die oben beschriebene Weise auf. Das Schöne ist, dass removeObservers() so oft aufgerufen werden kann, wie es erforderlich ist, da eine Überprüfung erfolgt, die zurückgegeben werden muss, wenn keine Beobachter anwesend sind.

0
bubonic

Ich stimme @ vince zu, aber ich glaube, wir überspringen entweder lifecycleOwner und verwenden observerForever wie folgt:

fun <T> LiveData<T>.observeOnce(observer: Observer<T>) {
    observeForever(object : Observer<T> {
        override fun onChanged(t: T?) {
            observer.onChanged(t)
            removeObserver(this)
        }
    })
}

oder verwenden Sie die lifecycleOwner mit observe wie folgt:

fun <T> LiveData<T>.observeOnce(lifecycleOwner: LifecycleOwner, observer: Observer<T>) {
    observe(lifecycleOwner, object : Observer<T> {
        override fun onChanged(t: T?) {
            observer.onChanged(t)
            removeObserver(this)
        }
    })
}
0
Hakem Zaied
  1. Die LiveData-Klasse verfügt über zwei ähnliche Methoden zum Entfernen von Observers. Zuerst heißt es, 

removeObserver(@NonNull final Observer<T> observer) (sehen Sie sich den Namen der Methode sorgfältig an, es ist ein Singular), wodurch der Beobachter, den Sie aus der Liste der Beobachter desselben LifecycleOwner entfernen möchten, aufgenommen wird. 

  1. Zweite Methode ist 

removeObservers(@NonNull final LifecycleOwner owner) (siehe Methodenname im Plural). Diese Methode übernimmt den LifecycleOwner selbst und entfernt alle Observer des angegebenen LifecycleOwner.

In Ihrem Fall können Sie Ihren Observer auf zwei Arten entfernen (möglicherweise gibt es viele Möglichkeiten), eine von @ToniJoe in der vorherigen Antwort.

Eine andere Möglichkeit ist, dass Sie in Ihrem ViewModel ein boolesches MutableLiveData-Element haben, das True speichert, wenn es das erste Mal beobachtet wurde. Beobachten Sie auch Livedata. Wann immer es wahr wird, werden Sie benachrichtigt und können Ihren Beobachter entfernen, indem Sie diesen bestimmten Beobachter passieren.

0
Abhishek Kumar