如何在可组合的屏幕中使用协同程序?
Posted
技术标签:
【中文标题】如何在可组合的屏幕中使用协同程序?【英文标题】:How to use a coroutine inside a composable screen with flow? 【发布时间】:2021-11-10 03:55:52 【问题描述】:我正在使用 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))
视图模型
@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());
API
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)
正确的做法是什么?
【问题讨论】:
当您需要在渲染视图时更改状态时,您应该使用LaunchedEffect
,例如当视图刚刚出现或状态值之一已更改时。在你的情况下@Francesc 是对的。
【参考方案1】:
你必须使用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))
【讨论】:
【参考方案2】:补充 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 系统有时会多次调用可组合方法。
【讨论】:
以上是关于如何在可组合的屏幕中使用协同程序?的主要内容,如果未能解决你的问题,请参考以下文章