Tower Guidev2
12/02/2022, 1:38 PMval uiState by viewModel.uiState.collectAsStateWithLifecycle()
Tower Guidev2
12/02/2022, 1:50 PMprivate val listiState: Flow<ListUiState> = repository.listItems().map { ListUiState(it) }
private val displayItemState = MutableStateFlow(DisplayItemState())
val uiState: StateFlow<ListScreenUiState> =
combine(ListUiState, displayItemState) { Lists, displayItem -> ListScreenUiState(Lists, displayItem) }
.onStart { displayItemState.emit(DisplayItemState()) }
.stateIn(scope = viewModelScope, started = SharingStarted.WhileSubscribed(), initialValue = ListScreenUiState())
i do not like having this "hacky" fix
if i could replace the secondary StateFlow with a SharedFlow it would be great as SharedFlow is Fire-and-Forget
however when i make this change the user never sees the list items as combine doesnt appear to emit anything
until all its combined flows have emitted a value
can I improve on the above some how?Casey Brooks
12/02/2022, 3:31 PMChannel
for sending these one-off events to ensure they only get handled once, which basically runs alongside the persistent state, not as part of it.
I’ve also been building an MVI library, Ballast to handle this kind of stuff for you so you can just focus on the application logic, which you may find useful. You can join the #ballast channel if you have any questions about how to use the library, or about MVI in generalTower Guidev2
12/02/2022, 4:14 PMCasey Brooks
12/02/2022, 4:17 PMSharedFlow
for this use-case, but that is not usually the best since it could handle the event more than once if there are multiple subscribers to the SharedFlow
. A Channel is guaranteed to only process each item once, and the API for configuring and using them both is nearly the same. I think a lot of folks see a Channel as having been replaced by SharedFlow, but in reality they’re both two different things with different use-cases, and this situation of handling one-off events in an MVI model is definitely a case where you want a Channel instead of SharedFlowCasey Brooks
12/02/2022, 4:19 PMFrancesc
12/02/2022, 5:15 PMLaunchedEffect
on the list and communicating to the viewmodel that an event has been handled, once the UI consumes itCasey Brooks
12/02/2022, 5:48 PMSingleLiveEvent
as a response to people doing that same kind of pattern with LiveData
, because it’s easy to mess it up and forget to reset the “event” in the stateFrancesc
12/02/2022, 6:12 PMFrancesc
12/02/2022, 6:17 PMPablichjenkov
12/02/2022, 6:35 PMPablichjenkov
12/02/2022, 6:35 PMFrancesc
12/02/2022, 6:40 PMdata class AddEntryState(
// other stuff
val events: List<AddEntryEvent>,
) : State
@Composable
fun AddEntryScreen(
state: AddEntryState,
onDescriptionChange: (String) -> Unit,
onBackClick: () -> Unit,
onEventConsumed: () -> Unit,
modifier: Modifier = Modifier,
entry: ListEntry? = null,
screenState: AddEntryScreenState = rememberAddEntryScreenState(entry),
) {
LaunchedEffect(key1 = state.events) {
state.events.firstOrNull()?.let { event ->
when (event) {
AddEntryEvent.Dismiss -> onBackClick()
is AddEntryEvent.PasswordGenerated -> screenState.onPasswordUpdated(event.password)
}
onEventConsumed()
}
}
// other stuff
}
Casey Brooks
12/02/2022, 6:41 PMBUFFERED
channel instead of the default RENDEZVOUS
as used in the code snippet from the second article. When you do this, the Channel becomes a kind of State, and if the UI is not around to read from the Channel when the event is sent to it, the event stays in the Channel until the UI is able to consume it (this is how Events are implemented in Ballast, too). And 2) is done as the article suggested, collecting from the Channel on Dispatchers.Main.immediate
(which is the default for Compose).
So my take is that a Channel isn’t necessarily an anti-pattern to the one-off events, but you do need to know a bit about Coroutines to use this pattern “correctly”, while the method of setting-and-resetting in the state is more fool-proof. It’s all in a more nuanced understanding of “state” and how it gets delivered to the UI, with the understanding that State sometimes is ephemeral, and Events are sometimes statefulFrancesc
12/02/2022, 6:44 PMFrancesc
12/02/2022, 6:45 PMFrancesc
12/02/2022, 6:45 PMCasey Brooks
12/02/2022, 6:47 PMBUFFERED
channel for this casePablichjenkov
12/02/2022, 7:08 PMPablichjenkov
12/02/2022, 7:09 PMTower Guidev2
12/05/2022, 7:08 AMPablichjenkov
12/05/2022, 7:53 AM