Skip to content

Commit cc362a3

Browse files
Simpler bindings (#81)
* Remove the ValueBinding type and use the built in Binding type * Add comments * Make `current` on `ObservableValue` `Published` * Lint fix * Lint fix * Update documentation * Update docs script
1 parent a2c2428 commit cc362a3

File tree

11 files changed

+236
-314
lines changed

11 files changed

+236
-314
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@
33
/Packages
44
/*.xcodeproj
55
xcuserdata/
6+
docs

Documentation/Abstracts/Test Support.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ The `MockStore` can be used to override `Selector`s so that they always return a
3535
import FluxorTestSupport
3636
import XCTest
3737

38-
class GreetingView: XCTestCase {
38+
class GreetingViewTests: XCTestCase {
3939
func testGreeting() {
4040
let greeting = "Hi Bob!"
4141
let mockStore = MockStore(initialState: AppState(greeting: "Hi Steve!"))
@@ -48,13 +48,15 @@ class GreetingView: XCTestCase {
4848

4949
## Intercepting state changes
5050

51+
**NOTE:** This is built into the `MockStore`.
52+
5153
The `TestInterceptor` can be registered on the `Store`. When registered it gets all `Action`s dispatched and state changes. Everything it intercepts gets saved in an array in the order received. This can be used to assert which `Action`s are dispatched in a test.
5254

5355
```swift
5456
import FluxorTestSupport
5557
import XCTest
5658

57-
class GreetingView: XCTestCase {
59+
class GreetingViewTests: XCTestCase {
5860
func testGreeting() {
5961
let testInterceptor = TestInterceptor<AppState>()
6062
let store = Store(initialState: AppState())

Documentation/Abstracts/Using Fluxor with SwiftUI.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,24 +20,24 @@ struct DrawView: View {
2020

2121
## Binding a value which can be changed
2222

23-
The `ValueBinding` can be used to create a [`Binding`](https://developer.apple.com/documentation/swiftui/binding) to a value in the `State` and use an `ActionTemplate` to update the value through the `Store`. The `binding` property on `ValueBinding` will create a [`Binding`](https://developer.apple.com/documentation/swiftui/binding) which can be used like any other bindings. The value can also manually be updated using the `update` function. This will dispatch an `Action` on the `Store` based on the specified `ActionTemplate`.
23+
The `Store` is extended with functions to create [`Bindings`](https://developer.apple.com/documentation/swiftui/binding) to a value in the `State` and use an `ActionTemplate` to update the value through the `Store`. The [`Binding`](https://developer.apple.com/documentation/swiftui/binding) can be used like any other bindings. When the value in the [`Binding`](https://developer.apple.com/documentation/swiftui/binding) is changed an `Action` will be dispatched on the `Store` based on the specified `ActionTemplate`.
2424

2525
```swift
2626
import FluxorSwiftUI
2727
import SwiftUI
2828

2929
struct GreetingView: View {
30-
var greeting = Current.store.binding(get: Selectors.getGreeting, send: Actions.setGreeting)
30+
var greeting: Binding<String> = Current.store.binding(get: Selectors.getGreeting, send: Actions.setGreeting)
3131

3232
var body: some View {
33-
TextField("Greeting", text: greeting.binding)
33+
TextField("Greeting", text: greeting)
3434
}
3535
}
3636
```
3737

3838
## Binding a value which can be enabled and disabled
3939

40-
When the `ValueBinding` has a `Bool` value, it can be created with a `ActionTemplate` for enabling the value (making it `true`) and another one for disabling the value (making it `false`). When the value is `Bool` and the payload for the `ActionTemplate` is either `Void` or `Bool`, the `ValueBinding` gains more functions beside the `update` function to `toggle`, `enable` and `disable` the value.
40+
When the `Binding` has a `Bool` value, it can be created with a `ActionTemplate` for enabling the value (making it `true`) and another one for disabling the value (making it `false`). When the value is `Bool` and the payload for the `ActionTemplate` is either `Void` or `Bool`, the `ValueBinding` gains more functions beside the `update` function to `toggle`, `enable` and `disable` the value.
4141

4242
```swift
4343
import FluxorSwiftUI
@@ -49,8 +49,8 @@ struct DrawView: View {
4949
disable: Actions.cancelClear)
5050

5151
var body: some View {
52-
Button(action: { self.showClearActionSheet.enable() }, label: { Text("Clear") })
53-
.actionSheet(isPresented: showClearActionSheet.binding) { clearActionSheet }
52+
Button(action: { Current.store.dispatch(action: Actions.askToClear() }, label: { Text("Clear") })
53+
.actionSheet(isPresented: showClearActionSheet) { clearActionSheet }
5454
}
5555

5656
private var clearActionSheet: ActionSheet {

Documentation/generate_docs.sh

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
rm -rf docs
2-
sourcekitten doc --spm --module-name Fluxor > mod1.json
3-
sourcekitten doc --spm --module-name FluxorTestSupport > mod2.json
4-
sourcekitten doc --spm --module-name FluxorSwiftUI > mod3.json
5-
jazzy --sourcekitten-sourcefile mod1.json,mod2.json,mod3.json
2+
mkdir docs
3+
sourcekitten doc --spm --module-name Fluxor > docs/mod1.json
4+
sourcekitten doc --spm --module-name FluxorTestSupport > docs/mod2.json
5+
sourcekitten doc --spm --module-name FluxorSwiftUI > docs/mod3.json
6+
jazzy --sourcekitten-sourcefile docs/mod1.json,docs/mod2.json,docs/mod3.json
67
open docs/index.html

Package.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,6 @@ let package = Package(
4040
dependencies: ["Fluxor"]),
4141
.testTarget(
4242
name: "FluxorSwiftUITests",
43-
dependencies: ["FluxorSwiftUI"]),
43+
dependencies: ["FluxorSwiftUI", "FluxorTestSupport"]),
4444
]
4545
)

Sources/FluxorSwiftUI/ObservableValue.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ public extension Store {
2525
*/
2626
public class ObservableValue<Value>: ObservableObject {
2727
/// The current value. This will change everytime the `State` in the `Store` changes
28-
public private(set) var current: Value { willSet { objectWillChange.send() } }
28+
@Published public private(set) var current: Value
2929
private var cancellable: AnyCancellable!
3030

3131
/**
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/*
2+
* FluxorSwiftUI
3+
* Copyright (c) Morten Bjerg Gregersen 2020
4+
* MIT license, see LICENSE file for details
5+
*/
6+
7+
import Fluxor
8+
import SwiftUI
9+
10+
public extension Store {
11+
/**
12+
Creates a `ValueBinding` from the given `Selector` and `ActionTemplate`.
13+
14+
When the value is updated an `Action`, created from the `ActionTemplate`, is dispatched on the `Store`.
15+
16+
- Parameter selector: The `Selector`s to use for getting the current value
17+
- Parameter actionTemplate: The `ActionTemplate` to use for dispatching an `Action` when the value changes
18+
- Returns: A `Binding` based on the given `Selector` and `ActionTemplate`
19+
*/
20+
func binding<Value>(get selector: Fluxor.Selector<State, Value>,
21+
send actionTemplate: ActionTemplate<Value>) -> Binding<Value> {
22+
.init(get: { self.selectCurrent(selector) },
23+
set: { self.dispatch(action: actionTemplate.createAction(payload: $0)) })
24+
}
25+
26+
/**
27+
Creates a `Binding` from the given `Selector` and `ActionTemplate`s for enabling and disabling the value.
28+
29+
When the value is enabled/disabled, an `Action`, created from one of the `ActionTemplate`s,
30+
is dispatched on the `Store`.
31+
32+
- Parameter selector: The `Selector`s to use for getting the current value
33+
- Parameter enableActionTemplate: The `ActionTemplate` to use for dispatching an `Action`
34+
when the value should be enabled
35+
- Parameter disableActionTemplate: The `ActionTemplate` to use for dispatching an `Action`
36+
when the value should be disabled
37+
- Returns: A `Binding` based on the given `Selector` and `ActionTemplate`s
38+
*/
39+
func binding(get selector: Fluxor.Selector<State, Bool>,
40+
enable enableActionTemplate: ActionTemplate<Void>,
41+
disable disableActionTemplate: ActionTemplate<Void>)
42+
-> Binding<Bool> {
43+
return .init(get: { self.selectCurrent(selector) },
44+
set: { self.dispatch(action: ($0 ? enableActionTemplate : disableActionTemplate)()) })
45+
}
46+
47+
/**
48+
Creates a `Binding` from the given `Selector` and `ActionTemplate`.
49+
50+
When the value is updated an `Action` (returned by the closure), is dispatched on the `Store`.
51+
52+
- Parameter selector: The `Selector`s to use for getting the current value
53+
- Parameter action: A closure used to decide which `ActionTemplate` to use
54+
for dispatching an `Action` when the value changes
55+
- Parameter value: The value used to decide which `ActionTemplate` to use for the update.
56+
This can either be the current value or the one used for the update
57+
- Returns: A `Binding` based on the given `Selector` and `ActionTemplate`
58+
*/
59+
func binding<Value>(get selector: Fluxor.Selector<State, Value>,
60+
send action: @escaping (_ value: Value) -> Action)
61+
-> Binding<Value> {
62+
return .init(get: { self.selectCurrent(selector) },
63+
set: { self.dispatch(action: action($0)) })
64+
}
65+
}

Sources/FluxorSwiftUI/ValueBinding.swift

Lines changed: 0 additions & 190 deletions
This file was deleted.

Tests/FluxorSwiftUITests/ObservableValueTests.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,17 @@ class ObservableValueTests: XCTestCase {
2020
])
2121

2222
func testBindingWithOneActionTemplate() {
23+
// Given
24+
let expectation = XCTestExpectation(description: debugDescription)
2325
let observableValue = store.observe(counterSelector)
26+
let cancellable = observableValue.objectWillChange.sink { expectation.fulfill() }
2427
XCTAssertEqual(observableValue.current, 42)
28+
// When
2529
store.dispatch(action: increment(payload: 1))
30+
// Then
2631
XCTAssertEqual(observableValue.current, 43)
32+
wait(for: [expectation], timeout: 1)
33+
XCTAssertNotNil(cancellable)
2734
}
2835
}
2936

0 commit comments

Comments
 (0)