Ein Android ViewModel manuell löschen?

Lesezeit: 10 Minuten

Benutzer-Avatar
Richard Le Mesurier

Bearbeiten: Diese Frage ist jetzt etwas veraltet, da Google uns die Möglichkeit gegeben hat, sie zu erfassen ViewModel zu den Navigationsgrafiken. Der bessere Ansatz (anstatt zu versuchen, aktivitätsbezogene Modelle zu löschen) wäre, spezifische Navigationsdiagramme für die richtige Anzahl von Bildschirmen und den entsprechenden Umfang zu erstellen.


Unter Bezugnahme auf die android.arch.lifecycle.ViewModel Klasse.

ViewModel ist auf den Lebenszyklus der UI-Komponente beschränkt, auf die sie sich bezieht, also in a Fragment-basierte App, das wird der Fragment-Lebenszyklus sein. Das ist eine gute Sache.


In einigen Fällen möchte man a teilen ViewModel Instanz zwischen mehreren Fragmenten. Insbesondere interessiert mich der Fall, wo Viele Bildschirme beziehen sich auf dieselben zugrunde liegenden Daten.

(Die Dokumentation schlägt einen ähnlichen Ansatz vor, wenn mehrere verwandte Fragmente auf demselben Bildschirm angezeigt werden, aber dies kann umgangen werden, indem ein einzelnes Hostfragment gemäß der folgenden Antwort verwendet wird.)

Dies wird in der diskutiert offizielle ViewModel-Dokumentation:

ViewModels können auch als Kommunikationsschicht zwischen verschiedenen Fragmenten einer Aktivität verwendet werden. Jedes Fragment kann das ViewModel mit demselben Schlüssel über seine Aktivität abrufen. Dies ermöglicht eine entkoppelte Kommunikation zwischen Fragmenten, sodass sie nie direkt mit dem anderen Fragment sprechen müssen.

Mit anderen Worten, um Informationen zwischen Fragmenten auszutauschen, die verschiedene Bildschirme darstellen, die ViewModel sollte sich auf die erstrecken Activity Lebenszyklus (und laut Android-Dokumentation kann dies auch in anderen gemeinsam genutzten Instanzen verwendet werden).


Im neuen Jetpack-Navigationsmuster wird jetzt empfohlen, eine „Eine Aktivität/Viele Fragmente“-Architektur zu verwenden. Das bedeutet, dass die Aktivität für die gesamte Nutzungsdauer der App aktiv ist.

dh alle geteilt ViewModel Instanzen, die im Bereich sind Activity Lebenszyklus wird nie gelöscht – der Speicher bleibt in ständiger Verwendung.

Um Speicherplatz zu sparen und zu jedem Zeitpunkt so wenig wie nötig zu verwenden, wäre es schön, Shared löschen zu können ViewModel Instanzen, wenn sie nicht mehr benötigt werden.


Wie kann man a ViewModel von seinem ViewModelStore oder Halterfragment?

  • verwandt: Shared ViewModel-Lebenszyklus für Android JetPack

    – Richard Le Mesurier

    6. Dezember 2018 um 14:05 Uhr

  • Hey! Wie wäre es, wenn Sie Ihr eigenes beibehaltenes Fragment erstellen und Ihr Ansichtsmodell auf dieses beibehaltene Fragment ausrichten? Jetzt haben Sie die vollständige Kontrolle über den Lebenszyklus Ihres Ansichtsmodells. Sie müssen die Aktivität nur dazu bringen, das Fragment bei Bedarf hinzuzufügen oder zu entfernen, und das beibehaltene Fragment und andere Fragmente durch die Aktivität miteinander verbinden. Es hört sich zwar an, als würde man einen Boiler-Plate-Code schreiben, aber ich möchte wissen, was Sie denken.

    – Archie G. Quiñones

    26. Februar 2019 um 8:01 Uhr

  • Ich habe keine Ahnung, ob es in Ordnung ist, getTargetFragment() für den Bereich zu verwenden: ViewModelProvider(requireNotNull(targetFragment)).get(MyViewModel::class.java)

    Benutzer4003256

    22. Oktober 2019 um 10:07 Uhr


  • Ja, es gibt eine Möglichkeit, ich habe es hier erklärt

    – Mostafa Arian Nejad

    6. Januar 2020 um 17:50 Uhr

  • für Leute, die versuchen, die aktualisierte Lösung zu implementieren, gehen Sie hier medium.com/androiddevelopers/…

    – hushed_voice

    20. Mai 2020 um 18:22 Uhr

Schnelle Lösung ohne zu verwenden Navigation Component Bibliothek:

getActivity().getViewModelStore().clear();

Dies wird dieses Problem lösen, ohne die einzubeziehen Navigation Component Bibliothek. Es ist auch eine einfache Codezeile. Es wird diese löschen ViewModels die zwischen geteilt werden Fragments über die Activity

Benutzer-Avatar
Robert Nagy

Wenn Sie den Code überprüfen hier Sie werden herausfinden, dass Sie die bekommen können ViewModelStore von einem ViewModelStoreOwner und Fragment, FragmentActivity zum Beispiel implementiert diese Schnittstelle.

Also von dort aus könntest du einfach anrufen viewModelStore.clear()die, wie die Dokumentation sagt:

 /**
 *  Clears internal storage and notifies ViewModels that they are no longer used.
 */
public final void clear() {
    for (ViewModel vm : mMap.values()) {
        vm.clear();
    }
    mMap.clear();
}

Hinweis: Dadurch werden alle verfügbaren ViewModels für den bestimmten LifeCycleOwner gelöscht, Sie können jedoch kein bestimmtes ViewModel löschen.

  • Sehr schön, ich habe in diese Richtung geschaut, aber den offensichtlichen Teil verpasst, der, wie Sie sagen, “FragmentActivity … implementiert, diese Schnittstelle [ViewModelStoreOwner]”.

    – Richard Le Mesurier

    11. Dezember 2018 um 5:17 Uhr


  • Ok, wir könnten das ViewModel manuell löschen, aber ist es eine gute Idee? Wenn ich das Ansichtsmodell durch diese Methode lösche, gibt es etwas, auf das ich achten oder sicherstellen sollte, dass ich es richtig gemacht habe?

    – Archie G. Quiñones

    23. Februar 2019 um 4:33 Uhr


  • Mir ist auch aufgefallen, dass Sie nicht nur ein bestimmtes Ansichtsmodell löschen konnten, was der Fall sein sollte. Wenn Sie viewmodelstoreowner.clear() aufrufen, werden alle gespeicherten Ansichtsmodelle gelöscht.

    – Archie G. Quiñones

    26. Februar 2019 um 2:59 Uhr

  • Ein Wort der Warnung dabei, wenn Sie die neue verwenden SavedStateViewModelFactory Um ein bestimmtes Ansichtsmodell zu erstellen, müssen Sie anrufen savedStateRegistry.unregisterSavedStateProvider(key) – Der Schlüssel ist derjenige, den Sie verwenden sollten, wenn Sie anrufen ViewModelProvider(~).get(key, class). Andernfalls, wenn Sie versuchen, das Ansichtsmodell in Zukunft zu erhalten (dh zu erstellen), erhalten Sie es IllegalArgumentException: SavedStateProvider with the given key is already registered

    – hmac

    28. November 2019 um 19:16 Uhr


Benutzer-Avatar
gedämpfte_stimme

Wie OP und Archie sagten, hat Google uns die Möglichkeit gegeben, ViewModel auf Navigationsdiagramme auszurichten. Ich werde hier hinzufügen, wie es geht, wenn Sie die Navigationskomponente bereits verwenden.

Sie können alle Fragmente auswählen, die innerhalb des Navigationsdiagramms gruppiert werden müssen, und right-click->move to nested graph->new graph

Jetzt werden die ausgewählten Fragmente wie folgt in ein verschachteltes Diagramm innerhalb des Hauptnavigationsdiagramms verschoben:

<navigation app:startDestination="@id/homeFragment" ...>
    <fragment android:id="@+id/homeFragment" .../>
    <fragment android:id="@+id/productListFragment" .../>
    <fragment android:id="@+id/productFragment" .../>
    <fragment android:id="@+id/bargainFragment" .../>

    <navigation 
        android:id="@+id/checkout_graph" 
        app:startDestination="@id/cartFragment">

        <fragment android:id="@+id/orderSummaryFragment".../>
        <fragment android:id="@+id/addressFragment" .../>
        <fragment android:id="@+id/paymentFragment" .../>
        <fragment android:id="@+id/cartFragment" .../>

    </navigation>

</navigation>

Tun Sie dies jetzt innerhalb der Fragmente, wenn Sie das Ansichtsmodell initialisieren

val viewModel: CheckoutViewModel by navGraphViewModels(R.id.checkout_graph)

Wenn Sie die Viewmodel-Fabrik passieren müssen (möglicherweise zum Injizieren des Viewmodels), können Sie dies folgendermaßen tun:

val viewModel: CheckoutViewModel by navGraphViewModels(R.id.checkout_graph) { viewModelFactory }

Stellen Sie sicher, dass es R.id.checkout_graph und nicht R.navigation.checkout_graph

Aus irgendeinem Grund das Nav-Diagramm erstellen und verwenden include Es in das Hauptnavigationsdiagramm zu verschachteln, funktionierte bei mir nicht. Wahrscheinlich ist es ein Bug.

Quelle: https://medium.com/androiddevelopers/viewmodels-with-saved-state-jetpack-navigation-data-binding-and-coroutines-df476b78144e

Danke, OP und @Archie, dass sie mich in die richtige Richtung gewiesen haben.

  • Ja … ich wollte nur den “id”-Teil betonen

    – hushed_voice

    21. Mai 2020 um 15:59 Uhr

  • Gutes Zeug. Ich wollte nicht einspringen und es selbst ändern, falls das die Absicht war.

    – Richard Le Mesurier

    23. Mai 2020 um 6:20 Uhr

  • Damit kann man scheinbar nicht argumentieren. Das Teildiagramm enthält die Aktionen für das Fragment, aber es generiert die Richtungen nicht richtig, um die Argumente zu übernehmen.

    – Brillant Pappin

    2. Oktober 2020 um 14:34 Uhr


Benutzer-Avatar
Archie G. Quiñones

Ich glaube, ich habe eine bessere Lösung.

Wie von @Nagy Robi angegeben, könnten Sie die löschen ViewModel per Anruf viewModelStore.clear(). Das Problem dabei ist, dass ALLE darin enthaltenen Ansichtsmodelle gelöscht werden ViewModelStore. Mit anderen Worten, Sie haben keine Kontrolle darüber, welche ViewModel zu löschen.

Aber laut @mikehc hier. Wir könnten tatsächlich unsere eigenen erstellen ViewModelStore stattdessen. Dadurch können wir genau steuern, in welchem ​​Umfang das ViewModel vorhanden sein muss.

Hinweis: Ich habe niemanden gesehen, der diesen Ansatz verwendet, aber ich hoffe, dass dies ein gültiger ist. Dies ist eine wirklich gute Möglichkeit, Bereiche in einer Einzelaktivitätsanwendung zu steuern.

Bitte geben Sie einige Rückmeldungen zu diesem Ansatz. Alles wird geschätzt.

Aktualisieren:

Seit Navigationskomponente v2.1.0-alpha02, ViewModels könnten nun einem Flow zugeordnet werden. Der Nachteil dabei ist, dass Sie implementieren müssen Navigation Component zu Ihrem Projekt und Sie haben auch keine granulare Kontrolle über den Umfang Ihres Projekts ViewModel. Aber das scheint eine bessere Sache zu sein.

Wenn Sie das nicht möchten ViewModel umfasst zu werden Activity Lebenszyklus, können Sie es auf den Lebenszyklus des übergeordneten Fragments beschränken. Wenn Sie also eine Instanz der ViewModel Bei mehreren Fragmenten in einem Bildschirm können Sie die Fragmente so anordnen, dass sie alle ein gemeinsames übergeordnetes Fragment teilen. Auf diese Weise, wenn Sie die instanziieren ViewModel Sie können einfach dies tun:

CommonViewModel viewModel = ViewModelProviders.of(getParentFragment()).class(CommonViewModel.class);

Hoffentlich hilft das!

  • Was Sie schreiben, ist wahr, aber dies ist für einen Fall, in dem ich es auf den Umfang beschränken möchte Activity Lebenszyklus, insbesondere um es zwischen mehreren Fragmenten zu teilen, die möglicherweise nicht gleichzeitig angezeigt werden. Dies ist eine gute Antwort in dem anderen Fall, den ich erwähnt habe, und ich denke, ich muss meine Frage aktualisieren, um diesen Fall zu entfernen (da dies zu Verwirrung führt – Entschuldigung dafür).

    – Richard Le Mesurier

    6. Dezember 2018 um 14:17 Uhr

Benutzer-Avatar
Oleksandr

Es scheint, als wäre es bereits in der neuesten Version der Architekturkomponenten gelöst worden.

ViewModelProvider hat folgenden Konstruktor:

    /**
 * Creates {@code ViewModelProvider}, which will create {@code ViewModels} via the given
 * {@code Factory} and retain them in a store of the given {@code ViewModelStoreOwner}.
 *
 * @param owner   a {@code ViewModelStoreOwner} whose {@link ViewModelStore} will be used to
 *                retain {@code ViewModels}
 * @param factory a {@code Factory} which will be used to instantiate
 *                new {@code ViewModels}
 */
public ViewModelProvider(@NonNull ViewModelStoreOwner owner, @NonNull Factory factory) {
    this(owner.getViewModelStore(), factory);
}

Was im Fall von Fragment Scoped ViewModelStore verwenden würde.

androidx.fragment.app.Fragment#getViewModelStore

    /**
 * Returns the {@link ViewModelStore} associated with this Fragment
 * <p>
 * Overriding this method is no longer supported and this method will be made
 * <code>final</code> in a future version of Fragment.
 *
 * @return a {@code ViewModelStore}
 * @throws IllegalStateException if called before the Fragment is attached i.e., before
 * onAttach().
 */
@NonNull
@Override
public ViewModelStore getViewModelStore() {
    if (mFragmentManager == null) {
        throw new IllegalStateException("Can't access ViewModels from detached fragment");
    }
    return mFragmentManager.getViewModelStore(this);
}

androidx.fragment.app.FragmentManagerViewModel#getViewModelStore

    @NonNull
ViewModelStore getViewModelStore(@NonNull Fragment f) {
    ViewModelStore viewModelStore = mViewModelStores.get(f.mWho);
    if (viewModelStore == null) {
        viewModelStore = new ViewModelStore();
        mViewModelStores.put(f.mWho, viewModelStore);
    }
    return viewModelStore;
}

  • Was Sie schreiben, ist wahr, aber dies ist für einen Fall, in dem ich es auf den Umfang beschränken möchte Activity Lebenszyklus, insbesondere um es zwischen mehreren Fragmenten zu teilen, die möglicherweise nicht gleichzeitig angezeigt werden. Dies ist eine gute Antwort in dem anderen Fall, den ich erwähnt habe, und ich denke, ich muss meine Frage aktualisieren, um diesen Fall zu entfernen (da dies zu Verwirrung führt – Entschuldigung dafür).

    – Richard Le Mesurier

    6. Dezember 2018 um 14:17 Uhr

Benutzer-Avatar
Dhabensky

Ich schreibe gerade eine Bibliothek, um dieses Problem zu lösen: scoped-vm, schau es dir gerne an und ich freue mich über jedes Feedback. Unter der Haube verwendet es den von @Archie erwähnten Ansatz – es verwaltet einen separaten ViewModelStore pro Bereich. Aber es geht noch einen Schritt weiter und löscht ViewModelStore selbst, sobald das letzte Fragment, das Viewmodel von diesem Bereich angefordert hat, zerstört wird.

Ich sollte sagen, dass derzeit das gesamte Viewmodel-Management (und diese Bibliothek besonders) von a betroffen ist schwerwiegender Fehler mit dem Backstack, hoffentlich wird es behoben.

Zusammenfassung:

  • Wenn Sie sich darum kümmern ViewModel.onCleared() nicht angerufen wird, ist es (vorerst) am besten, es selbst zu löschen. Aufgrund dieses Fehlers haben Sie keine Garantie dafür, dass das Ansichtsmodell von a fragment wird jemals gelöscht.
  • Wenn Sie sich nur Sorgen um durchgesickerte machen ViewModel – Keine Sorge, sie werden wie alle anderen nicht referenzierten Objekte von der Garbage Collection erfasst. Fühlen Sie sich frei, meine Bibliothek für eine feinkörnige Scoping zu verwenden, wenn es Ihren Bedürfnissen entspricht.

  • Ich habe Abonnements implementiert – jedes Mal, wenn ein Fragment anfordert, wird ein viewModel-Abonnement erstellt. Abonnements sind selbst Ansichtsmodelle und werden daher automatisch im ViewModelStore dieses Fragments gelöscht. Ein Abonnement, das ViewModel erweitert, ist gleichzeitig der schönste und hässlichste Teil einer Bibliothek!

    – Dhabensky

    9. April 2019 um 9:23 Uhr

  • Hört sich interessant an! Aktualisieren Sie mich von Zeit zu Zeit damit. Ich würde es mir die Tage wahrscheinlich mal anschauen. 🙂

    – Archie G. Quiñones

    9. April 2019 um 13:01 Uhr

  • @ArchieG.Quiñones Gerade veröffentlicht frische neue Version 0.4. Der Lifecycle-Viewmodel-Fehler scheint in naher Zukunft behoben zu sein, da er P1-Priorität hat und es gibt Letzte Änderungen im Depot. Sobald es behoben ist, plane ich, 1.0 zu gehen

    – Dhabensky

    13. April 2019 um 7:18 Uhr


1312170cookie-checkEin Android ViewModel manuell löschen?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy