防止 LaunchedEffect 在配置更改时重新 运行

Prevent LaunchedEffect from re-running on configuration change

我只想在加载可组合项时 运行 代码一次。所以我使用 LaunchedEffect with key as true 来实现这个。

LaunchedEffect(true) {
    // do API call
}

此代码运行良好,但每当有任何配置更改(如屏幕旋转)时,都会再次执行此代码。如果配置更改,如何防止它再次 运行ning?

最简单的解决方案是存储有关您是否使用 rememberSaveable 进行 API 调用的信息:它会在配置更改时生效。

var initialApiCalled by rememberSaveable { mutableStateOf(false) }
if (!initialApiCalled) {
    LaunchedEffect(Unit) {
        // do API call
        initialApiCalled = false
    }
}

此解决方案的缺点是,如果在 API 调用完成之前更改配置,LaunchedEffect 协程将被取消,您的 API 调用也会被取消。

最干净的解决方案是使用视图模型,并在 init:

中执行 API 调用
class ScreenViewModel: ViewModel() {
    init {
        viewModelScope.launch {
            // do API call
        }
    }
}

@Composable
fun Screen(viewModel: ScreenViewModel = viewModel()) {
    
}

official documentation. In the prod code you don't need to pass any parameter to this view, just call it like Screen(): the view model will be created by default viewModel() parameter. It is moved to the parameter for test/preview capability as shown in 推荐像这样传递视图模型作为参数。

我假设最好的方法是在 livedata/stateflow 惰性创建上使用 .also ,这样您就可以保证只要视图模型处于活动状态,loadState 只会被调用一次,并且还可以保证除非有人正在收听,否则不会调用服务本身。然后你从视图模型中监听状态,并且不需要调用任何东西 api 从启动的效果中调用,你的代码也会对特定状态做出反应。

这是一个代码示例

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.
}
}

使用这段代码的时候,根本不需要在activity中调用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
                    }
                }
            }
        }
    }
}

}}

@Islam Mansour 对 UI 的专用 viewModel 的回答很有效,但我的案例由许多 UIs 片段共享 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>()
}

mutableStateListOf<LIST_TYPE> 当数据添加到此

时自动重组UI

by lazy 应用的变量仅在显式调用时初始化一次