简体   繁体   English

在 Android Jetpack Compose 中使用 State 时出现 java.lang.IllegalStateException

[英]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解决这个问题的三种方法是

    1. To call the method in a launched effect block in your composable在可组合项的已启动效果块中调用该方法
    1. Or set the Context to Dispatchers.Main when setting value of the mutableState using withContext(Dispatchers.Main)或者在使用 withContext(Dispatchers.Main) 设置 mutableState 的值时将 Context 设置为 Dispatchers.Main
    1. Or change the mutable state in viewModel to mutableState flow and use collectAsState() in composable to collect it as a state.或者将 viewModel 中的可变 state 更改为 mutableState 流,并使用可组合项中的 collectAsState() 将其收集为 state。

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.

相关问题 Jetpack 撰写 java.lang.IllegalStateException:应用程序无效 - Jetpack compose java.lang.IllegalStateException: Invalid applier Jetpack 组合 LazyVerticalGrid 项目抛出 java.lang.IllegalStateException - Jetpack compose LazyVerticalGrid items throws java.lang.IllegalStateException jetpack compose java.lang.IllegalStateException:开始/结束不平衡 - jetpack compose java.lang.IllegalStateException: Start/end imbalance java.lang.IllegalStateException:合成需要一个活动的合成上下文(Android Jetpack Compose) - java.lang.IllegalStateException: Composition requires an active composition context (Android Jetpack Compose) 无法启动活动 ComponentInfo {className} java.lang.IllegalStateException:找不到颜色! (Android JetPack Compose) - Unable to start activity ComponentInfo {className} java.lang.IllegalStateException: No colors found! (Android JetPack Compose) Jetpack Compose 测试在连续运行时导致“java.lang.IllegalStateException: no event up from DESTROYED” - Jetpack Compose Tests cause "java.lang.IllegalStateException: no event up from DESTROYED" when run in succession java.lang.IllegalStateException:在 Jetpack compose 中滚动分页惰性列时,LayoutNode 应附加到所有者 - java.lang.IllegalStateException: LayoutNode should be attached to an owner when scrolling paginated Lazy column in Jetpack compose Jetpack Compose 视图中的提前返回会因“java.lang.IllegalStateException:开始/结束不平衡”而崩溃? - Early return in Jetpack Compose view will crash with `java.lang.IllegalStateException: Start/end imbalance`? Jetpack Compose:带有verticalScroll修饰符的列中的LazyVerticalGrid抛出java.lang.IllegalStateException - Jetpack Compose:LazyVerticalGrid within Column with verticalScroll modifier throws java.lang.IllegalStateException Android中的java.lang.IllegalStateException - java.lang.IllegalStateException In Android
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM