ViewModel – Übersicht Teil von Android Jetpack
Die Klasse ViewModel
ist ein Inhaber der Geschäftslogik oder eines Status auf Bildschirmebene. Er zeigt den Status für die Benutzeroberfläche an und kapselt die zugehörige Geschäftslogik.
Der Hauptvorteil besteht darin, dass der Status im Cache gespeichert wird und über Konfigurationsänderungen beibehalten wird. Das bedeutet, dass Ihre UI keine Daten noch einmal abrufen muss, wenn Sie zwischen Aktivitäten wechseln oder Änderungen an der Konfiguration vornehmen, z. B. beim Drehen des Bildschirms.
Weitere Informationen zu Inhabern von Bundesstaaten finden Sie im Leitfaden für Inhaber von Bundesstaaten. Weitere allgemeine Informationen zur UI-Ebene finden Sie in der Anleitung UI-Ebene.
ViewModel-Vorteile
Die Alternative zu einem ViewModel ist eine einfache Klasse, die die Daten enthält, die Sie in Ihrer UI anzeigen. Dies kann beim Wechseln zwischen Aktivitäten oder Navigationszielen zu Problemen werden. Dadurch werden diese Daten gelöscht, wenn Sie sie nicht mit dem Mechanismus zum Speichern des Instanzstatus speichern. ViewModel bietet eine praktische API für Datenpersistenz, die dieses Problem löst.
Im Wesentlichen hat die ViewModel-Klasse zwei wichtige Vorteile:
- Sie können damit den UI-Status beibehalten.
- Sie ermöglicht Zugriff auf die Geschäftslogik.
Persistenz
ViewModel ermöglicht Persistenz sowohl über den Status, den eine ViewModel hat, als auch die Vorgänge, die eine ViewModel auslöst. Dieses Caching bedeutet, dass Sie Daten nicht durch gängige Konfigurationsänderungen wie eine Bildschirmdrehung noch einmal abrufen müssen.
Aufgabenstellung
Wenn Sie ein ViewModel instanziieren, übergeben Sie ein Objekt, das die ViewModelStoreOwner
-Schnittstelle implementiert. Dies kann ein Navigationsziel, ein Navigationsdiagramm, eine Aktivität, ein Fragment oder ein anderer Typ sein, mit dem die Schnittstelle implementiert wird. Ihr ViewModel wird dann auf den Lebenszyklus des ViewModelStoreOwner
beschränkt. Es bleibt im Arbeitsspeicher, bis 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, auf die sich die ViewModel bezieht, gelöscht wird, wird die asynchrone Arbeit in der darauf bezogenen ViewModel-Funktion fortgesetzt. Dies ist der Schlüssel zur Beharrlichkeit.
Weitere Informationen finden Sie unten im Abschnitt ViewModel-Lebenszyklus.
SavedStateHandle
SavedStateHandle ermöglicht es Ihnen, Daten nicht nur durch Konfigurationsänderungen, sondern auch über die Neuerstellung von Prozessen beizubehalten. Das heißt, Sie können den Status der Benutzeroberfläche auch dann erhalten, wenn der Nutzer die App schließt und später öffnet.
Zugriff auf Geschäftslogik
Obwohl der Großteil der Geschäftslogik in der Datenebene vorhanden ist, kann die UI-Ebene auch Geschäftslogik enthalten. Das kann der Fall sein, wenn Daten aus mehreren Repositories kombiniert werden, um den Bildschirm-UI-Status zu erstellen, oder wenn für einen bestimmten Datentyp keine Datenschicht erforderlich ist.
ViewModel ist der richtige Ort für die Verarbeitung der Geschäftslogik auf der UI-Ebene. ViewModel verarbeitet außerdem Ereignisse und delegiert sie an andere Hierarchieebenen, wenn Geschäftslogik zur Änderung von Anwendungsdaten angewendet werden muss.
Jetpack Compose
Bei Verwendung von Jetpack Compose ist ViewModel das primäre Mittel, um den Bildschirm-UI-Status für zusammensetzbare Funktionen freizugeben. In einer Hybridanwendung hosten Aktivitäten und Fragmente einfach Ihre zusammensetzbaren Funktionen. Dies ist eine Veränderung 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, wodurch sie als UI-Controller viel aktiver wurden.
Wenn Sie ViewModel mit Compose verwenden, ist es am wichtigsten, dass Sie ein ViewModel nicht auf eine zusammensetzbare Funktion beschränken können. Das liegt daran, dass eine zusammensetzbare Funktion kein ViewModelStoreOwner
ist. Zwei Instanzen derselben zusammensetzbaren Funktion in der Komposition oder zwei verschiedene zusammensetzbare Funktionen, die auf denselben ViewModel-Typ unter derselben ViewModelStoreOwner
zugreifen, erhalten dieselbe Instanz der ViewModel. Das ist oft nicht das erwartete Verhalten.
Um die Vorteile von ViewModel in Compose zu nutzen, hosten Sie jeden Bildschirm in einem Fragment oder einer Aktivität oder verwenden Sie die Funktion "Compose Navigation" und verwenden Sie ViewModels in zusammensetzbaren Funktionen, die so nahe wie möglich am Navigationsziel sind. Das liegt daran, dass Sie ein ViewModel auf Navigationsziele, Navigationsgrafiken, Aktivitäten und Fragmente beschränken können.
Weitere Informationen finden Sie in der Anleitung zu Zustandswinden für Jetpack Compose.
ViewModel implementieren
Im Folgenden sehen 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 wie folgt über eine Aktivität auf das ViewModel 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. Asynchrone Arbeiten können auf dieselbe Weise wie der UI-Status beibehalten werden.
Weitere Informationen finden Sie unter Kotlin-Koroutinen 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
, dem es zugeordnet ist, verschwindet. Dies kann in folgenden Kontexten der Fall sein:
- Bei einer Aktivität, wenn diese beendet ist.
- Bei der Trennung eines Fragments.
- Bei einem Navigationseintrag, wenn er aus dem Back Stack entfernt wird
Daher ist ViewModels eine hervorragende Lösung zum Speichern von Daten, die Konfigurationsänderungen überstehen.
Abbildung 1 zeigt die verschiedenen Lebenszyklusstatus einer Aktivität, während sie eine Rotation durchläuft und dann beendet wird. Die Abbildung zeigt auch die Lebensdauer von ViewModel
neben dem zugehörigen Aktivitätslebenszyklus. Dieses Diagramm veranschaulicht die Zustände einer Aktivität. Dieselben Grundzustände gelten
für den Lebenszyklus eines Fragments.
Normalerweise fordern Sie ein ViewModel
-Objekt an, wenn das System zum ersten Mal die onCreate()
-Methode eines Aktivitätsobjekts aufruft. Wenn eine Aktivität aktiv ist, ruft das System möglicherweise mehrmals onCreate()
auf, z. B. wenn ein Gerätebildschirm gedreht wird. Der ViewModel
existiert ab dem Zeitpunkt der ersten Anfrage eines ViewModel
bis zum Abschluss und Löschen der Aktivität.
ViewModel-Abhängigkeiten löschen
ViewModel ruft die Methode onCleared
auf, wenn ViewModelStoreOwner
sie während ihres Lebenszyklus löscht. So können Sie alle Arbeiten und Abhängigkeiten bereinigen, die dem Lebenszyklus von ViewModel folgen.
Das folgende Beispiel zeigt eine Alternative zu viewModelScope
.
viewModelScope
ist eine integrierte CoroutineScope
, die dem Lebenszyklus von ViewModel automatisch folgt. Das ViewModel löst damit geschäftsbezogene Vorgänge aus. Wenn Sie für einfachere Tests anstelle von viewModelScope
einen benutzerdefinierten Bereich verwenden möchten, kann das ViewModel einen CoroutineScope
als Abhängigkeit in seinem Konstruktor erhalten. Wenn ViewModelStoreOwner
das ViewModel am Ende seines Lebenszyklus löscht, bricht ViewModel auch das 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 Konstruktor des ViewModel übergeben, der automatisch geschlossen wird, 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 des Gültigkeitsbereichs sollten Sie ViewModels als Implementierungsdetails für einen Inhaber des Bildschirmstatus verwenden. Verwenden Sie sie nicht als Statusinhaber von wiederverwendbaren UI-Komponenten wie Chipgruppen oder Formularen. Andernfalls erhalten Sie dieselbe ViewModel-Instanz in unterschiedlichen Verwendungen derselben UI-Komponente unter demselben ViewModelStoreOwner.
- ViewModels sollten die Details der UI-Implementierung nicht kennen. Halten Sie die Namen der Methoden, die die ViewModel API verfügbar macht, und die Namen der UI-Statusfelder so allgemein wie möglich. So kann Ihr ViewModel jede Art von UI aufnehmen: Mobiltelefone, faltbare Geräte, Tablets oder sogar Chromebooks.
- Da sie potenziell länger als
ViewModelStoreOwner
verwendet werden können, sollte ViewModels keine Verweise auf lebenszyklusbezogene 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 an der Plattform platzieren. In der Nähe Ihrer zusammensetzbaren Funktion „Aktivität“, „Fragment“ oder „Bildschirmebene“. Dadurch wird verhindert, dass Komponenten niedrigerer Ebene auf mehr Daten und Logik zugreifen, als sie benötigen.
Weitere Informationen
Wenn Ihre Daten komplexer werden, empfiehlt es sich, zum Laden der Daten eine separate Klasse einzurichten. Der Zweck von ViewModel
besteht darin, die Daten für einen UI-Controller zu kapseln, damit sie Konfigurationsänderungen überdauern. 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.
Weitere Informationen
Weitere Informationen zur Klasse ViewModel
finden Sie in den folgenden Ressourcen.
Dokumentation
- UI-Ebene
- Ereignisse auf der Benutzeroberfläche
- Statusinhaber und UI-Status
- Zustandsfähige Produktion
- Datenschicht
Produktproben
Empfehlungen für dich
- Hinweis: Der Linktext wird angezeigt, wenn JavaScript deaktiviert ist.
- Kotlin-Koroutinen mit Komponenten verwenden, die den Lebenszyklus berücksichtigen
- UI-Zustände speichern
- Auslagerungsdaten laden und anzeigen