Skip to content

dongnh311/OnTheFly-Android

Repository files navigation

On The Fly

A lightweight dynamic UI engine for Android that renders native Jetpack Compose widgets from JavaScript scripts at runtime, powered by QuickJS.

No WebView. No HTML. Pure native UI driven by scripts.

Splash Screen Home Screen Demo Home

How It Works

┌──────────────────────────────────────────────────────────────┐
│  JavaScript (main.js)                                        │
│                                                              │
│  OnTheFly.setUI({                                            │
│    type: "Column", children: [                               │
│      { type: "Text", props: { text: "Hello" } },            │
│      { type: "Button", props: { text: "Tap", onClick: "f" }}│
│    ]                                                         │
│  });                                                         │
│       │                              ▲                       │
│       ▼                              │                       │
│  ┌─────────┐    ┌─────────┐    ┌─────────┐                  │
│  │ QuickJS │───▶│ JNI     │───▶│ Compose │──▶ Native UI     │
│  │ (C)     │    │ Bridge  │    │ Render  │                   │
│  └─────────┘    └─────────┘    └─────────┘                   │
│                                    │                         │
│                              User taps button                │
│                                    │                         │
│                              onClick("f") → JS               │
└──────────────────────────────────────────────────────────────┘

JavaScript defines the UI structure and logic. QuickJS executes it natively (no WebView). The JNI bridge passes the UI tree to Kotlin, where Jetpack Compose renders real native widgets. User interactions flow back to JS.

Features

Feature Description
Dynamic UI Define UI in JavaScript, rendered as native Compose widgets
Hot Reload Edit JS → save → app auto-reloads in ~2 seconds
Navigation Multi-screen navigation driven entirely by JS
API Calls JS requests HTTP calls, native Ktor client executes, response returns to JS
Popups Full-screen overlays and confirm dialogs, controlled from JS
Event System Lifecycle, data, component events — bidirectional bridge
Targeted Updates Update only changed nodes, not the entire UI tree
Style System Centralized themes in theme.js, components reference by name
Timber Logging OnTheFly.log.d/i/w/e() in JS → Timber in Logcat
Dev Server Python server with file watcher, validator, deploy commands
Script Validator Check JS syntax from terminal before deploying

Architecture

graph TB
    subgraph "Presentation"
        MA[MainActivity]
        SP[SplashScreen]
        SS[ScriptScreen]
        DR[DynamicRenderer]
    end

    subgraph "ViewModel"
        VM[ScriptViewModel]
    end

    subgraph "Domain"
        UC[LoadScriptUseCase]
        EE[EngineEvent]
        NA[NativeAction]
    end

    subgraph "Data"
        LS[LocalScriptStorage]
        UM[ScriptUpdateManager]
        NS[NetworkSource - Ktor]
        DS[DevServerSource]
    end

    subgraph "Engine"
        QJS[QuickJSEngine]
        STY[StyleRegistry]
        BR[bridge.cpp - JNI]
        QC[QuickJS C Library]
    end

    MA --> SP
    SP --> SS
    SS --> VM
    SS --> DR
    VM --> UC
    VM --> QJS
    VM --> UM
    VM --> NS
    UM --> DS
    UM --> LS
    UC --> LS
    QJS --> STY
    QJS --> BR
    BR --> QC
Loading

Script Storage Flow

┌─────────────────────────────────────────────────────────────┐
│                                                             │
│  FIRST LAUNCH                                               │
│  assets/scripts/ ──copy──▶ files/scripts/ (local storage)   │
│                                                             │
│  RUNTIME                                                    │
│  Always load from files/scripts/ (local)                    │
│                                                             │
│  UPDATE (Dev)                                               │
│  Dev Server ──download──▶ files/scripts/ ──▶ auto-reload    │
│                                                             │
│  UPDATE (Production)                                        │
│  Remote CDN ──download──▶ files/scripts/ ──▶ reload         │
│                                                             │
└─────────────────────────────────────────────────────────────┘

Project Structure

OnTheFly-Android/
├── devserver/                          # Dev tools
│   ├── scripts/                        #   Source of truth for all JS bundles
│   │   ├── version.json                #   Version manifest
│   │   ├── home/                       #   Home screen
│   │   │   ├── manifest.json
│   │   │   ├── main.js
│   │   │   └── theme.js
│   │   ├── demo-app/                   #   Demo: navigation, popups, events
│   │   ├── detail-app/                 #   Demo: data passing
│   │   ├── api-demo/                   #   Demo: HTTP API calls
│   │   ├── popup-fullscreen/           #   Demo: full screen popups
│   │   └── popup-confirm/             #   Demo: confirm dialogs
│   ├── server.py                       #   Dev server + validator + deploy
│   └── start.sh                        #   Quick start script
│
├── app/src/main/
│   ├── assets/scripts/                 #   Build artifact (auto-copied from devserver/)
│   ├── cpp/                            #   Native C/C++ layer
│   │   ├── quickjs/                    #     QuickJS engine source
│   │   ├── bridge.cpp                  #     JNI bridge
│   │   └── CMakeLists.txt
│   └── java/com/dongnh/onthefly/
│       ├── engine/                     #   QuickJS wrapper + style registry
│       ├── domain/                     #   Models, use cases, interfaces
│       ├── data/                       #   Local storage, network, dev server
│       ├── presentation/              #   Screens, renderer, navigation, viewmodel
│       ├── ui/theme/                   #   Material3 theme
│       ├── OnTheFlyApp.kt             #   Application class (init storage)
│       └── MainActivity.kt
│
├── docs/images/                        #   Screenshots
└── README.md

Dev Server

Interactive dev server with file watcher, validator, and deploy:

# Start (auto-setup venv on first run)
./devserver/start.sh
  OnTheFly Dev Server
  ───────────────────────────────────────
  Port:      8080
  Bundles:   home, demo-app, api-demo, ...
  Emulator:  http://10.0.2.2:8080
  ───────────────────────────────────────
  ✓ File watcher active (watchdog)
  ✓ HTTP server on port 8080
  ───────────────────────────────────────

  onthefly> _

Terminal Commands

Command Description
v, validate [bundle] Check JS syntax errors
d, deploy [bundle] Copy scripts → Android assets
l, list List all bundles
r, reload Force app reload
h, help Show commands
q, quit Stop server

CLI Commands

python server.py                    # Start server
python server.py validate           # Validate all bundles
python server.py validate home      # Validate one bundle
python server.py deploy             # Deploy all → Android assets
python server.py deploy home        # Deploy one bundle

Hot Reload Flow

sequenceDiagram
    participant Dev as Developer
    participant FS as File System
    participant WD as Watchdog
    participant SRV as Dev Server
    participant APP as Android App

    Dev->>FS: Save main.js
    FS->>WD: File change detected
    WD->>SRV: Bump globalVersion
    APP->>SRV: Poll /version (every 2s)
    SRV-->>APP: New globalVersion
    APP->>SRV: Download changed files
    SRV-->>APP: main.js, theme.js
    APP->>APP: Save to local storage
    APP->>APP: Reload engine + re-render UI
Loading

Script Format

Each screen is a bundle — a folder with manifest, theme, and entry script:

home/
├── manifest.json      # { "name": "Home", "version": "1.0.0", "entry": "main.js" }
├── theme.js           # Centralized styles
└── main.js            # UI + logic

main.js

function onCreateView() {
    OnTheFly.log.i("Screen loaded");
}

function render() {
    OnTheFly.setUI({
        type: "Column",
        props: { style: "container" },
        children: [
            { type: "Text", props: { text: "Hello", style: "title" } },
            { type: "Button", props: { text: "Next", onClick: "goNext", style: "primary" } }
        ]
    });
}

function goNext() {
    OnTheFly.sendToNative("navigate", { screen: "detail", data: { id: 1 } });
}

render();

theme.js

OnTheFly.registerStyles({
    title:     { fontSize: 28, fontWeight: "bold", color: "#1A1A2E" },
    primary:   { backgroundColor: "#0F3460", color: "#FFF", cornerRadius: 12 },
    container: { padding: 24, spacing: 16, alignment: "center" }
});

UI Components

Component Props Description
Column padding, spacing, alignment, style Vertical layout
Row spacing, alignment, style Horizontal layout
Text text, style, id Text display
Button text, onClick, id, style Clickable button
Toggle id, label, checked Switch/toggle
Spacer height, style Empty space
FullScreenPopup id, visible, onDismiss, style Full screen overlay
ConfirmDialog id, visible, title, message, onConfirm, onCancel Alert dialog

JS API

Method Description
OnTheFly.setUI(tree) Set full UI tree
OnTheFly.update(id, props) Targeted update (fast)
OnTheFly.registerStyles(styles) Register named styles
OnTheFly.sendToNative(action, data) Navigate, API call, toast, goBack
OnTheFly.log(msg) Log to Timber (INFO)
OnTheFly.log.d/i/w/e(msg) Log with level
console.log(msg) Log to Timber (DEBUG)

Native Actions

OnTheFly.sendToNative("navigate", { screen: "detail", data: {...} });
OnTheFly.sendToNative("goBack");
OnTheFly.sendToNative("showToast", { message: "Hello!" });
OnTheFly.sendToNative("sendRequest", { id: "r1", url: "...", method: "GET" });

Lifecycle Events

function onCreateView() { }   // Screen created
function onResume() { }       // App foreground
function onPause() { }        // App background
function onDestroy() { }      // Screen destroyed
function onVisible() { }      // Screen re-entered
function onBackPressed() { }  // Back button
function onDataReceived(data) { }  // API response
function onViewData(data) { }     // Data from navigation

Tech Stack

Component Technology
UI Jetpack Compose + Material3
Script Engine QuickJS (v2025-09-13, ~210KB)
Native Bridge JNI via NDK + CMake
HTTP Client Ktor Client + OkHttp
Navigation Compose Navigation
Logging Timber
Architecture Clean Architecture
Language Kotlin + C/C++
Dev Server Python + watchdog
Min SDK 24 (Android 7.0)
Target SDK 35

Build & Run

# Build
./gradlew assembleDebug

# Install
./gradlew installDebug

# With hot-reload
./devserver/start.sh          # Terminal 1
./gradlew installDebug        # Terminal 2
# Edit devserver/scripts/home/main.js → app auto-reloads

License

MIT

About

Make lib on the fly for android.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors