Flux for SwiftUI, inspired by Vuex

Overview

Fluxus

⚠️ Fluxus is no longer maintained, and may not be using latest SwiftUI best practices.

👉 I encourage you to look at the source of Fluxus. If you do, you'll realize this is simply a pattern more than a framework, so please study and you can roll your own Vuex-style SwiftUI store.


Fluxus is an implementation of the Flux pattern for SwiftUI that replaces MVC, MVVM, Viper, etc.

  • Organize all your model data into a store and easily access in your views.
  • Use mutations to modify your app's state.
  • Use actions to perform asynchronous operations.
  • Keep your models and views as simple as possible.

Requirements

Xcode 11 beta on MacOS 10.14 or 10.15

Installation

In Xcode, choose File -> Swift Packages -> Add Package Dependency and enter this repo's URL.

Concepts

  • State is the root source of truth for your app
  • Mutations describe a synchronous change in state
  • Committers apply mutations to the state
  • Actions describe an asynchronous operation
  • Dispatchers execute asynchronous actions and commit mutations when complete

Obligatory Flux Diagram

When should I use it?

Fluxus helps us deal with shared state management at the cost of more concepts and boilerplate. If you're not building a complex app, and jump right into Fluxus, it may feel verbose and unnecessary. If your app is simple, you probably don't need it. But once your app grows to a certain complexity, you'll start looking for ways to organize shared state, and Fluxus is here to help with that. To quote Dan Abramov, author of Redux:

Flux libraries are like glasses: you’ll know when you need them.

Using Fluxus doesn't mean you should put all your state in Fluxus. If a piece of state strictly belongs to a single View, it might be fine to just use local @State. Check out the landmarks example to see how local @State and Fluxus state can work together.

Example apps

Articles

Usage

Create state

State is the root source of truth for the model data in your app. We create one state module, for a counter, and add it to the root state struct.

import Fluxus

struct CounterState: FluxState {
  var count = 0

  var myBoolValue = false

  var countIsEven: Bool {
    get {
      return count % 2 == 0
    }
  }

  func countIsDivisibleBy(_ by: Int) -> Bool {
    return count % by == 0
  }
}

struct RootState {
  var counter = CounterState()
}

Create mutations/committers

Mutations describe a change in state. Committers receive mutations and modify the state.

import Fluxus

enum CounterMutation: Mutation {
  case Increment
  case AddAmount(Int)
  case SetMyBool(Bool)
}

struct CounterCommitter: Committer {
  func commit(state: CounterState, mutation: CounterMutation) -> CounterState {
    var state = state

    switch mutation {
    case .Increment:
      state.count += 1
    case .AddAmount(let amount):
      state.count += amount
    case .SetMyBool(let value):
      state.myBoolValue = value
    }

    return state
  }
}

Create actions/dispatchers

Actions describe an asynchronous operation. Dispatchers receive actions, then commit mutations when the operation is complete.

import Foundation
import Fluxus

enum CounterAction: Action {
  case IncrementRandom
  case IncrementRandomWithRange(Int)
}

struct CounterDispatcher: Dispatcher {
  var commit: (Mutation) -> Void

  func dispatch(action: CounterAction) {
    switch action {
    case .IncrementRandom:
      IncrementRandom()
    case .IncrementRandomWithRange(let range):
      IncrementRandom(range: range)
    }
  }

  func IncrementRandom(range: Int = 100) {
    // Simulate API call that takes 150ms to complete
    DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(150), execute: {
      let exampleResultFromAsyncOperation = Int.random(in: 1..<range)
      self.commit(CounterMutation.AddAmount(exampleResultFromAsyncOperation))
    })
  }
}

Create store

The store holds the current state. It also provides commit and dispatch methods, which route mutations and actions to the correct modules.

import SwiftUI
import Combine
import Fluxus

let rootStore = RootStore()

final class RootStore: BindableObject {
  var didChange = PassthroughSubject<RootStore, Never>()

  var state = RootState() {
    didSet {
      didChange.send(self)
    }
  }

  func commit(_ mutation: Mutation) {
    switch mutation {
    case is CounterMutation:
      state.counter = CounterCommitter().commit(state: self.state.counter, mutation: mutation as! CounterMutation)
    default:
      print("Unknown mutation type!")
    }
  }

  func dispatch(_ action: Action) {
    switch action {
    case is CounterAction:
      CounterDispatcher(commit: self.commit).dispatch(action: action as! CounterAction)
    default:
      print("Unknown action type!")
    }
  }
}

Add store to environment

We now provide the store to our views inside SceneDelegate.swift.

window.rootViewController = UIHostingController(rootView: ContentView().environmentObject(rootStore))

Use in views

ContentView.swift:

import SwiftUI

struct ContentView : View {
  @EnvironmentObject var store: RootStore

  var body: some View {
    NavigationView {
      Form {
        // Read the count from the store, and use a getter function to decide color
        Text("Count: \(store.state.counter.count)")
          .color(store.state.counter.countIsDivisibleBy(3) ? .orange : .green)

        Section {
          // Commit a mutation without a param
          Button(action: { self.store.commit(CounterMutation.Increment) }) {
            Text("Increment")
          }

          // Commit a mutation with a param
          Button(action: { self.store.commit(CounterMutation.AddAmount(5)) }) {
            Text("Increment by amount (5)")
          }

          // Dispatch an action without a param
          Button(action: { self.store.dispatch(CounterAction.IncrementRandom) }) {
            Text("Increment random")
          }

          // Dispatch an action with a param
          Button(action: { self.store.dispatch(CounterAction.IncrementRandomWithRange(20)) }) {
            Text("Increment random with range (20)")
          }
        }

        // Use with bindings
        Toggle(isOn: myToggleBinding) {
          Text("My boolean is: \(myToggleBinding.value ? "true" : "false")")
        }
      }.navigationBarTitle(Text("Fluxus Example"))
    }
  }

  // Use computed properties to get/set state via a binding
  var myToggleBinding = Binding<Bool> (
    getValue: {
      rootStore.state.counter.myBoolValue
  },
    setValue: { value in
      rootStore.commit(CounterMutation.SetMyBool(value))
  })
}

#if DEBUG
struct ContentView_Previews : PreviewProvider {
  static var previews: some View {
    return ContentView().environmentObject(rootStore)
  }
}
#endif

Simulator Screen Shot - iPhone Xs - 2019-06-17 at 15 32 11

💡 You should now have an app that demonstrates the basics of the flux pattern with Fluxus & SwiftUI. If you're having trouble getting this running, download the example app, or file a Github issue and we'll try to help.

Where to go from here

Check out the landmarks example app to see fluxus used in a more complex app environment.

Troubleshooting

Swift/SourceKit are using 100% CPU!

This is a bug in Xcode 11 beta, it usually means something is wrong with your @EnvironmentObject, make sure you are passing .environmentObject() to your view correctly.

If you are presenting a new view (e.g. a modal) you will have to pass .environmentObject(store) to it, just like your root view controller.

Feedback

Please file an issue if you spot a bug or think of a better way to do something.

Follow me on twitter @jsusek for random thoughts on SwiftUI.

Other SwiftUI Flux implementations

You might also like...
An experimental time traveling state store for SwiftUI
An experimental time traveling state store for SwiftUI

SwiftUI Time Travel A SwiftUI state store and view that allow you to scrub through an application's state. This is a super rough prototype: it's only

Eazy is the missing piece in your SwiftUI and UIKit application.

Eazy is the missing piece in your SwiftUI and UIKit application. It aims at harmonizing how your views communicate with the model and vice versa in a clear and consistent way. Eazy can be used on any Apple platform.

A lightweight Elm-like Store for SwiftUI

ObservableStore A simple Elm-like Store for SwiftUI, based on ObservableObject. ObservableStore helps you craft more reliable apps by centralizing all

VueFlux is the architecture to manage state with unidirectional data flow for Swift, inspired by Vuex and Flux.
VueFlux is the architecture to manage state with unidirectional data flow for Swift, inspired by Vuex and Flux.

Unidirectional State Management Architecture for Swift - Inspired by Vuex and Flux Introduction VueFlux is the architecture to manage state with unidi

Unidirectional State Management Architecture for Swift - Inspired by Vuex and Flux
Unidirectional State Management Architecture for Swift - Inspired by Vuex and Flux

Unidirectional State Management Architecture for Swift - Inspired by Vuex and Flux Introduction VueFlux is the architecture to manage state with unidi

A simple and predictable state management library inspired by Flux + Elm + Redux.

A simple and predictable state management library inspired by Flux + Elm + Redux. Flywheel is built on top of Corotuines using the concepts of structured concurrency. At the core, lies the State Machine which is based on actor model.

SwiftUI & Combine app using MovieDB API. With a custom Flux (Redux) implementation.
SwiftUI & Combine app using MovieDB API. With a custom Flux (Redux) implementation.

MovieSwiftUI MovieSwiftUI is an application that uses the MovieDB API and is built with SwiftUI. It demos some SwiftUI (& Combine) concepts. The goal

Sample iOS project built by SwiftUI + Flux and Combine framework using GitHub API
Sample iOS project built by SwiftUI + Flux and Combine framework using GitHub API

SwiftUI-Flux Flux enables us to have unidirectional data flow and make it testable. It's used to be implemented using RxSwift or ReactiveSwift in the

SwiftUI Todo app example using a React/Redux monolithic state store with flux like dispatch/reduce actions
SwiftUI Todo app example using a React/Redux monolithic state store with flux like dispatch/reduce actions

SwiftUI-Todo-Redux SwiftUI Todo Redux app example using a React/Redux monolithic state store with flux like dispatch/reduce actions Background SwiftUI

This is a tiny experimental application using SwiftUI with Flux architecture.
This is a tiny experimental application using SwiftUI with Flux architecture.

🚀 SwiftUI-Flux This is a tiny experimental application using SwiftUI with Flux architecture.

FluxCapacitor makes implementing Flux design pattern easily with protocols and typealias.
FluxCapacitor makes implementing Flux design pattern easily with protocols and typealias.

FluxCapacitor makes implementing Flux design pattern easily with protocols and typealias. Storable protocol Actionable protocol Dispatch

AsyncDispatcher is a lightweight Dispatcher implementation of Flux pattern.
AsyncDispatcher is a lightweight Dispatcher implementation of Flux pattern.

Simplified Dispatcher implementation of Flux pattern written in Swift using new async/await concurrency model

MVVM + FLUX iOS Instagram client  in Swift, eliminates Massive View Controller in unidirectional event/state flow manner
MVVM + FLUX iOS Instagram client in Swift, eliminates Massive View Controller in unidirectional event/state flow manner

CZInstagram MVVM + FLUX iOS Instagram client in Swift, eliminates Massive View Controller in unidirectional event/state flow manner. Unidirectional Da

SwiftAR is a declerative framework to build AR experiences, with a SwiftUI inspired design.
SwiftAR is a declerative framework to build AR experiences, with a SwiftUI inspired design.

SwiftAR is a declerative framework to build AR experiences, with a SwiftUI inspired design. It builds on the base types Experience, Anchor and

A micro UIStackView convenience API inspired by SwiftUI
A micro UIStackView convenience API inspired by SwiftUI

Stacks A micro UIStackView convenience API inspired by SwiftUI. let stack: UIView = .hStack(alignment: .center, margins: .all(16), [ .vStack(spaci

SwiftUI: Components Library Inspired by Twitter's Bootstrap
SwiftUI: Components Library Inspired by Twitter's Bootstrap

bootswiftui SwiftUI: Components Library Inspired by Twitter's Bootstrap Warning This is just SwiftUI exercise. Please do not consider using this repo

Kotlin Multiplatform sample with SwiftUI and Compose (Desktop and Android) clients. Heavily inspired by Wordle game.
Kotlin Multiplatform sample with SwiftUI and Compose (Desktop and Android) clients. Heavily inspired by Wordle game.

WordMasterKMP Kotlin Multiplatform sample heavily inspired by Wordle game and also Word Master and wordle-solver samples. The main game logic/state is

MySQL client library for Swift. This is inspired by Node.js' mysql.

mysql-swift MySQL client library for Swift. This is inspired by Node.js' mysql. Based on libmysqlclient Raw SQL query Simple query formatting and esca

Swifty Date & Time API inspired from Java 8 DateTime API.

AnyDate Swifty Date & Time API inspired from Java 8 DateTime API. Background I think that date & time API should be easy and accurate. Previous dates,

Comments
  • Question: How to handle UI bindings the flux way?

    Question: How to handle UI bindings the flux way?

    In the Readme, a Toggle gets bound to the boolean variablemyBoolBinding. This binding mutates the State directly, which in my understanding should not be possible at all, as this should always be done through mutations.

    Do you have any opinions or ideas on how to handle that the flux way?

    opened by hffmnn 4
  • ObjectBinding/BindableObject are deprecated

    ObjectBinding/BindableObject are deprecated

    ObjectBinding/BindableObject are deprecated# @ObjectBinding is become @ObservedObject.

    BindableObject protocol is become ObservableObject and willChange become objectWillChange.

    opened by guyschlider 0
Owner
John Susek
John Susek
MVVM + FLUX iOS Instagram client in Swift, eliminates Massive View Controller in unidirectional event/state flow manner

CZInstagram MVVM + FLUX iOS Instagram client in Swift, eliminates Massive View Controller in unidirectional event/state flow manner. Unidirectional Da

Cheng Zhang 56 Nov 1, 2022
Unidirectional Data Flow in Swift - Inspired by Redux

ReSwift Supported Swift Versions: Swift 4.2, 5.x For Swift 3.2 or 4.0 Support use Release 5.0.0 or earlier. For Swift 2.2 Support use Release 2.0.0 or

null 7.3k Dec 25, 2022
🤖 RxSwift + State Machine, inspired by Redux and Elm.

RxAutomaton RxSwift port of ReactiveAutomaton (State Machine). Terminology Whenever the word "signal" or "(signal) producer" appears (derived from Rea

Yasuhiro Inami 719 Nov 19, 2022
Unidirectional Data Flow in Swift - Inspired by Redux

ReSwift Supported Swift Versions: Swift 4.2, 5.x For Swift 3.2 or 4.0 Support use Release 5.0.0 or earlier. For Swift 2.2 Support use Release 2.0.0 or

null 7.3k Jan 3, 2023
🌾 Harvest: Apple's Combine.framework + State Machine, inspired by Elm.

NOTE: This repository has been discontinued in favor of Actomaton. ?? Harvest Apple's Combine.framework (from iOS 13) + State Machine, inspired by Elm

Yasuhiro Inami 386 Dec 18, 2022
🟣 Verge is a very tunable state-management engine on iOS App (UIKit / SwiftUI) and built-in ORM.

Verge.swift ?? An effective state management architecture for iOS - UIKit and also SwiftUI ?? _ An easier way to get unidirectional data flow _ _ Supp

VergeGroup 478 Dec 29, 2022
A powerful, minimal and composable architecture for building reactive iOS apps with SwiftUI or UIKit

SourceArchitecture A simple yet powerful framework for reactive programming with only a minimal optimized set of types. Sources are self-contained, hi

Daniel Hall 6 Nov 1, 2022
Sample iOS application in SwiftUI presenting Redux architecture

SwiftUI-Redux-Demo Sample iOS application in SwiftUI presenting Redux architecture. My full article about Redux in detail you will find here: Redux ar

Wojciech Kulik 25 Nov 27, 2022
Netflix Onboarding made with SwiftUI

OnBoardSwiftUI-Netflix Netflix Onboarding made with SwiftUI.

Shreyas Bhike 22 Dec 21, 2022
💎 Redux like architecture for SwiftUI

Simple Architecture like Redux Installation SPM dependencies: [ .package(url: "https://github.com/gre4ixin/ReduxUI.git", .upToNextMinor(from: "1.0

Pavel 38 Dec 13, 2022