{"id":13751527,"url":"https://github.com/johnsusek/fluxus","last_synced_at":"2025-10-21T10:47:44.808Z","repository":{"id":63913155,"uuid":"191778616","full_name":"johnsusek/fluxus","owner":"johnsusek","description":"Flux for SwiftUI, inspired by Vuex","archived":false,"fork":false,"pushed_at":"2019-12-23T17:45:43.000Z","size":78,"stargazers_count":84,"open_issues_count":2,"forks_count":4,"subscribers_count":2,"default_branch":"master","last_synced_at":"2024-11-16T04:31:42.010Z","etag":null,"topics":["flux","redux","swiftui","vuex"],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/johnsusek.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-06-13T14:28:34.000Z","updated_at":"2023-08-13T14:21:46.000Z","dependencies_parsed_at":"2023-01-14T13:16:03.866Z","dependency_job_id":null,"html_url":"https://github.com/johnsusek/fluxus","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/johnsusek%2Ffluxus","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/johnsusek%2Ffluxus/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/johnsusek%2Ffluxus/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/johnsusek%2Ffluxus/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/johnsusek","download_url":"https://codeload.github.com/johnsusek/fluxus/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":225298581,"owners_count":17452234,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["flux","redux","swiftui","vuex"],"created_at":"2024-08-03T09:00:47.483Z","updated_at":"2025-10-21T10:47:44.472Z","avatar_url":"https://github.com/johnsusek.png","language":"Swift","readme":"# ![Fluxus](https://user-images.githubusercontent.com/611996/59575192-ec3a4980-907f-11e9-9073-4d32108a39f7.png)\n\n⚠️ Fluxus is no longer maintained, and may not be using latest SwiftUI best practices.\n\n👉 I encourage you to look at the [source of Fluxus](https://github.com/johnsusek/fluxus/blob/master/Sources/Fluxus/Fluxus.swift). 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. \n\n\n\n-----\n\n\nFluxus is an implementation of the Flux pattern for SwiftUI that replaces MVC, MVVM, Viper, etc. \n* Organize all your model data into a store and easily access in your views. \n* Use mutations to modify your app's state.\n* Use actions to perform asynchronous operations.  \n* Keep your models and views as simple as possible.\n\n## Requirements\n\nXcode 11 beta on MacOS 10.14 or 10.15\n\n## Installation\n\nIn Xcode, choose File -\u003e Swift Packages -\u003e Add Package Dependency and enter [this repo's URL](https://github.com/johnsusek/fluxus).\n\n## Concepts\n\n* **State** is the root source of truth for your app\n* **Mutations** describe a synchronous change in state\n* **Committers** apply mutations to the state\n* **Actions** describe an asynchronous operation\n* **Dispatchers** execute asynchronous actions and commit mutations when complete\n\n![Obligatory Flux Diagram](https://user-images.githubusercontent.com/611996/59575032-4b4b8e80-907f-11e9-8fc3-9efe5aaa0682.png)\n\n## When should I use it?\n\nFluxus 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:\n\n\u003e Flux libraries are like glasses: you’ll know when you need them.\n\n*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.\n\n## Example apps\n\n* The [minimal example app](https://github.com/johnsusek/fluxus-example-app) includes all the below code in a ready to run sample.\n* The [landmarks example app](https://github.com/johnsusek/fluxus-landmark-example) is a reimplementation of the official landmarks tutorial app using fluxus.\n* The [todo example app](https://github.com/johnsusek/fluxus-todo-example) is a very simple implementation of a todo list.\n\n## Articles\n* [Let's Write a To-Do App with Fluxus](https://www.notion.so/7e6f682ca7e24d0f8504eedfd0cec132)\n\n## Usage\n\n### Create state\n\nState 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.\n\n```swift\nimport Fluxus\n\nstruct CounterState: FluxState {\n  var count = 0\n\n  var myBoolValue = false\n\n  var countIsEven: Bool {\n    get {\n      return count % 2 == 0\n    }\n  }\n\n  func countIsDivisibleBy(_ by: Int) -\u003e Bool {\n    return count % by == 0\n  }\n}\n\nstruct RootState {\n  var counter = CounterState()\n}\n```\n\n### Create mutations/committers\n\nMutations describe a change in state. Committers receive mutations and modify the state.\n\n```swift\nimport Fluxus\n\nenum CounterMutation: Mutation {\n  case Increment\n  case AddAmount(Int)\n  case SetMyBool(Bool)\n}\n\nstruct CounterCommitter: Committer {\n  func commit(state: CounterState, mutation: CounterMutation) -\u003e CounterState {\n    var state = state\n\n    switch mutation {\n    case .Increment:\n      state.count += 1\n    case .AddAmount(let amount):\n      state.count += amount\n    case .SetMyBool(let value):\n      state.myBoolValue = value\n    }\n\n    return state\n  }\n}\n```\n\n### Create actions/dispatchers\n\nActions describe an asynchronous operation. Dispatchers receive actions, then commit mutations when the operation is complete.\n\n```swift \nimport Foundation\nimport Fluxus\n\nenum CounterAction: Action {\n  case IncrementRandom\n  case IncrementRandomWithRange(Int)\n}\n\nstruct CounterDispatcher: Dispatcher {\n  var commit: (Mutation) -\u003e Void\n\n  func dispatch(action: CounterAction) {\n    switch action {\n    case .IncrementRandom:\n      IncrementRandom()\n    case .IncrementRandomWithRange(let range):\n      IncrementRandom(range: range)\n    }\n  }\n\n  func IncrementRandom(range: Int = 100) {\n    // Simulate API call that takes 150ms to complete\n    DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(150), execute: {\n      let exampleResultFromAsyncOperation = Int.random(in: 1..\u003crange)\n      self.commit(CounterMutation.AddAmount(exampleResultFromAsyncOperation))\n    })\n  }\n}\n```\n\n### Create store\n\nThe store holds the current state. It also provides commit and dispatch methods, which route mutations and actions to the correct modules.\n\n```swift\nimport SwiftUI\nimport Combine\nimport Fluxus\n\nlet rootStore = RootStore()\n\nfinal class RootStore: BindableObject {\n  var didChange = PassthroughSubject\u003cRootStore, Never\u003e()\n\n  var state = RootState() {\n    didSet {\n      didChange.send(self)\n    }\n  }\n\n  func commit(_ mutation: Mutation) {\n    switch mutation {\n    case is CounterMutation:\n      state.counter = CounterCommitter().commit(state: self.state.counter, mutation: mutation as! CounterMutation)\n    default:\n      print(\"Unknown mutation type!\")\n    }\n  }\n\n  func dispatch(_ action: Action) {\n    switch action {\n    case is CounterAction:\n      CounterDispatcher(commit: self.commit).dispatch(action: action as! CounterAction)\n    default:\n      print(\"Unknown action type!\")\n    }\n  }\n}\n```\n\n### Add store to environment\n\nWe now provide the store to our views inside SceneDelegate.swift.\n\n```swift\nwindow.rootViewController = UIHostingController(rootView: ContentView().environmentObject(rootStore))\n```\n\n### Use in views\n\nContentView.swift:\n```swift\nimport SwiftUI\n\nstruct ContentView : View {\n  @EnvironmentObject var store: RootStore\n\n  var body: some View {\n    NavigationView {\n      Form {\n        // Read the count from the store, and use a getter function to decide color\n        Text(\"Count: \\(store.state.counter.count)\")\n          .color(store.state.counter.countIsDivisibleBy(3) ? .orange : .green)\n\n        Section {\n          // Commit a mutation without a param\n          Button(action: { self.store.commit(CounterMutation.Increment) }) {\n            Text(\"Increment\")\n          }\n\n          // Commit a mutation with a param\n          Button(action: { self.store.commit(CounterMutation.AddAmount(5)) }) {\n            Text(\"Increment by amount (5)\")\n          }\n\n          // Dispatch an action without a param\n          Button(action: { self.store.dispatch(CounterAction.IncrementRandom) }) {\n            Text(\"Increment random\")\n          }\n\n          // Dispatch an action with a param\n          Button(action: { self.store.dispatch(CounterAction.IncrementRandomWithRange(20)) }) {\n            Text(\"Increment random with range (20)\")\n          }\n        }\n\n        // Use with bindings\n        Toggle(isOn: myToggleBinding) {\n          Text(\"My boolean is: \\(myToggleBinding.value ? \"true\" : \"false\")\")\n        }\n      }.navigationBarTitle(Text(\"Fluxus Example\"))\n    }\n  }\n\n  // Use computed properties to get/set state via a binding\n  var myToggleBinding = Binding\u003cBool\u003e (\n    getValue: {\n      rootStore.state.counter.myBoolValue\n  },\n    setValue: { value in\n      rootStore.commit(CounterMutation.SetMyBool(value))\n  })\n}\n\n#if DEBUG\nstruct ContentView_Previews : PreviewProvider {\n  static var previews: some View {\n    return ContentView().environmentObject(rootStore)\n  }\n}\n#endif\n```\n\n![Simulator Screen Shot - iPhone Xs - 2019-06-17 at 15 32 11](https://user-images.githubusercontent.com/611996/59634784-2dc90400-9115-11e9-9180-e4d4639d34d5.png)\n\n💡 You should now have an app that demonstrates the basics of the flux pattern with Fluxus \u0026 SwiftUI. If you're having trouble getting this running, download the example app, or file a Github issue and we'll try to help.\n\n## Where to go from here\n\nCheck out the [landmarks example app](https://github.com/johnsusek/fluxus-landmark-example) to see fluxus used in a more complex app environment. \n\n## Troubleshooting\n\n**Swift/SourceKit are using 100% CPU!**\n\n*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.*\n\n*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.*\n\n## Feedback\n\nPlease file an issue if you spot a bug or think of a better way to do something. \n\nFollow me on twitter [@jsusek](https://twitter.com/jsusek) for random thoughts on SwiftUI.\n\n## Other SwiftUI Flux implementations \n* https://github.com/Dimillian/SwiftUIDemo\n* https://github.com/pocket7878/swift-ui-redux-like\n* https://github.com/alexdrone/DispatchStore\n* https://github.com/StevenLambion/SwiftDux\n* https://github.com/ra1028/SwiftUI-Flux\n* https://github.com/kitasuke/SwiftUI-Flux\n\n","funding_links":[],"categories":["vuex","🌎 by the community"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjohnsusek%2Ffluxus","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjohnsusek%2Ffluxus","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjohnsusek%2Ffluxus/lists"}