Jetpack Compose可组合页配合Flow用协程,LaunchedEffect报错如何解决
报错原因
LaunchedEffect 是带有 @Composable 注解的函数,只能在可组合函数的上下文中调用。你将它写在了 Button 的 onClick 回调中,该回调不属于可组合上下文,因此触发了编译错误。
正确实现方式
第一步:修改ViewModel
不要暴露 suspend 类型的 login 方法给UI层,改为普通方法,内部通过viewModelScope启动协程处理Flow的收集逻辑,避免UI层处理协程作用域问题:
@HiltViewModel class LoginViewModel @Inject constructor(private val toLogin: ToLogin) : ViewModel() { private val _usernameValue = mutableStateOf("") val usernameValue: State<String> = _usernameValue private val _passwordValue = mutableStateOf("") val passwordValue: State<String> = _passwordValue fun setUsernameValue(username: String) { _usernameValue.value = username } fun setPasswordValue(password: String) { _passwordValue.value = password } // 改为普通方法,内部用viewModelScope启动协程 fun login() { viewModelScope.launch { val r = toLogin(usernameValue.value, passwordValue.value) r.collect { Log.d("XTRACE", it.toString()) // 后续可在此处处理登录结果,比如跳转页面、提示错误等 } } } }
第二步:修改LoginScreen点击逻辑
直接在onClick中调用viewModel的login方法即可,不需要LaunchedEffect:
@Composable fun LoginScreen( navController: NavController, viewModel: LoginViewModel = hiltViewModel() ) { Box( modifier = Modifier.fillMaxSize().fillMaxHeight() ) { Column( modifier = Modifier.fillMaxWidth().padding(15.dp), ) { // TextField username 注意绑定 viewModel.setUsernameValue 回调 // TextField password 注意绑定 viewModel.setPasswordValue 回调 Button( onClick = { // 直接调用login方法即可 viewModel.login() }, ) { Text(text = stringResource(id = R.string.login)) } } } }
如果你确实需要用LaunchedEffect实现(比如需要关联可组合生命周期),也可以通过状态触发的方式实现:
- 在ViewModel中定义一个
Boolean类型的触发状态,比如val triggerLogin = mutableStateOf(false)- 点击按钮时把
triggerLogin.value设为true- 在可组合函数的顶层(不能放在回调里)写
LaunchedEffect(viewModel.triggerLogin.value) { if (viewModel.triggerLogin.value) { viewModel.login() } }
不过这种方案对于单次点击触发的场景比较冗余,不如直接在ViewModel中启动协程的方案简洁。
内容的提问来源于stack exchange,提问作者BeGo
相关产品推荐
相关产品推荐

