You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Kotlin Compose Desktop中使用runBlocking时如何刷新UI?

问题:Compose Desktop调用API前无法先刷新UI显示“Wait...”

在Compose Desktop项目中,尝试在发起API请求前让UI显示“Wait...”,但由于请求被包裹在runBlocking中,导致UI线程被阻塞,直到请求完成才会更新,无法先显示加载状态。以下是原始代码:

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()
    }
}

解决方案:使用协程避免阻塞UI线程

核心问题是runBlocking会阻塞当前的UI线程,导致UI无法及时更新。Compose提供了专门的协程工具来处理异步操作,不会阻塞UI:

方法1:使用rememberCoroutineScope

rememberCoroutineScope会创建一个与Composable生命周期绑定的协程作用域,在其中启动协程不会阻塞UI线程:

修改后的App函数代码:

@OptIn(ExperimentalComposeUiApi::class)
@Composable
@Preview
fun App() {
    var text by remember { mutableStateOf("Test button") }
    // 获取与Composable绑定的协程作用域
    val scope = rememberCoroutineScope()

    Box(
        modifier = Modifier.fillMaxSize(),
        contentAlignment = Alignment.Center
    ) {
        Column(
            modifier = Modifier.padding(50.dp)
        ) {
            Button(
                onClick = {
                    // 立即更新UI显示加载状态
                    text = "Wait..."
                    // 在协程中执行耗时操作
                    scope.launch {
                        delay(5000)// 模拟耗时请求
                        val response: HttpResponse = client.request("https://myapi.com/") {
                            // 配置请求参数
                        }

                        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 }
            )
        }
    }
}

方法2:使用LaunchedEffect(适合基于状态触发的场景)

如果需要根据某个状态变量触发异步操作,可以用LaunchedEffect,它会在状态变化时自动启动协程:

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

    // 当isLoading变为true时启动协程
    LaunchedEffect(isLoading) {
        if (isLoading) {
            delay(5000)// 模拟耗时请求
            val response: HttpResponse = client.request("https://myapi.com/") {
                // 配置请求参数
            }

            if (response.status == HttpStatusCode.OK) {
                val body = response.body<String>()
                println(body)
            } else {
                println("Error has occurred")
            }
            // 请求完成后更新状态
            isLoading = false
            text = "Test button"
        }
    }

    Box(
        modifier = Modifier.fillMaxSize(),
        contentAlignment = Alignment.Center
    ) {
        Column(
            modifier = Modifier.padding(50.dp)
        ) {
            Button(
                onClick = {
                    isLoading = true
                    text = "Wait..."
                },
                modifier = Modifier.fillMaxWidth(),
                enabled = !isLoading // 加载时禁用按钮防止重复点击
            ) {
                Text(text)
            }
            OutlinedTextField(
                value = "",
                singleLine = true,
                onValueChange = { text = it }
            )
        }
    }
}

关键说明

  • runBlocking会阻塞调用线程(这里是UI线程),导致UI无法响应和更新,绝对不要在UI事件回调中使用runBlocking。
  • rememberCoroutineScope和LaunchedEffect都是Compose推荐的异步处理方式,它们的协程运行在非阻塞的上下文中,不会影响UI刷新。
  • 第二种方法中添加了enabled = !isLoading,可以防止用户在加载过程中重复点击按钮,提升交互体验。

内容的提问来源于stack exchange,提问作者ramires.cabral

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.17 11:50:46