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
相关产品推荐
相关产品推荐

