Skip to content

Repository files navigation

Logo

GoodReactor

Check out the Documentation

iOS VersionSwift VersionContains TestDependency Manager

TBD: updated description

Installation

Swift Package Manager

Create a Package.swift file and add the package dependency into the dependencies list. Or to integrate without package.swift add it through the Xcode add package interface.

import PackageDescription
letpackage=Package(
name:"SampleProject",
dependencies:[.package(url:"https://github.com/GoodRequest/GoodReactor".upToNextMajor("2.3.0"))])

Usage

GoodReactor

ViewModel

In your ViewModel define Actions, Mutations, Destinations and the State

  • State defines all data of a View (or a ViewController)
  • Action represents user actions that are sent from the View.
  • Mutation represents state changes from external sources.
  • Destination represents all possible destinations, where user can navigate.
@ObservablefinalclassViewModel:Reactor{enumAction{case login(username:String, password:String)}enumMutation{case didReceiveAuthResponse(Credentials)}enumDestination{case homeScreen
case errorAlert
}@ObservablefinalclassState{varusername:Stringvarpassword:String}}

You can provide the initial state of the view in the makeInitialState function.

func makeInitialState()->State{returnState()}

Finally in the reduce function you define how state changes, according to certain events:

typealiasEvent=GoodReactor.Event<Action,Mutation,Destination>func reduce(state:inoutState, event:Event){switch event.kind {case.action(.login(...)):
// ...
case .mutation:
// ...
case.destination:
// ...
}}

You can run asynchronous tasks by using run and returning the result in form of a Mutation.

func reduce(state:inoutState, event:Event){switch event.kind {case.action(.login(let username,let password)):run(event){letcredentials=await networking.login(username, password)returnMutation.didReceiveAuthResponse(credentials)}
// ...
case.mutation(.didReceiveAuthResponse(let credentials)):
// proceed with login
}}

You can listen to external changes by subscribe-ing to event Publisher-s. You start the subscriptions by calling the start() function.

// in ViewModel:
func transform(){subscribe{awaitExternalTimer.shared.timePublisher
} map:{Mutation.didChangeTime(seconds: $0)}}
// in View (SwiftUI):
varbody:someView{MyContentView().task{ viewModel.start()}}

View (SwiftUI)

You add the ViewModel as a property wrapper to your view:

@ViewModelvarmodel:AnyReactor=MyViewModel().eraseToAnyReactor()

AnyReactor does not start the wrapped reactor automatically. Start it explicitly:

.task { model.start()}

To access the current State you use:

// read-only access
Text(model.username)
// binding (refactored to a variable for better readability)
letbinding= model.bind(\.username, action:{.setUsername($0)})TextField("Username", text: binding)

To send an event to the ViewModel you call:

model.send(action:.login(username, password))
model.send(destination:.errorAlert)

UIViewController (UIKit/Combine)

From UIViewController (in UIKit, or any other frameworks) you can send actions to ViewModel via Combine:

myButton.publisher(for:.touchUpInside).map{ _ in.login(username, password)}.map{.action($0)}.subscribe(model.eventStream).store(in:&cancellables)

Then use Combine to subscribe to state changes, so every time the state is changed, ViewController can be updated as well:

reactor.stateStream
.map{String($0.username)}.assign(to: \.text, on: usernameLabel, ownership:.weak).store(in:&cancellables)

Logging

structSampleLogger:ReactorLogger{func logReactorEvent(_ message:Any, level:LogLevel, fileName:String, lineNumber:Int){print("[\(level)] \(message) (\(fileName):\(lineNumber))")}}ReactorConfiguration.logger =SampleLogger()

Previews

You can easily mock state for Xcode Previews by using Stub reactor implementation:

#Preview("Empty"){NavigationStack{HomeScreen(viewModel: Stub<HomeScreenViewModel>{letstate=HomeScreenViewModel.State()
state.items =[] // use empty array to mock empty state
return state
}.eraseToAnyReactor())}}

License

GoodReactor repository is released under the MIT license. See LICENSE for details.

About

⚛️ GoodReactor is a Redux-inspired Reactor framework for iOS developed using Swift. It enables seamless communication between the View Model, View Controller, and Coordinator through state and navigation functions. It ensures no side-effects by interacting with dependencies outside of the Reduce function. Integrate it with using SPM!

Topics

Resources

Stars

33 stars

Watchers

2 watching

Forks

Releases

Packages

Used by

Contributors

Languages