ViewModel – Übersicht Teil von Android Jetpack.
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.
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
oderResources
, 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
Empfehlungen für dich
- Hinweis: Linktext wird angezeigt, wenn JavaScript deaktiviert ist.
- Kotlin-Coroutinen mit lebenszyklusbewussten Komponenten verwenden
- UI-Zustände speichern
- Paginierte Daten laden und anzeigen