HomeAndroidFacet Results Abstract in Jetpack Compose

Facet Results Abstract in Jetpack Compose


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 date worth 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 a CoroutineScope. The next instance makes use of the MainScope().

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

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Most Popular

Recent Comments