UI-Status in „Compose“ speichern

Je nachdem, wo Ihr Bundesstaat hochgezogen wird und welche Logik erforderlich ist, können Sie verschiedene APIs verwenden, um Ihren UI-Status zu speichern und wiederherzustellen. Dazu wird bei jeder Anwendung eine Kombination aus APIs verwendet.

Android-Apps können ihren UI-Status aufgrund von Aktivitäten oder Prozessen verlieren. Dieser Statusverlust kann durch folgende Ereignisse auftreten:

Die Beibehaltung des Zustands nach diesen Ereignissen ist für eine positive Nutzererfahrung unerlässlich. Die Auswahl des beibehaltenen Status hängt von den Abläufen einzelner Nutzer in Ihrer Anwendung ab. Es hat sich bewährt, zumindest die Nutzereingabe und den navigationsbezogenen Status beizubehalten. Beispiele dafür sind die Scrollposition einer Liste, die ID des Elements, über das der Nutzer weitere Informationen wünscht, die Auswahl von Nutzereinstellungen in Bearbeitung oder Eingaben in Textfeldern.

Auf dieser Seite werden die APIs zusammengefasst, die zum Speichern des UI-Status verfügbar sind, je nachdem, wo Ihr Bundesstaat hochgezogen wird und welche Logik ihn benötigt.

UI-Logik

Wenn Ihr Zustand in der UI entweder in zusammensetzbaren Funktionen oder in einfachen Status-Holder-Klassen, die auf die Zusammensetzung beschränkt sind, hochgezogen wird, können Sie rememberSaveable verwenden, um den Status über Aktivitäten und Prozesswiederherstellungen hinweg beizubehalten.

Im folgenden Snippet wird mit rememberSaveable der Status eines einzelnen booleschen UI-Elements gespeichert:

@Composable
fun ChatBubble(
    message: Message
) {
    var showDetails by rememberSaveable { mutableStateOf(false) }

    ClickableText(
        text = AnnotatedString(message.content),
        onClick = { showDetails = !showDetails }
    )

    if (showDetails) {
        Text(message.timestamp)
    }
}

Abbildung 1: Das Infofeld für die Chatnachricht wird beim Antippen maximiert und minimiert.

showDetails ist eine boolesche Variable, die gespeichert wird, ob das Chat-Infofeld minimiert oder maximiert wird.

rememberSaveable speichert den Status des UI-Elements über den gespeicherten Instanzstatusmechanismus in einem Bundle.

Es kann primitive Typen automatisch im Bundle speichern. Wenn der Status in einem nicht einfachen Typ enthalten ist, z. B. einer Datenklasse, können Sie verschiedene Speichermechanismen verwenden, z. B. die Annotation Parcelize, Compose APIs wie listSaver und mapSaver oder eine benutzerdefinierte Saver-Klasse implementieren, um die Compose-Laufzeitklasse Saver zu erweitern. Weitere Informationen zu diesen Methoden finden Sie in der Dokumentation zu Möglichkeiten zum Speichern von Status.

Im folgenden Snippet wird in der rememberLazyListState Compose API LazyListState gespeichert. Dieser Wert umfasst den Scrollstatus von LazyColumn oder LazyRow mit rememberSaveable. Dabei wird ein LazyListState.Saver verwendet. Das ist ein benutzerdefinierter Speicher, mit dem der Scrollstatus gespeichert und wiederhergestellt werden kann. Nach der Neuerstellung einer Aktivität oder eines Prozesses (z. B. nach einer Konfigurationsänderung wie einer Änderung der Geräteausrichtung) wird der Scrollstatus beibehalten.

@Composable
fun rememberLazyListState(
    initialFirstVisibleItemIndex: Int = 0,
    initialFirstVisibleItemScrollOffset: Int = 0
): LazyListState {
    return rememberSaveable(saver = LazyListState.Saver) {
        LazyListState(
            initialFirstVisibleItemIndex, initialFirstVisibleItemScrollOffset
        )
    }
}

Best Practice

rememberSaveable verwendet ein Bundle zum Speichern des UI-Status, der von anderen APIs gemeinsam genutzt wird, die ebenfalls in ihn schreiben, z. B. onSaveInstanceState()-Aufrufe in Ihrer Aktivität. Die Größe dieses Bundle ist jedoch begrenzt und das Speichern großer Objekte kann während der Laufzeit zu TransactionTooLarge-Ausnahmen führen. Dies kann besonders bei einzelnen Activity-Anwendungen problematisch sein, bei denen in der gesamten App dasselbe Bundle verwendet wird.

Um diese Art von Absturz zu vermeiden, sollten Sie keine großen komplexen Objekte oder Listen von Objekten im Bundle speichern.

Speichern Sie stattdessen den erforderlichen Mindeststatus wie IDs oder Schlüssel und verwenden Sie diese, um die Wiederherstellung des komplexeren UI-Status an andere Mechanismen wie nichtflüchtigen Speicher zu delegieren.

Diese Designentscheidungen hängen von den spezifischen Anwendungsfällen für Ihre Anwendung und dem Verhalten Ihrer Nutzer ab.

Wiederherstellung des Status prüfen

Sie können prüfen, ob der mit rememberSaveable in Ihren Composer-Elementen gespeicherte Status korrekt wiederhergestellt wird, wenn die Aktivität oder der Prozess neu erstellt wird. Dafür gibt es bestimmte APIs, z. B. StateRestorationTester. Weitere Informationen finden Sie in der Testdokumentation.

Geschäftslogik

Wenn Ihr UI-Elementstatus in die ViewModel gezogen wird, weil dies für die Geschäftslogik erforderlich ist, können Sie die APIs von ViewModel verwenden.

Einer der Hauptvorteile der Verwendung von ViewModel in deiner Android-App besteht darin, dass Konfigurationsänderungen kostenlos verarbeitet werden. Wenn es eine Konfigurationsänderung gibt und die Aktivität gelöscht und neu erstellt wird, wird der UI-Status, der in ViewModel übertragen wurde, im Arbeitsspeicher gehalten. Nach der Neuerstellung wird die alte ViewModel-Instanz der neuen Aktivitätsinstanz zugeordnet.

Eine ViewModel-Instanz überlebt den vom System initiierten Prozessende jedoch nicht. Damit der UI-Status bestehen bleibt, verwenden Sie das Saved State-Modul für ViewModel, das die SavedStateHandle API enthält.

Best Practice

In SavedStateHandle wird auch der Bundle-Mechanismus zum Speichern des UI-Status verwendet. Daher sollten Sie ihn nur zum Speichern eines einfachen UI-Elementstatus verwenden.

Der Bildschirm-UI-Status, der durch die Anwendung von Geschäftsregeln und den Zugriff auf andere Ebenen Ihrer Anwendung als UI generiert wird, sollte aufgrund seiner potenziellen Komplexität und Größe nicht in SavedStateHandle gespeichert werden. Sie können verschiedene Mechanismen zum Speichern komplexer oder großer Daten verwenden, beispielsweise lokaler nichtflüchtiger Speicher. Nach einer Prozesswiederherstellung wird der Bildschirm mit dem wiederhergestellten vorübergehenden Status neu erstellt, der gegebenenfalls in SavedStateHandle gespeichert wurde. Der Bildschirm-UI-Status wird wieder aus der Datenschicht erzeugt.

SavedStateHandle APIs

SavedStateHandle hat verschiedene APIs zum Speichern des Status der UI-Elemente, insbesondere:

Schreiben State saveable()
StateFlow getStateFlow()

State verfassen

Verwenden Sie die saveable API von SavedStateHandle, um den Status des UI-Elements als MutableState zu lesen und zu schreiben. So werden Aktivitäten und Neuerstellungen mit minimaler Codeeinrichtung überstanden.

Die saveable API unterstützt standardmäßig primitive Typen und empfängt einen stateSaver-Parameter, um benutzerdefinierte Saver wie rememberSaveable() zu verwenden.

Im folgenden Snippet speichert message die Nutzereingabetypen in einer TextField:

class ConversationViewModel(
    savedStateHandle: SavedStateHandle
) : ViewModel() {

    var message by savedStateHandle.saveable(stateSaver = TextFieldValue.Saver) {
        mutableStateOf(TextFieldValue(""))
    }
        private set

    fun update(newMessage: TextFieldValue) {
        message = newMessage
    }

    /*...*/
}

val viewModel = ConversationViewModel(SavedStateHandle())

@Composable
fun UserInput(/*...*/) {
    TextField(
        value = viewModel.message,
        onValueChange = { viewModel.update(it) }
    )
}

Weitere Informationen zur Verwendung der saveable API finden Sie in der Dokumentation zu SavedStateHandle.

StateFlow

Verwenden Sie getStateFlow(), um den Status des UI-Elements zu speichern und als Ablauf aus SavedStateHandle zu übernehmen. Das StateFlow ist schreibgeschützt und die API erfordert, dass Sie einen Schlüssel angeben, damit Sie den Ablauf ersetzen können, um einen neuen Wert auszugeben. Mit dem von Ihnen konfigurierten Schlüssel können Sie StateFlow abrufen und den neuesten Wert erfassen.

Im folgenden Snippet ist savedFilterType eine StateFlow-Variable, die einen Filtertyp speichert, der auf eine Liste von Chatkanälen in einer Chat-App angewendet wird:

private const val CHANNEL_FILTER_SAVED_STATE_KEY = "ChannelFilterKey"

class ChannelViewModel(
    channelsRepository: ChannelsRepository,
    private val savedStateHandle: SavedStateHandle
) : ViewModel() {

    private val savedFilterType: StateFlow<ChannelsFilterType> = savedStateHandle.getStateFlow(
        key = CHANNEL_FILTER_SAVED_STATE_KEY, initialValue = ChannelsFilterType.ALL_CHANNELS
    )

    private val filteredChannels: Flow<List<Channel>> =
        combine(channelsRepository.getAll(), savedFilterType) { channels, type ->
            filter(channels, type)
        }.onStart { emit(emptyList()) }

    fun setFiltering(requestType: ChannelsFilterType) {
        savedStateHandle[CHANNEL_FILTER_SAVED_STATE_KEY] = requestType
    }

    /*...*/
}

enum class ChannelsFilterType {
    ALL_CHANNELS, RECENT_CHANNELS, ARCHIVED_CHANNELS
}

Jedes Mal, wenn der Nutzer einen neuen Filtertyp auswählt, wird setFiltering aufgerufen. Dadurch wird in SavedStateHandle ein neuer Wert gespeichert, der mit dem Schlüssel _CHANNEL_FILTER_SAVED_STATE_KEY_ gespeichert wird. savedFilterType ist ein Ablauf, der den neuesten im Schlüssel gespeicherten Wert ausgibt. filteredChannels hat den Ablauf abonniert, um die Kanalfilterung durchzuführen.

Weitere Informationen zur getStateFlow() API finden Sie in der Dokumentation zu SavedStateHandle.

Zusammenfassung

In der folgenden Tabelle erhalten Sie eine Übersicht über die in diesem Abschnitt behandelten APIs und ihre Verwendung zum Speichern des UI-Status:

Veranstaltung UI-Logik Geschäftslogik in einer ViewModel
Konfigurationsänderungen rememberSaveable Automatisch
Vom System initiierter Tod rememberSaveable SavedStateHandle

Welche API verwendet wird, hängt davon ab, wo sich der Status befindet und welche Logik er erfordert. Verwende rememberSaveable für einen Status, der in der UI-Logik verwendet wird. Wenn Sie einen Status, der in der Geschäftslogik verwendet wird, in einer ViewModel speichern, speichern Sie ihn mit SavedStateHandle.

Sie sollten die Bundle APIs (rememberSaveable und SavedStateHandle) verwenden, um kleine Mengen an UI-Status zu speichern. Diese Daten sind das Minimum, das erforderlich ist, um den vorherigen Zustand der UI wiederherzustellen, zusammen mit anderen Speichermechanismen. Wenn Sie beispielsweise die ID eines Profils speichern, das sich der Nutzer im Bundle angesehen hat, können Sie umfangreiche Daten wie Profildetails aus der Datenschicht abrufen.

Weitere Informationen zu den verschiedenen Möglichkeiten zum Speichern des UI-Status finden Sie in der allgemeinen Dokumentation zum Speichern des UI-Status und auf der Seite Datenschicht des Architekturleitfadens.