Konzepte und Jetpack Compose-Implementierung
Die Benutzeroberfläche dient dazu, die Anwendungsdaten auf dem Bildschirm anzuzeigen und als primärer Punkt der Nutzerinteraktion zu fungieren. Immer wenn sich die Daten ändern, entweder aufgrund einer Nutzerinteraktion (z. B. durch Drücken einer Schaltfläche) oder einer externen Eingabe (z. B. einer Netzwerkantwort), sollte die Benutzeroberfläche diese Änderungen widerspiegeln. Die Benutzeroberfläche ist im Grunde eine visuelle Darstellung des Anwendungsstatus, der aus der Datenschicht abgerufen wird.
Die Anwendungsdaten, die Sie aus der Datenschicht erhalten, haben jedoch in der Regel ein anderes Format als die Informationen, die Sie anzeigen müssen. Möglicherweise benötigen Sie beispielsweise nur einen Teil der Daten für die Benutzeroberfläche oder Sie müssen zwei verschiedene Datenquellen zusammenführen, um Informationen zu präsentieren, die für den Nutzer relevant sind. Unabhängig von der angewendeten Logik müssen Sie der Benutzeroberfläche alle Informationen übergeben, die für das vollständige Rendering erforderlich sind. Die Benutzeroberflächenschicht ist die Pipeline, die Änderungen an Anwendungsdaten in ein Format umwandelt, das die Benutzeroberfläche darstellen kann, und sie dann anzeigt.
UI-Status verfügbar machen
Nachdem Sie den UI-Status definiert und festgelegt haben, wie Sie die Erstellung dieses Status verwalten, besteht der nächste Schritt darin, den erstellten Status der Benutzeroberfläche zu präsentieren. Da Sie UDF verwenden, um die Erstellung des Status zu verwalten, können Sie den erstellten Status als Stream betrachten. Das heißt, im Laufe der Zeit werden mehrere Versionen des Status erstellt. Daher sollten Sie den UI-Status in einem beobachtbaren Datencontainer wie LiveData oder StateFlow verfügbar machen. So kann die Benutzeroberfläche auf alle Änderungen im Status reagieren, ohne Daten manuell direkt aus dem ViewModel abrufen zu müssen. Diese Typen haben auch den Vorteil, dass immer die neueste Version des UI-Status im Cache gespeichert ist. Das ist nützlich für die schnelle Wiederherstellung des Status nach Konfigurationsänderungen.
class NewsViewModel(...) : ViewModel() {
val uiState: StateFlow<NewsUiState> = …
}
Eine gängige Methode zum Erstellen eines Streams von UiState besteht darin, einen zugrunde liegenden veränderlichen
Stream als unveränderlichen Stream aus dem ViewModel verfügbar zu machen, z. B.
MutableStateFlow<UiState> als StateFlow<UiState>.
class NewsViewModel(...) : ViewModel() {
private val _uiState = MutableStateFlow(NewsUiState())
val uiState: StateFlow<NewsUiState> = _uiState.asStateFlow()
...
}
Das ViewModel kann dann Methoden verfügbar machen, die den Status intern ändern und Updates veröffentlichen, die von der Benutzeroberfläche verwendet werden können. Nehmen wir an, eine
asynchrone Aktion muss ausgeführt werden. Eine Coroutine kann mit
viewModelScope gestartet werden und
der veränderliche Status kann nach Abschluss aktualisiert werden.
class NewsViewModel(
private val repository: NewsRepository,
...
) : ViewModel() {
private val _uiState = MutableStateFlow(NewsUiState())
val uiState: StateFlow<NewsUiState> = _uiState.asStateFlow()
private var fetchJob: Job? = null
fun fetchArticles(category: String) {
fetchJob?.cancel()
fetchJob = viewModelScope.launch {
try {
val newsItems = repository.newsItemsForCategory(category)
_uiState.update {
it.copy(newsItems = newsItems)
}
} catch (ioe: IOException) {
// Handle the error and notify the UI when appropriate.
_uiState.update {
val messages = getMessagesFromThrowable(ioe)
it.copy(userMessages = messages)
}
}
}
}
}
UI-Status verwenden
Wenn Sie beobachtbare Datencontainer in der Benutzeroberfläche verwenden, müssen Sie den Lebenszyklus der Benutzeroberfläche berücksichtigen. Das ist wichtig, da die Benutzeroberfläche den UI-Status nicht beobachten sollte, wenn die Ansicht dem Nutzer nicht angezeigt wird. Weitere Informationen zu diesem Thema finden Sie in diesem Blog
post.
Bei Verwendung von LiveData kümmert sich der LifecycleOwner implizit um Lebenszyklusprobleme. Bei Verwendung von Flows ist es am besten, dies mit dem entsprechenden Coroutine-Bereich und der repeatOnLifecycle-API zu handhaben:
class NewsActivity : AppCompatActivity() {
private val viewModel: NewsViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
...
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.uiState.collect {
// Update UI elements
}
}
}
}
}
Laufende Vorgänge anzeigen
Eine einfache Möglichkeit, Ladestatus in einer UiState-Klasse darzustellen, ist ein boolesches Feld:
data class NewsUiState(
val isFetchingArticles: Boolean = false,
...
)
Der Wert dieses Flags gibt an, ob in der Benutzeroberfläche ein Fortschrittsbalken vorhanden ist oder nicht.
class NewsActivity : AppCompatActivity() {
private val viewModel: NewsViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
...
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
// Bind the visibility of the progressBar to the state
// of isFetchingArticles.
viewModel.uiState
.map { it.isFetchingArticles }
.distinctUntilChanged()
.collect { progressBar.isVisible = it }
}
}
}
}
Animationen
Um flüssige und reibungslose Übergänge auf oberster Ebene zu ermöglichen, sollten Sie warten, bis die Daten für den zweiten Bildschirm geladen sind, bevor Sie die Animation starten.
Das Android-Ansichts-Framework bietet Hooks, um Übergänge zwischen Fragment
Zielen mit den
postponeEnterTransition()
und
startPostponedEnterTransition()
APIs zu verzögern. Mit diesen APIs können Sie dafür sorgen, dass die UI-Elemente auf dem zweiten Bildschirm (in der Regel ein aus dem Netzwerk abgerufenes Bild) angezeigt werden können, bevor die Benutzeroberfläche den Übergang zu diesem Bildschirm animiert.
Empfehlungen für Sie
- Hinweis: Linktext wird angezeigt, wenn JavaScript deaktiviert ist
- Erstellung des UI-Status
- Status-Holder und UI-Status {:#mad-arch}
- Leitfaden zur App-Architektur