dave08
12/24/2023, 2:40 PMdave08
12/24/2023, 2:40 PMabstract class VMStateMachine<S: Any, A: Any, E: Any>(
initialState: S
) : FlowReduxStateMachine<S, A>(initialState) {
protected val _effects = MutableSharedFlow<AppSideEffects>()
val effects = _effects.asSharedFlow()
}
abstract class ComposeViewModelBase<S: Any, A: Any, E: Any> : ViewModel() {
abstract val stateMachine: VMStateMachine<S, A, E>
val uiState: StateFlow<S> by lazy {
stateMachine.state.stateIn(
viewModelScope,
SharingStarted.WhileSubscribed(5_000),
ExternalAppsUiState2.Loading
) as StateFlow<S>
}
val effects by lazy { stateMachine.effects }
fun dispatch(event: A) {
viewModelScope.launch {
stateMachine.dispatch(event)
}
}
}
dave08
12/24/2023, 2:41 PMas StateFlow
if stateMaching.state
is a Flow<S>?dave08
12/24/2023, 2:42 PMval uiState by vm.uiState.collectAsState()
when(uiState) {
is ApplicationDetailsState.Loading -> {}
is ApplicationDetailsState.ShowContent -> {
// Here uiState is NOT smart casted to ShowContent... why?
Youssef Shoaib [MOD]
12/24/2023, 2:42 PMstateMachine.state.stateIn
Youssef Shoaib [MOD]
12/24/2023, 2:44 PMuiState
will return the same object, so you need to do something like:
val uiState by vm.uiState.collectAsState()
when(val uiState = uiState) {
is ApplicationDetailsState.Loading -> {}
is ApplicationDetailsState.ShowContent -> {
// Here uiState is smart casted to ShowContent...
dave08
12/24/2023, 2:45 PMdave08
12/24/2023, 2:47 PMval uiState
in the when
it managed to smart cast that at least!Youssef Shoaib [MOD]
12/24/2023, 2:48 PMby lazy
. I believe it's because Compose's State.getValue
could theoretically return a different value between your when branch condition and inside the when blockYoussef Shoaib [MOD]
12/24/2023, 2:49 PMuiState
is delegated, and so it has a custom getter, and hence there's no guarantees that the object won't change between different calls to State.getValue
dave08
12/24/2023, 2:51 PMYour localBut it's ais delegated, and so it has a custom getter,uiState
val
?dave08
12/24/2023, 2:51 PMlazy
can't change the instancedave08
12/24/2023, 2:52 PMgetValue
, I guess it COULD change though... so the by
IS the problem... just not necessarily the lazy
?Youssef Shoaib [MOD]
12/24/2023, 3:01 PMState
. What the compiler sees is that yes you have one singular State
instance that you've defined, the issue is that your local variable is delegating to State
, and the Kotlin compiler doesn't know the intrinsic mechanics of State
, so to it, it thinks that State
might change the value it's returning between calls. Let me stress this enough: every time you write uiState
, that's replaced by a call to State.getValue
. Every time. And so there is no telling what side effects can change the value returned by that function.
If I understand correctly, the way that compose works is that it will cancel your composition if the value changes and will run it again from the beginning, but the compiler isn't aware of that. If that's how it works, then rerhaps the Compose compiler plugin can somehow trick the compiler into allowing smart casts theredave08
12/24/2023, 3:04 PM@Stable
val uiState: StateFlow<S> by lazy {
stateMachine.state.stateIn(
viewModelScope,
SharingStarted.WhileSubscribed(5_000),
stateMachine.initialState
)
}
doesn't seem to workdave08
12/24/2023, 3:05 PM@Stable
tells the compose plugin that it's immutable?dave08
12/24/2023, 3:05 PMYoussef Shoaib [MOD]
12/24/2023, 3:05 PMval uiState = vm.uiState.collectAsState().value
But that means your entire composable would be recomposed when uiState changes. The upside of the by
approach is that only the places that directly use uiState
would get recomposeddave08
12/24/2023, 3:08 PMval uiState by vm.uiState.collectAsState()
... not mine in the view model... (maybe it would also cause these problems, but the one in the screen causes them first...)Youssef Shoaib [MOD]
12/24/2023, 3:08 PMdave08
12/24/2023, 3:11 PM