Was ist der Unterschied zwischen den Methoden map () und switchMap ()?


72

Was ist der Unterschied zwischen diesen beiden Methoden der LiveData-Klasse? Das offizielle Dokument und das Tutorial sind dazu ziemlich vage. In der Karte () Methode aufgerufen der erste Parameter Quelle , sondern in der switchMap () es genannt Trigger . Was ist der Grund dafür?

Antworten:


72

Gemäß Dokumentation

Transformations.map ()

Wendet eine Funktion auf den im LiveData-Objekt gespeicherten Wert an und gibt das Ergebnis nachgelagert weiter.

Transformations.switchMap ()

Wendet ähnlich wie map eine Funktion auf den im LiveData-Objekt gespeicherten Wert an und packt das Ergebnis stromabwärts aus und versendet es. Die an switchMap () übergebene Funktion muss ein LiveData-Objekt zurückgeben .

Mit anderen Worten, ich bin möglicherweise nicht 100% korrekt, aber wenn Sie mit RxJava vertraut sind; Transformations#mapist ähnlich wie Observable#map& Transformations#switchMapist ähnlich wie Observable#flatMap.

Nehmen wir ein Beispiel, es gibt eine LiveData, die eine Zeichenfolge ausgibt, und wir möchten diese Zeichenfolge in Großbuchstaben anzeigen.

Ein Ansatz wäre wie folgt; in einer Aktivität oder einem Fragment

Transformations.map(stringsLiveData, String::toUpperCase)
    .observe(this, textView::setText);

Die an übergebene Funktion mapgibt nur einen String zurück, aber Transformation#mapletztendlich gibt sie einen zurück LiveData.

Der zweite Ansatz; in einer Aktivität oder einem Fragment

Transformations.switchMap(stringsLiveData, this::getUpperCaseStringLiveData)
            .observe(this, textView::setText);

private LiveData<String> getUpperCaseStringLiveData(String str) {
    MutableLiveData<String> liveData = new MutableLiveData<>();
    liveData.setValue(str.toUpperCase());
    return liveData;
}

Wenn Sie sehen, Transformations#switchMaphat das tatsächlich gewechselt LiveData. Also noch einmal gemäß Dokumentation. Die an switchMap () übergebene Funktion muss ein LiveData-Objekt zurückgeben .

Also, bei mapes ist die Quelle LiveData Sie wandeln und bei switchMapder übergebene LiveDataals handeln Auslöser , auf dem es zu einem anderen wechseln wird LiveDatanach auspacken und den Versand des Ergebnisses downstream.


20
Das würde die Benennung erklären. Beide sollten also jedes Mal ausgelöst werden, wenn sich die zugrunde liegenden LiveData ändern, und "switch" bedeutet, dass LiveData auf ein anderes LiveData-Objekt umgeschaltet wird. Vielen Dank!
Igor Bubelov

2
Nizza Erklärung - ich habe hinzugefügt , das ein Beispiel einer Kombination aus einem switchMap und Karte auf meinem Repo .. github.com/febaisi/ListenableWorkerExample/blob/master/app/src/... zu .. ‚SwitchMap‘ wird verwendet , nur um zu hören ein Schaltflächenereignis und wechseln Sie zu den richtigen LiveData, die eine Map of a Worker-Ergebnis sind. Ich hoffe es hilft auch.
Februar

Beide Transformationen (map, switchMap) geben die LiveData-Objekte zurück.
Qumber Abbas

30

Meine Beobachtung ist, dass Sie sich für die Verwendung entscheiden können, wenn Ihr Transformationsprozess schnell ist (ohne Datenbankbetrieb oder Netzwerkaktivität) map.

Wenn Ihr Transformationsprozess jedoch langsam ist (einschließlich Datenbankbetrieb oder Netzwerkaktivität), müssen Sie verwenden switchMap

switchMap wird bei zeitaufwändigen Operationen verwendet

class MyViewModel extends ViewModel {
    final MutableLiveData<String> mString = new MutableLiveData<>();
    final LiveData<Integer> mCode;


    public MyViewModel(String string) {

        mCode = Transformations.switchMap(mString, input -> {
            final MutableLiveData<Integer> result = new MutableLiveData<>();

            new Thread(new Runnable() {
                @Override
                public void run() {
                    // Pretend we are busy
                    try {
                        Thread.sleep(5000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    int code = 0;
                    for (int i=0; i<input.length(); i++) {
                        code = code + (int)input.charAt(i);
                    }

                    result.postValue(code);
                }
            }).start();

            return result;
        });

        if (string != null) {
            mString.setValue(string);
        }
    }

    public LiveData<Integer> getCode() {
        return mCode;
    }

    public void search(String string) {
        mString.setValue(string);
    }
}

map ist nicht für zeitaufwändigen Betrieb geeignet

class MyViewModel extends ViewModel {
    final MutableLiveData<String> mString = new MutableLiveData<>();
    final LiveData<Integer> mCode;


    public MyViewModel(String string) {

        mCode = Transformations.map(mString, input -> {
            /* 
                Note: You can't launch a Thread, or sleep right here. 
                If you do so, the APP will crash with ANR.
            */
            /*
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            */

            int code = 0;
            for (int i=0; i<input.length(); i++) {
                code = code + (int)input.charAt(i);
            }
            return code;
        });

        if (string != null) {
            mString.setValue(string);
        }
    }

    public LiveData<Integer> getCode() {
        return mCode;
    }

    public void search(String string) {
        mString.setValue(string);
    }
}

10
Einfache und klare Antwort, die meisten Antworten erklären immer wieder, wie es intern funktioniert, aber das erste, was mir wichtig ist, ist, warum ich es verwenden würde, ohne das interne Verhalten verstehen zu müssen. Vielen Dank.
Ambroise Rabier

26

Zu allererst map()und switchMap()Methoden sowohl auf dem Haupt - Thread aufgerufen wird . Und sie haben nichts damit zu tun, für schnelle oder langsame Aufgaben verwendet zu werden. Es kann jedoch zu Verzögerungen auf der Benutzeroberfläche kommen, wenn Sie komplexe rechnerische oder zeitaufwändige Aufgaben innerhalb dieser Methoden anstelle eines Arbeitsthreads ausführen und beispielsweise eine lange und / oder komplexe JSON-Antwort analysieren oder konvertieren, da diese auf dem UI-Thread ausgeführt werden.

  • Karte()

Der Code der map () -Methode lautet

@MainThread
public static <X, Y> LiveData<Y> map(@NonNull LiveData<X> source,
        @NonNull final Function<X, Y> func) {
    final MediatorLiveData<Y> result = new MediatorLiveData<>();
    result.addSource(source, new Observer<X>() {
        @Override
        public void onChanged(@Nullable X x) {
            result.setValue(func.apply(x));
        }
    });
    return result;
}

Es verwendet eine Quell-LiveData, I ist der Eingabetyp und ruft setValue (O) für LiveData auf, wobei O der Ausgabetyp ist.

Damit es klar ist, möchte ich ein Beispiel geben. Sie möchten den Benutzernamen und den Nachnamen in textView schreiben, wenn sich ein Benutzer ändert.

  /**
     * Changes on this user LiveData triggers function that sets mUserNameLiveData String value
     */
    private MutableLiveData<User> mUserLiveData = new MutableLiveData<>();

    /**
     * This LiveData contains the data(String for this example) to be observed.
     */
    public final LiveData<String> mUserNameLiveData;

Lassen Sie uns jetzt Änderungen an der Zeichenfolge von mUserNameLiveData auslösen, wenn sich mUserLiveData ändert.

   /*
     * map() method emits a value in type of destination data(String in this example) when the source LiveData is changed. In this example
     * when a new User value is set to LiveData it trigger this function that returns a String type
     *         
     *              Input, Output
     * new Function<User, String>
     *
     *  public String apply(User input) { return output;}
     */

    // Result<Output>                        Source<Input>               Input, Output
    mUserNameLiveData = Transformations.map(mUserLiveData, new Function<User, String>() {
        @Override
        public String apply(User input) {
            // Output
            return input.getFirstName() + ", " + input.getLastName();
        }
    });

Und machen wir dasselbe mit MediatorLiveData

 /**
     * MediatorLiveData is what {@link Transformations#map(LiveData, Function)} does behind the scenes
     */
    public MediatorLiveData<String> mediatorLiveData = new MediatorLiveData<>();
    /*
     * map() function is actually does this
     */
    mediatorLiveData.addSource(mUserLiveData, new Observer<User>() {
        @Override
        public void onChanged(@Nullable User user) {
            mediatorLiveData.setValue(user.getFirstName() + ", " + user.getLastName());
        }
    });

Und wenn Sie MediatorLiveData bei Aktivität oder Fragment beobachten, erhalten Sie das gleiche Ergebnis wie beim Beobachten LiveData<String> mUserNameLiveData

userViewModel.mediatorLiveData.observe(this, new Observer<String>() {
    @Override
    public void onChanged(@Nullable String s) {
        TextView textView = findViewById(R.id.textView2);

        textView.setText("User: " + s);

        Toast.makeText(MainActivity.this, "User: " + s, Toast.LENGTH_SHORT).show();
    }
});
  • switchMap ()

switchMap () gibt bei jeder Änderung von SourceLiveData die gleichen MediatorLiveData zurück, keine neuen LiveData.

Es ist Quellcode ist

@MainThread
public static <X, Y> LiveData<Y> switchMap(@NonNull LiveData<X> trigger,
                                           @NonNull final Function<X, LiveData<Y>> func) {

    final MediatorLiveData<Y> result = new MediatorLiveData<>();

    result.addSource(trigger, new Observer<X>() {
        LiveData<Y> mSource;

        @Override
        public void onChanged(@Nullable X x) {
            LiveData<Y> newLiveData = func.apply(x);
            if (mSource == newLiveData) {
                return;
            }
            if (mSource != null) {
                result.removeSource(mSource);
            }
            mSource = newLiveData;
            if (mSource != null) {
                result.addSource(mSource, new Observer<Y>() {
                    @Override
                    public void onChanged(@Nullable Y y) {
                        result.setValue(y);
                    }
                });
            }
        }
    });
    return result;
}

Grundsätzlich wird eine endgültige MediatorLiveData erstellt und wie map () auf das Ergebnis gesetzt, aber diese Zeitfunktion gibt LiveData zurück

   public static <X, Y> LiveData<Y> map(@NonNull LiveData<X> source,
                                         @NonNull final Function<X, **Y**> func) {

        final MediatorLiveData<Y> result = new MediatorLiveData<>();

        result.addSource(source, new Observer<X>() {

            @Override
            public void onChanged(@Nullable X x) {
                result.setValue(func.apply(x));
            }

        });

        return result;
    }

    @MainThread
    public static <X, Y> LiveData<Y> switchMap(@NonNull LiveData<X> trigger,
                                               @NonNull final Function<X, **LiveData<Y>**> func) {

        final MediatorLiveData<Y> result = new MediatorLiveData<>();

        result.addSource(trigger, new Observer<X>() {
            LiveData<Y> mSource;

            @Override
            public void onChanged(@Nullable X x) {
                LiveData<Y> newLiveData = func.apply(x);
                if (mSource == newLiveData) {
                    return;
                }
                if (mSource != null) {
                    result.removeSource(mSource);
                }
                mSource = newLiveData;
                if (mSource != null) {
                    result.addSource(mSource, new Observer<Y>() {
                        @Override
                        public void onChanged(@Nullable Y y) {
                            result.setValue(y);
                        }
                    });
                }
            }
        });
        return result;
    }

Also map()nimmt LiveData<User>und transformiert es in ein String, wenn sich das UserObjekt ändert, ändert sich beispielsweise das Namensfeld.

switchMap()nimmt einen String und LiveData<User>benutzt ihn. Fragen Sie einen Benutzer aus dem Web oder der Datenbank mit einem String ab und erhalten Sie einen LiveData<User>als Ergebnis.


2
Gute Antwort !!
Narshim

17

Map () ist konzeptionell identisch mit der Verwendung in RXJava. Grundsätzlich ändern Sie einen Parameter von LiveData in einem anderen Geben Sie hier die Bildbeschreibung ein

SwitchMap () stattdessen ersetzen Sie die LiveData selbst durch eine andere! Ein typischer Fall ist, wenn Sie beispielsweise einige Daten aus einem Repository abrufen und die vorherigen LiveData "entfernen" (um den Speicher zu sammeln, um den Speicher normalerweise effizienter zu gestalten), übergeben Sie eine neue LiveData, die dieselbe Aktion ausführt (eine Abfrage für Beispiel)


1
bei weitem die einzige Antwort, die die Realität mit einem einfachen Beispiel widerspiegelt
Ace

13

Es gibt oben bereits einige gute Antworten, aber ich habe immer noch mit ihnen gekämpft, bis ich sie verstanden habe. Deshalb werde ich versuchen, ein konkretes Beispiel für Menschen mit meiner Denkweise zu erklären, ohne auf technische Details und Code einzugehen.

In den beiden mapund switchMapes gibt eine Quelle (oder Trigger) Live - Daten, und in beiden Fällen Sie wollen verwandeln es in ein anderes Live - Daten . Welches Sie verwenden werden, hängt von der Aufgabe ab, die Ihre Transformation ausführt.

map

Stellen Sie sich dasselbe einfache Beispiel vor, das überall verwendet wird - Ihre Quell- Live-Daten enthalten ein UserObjekt - LiveData<User>, das auf den aktuell angemeldeten Benutzer verweist. Sie möchten einen Text in Ihrer Benutzeroberfläche anzeigen Current user: <USERNAME>. In diesem Fall sollte jedes Änderungssignal von der Quelle genau ein Signal der resultierenden "abgebildeten" auslösen LiveData. Das aktuelle UserObjekt ist beispielsweise "Bob", dann wird der UI-Text angezeigt Current user: Bob. Sobald Sie LiveData<User>eine Änderung auslösen, wird Ihre Benutzeroberfläche diese beobachten und den Text auf aktualisieren Current user: Alice. Sehr einfach, linear, eins zu eins ändern.

switchMap

Betrachten Sie das folgende Beispiel: Sie möchten eine Benutzeroberfläche erstellen, in der die Benutzer angezeigt werden, deren Name mit dem angegebenen Suchbegriff übereinstimmt. Wir können ziemlich schlau sein und den Suchbegriff als LiveData halten! Es wird also ein LiveData<String>und jedes Mal, wenn der Benutzer eine neue Abfragezeichenfolge eingibt, setzt unser Fragment/ Activityeinfach den Texteingabewert auf diese Live-Daten in der ViewModel. Infolgedessen lösen diese Live-Daten ein Änderungssignal aus. Sobald wir dieses Signal erhalten, suchen wir nach den Benutzern. Nehmen wir nun an, unsere Suche ist so schnell, dass sofort ein Wert zurückgegeben wird. An diesem Punkt denken Sie, dass Sie nur eine verwenden könnenmapund geben Sie die übereinstimmenden Benutzer zurück, die die Benutzeroberfläche aktualisieren. Nun, Sie werden jetzt einen Fehler haben - stellen Sie sich vor, Sie aktualisieren die Datenbank regelmäßig und nach dem nächsten Update werden mehr Benutzer angezeigt, die dem Suchbegriff entsprechen! Wie Sie sehen, führt der Quellauslöser (Suchbegriff) in diesem Szenario nicht unbedingt zu einem einzigen Auslöser für zugeordnete Live-Daten. Die der Benutzeroberfläche zugewiesenen zugeordneten Live-Daten müssen möglicherweise weiterhin die Werte auslösen, nachdem neue Benutzer hinzugefügt wurden die Datenbank. An dieser Stelle könnte man sagen, dass wir "intelligentere" Live-Daten zurückgeben könnten, die nicht nur auf Quellauslöser warten, sondern auch die Datenbank auf Benutzer überwachen, die dem angegebenen Begriff entsprechen (dies können Sie mit RoomDB out tun der Box). Aber dann kommt eine andere Frage - was ist, wenn sich der Suchbegriff ändert? Ihre Amtszeit war alsoxEs wurden Live-Daten ausgelöst, die die Benutzer abfragen und die Datenbank im Auge behalten. Sie werden zurückgegeben userx, userxxund nach fünf Minuten zurückgegeben userx, userxxxund so weiter. Dann wurde der Begriff in geändert y. Jetzt müssen wir irgendwie Anschlag auf die Smart - Live - Daten hören uns Benutzer geben mit xdrin, und schalten Sie es mit den neuen Smart - Live - Daten , die überwacht und geben uns Benutzer mit yin ihren Namen. Und genau das switchMapmacht es! Und Hinweis, diese Schalter werden muss , um in einer solchen Art und Weise durchgeführt, dass in Ihrer Benutzeroberfläche Sie schreiben Sie einfach switchMap(...).observeeinmal, dass Mittel , die switchMapeinen Wrapper zurückgeben muss , LiveDatadie die gleiche während der Ausführung bleiben, sondern schaltet die Live - Datenquellen unter der Haube für uns.

Fazit

Obwohl sie auf den ersten Blick gleich aussehen, die Anwendungsfälle für mapund switchMapunterschiedlich sind, werden Sie das Gefühl bekommen, welche Sie verwenden sollen, sobald Sie mit der Implementierung Ihres Falls beginnen, meistens wenn Sie feststellen, dass Sie in Ihrer Mapping-Funktion einige aufrufen müssen Code von Ihren anderen Modulen (wie Repositories), die zurückkehren LiveData.


tolle Erklärung. Ich habe ein klares Bild bezüglich der Unterschiede, aber was wäre das Problem, wenn wir die 'map' anstelle der 'switchmap' verwenden, um ein Benutzerobjekt zu erhalten, wenn nach einem Benutzer gesucht wird, und es jedes Mal in die gleichen Livedata einschließen. Warum müssen wir die Livedata-Instanz selbst ändern, wenn wir nach einem neuen Benutzer fragen? @ Frangulyan
Hari Kiran

@HariKiran Da das Ergebnis einer Einzelbenutzersuche dynamisch ist, ändert es sich im Laufe der Zeit. Deshalb handelt es sich um Live-Daten. Stellen Sie sich vor, Sie rufen an val searcResultLiveData = database.getFirstUserMatching("alice")und hören sich die Änderungen der Suchergebnisse für "Alice" an. Dann müssen Sie es der Zeichenfolge "Gefunden: <Benutzername>" zuordnen. Ihre Zuordnung wird nicht funktionieren , da Sie nennen mapauf , searcResultLiveDataaber sobald Sie den Suchbegriff Ihr Suchergebnis ändern müssen , Live - Daten geändert werden, werden Sie auch die abgebildeten Live - Daten zu aktualisieren.
Frangulyan

1
@ HariKiran In der realen Welt werden Sie die Situation aus einer anderen Perspektive begegnen. Sie erstellen Live-Daten für ein Objekt X- nennen wir es val liveX: LiveData<X>. Und dann müssen Sie ein Live-Datenobjekt behalten, Ydas abhängt von X: val liveY: LiveData<Y>. Es ist logisch, eine Karte zu implementieren : val liveY = liveX.map {...}. Dann beginnen Sie mit der Codierung der Mapping-Funktion in {...}und bam! Sie erkennen, dass Sie in Ihrer Zuordnungsfunktion eine Drittanbieterfunktion (wie einen DB-Aufruf) aufrufen MÜSSEN, die Live-Daten zurückgibt! Dann haben Sie keine andere Wahl , als zu verwenden , switchMapstatt map.
Frangulyan

5
  • Mit mapIhnen haben gleiche Quelle live am Ende , aber es ist Daten (Wert) ändert sich mit der vorgesehenen Funktion vor Emittieren

  • Mit verwenden switchMapSie Quell-Livedata nur als Auslöser für die Rückgabe einer eigenständigen Livedata (natürlich können Sie Trigger-Daten in Ihrer Funktionseingabe verwenden).

  • Auslöser: Alles, was dazu führt, dass der Beobachter von Livedata onChanged()anruft

4

switchMap: Nehmen wir an, wir suchen nach dem Benutzernamen Alice. Das Repository erstellt eine neue Instanz dieser User LiveData-Klasse und danach werden die Benutzer angezeigt. Nach einiger Zeit müssen wir nach dem Benutzernamen Bob suchen, da das Repository eine neue Instanz von LiveData erstellt und unsere Benutzeroberfläche diese LiveData abonniert. In diesem Moment abonniert unsere Benutzeroberfläche zwei Instanzen von LiveData, da wir die vorherige nie entfernen. Dies bedeutet, dass unser Repository bei jeder Änderung der Benutzerdaten ein zweimaliges Abonnement sendet. Wie lösen wir dieses Problem?

Was wir tatsächlich brauchen, ist ein Mechanismus, mit dem wir aufhören können, von der vorherigen Quelle aus zu beobachten, wann immer wir eine neue beobachten wollen. Dazu würden wir switchMap verwenden. Unter der Haube verwendet switchMap MediatorLiveData, mit dem die ursprüngliche Quelle entfernt wird, wenn die neue Quelle hinzugefügt wird. Kurz gesagt, es übernimmt den gesamten Mechanismus zum Entfernen und Hinzufügen eines neuen Beobachters für uns.

Die Karte ist jedoch statisch und wird verwendet, wenn Sie nicht jedes Mal neue Live-Daten abrufen müssen


0

Kurz gesagt, die Benennung ist analog zu rx map / switchMap.

Die Zuordnung ist eine 1: 1-Zuordnung, die leicht zu verstehen ist.

SwitchMap hingegen ordnet jeweils nur den aktuellsten Wert zu, um unnötige Berechnungen zu reduzieren.

Hoffe, diese kurze Version der Antwort kann das Problem aller leicht lösen.


0

Transformation.map ()

fun <X, Y> map(trigger: LiveData<X>, mapFunction: Function<X, Y> ): LiveData<Y>?

trigger- Die LiveData-Variable, die einmal geändert wurde, löst mapFunctiondie Ausführung aus.

mapFunction- Die Funktion, die aufgerufen werden soll, wenn eine Änderung an den triggerLiveData vorgenommen wird. Parameter X ist eine Referenz auf trigger(via it). Die Funktion gibt ein Ergebnis des angegebenen Typs Y zurück, das letztendlich map()als LiveData-Objekt zurückgegeben wird.

Verwenden map()Sie diese Option, wenn Sie eine Operation (über mapFunction) ausführen möchten, wenn sich die triggerLiveData-Variable ändert. map()gibt ein LiveData-Objekt zurück, das beim Aufruf beachtet werden mapFunctionsollte.

Beispiel:

Nehmen Sie eine einfache Liste von Bowlernamen an, deren Durchschnitt und Durchschnitt mit Handicap:

data class Bowler(val name:String, val average:Int, var avgWHDCP:Int)
var bowlers = listOf<Bowler>(Bowler("Steve", 150,150), Bowler ("Tom", 210, 210))

Angenommen, eine MutableLiveData IntVariable enthält einen Handicap-Inkrementwert. Wenn sich dieser Wert ändert, müssen avgWHDCPalle Bowler in der Liste neu berechnet werden. Anfangs wird es auf Null gesetzt.

var newHDCP:MutableLiveData<Int> = MutableLiveData(0)

Erstellen Sie eine Variable, die aufruft Tranformation.map(). Das erste Argument ist newHDCP. Das zweite Argument ist die Funktion, die bei newHDCPÄnderungen aufgerufen werden soll . In diesem Beispiel durchläuft die Funktion alle Bowlerobjekte, berechnet die neue avgWHDCPfür jeden Bowler in der Bowlerliste und gibt das Ergebnis als beobachtbare Liste von LiveData Bowler-Objekten zurück. Beachten Sie, dass in diesem Beispiel die ursprüngliche Nicht-LiveData-Liste der Bowler und die zurückgegebene Liste der Bowler denselben Wert widerspiegeln, da sie auf denselben Datenspeicher verweisen. Das Ergebnis der Funktion ist jedoch beobachtbar. Die ursprüngliche Liste der Bowler ist nicht so, wie sie nicht als LiveData eingerichtet wurde.

var updatedBowlers: LiveData<List<Bowler>> = Transformations.map(newHDCP) {
    bowlers.forEach { bowler ->
        bowler.avgWHDCP  = bowler.average +  it
    }
    return@map bowlers
}

Fügen Sie irgendwo in Ihrem Code eine Methode zum Aktualisieren hinzu newHDCP. In meinem Beispiel wird beim Klicken auf ein Optionsfeld newHDCPetwas geändert, und der Prozess wird ausgelöst, um die in angegebene Funktion aufzurufenTransformations.map()

rbUpdateBy20.setOnCheckedChangeListener { _, isChecked ->
        viewModel.bowlingBallObject.newHDCP.value = 20
}

Schließlich funktioniert dies alles nur, wenn updatedBowlerses beachtet wird. Dies würde in Ihrer Aktivität oder Fragment in einer Methode wie platziertOnViewCreated()

viewModel.updatedBowlers.observe(viewLifecycleOwner, Observer { bowler ->
    if (viewLifecycleOwner.lifecycle.currentState == Lifecycle.State.RESUMED) {
        refreshRecycler()
    }
})

Wenn Sie etwas prägnanter werden möchten und wirklich keinen Live-Verweis benötigen updatedBowlers, können Sie Folgendes updateBowlersmit dem Beobachter kombinieren :

Transformations.map(viewModel.newHDCP) {
    viewModel.bowlers.forEach { bowler ->
        bowler.avgWHDCP  = bowler.average +  it
    }
    return@map viewModel.bowlers
}.observe(viewLifecycleOwner, Observer { bowler ->
    if (viewLifecycleOwner.lifecycle.currentState == Lifecycle.State.RESUMED) {
        refreshRecycler()
    }
})

Und das war's auch schon. Jedes Mal, wenn Sie den Wert von ändern newHDCP, wird die in angegebene Funktion Transformation.map()aufgerufen, das Bowler-Objekt mit dem neu berechneten transformiert avgWHDCPund ein LiveData-Objekt von zurückgegebenList<Bowler>

Transformation.switchMap ()

fun <X, Y> switchMap(source: LiveData<X>, switchMapFunction: Function<X, LiveData<Y>!>): LiveData<Y>

source- Die LiveData-Variable, die nach dem Ändern die switchMapFunctionAusführung auslöst .

switchMapFunction- Die Funktion, die aufgerufen werden soll, wenn eine Änderung an den LiveData-Quellen vorgenommen wird. Parameter X bezieht sich auf dasselbe Quellobjekt (via it). Die switchMapFunctionFunktion MUSS ein LiveData-Ergebnis zurückgeben, das effektiv über zurückgegeben wird Transformation.switchMap(). Im Wesentlichen können Sie so eine Referenz eines LiveData-Containerobjekts gegen eine andere austauschen.

Verwenden switchMap()Sie diese Option, wenn Sie eine Variable haben, die auf ein LiveData-Objekt verweist, und diese Variable auf eine andere umschalten oder auf andere Weise den vorhandenen LiveData-Container aktualisieren möchten. Dies ist beispielsweise nützlich, wenn Ihre LiveData-Variable auf einen Datenbankdatenspeicher verweist und Sie eine Anforderung mit verschiedenen Parametern durchführen möchten. switchMapMit dieser Option können Sie die Abfrage erneut ausführen und durch neue LiveData-Ergebnisse ersetzen.

Beispiel :

Angenommen, ein Datenbank-Repository mit einer Reihe von Bowlingkugel-Abfragen aus einer BowlingBall-DAO-Tabelle:

private val repository  = BowlingBallRepository(application)

Und ich möchte eine Abfrage ausführen, die aktive oder inaktive Bowlingkugeln abruft, je nachdem, was der Benutzer angibt. Über die Benutzeroberfläche kann der Benutzer aktiv oder inaktiv auswählen, sodass meine Abfrage beide verarbeiten muss. Also erstelle ich eine MutableLiveDataVariable, die einen aktiven oder inaktiven Status hat. In diesem Beispiel ist standardmäßig 'A' für aktiv.

var activeFlag:MutableLiveData<String> = MutableLiveData(“A”)

Jetzt benötigen wir eine LiveData-Variable, die das Ergebnis meiner Abfrage enthält, um alle Bowlingkugeln eines bestimmten Status abzurufen. Also erstelle ich eine Variable namens allBowlingBallstype LiveData<List<BowlingBallTable>>?und weise sie zu Transformation.switchMap. Ich übergebe der switchMapFunktion die activeFlagVariable sowie eine Lambda-Funktion, die dieselbe activeFlagVariable (via it) empfängt, und die Funktion ruft eine Abfrage im DB-Repository auf, um alle Bowlingkugeln mit dem übergebenen Status erneut abzurufen. Das LiveData-Ergebnis der Lambda-Funktion durchläuft die switchMapMethode und wird neu zugewiesen allBowlingBalls.

private var allBowlingBalls: LiveData<List<BowlingBallTable>>? = Transformations.switchMap(activeFlag) {repository.getAllBalls(it)}

Ich brauche einen Weg, um eine Aktualisierung von auszulösen allBowlibgBalls. Dies wird wiederum bei activeFlagÄnderungen durchgeführt. Fügen Sie irgendwo in Ihrem Code eine zu aktualisierende Funktion hinzu activeFlag. In meinem Beispiel wird beim Klicken auf ein Optionsfeld activeFlagetwas geändert, und der Prozess wird ausgelöst, um die in angegebene Funktion aufzurufenTransformations.switchMap()

rbActive.setOnCheckedChangeListener { _, isChecked ->
    if (isChecked) {
        viewModel.activeFlag.value = ActiveInactive.ACTIVE.flag
        refreshRecycler()
    }
}

Schließlich funktioniert dies alles nur, wenn allBowlingBalls beobachtet wird. Erstellen Sie also zuerst eine Funktion zum Abrufen von allBowlingBalls:

fun getAllBowlingBalls():LiveData<List<BowlingBallTable>>? {
    return  allBowlingBalls
}

Dann setzen Sie einen Beobachter auf getAllBowlingBalls():

viewModel.getAllBowlingBalls()?.observe(viewLifecycleOwner, Observer { balls ->
    if (viewLifecycleOwner.lifecycle.currentState == Lifecycle.State.RESUMED) {
        refreshRecycler()
    }
})

Und das war's. Jedes Mal activeFlag, wenn sich Änderungen ergeben, allBowlingBallswird es mit einem Aufruf des Repositorys aktualisiert und das onChangeEreignis des Beobachters allBowlingBallswird ausgelöst. Eine einfache Technik zum Aufbau einer dynamischen Suchmaschine.


0

Lassen Sie mich anhand eines Beispiels erklären, was ich verstanden habe. Betrachten Sie eine Schülerdatenklasse

data class Student(val name: String, val marks: Int)

Transformation.map ()

Wandelt den Wert von LiveData in einen anderen um. Es nimmt den Wert, wendet die Funktion auf den Wert an und legt die Ausgabe der Funktion als Wert für die zurückgegebenen LiveData fest. Hier ist ein Beispiel, wie dies für die obige Datenklasse verwendet werden kann:

 val student: LiveData<Student> = (get liveData<Student> from DB or network call)
 val studentName: LiveData<String> = Transformations.map(student) {it.name}

Hier erhalten wir LiveData eines Schülers aus einem Netzwerk oder einer Datenbank. Anschließend nehmen wir den Wert aus LiveData, das das Student-Objekt ist, und rufen einfach den Namen des Schülers ab und ordnen ihn einem anderen LiveData zu.

Transformation.switchMap ()

Wandelt den Wert einer LiveData in eine andere LiveData um. Stellen Sie sich vor, wir möchten eine Suchfunktion für Schüler implementieren. Jedes Mal, wenn sich der Suchtext ändert, möchten wir die Suchergebnisse aktualisieren. Der folgende Code zeigt, wie das funktioniert.

val searchQuery: LiveData<String> = ...

val searchResults: LiveData<List<Student>> = 
    Transformations.switchMap(searchQuery) { getSearchResults(it) }

fun getSearchResults(query: String): LiveData<List<Student>> = (get liveData<List<Student>> from DB or network call)

Jedes Mal, wenn in searchQuery ein neuer Wert vorhanden ist, wird getSearchResults mit einer neuen Suchabfrage aufgerufen und searchResults wird aktualisiert.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.