Logic Engine
Define app behavior as data in your Runtime Bundle. Control navigation flows, conditional logic, and event handling from the server — without changing Dart code.
What the Logic Engine does
The Logic Engine lets you define event-driven flows in your Runtime Bundle. When a KoolbaseDynamicScreen emits an event, the SDK automatically checks if a matching flow exists in the active bundle and evaluates it. The flow can branch on conditions, set context values, and emit a new event back to your app.
You can also call flows directly from anywhere in your app using Koolbase.executeFlow().
What the Logic Engine can control
- Conditional navigation — show different screens based on user state
- Plan-based gating — redirect free users to upgrade modal
- Context mutation — set values before emitting events
- Multi-step sequences — chain conditions and actions
- Arbitrary Dart code execution (requires a store release)
- Network calls or side effects inside flows
How it works
- You define flows in a
flows.jsonfile in your bundle directory - The CLI packages it into the Runtime Bundle alongside config, flags, and screens
- When a
KoolbaseDynamicScreenemits an event, the SDK looks up a flow with the same ID - The
FlowExecutorevaluates the node tree against the current context - If the flow emits an event, that event is passed to your
onEventhandler instead of the original
Merge precedence
onEvent handler. If a flow emits a new event, that replaces the original. If no flow exists for an event ID, the original event is passed through unchanged.Bundle format
Add a flows.json file to your bundle directory. Each key is a flow ID that matches an event name from your rfw screens.
// bundle/flows.json
{
"on_checkout_tap": {
"type": "if",
"condition": {
"op": "eq",
"left": { "from": "context.plan" },
"right": "free"
},
"then": { "type": "event", "name": "show_upgrade" },
"else": { "type": "event", "name": "go_checkout" }
},
"on_load": {
"type": "if",
"condition": {
"op": "exists",
"value": { "from": "context.user" }
},
"then": { "type": "event", "name": "show_dashboard" },
"else": { "type": "event", "name": "show_login" }
}
}Node types
| Type | Description |
|---|---|
if | Branch on a condition. Evaluates then or else node. |
sequence | Run steps in order. Stops at first terminal event. |
event | Emit a named event. Terminal — execution stops here. |
set | Set a value in the mutable context. Non-terminal. |
Operators
| Operator | Description |
|---|---|
eq | Equals — left == right (string comparison) |
neq | Not equals — left != right |
gt | Greater than — left > right (numeric) |
gte | Greater than or equals — left >= right (numeric) |
lt | Less than — left < right (numeric) |
lte | Less than or equals — left <= right (numeric) |
contains | String or list contains value |
starts_with | String starts with value |
ends_with | String ends with value |
in_list | Value is in a list — right: ["a", "b", "c"] |
not_in_list | Value is not in a list |
between | Numeric value in range — right: [min, max] |
is_true | Value is boolean true |
is_false | Value is boolean false |
exists | Value is not null or undefined |
not_exists | Value is null or undefined |
and | All sub-conditions must be true |
or | At least one sub-condition must be true |
Data sources
Conditions read values from three sources using dot notation:
| Source | Example | Description |
|---|---|---|
context | { "from": "context.plan" } | App-provided data passed to executeFlow() |
config | { "from": "config.timeout" } | Active bundle config values |
flags | { "from": "flags.new_checkout" } | Active bundle flag values |
Automatic execution via KoolbaseDynamicScreen
When you wrap a screen in KoolbaseDynamicScreen, flows execute automatically. If the rfw widget emits an event named on_checkout_tap and a flow with that ID exists in the bundle, the SDK evaluates it and passes the result to your onEvent handler.
KoolbaseDynamicScreen(
screenId: 'checkout',
data: {
'plan': user.plan, // available as context.plan
'cart_total': cart.total, // available as context.cart_total
},
onEvent: (eventName, args) {
// eventName may have been transformed by a flow
switch (eventName) {
case 'show_upgrade':
Navigator.pushNamed(context, '/upgrade');
case 'go_checkout':
Navigator.pushNamed(context, '/checkout');
}
},
fallback: const CheckoutScreen(),
)Direct execution
Call flows directly from anywhere in your app — not just from dynamic screens:
final result = Koolbase.executeFlow(
flowId: 'on_checkout_tap',
context: {
'plan': user.plan,
'cart_total': cart.total,
},
);
if (result.hasEvent) {
print('Flow emitted: ${result.eventName}');
// Handle result.eventName
}
if (!result.completed) {
print('Flow error: ${result.error}');
}Always safe
executeFlow() never throws. If the flow ID doesn't exist, the bundle is inactive, or evaluation fails — it returns a safe FlowResult with hasEvent: false.Full example
A complete flow using sequence, set, and conditional branching:
{
"on_checkout_tap": {
"type": "if",
"condition": {
"op": "and",
"conditions": [
{
"op": "gte",
"left": { "from": "context.usage" },
"right": 5
},
{
"op": "in_list",
"left": { "from": "context.plan" },
"right": ["free", "trial"]
}
]
},
"then": { "type": "event", "name": "show_upgrade" },
"else": { "type": "event", "name": "go_checkout" }
},
"on_load": {
"type": "if",
"condition": {
"op": "between",
"left": { "from": "context.days_inactive" },
"right": [7, 30]
},
"then": { "type": "event", "name": "show_retention_message" },
"else": { "type": "event", "name": "show_dashboard" }
}
}API reference
| Method / Property | Description |
|---|---|
Koolbase.executeFlow(flowId, context) | Execute a named flow. Returns FlowResult. Never throws. |
Koolbase.codePush.executeFlow(flowId, context) | Direct client access. Same behaviour. |
FlowResult.hasEvent | True if the flow emitted a terminal event. |
FlowResult.eventName | The emitted event name, or null. |
FlowResult.args | Args attached to the emitted event. |
FlowResult.completed | True if evaluation succeeded without errors. |
FlowResult.error | Error message if evaluation failed, or null. |