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.
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 wieContext
oderResources
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
- UI-Ebene
- Ereignisse auf der Benutzeroberfläche
- State Owners und UI-Status
- Statusproduktion
- Datenschicht
Produktproben
Empfehlungen für dich
- Hinweis: Der Linktext wird angezeigt, wenn JavaScript deaktiviert ist.
- Kotlin-Koroutinen mit lebenszyklusbezogenen Komponenten verwenden
- UI-Status speichern
- Daten aus Seiten laden und anzeigen