Updated in v2.6.0

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

  1. You define flows in a flows.json file in your bundle directory
  2. The CLI packages it into the Runtime Bundle alongside config, flags, and screens
  3. When a KoolbaseDynamicScreen emits an event, the SDK looks up a flow with the same ID
  4. The FlowExecutor evaluates the node tree against the current context
  5. If the flow emits an event, that event is passed to your onEvent handler instead of the original

Merge precedence

Flows execute between the rfw event and your 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.

json
// 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

TypeDescription
ifBranch on a condition. Evaluates then or else node.
sequenceRun steps in order. Stops at first terminal event.
eventEmit a named event. Terminal — execution stops here.
setSet a value in the mutable context. Non-terminal.

Operators

OperatorDescription
eqEquals — left == right (string comparison)
neqNot equals — left != right
gtGreater than — left > right (numeric)
gteGreater than or equals — left >= right (numeric)
ltLess than — left < right (numeric)
lteLess than or equals — left <= right (numeric)
containsString or list contains value
starts_withString starts with value
ends_withString ends with value
in_listValue is in a list — right: ["a", "b", "c"]
not_in_listValue is not in a list
betweenNumeric value in range — right: [min, max]
is_trueValue is boolean true
is_falseValue is boolean false
existsValue is not null or undefined
not_existsValue is null or undefined
andAll sub-conditions must be true
orAt least one sub-condition must be true

Data sources

Conditions read values from three sources using dot notation:

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

dart
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:

dart
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:

json
{
  "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 / PropertyDescription
Koolbase.executeFlow(flowId, context)Execute a named flow. Returns FlowResult. Never throws.
Koolbase.codePush.executeFlow(flowId, context)Direct client access. Same behaviour.
FlowResult.hasEventTrue if the flow emitted a terminal event.
FlowResult.eventNameThe emitted event name, or null.
FlowResult.argsArgs attached to the emitted event.
FlowResult.completedTrue if evaluation succeeded without errors.
FlowResult.errorError message if evaluation failed, or null.