ViewModel-Übersicht Teil von Android Jetpack

Die Klasse ViewModel ist ein Geschäftslogik oder Statusinhaber auf Bildschirmebene. Er stellt den Status der Benutzeroberfläche dar und kapselt die zugehörige Geschäftslogik. Der wesentliche Vorteil besteht darin, dass der Status im Cache gespeichert wird, damit er bei Konfigurationsänderungen beibehalten wird. Das bedeutet, dass Ihre UI beim Wechseln zwischen Aktivitäten oder nach Konfigurationsänderungen, z. B. beim Drehen des Bildschirms, keine Daten noch einmal abrufen muss.

Weitere Informationen zu Inhabern von Bundesstaaten finden Sie in den Richtlinien für Inhaber von Bundesstaaten. Weitere allgemeine Informationen zur UI-Ebene finden Sie in der Anleitung UI-Ebene.

Vorteile von ViewModel

Die Alternative zu ViewModel ist eine einfache Klasse, die die in Ihrer UI angezeigten Daten enthält. Dies kann bei der Navigation zwischen Aktivitäten oder Navigationszielen zu einem Problem werden. Dadurch werden die Daten gelöscht, wenn Sie sie nicht mit dem Mechanismus zum Speichern des Instanzstatus speichern. ViewModel bietet eine praktische API für die Datenpersistenz, mit der dieses Problem behoben wird.

Die wichtigsten Vorteile der ViewModel-Klasse sind im Wesentlichen zwei:

  • Sie können damit den UI-Status beibehalten.
  • Es bietet Zugriff auf die Geschäftslogik.

Persistenz

ViewModel ermöglicht Persistenz sowohl für den Status, den eine ViewModel-Ressource aufweist, als auch für die Vorgänge, die eine ViewModel-Ressource auslöst. Dieses Caching bedeutet, dass Sie Daten nicht durch häufige Konfigurationsänderungen, wie z. B. eine Bildschirmdrehung, noch einmal abrufen müssen.

Aufgabenstellung

Wenn Sie ein ViewModel instanziieren, übergeben Sie ein Objekt, das die Schnittstelle ViewModelStoreOwner implementiert. Dabei kann es sich um ein Navigationsziel, eine Navigationsgrafik, eine Aktivität, ein Fragment oder einen anderen Typ handeln, der die Schnittstelle implementiert. Ihr ViewModel bezieht sich dann auf den Lebenszyklus der ViewModelStoreOwner. Es bleibt im Arbeitsspeicher, bis sein ViewModelStoreOwner dauerhaft gelöscht wird.

Eine Reihe von Klassen sind entweder direkte oder indirekte abgeleitete Klassen der ViewModelStoreOwner-Schnittstelle. Die direkten abgeleiteten Klassen sind ComponentActivity, Fragment und NavBackStackEntry. Eine vollständige Liste der indirekten abgeleiteten Klassen finden Sie in der Referenz zu ViewModelStoreOwner.

Wenn das Fragment oder die Aktivität gelöscht wird, auf die sich das ViewModel bezieht, wird die asynchrone Arbeit in der zugehörigen ViewModel-Ressource fortgesetzt. Dies ist der Schlüssel zur Persistenz.

Weitere Informationen finden Sie unten im Abschnitt zum ViewModel-Lebenszyklus.

SavedStateHandle

Mit SavedStateHandle können Sie Daten nicht nur durch Konfigurationsänderungen, sondern über mehrere Prozesse hinweg speichern. Das heißt, Sie können den UI-Status beibehalten, auch wenn der Nutzer die App schließt und später öffnet.

Zugriff auf Geschäftslogik

Obwohl ein Großteil der Geschäftslogik in der Datenschicht vorhanden ist, kann die UI-Ebene auch Geschäftslogik enthalten. Dies kann der Fall sein, wenn Daten aus mehreren Repositories kombiniert werden, um den Bildschirm-UI-Status zu erstellen, oder wenn ein bestimmter Datentyp keine Datenschicht erfordert.

ViewModel ist der richtige Ort für die Verarbeitung der Geschäftslogik auf der UI-Ebene. ViewModel ist auch dafür zuständig, Ereignisse zu verarbeiten und sie an andere Hierarchieebenen zu delegieren, wenn Geschäftslogik zur Änderung von Anwendungsdaten angewendet werden muss.

Jetpack Compose

Wenn Sie Jetpack Compose verwenden, ist ViewModel die Hauptmethode, um den Bildschirm-UI-Status für Ihre zusammensetzbaren Funktionen bereitzustellen. In einer hybriden Anwendung hosten Aktivitäten und Fragmente einfach die zusammensetzbaren Funktionen. Dies ist ein Wechsel von früheren Ansätzen, bei denen es nicht so einfach und intuitiv war, wiederverwendbare UI-Elemente mit Aktivitäten und Fragmenten zu erstellen, wodurch diese viel aktiver als UI-Controller waren.

Das Wichtigste bei der Verwendung von ViewModel mit Compose ist, dass Sie ein ViewModel nicht auf eine zusammensetzbare Funktion anwenden können. Das liegt daran, dass eine zusammensetzbare Funktion keine ViewModelStoreOwner ist. Zwei Instanzen derselben zusammensetzbaren Funktion in der Komposition oder zwei verschiedene zusammensetzbare Funktionen, die auf denselben ViewModel-Typ unter demselben ViewModelStoreOwner zugreifen, erhalten dieselbe Instanz des ViewModel, was häufig nicht dem erwarteten Verhalten entspricht.

Hosten Sie jeden Bildschirm in einem Fragment oder einer Aktivität, um die Vorteile von ViewModel in Compose zu nutzen, oder verwenden Sie die Funktion „Navigation erstellen“ und verwenden Sie ViewModels in zusammensetzbaren Funktionen so nah wie möglich am Navigationsziel. Das liegt daran, dass Sie ein ViewModel auf Navigationsziele, Navigationsdiagramme, Aktivitäten und Fragmente festlegen können.

Weitere Informationen finden Sie im Leitfaden zu Zustandswinden für Jetpack Compose.

ViewModel implementieren

Im Folgenden finden Sie eine Beispielimplementierung einer ViewModel für einen Bildschirm, mit dem der Nutzer Würfel werfen kann.

Kotlin

data class DiceUiState(
    val firstDieValue: Int? = null,
    val secondDieValue: Int? = null,
    val numberOfRolls: Int = 0,
)

class DiceRollViewModel : ViewModel() {

    // Expose screen UI state
    private val _uiState = MutableStateFlow(DiceUiState())
    val uiState: StateFlow<DiceUiState> = _uiState.asStateFlow()

    // Handle business logic
    fun rollDice() {
        _uiState.update { currentState ->
            currentState.copy(
                firstDieValue = Random.nextInt(from = 1, until = 7),
                secondDieValue = Random.nextInt(from = 1, until = 7),
                numberOfRolls = currentState.numberOfRolls + 1,
            )
        }
    }
}

Java

public class DiceUiState {
    private final Integer firstDieValue;
    private final Integer secondDieValue;
    private final int numberOfRolls;

    // ...
}

public class DiceRollViewModel extends ViewModel {

    private final MutableLiveData<DiceUiState> uiState =
        new MutableLiveData(new DiceUiState(null, null, 0));
    public LiveData<DiceUiState> getUiState() {
        return uiState;
    }

    public void rollDice() {
        Random random = new Random();
        uiState.setValue(
            new DiceUiState(
                random.nextInt(7) + 1,
                random.nextInt(7) + 1,
                uiState.getValue().getNumberOfRolls() + 1
            )
        );
    }
}

Sie können dann über eine Aktivität wie folgt auf die ViewModel-Ressource zugreifen:

Kotlin

import androidx.activity.viewModels

class DiceRollActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        // Create a ViewModel the first time the system calls an activity's onCreate() method.
        // Re-created activities receive the same DiceRollViewModel instance created by the first activity.

        // Use the 'by viewModels()' Kotlin property delegate
        // from the activity-ktx artifact
        val viewModel: DiceRollViewModel by viewModels()
        lifecycleScope.launch {
            repeatOnLifecycle(Lifecycle.State.STARTED) {
                viewModel.uiState.collect {
                    // Update UI elements
                }
            }
        }
    }
}

Java

public class MyActivity extends AppCompatActivity {
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Create a ViewModel the first time the system calls an activity's onCreate() method.
        // Re-created activities receive the same MyViewModel instance created by the first activity.
        DiceRollViewModel model = new ViewModelProvider(this).get(DiceRollViewModel.class);
        model.getUiState().observe(this, uiState -> {
            // update UI
        });
    }
}

Jetpack Compose

import androidx.lifecycle.viewmodel.compose.viewModel

// Use the 'viewModel()' function from the lifecycle-viewmodel-compose artifact
@Composable
fun DiceRollScreen(
    viewModel: DiceRollViewModel = viewModel()
) {
    val uiState by viewModel.uiState.collectAsStateWithLifecycle()
    // Update UI elements
}

Koroutinen mit ViewModel verwenden

ViewModel unterstützt Kotlin-Koroutinen. Sie kann asynchrone Arbeit auf dieselbe Weise beibehalten, wie der UI-Status beibehalten wird.

Weitere Informationen finden Sie unter Kotlin-Koroutinen mit Android-Architekturkomponenten verwenden.

Der Lebenszyklus einer ViewModel-Ressource

Der Lebenszyklus einer ViewModel ist direkt mit ihrem Umfang verbunden. Eine ViewModel verbleibt im Arbeitsspeicher, bis das ViewModelStoreOwner, dem sie zugeordnet ist, verschwindet. Dies kann in den folgenden Kontexten vorkommen:

  • Im Fall einer Aktivität, wenn diese beendet ist.
  • Ein Fragment, das sich löst.
  • Im Falle eines Navigationseintrags, wenn er aus dem Back Stack entfernt wird.

Daher ist ViewModels eine hervorragende Lösung zum Speichern von Daten, die Konfigurationsänderungen überstehen.

In Abbildung 1 sind die verschiedenen Lebenszyklusstatus einer Aktivität dargestellt, während sie eine Rotation durchläuft und dann abgeschlossen wird. Außerdem ist die Lebensdauer des ViewModel neben dem zugehörigen Aktivitätslebenszyklus zu sehen. Dieses spezielle Diagramm veranschaulicht den Status einer Aktivität. Für den Lebenszyklus eines Fragments gelten dieselben Grundzustände.

Veranschaulicht den Lebenszyklus eines ViewModel in Bezug auf den Status einer Aktivitätsänderung.

In der Regel fordern Sie ein ViewModel-Objekt an, wenn das System zum ersten Mal die Methode onCreate() eines Aktivitätsobjekts aufruft. onCreate() kann während einer Aktivität mehrmals aufgerufen werden, z. B. wenn ein Gerätebildschirm gedreht wird. Die ViewModel besteht ab dem Zeitpunkt der ersten ViewModel-Anfrage bis zum Abschluss und dem Löschen der Aktivität.

ViewModel-Abhängigkeiten löschen

ViewModel ruft die Methode onCleared auf, wenn sie von ViewModelStoreOwner im Laufe ihres Lebenszyklus gelöscht wird. Auf diese Weise können Sie alle Arbeiten oder Abhängigkeiten bereinigen, die dem Lebenszyklus von ViewModel folgen.

Das folgende Beispiel zeigt eine Alternative zu viewModelScope. viewModelScope ist ein integriertes CoroutineScope, das dem Lebenszyklus von ViewModel automatisch folgt. ViewModel verwendet ihn, um geschäftsbezogene Vorgänge auszulösen. Wenn Sie einen benutzerdefinierten Bereich statt viewModelScope für einfachere Tests verwenden möchten, kann ViewModel eine CoroutineScope als Abhängigkeit in seinem Konstruktor erhalten. Wenn ViewModelStoreOwner das ViewModel am Ende seines Lebenszyklus löscht, bricht ViewModel auch CoroutineScope ab.

class MyViewModel(
    private val coroutineScope: CoroutineScope =
        CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
) : ViewModel() {

    // Other ViewModel logic ...

    override fun onCleared() {
        coroutineScope.cancel()
    }
}

Ab Lebenszyklus Version 2.5 können Sie ein oder mehrere Closeable-Objekte an den ViewModel-Konstruktor übergeben. Dieser wird automatisch geschlossen, wenn die ViewModel-Instanz gelöscht wird.

class CloseableCoroutineScope(
    context: CoroutineContext = SupervisorJob() + Dispatchers.Main.immediate
) : Closeable, CoroutineScope {
    override val coroutineContext: CoroutineContext = context
    override fun close() {
        coroutineContext.cancel()
   }
}

class MyViewModel(
    private val coroutineScope: CoroutineScope = CloseableCoroutineScope()
) : ViewModel(coroutineScope) {
    // Other ViewModel logic ...
}

Best Practices

Im Folgenden finden Sie einige wichtige Best Practices, die Sie bei der Implementierung von ViewModel beachten sollten:

  • Verwenden Sie aufgrund des Gültigkeitsbereichs ViewModels als Implementierungsdetails für einen Statusinhaber auf Bildschirmebene. Verwenden Sie sie nicht als Statusinhaber von wiederverwendbaren UI-Komponenten wie Chipgruppen oder Formularen. Andernfalls erhalten Sie dieselbe ViewModel-Instanz bei unterschiedlichen Verwendungen derselben UI-Komponente unter demselben ViewModelStoreOwner, es sei denn, Sie verwenden pro Chip einen expliziten Ansichtsmodellschlüssel.
  • ViewModels sollte die Details der UI-Implementierung nicht kennen. Halten Sie die Namen der Methoden, die von der ViewModel API bereitgestellt werden, und der Namen der UI-Zustandsfelder möglichst allgemein gehalten. Auf diese Weise kann Ihr ViewModel an alle UI-Typen angepasst werden: Mobiltelefon, faltbares Tablet, Tablet oder sogar Chromebook.
  • Da sie potenziell länger als ViewModelStoreOwner sind, sollten ViewModels keine Verweise auf Lebenszyklus-APIs wie Context oder Resources enthalten, um Speicherlecks zu vermeiden.
  • Übergeben Sie ViewModels nicht an andere Klassen, Funktionen oder andere UI-Komponenten. Da sie von der Plattform verwaltet werden, sollten Sie sie so nah wie möglich daran halten. Nah an der zusammensetzbaren Funktion auf Aktivitäts-, Fragment- oder Bildschirmebene. Dadurch wird verhindert, dass Komponenten niedrigerer Ebene auf mehr Daten und Logik zugreifen, als erforderlich sind.

Weitere Informationen

Wenn Ihre Daten komplexer werden, können Sie eine separate Klasse verwenden, um die Daten zu laden. Der Zweck von ViewModel besteht darin, die Daten für einen UI-Controller zu kapseln, damit die Daten Konfigurationsänderungen überstehen. Informationen zum Laden, Beibehalten und Verwalten von Daten über Konfigurationsänderungen hinweg finden Sie unter Gespeicherte UI-Status.

Im Leitfaden zur Android-App-Architektur wird empfohlen, eine Repository-Klasse für diese Funktionen zu erstellen.

Zusätzliche Ressourcen

Weitere Informationen zur Klasse ViewModel finden Sie in den folgenden Ressourcen.

Dokumentation

Produktproben