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.

Zeigt den Lebenszyklus eines ViewModel, wenn sich der Status einer Aktivität ändert.

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 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 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

Produktproben