State<T> und MutableState<T> von Jetpack Compose sind Werthalter für allgemeine Zwecke, die vom Compose-Snapshot-System unterstützt werden. Obwohl sie am häufigsten verwendet werden, um die Neuzusammensetzung der Benutzeroberfläche zu steuern, ist das Snapshot-System selbst nicht an das UI-Rendering gebunden.
Mit den AppState-Bibliotheken Transform und StateStore können Sie Compose-State in ViewModels, Managern, Repositories und Hintergrunddiensten speichern, ableiten, beobachten und persistent speichern.
Warum Compose State über die UI hinaus verwenden?
Wenn Sie Compose State in einem zustandsbeobachtenden Block lesen, z. B. in einer komponierbaren UI-Funktion, transform oder derivedStateOf, bietet die Compose-Laufzeit zwei wichtige Vorteile für alle Ebenen Ihrer Architektur:
- Automatische und dynamische Abhängigkeitsverfolgung: Wenn der
.valueeines Status gelesen wird (oder dieby-Attributdelegierung verwendet wird), wird dieser Status automatisch als Abhängigkeit des Blocks registriert. Da das Tracking dynamisch ist, werden Abhängigkeiten zur Laufzeit durch die ausgeführten Codepfade bestimmt. Wenn ein bedingter Zweig nicht ausgeführt wird (z. B. nach einerif-Prüfung oder einem vorzeitigenreturn), werden die Zustände innerhalb dieses Zweigs nicht gelesen und Änderungen daran lösen keine unnötigen Neuberechnungen aus. - Konsistenz und Granularität: Sie können separate, detaillierte
MutableState-Attribute deklarieren und Schreibvorgänge mit mehreren Attributen inSnapshot.withMutableSnapshotverpacken. Zwischenupdates werden auf den änderbaren Snapshot beschränkt und atomar angewendet, wenn der Block abgeschlossen ist. Beobachter, die innerhalb eines Snapshots lesen, sehen also nie einen inkonsistenten Zwischenzustand und führen nur dann eine Neuberechnung durch, wenn sich die von ihnen gelesenen spezifischen Attribute ändern. Im Gegensatz zuStateFlowmüssen Sie sich nicht zwischen einem einzelnen monolithischenStateFlow<UiState>(der jeden Collector ungültig macht, wenn sich ein Feld ändert) und separatenStateFlow-Eigenschaften (die nicht zusammen in einer einzelnen atomaren Transaktion aktualisiert werden können) entscheiden.
Die richtige State API für Ihre Architektur auswählen
Wählen Sie aus, wie Sie Compose-State speichern und beibehalten möchten, je nachdem, wie lange ein Zustand bestehen soll und welche Komponenten darauf zugreifen müssen:
| Statuskategorie | Lebensdauer und Umfang | Beispiele | Empfohlene Compose-State-APIs |
|---|---|---|---|
| Livestatus | Wird immer im Arbeitsspeicher gespeichert, beim Start neu abgefragt und nicht über die Prozessbeendigung hinaus beibehalten. | Status der Netzwerkverbindung, Status der Bluetooth-Verbindung, temporärer UI-Status wie die Auswahl von Elementen. |
|
| Vorübergehender UI-Status | Übersteht Konfigurationsänderungen und vom System initiierte Prozessbeendigungen, wird nicht unbegrenzt gespeichert; auf einen einzelnen Bildschirm oder eine einzelne Aktivität beschränkt. | Maximierte oder minimierte UI-Elemente, Scrollposition, Suchanfrage auf dem Bildschirm oder Filter-Umschaltflächen. |
|
| Vorübergehender Sitzungsstatus | Überlebt die Beendigung von Hintergrundprozessen, wird nicht unbegrenzt gespeichert; wird von Aktivitäten, Diensten oder Widgets gemeinsam genutzt. | Aktiver Fokus-Timer, mehrstufiger Einkaufswagen oder Formularentwurf, der gerade bearbeitet wird. |
|
| Dauerhafter Zustand | Werden unbegrenzt auf dem Laufwerk gespeichert. | Nutzereinstellungen und -konfigurationen (DataStore), strukturierte App-Daten und Mediatheken (Room). | DataStore- oder Room-Flows, die in transform mit collectAsState erfasst werden |
Compose-Status in einem ViewModel verwenden
In einem ViewModel können Sie unabhängige Bildschirmeingaben als detaillierte MutableState-Properties deklarieren, sie mit SavedStateHandle.saveable über die Prozessbeendigung hinaus beibehalten, mehrere Properties mit Snapshot.withMutableSnapshot atomar aktualisieren und sie mit Repository-Flow-Streams in einem transform kombinieren:
sealed interface TaskListState { data object Loading : TaskListState data class Success( val tasks: List<Task>, val selectedTaskIds: Set<String>, val isOnline: Boolean = true, ) : TaskListState } @HiltViewModel class TaskListViewModel @Inject constructor( private val tasksRepository: TasksRepository, savedStateHandle: SavedStateHandle, ) : ViewModel() { var selectedTaskIds by mutableStateOf(emptySet<String>()) private set var searchQuery by savedStateHandle.saveable { mutableStateOf("") } private set fun updateSearchQuery(query: String) { Snapshot.withMutableSnapshot { searchQuery = query selectedTaskIds = emptySet() } } val tasksState: State<TaskListState> = transform<TaskListState>( initialValue = TaskListState.Loading, scope = viewModelScope, ) { val tasks = remember(searchQuery) { tasksRepository.getTasks(searchQuery) }.collectAsState(initial = null).value ?: return@transform TaskListState.Loading TaskListState.Success( tasks = tasks, selectedTaskIds = selectedTaskIds, ) } }
Wichtige Muster in diesem ViewModel:
SavedStateHandle.saveablefür reaktiven gespeicherten Zustand verwenden: Wenn SiesearchQuerymitmutableStateOf(ausandroidx.lifecycle:lifecycle-viewmodel-compose) anSavedStateHandle.saveabledelegieren, wird der Wert über die Prozessbeendigung hinaus alsMutableStatebeibehalten, sodasstransformLesevorgänge automatisch verfolgt. Vermeiden Sie die Verwendung vonSavedStateHandle.savedohne weitere Angaben, da dadurch eine einfache Kotlin-Property anstelle von Compose-Statezurückgegeben wird und bei Änderungen keine Neuzusammensetzung ausgelöst wird.- Atomare Schreibvorgänge für mehrere Properties mit
Snapshot.withMutableSnapshot: Wenn SiesearchQueryundselectedTaskIdsinnerhalb vonSnapshot.withMutableSnapshotaktualisieren, werden die Schreibvorgänge isoliert und beide Änderungen in einer einzigen atomaren Snapshot-Transaktion übernommen. Beobachter (einschließlichtransform) sehen beide Aktualisierungen zusammen in einem einzigen Neuzusammensetzungsdurchlauf, anstatt das neuesearchQuerymit dem veraltetenselectedTaskIdszu sehen. - Flüsse über Neukompositionen hinweg mit
remember(key)wiederverwenden: Wenn SietasksRepository.getTasks(searchQuery)inremembermitsearchQueryals Schlüssel verpacken, wird bei einer Änderung vonselectedTaskIdsund einer Neukomposition destransform-Blocks die vorhandene Datenbank-Flowwiederverwendet, anstatt die Abfrage neu zu starten.
Live-Systemstatus in der gesamten App teilen
Für den Live-Systemstatus, der von mehreren ViewModels, Aktivitäten oder Diensten gemeinsam genutzt wird, z. B. den Status der Netzwerk- oder Bluetooth-Verbindung, sollten Sie keine veralteten Statusdaten über die Prozessbeendigung hinaus auf dem Laufwerk speichern. Fragen Sie stattdessen beim Start den aktuellen Systemstatus ab, speichern Sie ihn in einer privaten mutableStateOf-Property in einem Singleton mit Anwendungsbereich und stellen Sie einen schreibgeschützten State<T> bereit:
interface NetworkMonitor { val isOnline: State<Boolean> } @Singleton class LiveNetworkMonitor @Inject constructor( @ApplicationContext context: Context, ) : NetworkMonitor { private val connectivityManager = context.getSystemService(ConnectivityManager::class.java) private val _isOnline = mutableStateOf(checkCurrentConnectivity()) override val isOnline: State<Boolean> = _isOnline private val callback = object : ConnectivityManager.NetworkCallback() { override fun onCapabilitiesChanged( network: Network, networkCapabilities: NetworkCapabilities, ) { _isOnline.value = networkCapabilities.hasCapability( NetworkCapabilities.NET_CAPABILITY_INTERNET ) } override fun onLost(network: Network) { _isOnline.value = false } } init { connectivityManager.registerDefaultNetworkCallback(callback) } private fun checkCurrentConnectivity(): Boolean { val network = connectivityManager.activeNetwork ?: return false val capabilities = connectivityManager.getNetworkCapabilities(network) ?: return false return capabilities.hasCapability( NetworkCapabilities.NET_CAPABILITY_INTERNET ) } }
MutableState unterstützt konsistente Lesevorgänge und Aktualisierungen von jedem Thread aus, sodass NetworkCallback _isOnline.value direkt über den Hintergrund-Callback-Thread aktualisieren kann. Wenn ein Hintergrund-Callback mehrere MutableState-Eigenschaften gleichzeitig aktualisiert, umschließen Sie die Schreibvorgänge mit Snapshot.withMutableSnapshot, damit sie atomar angewendet werden.
Jede Komponente in Ihrer App kann networkMonitor.isOnline.value lesen – entweder als einmalige Prüfung, innerhalb einer UI-Komposition oder innerhalb eines Blocks, der den Status beobachtet, z. B. listener, derivedStateOf oder transform. Beispiel: TaskListViewModel kann networkMonitor.isOnline.value im Block transform lesen:
val tasksState: State<TaskListState> = transform<TaskListState>( initialValue = TaskListState.Loading, scope = viewModelScope, ) { val tasks = remember(searchQuery) { tasksRepository.getTasks(searchQuery) }.collectAsState(initial = null).value ?: return@transform TaskListState.Loading TaskListState.Success( tasks = tasks, selectedTaskIds = selectedTaskIds, isOnline = networkMonitor.isOnline.value, ) }
Da transform networkMonitor.isOnline.value zusammen mit searchQuery, selectedTaskIds und dem Repository Flow verfolgt, wird bei jeder Änderung der Konnektivität TaskListState.Success automatisch neu berechnet, ohne dass die Datenbank erneut abgefragt werden muss.