Releases: subconsciousnetwork/ObservableStore
v0.6.1
What's Changed
- Fix Fx memory leak through Publisher.flatMap by @gordonbrander in #45
Full Changelog: v0.6.0...v0.6.1
v0.6.0
v0.5.0
v0.4.0
What's Changed
- Update README by @gordonbrander in #23
- Introduce ViewStore, allowing scoped portions of state to be passed down to views @gordonbrander in #25
- Access ViewStore state dynamically via closure by @gordonbrander in #36
- Allow initializing first update from closure by @gordonbrander in #30
- Introduce Future extensions for bridging to async-await by @gordonbrander in #32
- minor grammar cleanup by @pmulard in #33
New Contributors
Full Changelog: v0.3.0...v0.4.0
v0.3.0
What's Changed
- Introduce
actions
publisher by @gordonbrander in #22 - Introduce KeyedCursorProtocol, remove ViewStore in favor of forward by @gordonbrander in #19
Full Changelog: 0.2.0...v0.3.0
Breaking: removed ViewStore
in favor of forward
ViewStore has been removed in favor of a new child component patter that is closer to vanilla SwiftUI.
The key problem with the ViewStore approach was that the getter for the store value dynamically got the value, meaning that for stores of list items, the state of the store always had to be an Optional. SwiftUI avoids this problem by passing down bare properties to list items. See #19 for more details.
Child components are now handled the way you handle child components in vanilla SwiftUI, with bare state and a send
method.
struct ChildView: View {
var state: ChildModel
var send: (ChildAction) -> Void
var body: some View {
VStack {
Text("Count \(state.count)")
Button(
"Increment",
action: {
store.send(ChildAction.increment)
}
)
}
}
}
To integrate the child view, we can use Address.forward
to create a tagged send function for the child that will translate all child actions into parent actions.
struct ContentView: View {
@StateObject private var store = Store(
state: AppModel(),
environment: AppEnvironment()
)
var body: some View {
ChildView(
state: store.state.child,
send: Address.forward(
send: store.send,
tag: AppChildCursor.tag
)
)
}
}
Breaking: Binding signature change
Bindings have changed from Binding(store:get:tag)
to Binding(get:send:tag:)
. Typical use looks like this:
TextView(
"Placeholder",
Binding(
get: { store.state.text },
send: store.send,
tag: Action.setText
)
)
The old signature relied on ViewStore's conformance to StoreProtocol
. The new signature just requires a getter closure and a send function, so is less opinionated about the specifics of this library.
Actions publisher
Store
now exposes Store.actions
, a publisher which publishes all actions sent to send
. This is useful for logging actions. It is also useful for integrating ObservableStore
with other SwiftUI features, or for wiring together stores in codebases that use multiple stores.
Now both state and actions are published (state already has a publisher through $state
), so it is easy to wired up store events to other SwiftUI features using onReceive
.
Logging example:
.onReceive(store.actions) { action in
// do logging
}
0.2.0
Version 0.2.0 is an important update to ObservableStore. It introduces the ability to create scoped stores for sub-components, called ViewStores
. ViewStores
are conceptually like bindings to stores, except that they expose the store API, instead of a binding API. This approach is inspired by the component mapping pattern from Elm.
Changes
- Introduce
StoreProtocol
, a protocol that describes a kind of store. - Introduce
ViewStore<Model: ModelProtocol>
, which implementsStoreProtocol
- Implement
StoreProtocol
forStore
- Introduce
CursorProtocol
, which describes how to map from one domain to another, and provides a convenience function for updating child components. - Introduce
ModelProtocol
, which implements anupdate(state:action:environment)
static function.ModelProtocol
allows us to treat action and environment as associatedtypes, which simplifies many of our other type signatures.
- Synthesize
update(state:actions:environment)
method for any type that implementsModelProtocol
. This allows you to dispatch multiple actions immediately in sequence, effectively composing multiple actions.- This helper replaces
pipe
- Useful when dispatching actions down to multiple child sub-components
- This helper replaces
- Add new tests
Breaking changes
- Store requires that state implement
ModelProtocol
. This allows us to simplify the signatures of many other APIs- Update type signature changes from
Update<State, Action>
toUpdate<Model: ModelProtocol>
. - Store type signature changes from
Store<State, Action, Environment>
toStore<Model: ModelProtocol>
- Store initializer changes from
Store.init(update:state:environment:)
toStore.init(state:environment:)
. Now that state conforms toModelProtocol
, you don't have to explicitly pass in the update function as a closure. We can just call the protocol implementation.
- Update type signature changes from
store.binding
has been removed in favor ofBinding(store:get:send:)
- Remove Update.pipe. Redundant now. Was never happy with it anyway. It was an inelegant way to accomplish the same thing as
update(state:actions:environment:)
. - Join fx on main with a
.default
QoS. We have reduced the QoS from.userInteractive
to avoid spamming that QoS with actions. This should not affect ordinary use of ObservableStore. fx are async/never block user interaction anyway, so a default QoS should be fine.
0.1.0
Introduces new features, bugfixes, and cleans up geneneric type arguments, function signatures, and argument labels.
Note for upgrading from 0.0.7 to 0.1.0: 0.1.0 includes breaking changes to APIs. Because this version is pre-1.0, and we are still working out APIs, these breaking changes are expressed as a minor revision (0.1.0) instead of a major revision.
Release notes:
- Changed Store generic signature to
Store<State, Action, Environment>
. See #14 - Changed update function signature to
update(State, Action, Environment)
. See #14 - Removed argument label from send so it is just
Store.send(_)
. See #10 - Changed argument label for subscribe to
Store.subscribe(to:)
. See #10 - Introduced
.binding(get:tag:)
so that constructing a binding without specifying an animation will leave transaction animation untouched. See #12 - Added
Update.mergeFx(_)
which can be used to merge additional fx into an Update. Useful when composing update functions withUpdate.pipe
. See #9 - Removed
Update.transaction
method. See #9