Vla*_*ken 6 android kotlin kotlin-coroutines android-jetpack-compose
我ViewModel
使用 Kotlin 密封类为 UI 提供不同的状态。此外,我使用androidx.compose.runtime.State
对象来通知 UI 状态的变化。
如果错误MyApi
发生的要求,我把UIState.Failure
给MutableState
对象,然后我得到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)
Run Code Online (Sandbox Code Playgroud)
ViemModel 代码:
@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
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
我试图延迟设置 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
}
}
Run Code Online (Sandbox Code Playgroud)
我在 Composable 函数中观察 State 对象如下:
@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
)
}
}
Run Code Online (Sandbox Code Playgroud)
撰写版本: 1.0.0-beta03
如何UIState
使用 Compose处理密封类State
,使其不会抛出 IllegalStateException?
小智 50
解决这个问题的三种方法是
Joh*_*lly 17
https://kotlinlang.slack.com/archives/CJLTWPH7S/p1613581738163700中有一个关于看起来有些类似问题的讨论。
我认为该讨论的一些相关部分(来自亚当·鲍威尔)
至于快照状态的线程安全方面,您遇到的是快照事务性的结果。
当拍摄快照时(组合在幕后为您完成此操作),当前活动的快照是线程本地的。组合中发生的所有事情都是该事务的一部分,并且该事务尚未提交。
因此,当您在组合中创建一个新的 mutableStateOf 并将其传递给另一个线程时(如问题片段中的 GlobalScope.launch 所做的那样),您实际上已经让对尚不存在的快照状态的引用从事务中逃逸了。
这里的确切场景略有不同,但我认为关键问题相同。可能不会完全按照这种方式来做,但至少在这里它通过将init
in 的内容移动到新getCategories()
方法中来工作,然后从LaunchedEffect
block 中调用该方法。FWIW 我在其他地方所做的事情(同时仍然在 中调用init
)是StateFlow
在视图模型中使用,然后collectAsState()
在 Compose 代码中调用。
@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
)
}
}
Run Code Online (Sandbox Code Playgroud)
Vla*_*ken 13
因此,经过多次尝试解决这个问题,我找到了解决方案。在/sf/answers/4682450951/答案的帮助下,我发现快照是事务性的并在 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
}
}
}
Run Code Online (Sandbox Code Playgroud)
归档时间: |
|
查看次数: |
998 次 |
最近记录: |