Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve naming of public API properties #126

Merged
merged 5 commits into from
Jan 8, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,18 @@ Current master

- Nothing yet!

4.0 (In development)
--------------------

- Cleanup public API [#120](https://github.com/RxSwiftCommunity/Action/issues/120)
- Remove unneccessary properties
- `workFactory`
- `_enabledIf`
- Rename properties to match Swift API design [guidelines](https://swift.org/documentation/api-design-guidelines/#strive-for-fluent-usage)
- `enabled` ~> `isEnabled`
- `executing` ~> `isExecuting`
- Deprecate renamed properties

3.5.0
-----

Expand Down
6 changes: 3 additions & 3 deletions Demo/ViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,8 @@ class ViewController: UIViewController {
// Demo: observe the output of both actions, spin an activity indicator
// while performing the work
Observable.combineLatest(
button.rx.action!.executing,
self.navigationItem.rightBarButtonItem!.rx.action!.executing) { a,b in
button.rx.action!.isExecuting,
self.navigationItem.rightBarButtonItem!.rx.action!.isExecuting) { a,b in
// we combine two boolean observable and output one boolean
return a || b
}
Expand All @@ -90,7 +90,7 @@ class ViewController: UIViewController {
}
self.navigationItem.leftBarButtonItem?.rx.bind(to: sharedAction, input: .barButton)

sharedAction.executing.debounce(0, scheduler: MainScheduler.instance).subscribe(onNext: { [weak self] executing in
sharedAction.isExecuting.debounce(0, scheduler: MainScheduler.instance).subscribe(onNext: { [weak self] executing in
if (executing) {
self?.activityIndicator.startAnimating()
}
Expand Down
14 changes: 7 additions & 7 deletions Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ Usage

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.
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 `isExecuting` property sends `true` and `false` values as `Next` events.

```swift
action: Action<String, Bool> = Action(workFactory: { input in
Expand All @@ -46,19 +46,19 @@ action: Action<String, Bool> = Action(enabledIf: validEmailAddress, workFactory:

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.
Note that `enabledIf` isn't the same as the `isEnabled` 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>`.

```swift
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](https://github.com/artsy/eidolon/blob/cb31168fa29dcc7815fd4a2e30e7c000bd1820ce/Kiosk/Bid%20Fulfillment/GenericFormValidationViewModel.swift).
Now when the button is pressed, the action is executed. The button's `isEnabled` state is bound to the action's `isEnabled` 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](https://github.com/artsy/eidolon/blob/cb31168fa29dcc7815fd4a2e30e7c000bd1820ce/Kiosk/Bid%20Fulfillment/GenericFormValidationViewModel.swift).

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](http://www.sm-cloud.com/rxswift-action/).

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.
If your scenario involves many buttons that needs to trigger the same `Action` providing different input, you can use `bind(to:)` on each `UIButton` with a closure that returns correct input.

```swift
let button1 = UIButton()
Expand All @@ -68,13 +68,13 @@ 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.rx.bind(to: action) {_ in return "Hello"}
button2.rx.bind(to: 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.
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 `isExecuting`, `errors` and `elements` once and in a single common place.

There's also a really cool extension on `UIAlertAction`, used by [`UIAlertController`](http://ashfurrow.com/blog/uialertviewcontroller-example/). One catch: because of the limitations of that class, you can't instantiate it with the normal initializer. Instead, call this class method:

Expand Down
31 changes: 19 additions & 12 deletions Sources/Action/Action.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,6 @@ When this excuted via execute() or inputs subject, it passes its parameter to th
public final class Action<Input, Element> {
public typealias WorkFactory = (Input) -> Observable<Element>

public let _enabledIf: Observable<Bool>
public let workFactory: WorkFactory

/// Inputs that triggers execution of action.
/// This subject also includes inputs as aguments of execute().
/// All inputs are always appear in this subject even if the action is not enabled.
Expand All @@ -40,7 +37,7 @@ public final class Action<Input, Element> {
public let elements: Observable<Element>

/// Whether or not we're currently executing.
public let executing: Observable<Bool>
public let isExecuting: Observable<Bool>

/// Observables returned by the workFactory.
/// Useful for sending results back from work being completed
Expand All @@ -50,7 +47,7 @@ public final class Action<Input, Element> {
/// Whether or not we're enabled. Note that this is a *computed* sequence
/// property based on enabledIf initializer and if we're currently executing.
/// Always observed on MainScheduler.
public let enabled: Observable<Bool>
public let isEnabled: Observable<Bool>

private let disposeBag = DisposeBag()

Expand All @@ -67,17 +64,14 @@ public final class Action<Input, Element> {
enabledIf: Observable<Bool> = Observable.just(true),
workFactory: @escaping WorkFactory) {

self._enabledIf = enabledIf
self.workFactory = workFactory

let enabledSubject = BehaviorSubject<Bool>(value: false)
enabled = enabledSubject.asObservable()
isEnabled = enabledSubject.asObservable()

let errorsSubject = PublishSubject<ActionError>()
errors = errorsSubject.asObservable()

executionObservables = inputs
.withLatestFrom(enabled) { input, enabled in (input, enabled) }
.withLatestFrom(isEnabled) { input, enabled in (input, enabled) }
.flatMap { input, enabled -> Observable<Observable<Element>> in
if enabled {
return Observable.of(workFactory(input)
Expand All @@ -93,7 +87,7 @@ public final class Action<Input, Element> {
elements = executionObservables
.flatMap { $0.catchError { _ in Observable.empty() } }

executing = executionObservables.flatMap {
isExecuting = executionObservables.flatMap {
execution -> Observable<Bool> in
let execution = execution
.flatMap { _ in Observable<Bool>.empty() }
Expand All @@ -107,7 +101,7 @@ public final class Action<Input, Element> {
.share(replay: 1, scope: .forever)

Observable
.combineLatest(executing, enabledIf) { !$0 && $1 }
.combineLatest(isExecuting, enabledIf) { !$0 && $1 }
.bind(to: enabledSubject)
.disposed(by: disposeBag)
}
Expand Down Expand Up @@ -135,3 +129,16 @@ public final class Action<Input, Element> {
return subject.asObservable()
}
}

// MARK: Deprecated
extension Action {
@available(*, deprecated, renamed: "isExecuting")
public var executing: Observable<Bool> {
return isExecuting
}

@available(*, deprecated, renamed: "isEnabled")
public var enabled: Observable<Bool> {
return isEnabled
}
}
2 changes: 1 addition & 1 deletion Sources/Action/CommonUI/Button+Action.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public extension Reactive where Base: Button {
// Set up new bindings, if applicable.
if let action = newValue {
action
.enabled
.isEnabled
.bind(to: self.isEnabled)
.disposed(by: self.base.actionDisposeBag)

Expand Down
2 changes: 1 addition & 1 deletion Sources/Action/CommonUI/Control+Action.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public extension Reactive where Base: Control {

// Bind the enabled state of the control to the enabled state of the action
action
.enabled
.isEnabled
.bind(to: self.isEnabled)
.disposed(by: self.base.actionDisposeBag)
}
Expand Down
6 changes: 3 additions & 3 deletions Sources/Action/UIKitExtensions/UIAlertAction+Action.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,14 @@ public extension Reactive where Base: UIAlertAction {
// Set up new bindings, if applicable.
if let action = newValue {
action
.enabled
.bind(to: self.enabled)
.isEnabled
.bind(to: self.isEnabled)
.disposed(by: self.base.actionDisposeBag)
}
}
}

public var enabled: AnyObserver<Bool> {
public var isEnabled: AnyObserver<Bool> {
return AnyObserver { [weak base] event in
MainScheduler.ensureExecutingOnScheduler()

Expand Down
4 changes: 2 additions & 2 deletions Sources/Action/UIKitExtensions/UIBarButtonItem+Action.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public extension Reactive where Base: UIBarButtonItem {
// Set up new bindings, if applicable.
if let action = newValue {
action
.enabled
.isEnabled
.bind(to: self.isEnabled)
.disposed(by: self.base.actionDisposeBag)

Expand All @@ -47,7 +47,7 @@ public extension Reactive where Base: UIBarButtonItem {
.disposed(by: self.base.actionDisposeBag)

action
.enabled
.isEnabled
.bind(to: self.isEnabled)
.disposed(by: self.base.actionDisposeBag)
}
Expand Down
8 changes: 4 additions & 4 deletions Tests/ActionTests/ActionTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -73,11 +73,11 @@ class ActionTests: QuickSpec {
.bind(to: errors)
.disposed(by: disposeBag)

action.enabled
action.isEnabled
.bind(to: enabled)
.disposed(by: disposeBag)

action.executing
action.isExecuting
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wrong indentation

.bind(to: executing)
.disposed(by: disposeBag)

Expand All @@ -89,8 +89,8 @@ class ActionTests: QuickSpec {
action.inputs.subscribe().disposed(by: disposeBag)
action.elements.subscribe().disposed(by: disposeBag)
action.errors.subscribe().disposed(by: disposeBag)
action.enabled.subscribe().disposed(by: disposeBag)
action.executing.subscribe().disposed(by: disposeBag)
action.isEnabled.subscribe().disposed(by: disposeBag)
action.isExecuting.subscribe().disposed(by: disposeBag)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wrong indentation

action.executionObservables.subscribe().disposed(by: disposeBag)
}

Expand Down