Compose + Coroutines

Launched Effect

println("recomposing hello button")
    if(value % 3 ==0)  {
        Text(text = "can divide by 3")
        LaunchedEffect(key1 = value){
            try {
                println("********************")
                println("current thread : ${Thread.currentThread().name}")
                withContext(Dispatchers.IO){
                    println("IO thread? : ${Thread.currentThread().name}")
                }
                delay(3000)
                println("ending launched effect")
            }catch (e:CancellationException){
                println("******Launched effect was cancelled******")
            }
        }
    }

similarly to :

val result=remember(key){calculate()}

"hacks/workarounds" already in official examples

@Composable
fun LandingScreen(modifier: Modifier = Modifier, onTimeout: () -> Unit) {
    Box(modifier = modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
        // 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 or onTimeout changes, 
        // the delay shouldn't start again.
        LaunchedEffect(true) {
            delay(SplashWaitTime)
            currentOnTimeout()
        }

        Image(painterResource(id = R.drawable.ic_crane_drawer), contentDescription = null)
    }
}

coroutine "problems"

  LaunchedEffect(key1 = value){
            try {
                println("********************")
                println("current thread : ${Thread.currentThread().name}")
                withContext(Dispatchers.IO){
                    println("IO thread? : ${Thread.currentThread().name}")
                    Thread.sleep(3000)
                }
                println("ending launched effect")
            }catch (e:CancellationException){
                println("******Launched effect was cancelled******")
            }
        }

DisposableEffect?

 DisposableEffect(key1 = value){
            try {
                println("********************")
                println("current thread : ${Thread.currentThread().name}")
                withContext(Dispatchers.IO){  // won't work
                    println("IO thread? : ${Thread.currentThread().name}")
                    delay(3000)
                }
                println("ending launched effect")
            }catch (e:CancellationException){
                println("******Launched effect was cancelled******")
            }
            onDispose { /**clean**/ }
        }
        
        // current thread : main !!!!

Coroutine outside compose

Wrong approach

 val onClickProcedure: () -> Unit = {
        LaunchedEffect(value){   //WONMT work!!
            state.updateEvent(value + 1)
        }
    }

    Button(onClick = onClickProcedure){
        WhiteText("clicked $value times")
    }

...........

  val onClickProcedure: @Composable () -> Unit = {  //Add @Composable
        LaunchedEffect(value){
            state.updateEvent(value + 1)
        }
    }

    Button(onClick = onClickProcedure){   //error transfered here
        WhiteText("clicked $value times")
    }

Correct Approach

 val onClickProcedure: () -> Unit = {
        scope.launch(Dispatchers.IO) {
            try{
                withContext(Dispatchers.Main){
                    state.updateEvent(value + 1)
                }
                delay(2000)
                println("*************Coroutine ended**************")
            }catch (e:CancellationException){
                println("*************Coroutine cancelled**************")
            }
        }
    }

    Button(onClick = onClickProcedure){
        WhiteText("clicked $value times")
    }

SharedFlow in compose

val flow = MutableSharedFlow<Int>()

@Composable
fun FlowExperiment1(){

    val scope = rememberCoroutineScope()

    LaunchedEffect(true){
        scope.launch(Dispatchers.IO) {
            (1 .. 50).forEach{
                println("emiting $it")
                flow.emit(it)
                delay(50)
            }
        }
    }

    val flowValue by flow.collectAsState(initial = 0)
    Text("received $flowValue")
}

Last updated

Was this helpful?