hello everyone. how are you handling one-time eve...
# compose-android
a
hello everyone. how are you handling one-time events between the viewmodel and compose? snackbars and navigation for example
m
You can model the events as part of your state. Your ui state can have a
message
String, and you can show a Snackbar when it's set, while also informing your ViewModel that you did, so it can set it back to null. Or some other mechanism that makes it only readable once.
c
Yep! Everything Marcin talked about is shown in detail here: https://developer.android.com/topic/architecture/ui-layer/events
a
I don't really like the idea of having to call the ViewModel to say that the event has been handled, doesn't seem scalable. what do you guys think about wrapping the event in a class? something like:
Copy code
open class Event<out T>(private val content: T) {

    var consumed = false
        private set

    
    fun consume(): T? {
        return if (consumed) {
            null
        } else {
            consumed = true
            content
        }
    }
}
also, I am trying to work with separated flows for UiState and NavigationState, what do you think about that? what are the concerns? any pros and cons?
Copy code
private val _uiState = MutableStateFlow(SelectWallpaperUiState.initial)
    val uiState = _uiState.asStateFlow()

    private val _navigationState = MutableStateFlow<SelectWallpaperNavigationState>(SelectWallpaperNavigationState.None)
    val navigationState = _navigationState.asStateFlow()
the first problem comes right here, since stateflow requires you to provide a initial value
and when creating the navigation StateFlow, we don't actually need/have a starting navigation event
m
Looks like your "navigationState" isn't a state then, and shouldn't use a StateFlow. You can use a SharedFlow if you don't want to have an initial state.
p
I way i do it to have a navigationEvent with an initial value of NoEvent , and every time i fire a navigationEvent it's UI responsibility to make it to NoEvent again, works both in compose and xml
And i use stateFlow only for this
Bcz if u use sharedFlow , it will work in XML but on configuration changes or if u use compose, it might create more problems then it will solve
d
I like the approach as per Google's recommendation. In my experience it does scale, opposite ones initial intuition. It also has a nice side effect of making the code far more understandable and debuggable. It comes only at one cost, effort. A worthy price to pay!
a
Bcz if u use sharedFlow , it will work in XML but on configuration changes or if u use compose, it might create more problems then it will solve
@Prateek Kumar do you mind explaining in more detail the possible problems?
so I think I'll have a
navigationEvent: Event<SelectWallpaperNavigationState>
inside my state
what do you guys think about that?
this way I keep the events inside my state, and it being an
Event
it will also handle the
one-shot
thing
p
I mean the benefit of sharedFlow will be not giving initial value and no difference check but it also requires some extra setup like replay 1 and befferoverflow drop oldest i think. Also, only for one-off event u will use shredFlow and for others u will use stateFlow? Or u can go all out sharedFlow only. But if u think of ur UI being replica of your state i don't see why stateFlow is not self sufficient. Have a initial NoState and once a state it handled set it back to NoState properly.
This is how i keep all my one-off events in just one place
d
I think the problem is really that the ViewModel even knows anything about navigation at all. The ViewModel is meant to be a state holder. The UI is a reflection of that state. Also navigation is purely a UI framework implementation detail. Those facts lead to all this confusion around how to handle navigation and events from a construct that is specifically designed to not care about that.
Putting the wrong type of logic into the ViewModel will have sideeffects on your architecture that is simply not pragmatic let alone scalable.
Let me give you an example. Lets say the user is filling out a form. When the form is completed and submitted with out issue then the user should be navigated back from where they came. The form state could look like so:
Copy code
data class FormState(val isCompleted: Boolean = false)
When the UI controller (Composable, Fragment, or Activity) observes that
isCompleted
is
true
then it decides to perform the back navigation.
The
ViewModel
does not need to know about UI details like navigation. That’s also why you might see the examples given by Google where the UI shows a
SnackBar
if a a
String
on a state in a
ViewModel
!= null
and then hiding the
SnackBar
if it is
null
. The ViewModel layer knows nothing about a purely UI concern. This makes things easier to test, maintain, reason about, and safer under some conditions.
c
also. view the bottom of the docs i linked about event streams being an anti pattern.
2023-05-09 at 17.45.30.png
p
There was a recent discussion about the subject. And yes Marcin answer is the accepted way. There doesn't seem to be a guarantee of "one time delivery" successfully. Due to config changes or screen off. So yeah verbose and boilerplate but that's the latest trend. I still gotta migrate a lot of channel based communication to this 🤫
t
p
Yeah, that article was the trigger in the last discussion
a
For snackbars, I'm using this pattern suggested by Adam and I really like it. Simple and easy to test.
p
@dewildte data class FormState(val isCompleted: Boolean = false) This example seems to work for the given usecase ,but suppose on isCompleted u are sending someone to next screen, But what if they come back, u still need to update the Viewmodel that before going to the new screen that , inCompleted is false, or else it will go into a UI loop. I do not see how we avoid notifying VM that UI has handled it.
Suppose, when we click on a button we have to get a data from VM and send it to next screen, Yes we can make a new state like abcData?Abc? = null And when it is not null , open next screen and set it back to null It gives a fake sense to VM that yes it does not know anything about UI , but still have things it should not have I still feel this way VM knows what’s going on in UI but is easier to understand
d
Assuming you are talking about navigating to a UI that is on the back stack. In that case, prior to performing the forward navigation from UI A to UI B. Just before making the call to navigate, UI A informs it’s ViewModel to reset the state.
Think about the objects as people communicating effectively. But each person is kind of on a need to know basis, like the military so to speak.
373 Views