ViewModel: One-off occasion antipatterns | by Manuel Vivo | Android Builders | Jun, 2022


ViewModel occasions are actions originated from the ViewModel that the UI ought to carry out. For instance, displaying an informative message to the person, or navigating to a distinct display when the appliance state modifications.

Our steering on ViewModel occasions is opinionated in two alternative ways:

  1. At any time when a one-off occasion originates within the ViewModel, the ViewModel ought to deal with that occasion instantly inflicting a state replace. The ViewModel ought to solely expose utility state. Exposing occasions that haven’t been decreased to state from a ViewModel means the ViewModel isn’t the supply of reality for the state derived from these occasions; Unidirectional Information Circulation (UDF) describes some great benefits of sending occasions solely to shoppers that outlive their producers.
  2. State needs to be uncovered utilizing an observable knowledge holder sort.
Following UDF, state flows down from the ViewModel to the UI, and occasions go up from the UI to the ViewModel

In your app, you is perhaps exposing ViewModel occasions to the UI utilizing Kotlin Channels or different reactive streams similar to SharedFlow, or possibly it is a sample you’ve seen in different tasks. When the producer (the ViewModel) outlives the patron (UI—Compose or Views), which might be the case with ViewModel occasions, these APIs don’t assure the supply and processing of these occasions. This may end up in bugs and future issues for the developer, and it’s additionally an unacceptable person expertise for many apps.

It is best to deal with ViewModel occasions instantly, inflicting a UI state replace. Attempting to show occasions as an object utilizing different reactive options similar to Channel or SharedFlow doesn’t assure the supply and processing of the occasions.

Right here’s an instance of the implementation of a ViewModel in an app’s typical funds circulate. Within the following code snippets, the MakePaymentViewModel instantly tells the UI to navigate to the cost end result display when the results of the cost request comes again. We’ll use this instance to discover why dealing with one-off ViewModel occasions like this brings issues and better engineering prices.

The UI would then devour this occasion and navigate accordingly:

The navigateToPaymentResultScreen implementation seen above has a number of design flaws.

A Channel doesn’t assure the supply and processing of the occasions. Due to this fact, occasions might be misplaced, leaving the UI in an inconsistent state. An instance of this might occur when the UI (shopper) goes to the background and stops the Channel assortment simply after the ViewModel (producer) sends an occasion. The identical might be mentioned for different APIs that aren’t an observable knowledge holder sort similar to SharedFlow, which may emit occasions even when there aren’t any shoppers listening to them.

That is an antipattern as a result of the cost end result state modeled within the UI layer isn’t sturdy or atomic if we give it some thought by way of an ACID transaction. The cost could have succeeded so far as the repository is anxious, however we by no means moved to the correct subsequent display.

Word: This antipattern may very well be mitigated by utilizing Dispatchers.Principal.quick when sending and receiving occasions. Nonetheless, if that’s not enforced by a lint test, this resolution may very well be error-prone as devs may simply neglect it.

For an app that helps a number of display sizes, the UI motion to carry out given a ViewModel occasion is perhaps totally different relying on the display dimension. For instance, the case research app ought to navigate to the cost end result display when working on a cell phone; but when the app is working on a pill, the motion may present the lead to a distinct a part of the identical display.

The ViewModel ought to inform the UI what the app state is and the UI ought to decide how to replicate that. The ViewModel shouldn’t inform the UI which actions it ought to take.

Modeling the occasion as one thing that’s fireplace and neglect — in flight — is what results in issues. It’s more durable to adjust to ACID properties, so the best doable knowledge reliability and integrity can’t be ensured. State is, occasions occur. The longer an occasion isn’t dealt with, the more durable the issue turns into. For ViewModel occasions, course of the occasion as quickly as doable and generate a brand new UI state from it.

Within the case research, we created an object for the occasion — represented as a Boolean — and uncovered it utilizing a Channel:

// Create Channel with the occasion modeled as a Boolean
val _navigateToPaymentResultScreen = Channel<Boolean>()// Set off occasion

When you do that, you’ve taken accountability for making certain issues like exactly-once supply and dealing with. In case you should mannequin an occasion as an object for some purpose, restrict its lifespan to be as brief as doable in order that it doesn’t have an opportunity to get misplaced.

Dealing with a one-off occasion within the ViewModel normally comes all the way down to a way name — for instance, updating the UI state. When you name that technique, you recognize whether or not it accomplished efficiently or threw an exception, and you recognize that it occurred precisely as soon as.

In case you’re in one among these conditions, rethink what that one-off ViewModel occasion truly means to your UI. Deal with them instantly and cut back them to UI state which is uncovered utilizing an observable knowledge holder similar to StateFlow or mutableStateOf.

UI state higher represents the UI at a given cut-off date, it provides you extra supply and processing ensures, it’s normally simpler to check, and it integrates constantly with the remainder of your app.

In case you wrestle to discover a technique to cut back one-off ViewModel occasions to state, rethink what that occasion truly means to your UI.

Within the instance above, the ViewModel ought to expose what’s the precise utility knowledge — the cost knowledge on this case — as an alternative of telling the UI the motion to take. The next is a greater illustration of that ViewModel occasion dealt with and decreased to state, and uncovered utilizing an observable knowledge holder sort.

Within the code above, the occasion is dealt with instantly by calling _uiState.replace (#L27) with the brand new paymentResult knowledge (#L30); there’s no manner for this occasion to get misplaced now. The occasion has been decreased to state, and the paymentResult discipline in MakePaymentUiState displays the cost end result utility knowledge.

With this, the UI would react to paymentResult modifications and act accordingly.

Word: If in your use case the Exercise doesn’t end() and is stored within the backstack, your ViewModel would want to show a perform to clear the paymentResult from the UiState (i.e. setting the sector to null) that will likely be known as after the Exercise begins the opposite one. An instance of this may be discovered within the Consuming occasions can set off state updates part of the docs.

As talked about within the UI layer’s extra concerns part, you possibly can expose the UI state of your display with a number of streams if that’s what your use case requires. What’s essential is that these streams are observable knowledge holder sorts. Within the instance above, a singular UI state stream is uncovered as a result of the isLoading flag and the paymentResult property are extremely intertwined. Separating them out may trigger inconsistencies within the UI—for instance, if isLoading is true and paymentResult isn’t null. By having them collectively in the identical UiState class, we’re extra aware of the totally different fields that make the UI state of the display which lead us to fewer bugs.



Leave a Comment