我只想在加载可组合项时运行代码一次。因此,我使用 LaunchedEffect 并将 key 设置为 true 来实现此目的。
LaunchedEffect(true) {
// do API call
}
此代码工作正常,但每当有任何配置更改(例如屏幕旋转)时,此代码都会再次执行。如果配置更改,如何防止它再次运行?
rememberSaveable
进行 API 调用的信息:它会在配置更改时生效。
var initialApiCalled by rememberSaveable { mutableStateOf(false) }
if (!initialApiCalled) {
LaunchedEffect(Unit) {
// do API call
initialApiCalled = false
}
}
此解决方案的缺点是,如果在 API 调用完成之前配置发生更改,
LaunchedEffect
协程将被取消,您的 API 调用也将被取消。
最干净的解决方案是使用视图模型,并在内部执行API调用
init
:
class ScreenViewModel: ViewModel() {
init {
viewModelScope.launch {
// do API call
}
}
}
@Composable
fun Screen(viewModel: ScreenViewModel = viewModel()) {
}
官方文档推荐像这样传递视图模型作为参数。在产品代码中,您不需要向此视图传递任何参数,只需像 Screen()
那样调用它即可:视图模型将由默认的
viewModel()
参数创建。它已移至测试/预览功能的参数,如此答案所示。
这是一个代码示例
class MyViewModel : ViewModel() {
private val uiScreenState: : MutableStateFlow<WhatEverState> =
MutableStateFlow(WhatEverIntialState).also {
loadState()
}
fun loadState(): StateFlow<WhatEverState>> {
return users
}
private fun loadUsers() {
// Do an asynchronous operation to fetch users.
}
}
使用此代码时,您根本不必在活动中调用loadstate,您只需监听观察者即可。您可以查看以下代码进行收听
class MyFragment : Fragment {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
return ComposeView(requireContext()).apply {
setContent {
StartingComposeTheme {
Box(modifier = Modifier.fillMaxSize()) {
val state by viewModel.uiScreenState.collectAsState()
when (state) {
//do something
}
}
}
}
}
}
}}
LaunchedEffect(lifeCycleOwner) {
}
看起来不错
Islam Mansour 答案对于 UI 的专用 viewModel 很有用,但我的情况是由许多 UI 片段共享 ViewModel
就我而言,上述答案并不能解决当用户导航到相关 UI 部分时仅首次调用 API 的问题。
因为我在NavHost
中有多个可组合的UI作为Fragment 还有我的
ViewModel
穿过所有碎片因此,API 应该仅在用户导航到所需片段时调用
所以,下面的惰性属性初始化器解决了我的问题;
val myDataList by lazy {
Log.d("test","call only once when called from UI used inside)")
loadDatatoThisList()
mutableStateListOf<MyModel>()
}
mutableStateListOfLIST_TYPE
<
>当数据添加到此时自动重构UI 由
by lazy
附加的变量仅在显式调用时初始化一次