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.
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.
The compiler
will advise you in case that you mix the things. The compiler will be your best friend.
The Step could be easily extended, allowing a custom step base object, meanwhile the required properties were provided.
FlowKit allows you to create non-linear flows by specifying multiple nextSteps
or using DisplayConditions
in a step.
Define a flow output that FlowKit will use to generate a typed output with autocompletion support while you are developing.
Create your flow steps with dynamic content and control step display and output.
We have added an Example project to demostrate how to use FlowKit in your project.
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
)
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()
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
}
}
}
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
and inject a UINavigationController
flow.start(
on: navigationController,
onFinish: { flowOutput in
//You have the output of the whole flow.
//Add here you stuff.
}
)
To present a Flow in SwiftUI we create our FlowKitSwiftUIViewModel
using StepFactory
and FlowData
let flowViewModel = FlowKitSwiftUIViewModel<YourFlowDefinition>(
flowData: flowData,
featureStepFactory: YourFactory()
onFinish: { flowOutput, navigation in
//You have the output of the whole flow.
//Add here you stuff.
}
)
To present the flow modally on SwiftUI we use the FlowKitView
.sheet(isPresented: $presentFlow) {
FlowKitSwiftUIView<YourFlowDefinition>(viewModel: flowViewModel)
}
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.
}
)
Add flowkit-ios to your Podfile:
pod 'flowkit-ios'
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"
])
],
Copyright (c) 2023 N26 AG, licensed under the MIT license.
Contributions are welcome and appreciated. Check CONTRIBUTING for information on how to contribute.