Maxime Michel
02/21/2025, 2:37 PMThis isn't correct.
You are turning your Hot flow of states into a cold flow by using onStart, which creates a new cold flow. Basically, you are losing the distinction and statefullness of your flow by using that operator.
Then you are turning the cold flow back to a hot flow by immediatelly subscribing to it using the `stateIn` operator. You aren't really loading initial data upon the subscription, you're just hiding this problem with the `Lazy` delegate.
At best, this is suboptimal because you're recreating the same flow multiple times and creating an additional coroutine that does basically nothing (as StateFlow does not need conversion in the first place).
I find the comment rather blunt and it seems more accusing than helpful but anyway. Here is the implementation that isn't quite right to them:
private val _state: MutableStateFlow<State> = MutableStateFlow(initialState)
val state: StateFlow<State> by lazy {
_state.onStart {
viewModelScope.launch {
initialDataLoad()
}
}.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5000L),
initialValue = initialState
)
}
I'm wondering firstly if the comment is correct and secondly, if so, how it could be fixed?Maxime Michel
02/21/2025, 2:45 PMmarlonlom
02/22/2025, 6:02 AMMark Marcel
02/22/2025, 7:24 AMLeo N
02/22/2025, 2:52 PMval state: StateFlow<State> by lazy {
_state.onStart {
...
}.stateIn(
...
)
}
How does it really work? Can you please expand more on this 🙏Maxime Michel
02/24/2025, 10:05 AMMaxime Michel
02/25/2025, 9:51 AMprivate val _state: MutableStateFlow<State> = MutableStateFlow(initialState)
val state: StateFlow<State> =_state.onSubscription {
initialDataLoad()
}.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5000L),
initialValue = initialState
)
At first glance, it seems to me that this does what I want: It calls a function when a collector appears and provides a StateFlow without an intermediate Cold Flow conversion.Mark Marcel
02/25/2025, 10:22 AMMaxime Michel
02/25/2025, 10:28 AMinitialState
contains a preconfigured state used when the screen is first displayed. Typically a loading state or other.
The initialDataLoad
function is intended to be used to call some network (or other) related operation to get some data for the screen.
A simple example would be a screen that displays a list that is provided via an API. At first, when you open the screen, there is nothing which is expected but there can be a loader or something similar and when the state starts to be collected (ie, the screen is drawn), we call the initialDataLoad
function to call the API and get the data which will then be loaded into the _state
variable (MVI does this with events and others things)Mark Marcel
02/25/2025, 10:34 AMMaxime Michel
02/25/2025, 10:35 AMMark Marcel
02/25/2025, 10:36 AMMaxime Michel
02/25/2025, 10:38 AMLeo N
02/25/2025, 10:42 AMval loom = loomIn(viewModelScope) { fetchData() }
val loomState = loom.state
// Collect state in your ViewModel
loomState.collect { state ->
when (state) {
is LoomState.Loading -> showLoadingIndicator()
is LoomState.Loaded -> updateUI(state.data)
is LoomState.Error -> showError(state.exception)
}
}
// or UI
@Composable
fun MyScreen(viewModel: MyViewModel = viewModel()) {
// Collect UI state from ViewModel
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
}
Mark Marcel
02/25/2025, 10:43 AMMaxime Michel
02/25/2025, 10:47 AMMark Marcel
02/25/2025, 10:52 AMMaxime Michel
02/25/2025, 10:53 AMMark Marcel
02/25/2025, 10:57 AMMaxime Michel
02/25/2025, 11:00 AMLeo N
02/25/2025, 11:08 AMMark Marcel
02/25/2025, 11:18 AMMaxime Michel
02/25/2025, 12:11 PMMohammad Fallah
02/25/2025, 3:25 PMval state = flow { initialDataLoad() }
.stateIn(...)
you also can use asFlow()
on the method reference, like this:
::initialDataLoad.asFlow()
it is a shorthand for `
flow { emit(initialDataLoad()) }
Hope it helps.Maxime Michel
02/25/2025, 3:34 PM