Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/johnsusek/fluxus
Flux for SwiftUI, inspired by Vuex
https://github.com/johnsusek/fluxus
flux redux swiftui vuex
Last synced: 3 months ago
JSON representation
Flux for SwiftUI, inspired by Vuex
- Host: GitHub
- URL: https://github.com/johnsusek/fluxus
- Owner: johnsusek
- Created: 2019-06-13T14:28:34.000Z (over 5 years ago)
- Default Branch: master
- Last Pushed: 2019-12-23T17:45:43.000Z (almost 5 years ago)
- Last Synced: 2024-07-11T08:45:05.808Z (4 months ago)
- Topics: flux, redux, swiftui, vuex
- Language: Swift
- Homepage:
- Size: 76.2 KB
- Stars: 84
- Watchers: 2
- Forks: 4
- Open Issues: 2
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
- fucking-about-SwiftUI - Fluxus
- awesome - fluxus - Flux for SwiftUI, inspired by Vuex (vuex)
- awesome - fluxus - Flux for SwiftUI, inspired by Vuex (flux)
README
# ![Fluxus](https://user-images.githubusercontent.com/611996/59575192-ec3a4980-907f-11e9-9073-4d32108a39f7.png)
⚠️ Fluxus is no longer maintained, and may not be using latest SwiftUI best practices.
👉 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.
-----
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](https://github.com/johnsusek/fluxus).
## 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](https://user-images.githubusercontent.com/611996/59575032-4b4b8e80-907f-11e9-8fc3-9efe5aaa0682.png)
## 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
* The [minimal example app](https://github.com/johnsusek/fluxus-example-app) includes all the below code in a ready to run sample.
* The [landmarks example app](https://github.com/johnsusek/fluxus-landmark-example) is a reimplementation of the official landmarks tutorial app using fluxus.
* The [todo example app](https://github.com/johnsusek/fluxus-todo-example) is a very simple implementation of a todo list.## Articles
* [Let's Write a To-Do App with Fluxus](https://www.notion.so/7e6f682ca7e24d0f8504eedfd0cec132)## 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.
```swift
import Fluxusstruct CounterState: FluxState {
var count = 0var 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.
```swift
import Fluxusenum CounterMutation: Mutation {
case Increment
case AddAmount(Int)
case SetMyBool(Bool)
}struct CounterCommitter: Committer {
func commit(state: CounterState, mutation: CounterMutation) -> CounterState {
var state = stateswitch 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.
```swift
import Foundation
import Fluxusenum CounterAction: Action {
case IncrementRandom
case IncrementRandomWithRange(Int)
}struct CounterDispatcher: Dispatcher {
var commit: (Mutation) -> Voidfunc 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..()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.
```swift
window.rootViewController = UIHostingController(rootView: ContentView().environmentObject(rootStore))
```### Use in views
ContentView.swift:
```swift
import SwiftUIstruct ContentView : View {
@EnvironmentObject var store: RootStorevar 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 (
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](https://user-images.githubusercontent.com/611996/59634784-2dc90400-9115-11e9-9180-e4d4639d34d5.png)
💡 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](https://github.com/johnsusek/fluxus-landmark-example) 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](https://twitter.com/jsusek) for random thoughts on SwiftUI.
## Other SwiftUI Flux implementations
* https://github.com/Dimillian/SwiftUIDemo
* https://github.com/pocket7878/swift-ui-redux-like
* https://github.com/alexdrone/DispatchStore
* https://github.com/StevenLambion/SwiftDux
* https://github.com/ra1028/SwiftUI-Flux
* https://github.com/kitasuke/SwiftUI-Flux