Kotlin推论出现故障



我正在Kotlin编写一个Android Jetpack Compose移动应用程序。我在郊游方面遇到了很多问题。我使用的是共享的首选项,所以我需要在协同程序范围内调用那些挂起的函数。我是新来的郊游,我显然不太了解它们。

我有一个选项,我正在保存在Android数据存储中,以便在应用程序启动时绕过启动屏幕。我在启动屏幕上还有一个复选框,从现在起可以绕过屏幕。我需要能够在启动屏幕显示时选中该选项,但如果确实显示了,也可以允许更改复选框。我最终想要一份汉堡菜单来设置这个和其他选项。由于DataStore操作是异步的,所以我不得不处理挂起例程

这是我的来电者,我正试图在这里决定是否绕过启动屏幕

var shouldBypassSplashScreen: Boolean = false
runBlocking {
val job = launch {
shouldBypassSplashScreen = mainViewModel.isBypassSplashScreenOptChecked()
}
job.join()
}
LogHelper.logDebug("${MODULE_NAME}.$FUNCTION_NAME: shouldBypassSplashScreen = $shouldBypassSplashScreen")

这是被调用的函数,在我的MainViewModel中(对不起,下面的粗体字不起作用,但我添加了下面的前两行):

**
var bypassSplashScreenOpt: MutableLiveData<Boolean>
= MutableLiveData(false)
private set
**
...
suspend fun isBypassSplashScreenOptChecked(): Boolean {
val FUNCTION_NAME = "isBypassSplashScreenOptChecked()"
viewModelScope.launch {
userPrefRepo.shouldBypassSplashScreen().collect {
withContext(Dispatchers.Main) {
LogHelper.logDebug("$MODULE_NAME.$FUNCTION_NAME: bypassSplashScreenOpt.value = ${bypassSplashScreenOpt.value}, it = $it")
bypassSplashScreenOpt.value = it
}
}
}
return bypassSplashScreenOpt.value!!
}

我尝试过很多不同的方法,但无论我做什么,调用函数后的日志消息总是显示在被调用函数中的日志消息之前。问题是,我的boolean在调用函数中总是false,因为它似乎不会等到调用程序运行。

===============

更新:

首先,感谢你们回答了我的问题。我现在意识到我对郊游的了解是多么的少。很明显,我需要仔细阅读这些建议,并努力思考。现在,即使是看你的建议,我也有点力不从心。

为了尽量不在我的原始帖子中放太多代码,我意识到我没有给出足够的上下文来说明我要做的事情。所以我更新了上面的原始帖子,新信息在粗体文本中。

我在这里真正想做的就是在DataStore中有一些选项,我可以在屏幕上设置这些选项,并检查我的逻辑。由于从DataStore读取数据是一个异步事件,所以我只需要能够等到它取回数据后才能检查、显示或设置数据。这看起来不应该那么复杂,但很明显。

我根据Tenfour04的初始响应更改了MainViewModel中的函数,该部分工作正常。

suspend fun isBypassSplashScreenOptChecked(): Boolean {
return userPrefRepo.shouldBypassSplashScreen().first()
}

从我读到的所有内容来看,我完全同意你的评论,即我不应该使用runBlocking。然而,我完全不明白为什么会编译:

runBlocking {
launch {
shouldBypassSplashScreen = mainViewModel.isBypassSplashScreenOptChecked()
}
}

但这告诉我,当我编译launch时,它是未定义的:

launch {
shouldBypassSplashScreen = mainViewModel.isBypassSplashScreenOptChecked()
}

这确实是我使用runBlocking的唯一原因,因为没有它我就无法克服编译错误

isBypassSplashScreenOptChecked()函数的问题在于,它不执行任何挂起的工作,而是异步地启动一些新的、不相关的协程,不等待该协程,只在其他协程启动之前返回变量的当前值。

它是一个suspend函数,所以它只能在您调用它时获得流的最新值。完全收集它没有意义,所以对它调用first()

假设userPrefRepo.shouldBypassSplashScreen()返回一个Flow并且它正常工作,那么这个函数的正确实现是:

suspend fun isBypassSplashScreenOptChecked(): Boolean {
return userPrefRepo.shouldBypassSplashScreen().first()
}

我不知道你在一旁搞什么bypassSplashScreenOpt。如果你没有在其他地方使用它,就删除它。

您在Composable中的代码非常糟糕。您应该永远避免调用runBlocking,尤其是在Composable中,因为这意味着每次进行重组时,它都会阻塞一些长时间运行的操作。

顺便说一句,你启动一个协同程序并立即join()是没有意义的。该代码的正确实现(如果在可组合程序中使用runBlocking不是一个糟糕的想法)应该是val shouldBypassSplashScreen = runBlocking { mainViewModel.isBypassSplashScreenOptChecked() }


因此,由于检索该值需要一段时间,并且您应该避免在作文中出现阻塞,因此可以使用一些不同的策略。这里只有一个建议:

由于这可能不会花超过一秒钟的时间,只需在你的可堆肥中显示一个空白屏幕,直到你决定是否显示启动屏幕。因此,你在没有阻塞的情况下完成了你的合成,主线程也不会被冻结,这会让你的应用程序和设备看起来很糟糕。

当它第一次组成你的入口点可组合时,使用一个启动的效果来运行这个。当重组发生时,没有必要重新运行它。首先,这个组合中没有任何东西会导致它重新组合,但同时,你只是在等待第一个值到达,然后离开这个组合。

LaunchedEffect(Unit) {
if (userPrefRepo.shouldBypassSplashScreen()) {
navController.nagivate("main")
else {
navController.nagivate("splash")
}
}
// Just don't have anything displayed in this entry point composable.

我想我有一个解决方案可以修复这个错误。您在日志中只看到默认状态值,因为您试图直接在组合上下文中启动协程,而不是使用效果处理程序。

让我们从更改ViewModel中的代码开始:

// A mutable state flow holding the value.
private val _isBypassSplashScreenOptChecked = MutableStateFlow(false)
val isBypassSplashScreenOptChecked = _isBypassSplashScreenOptChecked.asStateFlow()
// Observe the value from the repository.
private fun getBypassSplashScreenOptChecked() {
viewModelScope.launch(Dispatchers.IO) {
userPrefRepo.shouldBypassSplashScreen().collect { isOptChecked ->
// Update the state value
withContext(Dispatchers.Main) {
LogHelper.logDebug("$MODULE_NAME shouldBypassSplashScreen: $isOptChecked")
_isBypassSplashScreenOptChecked.update { isOptChecked }
}
}
}
}
// Start observing the value from the repository
// when the ViewModel is created.
init {
getBypassSplashScreenOptChecked()
}

最后是可组合:

// Collect the state value from the ViewModel
val isBypassSplashScreenOptChecked by viewModel.isBypassSplashScreenOptChecked.collectAsState()
// Log the value whenever it changes
LaunchedEffect(key1 = isBypassSplashScreenOptChecked) {
LogHelper.logDebug("$MODULE_NAME shouldBypassSplashScreen: $isBypassSplashScreenOptChecked")
}

要了解有关Compose中状态的更多信息,您可以访问此链接。

相关内容

  • 没有找到相关文章

最新更新