Search code examples
kotlincompose-desktop

How to refresh UI in Kotlin with Compose desktop when runBlocking?


I'm learning Kotlin and Compose Desktop and I'm trying refresh the UI before fetch data from an API. But the request is running inside a runBlocking, thus the UI freezes until request is completed. This is my code, everything works.

val client = HttpClient(CIO)

@OptIn(ExperimentalComposeUiApi::class)
@Composable
@Preview
fun App() {
    var text by remember { mutableStateOf("Test button") }

    Box(
        modifier = Modifier.fillMaxSize(),
        contentAlignment = Alignment.Center
    ) {
        Column(
            modifier = Modifier.padding(50.dp)
        ) {
            Button(
                onClick = {

                    text = "Wait..."//How to refresh UI to display this text?
                    runBlocking {

                        delay(5000)//blocking test
                        val response: HttpResponse = client.request("https://myapi.com/") {
                            // Configure request parameters exposed by HttpRequestBuilder
                        }

                        if (response.status == HttpStatusCode.OK) {
                            val body = response.body<String>()
                            println(body)
                        } else {
                            println("Error has occurred")
                        }
                    }
                    text = "Test button"
                },
                modifier = Modifier.fillMaxWidth()
            ) {
                Text(text)
            }
            OutlinedTextField(
                value = "",
                singleLine = true,
                onValueChange = { text = it }
            )
        }
    }

}

fun main() = application {
    Window(
        onCloseRequest = ::exitApplication,
        state = WindowState(size = DpSize(350.dp, 500.dp)),
        title = "Compose test"
    ) {
        App()
    }
}

How to achieve that?


Solution

  • The problem here is that you are using runBlocking at all.

    The most straightforward solution for your case would be to replace your runBlocking {} with a coroutine scope. At the top of your App() function, create your scope: val scope = rememberCoroutineScope(), then instead of runBlocking you can say scope.launch {}.

    New code would be:

    @OptIn(ExperimentalComposeUiApi::class)
    @Composable
    @Preview
    fun App() {
        var text by remember { mutableStateOf("Test button") }
    val scope = rememberCoroutineScope()
    
        Box(
            modifier = Modifier.fillMaxSize(),
            contentAlignment = Alignment.Center
        ) {
            Column(
                modifier = Modifier.padding(50.dp)
            ) {
                Button(
                    onClick = {
    
                        text = "Wait..."//How to refresh UI to display this text?
                        scope.launch {
    
                            delay(5000)//blocking test
                            val response: HttpResponse = client.request("https://myapi.com/") {
                                // Configure request parameters exposed by HttpRequestBuilder
                            }
    
                            if (response.status == HttpStatusCode.OK) {
                                val body = response.body<String>()
                                println(body)
                            } else {
                                println("Error has occurred")
                            }
                        }
                        text = "Test button"
                    },
                    modifier = Modifier.fillMaxWidth()
                ) {
                    Text(text)
                }
                OutlinedTextField(
                    value = "",
                    singleLine = true,
                    onValueChange = { text = it }
                )
            }
        }
    
    }
    

    I saw one comment say to use LaunchedEffect() but this won't work in your case since you can't use that in an onClick since it's not a Composable.