Skip to content

Repository files navigation

Flutter Mini App

中文文档

Experimental project: flutter_mini_app is still evolving rapidly. The goal is to turn it into the most performance-focused, AI-native app orchestration solution for Flutter. Contributions and feedback are very welcome.

A Flutter Mini App framework that supports both native and HTML rendering.

Highlights

  • MiniAppRuntime: unified runtime entry point for registration, lifecycle, rendering, and native schema/tool-definition export
  • MiniAppCatalog: factory registration, dependency validation, instance caching, and state management
  • MiniAppRenderer: synchronous build entry point with async boundaries handled internally
  • MiniAppHost: provides runtime access to the widget tree through scope
  • HTML cards: template versioning, schema validation, bridge communication, and session-level hot updates without rebuilding the WebView
  • Realtime pipeline: MiniAppRealtimeController handles snapshot diffs, self-ack deduplication, and write-back orchestration

Installation

dependencies:
  flutter_mini_app: ^0.1.0

You can still use a local path: dependency during local development, but a hosted version is recommended after publishing to pub.dev.

Quick Start

1. Create a runtime and register factories

import 'package:flutter/material.dart';
import 'package:flutter_mini_app/flutter_mini_app.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  final runtime = MiniAppRuntime(
    config: const MiniAppRuntimeConfig(
      maxCachedInstances: 10,
      idleTimeout: Duration(minutes: 5),
      enablePreload: true,
      enableDebugLog: false,
    ),
  );

  runtime.registerAll([
    MyMiniAppFactory(),
  ]);

  await runtime.initialize();

  runApp(
    MiniAppHost(
      runtime: runtime,
      child: const MyApp(),
    ),
  );
}

2. Render with MiniAppWidget

MiniAppWidget(
  jsonData: {
    'ID': 'my_component',
    'title': 'Hello',
  },
)

3. Build directly from the runtime

final runtime = MiniAppHost.of(context);

final widget = runtime.buildFromJson(
  {
    'ID': 'my_component',
    'title': 'Hello',
  },
  isMaximized: false,
);

HTML + JSON Driven Rendering

Core Models

  • HtmlCardBuild: build record defined by moduleName + cardVersion + schema + html
  • HtmlTemplateRepository: template resolution and caching
  • HtmlMiniAppFactory: HTML factory with mapper, validation strategy, and bridge callbacks
  • HtmlHotUpdatePayload: minimal hot-update payload with module, version, and normalized data

Register an HTML Factory

import 'package:flutter/services.dart';
import 'package:flutter_mini_app/flutter_mini_app.dart';

final builds = <int, HtmlCardBuild>{
  1: HtmlCardBuild(
    moduleName: 'todo_html',
    cardVersion: 1,
    schemaContent: {
      r'$schema': 'https://json-schema.org/draft/2020-12/schema',
      'type': 'object',
      'required': ['title', 'items'],
      'properties': {
        'title': {'type': 'string'},
        'items': {
          'type': 'array',
          'items': {'type': 'string'},
        },
      },
    },
    htmlObjectPath: 'assets/index.html',
  ),
};

final htmlFactory = HtmlMiniAppFactory(
  id: 'todo_html',
  defaultCardVersion: 1,
  templateRepository: HtmlTemplateRepository(
    buildResolver: ({required moduleName, int? cardVersion}) async {
      if (moduleName != 'todo_html') return null;
      return cardVersion == null ? builds[1] : builds[cardVersion];
    },
    objectPathLoader: (path) => rootBundle.loadString(path),
  ),
  validateData: true,
  securityPolicy: HtmlSecurityPolicy.unsafe,
);

runtime.registerHtml(htmlFactory);

Build an HTML Card

final htmlWidget = runtime.buildFromJson({
  'ID': 'todo_html',
  'cardVersion': 1,
  'title': 'This Week',
  'items': ['Book hotel', 'Buy tickets'],
});

final htmlWidget2 = runtime.buildFromCardBuild(
  moduleName: 'todo_html',
  cardVersion: 1,
  data: {
    'title': 'This Week',
    'items': ['Book hotel', 'Buy tickets'],
  },
);

HTML Hot Update Flow

  • Cold path: resolve template, validate, interpolate, and load the WebView
  • Hot path: within the same module + version + isMaximized session, only inject data through miniapp:data-update without rebuilding the WebView

Templates can listen to:

  • miniapp:data-ready
  • miniapp:data-update

Troubleshooting: Card Labels Update but HTML Content Does Not

Symptom: business data, such as a Supabase realtime stream, is updated and Flutter card state or labels also refresh, but the HTML card content only changes after a full page refresh.

Common root causes:

  • The template listens only to miniapp:data-ready and never handles miniapp:data-update.
  • The content returned by objectPathLoader did not actually change, or stale HTML is still being served by external storage, a signed URL, a CDN, or HtmlTemplateRepository cache.

Recommended order of investigation:

  1. Make the template listen to both miniapp:data-ready and miniapp:data-update, then redraw the DOM from event.detail.data or window.__CARD_DATA__.
  2. If templates are loaded via htmlObjectPath and objectPathLoader, make sure the underlying file, signed URL, or CDN response has been refreshed. Updating only build metadata is not enough if the loader still resolves stale HTML.
  3. Evict the affected template cache before retesting, for example with htmlFactory.templateRepository.evictModule('todo_html') or clearCache().

WebView Reloads Inside PageView

When HTML cards are placed inside PageView or PageView.builder, off-screen pages may be disposed. In that case, the WebView inside MiniAppWidget is destroyed and recreated when the user scrolls back, which usually appears as flicker, lost state, and repeated JavaScript initialization.

Common triggers:

  • The page is lazily built with PageView.builder and the card state is not kept alive
  • Each page does not have a stable Key, so Flutter may reuse or rebuild elements unpredictably

Recommended approach:

class _MiniAppCardState extends State<_MiniAppCard>
    with AutomaticKeepAliveClientMixin {
  @override
  bool get wantKeepAlive => true;

  @override
  Widget build(BuildContext context) {
    super.build(context); // Required for keepAlive
    return MiniAppWidget(
      jsonData: jsonData,
      isMaximized: false,
    );
  }
}

PageView.builder(
  itemCount: htmlKeys.length,
  itemBuilder: (context, index) {
    final key = htmlKeys[index];
    return _MiniAppCard(
      key: ValueKey<String>('html-$key'),
      dataKey: key,
    );
  },
);

Notes:

  • AutomaticKeepAliveClientMixin helps preserve off-screen pages
  • ValueKey gives each card page a stable identity and avoids unnecessary rebuilds
  • This works with the framework hot-update flow so data changes go through miniapp:data-update instead of a full reload

Realtime Pipeline (Backend Agnostic)

The framework provides three abstractions and one controller so application code can adapt different backends such as Supabase, Firebase, or custom services:

  • MiniAppRealtimeRecord: normalized record model with key/appId/json/updatedAt
  • MiniAppRealtimeSource: produces a Stream<List<R>> snapshot stream
  • MiniAppWriteSink: exposes patch/delete write capabilities
  • MiniAppRealtimeController: handles diffing, write-back deduplication, and self-ack suppression
final controller = MiniAppRealtimeController<MyRealtimeRecord>(
  repository: runtime.data,
  source: myRealtimeSource,
  sink: myWriteSink,
  onSnapshotReport: (report) {
    // report.kind: externalUpdate / selfAck / noOp
  },
);

await controller.start();

await controller.patch(
  key: 'card-1',
  patch: {'title': 'Updated title'},
);

await controller.delete(key: 'card-1');
await controller.dispose();

MiniAppRealtimeController.patch(...) returns:

  • written
  • noRecord
  • noSemanticChange
  • duplicatePending

Data Repository

final repo = runtime.data;

repo.put('card-1', {
  'ID': 'todo_html',
  'title': 'Travel Checklist',
});

final json = repo.get('card-1');
repo.patch('card-1', {'title': 'Updated title'});

Schema and Tool Definitions

final errors = await runtime.validateJson({
  'ID': 'my_component',
  'title': 'hello',
});

final schemasJson = runtime.exportSchemasAsJson();
final toolDefs = runtime.generateToolDefinitions();
final toolDefsJson = runtime.generateToolDefinitionsJson();

Notes:

  • exportSchemasAsJson(), generateToolDefinitions(), and validateJson() operate on schemas exposed by registered native MiniAppFactory instances.
  • HTML build schemas are resolved separately through validateDataWithBuild(...).
  • validateDataWithBuild(...) validates the data you pass to it directly. During actual HTML rendering, the framework validates normalized data after HtmlMiniAppFactory.dataMapper is applied.

Main APIs

MiniAppRuntime

API Description
register/registerAll Register native factories
registerHtml/registerHtmlAll Register HTML factories
initialize() Initialize the runtime
getApp(id) Get an initialized native instance
buildFromJson(...) Synchronous build entry point with automatic native or HTML dispatch
buildFromDataKey(...) Build from a data repository key
buildFromCardBuild(...) Build an HTML card from a specific module and version
describeHtmlHotUpdate(...) Produce an HTML hot-update descriptor
validateDataWithBuild(...) Validate input data against a specific HTML build version
validateJson(...) Validate native schema data
stats Runtime statistics

Realtime APIs

API Description
MiniAppRealtimeRecord Standard realtime record interface with key/appId/json/updatedAt
MiniAppRealtimeSource.watch() Produce snapshot list streams
MiniAppWriteSink.patch/delete Application write-back interface
MiniAppRealtimeController.start/stop/dispose Lifecycle control
MiniAppRealtimeController.patch(...) Write back after semantic deduplication and return the patch result
MiniAppRealtimeController.delete(...) Execute delete write-back

MiniAppHost

API Description
MiniAppHost(runtime, child) Inject the runtime into scope
MiniAppHost.of(context) Read the runtime from scope

Compatibility Notes

The current release is based on the Runtime architecture:

  • MiniAppRegistry and MiniAppRegistryConfig were removed
  • Async render* entry points were replaced by synchronous build* entry points
  • MiniAppHost is the recommended way to provide the runtime at the app root

About

No description, website, or topics provided.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages