我正在使用jetpack Compose和flow,我得到一个错误,试图获得与LaunchedEffect
可组合屏幕内的数据
@Composable的调用只能在@Composable函数的上下文中发生
这里我详细说明了我的代码流程
这里它在LaunchedEffect
@Composable
fun LoginScreen(
navController: NavController,
viewModel: LoginViewModel = hiltViewModel()
) {
Box(
modifier = Modifier.fillMaxSize().fillMaxHeight()
) {
Column(
modifier = Modifier.fillMaxWidth().padding(15.dp),
) {
//TextField username
//TextField password
Button(
onClick = {
// Error
// @Composable invocations can only happen from the context of a @Composable function
LaunchedEffect(Unit) {
viewModel.login(
viewModel.passwordValue.value, viewModel.usernameValue.value
)
}
},
) {
Text(text = stringResource(id = R.string.login))
}
}
}
}
ViewModel h5> API h5>
class ToLogin @Inject constructor(private val apiAuth: AuthApiSource) {
operator fun invoke(username: String, password: String): Flow<Result<AccessToken?>> =
flow {
val response = runCatching {
val token = apiAuth.login(username, password)
token.getOrThrow()
}
emit(response)
}
}
正确的做法是什么?
你必须使用rememberCoroutineScope
:
@Composable
fun LoginScreen(
navController: NavController,
viewModel: LoginViewModel = hiltViewModel()
) {
val scope = rememberCoroutineScope()
Box(
modifier = Modifier.fillMaxSize().fillMaxHeight()
) {
Column(
modifier = Modifier.fillMaxWidth().padding(15.dp),
) {
//TextField username
//TextField password
Button(
onClick = {
// Error
// @Composable invocations can only happen from the context of a @Composable function
scope.launch {
viewModel.login(
viewModel.passwordValue.value, viewModel.usernameValue.value
)
}
},
) {
Text(text = stringResource(id = R.string.login))
}
}
}
}
作为Francesc回答的补充,您可以将viewModel的方法作为参数传递,如:
@Composable
fun LoginScreen(
navController: NavController,
clickButtonCallback: () -> Unit
) {
Box(
modifier = Modifier.fillMaxSize().fillMaxHeight()
) {
Column(
modifier = Modifier.fillMaxWidth().padding(15.dp)
) {
//TextField username
//TextField password
Button(onClick = clickButtonCallback) {
Text(text = stringResource(id = R.string.login))
}
}
}
}
并在调用可组合方法时使用:
val scope = rememberCoroutineScope()
val viewModel: LoginViewModel = hiltViewModel()
LoginScreen(
navController = navController,
clickButtonCallback = {
scope.launch {
viewModel.getNewSessionToken()
}
}
)
这样做,你的ViewModel只创建一次,因为可组合方法有时被Android系统调用多次。