All Projects → johnsusek → Fluxus

johnsusek / Fluxus

Flux for SwiftUI, inspired by Vuex

Programming Languages

swift
15916 projects

Projects that are alternatives of or similar to Fluxus

Beauty Vuejs Boilerplate
❤️ Real world base Vue.js app. Access/refresh tokens auth, api services, http client, vuex modules
Stars: ✭ 583 (+619.75%)
Mutual labels:  flux, vuex
Vueflux
♻️ Unidirectional State Management Architecture for Swift - Inspired by Vuex and Flux
Stars: ✭ 315 (+288.89%)
Mutual labels:  flux, vuex
Verge
🟣 Verge is a very tunable state-management engine on iOS App (UIKit / SwiftUI) and built-in ORM.
Stars: ✭ 273 (+237.04%)
Mutual labels:  flux, vuex
Vue Entity Adapter
Package to maintain entities in Vuex.
Stars: ✭ 20 (-75.31%)
Mutual labels:  flux, vuex
Putongoj Fe
The front end of the Putong Online Judge -- An online judge with nothing special
Stars: ✭ 74 (-8.64%)
Mutual labels:  vuex
Docker Vue
Frontend for DockerRails, built with Vue.js
Stars: ✭ 72 (-11.11%)
Mutual labels:  vuex
Vue Awesome Template
☀基于[email protected]的[email protected] 项目模板;集成各种常用组件、轮子、最佳实践;
Stars: ✭ 70 (-13.58%)
Mutual labels:  vuex
Vue Studymaps
使用 Vue.js 开发的聚合应用。通过爬虫抓取平时浏览的网站,省去逐个点开网页的时间。
Stars: ✭ 71 (-12.35%)
Mutual labels:  vuex
Sahx Admin
SAHX-Admin 是套功能较为完整的后台管理系统架构, 以Thinkjs 作为中间层, Vuejs作为前端模块化开发, AdminLET作为前端UI, 实现了前后端分离, 前端组件化, 便于多人协同开发.
Stars: ✭ 80 (-1.23%)
Mutual labels:  vuex
Vuex Multi Tab State
💾🔗🖥️ Share, synchronize and persist state between multiple tabs with this plugin for Vuex. TypeScript types included.
Stars: ✭ 77 (-4.94%)
Mutual labels:  vuex
Wxappstore
微信小程序全局状态管理,并提供Vuex的开发体验
Stars: ✭ 74 (-8.64%)
Mutual labels:  vuex
Flocks.js
A radically simpler alternative to Flux - opinionated React state and rendering management
Stars: ✭ 72 (-11.11%)
Mutual labels:  flux
Vue Cli3 Multipage
由vue-cli3搭建的多页面多路由初始化项目包,包含ESlint,Axios,vue-router,vuex,babel,以及自己封装的异步请求API接口。
Stars: ✭ 76 (-6.17%)
Mutual labels:  vuex
Lottery Project
彩票项目
Stars: ✭ 72 (-11.11%)
Mutual labels:  vuex
Speedtest
HTML5 speedtest, javascript client and golang server. Responsive interface, can be used on desktop, tablets and phones. Uses the same algorithm as Ookla / speedtest.net, and gives approximately the same results.
Stars: ✭ 77 (-4.94%)
Mutual labels:  vuex
Reactive Flux
Fluxish model implemented with RxJS
Stars: ✭ 71 (-12.35%)
Mutual labels:  flux
Vue Mall
微信公众号测试项目
Stars: ✭ 74 (-8.64%)
Mutual labels:  vuex
Material Flux
No magic flux implementation library.
Stars: ✭ 76 (-6.17%)
Mutual labels:  flux
Fleur
A fully-typed, type inference and testing friendly Flux Framework
Stars: ✭ 74 (-8.64%)
Mutual labels:  flux
Element Vue Admin
vue admin template base on element 2
Stars: ✭ 73 (-9.88%)
Mutual labels:  vuex

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

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].