This library is used with RxSwift to provide an abstraction on top of observables: actions.
An action is a way to say "hey, later I'll need you to subscribe to this thing." It's actually a lot more involved than that.
Actions accept a workFactory
: a closure that takes some input and produces an observable. When execute()
is called, it passes its parameter to this closure and subscribes to the work.
- Can only be executed while "enabled" (
true
if unspecified). - Only execute one thing at a time.
- Aggregates next/error events across individual executions.
Oh, and it has this really swift thing with UIButton
that's pretty cool. It'll manage the button's enabled state, make sure the button is disabled while your work is being done, all that stuff 👍
You have to pass a workFactory
that takes input and returns an Observable
. This represents some work that needs to be accomplished. Whenever you call execute()
, you pass in input that's fed to the work factory. The Action
will subscribe to the observable and emit the Next
events on its elements
property. If the observable errors, the error is sent as a Next
even on the errors
property. Neat.
Actions can only execute one thing at a time. If you try to execute an action that's currently executing, you'll get an error. The executing
property sends true
and false
values as Next
events.
action: Action<String, Bool> = Action(workFactory: { input in
return networkLibrary.checkEmailExists(input)
})
...
action.execute("ash@ashfurrow.com")
Notice that the first generic parameter is the type of the input, and the second is the type of observable that workFactory
creates. You can think of it a bit like the action's "output."
You can also specify an enabledIf
parameter to the Action
initializer.
let validEmailAddress = emailTextField.rx.text.map(isValidEmail)
action: Action<String, Bool> = Action(enabledIf: validEmailAddress, workFactory: { input in
return networkLibrary.checkEmailExists(input)
})
Now execute()
only does the work if the email address is valid. Super cool!
Note that enabledIf
isn't the same as the enabled
property. You pass in enabledIf
and the action uses that, and its current executing state, to determine if it's currently enabled.
What's really cool is the UIButton
extension. It accepts a CocoaAction
, which is just Action<Void, Void>
.
button.rx.action = action
Now when the button is pressed, the action is executed. The button's enabled
state is bound to the action's enabled
property. That means you can feed your form-validation logic into the action as a signal, and your button's enabled state is handled for you. Also, the user can't press the button again before the action is done executing, since it only handles one thing at a time. Cool. Check out this code example of CocoaAction in action.
If you'd like to use Action
to do a complex operation such as file download with download progress report (to update progress bar in the UI for example) you'd use Action<Void, Int>
instead of CocoaAction
. Out of the box CocoaAction
can't emit progress values, your own Action<Void, Int>
will do that. For details refer to this article.
If your scenario involves many buttons that needs to trigger the same Action
providing different input, you can use bindTo
on each UIButton
with a closure that returns correct input.
let button1 = UIButton()
let button2 = UIButton()
let action = Action<String,String> { input in
print(input)
return .just(input)
}
button1.rx.bindTo(action) {_ in return "Hello"}
button2.rx.bindTo(action) {_ in return "Goodbye"}
button1
and button2
are sharing the same Action
, but they are feeding it with different input (Hello
and Goodbye
that will be printed for corresponding tap).
A more complex use case can be a single action related to a UIViewController
that manages your navigation, error handling and loading state. With this approach, you can have as many UIButton
s (or UIBarButtonItem
s) as you want and subscribe to executing
, errors
and elements
once and in a single common place.
There's also a really cool extension on UIAlertAction
, used by UIAlertController
. One catch: because of the limitations of that class, you can't instantiate it with the normal initializer. Instead, call this class method:
let action = UIAlertAction.Action("Hi", style: .default)
Just add the line below to your Podfile:
pod 'Action'
Then run pod install
and that'll be 👌
Add this to Cartfile
github "RxSwiftCommunity/Action" ~> 2.3.0
If you are using RxSwift 3.2.0 or below, Use Action ~2.2.0
instead!
then run
$ carthage update
This library is (pretty obviously) inspired by ReactiveCocoa's Action
class. Those developers deserve a lot of thanks!
MIT obvs.