Ravi
06/03/2022, 10:22 AMSideEffect
pattern from orbit-mvi
Mikolaj Leszczynski
06/03/2022, 11:06 AMAntipattern #1: State about payment completion can be lostThis is not an issue with Orbit, since we ensure delivery by only sending the events when there are subscribers listening, and caching them if there are none. That said, this prevents being able to multicast the side effects (as with State). However in 99% of cases this is not a problem.
Antipattern #2: Telling the UI to take an actionYeah fair points there. I have no counter arguments 🙂
Antipattern #3: Not handling the one-off event immediatelyTo be honest, I’ve read this several times and don’t get this point. Something that is not super clearly communicated are the major downsides of this approach. 1. Every event must be handled by the UI. This means the UI needs to call a ViewModel method to signal that it has “handled” the event. This creates extra verbosity. 2. Repeatable one-off events are more difficult to model - your state would require some sort of list for these. If you used a field, they might get overwritten. (2) should rarely happen, but (1) is important! From our own experience, UI toolkit is poorly suited for handling these acknowledgements. I wouldn’t recommend using it with state-based one-off events. However, if you use Jetpack Compose this becomes a viable strategy and I can certainly see the appeal! Compose is much better at handling frequent state updates and two-way binding situations. This is just my personal opinion of course! With Orbit, you are free to use either approach! I’m curious to hear what others think.
Mikolaj Leszczynski
06/03/2022, 12:03 PMAntipattern #3: Not handling the one-off event immediatelyOK, I think I get this one. Imagine you get an event with a one-off event inside. When handling the event, you need to immediately “handle” the event by calling the ViewModel to change the state to reflect that. If you do that in a multithreaded environment you run the risk of repeating the event in case some other thread updates it before your “handling” has a chance to apply, i.e. you get another state update with the “one-off” event still there first, before you get your handled version. I’m not sure if this would actually be an issue in Orbit, there’s probably an extremely slim chance of this happening due to two facts: 1. We handle state updates on a single thread 2. StateFlow conflates the updates That said, this is theoretically possible (?). I’d have to run some tests to confirm. To be 100% sure this doesn’t happen we’d have to allow you to reduce from the same thread that handles the one-off event i.e. the UI thread. Which kind of goes against our current design.
Mikolaj Leszczynski
06/03/2022, 12:21 PMMikolaj Leszczynski
06/03/2022, 12:25 PMkioba
06/06/2022, 10:01 AMSideEffect
in orbit.
PS: I'm not an expert on compose or orbit internals but both LaunchEffect
and SideEffect
has similar goals.Mikolaj Leszczynski
06/06/2022, 10:04 AMkioba
06/06/2022, 10:05 AMGuilherme Delgado
06/06/2022, 3:48 PMAntipattern #2: Telling the UI to take an actionI kind of “disagree” with Manuel in this one. He suggest that “navigation” could be part of the StateUI. But I think navigation it’s more a SideEffect. Thus, in my opinion, SideEffects represent “OneOffEventState” where UiState is exclusively for UI.
Antipattern #3: Not handling the one-off event immediatelyIf I understood correctly the point, and so far in my experiments - while using a FSM - this didn’t became a problem, because the FSM is the one who knows/validates where you can go to, and not the “UIState” itself. Orbit does this very well in my opinion: 2 flows for different purpose. Usually I have a LaunchedEffect listening for one-off events in my Activity/Fragment, and inside my Composables root I have another listening for UiStates:
override fun onCreate(savedInstanceState: Bundle?) {
...
setContent {
with(viewModel) {
collectSideEffect {
when (it) {
is ShowAbout -> AlertDialog.Builder(this@LandingActivity)
.setTitle(R.string.dialog_about_title)
.setMessage(R.string.dialog_about_message)
.setCancelable(false)
.setNeutralButton(android.R.string.ok) { dialog, _ -> dialog.dismiss() }
.create()
.show()
is NavigateToHome -> {
startActivity(Intent(this@LandingActivity, MainActivity::class.java))
finish()
}
is NotifyWithToast -> Toast.makeText(this@LandingActivity, it.message, Toast.LENGTH_SHORT).show()
}
}
AppTheme { LandingScreen(this) }
}
}
}
@Composable
fun LandingScreen(viewModel: LandingViewModel) {
with(viewModel.collectAsState().value) {
Landing(...)
}
}
appmattus
06/06/2022, 5:15 PMappmattus
06/06/2022, 5:16 PMkioba
06/06/2022, 5:31 PMappmattus
06/06/2022, 6:40 PMOleksii Malovanyi
06/07/2022, 7:17 AMthere is no silver bullet herecan’t agree more. And while idiomatically Vivo is right in terms of classic redux-like systems, for me it seems like overkill for mobile development and this rational exists more due to implementation issues, rather than architecture benefits ¯\_(ツ)_/¯
Oleksii Malovanyi
06/07/2022, 7:18 AMDispatchers.Main.immediate
does look like something to think about 🤔twisterrob
05/22/2023, 3:33 PM