To Action or not to Action?

Hi there,

Chapter 20 of the RxSwift book advocates the use of Action, as something really useful and “Perfectly suited for MVVM” (the title of a section, not my words). But after toying a bit with them I’m about to drop its use and I would like to know if there’s something I may be missing.

From my point of view:

Action excels at taking charge of a button state, so it automatically disables the button while the associated action is in progress. But the only way to do this is to link the button to a CocoaAction (Action<Void, Void>). This means that the action has no input parameters, so it has to take its input values from elsewhere. This in turn means introducing state into the view model, as you are no longer combining observable streams to produce some output; you are taking some value from elsewhere to create a completable piece of work. Of course you may use a BehaviorSubject or a connectable observable to keep state in a reactive world, but I’ve found that this pollutes the view model and complicates programming it.

If we instead use an action with input parameters we transfer the responsibility for feeding those parameters to the view controller and we must combine some observables in the view controller from input fields (or even state exposed by the view model) to provide the input to the action, breaking the view controller <-> view model boundaries and responsibilities: You are no longer binding the view to the view model, but you are only binding certain combinations of behaviors to actions in the view model. In addition when you do this you lose the button related functionality, so Action becomes a glorified closure with some reactive related behavior.

Summing up, I’ve found that adding Actions to my application:

  • Introduces imperative programming concepts into reactive programming
  • Forces you to maintain state for all but non trivial view models, complicating the view model and the reasoning about it, or
  • Breaks the view controller <-> view model responsibility boundaries

What am I missing?

For the record, my non trivial view model is a video player view model, where you have several buttons to control playback, and the behavior of some of the buttons depend on the current state of the playback (for example, think of the usual combined play/pause button, where the button fullfils both functionalities, changing its icon depending on whether the video is currently playing or paused)

Thanks, best regards
José

@fpillet Can you please help with this when you get a chance? Thank you - much appreciated! :]

@jgongo I think that in your case, Action may not be the most appropriate. It’s OK to trigger something when a button is taped but not enough to aggregate state and disable the button when needed.

You could think about decoupling your mechanisms between the simple action that’s triggered when the button is pressed, and used (combined with another input sequence) as the input parameter for another action exposed in the ViewModel. I do this at times, it means that the simple action from the button is actually a trigger that delivers the latest value from another source using the withLatestFrom operator. It can then provide the input data for an Action<Type,Void>.

Alternatively, you may want to use a form of state machine to control this, associated to some state storage. Any form of Redux-like mechanism will do, and I can recommend using RxFeedback. It’s initially a lot more involved than Action (and this is the reason why I didn’t cover it in the book) but I ran a workshop where I demoed a mini A/V player state machine you could take some ideas from for your player.

Feel free to have a look and reuse code from GitHub - FrenchKit/RxSwiftClassroom: Intermediate and Advanced RxSwift, in particular the RxFeedback section.

Hope this helps,
Florent

This topic was automatically closed after 166 days. New replies are no longer allowed.