UI Eventing on Android
You fire off an event to the server and it succeeds, now all you need to do is notify the UI to show a snackbar or perhaps perform navigation, but how are you going to do that? Sounds like you need to leverage a ui event strategy! Let's review some common options as well as their pros and cons. Want a more TL;DR version or just a project to follow along with? Check out this GitHub repo that covers the strategies we will be outlining below.
Strategy 1 - Callbacks #
The first option would be to expose callbacks to the ViewModel from your View. Then when you want to notify the view that an event has occurred, you call the callback function and the view performs the action desired. This is very easy to set up and ensures the desired behaviors are requested by the ViewModel. On the other hand, this tightly couples the View and ViewModel while also risking lost events if the callback is unregistered during a configuration change for example.
Link to the Relevant Example Code
Considerations
- Tight Coupling
- Requires Full Implementation
- No Guarantees of Call
- Need to Ensure Life-Cycle Aware
Strategy 2 - Mark on Send #
Another option is to mark the events as handled in the ViewModel as they are sent to the View. This is a slight improvement over the callback implementation because we can manage the event before it is sent ensuring we know the call was made from the ViewModel. We do not however know if the call was received and handled by the View, it could still be lost if a configuration change occurs.
Link to the Relevant Example Code
Considerations
- Loose Coupling
- No Guarantee of Consumption
- Flexibility in Determining Sent
- Verbose
Strategy 3 - Fire and Forget #
The next option is to just fire off the event and don't worry if it gets handled. This option is great if the event is just nice to have and could fail without adversely affecting your users (think animations perhaps). This is a popular technique and is often implemented as a Channel
in Kotlin when used. The biggest and most obvious drawback is that you have no guarantees that the event is seen and handled. The biggest upside is that this option is easily the lowest effort to implement.
Link to the Relevant Example Code
Considerations
- Loose Coupling
- No Guarantee of Consumption
- No Flexibility Determining Sent
- Low Effort to Implement
Strategy 4 - Mark on Consume #
Mark on consume is another popular option that builds on "Fire and Forget" and "Mark on Send" by exposing events from the ViewModel and requiring the View to notify the ViewModel that event has been handled. This ensures that the user's experience is as expected and the app doesn't end up in an unexpected state such as if navigation fails. The trade-off for this consistency is the additional overhead of marking the event as handled.
Link to the Relevant Example Code
Considerations
- Loose Coupling
- Guarantee of Consumption
- Flexibility Determining Sent
- Verbose
Strategy 5 - Events as State #
The last option is to model your events as a part of your state. If you are working in Compose UI then you are already familiar with exposing ViewModel state as a single entity to your View, so what if we extended that to include our ui events? This has some key advantages over other eventing models because it allows us to adapt to the state for different layouts. Imagine a list-detail flow where you choose an entity in a list and review it in detail in another view. We can model this as selectedEntity: Entity?
. On mobile we respond to changes in that value by clearing it and navigating, but on tablets we just show the selected entity in the details pane, no additional code required! This is great for a lot of cases but has the same major drawback of "Mark on Consume" in that there is often a lot of additional code and overhead as opposed to other strategies.
Link to the Relevant Example Code
Considerations
- Loose Coupling
- Highly Reusable
- Guarantee of Consumption
- Verbose
Recommendations #
The next obvious question is what should I use in my app. I'll give my recommendation based on what I have used and had success with. For the longest time I used "Fire and Forget" without issue, but I've since stopped using that option to save myself having to track down bugs in the future. So now I tend to use "Mark on Consume" and "Events as State". My go to is "Mark on Consume" because I find the code more readable and testable if the events are in a separate structure as well as it becomes easier to avoid unnecessary recompositions in Compose. I will reach for "Events as State" only if I need to handle different layouts or there are very few event types.
Conclusion #
Having a toolbox of options and strategies to solve common problems you encounter is critical when working in software. Now with tools to handle those pesky ui events, you can trigger snackbars, navigation, and one off animations with ease. Until next time, thanks!
Did you find this content helpful?
Please share this post and be sure to subscribe to the RSS feed to be notified of all future articles!
Want to go above and beyond? Help me out by sending me $1 on Ko-fi. It goes a long way in helping run this site and keeping it advertisement free. Thank you in advance!