System Architecture & Platform Bridge

Neon's performance and developer experience stem from its decoupled architecture. Rather than running an embedded C++ canvas engine or interpreting JavaScript via an asynchronous bridge, Neon runs a Dart UI server that instructs native Android and iOS platform shells via an optimized JSON protocol.


The Platform Bridge Pattern

The core interaction between the Dart framework and native operating systems follows a high-throughput, low-latency client-server model:

    ┌────────────────────────┐
    │      User Action       │ (Tap, Swipe, Input, Toggle)
    └───────────┬────────────┘
                │
                ▼
    ┌────────────────────────┐
    │   Native Shell (OS)    │
    │  Android: Kotlin View  │
    │  iOS: UIKit UIView     │
    └───────────┬────────────┘
                │
                │ HTTP POST /action
                │ { "id": "btn_submit", "clientRevision": 1 }
                ▼
    ┌────────────────────────┐
    │   Dart Backend Server  │ (Runs on localhost:8080)
    │  1. Routes to Widget   │
    │  2. Executes Callback  │
    │  3. Flushes Microtasks │
    │  4. Rebuilds Tree      │
    │  5. NeonWidgetTreeDiff │ (Computes delta from baseRevision)
    └───────────┬────────────┘
                │
                │ HTTP Response (200 OK)
                │ { "type": "patch", "revision": 2, "operations": [...] }
                │ (or "type": "full" if revision mismatch / recovery)
                ▼
    ┌────────────────────────┐
    │   Native Shell (OS)    │
    │  Applies patch in-place│ (Preserves native view state & focus)
    │  or recovers full tree │
    └────────────────────────┘

1. The Dart Engine Backend

The Dart backend serves as the brain of the application:

  • NeonApp.run(): Initializes the application loop and spins up an HTTP server (default port 8080).
  • GET /api/tree: Returns the initial full serialized representation of the active NeonWidgetTree encapsulated with type: "full", version: 1, and revision: 1.
  • POST /action: Receives user interactions dispatched from the native shell:
    {
      "id": "counter-btn-add",
      "clientRevision": 1,
      "patchVersion": 1,
      "value": null
    }
    
  • Action Dispatcher: Looks up the target widget by its unique ID in the element tree, invokes its registered event listener (e.g. onPressed, onChanged), and triggers setState().
  • Microtask Queue Flush: The server waits for the microtask queue to drain (await Future.delayed(Duration.zero)) before rebuilding the tree.
  • Incremental Patching: Using NeonWidgetTreeDiff, the engine calculates atomic operations (update, replace, remove, insert) relative to the client's clientRevision. If revisions match, it transmits a lightweight type: "patch" response. If a revision mismatch occurs or recovery is requested, it seamlessly falls back to a complete type: "full" response. For detailed information, see Widget Tree Diffing.

2. Native Shell Implementations

Android Native Shell (MainActivity.kt)

  • Built using modern Kotlin and Gradle targeting Android SDK 21–34.
  • Uses native java.net.URL and coroutines to establish high-speed local HTTP requests.
  • Maps JSON elements to real Android components:
    • Container ➔ FrameLayout / LinearLayout
    • FilledButton / ElevatedButton ➔ com.google.android.material.button.MaterialButton
    • TextField ➔ com.google.android.material.textfield.TextInputEditText
    • Switch ➔ com.google.android.material.materialswitch.MaterialSwitch
    • ListView ➔ androidx.recyclerview.widget.RecyclerView

iOS Native Shell (ViewController.swift)

  • Built using Swift and UIKit (targeting iOS 15.0+).
  • Uses standard URLSession with background streaming.
  • Maps JSON elements to real Cocoa Touch views:
    • Container ➔ UIView with CALayer styling (corner radius, border, drop shadows)
    • Button ➔ UIButton (with iOS 15+ UIButton.Configuration)
    • TextField ➔ UITextField
    • Switch ➔ UISwitch
    • ListView ➔ UITableView / UICollectionView

3. Critical Framework Design Decisions

Interactive Widget Keys

Because the native UI layer is decoupled from Dart memory pointers, every interactive widget requires a stable String key:

// Recommended: Explicit string keys for interactive elements
FilledButton(
  key: 'login-submit-button',
  onPressed: _handleLogin,
  child: Text('Log In'),
)

Without a key, the native shell cannot bind tap or input gestures back to the corresponding Dart event listener.

double.infinity Clamping

The standard JSON specification does not support double.infinity or double.nan. When developers specify unbounded layouts (e.g., width: double.infinity), the Neon rendering pipeline automatically clamps infinity to a sentinel value (-1.0 or MATCH_PARENT) before serialization so that the native shells can apply full-width/full-height layout constraints.


4. Comparison: Neon vs Flutter vs React Native

| Dimension | Neon Framework | Flutter | React Native | | :--- | :--- | :--- | :--- | | Render Engine | 100% Native OS Views (UIKit & Material) | Skia / Impeller custom canvas | Native OS Views | | Logic Language | Dart 3 | Dart 3 | JavaScript / TypeScript | | Engine Overhead | ~0 MB (no custom graphics engine) | ~15MB–25MB engine runtime | ~8MB–12MB JS runtime (Hermes) | | Platform Look & Feel | Flawless Native (Platform handles text selection, accessibility, haptics) | Canvas-drawn approximation | Native | | Hot Reload | 3-Tier Sub-100ms | State-based Hot Reload | Fast Refresh | | Access to Platform APIs | Direct via Native Shell HTTP bridge | Platform Channels / FFI | JSI / TurboModules |


Next Steps