I'm facing an issue when collecting StateFlow
in Composable
function. Although I checked its type, an exception is thrown indicating that cannot cast.
It runs correctly in the first call to iewModel.fetchFixtures()
, but it throws exception on the next time this method is invoked.
Did I misimplement something?
@Composable
fun Home(
...
) {
...
val uiState by viewModel.fixtureStateFlow.collectAsState()
when (uiState) {
is UiState.Loading -> Box(modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
CircularProgressIndicator()
}
is UiState.Success<List<Fixture>> -> LazyColumn(
modifier.fillMaxSize(),
contentPadding = PaddingValues(16.dp)
) {
items((uiState as UiState.Success<List<Fixture>>).data) { fixture -> // line 53
FixtureItem(fixture = fixture) {
println("Clicked")
}
}
}
}
}
ViewModel
@HiltViewModel
class HomeViewModel @Inject constructor(
...
) : ViewModel() {
...
private val _fixturesUiState = MutableStateFlow<UiState<List<Fixture>>>(UiState.Loading)
val fixturesUiState = _fixturesUiState.asStateFlow()
fun fetchFixtures(date: LocalDate) {
viewModelScope.launch(coroutineExceptionHandler) {
_fixturesUiState.value = UiState.Loading
_fixturesUiState.value = UiState.Success(GetFixtures(repository, date).execute())
}
}
}
sealed class UiState<out T> {
object Loading : UiState<Nothing>()
class Success<T>(
val data: T
) : UiState<T>()
}
Stacktrace
FATAL EXCEPTION: main
Process: com.dainghia.brewfootball, PID: 21288
java.lang.ClassCastException: com.dainghia.brewfootball.ui.UiState$Loading cannot be cast to com.dainghia.brewfootball.ui.UiState$Success
at com.dainghia.brewfootball.ui.main.composable.FixtureListKt$FixtureList$2$1.invoke(FixtureList.kt:53)
at com.dainghia.brewfootball.ui.main.composable.FixtureListKt$FixtureList$2$1.invoke(FixtureList.kt:52)
at androidx.compose.foundation.lazy.LazyListItemProviderKt$rememberLazyListItemProvider$1$itemProviderState$1.invoke(LazyListItemProvider.kt:54)
at androidx.compose.foundation.lazy.LazyListItemProviderKt$rememberLazyListItemProvider$1$itemProviderState$1.invoke(LazyListItemProvider.kt:53)
at androidx.compose.runtime.snapshots.Snapshot$Companion.observe(Snapshot.kt:2200)
at androidx.compose.runtime.DerivedSnapshotState.currentRecord(DerivedState.kt:161)
at androidx.compose.runtime.DerivedSnapshotState.getCurrentValue(DerivedState.kt:231)
at androidx.compose.runtime.snapshots.SnapshotStateObserver$ObservedScopeMap.recordInvalidation(SnapshotStateObserver.kt:523)
at androidx.compose.runtime.snapshots.SnapshotStateObserver.drainChanges(SnapshotStateObserver.kt:66)
at androidx.compose.runtime.snapshots.SnapshotStateObserver.access$drainChanges(SnapshotStateObserver.kt:38)
at androidx.compose.runtime.snapshots.SnapshotStateObserver$applyObserver$1.invoke(SnapshotStateObserver.kt:45)
at androidx.compose.runtime.snapshots.SnapshotStateObserver$applyObserver$1.invoke(SnapshotStateObserver.kt:43)
at androidx.compose.runtime.snapshots.SnapshotKt.advanceGlobalSnapshot(Snapshot.kt:1768)
at androidx.compose.runtime.snapshots.SnapshotKt.advanceGlobalSnapshot(Snapshot.kt:1779)
at androidx.compose.runtime.snapshots.SnapshotKt.access$advanceGlobalSnapshot(Snapshot.kt:1)
at androidx.compose.runtime.snapshots.Snapshot$Companion.sendApplyNotifications(Snapshot.kt:568)
at androidx.compose.ui.platform.GlobalSnapshotManager$ensureStarted$1.invokeSuspend(GlobalSnapshotManager.android.kt:46)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
at androidx.compose.ui.platform.AndroidUiDispatcher.performTrampolineDispatch(AndroidUiDispatcher.android.kt:81)
at androidx.compose.ui.platform.AndroidUiDispatcher.access$performTrampolineDispatch(AndroidUiDispatcher.android.kt:41)
at androidx.compose.ui.platform.AndroidUiDispatcher$dispatchCallback$1.run(AndroidUiDispatcher.android.kt:57)
at android.os.Handler.handleCallback(Handler.java:942)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loopOnce(Looper.java:201)
at android.os.Looper.loop(Looper.java:288)
at android.app.ActivityThread.main(ActivityThread.java:7884)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:936)
Suppressed: kotlinx.coroutines.DiagnosticCoroutineContextException: [androidx.compose.ui.platform.AndroidUiFrameClock@b9756e1, StandaloneCoroutine{Cancelling}@eeebc06, AndroidUiDispatcher@b9412c7]
I tried to make this change in Composable
function:
val uiState = fixtureStateFlow.collectAsState().value
And this works.
Could you please help me understand the root cause of this issue?
You can modify your code in this manner to fix the above mentioned crash:
val uiState by viewModel.fixtureStateFlow.collectAsState()
uiState.let{ state->
when (state) {
is UiState.Loading -> Box(modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
CircularProgressIndicator()
}
is UiState.Success<List<Fixture>> -> LazyColumn(
modifier.fillMaxSize(),
contentPadding = PaddingValues(16.dp)
) {
val fixtures = state.data
items(fixtures) { fixture -> // line 53
FixtureItem(fixture = fixture) {
println("Clicked")
}
}
}
}
}
Now in context of your question: Why there is no crash while using this-> val uiState = fixtureStateFlow.collectAsState().value
, the answer is:
val value by viewmodel.viewmodelvalue.collectAsState()
: In this style, you're using property delegation. The by
keyword delegates the collection of the MutableStateFlow to the collectAsState() function, which returns a State object. The value property of the State object gives you access to the current value of the MutableStateFlow.
val value = viewmodel.viewmodelvalue.collectAsState().value
: Here, you're explicitly calling the value property on the State object returned by collectAsState(). This directly gives you the current value of the MutableStateFlow.
Both styles will provide you with the same result, which is the current value of the MutableStateFlow. However, using the property delegation style (first style) is generally considered more concise and readable. It abstracts away the details of calling collectAsState() and directly provides access to the value through the value property.