Live demo → https://bitcode-framework.github.io/ui-flutter/
The Tier C Flutter adapter for BitCode UI.
It provides contract-driven Dart widgets plus a schema renderer that consumes the
same UiSchema documents as the React, Vue, and React Native adapters — so one
JSON definition drives every platform.
BitCode is a modular low-code ERP platform where JSON is the source code. A UI document describes forms, lists, grids, and sections; each adapter renders it natively. This package is the Flutter side of that contract (ADR-011).
UiSchema (JSON)
├── @bitcode/ui → React / Vue / React Native (Mitosis)
└── bc_ui_flutter → Flutter (this package)
Same schema, same props, same behaviour — only the implementation language
differs. A BcDateField takes a JSON date string on web and a DateTime in
Dart; this adapter reconciles the two automatically (see JSON value coercion).
https://bitcode-framework.github.io/ui-flutter/
17 sections, 100+ widgets, running as a real Flutter web build. Each section is also covered by a widget test that fails on a render crash or a layout overflow.
# pubspec.yaml
dependencies:
bc_ui_flutter:
git:
url: https://github.com/bitcode-framework/ui-flutter.gitimport 'package:flutter/material.dart';
import 'package:bc_ui_flutter/bc_ui_flutter.dart';
void main() => runApp(BcApp(home: MyScreen()));
class MyScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: BcRenderer(
props: BcRendererProps(
schema: const UiSchema(
type: UiSchemaKind.form,
fields: [
FieldNode(name: 'email', label: 'Email', type: FieldType.email),
FieldNode(name: 'active', label: 'Active', type: FieldType.toggle),
],
),
values: const {'active': true},
onSubmit: (values) => debugPrint('submit: $values'),
),
),
);
}
}BcApp applies the platform theme (Material on Android/Windows/Linux/Web,
Cupertino on iOS/macOS). Use MaterialApp directly if you manage your own theme.
BcRenderer walks a UiSchema tree and dispatches on type:
| Kind | Behaviour |
|---|---|
form |
BcForm with fields, actions, and reactions |
detail |
Same as form, rendered disabled with submit hidden |
list |
Async load via loadDataSource, rendered as BcDataTable |
section |
Title/description + children in a Column |
grid |
Title/description + children in content-sized columns |
custom |
Delegates to the customBuilder callback |
Nested section/grid nodes propagate values, disabled, and every callback
to child renderers. Each nested form owns an independent ReactiveStore.
| Area | Entry points |
|---|---|
| Rendering | BcRenderer, BcRendererProps, buildField |
| Schema | UiSchema, UiSchemaKind, FieldNode, FieldType, ColumnDef, UiAction |
| Reactivity | ReactiveStore, ReactiveStoreOptions, Reaction, FieldMeta |
| Data sources | loadDataSource (null / local / fetcher / url / model) |
| Theming | BcTheme, BcApp, BcSetup |
| Expressions | src/expressions — portable AST mirror of the TS package |
| Actions | src/actions — action runtime mirror of the TS package |
| Block renderer | src/block_renderer — contract mirror + ready catalog (ADR-017) |
ReactiveStore.get() returns dynamic, and renderer values normally originate
from JSON documents rather than Dart natives. buildField therefore coerces,
never hard-casts. coerceString, coerceNum, coerceDateTime, and
coerceBool accept the native type as well as the JSON shape — ISO 8601 date
strings, numeric strings, epoch millis, 1/0, 'true'/'false'. Anything
unconvertible degrades to null instead of throwing, and the store value is
never mutated.
grid sizes each cell from its content (Row + Expanded, top-aligned), so a
detail block with many fields grows its row instead of overflowing a fixed
cell height.
BcPivot, BcViewerDocument, BcViewerInstagram, and BcViewerTiktok branch
on incoming constraints via LayoutBuilder. They fill the box when the host
provides a bounded height, and fall back to the web adapter's 600px default
viewport when it does not — for example inside a SingleChildScrollView. This
mirrors the web contracts (BcPivot is overflow-auto with natural height; the
viewers default to height: '600px') and keeps them safe in any parent.
Every composite widget tolerates narrow hosts. Toolbars scroll horizontally below their natural width, calendar headers reflow to two rows, builder panes keep a minimum canvas width, and field cards ellipsize rather than clip. The demo is audited at 1400 / 1024 / 800 / 600 px with zero overflows.
The web adapter renders media through real browser elements: an <iframe> for
YouTube, Instagram, TikTok, PDF, documents, and maps; a <video> / <audio>
element for players. Flutter has no widget that hosts those, so this package
registers real DOM elements with platformViewRegistry and displays them via
HtmlElementView — the same technique BcChart uses for ECharts.
The seam is bc_web_embed.dart, a conditional import:
| File | Platform | Behaviour |
|---|---|---|
bc_web_embed_web_impl.dart |
web | registers and hosts a live <iframe> / <video> / <audio> |
bc_web_embed_stub.dart |
mobile, desktop | bcWebEmbedSupported = false; dart:ui_web is never compiled |
Every viewer guards on bcWebEmbedSupported. On web it embeds; elsewhere it
keeps its previous informational placeholder, so the package still builds and
tests on native targets.
Two details matter when hosting a DOM element through HtmlElementView:
- The element must have an explicit height. A platform view has no
intrinsic size, so a hosted
<audio>collapses to 0px and drags down any enclosingExpanded— which silently truncates the rest of the page. The audio player reserves 54px for this reason. - Each instance needs its own view type. The registry maps a type to one element factory, so two embeds sharing a type would render the same element twice.
Some embeds depend on the remote service, not on this package:
| Embed | Requirement |
|---|---|
| YouTube, PDF, map, video, audio | loads directly |
| requires a logged-in Instagram session; anonymous visitors see Instagram's own login prompt | |
| TikTok | may rate-limit anonymous embeds from datacenter IPs (overload_protect triggered) |
Document (office engine) |
Office Online only renders actual Office files, not PDFs |
~120 widgets across layout, display, forms, views, media, and admin builders. Highlights:
- Layout —
BcBox,BcXStack,BcYStack,BcStack,BcFlex,BcCenter,BcSplitter - Forms —
BcInput,BcNumberField,BcDateField,BcTimeField,BcSelect,BcMultiSelect,BcCheckbox,BcToggle,BcSlider,BcRangeSlider,BcCombobox,BcTagsInput,BcForm,BcFieldMorph,BcFieldDuration - Data —
BcDataTable,BcList,BcTreeView,BcTreeSelect,BcPivot,BcDashboard,BcStatistic,BcDescriptions,BcSteps - Views —
BcKanbanBoard,BcKanbanView,BcCalendarView,BcViewGantt,BcViewMap,BcViewReport,BcViewActivity,BcOrgChart,BcVirtualScroller - Media —
BcImage,BcViewerImage,BcViewerVideo,BcViewerAudio,BcViewerPdf,BcViewerDocument,BcViewerYoutube,BcViewerInstagram,BcViewerTiktok,BcGalleria,BcLightbox - Builders —
BcFormBuilder,BcSchemaBuilder,BcWorkflowBuilder(drag-drop canvas + property panel + JSON preview) - Feedback / overlays —
BcAlert,BcNotification,BcDialog,BcDrawer,BcPopover,BcContextMenu,BcTour,BcLoadingOverlay
See CONTRACT_INVENTORY.md for the mapping between
@bitcode/ui contracts and their Dart implementations.
cd packages/ui-flutter
flutter pub get
flutter test # 2144 tests
flutter analyze # 0 errors
dart run build_runner build --delete-conflicting-outputs # regenerate freezed/jsonScoped runs:
flutter test test/renderer # renderer + field builder
flutter test test/unbounded_height_test.dart
cd example && flutter test # demo section testscd packages/ui-flutter/example
flutter run -d chrome --web-port=5050- 2165 tests passing, 0 failing.
flutter analyze: 0 errors.- Demo: 24 section tests green; overflow audit clean at 1400 / 1024 / 800 / 600 px.
- Chart parity (ADR-015): 111 tests — 48 golden fixtures, 34 helpers, 29 widget contracts.
- Renderer scope: 294 tests, including
bc_field_coercion_test.dart(18),bc_grid_layout_test.dart(7), andunbounded_height_test.dart(13). - Media embeds:
media_embed_test.dart(21) pins the platform seam and the URL construction feeding the web branch. Verified in a browser: PDF, YouTube, map, video, and audio all render live; Instagram and TikTok mount the correct iframe but the remote service gates the content.
ui-flutter/
├── lib/
│ ├── bc_ui_flutter.dart barrel export
│ └── src/
│ ├── components/ ~120 widgets
│ ├── renderer/ BcRenderer, field builder, data source loader
│ ├── schema/ UiSchema types (freezed) + field resolver
│ ├── core/ reactive store, hooks, animation, adapt, chart
│ ├── expressions/ portable expression AST
│ ├── actions/ action runtime
│ ├── block_renderer/ ADR-017 contract + ready catalog
│ └── theme/ BcTheme tokens
├── example/ demo app (published to GitHub Pages)
├── test/ library tests
└── doc/
bitcode-framework/ui— Mitosis component source + React demobitcode-framework/ui-schema— shared schema contracts
MIT — see LICENSE.