FluxorOrg/Fluxor

View on GitHub
README.md

Summary

Maintainability
Test Coverage
<p align="center">
    <br />
    <img src="https://raw.githubusercontent.com/FluxorOrg/Fluxor/master/Assets/Fluxor-logo-light.png#gh-light-mode-only" width="400" max-width="90%" alt="Fluxor" />
    <img src="https://raw.githubusercontent.com/FluxorOrg/Fluxor/master/Assets/Fluxor-logo-dark.png#gh-dark-mode-only" id="dark-logo" width="400" max-width="90%" alt="Fluxor" />
</p>

<p align="center">
    <b>Unidirectional Data Flow in Swift - inspired by <a href="https://redux.js.org">Redux</a> and <a href="https://ngrx.io">NgRx</a>.</b><br />
    Based on <a href="https://developer.apple.com/documentation/combine">Combine</a> - ideal for use with <a href="https://developer.apple.com/documentation/swiftui">SwiftUI</a>.<br />
    <br />
    <a href="https://swiftpackageindex.com/FluxorOrg/Fluxor">
        <img src="https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2FFluxorOrg%2FFluxor%2Fbadge%3Ftype%3Dswift-versions" alt="Swift version" />
    </a>
    <a href="https://swiftpackageindex.com/FluxorOrg/Fluxor">
        <img src="https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2FFluxorOrg%2FFluxor%2Fbadge%3Ftype%3Dplatforms" alt="Platforms" />
    </a>
    <br />
    <img src="https://github.com/FluxorOrg/Fluxor/workflows/CI/badge.svg" alt="CI" />
    <a href="https://fluxor.dev">
        <img src="https://raw.githubusercontent.com/FluxorOrg/Fluxor/gh-pages/badge.svg" alt="Documentation" />
    </a>
    <a href="https://codeclimate.com/github/FluxorOrg/Fluxor/maintainability">
        <img src="https://api.codeclimate.com/v1/badges/f2ea66abc81e4a578a31/maintainability" alt="Maintainability" />
    </a>
    <a href="https://codeclimate.com/github/FluxorOrg/Fluxor/test_coverage">
        <img src="https://api.codeclimate.com/v1/badges/f2ea66abc81e4a578a31/test_coverage" alt="Test Coverage" />
    </a>
    <a href="https://twitter.com/mortengregersen">
        <img src="https://img.shields.io/badge/twitter-@mortengregersen-blue.svg?style=flat" alt="Twitter" />
    </a>
</p>

## 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.

![](https://raw.githubusercontent.com/FluxorOrg/Fluxor/master/Assets/Diagram.png)

## Installation

Fluxor can be installed as a dependency to your project using [Swift Package Manager](https://swift.org/package-manager), by simply adding `https://github.com/FluxorOrg/Fluxor.git`.

### Requirements

- iOS 13.0+ / macOS 10.15+ / tvOS 13.0+ / watchOS 6.0+ / Linux
- Xcode 11.4+ / Swift 5.2+

## Usage
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).

```swift
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 `Action`s 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.

```swift
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 `Action`s 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 `Action`s and state changes to the log.
* `TestInterceptor` to help assert that specific `Action`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.

* [More info on how to use it with SwiftUI](https://fluxor.dev/Using%20Fluxor%20with%20SwiftUI.html)
* [More info on how to test apps using Fluxor](https://fluxor.dev/Test%20Support.html)

## Debugging with FluxorExplorer
Fluxor has a companion app, [**FluxorExplorer**](https://github.com/FluxorOrg/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.

<a href="https://apps.apple.com/us/app/fluxorexplorer/id1515805273?mt=8">
    <img src="https://linkmaker.itunes.apple.com/en-us/badge-lrg.svg?releaseDate=2020-06-08&kind=iossoftware&bubble=ios_apps" style="width: 135px; height: 40px" alt="Download on the App Store" />
</a>

To learn more about how to use FluxorExplorer, [go to the repository for the app](https://github.com/FluxorOrg/FluxorExplorer).

![](https://raw.githubusercontent.com/FluxorOrg/Fluxor/master/Assets/FluxorExplorer.png)


## Apps using Fluxor

### Real world apps

* [FluxorExplorer](https://github.com/FluxorOrg/FluxorExplorer)

### Sample apps

* [FluxorSampleToDo](https://github.com/FluxorOrg/FluxorSampleToDo)