Unidirectional Data Flow in Swift - inspired by Redux and NgRx.
Based on Combine - ideal for use with SwiftUI.
Why do I need Fluxor?
When developing apps, it can quickly become difficult to keep track of the flow of data. Data flows in multiple directions and can easily become inconsistent with Multiple Sources of Truth.
With Fluxor, data flows in only one direction, there is only one Single Source of Truth, updates to the state are done with pure functions, the flow in the app can easily be followed, and all the individual parts can be unit tested separately.
How does it work?
Fluxor is made up from the following types:
Store contains an immutable state (the Single Source of Truth).
Actions are dispatched on the Store to update the state.
Reducers gives the Store a new state based on the Actions dispatched.
Selectors selects (and eventually transform) part(s) of the state to use (eg. in views).
Effects gets triggered by Actions, and can perform async task which in turn can dispatch new Actions.
Interceptors intercepts every dispatched Action and state change for easier debugging.
Installation
Fluxor can be installed as a dependency to your project using Swift Package Manager, by simply adding https://github.com/FluxorOrg/Fluxor.git.
As a minimum, an app using Fluxor will need a Store, an Action, a Reducer, a Selector and a state.
Here is a setup where firing the IncrementAction (1) will increment the counter (2) in AppState (3), and when selecting with the counterSelector (4) on the Store will publish the counter everytime the state changes (5).
import Combine
import Fluxor
import Foundation
// 3
struct AppState {
var counter: Int
}
// 1
struct IncrementAction: Action {
let increment: Int
}
// 4
let counterSelector = Selector(keyPath: \AppState.counter)
let store = Store(initialState: AppState(counter: 0))
store.register(reducer: Reducer(
ReduceOn(IncrementAction.self) { state, action in
state.counter += action.increment // 2
}
))
let cancellable = store.select(counterSelector).sink {
print("Current count: \($0)") // 5
}
store.dispatch(action: IncrementAction(increment: 42))
// Will print out "Current count: 42"
Side Effects
The above example is a simple use case, where an Action is dispatched and the state is updated by a Reducer. In cases where something should happen when an Action is dispatched (eg. fetching data from the internet or some system service), Fluxor provides Effects.
Effects are registered in the Store and will receive all Actions dispatched. An Effect will in most cases be a Publisher mapped from the dispatched Action - the mapped Action will be dispatched on the Store.
Alternatively an Effect can also be a Cancellable when it don’t need to have an Action dispatched.
import Combine
import Fluxor
import Foundation
class TodosEffects: Effects {
typealias Environment = AppEnvironment
let fetchTodos = Effect<Environment>.dispatchingOne { actions, environment in
actions.ofType(FetchTodosAction.self)
.flatMap { _ in
environment.todoService.fetchTodos()
.map { DidFetchTodosAction(todos: $0) }
.catch { _ in Just(DidFailFetchingTodosAction(error: "An error occurred.")) }
}
.eraseToAnyPublisher()
}
}
Intercepting actions and changes
If read-only access to all Actions dispatched and state changes is needed, an Interceptor can be used. Interceptor is just a protocol, and when registered in the Store, instances of types conforming to this protocol will receive a callback everytime an Action is dispatched.
Fluxor comes with two implementations of Interceptor:
PrintInterceptor for printing Actions and state changes to the log.
TestInterceptor to help assert that specific Actions was dispatched in unit tests.
Packages for using it with SwiftUI and testing
Fluxor comes with packages, to make it easier to use it with SwiftUI and for testing apps using Fluxor.
Fluxor has a companion app, FluxorExplorer, which helps when debugging apps using Fluxor. FluxorExplorer lets you look through the dispatched Actions and state changes, to debug the data flow of the app.
FluxorExplorer is available on the App Store but also available as open source.
Unidirectional Data Flow in Swift - inspired by Redux and NgRx.
Based on Combine - ideal for use with SwiftUI.
Why do I need Fluxor?
When developing apps, it can quickly become difficult to keep track of the flow of data. Data flows in multiple directions and can easily become inconsistent with Multiple Sources of Truth.
With Fluxor, data flows in only one direction, there is only one Single Source of Truth, updates to the state are done with pure functions, the flow in the app can easily be followed, and all the individual parts can be unit tested separately.
How does it work?
Fluxor is made up from the following types:
Store
contains an immutable state (the Single Source of Truth).Action
s are dispatched on the Store to update the state.Reducer
s gives the Store a new state based on the Actions dispatched.Selector
s selects (and eventually transform) part(s) of the state to use (eg. in views).Effect
s gets triggered by Actions, and can perform async task which in turn can dispatch new Actions.Interceptor
s intercepts every dispatched Action and state change for easier debugging.Installation
Fluxor can be installed as a dependency to your project using Swift Package Manager, by simply adding
https://github.com/FluxorOrg/Fluxor.git
.Requirements
Usage
As a minimum, an app using Fluxor will need a
Store
, anAction
, aReducer
, aSelector
and a state.Here is a setup where firing the
IncrementAction
(1) will increment thecounter
(2) inAppState
(3), and when selecting with thecounterSelector
(4) on theStore
will publish thecounter
everytime the state changes (5).Side Effects
The above example is a simple use case, where an
Action
is dispatched and the state is updated by aReducer
. In cases where something should happen when anAction
is dispatched (eg. fetching data from the internet or some system service), Fluxor providesEffects
.Effects
are registered in theStore
and will receive allAction
s dispatched. AnEffect
will in most cases be aPublisher
mapped from the dispatchedAction
- the mappedAction
will be dispatched on theStore
.Alternatively an
Effect
can also be aCancellable
when it don’t need to have anAction
dispatched.Intercepting actions and changes
If read-only access to all
Action
s dispatched and state changes is needed, anInterceptor
can be used.Interceptor
is just a protocol, and when registered in theStore
, instances of types conforming to this protocol will receive a callback everytime anAction
is dispatched.Fluxor comes with two implementations of
Interceptor
:PrintInterceptor
for printingAction
s and state changes to the log.TestInterceptor
to help assert that specificAction
s was dispatched in unit tests.Packages for using it with SwiftUI and testing
Fluxor comes with packages, to make it easier to use it with SwiftUI and for testing apps using Fluxor.
Debugging with FluxorExplorer
Fluxor has a companion app, FluxorExplorer, which helps when debugging apps using Fluxor. FluxorExplorer lets you look through the dispatched
Action
s and state changes, to debug the data flow of the app.FluxorExplorer is available on the App Store but also available as open source.
To learn more about how to use FluxorExplorer, go to the repository for the app.
Apps using Fluxor
Real world apps
Sample apps