我正在使用喷气背包撰写和流,并且尝试使用 LaunchedEffect
在可组合屏幕中获取数据时出错
@Composable的调用只能在 @ 可组合函数的上下文中进行
在这里,我详细介绍了我的代码流程
在这里,它在启动效果
中生成错误
@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))
}
}
}
}
@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
}
suspend fun login(username: String, password: String) {
val r = toLogin(username, password);
r.collect {
Log.d("XTRACE", it.toString());
}
}
}
class AuthApiSource @Inject constructor(
private val loginApiService: LoginApiService,
) {
suspend fun login(username: String, password: String): Result<AccessToken?> = runCatching {
loginApiService.toLogin(
username = username,
password = password,
).body();
}
}
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 系统有时会多次调用可组合方法。