AsyncRedux — Asynchronous Redux Framework for Swift

Introduction

AsyncRedux is a lightweight, asynchronous Redux framework for Swift. It simplifies state management in your Swift applications. Whether you are using SwiftUI or UIKit, AsyncRedux offers built-in support for action creators, allowing for a clean, easy-to-understand data flow.

Usage Example

Here’s a complete example that uses AsyncRedux in a SwiftUI application to manage a simple chat view:

import AsyncRedux

struct ChatView: View {
    
    // obtain state storage from the environment
    @EnvironmentObject var stateStorage: StateStorage
    @State private var newMessage: String = ""
    @State private var messages: [String] = []
    
    var body: some View {
        VStack {
            // display messages
            LazyVStack {
                ForEach(messages) { message in
                    Text(message)
                }
            }
            TextField("Enter message...", text: $newMessage)
            Button {
                // dispatch `sendMessage` action
                stateStorage.reduxDispatcher.dispatch(action: .sendMessage(newMessage))
                newMessage = ""
            } label: {
                Text("Send")
            }
        }.task {
            // observe state change in Redux stack and update 
            for await state in stateStorage.observeState()  {
                messages = state.messages
            }
        }
    }
}

Installation

To install AsyncRedux, you can use Swift Package Manager. Add the following line to your Package.swift file:

dependencies: [
    .package(url: "hhttps://github.com/andrwturk/AsyncRedux.git", .upToNextMajor(from: "1.0.0"))
]

Setting up core Redux components

After you’ve installed AsyncRedux, you can quickly get started by importing it:

import AsyncRedux

Set up your state and actions:

// Create feature state
struct State {
    // define feature state fields
    let messages: [String]
}

// Create actions
struct Action {
    case fetchMessages([String])
    case sendMessage(String)
}

Then create action creators and state reducer:

class FetchMessagesActionCreator: RecursiveActionCreator {
    
    // implement effect which depends on state and returns action
    func observeActions(stateObservable: AnyAsyncSequence<State>) -> AnyAsyncSequence<Action> {
            stateObservable
            .compactMap { state -> ChatAction? in
                // define effect for specific state — no messages
                if state.messages.isEmpty { 
                    // fetch messages from repository
                    return .addMessages([MESSAGES])
                    
                // skip when messages already loaded
                } else {
                    return nil
                }
            }.toAny()
    }
}

// add reducer
final class StateReducer {
    static func invoke(state: State,
                       action: Action) -> State {
        switch action {
        case .fetchMessages(let messages):
            return ChatState(messages: messages)
        case .sendMessage(let message):
            return ChatState(messages: state.messages + [message])
        }
    }
}

Finally, set up dispatcher and state storage:

// add dispatcher
var dispatcher = ReduxDispatcher<State, Action>(
        recursiveActionCreators: [FetchMessagesActionCreator().toAny()],
        actionCreators: [])
        
// add dispatcher to the storage
var storage = StateStorage(
        initialState: State(),
        reduxDispatcher: dispatcher,
        reducer: StateReducer.invoke(state:action:))

Example App: Building a Chat Application

In this repository, you will find a complete example application that demonstrates how to build a real-time chat application using the AsyncRedux framework. The example aims to showcase the core features and usage patterns of AsyncRedux in a more complex use-case.

Contributing

Your contributions are welcome! Feel free to open issues for feature requests or bug reports, and submit pull requests for new features or fixes. For major changes, it’s always good to open an issue first to discuss what you would like to change.

GitHub

View Github