[英]java.lang.IllegalStateException when using State in Android Jetpack Compose
I have ViewModel
with Kotlin sealed class to provide different states for UI.我有 Kotlin 密封 class 的ViewModel
为 UI 提供不同的状态。 Also, I use androidx.compose.runtime.State
object to notify UI about changes in state.另外,我使用androidx.compose.runtime.State
object 通知 UI state 的变化。
If error on MyApi
request occurs, I put UIState.Failure
to MutableState
object and then I get IllegalStateException
:如果MyApi
请求发生错误,我将UIState.Failure
放入MutableState
object 然后我得到IllegalStateException
:
java.lang.IllegalStateException: Reading a state that was created after the snapshot was taken or in a snapshot that has not yet been applied
at androidx.compose.runtime.snapshots.SnapshotKt.readError(Snapshot.kt:1524)
at androidx.compose.runtime.snapshots.SnapshotKt.current(Snapshot.kt:1764)
at androidx.compose.runtime.SnapshotMutableStateImpl.setValue(SnapshotState.kt:797)
at com.vladuken.compose.ui.category.CategoryListViewModel$1.invokeSuspend(CategoryListViewModel.kt:39)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:104)
at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:571)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:738)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:678)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:665)
ViemModel code: Viem模型代码:
@HiltViewModel
class CategoryListViewModel @Inject constructor(
private val api: MyApi
) : ViewModel() {
sealed class UIState {
object Loading : UIState()
data class Success(val categoryList: List<Category>) : UIState()
object Error : UIState()
}
val categoryListState: State<UIState>
get() = _categoryListState
private val _categoryListState =
mutableStateOf<UIState>(UIState.Loading)
init {
viewModelScope.launch(Dispatchers.IO) {
try {
val categories = api
.getCategory().schemas
.map { it.toDomain() }
_categoryListState.value = UIState.Success(categories)
} catch (e: Exception) {
//this does not work
_categoryListState.value = UIState.Error
}
}
}
}
I tried to delay setting UIState.Error - and it worked, but I don't think it is normal solution:我试图延迟设置 UIState.Error - 它起作用了,但我认为这不是正常的解决方案:
viewModelScope.launch(Dispatchers.IO) {
try {
val categories = api
.getCategory().schemas
.map { it.toDomain() }
_categoryListState.value = UIState.Success(categories)
} catch (e: Exception) {
//This works
delay(10)
_categoryListState.value = UIState.Error
}
}
I observe State object in Composable function as follows:我在 Composable function 中观察到 State object 如下:
@Composable
fun CategoryScreen(
viewModel: CategoryListViewModel,
onCategoryClicked: (Category) -> Unit
) {
when (val uiState = viewModel.categoryListState.value) {
is CategoryListViewModel.UIState.Error -> CategoryError()
is CategoryListViewModel.UIState.Loading -> CategoryLoading()
is CategoryListViewModel.UIState.Success -> CategoryList(
categories = uiState.categoryList,
onCategoryClicked
)
}
}
Compose Version: 1.0.0-beta03
撰写版本: 1.0.0-beta03
How to process sealed class UIState
with Compose State
so that it doesn't throw IllegalStateException?如何使用 Compose State
处理密封的 class UIState
,以免抛出 IllegalStateException?
Three ways this can be resolved are解决这个问题的三种方法是
There's a discussion about what looks like somewhat similar issue in kotlinlang.slack.com/archives/CJLTWPH7S/p1613581738163700.在 kotlinlang.slack.com/archives/CJLTWPH7S/p1613581738163700 中有一个关于看起来有点相似的问题的讨论。
Some relevant parts of that discussion I think (from Adam Powell)我认为该讨论的一些相关部分(来自亚当鲍威尔)
As for the thread-safety aspects of snapshot state, what you've encountered is the result of snapshots being transactional.至于快照 state 的线程安全方面,您遇到的是快照是事务性的结果。
When a snapshot is taken (and composition does this for you under the hood) the currently active snapshot is thread-local.当拍摄快照时(合成会在后台为您执行此操作),当前活动的快照是线程本地的。 Everything that happens in composition is part of this transaction, and that transaction hasn't committed yet.组合中发生的一切都是该事务的一部分,并且该事务尚未提交。
So when you create a new mutableStateOf in composition and then pass it to another thread, as the GlobalScope.launch in the problem snippet does, you've essentially let a reference to snapshot state that doesn't exist yet escape from the transaction.因此,当您在组合中创建一个新的 mutableStateOf 然后将其传递给另一个线程时,正如问题片段中的 GlobalScope.launch 所做的那样,您实际上已经让对尚不存在的快照 state 的引用从事务中逃脱。
The exact scenario is a little different here but I think same key issue.确切的情况在这里有点不同,但我认为同样的关键问题。 Probably wouldn't do it exactly this way but at least here it worked by moving contents of init
in to new getCategories()
method which is then called from LaunchedEffect
block.可能不会完全这样做,但至少在这里它通过将init
的内容移动到新的getCategories()
方法来工作,然后从LaunchedEffect
块调用该方法。 FWIW what I've done elsewhere in case like this (while still invoking in init
) is using StateFlow
in view model and then call collectAsState()
in Compose code. FWIW 我在其他地方做的这种情况(同时仍在init
中调用)是在视图 model 中使用StateFlow
,然后在 Compose 代码中调用collectAsState()
。
@Composable
fun CategoryScreen(
viewModel: CategoryListViewModel,
onCategoryClicked: (Category) -> Unit
) {
LaunchedEffect(true) {
viewModel.getCategories()
}
when (val uiState = viewModel.categoryListState.value) {
is CategoryListViewModel.UIState.Error -> CategoryError()
is CategoryListViewModel.UIState.Loading -> CategoryLoading()
is CategoryListViewModel.UIState.Success -> CategoryList(
categories = uiState.categoryList,
onCategoryClicked
)
}
}
So, after more attempts of fixing this issue I found a solution.因此,经过更多尝试解决此问题后,我找到了解决方案。 With help of https://stackoverflow.com/a/66892156/13101450 answer I've get that snapshots are transactional and run on ui thread - changing dispatcher helped:在https://stackoverflow.com/a/66892156/13101450答案的帮助下,我知道快照是事务性的并且在 ui 线程上运行 - 更改调度程序帮助:
viewModelScope.launch(Dispatchers.IO) {
try {
val categories = api
.getCategory().schemas
.map { it.toDomain() }
_categoryListState.value = UIState.Success(categories)
} catch (e: Exception) {
withContext(Dispatchers.Main) {
_categoryListState.value = UIState.Error
}
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.