💎 Redux like architecture for SwiftUI

Overview

logo

Platform Swift License Platform Version

Simple Architecture like Redux

Installation

SPM

dependencies: [
    .package(url: "https://github.com/gre4ixin/ReduxUI.git", .upToNextMinor(from: "1.0.0"))
]

Usage

import ReduxUI

class SomeCoordinator: Coordinator {
    func perform(_ route: SomeRoute) { }
}

enum SomeRoute: RouteType {

}

enum AppAction: AnyAction {
    case increase
    case decrease
}

struct AppState: AnyState {
    var counter: Int = 0
}

class AppReducer: Reducer {
    typealias Action = AppAction

    func reduce(_ state: inout AppState, action: AppAction, performRoute: @escaping ((_ route: SomeRoute) -> Void)) {
        switch action {
        case .increase:
            state.counter += 1
        case .decrease:
            state.counter -= 1
        }
    }
}

class ContentView: View {
    @EnvironmentObject var store: Store<AppState, AppAction, SomeRouter>

    var body: some View {
        VSTack {
            Text(store.state.counter)

            Button {
                store.dispatch(.increase)
            } label: {
                Text("increment")
            }

            Button {
                store.dispatch(.decrease)
            } label: {
                Text("decrement")
            }
        }
    }
}

class AppModuleAssembly {
    func build() -> some View {
        let reducer = AppReducer().eraseToAnyReducer()
        let coordinator = SomeCoordinator().eraseToAnyCoordinator()
        let store = Store(initialState: AppState(), coordinator: coordinator, reducer: reducer)
        let view = ContentView().environmentObject(store)
        return view
    }
}

That was very simple example, in real life you have to use network request, action in app state changes and many other features. In these cases you can use Middleware.

Middlewares calls after reducer function and return
 AnyPublisher<MiddlewareAction, Never>
For example create simple project who fetch users from https://jsonplaceholder.typicode.com/users.

Create DTO (Decode to object) model

struct UserDTO: Decodable, Equatable, Identifiable {
    let id: Int
    let name: String
    let username: String
    let phone: String
}

Equatable protocol for our state, Identifiable for ForEach generate view in SwiftUI View.

Simple network request without error checking
import Foundation
import Combine

protocol NetworkWrapperInterface {
    func request<D: Decodable>(path: URL, decode: D.Type) -> AnyPublisher<D, NetworkError>
}

struct NetworkError: Error {
    let response: URLResponse?
    let error: Error?
}

class NetworkWrapper: NetworkWrapperInterface {
    
    func request<D: Decodable>(path: URL, decode: D.Type) -> AnyPublisher<D, NetworkError> {
        return Deferred {
            Future<D, NetworkError> { promise in
                let request = URLRequest(url: path)
                URLSession.shared.dataTask(with: request) { [weak self] data, response, error in
                    guard let _ = self else { return }
                    if let _error = error {
                        promise(.failure(NetworkError(response: response, error: _error)))
                    }
                    
                    guard let unwrapData = data, let json = try? JSONDecoder().decode(decode, from: unwrapData) else {
                        promise(.failure(NetworkError(response: response, error: error)))
                        return
                    }
                    
                    promise(.success(json))
                    
                }.resume()
            }
        }.eraseToAnyPublisher()
    }
    
}
Make State, Action and Reducer
enum AppAction: AnyAction {
    case fetch
    case isLoading
    case loadingEnded
    case updateUsers([UserDTO])
    case error(message: String)
}

struct AppState: AnyState {
    var users: [UserDTO] = []
    var isLoading = false
    var errorMessage = ""
}

class AppReducer: Reducer {
    typealias Action = AppAction
    
    func reduce(_ state: inout AppState, action: AppAction, performRoute: @escaping ((RouteWrapperAction) -> Void)) {
        switch action {
        case .fetch:
            state.isLoading = true
            state.errorMessage = ""
        case .isLoading:
            state.isLoading = true
        case .loadingEnded:
            state.isLoading = false
        case .updateUsers(let users):
            state.users = users
            state.isLoading = false
            state.errorMessage = ""
        case .error(let message):
            state.errorMessage = message
        }
    }
}
Middleware for make network request and return users DTO.
class AppMiddleware: Middleware {
    typealias State = AppState
    typealias Action = AppAction
    typealias Router = RouteWrapperAction
    
    let networkWrapper: NetworkWrapperInterface
    
    var cancelabels = CombineBag()
    
    init(networkWrapper: NetworkWrapperInterface) {
        self.networkWrapper = networkWrapper
    }
    
    func execute(_ state: AppState, action: AppAction) -> AnyPublisher<MiddlewareAction<AppAction, RouteWrapperAction>, Never>? {
        switch action {
        case .fetch:
            return Deferred {
                Future<MiddlewareAction<AppAction, RouteWrapperAction>, Never> { [weak self] promise in
                    guard let self = self else { return }
                    self.networkWrapper
                        .request(path: URL(string: "https://jsonplaceholder.typicode.com/users")!, decode: [UserDTO].self)
                        .sink { result in
                            switch result {
                            case .finished: break
                            case .failure(let error):
                                promise(.success(.performAction(.error(message: "Something went wrong!"))))
                            }
                        } receiveValue: { dto in
                            promise(.success(.performAction(.updateUsers(dto))))
                        }.store(in: &self.cancelabels)
                }
            }.eraseToAnyPublisher()
        default:
            return nil
        }
    }
}

Content View

@EnvironmentObject var store: Store<AppState, AppAction, RouteWrapperAction>
    
var body: some View {
    VStack {
        ScrollView {
            ForEach(store.state.users) { user in
                HStack {
                    VStack {
                        Text(user.name)
                            .padding(.leading, 16)
                        Text(user.phone)
                            .padding(.leading, 16)
                    }
                    Spacer()
                }
                Divider()
            }
        }
        Spacer()
        if store.state.isLoading {
            Text("Loading")
        }
        
        if !store.state.errorMessage.isEmpty {
            Text(LocalizedStringKey(store.state.errorMessage))
        }
        
        Button {
            store.dispatch(.fetch)
        } label: {
            Text("fetch users")
        }
    }
}

When reducer ended his job with action, our store check all added middlewares for some Publishers for curent Action, if Publisher not nil, Store runing that Publisher.

You can return action for reducer and change some data, return action for routing, return .multiple actions.

case multiple([MiddlewareAction<A, R>])

You can return Deferred Action.

public protocol DeferredAction {
    associatedtype Action: AnyAction
    func observe() -> AnyPublisher<Action, Never>?
    
    func eraseToAnyDeferredAction() -> AnyDeferredAction<A>
}

If you want route to Authorization, when your Session Provider send event about dead you session, you can use it action. All you need that conform to protocol DeferredAction you class/struct and erase it to AnyDeferredAction with generic Action.

You might also like...
Flux for SwiftUI, inspired by Vuex
Flux for SwiftUI, inspired by Vuex

⚠️ Fluxus is no longer maintained, and may not be using latest SwiftUI best practices. 👉 I encourage you to look at the source of Fluxus. If you do,

An experimental time traveling state store for SwiftUI
An experimental time traveling state store for SwiftUI

SwiftUI Time Travel A SwiftUI state store and view that allow you to scrub through an application's state. This is a super rough prototype: it's only

Eazy is the missing piece in your SwiftUI and UIKit application.

Eazy is the missing piece in your SwiftUI and UIKit application. It aims at harmonizing how your views communicate with the model and vice versa in a clear and consistent way. Eazy can be used on any Apple platform.

ReSwift is a Redux-like implementation of the unidirectional data flow architecture in Swift.
ReSwift is a Redux-like implementation of the unidirectional data flow architecture in Swift.

ReSwift is a Redux-like implementation of the unidirectional data flow architecture in Swift. ReSwift helps you to separate three important concerns of your app's components.

REDUX like architecture sample for iOS

perdux_sample_swiftUI REDUX like architecture sample for iOS (target 14.*) Motivation: to share reactive architecture approach with single direction d

Fast Multi-store Redux-like architecture for iOS/OSX applications

Highway Highway is implementation of Redux-like architecture pattern using Swift. If you were looking for a something like this: TEA (The Elm Architec

Sample iOS application in SwiftUI presenting Redux architecture
Sample iOS application in SwiftUI presenting Redux architecture

SwiftUI-Redux-Demo Sample iOS application in SwiftUI presenting Redux architecture. My full article about Redux in detail you will find here: Redux ar

A powerful SwiftUI Architecture that merges Redux to the functional world of Swift. While bringing powerful workflows to streamline CoreML/Metal/IPFS usage in the Apple ecosystem.
A powerful SwiftUI Architecture that merges Redux to the functional world of Swift. While bringing powerful workflows to streamline CoreML/Metal/IPFS usage in the Apple ecosystem.

GraniteUI - v0.0 - WIP A powerful SwiftUI Architecture that merges Redux event handling and state management with functional programming. While bringi

SwiftUI Todo app example using a React/Redux monolithic state store with flux like dispatch/reduce actions
SwiftUI Todo app example using a React/Redux monolithic state store with flux like dispatch/reduce actions

SwiftUI-Todo-Redux SwiftUI Todo Redux app example using a React/Redux monolithic state store with flux like dispatch/reduce actions Background SwiftUI

A barebone, thread-safe Redux-like container for Swift.

SwiftTinyRedux SwiftTinyRedux is a barebone, thread-safe Redux-like container for Swift. It features a minimal API and supports composable reducers. I

Best architecture for SwiftUI + CombineBest architecture for SwiftUI + Combine

Best architecture for SwiftUI + Combine The content of the presentation: First of the proposed architectures - MVP + C Second of the proposed architec

Mvi Architecture for SwiftUI Apps. MVI is a unidirectional data flow architecture.
Mvi Architecture for SwiftUI Apps. MVI is a unidirectional data flow architecture.

Mvi-SwiftUI If you like to read this on Medium , you can find it here MVI Architecture for SwiftUI Apps MVI Architecture Model-View-Intent (MVI) is a

SwiftUI & Combine app using MovieDB API. With a custom Flux (Redux) implementation.
SwiftUI & Combine app using MovieDB API. With a custom Flux (Redux) implementation.

MovieSwiftUI MovieSwiftUI is an application that uses the MovieDB API and is built with SwiftUI. It demos some SwiftUI (& Combine) concepts. The goal

Simple Todo Application using SwiftUI / Firebase / Redux.
Simple Todo Application using SwiftUI / Firebase / Redux.

Simple Todo Application using SwiftUI/Firebase/Redux/Combine. Light _ _ _ _ Dark _ _ _ Feature Use SwiftUI fully. Use Firebase. Authentication Cloud F

🚀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.

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

Swift Apps in a Swoosh! A modern framework for creating iOS apps, inspired by Redux.
Swift Apps in a Swoosh! A modern framework for creating iOS apps, inspired by Redux.

Katana is a modern Swift framework for writing iOS applications' business logic that are testable and easy to reason about. Katana is strongly inspire

Tempura - A holistic approach to iOS development, inspired by Redux and MVVM
Tempura - A holistic approach to iOS development, inspired by Redux and MVVM

Tempura is a holistic approach to iOS development, it borrows concepts from Redux (through Katana) and MVVM. 🎯 Installation Requirements CocoaPods 🤔

Redux abstractions for BetterMe projects and iOS community

ReduxCore Redux abstractions on Swift for BetterMe projects and iOS community Installation CocoaPods You can install ReduxCore via CocoaPods by adding

A holistic approach to iOS development, inspired by Redux and MVVM
A holistic approach to iOS development, inspired by Redux and MVVM

Tempura is a holistic approach to iOS development, it borrows concepts from Redux (through Katana) and MVVM. 🎯 Installation Requirements CocoaPods Sw

Comments
Releases(1.0.0)
  • 1.0.0(Jun 18, 2022)

    What's Changed

    • [ImgBot] Optimize images by @imgbot in https://github.com/gre4ixin/ReduxUI/pull/1

    New Contributors

    • @imgbot made their first contribution in https://github.com/gre4ixin/ReduxUI/pull/1

    Full Changelog: https://github.com/gre4ixin/ReduxUI/compare/0.0.12...1.0.0

    Source code(tar.gz)
    Source code(zip)
Owner
Pavel
iOS Developer. telegram: @gre4ixin, mail: [email protected]
Pavel
A barebone, thread-safe Redux-like container for Swift.

SwiftTinyRedux SwiftTinyRedux is a barebone, thread-safe Redux-like container for Swift. It features a minimal API and supports composable reducers. I

Valentin Radu 9 Nov 13, 2022
Unidirectional Data Flow in Swift - Inspired by Redux

ReSwift Supported Swift Versions: Swift 4.2, 5.x For Swift 3.2 or 4.0 Support use Release 5.0.0 or earlier. For Swift 2.2 Support use Release 2.0.0 or

null 7.3k Dec 25, 2022
🤖 RxSwift + State Machine, inspired by Redux and Elm.

RxAutomaton RxSwift port of ReactiveAutomaton (State Machine). Terminology Whenever the word "signal" or "(signal) producer" appears (derived from Rea

Yasuhiro Inami 719 Nov 19, 2022
Redux for Swift - a predictable state container for Swift apps

Merge / deprecation announcement: ReduxKit and Swift-Flow have joined forces! The result is ReSwift. The nitty gritty: We decided to deprecate ReduxKi

null 613 Jan 3, 2023
Unidirectional Data Flow in Swift - Inspired by Redux

ReSwift Supported Swift Versions: Swift 4.2, 5.x For Swift 3.2 or 4.0 Support use Release 5.0.0 or earlier. For Swift 2.2 Support use Release 2.0.0 or

null 7.3k Jan 3, 2023
A powerful, minimal and composable architecture for building reactive iOS apps with SwiftUI or UIKit

SourceArchitecture A simple yet powerful framework for reactive programming with only a minimal optimized set of types. Sources are self-contained, hi

Daniel Hall 6 Nov 1, 2022
Unidirectional State Management Architecture for Swift - Inspired by Vuex and Flux

Unidirectional State Management Architecture for Swift - Inspired by Vuex and Flux Introduction VueFlux is the architecture to manage state with unidi

Ryo Aoyama 324 Dec 17, 2022
A lightweight Elm-like Store for SwiftUI

ObservableStore A simple Elm-like Store for SwiftUI, based on ObservableObject. ObservableStore helps you craft more reliable apps by centralizing all

Subconscious 28 Nov 8, 2022
🟣 Verge is a very tunable state-management engine on iOS App (UIKit / SwiftUI) and built-in ORM.

Verge.swift ?? An effective state management architecture for iOS - UIKit and also SwiftUI ?? _ An easier way to get unidirectional data flow _ _ Supp

VergeGroup 478 Dec 29, 2022
Netflix Onboarding made with SwiftUI

OnBoardSwiftUI-Netflix Netflix Onboarding made with SwiftUI.

Shreyas Bhike 22 Dec 21, 2022