Skip to content

Run Flutter widget code on Codename One - #5883

Open
shai-almog wants to merge 593 commits into
masterfrom
flutter-dart-transpilation
Open

shai-almog wants to merge 593 commits into
masterfrom
flutter-dart-transpilation

Conversation

@shai-almog

Copy link
Copy Markdown
Collaborator

Compiles Dart widget source to Java at build time and runs it on Codename One's own renderer. There is no Dart VM in the result, no embedded engine and no platform view — a Dart screen becomes ordinary Codename One components, so it inherits the theme, the event thread, the accessibility tree and the native build.

Two entry points, which are the two things a user actually wants to do:

  • FlutterUI.wrap(widget) returns a Container that goes anywhere an ordinary component does, for putting one Dart screen inside an existing app. It deliberately does not install the Material base theme, so it will not restyle the screen around it.
  • FlutterUI.runApp(widget) mounts the tree as the whole UI, and does install it.

The build wiring is the archetype's own: the transcode-flutter goal is already bound and is a silent no-op until src/main/flutter exists. No Dart SDK is involved — the transpiler is Java and the Dart is input, never executed.

Benchmark

scripts/flutter-bench builds one application two ways — the Flutter toolchain's release build, and the identical Dart source transpiled by Codename One — and publishes size, start-up and idle memory per platform to the PR and to port status.

Neither application is vendored. prepare.sh takes the gallery from the Flutter SDK CI clones and copies the same 159 files into both trees, so the comparison cannot drift: there is no second copy for an edit to land on. An earlier round of this work compared two different galleries and every number it produced was meaningless. The Codename One side is generated from the shipping archetype with the runtime dependency enabled — the two steps the guide tells a user to take — so a change that breaks the documented wiring breaks the benchmark too.

Three measurement decisions, each because the obvious alternative flattered us:

  • Start-up is a bracket. Flutter's FIRSTCONTENT is a UI-thread callback that runs before that frame is rasterised, while our FIRSTFRAME fires once the form is on screen. Comparing them charges one runtime for rasterising its first screen and not the other — which is what the harness this replaces did. Flutter's figure is reported as a range and the ratio uses the end least favourable to us.
  • Executable code is every Mach-O in the bundle, not the main executable. On iOS a Flutter application's own code is not in the executable at all: Runner is a thin launcher and the Dart image sits in Frameworks/App.framework. Sizing the executable compared our whole runtime against their stub.
  • Assets are staged from Flutter's own built bundle. Staging the whole asset package ships files Flutter tree-shakes away; staging only the 1x images ships fewer than Flutter does.

And two refusals rather than a substituted number:

  • iOS start-up and memory are not measured. Dart cannot AOT-compile for the simulator, so a simulator run would time Flutter's JIT debug engine against our release build. Sizes come from release device bundles, which need no signing.
  • Desktop is the native ports only — AppKit, clang-cl, GTK3/Cairo — never JavaSE, whose bundled JVM and dependency directory are not the same kind of artifact as Flutter's native bundle.

Nothing contacts the build server: the *-source and local-* targets build on the runner. Only our own numbers are gated, so a Flutter SDK upgrade that grows their build cannot turn ours red.

Fidelity work in this PR

  • RoundBorder draws itself when there is no shadow and no uiid mode, instead of going through a component-sized offscreen image. The cache hides the cost for a static shape; for one that animates its size it does not — a circle growing to 1618×1618 threw away a 10MB surface per frame.
  • A paint-only shift needs a box it can paint into. Component.paintInternalImpl clips every component to its own rectangle, so FractionalTranslation drew most of itself into the discarded region — the feature-discovery circle rendered as a quadrant with two straight edges meeting at its centre.
  • FittedBox was a pass-through. Flutter lays its child out unbounded and scales the result, which is why text inside one shrinks instead of wrapping.
  • Card rebuilt its border every time, and RoundRectBorder caches its shadow against the border instance, so every rebuild re-rendered it with a gaussian blur. With the cache off it also translates the live Graphics by the shadow offset and never undoes it, so cards drifted 9px down and 4px across, accumulating down the page.

Verification

  • 398 flutter-runtime tests, plus 21 new tests for the benchmark's arithmetic
  • Parity sweep 48/48 routes: device mean 2.06%, median 1.66%; /demo/card 14.98% → 2.30%, /demo/grid-lists 26.30% → 0.32%
  • prepare.sh verified end to end; the generated project builds 563 Java files from 159 Dart files, which also exercises the documented user path
  • actionlint 74/74, copyright and control-character gates clean against the merge base

Not yet proven

No benchmark platform adapter has been exercised end to end — run_bench.py --list reports that rather than implying otherwise, and the first CI run is the thing to review. The native compile steps (xcodebuild, gradle, clang-cl, GTK3) have not run on a runner.

This is also the first mention of the feature in docs/, which has carried none until now.

Loading
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant