• Stars
    star
    109
  • Rank 319,077 (Top 7 %)
  • Language
    Swift
  • License
    MIT License
  • Created about 6 years ago
  • Updated almost 5 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

🔁Unidirectional data flow architecture with MVP and Flux combination for Swift

Platform Language Carthage Version License CI Status

Prex is a framework which makes an unidirectional data flow application possible with MVP architecture.

Concept

Prex represents Presenter + Flux, therefore it is a combination of Flux and MVP architecture. In addition, Reactive frameworks are not used in Prex. To reflect a state to a view, using Passive View Pattern. Flux are used behind of the Presenter. Data flow is unidirectional that like a below figure.

If you use Prex, you have to implement those components.

State

The State has properties to use in the View and the Presenter.

struct CounterState: State {
    var count: Int = 0
}

Action

The Action represents internal API of your application. For example, if you want to increment the count of CounterState, dispatch Action.increment to Dispatcher.

enum CounterAction: Action {
    case increment
    case decrement
}

Mutation

The Mutation is allowed to mutate the State with the Action.

struct CounterMutation: Mutation {
    func mutate(action: CounterAction, state: inout CounterState) {
        switch action {
        case .increment:
            state.count += 1

        case .decrement:
            state.count -= 1
        }
    }
}

Presenter

The Presenter has a role to connect between View and Flux components. If you want to access side effect (API access and so on), you must access them in the Presenter. Finally, you dispatch those results with Presenter.dispatch(_:).

extension Presenter where Action == CounterAction, State == CounterState {
    func increment() {
        dispatch(.increment)
    }

    func decrement() {
        if state.count > 0 {
            dispatch(.decrement)
        }
    }
}

View

The View displays the State with View.reflect(change:). It is called by the Presenter when the State has changed. In addition, it calls the Presenter methods by User interactions.

final class CounterViewController: UIViewController {
    private let counterLabel: UILabel
    private lazy var presenter = Presenter(view: self,
                                           state: CounterState(),
                                           mutation: CounterMutation())

    @objc private func incrementButtonTap(_ button: UIButton) {
        presenter.increment()
    }

    @objc private func decrementButtonTap(_ button: UIButton) {
        presenter.decrement()
    }
}

extension CounterViewController: View {
    func reflect(change: StateChange<CounterState>) {
        if let count = change.count?.value {
            counterLabel.text = "\(count)"
        }
    }
}

You can get only specified value that has changed in the State from StateChange.changedProperty(for:).

Advanced Usage

Shared Store

Initializers of the Store and the Dispatcher are not public access level. But you can initialize them with Flux and inject them with Presenter.init(view:flux:).

This is shared Flux components example.

extension Flux where Action == CounterAction, State == CounterState {
    static let shared = Flux(state: CounterState(), mutation: CounterMutation())
}

or

enum SharedFlux {
    static let counter = Flux(state: CounterState(), mutation: CounterMutation())
}

Inject Flux like this.

final class CounterViewController: UIViewController {
    private lazy var presenter = {
        let flux =  Flux<CounterAction, CounterState>.shared
        return Presenter(view: self, flux: flux)
    }()
}

Presenter Subclass

The Presenter is class that has generic parameters. You can create the Presenter subclass like this.

final class CounterPresenter: Presenter<CounterAction, CounterState> {
    init<T: View>(view: T) where T.State == CounterState {
        let flux = Flux(state: CounterState(), mutation: CounterMutation())
        super.init(view: view, flux: flux)
    }

    func increment() {
        dispatch(.increment)
    }

    func decrement() {
        if state.count > 0 {
            dispatch(.decrement)
        }
    }
}

Testing

I'll explain how to test with Prex. Focus on two test cases in this document.

1. Reflection state testing 2. Create actions testing

Both tests need the View to initialize a Presenter. You can create MockView like this.

final class MockView: View {
    var refrectParameters: ((StateChange<CounterState>) -> ())?

    func reflect(change: StateChange<CounterState>) {
        refrectParameters?(change)
    }
}

1. Reflection state testing

This test starts with dispatching an Action. An action is passed to Mutation, and Mutation mutates state with a received action. The Store notifies changes of state, and the Presenter calls reflect method of the View to reflects state. Finally, receives state via reflect method parameters of the View.

This is a sample test code.

func test_presenter_calls_reflect_of_view_when_state_changed() {
    let view = MockView()
    let flux = Flux(state: CounterState(), mutation: CounterMutation())
    let presenter = Presenter(view: view, flux: flux)

    let expect = expectation(description: "wait receiving ValueChange")
    view.refrectParameters = { change in
        let count = change.changedProperty(for: \.count)?.value
        XCTAssertEqual(count, 1)
        expect.fulfill()
    }

    flux.dispatcher.dispatch(.increment)
    wait(for: [expect], timeout: 0.1)
}

2. Create actions testing

This test starts with calling the Presenter method as dummy user interaction. The Presenter accesses side-effect and finally creates an action from that result. That action is dispatched to the Dispatcher. Finally, receives action via register callback of the Dispatcher.

This is a sample test code.

func test_increment_method_of_presenter() {
    let view = MockView()
    let flux = Flux(state: CounterState(), mutation: CounterMutation())
    let presenter = Presenter(view: view, flux: flux)

    let expect = expectation(description: "wait receiving ValueChange")
    let subscription = flux.dispatcher.register { action in
        XCTAssertEqual(action, .increment)
        expect.fulfill()
    }

    presenter.increment()
    wait(for: [expect], timeout: 0.1)
    flux.dispatcher.unregister(subscription)
}

An addition

You can test mutating state like this.

func test_mutation() {
    var state = CounterState()
    let mutation = CounterMutation()

    mutation.mutate(action: .increment, state: &state)
    XCTAssertEqual(state.count, 1)

    mutation.mutate(action: .decrement, state: &state)
    XCTAssertEqual(state.count, 0)
}

Example

Project

You can try Prex with GitHub Repository Search Application Example. Open PrexSample.xcworkspace and run it!

Playground

You can try Prex counter sample with Playground! Open Prex.xcworkspace and build Prex-iOS. Finally, you can run manually in Playground.

Requirements

  • Xcode 9.4.1 or greater
  • iOS 10.0 or greater
  • tvOS 10.0 or greater
  • macOS 10.10 or greater
  • watchOS 3.0 or greater
  • Swift 4.1 or greater

Installation

Carthage

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

github "marty-suzuki/Prex"

CocoaPods

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

pod 'Prex'

Swift Package Manager

Prex is available through Swift Package Manager. Just add the url of this repository to your Package.swift.

dependencies: [
    .package(url: "https://github.com/marty-suzuki/Prex.git", from: "0.2.0")
]

Inspired by these unidirectional data flow frameworks

Author

marty-suzuki, [email protected]

License

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

More Repositories

1

ReverseExtension

A UITableView extension that enables cell insertion from the bottom of a table view.
Swift
1,675
star
2

SAHistoryNavigationViewController

SAHistoryNavigationViewController realizes iOS task manager like UI in UINavigationContoller. Support 3D Touch!
Swift
1,569
star
3

iOSDesignPatternSamples

This is Github user search demo app which made by many variety of design patterns. You can compare differences in MVC, MVP, MVVM and Flux.
Swift
690
star
4

URLEmbeddedView

URLEmbeddedView automatically caches the object that is confirmed the Open Graph Protocol.
Swift
650
star
5

SABlurImageView

You can use blur effect and it's animation easily to call only two methods.
Swift
557
star
6

MisterFusion

MisterFusion is Swift DSL for AutoLayout. It is the extremely clear, but concise syntax, in addition, can be used in both Swift and Objective-C. Support Safe Area and Size Class.
Swift
314
star
7

SAInboxViewController

UIViewController subclass inspired by "Inbox by google" animated transitioning.
Swift
297
star
8

SACollectionViewVerticalScalingFlowLayout

UICollectionViewLayout that performs scaling up and down automatically on disappearing cells, and applies UIDynamics.
Swift
272
star
9

SAParallaxViewControllerSwift

SAParallaxViewControllerSwift realizes parallax scrolling with blur effect. In addition, it realizes seamless opening transition.
Swift
260
star
10

MartyJunior

You can change tab contents with swipe gesture on middle of UITableView!!
Swift
245
star
11

TheAnimation

Type-safe CAAnimation wrapper. It makes preventing to set wrong type values.
Swift
224
star
12

GitHubSearchWithSwiftUI

SwiftUI and Combine based GitHubSearch example.
Swift
205
star
13

DuctTape

📦 KeyPath dynamicMemberLookup based syntax sugar for Swift.
Swift
175
star
14

HoverConversion

HoverConversion realized vertical paging with UITableView. UIViewController will be paged when reaching top or bottom of UITableView contentOffset.
Swift
163
star
15

NoticeObserveKit

NoticeObserveKit is type-safe NotificationCenter wrapper.
Swift
150
star
16

FluxCapacitor

This is what makes the Flux design pattern possible.
Swift
123
star
17

MSAlertController

MSAlertController has same feature at UIAlertViewController.
Objective-C
117
star
18

QiitaWithFluxSample

A sample project uses Flux and MVVM features with RxSwift.
Swift
104
star
19

Continuum

NotificationCenter based Lightweight UI / AnyObject binder.
Swift
80
star
20

Ricemill

🌾 ♻️ 🍚 Unidirectional Input / Output framework with Combine. Supports both of SwiftUI and UIKit.
Swift
63
star
21

PickerButton

PickerButton is subclass of UIButton that presents UIPickerView in UIKeyboard.
Swift
44
star
22

SASecretCommandViewController

You can use secret command with swipe gesture and A, B button. Show a secret mode you want!
Swift
43
star
23

ArtShredder

📱Banksy Shredder for iOS
Swift
42
star
24

SplittableViewKit

A cell of IndexPath(row: 0, section: 0) in UITableView is automatically moved to left view when device rotated.
Swift
37
star
25

UILayoutBuilder

An AutoLayout DSL that intuitive syntax and viewable hierarchy.
Swift
37
star
26

SAWaveToast

Show text with wave animated background and floating animation.
Swift
34
star
27

TicTacToe-SwiftUI

Unidirectional data flow tic-tac-toe sample with SwiftUI.
Swift
23
star
28

SafeAreaExtension

You can handle safeAreaInsets changes of every UIView.
Swift
20
star
29

SABlurImageViewObjc

Objective-C
19
star
30

AnyObservableObject

Swift
18
star
31

ReuseCellConfigure

You can configure ReusableCell without casting!
Swift
17
star
32

GitHubClientTestSample

A GitHub client sample for User Action Logs testing.
Swift
17
star
33

MOAlertController

Objective-C
15
star
34

ConnpassAttendanceChecker

An attendance checking iOS application for https://connpass.com
Swift
14
star
35

SimplestCounterSample

This is simplest Counter App written in many variety of design patterns.
Swift
14
star
36

MyFirstAndroidPractice

I had began learning Android App development since Aug 2021. This is my first Android app project for practice development.
Kotlin
11
star
37

GithubKitForSample

Swift
11
star
38

DiffMVPAndMVVM

MVP and MVVM implementations and test code sample.
Swift
9
star
39

YouTubeSampleWithMVP-DiffableDataSource

This is a YouTube like iOS app sample with the Model-View-Presenter pattern.
Swift
9
star
40

Connpass

Swift
8
star
41

LayoutGuaranteedView

LayoutGuaranteedView is a Phantom Type view holder. It guaranteed that a view had laid out after execute `guaranteeLayout` function.
Swift
8
star
42

Necom

🐈 New concept of MVP with Swift
Swift
7
star
43

RxAnyDispatcher

A observer(or observable)-only `Dispatcher` that is used in Flux.
Swift
7
star
44

kmm-with-spm-buildtoolplugin-sample

Kotlin
6
star
45

QiitaApiClient

API client for http://qiita.com/.
Swift
6
star
46

LazyInitializedViewModelInjectionSample

Swift
4
star
47

iOSDC2022LightningTalkSample

Swift
3
star
48

unio-kt

🔄 kProperty based Unidirectional Input / Output framework with Flow.
Kotlin
2
star
49

ReusableView

Swift
1
star
50

LayeredLayout

Swift
1
star
51

ViewIsolatedViewController

Swift
1
star