{"id":2130781,"url":"https://github.com/marty-suzuki/Prex","last_synced_at":"2025-07-13T11:30:56.349Z","repository":{"id":62451085,"uuid":"150768542","full_name":"marty-suzuki/Prex","owner":"marty-suzuki","description":"🔁Unidirectional data flow architecture with MVP and Flux combination for Swift ","archived":false,"fork":false,"pushed_at":"2020-01-22T19:06:50.000Z","size":1787,"stargazers_count":109,"open_issues_count":0,"forks_count":11,"subscribers_count":7,"default_branch":"master","last_synced_at":"2025-06-16T05:51:23.909Z","etag":null,"topics":["flux","ios","macos","mvp","swift","tvos","watchos"],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/marty-suzuki.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-09-28T16:41:04.000Z","updated_at":"2024-05-30T15:48:37.000Z","dependencies_parsed_at":"2022-11-02T01:01:36.855Z","dependency_job_id":null,"html_url":"https://github.com/marty-suzuki/Prex","commit_stats":null,"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/marty-suzuki/Prex","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marty-suzuki%2FPrex","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marty-suzuki%2FPrex/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marty-suzuki%2FPrex/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marty-suzuki%2FPrex/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/marty-suzuki","download_url":"https://codeload.github.com/marty-suzuki/Prex/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marty-suzuki%2FPrex/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":265131552,"owners_count":23716028,"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","ios","macos","mvp","swift","tvos","watchos"],"created_at":"2024-01-21T23:26:51.328Z","updated_at":"2025-07-13T11:30:55.986Z","avatar_url":"https://github.com/marty-suzuki.png","language":"Swift","funding_links":[],"categories":["HarmonyOS"],"sub_categories":["Windows Manager"],"readme":"\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"./Images/prex.png\"\u003e\n\u003c/p\u003e\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"http://img.shields.io/badge/platform-iOS%20|%20tvOS%20|%20macOS%20|%20watchOS-blue.svg?style=flat\" alt=\"Platform\" /\u003e\n  \u003ca href=\"https://developer.apple.com/swift\"\u003e\n    \u003cimg src=\"http://img.shields.io/badge/Swift-4.1%20|%204.2-brightgreen.svg?style=flat\" alt=\"Language\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://github.com/Carthage/Carthage\"\u003e\n    \u003cimg src=\"https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat\" alt=\"Carthage\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://cocoapods.org/pods/Prex\"\u003e\n    \u003cimg src=\"https://img.shields.io/cocoapods/v/Prex.svg?style=flat\" alt=\"Version\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://cocoapods.org/pods/Prex\"\u003e\n    \u003cimg src=\"https://img.shields.io/cocoapods/l/Prex.svg?style=flat\" alt=\"License\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://travis-ci.org/marty-suzuki/Prex\"\u003e\n    \u003cimg src=\"https://img.shields.io/travis/marty-suzuki/Prex.svg?style=flat\" alt=\"CI Status\"\u003e\n  \u003c/a\u003e\n\u003c/p\u003e\n\nPrex is a framework which makes an unidirectional data flow application possible with MVP architecture.\n\n## Concept\n\nPrex represents **Pre**senter + Flu**x**, therefore it is a combination of Flux and MVP architecture.\nIn addition, Reactive frameworks are not used in Prex.\nTo reflect a state to a view, using **Passive View Pattern**.\nFlux are used behind of the Presenter.\nData flow is unidirectional that like a below figure.\n\n![](./Images/data-flow.png)\n\nIf you use Prex, you have to implement those components.\n\n- [State](#state)\n- [Action](#action)\n- [Mutation](#mutation)\n- [Presenter](#presenter)\n- [View](#view)\n\n### State\n\nThe State has properties to use in the View and the Presenter.\n\n```swift\nstruct CounterState: State {\n    var count: Int = 0\n}\n```\n\n### Action\n\nThe Action represents internal API of your application.\nFor example, if you want to increment the count of CounterState, dispatch Action.increment to Dispatcher.\n\n```swift\nenum CounterAction: Action {\n    case increment\n    case decrement\n}\n```\n\n### Mutation\n\nThe Mutation is allowed to mutate the State with the Action.\n\n```swift\nstruct CounterMutation: Mutation {\n    func mutate(action: CounterAction, state: inout CounterState) {\n        switch action {\n        case .increment:\n            state.count += 1\n\n        case .decrement:\n            state.count -= 1\n        }\n    }\n}\n```\n\n### Presenter\n\nThe Presenter has a role to connect between View and Flux components.\nIf you want to access side effect (API access and so on), you must access them in the Presenter.\nFinally, you dispatch those results with `Presenter.dispatch(_:)`.\n\n```swift\nextension Presenter where Action == CounterAction, State == CounterState {\n    func increment() {\n        dispatch(.increment)\n    }\n\n    func decrement() {\n        if state.count \u003e 0 {\n            dispatch(.decrement)\n        }\n    }\n}\n```\n\n### View\n\nThe View displays the State with `View.reflect(change:)`.\nIt is called by the Presenter when the State has changed.\nIn addition, it calls the Presenter methods by User interactions.\n\n```swift\nfinal class CounterViewController: UIViewController {\n    private let counterLabel: UILabel\n    private lazy var presenter = Presenter(view: self,\n                                           state: CounterState(),\n                                           mutation: CounterMutation())\n\n    @objc private func incrementButtonTap(_ button: UIButton) {\n        presenter.increment()\n    }\n\n    @objc private func decrementButtonTap(_ button: UIButton) {\n        presenter.decrement()\n    }\n}\n\nextension CounterViewController: View {\n    func reflect(change: StateChange\u003cCounterState\u003e) {\n        if let count = change.count?.value {\n            counterLabel.text = \"\\(count)\"\n        }\n    }\n}\n```\n\nYou can get only specified value that has changed in the State from `StateChange.changedProperty(for:)`.\n\n## Advanced Usage\n\n### Shared Store\n\nInitializers of the Store and the Dispatcher are not public access level.\nBut you can initialize them with `Flux` and inject them with `Presenter.init(view:flux:)`.\n\nThis is shared Flux components example.\n\n```swift\nextension Flux where Action == CounterAction, State == CounterState {\n    static let shared = Flux(state: CounterState(), mutation: CounterMutation())\n}\n```\n\nor\n\n```swift\nenum SharedFlux {\n    static let counter = Flux(state: CounterState(), mutation: CounterMutation())\n}\n```\n\nInject `Flux` like this.\n\n```swift\nfinal class CounterViewController: UIViewController {\n    private lazy var presenter = {\n        let flux =  Flux\u003cCounterAction, CounterState\u003e.shared\n        return Presenter(view: self, flux: flux)\n    }()\n}\n```\n\n### Presenter Subclass\n\nThe Presenter is class that has generic parameters.\nYou can create the Presenter subclass like this.\n\n```swift\nfinal class CounterPresenter: Presenter\u003cCounterAction, CounterState\u003e {\n    init\u003cT: View\u003e(view: T) where T.State == CounterState {\n        let flux = Flux(state: CounterState(), mutation: CounterMutation())\n        super.init(view: view, flux: flux)\n    }\n\n    func increment() {\n        dispatch(.increment)\n    }\n\n    func decrement() {\n        if state.count \u003e 0 {\n            dispatch(.decrement)\n        }\n    }\n}\n```\n\n### Testing\n\nI'll explain how to test with Prex.\nFocus on two test cases in this document.\n\n| 1. Reflection state testing | 2. Create actions testing |\n| :-: | :-: |\n| ![](./Images/reflection-test.png) | ![](./Images/func-test.png) |\n\nBoth tests need the View to initialize a Presenter.\nYou can create **MockView** like this.\n\n```swift\nfinal class MockView: View {\n    var refrectParameters: ((StateChange\u003cCounterState\u003e) -\u003e ())?\n\n    func reflect(change: StateChange\u003cCounterState\u003e) {\n        refrectParameters?(change)\n    }\n}\n```\n\n\n\n#### 1. Reflection state testing\n\nThis test starts with dispatching an Action.\nAn action is passed to Mutation, and Mutation mutates state with a received action.\nThe Store notifies changes of state, and the Presenter calls reflect method of the View to reflects state.\nFinally, receives state via reflect method parameters of the View.\n\nThis is a sample test code.\n\n```swift\nfunc test_presenter_calls_reflect_of_view_when_state_changed() {\n    let view = MockView()\n    let flux = Flux(state: CounterState(), mutation: CounterMutation())\n    let presenter = Presenter(view: view, flux: flux)\n\n    let expect = expectation(description: \"wait receiving ValueChange\")\n    view.refrectParameters = { change in\n        let count = change.changedProperty(for: \\.count)?.value\n        XCTAssertEqual(count, 1)\n        expect.fulfill()\n    }\n\n    flux.dispatcher.dispatch(.increment)\n    wait(for: [expect], timeout: 0.1)\n}\n```\n\n#### 2. Create actions testing\n\nThis test starts with calling the Presenter method as dummy user interaction.\nThe Presenter accesses side-effect and finally creates an action from that result.\nThat action is dispatched to the Dispatcher.\nFinally, receives action via register callback of the Dispatcher.\n\nThis is a sample test code.\n\n```swift\nfunc test_increment_method_of_presenter() {\n    let view = MockView()\n    let flux = Flux(state: CounterState(), mutation: CounterMutation())\n    let presenter = Presenter(view: view, flux: flux)\n\n    let expect = expectation(description: \"wait receiving ValueChange\")\n    let subscription = flux.dispatcher.register { action in\n        XCTAssertEqual(action, .increment)\n        expect.fulfill()\n    }\n\n    presenter.increment()\n    wait(for: [expect], timeout: 0.1)\n    flux.dispatcher.unregister(subscription)\n}\n```\n\n#### An addition\n\nYou can test mutating state like this.\n\n```swift\nfunc test_mutation() {\n    var state = CounterState()\n    let mutation = CounterMutation()\n\n    mutation.mutate(action: .increment, state: \u0026state)\n    XCTAssertEqual(state.count, 1)\n\n    mutation.mutate(action: .decrement, state: \u0026state)\n    XCTAssertEqual(state.count, 0)\n}\n```\n\n## Example\n\n### Project\n\nYou can try Prex with GitHub Repository Search Application [Example](./Example).\nOpen PrexSample.xcworkspace and run it!\n\n### Playground\n\nYou can try Prex counter sample with Playground!\nOpen Prex.xcworkspace and build `Prex-iOS`.\nFinally, you can run manually in Playground.\n\n![](./Images/playground.png)\n\n## Requirements\n- Xcode 9.4.1 or greater\n- iOS 10.0 or greater\n- tvOS 10.0 or greater\n- macOS 10.10 or greater\n- watchOS 3.0 or greater\n- Swift 4.1 or greater\n\n## Installation\n\n### Carthage\n\nIf you’re using [Carthage](https://github.com/Carthage/Carthage), simply add Prex to your `Cartfile`:\n\n```ruby\ngithub \"marty-suzuki/Prex\"\n```\n\n### CocoaPods\n\nPrex is available through [CocoaPods](https://cocoapods.org). To install it, simply add the following line to your Podfile:\n\n```ruby\npod 'Prex'\n```\n\n### Swift Package Manager\n\nPrex is available through `Swift Package Manager`. Just add the url of this repository to your `Package.swift`.\n\n```Package.swift\ndependencies: [\n    .package(url: \"https://github.com/marty-suzuki/Prex.git\", from: \"0.2.0\")\n]\n```\n\n## Inspired by these unidirectional data flow frameworks\n\n- [VueFlux](https://github.com/ra1028/VueFlux) by [@ra1028](https://github.com/ra1028/VueFlux)\n- [ReactorKit](https://github.com/ReactorKit/ReactorKit) by [@devxoul](https://github.com/devxoul)\n\n## Author\n\nmarty-suzuki, s1180183@gmail.com\n\n## License\n\nPrex is available under the MIT license. See the [LICENSE](./LICENSE) file for more info.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmarty-suzuki%2FPrex","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmarty-suzuki%2FPrex","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmarty-suzuki%2FPrex/lists"}