Dynamic and type-safe framework for building linear and non-linear flows.

Last update: Aug 4, 2022

FlowKit

Swift Platforms CocoaPods Swift Package Manager Coverage

FlowKit is a dynamic flow framework capable of building a flow, based on conditions and ordered according to a logic of next steps. By using FlowKit it's possible to create flows for specific users or cases inside your app. Along with FlowKit there is FlowKitAdditions, a basic implementation that would allow you to use FlowKit directly without coding.

Features

Dynamic flows

An application that uses FlowKit is able to receive the Flow remotely in the form of JSON from a Back-end layer, which will contain all the information necessary to generate all possible specific flows and send it to the application. By having all the conditions and the possible flows generation in one single place, it will be easier to test them. Receiving the flow remotely is only an option: it is possible to save the Flow JSON locally in the device and load it as well, or create it programmatically in the code.

Type safe

The compiler will advise you in case that you mix the things. The compiler will be your best friend.

Easy to extend

The Step could be easily extended, allowing a custom step base object, meanwhile the required properties were provided.

Non-linear flows

FlowKit allows you to create non-linear flows by specifying multiple nextSteps or using DisplayConditions in a step.

Flow output typed

Define a flow output that FlowKit will use to generate a typed output with autocompletion support while you are developing.

Steps

Create your flow steps with dynamic content and control step display and output.

How to use it

1. Define your flow

Define your flow using FlowDefinition

struct YourFlowDefinition: FlowDefinition {
    typealias OUTPUT = YourFlowOutput
    typealias STEP = YourStepImplementation
}

where OUTPUT is your flow output type and STEP is your step implementation (You could use the one provided in FlowKitAdditions)

2. Create your steps

A StepHandler is in charge of building the step, notify with the completion that the step is completed and flow should show next step If your step is a screen, you should place the logic to present it there.

First you have to define your step, so create a struct that conforms to StepHandlerDefinition. It will force you to define the content, the step output and the flow output.

struct YourStepHandlerDefinition: StepHandlerDefinition {
    typealias CONTENT = YourContent
    typealias STEP_OUTPUT = YourStepOutput
    typealias FLOW_OUTPUT = YourFlowOutputDefinition
    typealias STEP = YourStepImplementation
    static let registerOutputKeyPath: KeyPath<FLOW_OUTPUT, STEP_OUTPUT>?
}
  • The FLOW_OUTPUT and STEP types must be the same as the once defined in YourFlowDefinition.
  • The STEP_OUTPUT is your step output type. If your step doesn't have an output, use the StepOutputEmpty.
  • The CONTENT is the dynamic content of the step, where you should place all the specific data that your step needs to be performed. For example, the image URL or text content we want to display in our step.

If your step has defined an output that you would like to set in YourFlowOutputDefinition you have to specify it.

static let registerOutputKeyPath: KeyPath<FLOW_OUTPUT, STEP_OUTPUT>? = \YourFlowOutputDefinition.yourProperty

Then you can create your step handler with the following sugar syntax:

  • Your step has an output
StepHandler<YourStepHandlerDefinition>.create { _, _, _, _, _ in }
  • Your step doesn't have an output
StepHandler<YourStepHandlerDefinition>.createWithEmptyOutput { _, _, _, _, _ in }

It would allow you to complete the step with a completion without type, completion()

3. Implement your step factory

The step factory is in charge of creating the concrete step handler for each step. The OUTPUT and STEP have to be the same that type in the Flow definition.

struct YourFactory: StepFactory {
    typealias OUTPUT = YourFlowOutput
    typealias STEP = YourStepInfo

    func makeHandler(for stepInfo: StepInfo) -> AnyStepHandler<OUTPUT, STEP>? {
        //Your steps
        if stepInfo.type == "your step type" {
            return AnyStepHandler(createYourStepHandler())
        }
        
        return nil
    }
    
    func createYourStepHandler() -> StepHandler<YourStepHandlerDefinition> {
        .create { stepInfo, content, navigation, output, completion in
            // Your concrete step code
        }
    }
}

4. Create your flow

To start creating a flow, we need the FlowData. Retrieve the FlowData from your Backend or create it locally in your app.

let flowData = FlowData<YourStep>(id: "idFlow", initialStepId: "stepId1", stepsInfo: [YourStepInfo])

Once we have our StepFactory and FlowData we can create our flow:

let flow = FlowKit<YourFlowDefinition>(flowData: flowData, featureStepFactory: YourFactory())

Wherever you want to start the flow, you have to call the method start

flow.start(
    on: navigationController,
    onFinish: { flowOutput in
        //You have the output of the whole flow.
        //Add here you stuff.
    }
)

5. How to use the flow output typed

To use the typed output, you have to provide a struct that conforms to FlowOutputDefinition.

struct ConcreteFlowOutput: FlowOutputDefinition {
    let name: String
    let birthday: Date
    let address: Address
}

Then you will be able to use it in your steps and in your flow output with sugar syntax

StepHandler<YourStepHandlerDefinition>.create { _, _, controller, output, closure in
    print(output.name)
    print(output.address?.city)
    //Add your stuff here
}

// Flow output
flow.start(
    on: navigationController,
    onFinish: { flowOutput in
        //You have the output of the whole flow.
        flowOutput.name
        //Add here you stuff.
    }
)

But before you could use it with value, a previous step should set the value

//Define your step
struct YourStepHandlerDefinition: StepHandlerDefinition {
    typealias CONTENT = YourContent
    typealias STEP_OUTPUT = YourStepOutput
    typealias FLOW_OUTPUT = YourFlowOutputDefinition
    typealias STEP = YourStep
    static let registerOutputKeyPath: KeyPath<FLOW_OUTPUT, STEP_OUTPUT>? = \YourFlowOutputDefinition.yourProperty
}

StepHandler<YourStepHandlerDefinition>.create { _, _, _, _, closure in
    closure(Value)
}

You could always access the previous step or flow output through a dictionary, but remember that you have to know the stepId to unwrap and cast the value.

// StepHandler
StepHandler<YourStepHandlerDefinition>.create { _, _, controller, output, closure in
    output.rawData["stepId"] as? String
    output.rawData["stepId2"] as? Address
    //Add your stuff here
}

// Flow output
flow.start(
    on: navigationController,
    onFinish: { flowOutput in
        //You have the output of the whole flow.
        flowOutput.rawData["stepId"] as? String
        //Add here you stuff.
    }
)

How to install

CocoaPods

Add flowkit-ios to your Podfile:

pod 'flowkit-ios'

Swift package manager

Add the flowkit-ios dependency in your project Package.swift file.

dependencies: [
    .package(url: "https://github.com/n26/flowkit-ios.git"),
],
targets: [
    .target(
        name: "YOUR_MODULE",
        dependencies: [
            "FlowKit"
        ])
],

License

Copyright (c) 2022 N26 GmbH, licensed under the MIT license.

Contributing

Contributions are welcome and appreciated. Check CONTRIBUTING for information on how to contribute.

GitHub

https://github.com/n26/flowkit-ios
You might also like...

🌾 Harvest: Apple's Combine.framework + State Machine, inspired by Elm.

🌾 Harvest: Apple's Combine.framework + State Machine, inspired by Elm.

NOTE: This repository has been discontinued in favor of Actomaton. 🌾 Harvest Apple's Combine.framework (from iOS 13) + State Machine, inspired by Elm

Apr 26, 2022

A lightweight, event-driven architectural framework

Trellis Trellis features a declarative DSL that simplifies service bootstrapping: let cluster = try await Bootstrap { Group { Store(model:

Jun 9, 2022

RxSwift extentions for Swift optionals and "Occupiable" types

RxOptional RxSwift extentions for Swift optionals and "Occupiable" types. Usage All operators are available on Driver as well unless otherwise marked.

Jun 28, 2020

🤖 RxSwift + State Machine, inspired by Redux and Elm.

🤖 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

Jun 29, 2022

Simple and lightweight Functional Reactive Coding in Swift for the rest of us

Simple and lightweight Functional Reactive Coding in Swift for the rest of us

The simplest ObservableT implementation for Functional Reactive Programming you will ever find. This library does not use the term FRP (Functional R

Jun 29, 2022

Lightweight observations and bindings in Swift

Lightweight observations and bindings in Swift

What is Hanson? Hanson is a simple, lightweight library to observe and bind values in Swift. It's been developed to support the MVVM architecture in o

Dec 27, 2021

A configurable api client based on Alamofire4 and RxSwift4 for iOS

SimpleApiClient A configurable api client based on Alamofire4 and RxSwift4 for iOS Requirements iOS 8.0+ Swift 4 Table of Contents Basic Usage Unwrap

Dec 7, 2020

STDevRxExt contains some extension functions for RxSwift and RxCocoa which makes our live easy.

STDevRxExt Example To run the Example.playground, clone the repo, and run pod install from the Example directory first. Requirements iOS 9.0+ tvOS 9.0

Mar 26, 2021

RxAlamoRecord combines the power of the AlamoRecord and RxSwift libraries to create a networking layer that makes interacting with API's easier than ever reactively.

RxAlamoRecord combines the power of the AlamoRecord and RxSwift libraries to create a networking layer that makes interacting with API's easier than ever reactively.

Written in Swift 5 RxAlamoRecord combines the power of the AlamoRecord and RxSwift libraries to create a networking layer that makes interacting with

Aug 7, 2020
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

Jun 24, 2022
Cocoa framework and Obj-C dynamism bindings for ReactiveSwift.
Cocoa framework and Obj-C dynamism bindings for ReactiveSwift.

Reactive extensions to Cocoa frameworks, built on top of ReactiveSwift. ⚠️ Looking for the Objective-C API? ?? Migrating from RAC 4.x? ?? Release Road

Aug 5, 2022
Binding - Data binding framework (view model binding on MVVM) written using propertyWrapper and resultBuilder

Binding Data binding framework (view model binding on MVVM) written using @prope

Mar 23, 2022
An observables framework for Swift
An observables framework for Swift

?? snail A lightweight observables framework, also available in Kotlin Installation Carthage You can install Carthage with Homebrew using the followin

Jul 21, 2022
Two-way data binding framework for iOS. Only one API to learn.

BindKit A simple to use two-way data binding framework for iOS. Only one API to learn. Supports Objective-C, Swift 5, Xcode 10.2, iOS 8 and above. Shi

May 25, 2022
RxReduce is a lightweight framework that ease the implementation of a state container pattern in a Reactive Programming compliant way.
RxReduce is a lightweight framework that ease the implementation of a state container pattern in a Reactive Programming compliant way.

About Architecture concerns RxReduce Installation The key principles How to use RxReduce Tools and dependencies Travis CI Frameworks Platform Licence

Jan 29, 2022
A Swift framework for reactive programming.

CwlSignal An implementation of reactive programming. For details, see the article on Cocoa with Love, CwlSignal, a library for reactive programming. N

May 31, 2022
Open source implementation of Apple's Combine framework for processing values over time.
Open source implementation of Apple's Combine framework for processing values over time.

OpenCombine Open-source implementation of Apple's Combine framework for processing values over time. The main goal of this project is to provide a com

Aug 3, 2022
Aftermath is a stateless message-driven micro-framework in Swift
Aftermath is a stateless message-driven micro-framework in Swift

Aftermath is a stateless message-driven micro-framework in Swift, which is based on the concept of the unidirectional data flow architecture.

Dec 24, 2021
This Repository holds learning data on Combine Framework

Combine Framework List of Topics Welcome, every section in this repo contains a collection of exercises demonstrating combine's utilization as well as

Mar 17, 2022