A aspect impact in Compose is a change to the state of the app that occurs exterior the scope of a composable operate.
To deal with these unwanted side effects, you should use impact handlers. There are 2 sorts of impact handlers.
Suspended Impact Handler | Non-suspended Impact Handler |
LaunchedEffect() | DisposableEffect() |
rememberCoroutineScope() | SideEffect() |
Suspended Impact Handler
A suspended impact handler means that you can carry out unwanted side effects in compose utilizing coroutines.
Right here is the abstract that covers totally different situations:
Eventualities | LaunchedEffect() | rememberCoroutineScope() |
Launch impact/coroutine from inside a composable? | Sure | No |
Launch impact/coroutine exterior a composable? E.g. from the callback | No | Sure |
When impact/coroutine is began? | LaunchedEffect() enters composition |
CoroutineScope.launch() is named |
When impact/coroutine is canceled? | LaunchedEffect() leaves composition, LaunchedEffect() is restarted (impact’s key modified) |
CoroutineScope leaves composition. Notice: When coroutine is restarted, the earlier coroutine will NOT be canceled |
When impact/coroutine is restarted? | LaunchedEffect() ‘s key modified (whereas it’s nonetheless in composition) |
CoroutineScope.launch() is named once more |
Non-suspended Impact Handler
For non-suspended/non-coroutine unwanted side effects, you should use these non-suspended impact handlers.
Eventualities | DisposableEffect() | SideEffect() |
Launch impact from inside a composable? | Sure | Sure |
Launch impact exterior a composable? E.g. from the callback | No | No |
When impact is began? | DisposableEffect() enters composition, after the present composition completes |
SideEffect() enters composition, after the present composition completes |
When impact is canceled? | Impact can NOT be canceled, because the execution can NOT be suspended(non-suspended operate) | Impact can NOT be canceled – similar as DisposableEffect() |
When impact is restarted? | DisposableEffect() ‘s key modified (whereas it’s nonetheless in composition) |
SideEffect() enters recomposition – each recomposition triggers the SideEffect() to run |
When onDispose() is named? |
DisposableEffect() leaves composition, DisposableEffect() is restarted (impact’s key modified) Notice: When the impact completes, it would NOT set off onDispose() |
N/A |
Varied Facet-effect States
These are the assorted compose state helpers for impact handlers above.
rememberUpdatedState
rememberUpdatedState
makes certain the MutableState
is all the time up to date with the most recent worth as an alternative of caching the preliminary composition worth.
See notes (1), (2), (3), (4) and (5) under.
@Composable
enjoyable RememberUpdatedStated(worth: String) {
val oldValue by bear in mind { mutableStateOf(worth) }
val newValue by rememberUpdatedState(worth)
LaunchedEffect(true) {
delay(1000)
}
}
Solely the
newValue
has the most recent up to dateworth
from the second recomposition
For those who have a look at the rememberUpdatedState
implementation, it applies the brand new worth to the MutableState
at any time when it’s known as or throughout recomposition.
@Composable
enjoyable <T> rememberUpdatedState(newValue: T): State<T> = bear in mind {
mutableStateOf(newValue)
}.apply { worth = newValue }
Technically,
val newValue by rememberUpdatedState(worth)
is equal to
var newValue by bear in mind { mutableStateOf(worth) }
newValue = worth
produceState
produceState
is the sugar syntax for LaunchedEffect()
.
LaunchedEffect()
under might be written as
@Composable
enjoyable DemoLaunchedEffect(){
var textual content by bear in mind { mutableStateOf("")}
LaunchedEffect(true) {
repeat(10) { rely ->
delay(1000)
textual content = rely.toString()
}
}
}
produceState()
under:
@Composable
enjoyable DemoProduceState(){
val textual content by produceState(initialValue ="") {
repeat(10) { rely ->
delay(1000)
worth = rely.toString()
}
}
}
One small distinction of produceState
is it produces State<T>
as an alternative of MutableState<T>
. Thus, you see the textual content
above is asserted with val
as an alternative of var
.
One further factor produceState
can do is awaitDispose()
operate, which lets you detect when the produceState
leaves the composition. That is much like onDispose()
from DisposableEffect()
@Composable
enjoyable DemoProduceStateAwaitDispose(){
val textual content by produceState(initialValue ="") {
val job = MainScope().launch {
repeat(10) { rely ->
delay(1000)
worth = rely.toString()
}
}
awaitDispose {
job.cancel()
}
}
}
Nonetheless, to make use of
awaitDispose()
you MUST manually launch a coroutine with aCoroutineScope
. The next instance makes use of theMainScope()
.
With out the MainScope()
or any CorutineScope
, the awaitDispose()
is not going to be known as. For instance, the next code will not work.
@Composable
enjoyable DemoProduceStateAwaitDispose(){
val textual content by produceState(initialValue ="") {
repeat(10) { rely ->
delay(1000)
worth = rely.toString()
}
awaitDispose {
job.cancel()
}
}
}
For some purpose, this requirement just isn’t documented. It takes me some time to determine the
awaitDispose()
requires you to launch the coroutine manually to get it working appropriately.
derivedStateOf
Undecided why that is categorized below unwanted side effects, however what derivedStateOf()
does, is combining a number of states right into a single State
.
@Composable
enjoyable DemoDerivedStateOf() {
var value1 by bear in mind { mutableStateOf(true) }
var value2 by bear in mind { mutableStateOf(false) }
val derivedValue by bear in mind(value1) {
derivedStateOf {
"value1: $value1 + value2: $value2"
}
}
}
When value1
or value2
is modified, derivedValue
is up to date. The bear in mind(value1)
is required in order that, throughout recomposition, derivedStateOf()
is skipped.
For those who take away the bear in mind(value1)
as the next, every little thing nonetheless works appropriately.
@Composable
enjoyable DemoDerivedStateOf() {
var value1 by bear in mind { mutableStateOf(true) }
var value2 by bear in mind { mutableStateOf(false) }
val derivedValue by derivedStateOf {
"value1: $value1 + value2: $value2"
}
}
Nonetheless, throughout each recomposition, this line "value1: $value1 + value2: $value2"
is executed. Thus, it merely wastes pointless sources right here.
snapShotFlow
snapShotFlow
converts State<T>
to Circulate<T>
. Right here is an instance:
@Composable
enjoyable DemoSnapShotFlow(){
var textState = bear in mind { mutableStateOf("") }
LaunchedEffect(textState) {
val movement = snapshotFlow { textState.worth }
movement.distinctUntilChanged()
movement.accumulate { textual content ->
Log.d("[SnapShotFlow]", "accumulating movement worth: $textual content")
}
}
}
For extra details about accumulating movement, discuss with the next article:
Conclusion
I simply merely doc these aspect impact handlers’ usages and their behaviors. Nonetheless, I have not identified the best way to use them successfully but. Generally I do discover it complicated which one to make use of. 🙂
Supply Code
That is my experimental code of taking part in round with unwanted side effects in Jetpack Compose. So it is perhaps a bit messy right here.
GitHub Repository: Demo_ComposeSideEffects