我正在构建一个分数跟踪应用程序,我有一个屏幕,您可以在其中向游戏中添加新玩家。这是一个简单的屏幕,用户可以指定玩家的名字和颜色。这个屏幕有自己的ViewModel
,我通过使用Koin框架将其注入到屏幕的可组合功能中,如下所示:
fun NewPlayerScreen(
viewModel: NewPlayerScreenViewModel = getViewModel(),
navController: NavHostController,
modifier: Modifier = Modifier
)
这样可以确保只要屏幕在屏幕上可见,ViewModel
就可以使用。当用户点击";保存";按钮,新玩家将被插入房间数据库。然而,我的问题是,插入数据库的操作是由NewPlayerScreenViewModel
处理的。一旦用户提交了新播放器,屏幕就会退出,ViewModel就会被销毁,这也意味着它的CoroutineScope
会被取消,这意味着我正在进行的将播放器插入数据库的数据库操作可能无法正常完成。
我知道有一个解决方案;我可以将事件从这样的功能中删除:
fun NewPlayerScreen(
viewModel: NewPlayerScreenViewModel = getViewModel(),
navController: NavHostController,
onPlayerSave: (newPlayer: Player) -> Unit,
modifier: Modifier = Modifier
)
然而,这意味着,我现在必须在MainScreenViewModel
中的另一个ViewModel
中处理插入数据库的操作,因为我的NewPlayerScreen()
可组合的父级是MainScreen()
。我不喜欢这种方法,因为我希望我的屏幕有自己的ViewModel
为自己处理数据库操作。还有其他选择吗?或者这是处理这种情况的正确方法吗?
您可以:
- 在导航到下一个屏幕之前等待插入
您可以通过将插入函数设置为suspend
函数,创建val coroutineScope = rememberCoroutineScope()
,然后在保存按钮的onClick回调上执行:
coroutineScope.launch {
viewModel.insertPlayer()
navigate()
}
这样做可以保证操作将按顺序进行,但缺点是用户必须等到添加了播放器后才能导航到下一个屏幕。
- 使用WorkManager的
OneTimeWorkRequest
安排工作
可以说,最好的(也是官方推荐的方法(是使用一个长期使用的应用程序范围(注入到ViewModel
中(来执行在ViewModel
被处理时不应取消的操作
class MyApplication : Application() {
// No need to cancel this scope as it'll be torn down with the process
val applicationScope = CoroutineScope(SupervisorJob() + otherConfig)
}
这篇文章对为什么这是最好的方法有很好的解释。