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

Overview

Logo

FluxCapacitor

Build Status Build Status Version License Platform Carthage compatible

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

  • Storable protocol
  • Actionable protocol
  • DispatchState protocol

Requirements

  • Xcode 10.1 or later
  • Swift 4.2 or later
  • iOS 10.0 or later

Installation

CocoaPods

FluxCapacitor is available through CocoaPods. To install it, simply add the following line to your Podfile:

pod "FluxCapacitor"

Carthage

If you’re using Carthage, simply add FluxCapacitor to your Cartfile:

github "marty-suzuki/FluxCapacitor"

Usage

This is ViewController sample that uses Flux design pattern. If ViewController calls fetchRepositories method of RepositoryAction, it is reloaded automatically by observed changes of Constant in RepositoryStore after fetched repositories from Github. Introducing how to implement Flux design pattern with FluxCapacitor.

final class UserRepositoryViewController: UIViewController {
    @IBOutlet weak var tableView: UITableView!

    private let repositoryAction = RepositoryAction()
    private let repositoryStore = RepositoryStore.instantiate()
    private let userStore = UserStore.instantiate()
    private let dustBuster = DustBuster()
    private let dataSource = UserRepositoryViewDataSource()

    override func viewDidLoad() {
        super.viewDidLoad()

        dataSource.configure(with: tableView)
        observeStore()

        if let user = userStore.selectedUser.value {
            repositoryAction.fetchRepositories(withUserId: user.id, after: nil)
        }
    }

    private func observeStore() {
        repositoryStore.repositories
            .observe(on: .main, changes: { [weak self] _ in
                self?.tableView.reloadData()
            })
            .cleaned(by: dustBuster)
    }
}

Dispatcher

First of all, implementing DispatchState. It connects Action and Store, but it plays a role that don't depend directly each other.

extension Dispatcher {
    enum Repository: DispatchState {
        typealias RelatedStoreType = RepositoryStore
        typealias RelatedActionType = RepositoryAction

        case isRepositoryFetching(Bool)
        case addRepositories([GithubApiSession.Repository])
        case removeAllRepositories
    }
}

Store

Implementing Store with Storable protocol. func reduce(with:_) is called when Dispatcher dispatches DispatchStateType. Please update store's value with Associated Values.

final class RepositoryStore: Storable {
    typealias DispatchStateType = Dispatcher.Repository

    let isRepositoryFetching: Constant<Bool>
    private let _isRepositoryFetching = Variable<Bool>(false)

    let repositories: Constant<[Repository]>
    private let _repositories = Variable<[Repository]>([])

    required init() {
        self.isRepositoryFetching = Constant(_isRepositoryFetching)
        self.repositories = Constant(_repositories)
    }

    func reduce(with state: Dispatcher.Repository) {
        switch state {
        case .isRepositoryFetching(let value):
            _isRepositoryFetching.value = value
        case .addRepositories(let value):
            _repositories.value.append(contentsOf: value)
        case .removeAllRepositories:
            _repositories.value.removeAll()
        }
    }
}

If you want to use any store, please use XXXStore.instantiate(). That static method returns its reference or new instance. If you want to unregister any store from Dispatcher, please call xxxStore.clear().

Action

Implementing Action with Actionable protocol. If you call invoke method, it can dispatch value related DispatchStateType.

final class RepositoryAction: Actionable {
    typealias DispatchStateType = Dispatcher.Repository

    private let session: ApiSession

    init(session: ApiSession = .shared) {
        self.session = session
    }

    func fetchRepositories(withUserId id: String, after: String?) {
        invoke(.isRepositoryFetching(true))
        let request = UserNodeRequest(id: id, after: after)
        _ = session.send(request) { [weak self] in
            switch $0 {
            case .success(let value):
                self?.invoke(.addRepositories(value.nodes))
            case .failure:
                break
            }
            self?.invoke(.isRepositoryFetching(false))
        }
    }
}

Observe changes with Constant / Variable

You can initialize a store with instantiate(). If reference of store is left, that method returns remained one. If reference is not left, that method returns new instance. You can observe changes by Constant or Variable. When called observe, it returns Dust. So, clean up with DustBuster.

let dustBuster = DustBuster()

func observeStore() {
    // Get store instance
    let store = RepositoryStore.instantiate()

    // Observer changes of repositories that is `Constant<[Github.Repository]>`.
    store.repositories
        .observe(on: .main) { value in
            // do something
        }
        .cleaned(by: dustBuster)
}

dustbuster Robert Zemeckis (1989) Back to the future Part II, Universal Pictures

Constant and Variable

Variable has getter and setter of Element.

let intVal = Variable<Int>(0)
intVal.value = 1
print(intVal.value) // 1

Constant has only getter of Element. So, you can initialize Constant with Variable. Variable shares its value with Constant.

let variable = Variable<Int>(0)
let constant = Constant(variable)
variable.value = 1
print(variable.value) // 1
print(constant.value) // 1

In addition, Constant that initialize with some Variable, it can use same observation.

let variable = Variable<Int>(0)
let constant = Constant(variable)

_ = variable.observe { value in
    print(value) // 0 -> 10
}

_ = constant.observe { value in
    print(value) // 0 -> 10
}

variable.value = 10

with RxSwift

You can use FluxCapacitor with RxSwift like this link.

Or implement func asObservable() like this.

// Constant
extension PrimitiveValue where Trait == ImmutableTrait {
    func asObservable() -> Observable<Element> {
        return Observable.create { [weak self] observer in
            let dust = self?.observe { observer.onNext($0) }
            return Disposables.create { dust?.clean() }
        }
    }
}

Example

To run the example project, clone the repo, and run pod install and carthage update from the Example directory first. In addition, you must set Github Personal access token.

// ApiSessionType.swift
extension ApiSession: ApiSessionType {
    static let shared: ApiSession = {
        let token = "" // Your Personal Access Token
        return ApiSession(injectToken: { InjectableToken(token: token) })
    }()
}

Application structure is like below.

flux_image

GithubKitForSample is used in this sample project.

Additional

Flux + MVVM Sample is here.

Migration Guide

FluxCapacitor 0.10.0 Migration Guide

Author

marty-suzuki, [email protected]

License

FluxCapacitor is available under the MIT license. See the LICENSE file for more info.

You might also like...
A Swift micro-framework to easily deal with weak references to self inside closures

WeakableSelf Context Closures are one of Swift must-have features, and Swift developers are aware of how tricky they can be when they capture the refe

WhatsNewKit enables you to easily showcase your awesome new app features.
WhatsNewKit enables you to easily showcase your awesome new app features.

WhatsNewKit enables you to easily showcase your awesome new app features. It's designed from the ground up to be fully customized to your needs. Featu

Transform strings easily in Swift.

swift-string-transform Transform strings easily in Swift. Table of Contents Installation How to use Contribution Installation Swift Package Manager (R

A library that helps developers to easily perform file-related operations In iOS

File Operations Preview A library that helps developers to easily perform file-related operations. In iOS, We write our files mainly into three direct

Monitor iOS app version easily.

AppVersionMonitor Monitor iOS app version easily. You can get previous version and installation history. Usage To run the example project, clone the r

Framework for easily parsing your JSON data directly to Swift object.
Framework for easily parsing your JSON data directly to Swift object.

Server sends the all JSON data in black and white format i.e. its all strings & we make hard efforts to typecast them into their respective datatypes

FeatureFlags.swift - Tools for easily defining feature flags for your projects

FeatureFlags.swift Tools for easily defining feature flags for your projects Int

 Zip - A Swift framework for zipping and unzipping files. Simple and quick to use. Built on top of minizip.
Zip - A Swift framework for zipping and unzipping files. Simple and quick to use. Built on top of minizip.

Zip A Swift framework for zipping and unzipping files. Simple and quick to use. Built on top of minizip. Usage Import Zip at the top of the Swift file

Useful Swift code samples, extensions, functionalities and scripts to cherry-pick and use in your projects

SwiftyPick 🦅 🍒 Useful Swift code samples, extensions, functionalities and scripts to cherry-pick and use in your projects. Purpose The idea behind t

Comments
  • An improvement example using Swift4

    An improvement example using Swift4

    // Definition
    protocol DispatcherValue {
        associatedtype RelatedStoreType: Storable
        associatedtype RelatedActionType: Actionable
    }
    
    protocol Storable: Codable {
        associatedtype DispatcherValueType: DispatcherValue
        static var name: String { get }
        static func instatntiate() -> Self?
        init()
        func bind(_ value: DispatcherValueType)
    }
    extension Storable {
        static var name: String {
            return String(describing: self)
        }
    
        static func instatntiate() -> Self? {
            // Load store from Disk cache
            return nil
        }
    }
    
    protocol Actionable {
        associatedtype DispatcherValueType: DispatcherValue
        func dispatch(_ value: DispatcherValueType)
    }
    extension Actionable where DispatcherValueType.RelatedStoreType.DispatcherValueType == DispatcherValueType {
        func dispatch(_ value: DispatcherValueType) {
            typealias Store = DispatcherValueType.RelatedStoreType
            let store = Store.instatntiate() ?? Store()
            store.bind(value)
        }
    }
    
    // Implementaion
    enum UserValue: DispatcherValue {
        typealias RelatedStoreType = UserStore
        typealias RelatedActionType = UserAction
        case id(Int)
    }
    
    final class UserStore: Storable, Codable {
        static let shared = UserStore()
        typealias DispatcherValueType = UserValue
        private(set) var id: Int = 0
        required init() {}
        static func instatntiate() -> UserStore? { return .shared }
        func bind(_ value: UserValue) {
            switch value {
            case .id(let value):
                self.id = value
            }
        }
    }
    
    class UserAction: Actionable {
        typealias DispatcherValueType = UserValue
    }
    
    UserAction().dispatch(.id(100))
    print(UserStore.instatntiate()?.id)
    
    enhancement 
    opened by marty-suzuki 0
Releases(0.11.0)
Owner
Taiki Suzuki
AbemaTV / University of Aizu 18th
Taiki Suzuki
Swift package containing collection protocols.

swift-collection-protocols A package containing collection protocols, for the Swift programming language. Overview No overview available. Availability

Alexandre H. Saad 0 Jul 28, 2022
A SARS-CoV-2 Mutation Pattern Query Tool

vdb A SARS-CoV-2 Mutation Pattern Query Tool 1. Purpose The vdb program is designed to query the SARS-CoV-2 mutational landscape. It runs as a command

null 13 Oct 25, 2022
A meta library to provide a better `Delegate` pattern.

Delegate A meta library to provide a better Delegate pattern described here and here. Usage Instead of a regular Apple's protocol-delegate pattern, us

Wei Wang 67 Dec 23, 2022
Swordinator is a simple way of integrating an iOS Coordinator pattern.

Swordinator is a minimal, lightweight and easy customizable navigation framework for iOS applications. Requirements iOS 14.0+, Swift 5.0+ Installation

Timotheus Laubengaier 10 Oct 17, 2022
🚀Comprehensive Redux library for SwiftUI, ensures State consistency across Stores with type-safe pub/sub pattern.

??Comprehensive Redux library for SwiftUI, ensures State consistency across Stores with type-safe pub/sub pattern.

Cheng Zhang 18 Mar 9, 2022
RandomKit is a Swift framework that makes random data generation simple and easy.

RandomKit is a Swift framework that makes random data generation simple and easy. Build Status Installation Compatibility Swift Package Manager CocoaP

Nikolai Vazquez 1.5k Dec 29, 2022
It makes a preview from an URL, grabbing all the information such as title, relevant texts and images.

Link Previewer for iOS, macOS, watchOS and tvOS It makes a preview from an URL, grabbing all the information such as title, relevant texts and images.

Leonardo Cardoso 1.3k Jan 2, 2023
FastLayout - A UIKit or AppKit package for fast UI design

FastLayout FastLayout is a UIKit or AppKit package for fast UI design. Layout Ex

null 1 Feb 19, 2022
A way to easily add Cocoapod licenses and App Version to your iOS App using the Settings Bundle

EasyAbout Requirements: cocoapods version 1.4.0 or above. Why you should use Well, it is always nice to give credit to the ones who helped you ?? Bonu

João Mourato 54 Apr 6, 2022