Moderne Benutzeroberflächen sind selten statisch. Der Status der Benutzeroberfläche ändert sich, wenn der Nutzer mit der Benutzeroberfläche interagiert oder wenn die App neue Daten anzeigen muss.
Dieses Dokument enthält Richtlinien für die Erstellung und Verwaltung von UI-Elementen. Bundesstaat. Am Ende sollten Sie Folgendes tun:
- Informieren Sie sich, mit welchen APIs Sie den UI-Status erstellen sollten. Das hängt davon ab, die Art der Ursachen für den Statuswechsel bei Ihren Inhabern, gemäß den Prinzipien des unidirektionalen Datenflusses.
- Sie sollten wissen, wie Sie den UI-Zustand erstellen können, Systemressourcen.
- Informieren Sie sich, wie Sie den UI-Status für die Nutzung durch die UI verfügbar machen sollten.
Im Grunde ist die staatliche Produktion die schrittweise Anwendung dieser Änderungen. in den UI-Zustand. Der Status ist immer vorhanden und ändert sich infolge von Ereignissen. Die Die Unterschiede zwischen Ereignissen und Status sind in der folgenden Tabelle zusammengefasst:
Ereignisse | Bundesland |
---|---|
Vorübergehend, unvorhersehbar und für einen begrenzten Zeitraum vorhanden. | Immer vorhanden. |
Die Eingaben der staatlichen Produktion. | Die Ausgabe der Zustandsproduktion. |
Das Produkt der Benutzeroberfläche oder anderer Quellen. | Wird von der UI verarbeitet. |
Eine gute Merkhilfe, die das Obige zusammenfasst, ist state is; Ereignisse stattfinden. Die Diagramm unten hilft bei der Visualisierung von Zustandsänderungen, wenn Ereignisse auf einer Zeitachse auftreten. Jedes Ereignis wird vom entsprechenden Inhaber des Bundesstaats verarbeitet, was zu einer Statusänderung:
<ph type="x-smartling-placeholder">![Ereignisse im Vergleich zu Status](https://developer.android.google.cn/static/images/topic/architecture/ui-layer/events-vs-state.png?authuser=9&hl=de)
Ereignisse können stammen aus:
- Nutzer: Wenn sie mit der UI der App interagieren.
- Andere Quellen von Statusänderungen: APIs, die App-Daten aus der Benutzeroberfläche, oder Datenebenen wie Zeitüberschreitungen, Anwendungsfälle oder Repositories.
Produktionspipeline für den UI-Status
Die Statusproduktion in Android-Apps kann man sich als Verarbeitungspipeline vorstellen. bestehend aus:
- Eingaben: Die Quellen von Statusänderungen. Mögliche Gründe:
<ph type="x-smartling-placeholder">
- </ph>
- Lokal für die UI-Ebene: Dabei kann es sich um Nutzerereignisse wie die Eingabe eines
Titel für eine Aufgabe Aufgabenverwaltungs-App oder APIs, die
Zugriff auf UI-Logik, die Änderungen am UI-Status vorantreibt Beispiel:
Durch Aufrufen der Methode
open
fürDrawerState
in Jetpack Compose - Außerhalb der UI-Ebene: Dies sind Quellen aus der Domain oder Daten.
Ebenen, die Änderungen am UI-Status verursachen. Zum Beispiel Nachrichten, die
aus einem
NewsRepository
oder anderen Ereignissen geladen werden. - Eine Mischung aus allem oben Genannten.
- Lokal für die UI-Ebene: Dabei kann es sich um Nutzerereignisse wie die Eingabe eines
Titel für eine Aufgabe Aufgabenverwaltungs-App oder APIs, die
Zugriff auf UI-Logik, die Änderungen am UI-Status vorantreibt Beispiel:
Durch Aufrufen der Methode
- Inhaber eines Bundesstaats: Typen, die Geschäftslogik anwenden und/oder UI-Logik für Quellen von Statusänderungen und Verarbeitung von Nutzerereignissen, um UI-Status
- Ausgabe: Der UI-Status, den die App rendern kann, um Nutzern die die sie benötigen.
![Die Zustandsproduktionspipeline](https://developer.android.google.cn/static/images/topic/architecture/ui-layer/state-production-pipeline.png?authuser=9&hl=de)
Staatliche Produktions-APIs
Es gibt zwei Haupt-APIs, die in der Zustandsproduktion verwendet werden, je nachdem, in welcher Phase der in welcher Pipeline Sie sich befinden:
Pipelinephase | API |
---|---|
Eingang | Sie sollten asynchrone APIs verwenden, um Arbeiten außerhalb des UI-Threads durchzuführen, damit die UI-Verzögerung kostenlos bleibt. Zum Beispiel Coroutines oder Flows in Kotlin und RxJava oder Callbacks in der Programmiersprache Java. |
Ausgabe | Sie sollten APIs für beobachtbare Dateninhaber verwenden, um die UI zu entwerten und neu zu rendern, wenn sich der Status ändert. Beispiel: StateFlow, Compose State oder LiveData. Inhaber beobachtbarer Daten garantieren, dass die UI immer einen UI-Status hat, der auf dem Bildschirm angezeigt werden kann. |
Davon hat die Wahl der asynchronen API für die Eingabe einen größeren Einfluss auf die Beschaffenheit der staatlichen Produktionspipeline als die Wahl der beobachtbaren API. für die Ausgabe. Das liegt daran, dass die Eingaben die Art der Verarbeitung vorgeben, auf die Pipeline angewendet werden kann.
Zusammensetzung der staatlichen Produktionspipeline
In den nächsten Abschnitten geht es um staatliche Produktionstechniken, die für verschiedene Eingaben und die übereinstimmenden Ausgabe-APIs. Jede Zustandsproduktionspipeline ist ein Kombination von Ein- und Ausgaben und sollte:
- Lebenszyklusbewusst: Falls die Benutzeroberfläche nicht sichtbar oder aktiv ist, wird der Status-Produktionspipeline sollte nur dann Ressourcen verbrauchen, erforderlich.
- Einfache Nutzung: Die erstellte UI sollte einfach gerendert werden können. Bundesstaat. Überlegungen zur Ausgabe der Zustandsproduktionspipeline variieren je nach View-APIs wie dem View-System oder Jetpack Compose.
Eingaben in Zustandsproduktionspipelines
Eingaben in eine Zustandsproduktionspipeline können entweder ihre Zustandsquellen angeben Ändern via:
- One-Shot-Operationen, die beispielsweise synchron oder asynchron sein können.
suspend
-Funktionen aufrufen. - Stream-APIs, z. B.
Flows
- Alle oben genannten Optionen
In den folgenden Abschnitten wird beschrieben, wie Sie eine Zustandsproduktionspipeline zusammenstellen für jede der oben genannten Eingaben.
One-Shot-APIs als Quellen von Statusänderungen
Die MutableStateFlow
API als beobachtbare, änderbare API verwenden
Container of state. In Jetpack Compose-Apps können Sie auch
mutableStateOf
, insbesondere wenn Sie mit
Text-APIs verfassen Beide APIs ermöglichen sichere
atomische Aktualisierungen der gehosteten Werte unabhängig davon, ob die Aktualisierungen
synchron oder asynchron sein.
Betrachten Sie beispielsweise Statusaktualisierungen in einer einfachen Würfel-App. Jede Rolle von
ruft der Nutzer die synchrone
Random.nextInt()
-Methode und das Ergebnis wird in den
UI-Status
Zustandsfluss
data class DiceUiState(
val firstDieValue: Int? = null,
val secondDieValue: Int? = null,
val numberOfRolls: Int = 0,
)
class DiceRollViewModel : ViewModel() {
private val _uiState = MutableStateFlow(DiceUiState())
val uiState: StateFlow<DiceUiState> = _uiState.asStateFlow()
// Called from the UI
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,
)
}
}
}
Erstellungsstatus
@Stable
interface DiceUiState {
val firstDieValue: Int?
val secondDieValue: Int?
val numberOfRolls: Int?
}
private class MutableDiceUiState: DiceUiState {
override var firstDieValue: Int? by mutableStateOf(null)
override var secondDieValue: Int? by mutableStateOf(null)
override var numberOfRolls: Int by mutableStateOf(0)
}
class DiceRollViewModel : ViewModel() {
private val _uiState = MutableDiceUiState()
val uiState: DiceUiState = _uiState
// Called from the UI
fun rollDice() {
_uiState.firstDieValue = Random.nextInt(from = 1, until = 7)
_uiState.secondDieValue = Random.nextInt(from = 1, until = 7)
_uiState.numberOfRolls = _uiState.numberOfRolls + 1
}
}
UI-Status bei asynchronen Aufrufen ändern
Starten Sie für Statusänderungen, die ein asynchrones Ergebnis erfordern, eine Koroutine in der
entsprechende CoroutineScope
. So kann die App die Arbeit verwerfen, wenn die
CoroutineScope
wurde abgesagt. Der Staatsinhaber schreibt dann das Ergebnis der
Aufruf der angehaltenen Methode an die beobachtbare API, mit der der UI-Status angezeigt wird.
Betrachten Sie zum Beispiel die AddEditTaskViewModel
in der
Architekturbeispiel. Wenn die Methode saveTask()
angehalten wird
die Aufgabe asynchron speichert, kann die Methode update
im
MutableStateFlow leitet die Statusänderung an den UI-Status weiter.
Zustandsfluss
data class AddEditTaskUiState(
val title: String = "",
val description: String = "",
val isTaskCompleted: Boolean = false,
val isLoading: Boolean = false,
val userMessage: String? = null,
val isTaskSaved: Boolean = false
)
class AddEditTaskViewModel(...) : ViewModel() {
private val _uiState = MutableStateFlow(AddEditTaskUiState())
val uiState: StateFlow<AddEditTaskUiState> = _uiState.asStateFlow()
private fun createNewTask() {
viewModelScope.launch {
val newTask = Task(uiState.value.title, uiState.value.description)
try {
tasksRepository.saveTask(newTask)
// Write data into the UI state.
_uiState.update {
it.copy(isTaskSaved = true)
}
}
catch(cancellationException: CancellationException) {
throw cancellationException
}
catch(exception: Exception) {
_uiState.update {
it.copy(userMessage = getErrorMessage(exception))
}
}
}
}
}
Erstellungsstatus
@Stable
interface AddEditTaskUiState {
val title: String
val description: String
val isTaskCompleted: Boolean
val isLoading: Boolean
val userMessage: String?
val isTaskSaved: Boolean
}
private class MutableAddEditTaskUiState : AddEditTaskUiState() {
override var title: String by mutableStateOf("")
override var description: String by mutableStateOf("")
override var isTaskCompleted: Boolean by mutableStateOf(false)
override var isLoading: Boolean by mutableStateOf(false)
override var userMessage: String? by mutableStateOf<String?>(null)
override var isTaskSaved: Boolean by mutableStateOf(false)
}
class AddEditTaskViewModel(...) : ViewModel() {
private val _uiState = MutableAddEditTaskUiState()
val uiState: AddEditTaskUiState = _uiState
private fun createNewTask() {
viewModelScope.launch {
val newTask = Task(uiState.value.title, uiState.value.description)
try {
tasksRepository.saveTask(newTask)
// Write data into the UI state.
_uiState.isTaskSaved = true
}
catch(cancellationException: CancellationException) {
throw cancellationException
}
catch(exception: Exception) {
_uiState.userMessage = getErrorMessage(exception))
}
}
}
}
UI-Status von Hintergrundthreads ändern
Es empfiehlt sich, Coroutines auf dem Haupt-Dispatcher für die Produktion zu starten.
des UI-Status. Das heißt, außerhalb des withContext
-Blocks in den Code-Snippets.
unten. Wenn Sie den UI-Status jedoch in einem anderen Hintergrund aktualisieren müssen,
Kontext bietet, können Sie dies mithilfe der folgenden APIs tun:
- Verwenden Sie die Methode
withContext
, um Koroutinen in einer gleichzeitigen Kontext. - Wenn Sie
MutableStateFlow
verwenden, verwenden Sie die Methodeupdate
als Üblicherweise. - Wenn Sie den Erstellungsstatus nutzen, können Sie mit
Snapshot.withMutableSnapshot
Atomare Aktualisierungen des Status im gleichzeitigen Kontext garantieren.
Nehmen wir zum Beispiel an, dass im DiceRollViewModel
-Snippet unten
SlowRandom.nextInt()
ist eine rechenintensive suspend
-Funktion, die von einer CPU-gebundenen Koroutine aufgerufen werden muss.
Zustandsfluss
class DiceRollViewModel(
private val defaultDispatcher: CoroutineScope = Dispatchers.Default
) : ViewModel() {
private val _uiState = MutableStateFlow(DiceUiState())
val uiState: StateFlow<DiceUiState> = _uiState.asStateFlow()
// Called from the UI
fun rollDice() {
viewModelScope.launch() {
// Other Coroutines that may be called from the current context
…
withContext(defaultDispatcher) {
_uiState.update { currentState ->
currentState.copy(
firstDieValue = SlowRandom.nextInt(from = 1, until = 7),
secondDieValue = SlowRandom.nextInt(from = 1, until = 7),
numberOfRolls = currentState.numberOfRolls + 1,
)
}
}
}
}
}
Erstellungsstatus
class DiceRollViewModel(
private val defaultDispatcher: CoroutineScope = Dispatchers.Default
) : ViewModel() {
private val _uiState = MutableDiceUiState()
val uiState: DiceUiState = _uiState
// Called from the UI
fun rollDice() {
viewModelScope.launch() {
// Other Coroutines that may be called from the current context
…
withContext(defaultDispatcher) {
Snapshot.withMutableSnapshot {
_uiState.firstDieValue = SlowRandom.nextInt(from = 1, until = 7)
_uiState.secondDieValue = SlowRandom.nextInt(from = 1, until = 7)
_uiState.numberOfRolls = _uiState.numberOfRolls + 1
}
}
}
}
}
APIs als Quellen von Statusänderungen streamen
Für Quellen von Zustandsänderungen, die im Laufe der Zeit in Streams mehrere Werte erzeugen, Das Aggregieren der Ergebnisse aller Quellen zu einem zusammenhängenden Ganzen ist einen einfachen Ansatz für die staatliche Produktion.
Bei der Verwendung von Kotlin-Flows erreichen Sie dies mit der Methode combine . Ein Beispiel hierfür finden Sie "Jetzt für Android" Sample in InterestsViewModel:
class InterestsViewModel(
authorsRepository: AuthorsRepository,
topicsRepository: TopicsRepository
) : ViewModel() {
val uiState = combine(
authorsRepository.getAuthorsStream(),
topicsRepository.getTopicsStream(),
) { availableAuthors, availableTopics ->
InterestsUiState.Interests(
authors = availableAuthors,
topics = availableTopics
)
}
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5_000),
initialValue = InterestsUiState.Loading
)
}
Durch die Verwendung des Operators stateIn
zum Erstellen von StateFlows
wird die Benutzeroberfläche differenzierter dargestellt.
die Aktivität der staatlichen Produktionspipeline zu kontrollieren,
nur aktiv sein, wenn die Benutzeroberfläche sichtbar ist.
- Verwenden Sie
SharingStarted.WhileSubscribed()
, wenn die Pipeline nur aktiv sein soll. Wenn die Benutzeroberfläche beim Erfassen des Ablaufs in einem Lebenszyklus-sensitiven Prozess sichtbar ist auf die Art und Weise. - Verwenden Sie
SharingStarted.Lazily
, wenn die Pipeline aktiv sein soll, solange die kann der Nutzer zur Benutzeroberfläche zurückkehren, d. h., die Benutzeroberfläche befindet sich auf dem Backstack oder in einem anderen Tab aus dem Bildschirm zu sehen.
In Fällen, in denen das Zusammenfassen von streambasierten Statusquellen nicht angewendet wird, werden Streams APIs wie Kotlin-Flows bieten eine Vielzahl von Transformationen, wie Zusammenführen, Abflachen usw. um die Streams in den UI-Zustand zu versetzen.
One-Shot- und Stream-APIs als Quellen von Statusänderungen
Wenn die Zustandsproduktionspipeline von beiden One-Shot-Aufrufen abhängt als Quellen für Zustandsänderungen. Streams sind die definierende Einschränkung. Konvertieren Sie also One-Shot-Aufrufe in Streams, APIs verwenden oder ihre Ausgabe in Streams weiterleiten und die Verarbeitung wie beschrieben fortsetzen finden Sie oben im Abschnitt „Streams“.
Bei Abläufen bedeutet dies in der Regel das Erstellen einer oder mehrerer privater Sicherungen
MutableStateFlow
Instanz, um Statusänderungen zu übertragen. Sie können auch
Snapshot-Abläufe erstellen.
Betrachten Sie die TaskDetailViewModel
aus der
architecture-sample:
Zustandsfluss
class TaskDetailViewModel @Inject constructor(
private val tasksRepository: TasksRepository,
savedStateHandle: SavedStateHandle
) : ViewModel() {
private val _isTaskDeleted = MutableStateFlow(false)
private val _task = tasksRepository.getTaskStream(taskId)
val uiState: StateFlow<TaskDetailUiState> = combine(
_isTaskDeleted,
_task
) { isTaskDeleted, task ->
TaskDetailUiState(
task = taskAsync.data,
isTaskDeleted = isTaskDeleted
)
}
// Convert the result to the appropriate observable API for the UI
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5_000),
initialValue = TaskDetailUiState()
)
fun deleteTask() = viewModelScope.launch {
tasksRepository.deleteTask(taskId)
_isTaskDeleted.update { true }
}
}
Erstellungsstatus
class TaskDetailViewModel @Inject constructor(
private val tasksRepository: TasksRepository,
savedStateHandle: SavedStateHandle
) : ViewModel() {
private var _isTaskDeleted by mutableStateOf(false)
private val _task = tasksRepository.getTaskStream(taskId)
val uiState: StateFlow<TaskDetailUiState> = combine(
snapshotFlow { _isTaskDeleted },
_task
) { isTaskDeleted, task ->
TaskDetailUiState(
task = taskAsync.data,
isTaskDeleted = isTaskDeleted
)
}
// Convert the result to the appropriate observable API for the UI
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5_000),
initialValue = TaskDetailUiState()
)
fun deleteTask() = viewModelScope.launch {
tasksRepository.deleteTask(taskId)
_isTaskDeleted = true
}
}
Ausgabetypen in Pipelines für die Statusproduktion
Die Auswahl der Ausgabe-API für den UI-Status und die Art der Darstellung hängt weitgehend von der API ab, die Ihre App zum Rendern der Benutzeroberfläche verwendet. In Android-Apps können entweder Views oder Jetpack Compose verwenden. Folgendes sollte dabei berücksichtigt werden:
- Status unter Berücksichtigung des Lebenszyklus lesen.
- Gibt an, ob der Status in einem oder mehreren Feldern aus dem als Inhaber des Bundesstaats.
In der folgenden Tabelle wird zusammengefasst, welche APIs für die Zustandsproduktion verwendet werden Pipeline für eine bestimmte Eingabe und einen bestimmten Nutzer:
Eingang | Nutzer | Ausgabe |
---|---|---|
One-Shot-APIs | Aufrufe | StateFlow oder LiveData |
One-Shot-APIs | Schreiben | StateFlow oder „Schreiben“ State |
Stream-APIs | Aufrufe | StateFlow oder LiveData |
Stream-APIs | Schreiben | StateFlow |
One-Shot- und Stream-APIs | Aufrufe | StateFlow oder LiveData |
One-Shot- und Stream-APIs | Schreiben | StateFlow |
Initialisierung der Produktionspipeline
Zum Initialisieren von Pipelines für die Statusproduktion müssen die Anfangsbedingungen festgelegt werden
für die Ausführung der Pipeline. Dazu können erste Eingabewerte gehören.
entscheidend für den Start der Pipeline ist, z. B. ein id
für die
Detailansicht eines Nachrichtenartikels anzeigen oder einen asynchronen Ladevorgang starten.
Sie sollten die Zustandsproduktionspipeline nach Möglichkeit verzögert initialisieren
um Systemressourcen zu schonen.
In der Praxis bedeutet dies oft, zu warten, bis ein Kunde des Kunden
. Flow
APIs ermöglichen dies mithilfe der
started
-Argument im stateIn
. Sollte dies nicht anwendbar sein,
Definieren eines idempotenten
initialize()
-Funktion zum expliziten Starten der Zustandsproduktionspipeline
Dies wird im folgenden Snippet gezeigt:
class MyViewModel : ViewModel() {
private var initializeCalled = false
// This function is idempotent provided it is only called from the UI thread.
@MainThread
fun initialize() {
if(initializeCalled) return
initializeCalled = true
viewModelScope.launch {
// seed the state production pipeline
}
}
}
Produktproben
Die folgenden Google-Beispiele zeigen die Produktion von Staat in der UI-Ebene. Sehen Sie sich diese Tipps in der Praxis an:
Empfehlungen für dich
- Hinweis: Der Linktext wird angezeigt, wenn JavaScript deaktiviert ist.
- UI-Ebene
- Eine Offline-App erstellen
- Statusinhaber und UI-Status {:#mad-arch}