ViewModel – Übersicht   Teil von Android Jetpack.

Mit Kotlin Multiplatform testen
Mit Kotlin Multiplatform kann die Geschäftslogik mit anderen Plattformen geteilt werden. ViewModel in KMP einrichten und verwenden

Die Klasse ViewModel ist ein Status-Holder auf Geschäftslogik- oder Bildschirmebene. Sie stellt den Status für die Benutzeroberfläche bereit und kapselt die zugehörige Geschäftslogik. Der Hauptvorteil besteht darin, dass der Status im Cache gespeichert und bei Konfigurationsänderungen beibehalten wird. Das bedeutet, dass Ihre Benutzeroberfläche beim Navigieren zwischen Aktivitäten oder nach Konfigurationsänderungen, z. B. beim Drehen des Bildschirms, keine Daten mehr abrufen muss.

Weitere Informationen zu State-Holdern finden Sie in der Anleitung zu State-Holdern. Weitere Informationen zur UI-Ebene finden Sie in der Anleitung zur UI-Ebene.

Vorteile von ViewModels

Die Alternative zu einem ViewModel ist eine einfache Klasse, die die Daten enthält, die Sie in Ihrer Benutzeroberfläche anzeigen. Das kann zu Problemen führen, wenn Sie zwischen Aktivitäten oder Navigationszielen wechseln. Dadurch werden die Daten zerstört, 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:

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

Persistenz

ViewModel ermöglicht die Persistenz sowohl durch den Status, den ein ViewModel enthält, als auch durch die Vorgänge, die ein ViewModel auslöst. Durch das Caching müssen Daten bei häufigen Konfigurationsänderungen wie einer Bildschirmdrehung nicht noch einmal abgerufen werden.

Umfang

Wenn Sie ein ViewModel instanziieren, übergeben Sie ihm ein Objekt, das die ViewModelStoreOwner-Schnittstelle implementiert. Das kann ein Navigationsziel, ein Navigationsdiagramm, eine Aktivität, ein Fragment oder ein beliebiger anderer Typ sein, der die Schnittstelle implementiert. Ihr ViewModel ist dann auf den Lifecycle der ViewModelStoreOwner beschränkt. Sie bleibt im Arbeitsspeicher, bis die ViewModelStoreOwner endgültig entfernt wird.

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

Wenn das Fragment oder die Aktivität, auf die das ViewModel beschränkt ist, zerstört wird, werden asynchrone Vorgänge im ViewModel, das darauf beschränkt ist, fortgesetzt. Das ist der Schlüssel zur Persistenz.

Weitere Informationen finden Sie unten im Abschnitt ViewModel-Lebenszyklus.

SavedStateHandle

Mit SavedStateHandle können Sie Daten nicht nur bei Konfigurationsänderungen, sondern auch bei der Neuerstellung von Prozessen beibehalten. So können Sie den UI-Status beibehalten, auch wenn der Nutzer die App schließt und später wieder öffnet.

Zugriff auf Geschäftslogik

Obwohl sich die Geschäftslogik größtenteils in der Datenschicht befindet, kann sie auch in der UI-Schicht enthalten sein. Das kann der Fall sein, wenn Daten aus mehreren Repositories kombiniert werden, um den UI-Status des Bildschirms zu erstellen, oder wenn für einen bestimmten Datentyp keine Datenschicht erforderlich ist.

ViewModel ist der richtige Ort, um Geschäftslogik in der UI-Schicht zu verarbeiten. Das ViewModel ist auch für die Verarbeitung von Ereignissen und die Weiterleitung an andere Ebenen der Hierarchie zuständig, wenn Geschäftslogik angewendet werden muss, um Anwendungsdaten zu ändern.

Jetpack Compose

Wenn Sie Jetpack Compose verwenden, ist ViewModel die primäre Methode, um den UI-Status des Bildschirms für Ihre Composables verfügbar zu machen. In einer Hybrid-App werden Ihre zusammensetzbaren Funktionen einfach in Aktivitäten und Fragmenten gehostet. Das ist ein Wandel gegenüber früheren Ansätzen, bei denen es nicht so einfach und intuitiv war, wiederverwendbare UI-Elemente mit Aktivitäten und Fragmenten zu erstellen. Dadurch waren sie als UI-Controller viel aktiver.

Das Wichtigste, was Sie bei der Verwendung von ViewModel mit Compose beachten müssen, ist, dass Sie ein ViewModel nicht auf ein Composable beschränken können. Das liegt daran, dass ein Composable kein ViewModelStoreOwner ist. Zwei Instanzen desselben Composables in der Komposition oder zwei verschiedene Composables, die auf denselben ViewModel-Typ unter demselben ViewModelStoreOwner zugreifen, würden die gleiche Instanz des ViewModels erhalten, was oft nicht das erwartete Verhalten ist.

Wenn Sie die Vorteile von ViewModel in Compose nutzen möchten, hosten Sie jeden Bildschirm in einem Fragment oder einer Aktivität oder verwenden Sie Compose-Navigation und 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 beschränken können.

Weitere Informationen finden Sie im Leitfaden zum State Hoisting für Jetpack Compose.

ViewModel implementieren

Das Folgende ist ein Beispiel für die Implementierung eines ViewModel für einen Bildschirm, auf dem der Nutzer würfeln 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 so auf das ViewModel aus einer Aktivität 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 bietet Unterstützung für Kotlin-Coroutinen. Sie kann asynchrone Vorgänge auf dieselbe Weise beibehalten wie den UI-Status.

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

Der Lebenszyklus eines ViewModels

Der Lebenszyklus eines ViewModel ist direkt an seinen Bereich gebunden. Ein ViewModel bleibt im Arbeitsspeicher, bis das ViewModelStoreOwner, auf das es beschränkt ist, verschwindet. Dies kann in den folgenden Kontexten auftreten:

  • Bei einer Aktivität, wenn sie abgeschlossen ist.
  • Bei einem Fragment, wenn es sich löst.
  • Bei einem Navigationseintrag, wenn er aus dem Backstack entfernt wird.

ViewModels sind daher eine gute Lösung zum Speichern von Daten, die Konfigurationsänderungen überdauern.

Abbildung 1 zeigt die verschiedenen Lebenszyklusstatus einer Aktivität, während sie durch eine Rotation geht und dann beendet wird. Die Abbildung zeigt auch die Lebensdauer des ViewModel neben dem zugehörigen Aktivitätslebenszyklus. In diesem Diagramm werden die Status einer Aktivität veranschaulicht. Für den Lebenszyklus eines Fragments gelten dieselben grundlegenden Status.

Hier wird der Lebenszyklus eines ViewModels veranschaulicht, wenn sich der Status einer Aktivität ändert.

Normalerweise fordern Sie ein ViewModel an, wenn das System zum ersten Mal die Methode onCreate() eines Aktivitätsobjekts aufruft. Das System kann onCreate() während der Lebensdauer einer Aktivität mehrmals aufrufen, z. B. wenn der Bildschirm eines Geräts gedreht wird. Die ViewModel ist ab dem Zeitpunkt vorhanden, an dem Sie zum ersten Mal eine ViewModel anfordern, bis die Aktivität beendet und zerstört wird.

ViewModel-Abhängigkeiten löschen

Das ViewModel ruft die Methode onCleared auf, wenn ViewModelStoreOwner es im Laufe seines Lebenszyklus zerstört. So können Sie alle Aufgaben oder Abhängigkeiten bereinigen, die dem Lebenszyklus des ViewModels folgen.

Das folgende Beispiel zeigt eine Alternative zu viewModelScope. viewModelScope ist ein integriertes CoroutineScope, das automatisch dem Lebenszyklus des ViewModels folgt. Das ViewModel verwendet es, um geschäftsbezogene Vorgänge auszulösen. Wenn Sie anstelle von viewModelScope einen benutzerdefinierten Bereich für einfachere Tests verwenden möchten, kann das ViewModel ein CoroutineScope als Abhängigkeit in seinem Konstruktor empfangen. Wenn die ViewModelStoreOwner das ViewModel am Ende ihres Lebenszyklus löscht, wird auch die CoroutineScope vom ViewModel abgebrochen.

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

    // Other ViewModel logic ...

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

Ab Version 2.5 des Lebenszyklus können Sie ein oder mehrere Closeable-Objekte an den Konstruktor des ViewModels übergeben, die automatisch geschlossen werden, 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:

  • Aufgrund ihrer Reichweite sollten ViewModels als Implementierungsdetails eines State Holders auf Bildschirmebene verwendet werden. Verwenden Sie sie nicht als Statusinhaber wiederverwendbarer UI-Komponenten wie Chip-Gruppen oder Formulare. Andernfalls erhalten Sie dieselbe ViewModel-Instanz bei unterschiedlichen Verwendungen derselben UI-Komponente unter demselben ViewModelStoreOwner, es sei denn, Sie verwenden einen expliziten ViewModel-Schlüssel pro Chip.
  • ViewModels sollten keine Informationen zu den Implementierungsdetails der Benutzeroberfläche haben. Halten Sie die Namen der Methoden, die die ViewModel API bereitstellt, und die der UI-Statusfelder so allgemein wie möglich. So kann Ihr ViewModel jede Art von Benutzeroberfläche unterstützen: Smartphones, Faltgeräte, Tablets oder sogar Chromebooks.
  • Da sie potenziell länger als die ViewModelStoreOwner bestehen können, sollten ViewModels keine Verweise auf APIs enthalten, die mit dem Lebenszyklus zusammenhängen, z. B. Context oder Resources, um Speicherlecks zu vermeiden.
  • Übergeben Sie ViewModels nicht an andere Klassen, Funktionen oder andere UI-Komponenten. Da die Plattform sie verwaltet, sollten Sie sie so nah wie möglich an der Plattform halten. In der Nähe der zusammensetzbaren Funktion auf Aktivitäts-, Fragment- oder Bildschirmebene. So wird verhindert, dass Komponenten auf niedrigerer Ebene auf mehr Daten und Logik zugreifen, als sie benötigen.

Weitere Informationen

Wenn Ihre Daten komplexer werden, können Sie eine separate Klasse nur zum Laden der Daten verwenden. Der Zweck von ViewModel besteht darin, die Daten für einen UI-Controller zu kapseln, damit die Daten Konfigurationsänderungen überstehen. Informationen zum Laden, Speichern und Verwalten von Daten bei Konfigurationsänderungen finden Sie unter Gespeicherte UI-Zustände.

Im Leitfaden zur Android-App-Architektur wird empfohlen, eine Repository-Klasse zu erstellen, um diese Funktionen zu verarbeiten.

Zusätzliche Ressourcen

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

Dokumentation

Produktproben