Ein Nebeneffekt ist eine Statusänderung der App, die außerhalb des Umfang einer zusammensetzbaren Funktion. Aufgrund von zusammensetzbaren Funktionen Lebenszyklus und Eigenschaften wie unvorhersehbare Neuzusammensetzungen, die Ausführung von Zusammensetzungen von Zusammensetzungen in unterschiedlicher Reihenfolge oder Neuzusammensetzungen, die verworfen werden können, sollten zusammensetzbare Funktionen idealerweise Nebenwirkungen haben kostenlos.
Manchmal sind jedoch Nebenwirkungen erforderlich, z. B. um ein einmaliges Ereignis auszulösen. etwa das Einblenden einer Snackbar oder das Navigieren zu einem anderen Bildschirm, Zustand. Diese Aktionen sollten von einem kontrollierten der den Lebenszyklus der zusammensetzbaren Funktion berücksichtigt. Auf dieser Seite erfahren Sie mehr über die verschiedenen Nebeneffekt-APIs, die Jetpack Compose bietet.
Zustand und Wirkung – Anwendungsfälle
Weitere Informationen hierzu finden Sie im Artikel Thinking in Compose sollten zusammensetzbare Funktionen keine Nebenwirkungen haben. Wenn Sie Änderungen am App-Status vorgenommen werden, wie in den Abschnitten Verwalten Dokument zur Dokumentation des Bundesstaates, sollten Sie den Effect APIs so, dass diese Nebenwirkungen vorhersehbar ausgeführt werden.
Da sich in „Compose“ viele verschiedene Effekte ergeben, zu häufig verwendet wird. Stellen Sie sicher, dass Ihre Arbeit unterbricht den unidirektionalen Datenfluss nicht, wie unter Verwaltungsstatus Dokumentation.
LaunchedEffect
: Beendigungsfunktionen im Bereich einer zusammensetzbaren Funktion ausführen
Um während der gesamten Lebensdauer einer zusammensetzbaren Funktion zu arbeiten und die Möglichkeit zu haben,
aussetzen, verwenden Sie
LaunchedEffect
zusammensetzbar sind. Wenn LaunchedEffect
in die Komposition eintritt, wird ein
coroutine mit dem als Parameter übergebenen Codeblock. Die Koroutine ist
wird abgebrochen, wenn LaunchedEffect
die Komposition verlässt. Wenn LaunchedEffect
gleich
mit unterschiedlichen Tasten angeordnet (siehe Abschnitt Neustart
Effekte unten), wird die bestehende Koroutine
abgebrochen und die neue Unterbrechungsfunktion wird in einer neuen Koroutine gestartet.
Hier sehen Sie als Beispiel eine Animation, bei der der Alphawert mit einem Konfigurierbare Verzögerung:
// Allow the pulse rate to be configured, so it can be sped up if the user is running // out of time var pulseRateMs by remember { mutableStateOf(3000L) } val alpha = remember { Animatable(1f) } LaunchedEffect(pulseRateMs) { // Restart the effect when the pulse rate changes while (isActive) { delay(pulseRateMs) // Pulse the alpha every pulseRateMs to alert the user alpha.animateTo(0f) alpha.animateTo(1f) } }
Im obigen Code verwendet die Animation die Unterbrechungsfunktion.
delay
die festgelegte Zeit zu warten. Dann wird die Alpha-Version der Reihe nach
auf null und wieder zurück,
animateTo
Dies wiederholt sich für die gesamte Lebensdauer der zusammensetzbaren Funktion.
rememberCoroutineScope
: Erlangt einen kompositionsbezogenen Bereich, um eine Koroutine außerhalb einer zusammensetzbaren Funktion zu starten.
Da LaunchedEffect
eine zusammensetzbare Funktion ist, kann sie nur in anderen
zusammensetzbaren Funktionen verwenden. Um eine Koroutine außerhalb einer zusammensetzbaren Funktion zu starten,
aber auf einen Gültigkeitsbereich begrenzt, sodass er automatisch abgebrochen wird, sobald er den Bereich
Zusammensetzung, Verwendung
rememberCoroutineScope
Verwenden Sie rememberCoroutineScope
auch, wenn Sie den Lebenszyklus eines
eine oder mehrere Koroutinen manuell erstellen, z. B. das Abbrechen einer Animation, wenn eine
Nutzerereignis eintritt.
rememberCoroutineScope
ist eine zusammensetzbare Funktion, die einen Wert
CoroutineScope
an den Punkt der Komposition gebunden, an der sie aufgerufen wird. Die
wird abgebrochen, wenn der Aufruf die Komposition verlässt.
Entsprechend dem vorherigen Beispiel könnten Sie mit diesem Code eine Snackbar
anzeigen.
wenn der Nutzer auf Button
tippt:
@Composable fun MoviesScreen(snackbarHostState: SnackbarHostState) { // Creates a CoroutineScope bound to the MoviesScreen's lifecycle val scope = rememberCoroutineScope() Scaffold( snackbarHost = { SnackbarHost(hostState = snackbarHostState) } ) { contentPadding -> Column(Modifier.padding(contentPadding)) { Button( onClick = { // Create a new coroutine in the event handler to show a snackbar scope.launch { snackbarHostState.showSnackbar("Something happened!") } } ) { Text("Press me") } } } }
rememberUpdatedState
: Verweist auf einen Wert in einem Effekt, der nicht neu gestartet werden sollte, wenn sich der Wert ändert
LaunchedEffect
wird neu gestartet, wenn sich einer der Schlüsselparameter ändert. In
In einigen Situationen können Sie einen Wert für Ihren Effekt erfassen, der,
möchten, dass der Effekt nicht erneut auftritt. Dazu muss
rememberUpdatedState
verwenden, um einen Verweis auf diesen Wert zu erstellen,
erfasst und aktualisiert werden kann. Dieser Ansatz ist hilfreich für Effekte, die
langlebigen Abläufen, deren Neuerstellung kostspielig oder zu kostspielig sein kann,
neu starten.
Angenommen, in Ihrer App ist ein LandingScreen
vorhanden, das nach einigen Sekunden verschwindet.
. Auch bei einer Neuzusammensetzung von LandingScreen
ist der Effekt, der eine Weile wartet,
und benachrichtigt Sie, dass die abgelaufene Zeit nicht neu gestartet werden sollte:
@Composable fun LandingScreen(onTimeout: () -> Unit) { // This will always refer to the latest onTimeout function that // LandingScreen was recomposed with val currentOnTimeout by rememberUpdatedState(onTimeout) // Create an effect that matches the lifecycle of LandingScreen. // If LandingScreen recomposes, the delay shouldn't start again. LaunchedEffect(true) { delay(SplashWaitTimeMillis) currentOnTimeout() } /* Landing screen content */ }
Um einen Effekt zu erzeugen, der dem Lebenszyklus der Anrufwebsite entspricht,
Eine nie ändernde Konstante wie Unit
oder true
wird als Parameter übergeben. Im
Code oben enthält, wird LaunchedEffect(true)
verwendet. Damit die onTimeout
Lambda enthält immer den letzten Wert, der von LandingScreen
neu zusammengesetzt wurde
Dabei muss onTimeout
mit der Funktion rememberUpdatedState
zusammengefasst werden.
Die zurückgegebene State
, currentOnTimeout
im Code, sollte im folgenden Beispiel verwendet werden:
Effekts.
DisposableEffect
: Effekte, die bereinigt werden müssen
Für Nebeneffekte, die nach dem Ändern der Tasten bereinigt werden müssen, oder wenn das
verlässt die Komposition. Verwenden Sie
DisposableEffect
Wenn sich die DisposableEffect
-Schlüssel ändern, muss die zusammensetzbare Funktion entfernt werden.
der Bereinigung) und durch erneutes Aufrufen des Effekts zurückgesetzt wird.
So können Sie z. B. Analyseereignisse senden, die auf
Lifecycle
Ereignisse
mit einer
LifecycleObserver
Wenn Sie in Compose auf diese Ereignisse warten möchten, registrieren Sie sich mit einem DisposableEffect
und
können Sie die Registrierung des Beobachters aufheben.
@Composable fun HomeScreen( lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current, onStart: () -> Unit, // Send the 'started' analytics event onStop: () -> Unit // Send the 'stopped' analytics event ) { // Safely update the current lambdas when a new one is provided val currentOnStart by rememberUpdatedState(onStart) val currentOnStop by rememberUpdatedState(onStop) // If `lifecycleOwner` changes, dispose and reset the effect DisposableEffect(lifecycleOwner) { // Create an observer that triggers our remembered callbacks // for sending analytics events val observer = LifecycleEventObserver { _, event -> if (event == Lifecycle.Event.ON_START) { currentOnStart() } else if (event == Lifecycle.Event.ON_STOP) { currentOnStop() } } // Add the observer to the lifecycle lifecycleOwner.lifecycle.addObserver(observer) // When the effect leaves the Composition, remove the observer onDispose { lifecycleOwner.lifecycle.removeObserver(observer) } } /* Home screen content */ }
Im Code oben wird durch den Effekt observer
zum
lifecycleOwner
. Wenn sich lifecycleOwner
ändert, wird der Effekt entfernt und
mit der neuen lifecycleOwner
neu gestartet.
Ein DisposableEffect
muss eine onDispose
-Klausel als letzte Anweisung enthalten.
in seinem Codeblock. Andernfalls zeigt die IDE einen Build-Zeitfehler an.
SideEffect
: Erstellungsstatus in Nicht-Compose-Code veröffentlichen
Wenn Sie den Status der Erstellung für Objekte freigeben möchten, die nicht vom Typ „Compose“ verwaltet werden, verwenden Sie den
SideEffect
zusammensetzbar sind. Mit einem SideEffect
wird sichergestellt, dass der Effekt nach jedem
erfolgreiche Neuzusammensetzung. Andererseits ist es falsch,
bevor eine erfolgreiche Neuzusammensetzung garantiert wird.
wenn Sie den Effekt direkt in eine zusammensetzbare Funktion schreiben.
Mithilfe der Analysebibliothek können Sie beispielsweise Nutzer in Segmente
durch Hinzufügen benutzerdefinierter Metadaten (in diesem Beispiel „Nutzereigenschaften“).
auf alle nachfolgenden Analyseereignisse anwenden. Um den Nutzertyp der
aktuellen Nutzer zu Ihrer Analysebibliothek hinzu, verwenden Sie SideEffect
, um den Wert zu aktualisieren.
@Composable fun rememberFirebaseAnalytics(user: User): FirebaseAnalytics { val analytics: FirebaseAnalytics = remember { FirebaseAnalytics() } // On every successful composition, update FirebaseAnalytics with // the userType from the current User, ensuring that future analytics // events have this metadata attached SideEffect { analytics.setUserProperty("userType", user.userType) } return analytics }
produceState
: Status ohne Erstellung in einen Erstellungsstatus umwandeln
produceState
startet eine Koroutine für die Komposition, die Werte in eine
hat State
zurückgegeben. Damit kannst du
Konvertieren Sie den Status „Nicht erstellt“ in den Status „Verfassen“, z. B. mit externen
abogesteuerten Status wie Flow
, LiveData
oder RxJava
in der
Komposition
Der Produzent wird gestartet, wenn produceState
die Komposition betritt, und wird
wird beim Verlassen der Komposition abgebrochen. Die zurückgegebene State
-Anfrage führt zu einer Zusammenführung:
wird keine Neuzusammensetzung ausgelöst.
Obwohl produceState
eine Koroutine erstellt, kann sie auch verwendet werden, um zu beobachten,
Datenquellen ohne Unterbrechung. Um das Abo für diese Quelle zu entfernen, verwenden Sie
die
awaitDispose
.
Das folgende Beispiel zeigt, wie Sie mit produceState
ein Bild aus dem
Netzwerk. Die zusammensetzbare Funktion loadNetworkImage
gibt einen State
-Wert zurück, der
die auch in anderen zusammensetzbaren
Funktionen verwendet werden können.
@Composable fun loadNetworkImage( url: String, imageRepository: ImageRepository = ImageRepository() ): State<Result<Image>> { // Creates a State<T> with Result.Loading as initial value // If either `url` or `imageRepository` changes, the running producer // will cancel and will be re-launched with the new inputs. return produceState<Result<Image>>(initialValue = Result.Loading, url, imageRepository) { // In a coroutine, can make suspend calls val image = imageRepository.load(url) // Update State with either an Error or Success result. // This will trigger a recomposition where this State is read value = if (image == null) { Result.Error } else { Result.Success(image) } } }
derivedStateOf
: Konvertiert ein oder mehrere Zustandsobjekte in einen anderen Status
In „Compose“ erfolgt die Neuzusammensetzung jedes Mal, wenn sich ein beobachtetes Statusobjekt oder eine zusammensetzbare Eingabe ändert. Ein Zustandsobjekt ändern, als die Benutzeroberfläche tatsächlich aktualisiert werden muss, und führt zu einer unnötigen Neuzusammensetzung.
Sie sollten den derivedStateOf
verwenden.
, wenn sich die Eingaben für eine zusammensetzbare Funktion häufiger als nötig ändern
um sich neu zusammenzusetzen. Das kommt häufig vor, wenn sich etwas häufig ändert, z. B.
eine Scrollposition, aber die zusammensetzbare Funktion muss nur darauf reagieren,
einen bestimmten Grenzwert erreichen. derivedStateOf
erstellt ein neues Objekt für den Erstellungsstatus,
dass sie nur so oft aktualisiert wird, wie Sie benötigen. Auf diese Weise
ähnlich wie bei Kotlin-Datenflüssen
distinctUntilChanged()
.
Richtige Verwendung
Das folgende Snippet zeigt einen geeigneten Anwendungsfall für derivedStateOf
:
@Composable // When the messages parameter changes, the MessageList // composable recomposes. derivedStateOf does not // affect this recomposition. fun MessageList(messages: List<Message>) { Box { val listState = rememberLazyListState() LazyColumn(state = listState) { // ... } // Show the button if the first visible item is past // the first item. We use a remembered derived state to // minimize unnecessary compositions val showButton by remember { derivedStateOf { listState.firstVisibleItemIndex > 0 } } AnimatedVisibility(visible = showButton) { ScrollToTopButton() } } }
In diesem Snippet ändert sich firstVisibleItemIndex
jedes Mal, wenn das erste sichtbare Element
Änderungen. Wenn Sie scrollen, ändert sich der Wert zu 0
, 1
, 2
, 3
, 4
, 5
usw.
Eine Neuzusammensetzung muss jedoch nur erfolgen, wenn der Wert größer als 0
ist.
Diese Abweichung bei der Aktualisierungshäufigkeit bedeutet,
dass dies ein guter Anwendungsfall für
derivedStateOf
Falsche Verwendung
Ein häufiger Fehler besteht darin, beim Kombinieren von zwei
Compose-Objekten den Fehler
sollten Sie derivedStateOf
verwenden, da Sie einen "Ableitungsstatus" angeben. Dieses
ist reiner Aufwand und nicht erforderlich, wie das folgende Snippet zeigt:
// DO NOT USE. Incorrect usage of derivedStateOf. var firstName by remember { mutableStateOf("") } var lastName by remember { mutableStateOf("") } val fullNameBad by remember { derivedStateOf { "$firstName $lastName" } } // This is bad!!! val fullNameCorrect = "$firstName $lastName" // This is correct
In diesem Snippet muss fullName
genauso oft aktualisiert werden wie firstName
und
lastName
Daher findet keine übermäßige Neuzusammensetzung statt.
derivedStateOf
ist nicht erforderlich.
snapshotFlow
: Zustand von „Compose“ in Abläufe konvertieren
snapshotFlow
verwenden
zum Umrechnen von State<T>
in einen kalten Flow verschieben. snapshotFlow
führt seinen Block aus, wenn das Gerät erfasst und ausgegeben wird
das Ergebnis der darin gelesenen State
-Objekte. Wenn eines der State
-Objekte
snapshotFlow
-Block mutate-Werte enthalten, gibt der Flow den neuen Wert aus
an seinen Collector, wenn der neue Wert nicht gleich
den vorherigen ausgegebenen Wert (dieses Verhalten ähnelt dem
Flow.distinctUntilChanged
)
Das folgende Beispiel zeigt einen Nebeneffekt, bei dem erfasst wird, wann der Nutzer scrollt das erste Element in einer Liste an Analytics übergeben:
val listState = rememberLazyListState()
LazyColumn(state = listState) {
// ...
}
LaunchedEffect(listState) {
snapshotFlow { listState.firstVisibleItemIndex }
.map { index -> index > 0 }
.distinctUntilChanged()
.filter { it == true }
.collect {
MyAnalyticsService.sendScrolledPastFirstItemEvent()
}
}
Im obigen Code wird listState.firstVisibleItemIndex
in einen Ablauf konvertiert, der
können von der Leistungsfähigkeit
der Flow-Operatoren profitieren.
Effekte neu starten
Einige Effekte in „Schreiben“, z. B. LaunchedEffect
, produceState
oder
DisposableEffect
eine variable Anzahl von Argumenten, Schlüsseln,
brechen Sie den laufenden Effekt ab und starten Sie einen neuen mit den neuen Schlüsseln.
Die typische Form für diese APIs lautet:
EffectName(restartIfThisKeyChanges, orThisKey, orThisKey, ...) { block }
Aufgrund der Feinheiten dieses Verhaltens können Probleme auftreten, wenn die Parameter die zum Neustart des Effekts verwendet wurden, nicht die richtigen sind:
- Wenn ein Neustart weniger Auswirkungen hat, als er sollte, können Fehler in der App auftreten.
- Ein Neustart kann ineffizient sein, als sie eigentlich sein sollten.
Als Faustregel gilt: Veränderliche und unveränderliche Variablen, die im Effektblock von
sollte der zusammensetzbaren Funktion als Parameter hinzugefügt werden. Abgesehen davon
können weitere Parameter hinzugefügt werden, um den Neustart des Effekts zu erzwingen. Wenn die Änderung von
Eine Variable sollte den Effekt nicht neu starten. Die Variable sollte umschlossen werden.
in rememberUpdatedState
Wenn die Variable nie
da es in eine remember
ohne Schlüssel verpackt ist, müssen Sie
Variable als Schlüssel für den Effekt übergeben.
Im oben gezeigten DisposableEffect
-Code nimmt der Effekt als Parameter der
lifecycleOwner
wird in diesem Block verwendet, da jede Änderung dazu führen sollte,
einen Effekt neu zu starten.
@Composable
fun HomeScreen(
lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
onStart: () -> Unit, // Send the 'started' analytics event
onStop: () -> Unit // Send the 'stopped' analytics event
) {
// These values never change in Composition
val currentOnStart by rememberUpdatedState(onStart)
val currentOnStop by rememberUpdatedState(onStop)
DisposableEffect(lifecycleOwner) {
val observer = LifecycleEventObserver { _, event ->
/* ... */
}
lifecycleOwner.lifecycle.addObserver(observer)
onDispose {
lifecycleOwner.lifecycle.removeObserver(observer)
}
}
}
currentOnStart
und currentOnStop
werden als DisposableEffect
nicht benötigt
Schlüssel, da ihr Wert sich bei der Zusammensetzung nie aufgrund der Verwendung von
rememberUpdatedState
. Wenn Sie lifecycleOwner
nicht als Parameter übergeben und
Es ändert sich, HomeScreen
wird neu zusammengesetzt, DisposableEffect
wird jedoch nicht entsorgt
und neu gestartet. Das verursacht Probleme, da die falsche lifecycleOwner
die ab diesem Zeitpunkt genutzt werden.
Konstanten als Schlüssel
Sie können eine Konstante wie true
als Effektschlüssel verwenden,
Dem Lebenszyklus der Anrufwebsite folgen Es gibt gültige Anwendungsfälle für
wie im LaunchedEffect
-Beispiel oben zu sehen. Zuvor sollten Sie jedoch
überlegen Sie sich genau,
ob das wirklich nötig ist.
Empfehlungen für dich
- Hinweis: Der Linktext wird angezeigt, wenn JavaScript deaktiviert ist.
- State und Jetpack Compose
- Kotlin für Jetpack Compose
- Ansichten in „Compose“ verwenden