[rfw] Remote Flutter Widgets package (#452)

diff --git a/.cirrus.yml b/.cirrus.yml
index 7267667..78b6a46 100644
--- a/.cirrus.yml
+++ b/.cirrus.yml
@@ -8,7 +8,7 @@
     - git fetch origin master # To set FETCH_HEAD for "git merge-base" to work
     # Pinned version of the plugin tools, to avoid breakage in this repository
     # when pushing updates from flutter/plugins.
-    - dart pub global activate flutter_plugin_tools 0.6.0+1
+    - dart pub global activate flutter_plugin_tools 0.7.1
 
 flutter_upgrade_template: &FLUTTER_UPGRADE_TEMPLATE
   upgrade_flutter_script:
@@ -41,7 +41,7 @@
       always:
         format_script: ./script/tool_runner.sh format --fail-on-change --clang-format=clang-format-5.0
         license_script: dart pub global run flutter_plugin_tools license-check
-        analyze_script: ./script/tool_runner.sh analyze --custom-analysis=web_benchmarks/testing/test_app,flutter_lints/example
+        analyze_script: ./script/tool_runner.sh analyze --custom-analysis=web_benchmarks/testing/test_app,flutter_lints/example,rfw/example
         pubspec_script: ./script/tool_runner.sh pubspec-check
     - name: publishable
       version_script: ./script/tool_runner.sh version-check
@@ -61,9 +61,18 @@
       script:
         # extension_google_sign_in_as_googleapis_auth is currently not building, see
         # https://github.com/flutter/flutter/issues/89301
-        - ./script/tool_runner.sh build-examples --apk --exclude=extension_google_sign_in_as_googleapis_auth
-        # Must come after apk build:
-        - ./script/tool_runner.sh native-test --android --no-integration
+        # rfw is excluded until the next Flutter stable release because it depends
+        # on features that have never shipped to stable. (The rfw package has
+        # never worked on stable so this is not going to break anyone.)
+        # When updating this, also look at the ios tests below.
+        # When updating this, also update the `rfw/run_tests.sh` file.
+        - if [[ "$CHANNEL" == "master" ]]; then
+        -   ./script/tool_runner.sh build-examples --apk --exclude=extension_google_sign_in_as_googleapis_auth
+        -   ./script/tool_runner.sh native-test --android --no-integration
+        - else
+        -   ./script/tool_runner.sh build-examples --apk --exclude=extension_google_sign_in_as_googleapis_auth,rfw
+        -   ./script/tool_runner.sh native-test --android --no-integration --exclude=rfw
+        - fi
       depends_on:
         - format+analyze
     - name: web_benchmarks_test
@@ -89,7 +98,16 @@
       CHANNEL: "stable"
   << : *FLUTTER_UPGRADE_TEMPLATE
   build_script:
-    - ./script/tool_runner.sh build-examples --ios
+    # Exclude rfw until the next Flutter stable release because it depends
+    # on features that have never shipped to stable. (The rfw package has
+    # never worked on stable so this is not going to break anyone.)
+    # When updating this, also look at the android tests above.
+    # When updating this, also update the `rfw/run_tests.sh` file.
+    - if [[ "$CHANNEL" == "master" ]]; then
+    -   ./script/tool_runner.sh build-examples --ios
+    - else
+    -   ./script/tool_runner.sh build-examples --ios --exclude=rfw
+    - fi
 
 task:
   name: local_tests
diff --git a/customer_testing.bat b/customer_testing.bat
index f7d81ec..375f20d 100644
--- a/customer_testing.bat
+++ b/customer_testing.bat
@@ -11,3 +11,6 @@
 CD packages/animations
 CALL flutter analyze --no-fatal-infos
 CALL flutter test
+
+REM We don't run the tests in packages/rfw because those tests are
+REM platform-sensitive and only work reliably on Linux.
diff --git a/customer_testing.sh b/customer_testing.sh
index b04edf9..16147f0 100755
--- a/customer_testing.sh
+++ b/customer_testing.sh
@@ -15,6 +15,21 @@
 
 set -e
 
-cd packages/animations
+pushd packages/animations
 flutter analyze --no-fatal-infos
 flutter test
+popd
+
+pushd packages/rfw
+flutter analyze --no-fatal-infos
+if [[ "$OSTYPE" == "linux-gnu" ]]; then
+    # We only run the full tests on Linux because golden files differ
+    # from platform to platform.
+    flutter test
+fi
+# The next script verifies that the coverage is not regressed; it does
+# not verify goldens. (It does run all the tests though, so it still
+# catches logic issues on other platforms, just not issue that only
+# affect golden files.)
+./run_tests.sh
+popd
diff --git a/packages/rfw/.gitignore b/packages/rfw/.gitignore
new file mode 100644
index 0000000..a247422
--- /dev/null
+++ b/packages/rfw/.gitignore
@@ -0,0 +1,75 @@
+# Miscellaneous
+*.class
+*.log
+*.pyc
+*.swp
+.DS_Store
+.atom/
+.buildlog/
+.history
+.svn/
+
+# IntelliJ related
+*.iml
+*.ipr
+*.iws
+.idea/
+
+# The .vscode folder contains launch configuration and tasks you configure in
+# VS Code which you may wish to be included in version control, so this line
+# is commented out by default.
+#.vscode/
+
+# Flutter/Dart/Pub related
+**/doc/api/
+.dart_tool/
+.flutter-plugins
+.flutter-plugins-dependencies
+.packages
+.pub-cache/
+.pub/
+build/
+
+# Android related
+**/android/**/gradle-wrapper.jar
+**/android/.gradle
+**/android/captures/
+**/android/gradlew
+**/android/gradlew.bat
+**/android/local.properties
+**/android/**/GeneratedPluginRegistrant.java
+
+# iOS/XCode related
+**/ios/**/*.mode1v3
+**/ios/**/*.mode2v3
+**/ios/**/*.moved-aside
+**/ios/**/*.pbxuser
+**/ios/**/*.perspectivev3
+**/ios/**/*sync/
+**/ios/**/.sconsign.dblite
+**/ios/**/.tags*
+**/ios/**/.vagrant/
+**/ios/**/DerivedData/
+**/ios/**/Icon?
+**/ios/**/Pods/
+**/ios/**/.symlinks/
+**/ios/**/profile
+**/ios/**/xcuserdata
+**/ios/.generated/
+**/ios/Flutter/App.framework
+**/ios/Flutter/Flutter.framework
+**/ios/Flutter/Flutter.podspec
+**/ios/Flutter/Generated.xcconfig
+**/ios/Flutter/ephemeral
+**/ios/Flutter/app.flx
+**/ios/Flutter/app.zip
+**/ios/Flutter/flutter_assets/
+**/ios/Flutter/flutter_export_environment.sh
+**/ios/ServiceDefinitions.json
+**/ios/Runner/GeneratedPluginRegistrant.*
+
+# Exceptions to above rules.
+!**/ios/**/default.mode1v3
+!**/ios/**/default.mode2v3
+!**/ios/**/default.pbxuser
+!**/ios/**/default.perspectivev3
diff --git a/packages/rfw/.metadata b/packages/rfw/.metadata
new file mode 100644
index 0000000..068d7b0
--- /dev/null
+++ b/packages/rfw/.metadata
@@ -0,0 +1,10 @@
+# This file tracks properties of this Flutter project.
+# Used by Flutter tool to assess capabilities and perform upgrades etc.
+#
+# This file should be version controlled and should not be manually edited.
+
+version:
+  revision: fbc4e9bcc5feaa037864a5beb6b40f4cb630330b
+  channel: master
+
+project_type: package
diff --git a/packages/rfw/CHANGELOG.md b/packages/rfw/CHANGELOG.md
new file mode 100644
index 0000000..0d8803f
--- /dev/null
+++ b/packages/rfw/CHANGELOG.md
@@ -0,0 +1,3 @@
+## 1.0.0
+
+* Initial release.
diff --git a/packages/rfw/LICENSE b/packages/rfw/LICENSE
new file mode 100644
index 0000000..c6823b8
--- /dev/null
+++ b/packages/rfw/LICENSE
@@ -0,0 +1,25 @@
+Copyright 2013 The Flutter Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+    * Neither the name of Google Inc. nor the names of its
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/packages/rfw/README.md b/packages/rfw/README.md
new file mode 100644
index 0000000..ce6cff5
--- /dev/null
+++ b/packages/rfw/README.md
@@ -0,0 +1,242 @@
+# Remote Flutter Widgets
+
+This package provides a mechanism for rendering widgets based on
+declarative UI descriptions that can be obtained at runtime.
+
+## Getting started
+
+A Flutter application can render remote widgets using the
+`RemoteWidget` widget, as in the following snippet:
+
+```dart
+// see example/hello
+
+class Example extends StatefulWidget {
+  const Example({Key? key}) : super(key: key);
+
+  @override
+  State<Example> createState() => _ExampleState();
+}
+
+class _ExampleState extends State<Example> {
+  final Runtime _runtime = Runtime();
+  final DynamicContent _data = DynamicContent();
+
+  // Normally this would be obtained dynamically, but for this example
+  // we hard-code the "remote" widgets into the app.
+  //
+  // Also, normally we would decode this with [decodeLibraryBlob] rather than
+  // parsing the text version using [parseLibraryFile]. However, to make it
+  // easier to demo, this uses the slower text format.
+  static final RemoteWidgetLibrary _remoteWidgets = parseLibraryFile('''
+    // The "import" keyword is used to specify dependencies, in this case,
+    // the built-in widgets that are added by initState below.
+    import core.widgets;
+    // The "widget" keyword is used to define a new widget constructor.
+    // The "root" widget is specified as the one to render in the build
+    // method below.
+    widget root = Container(
+      color: 0xFF002211,
+      child: Center(
+        child: Text(text: ["Hello, ", data.greet.name, "!"], textDirection: "ltr"),
+      ),
+    );
+  ''');
+  
+  @override
+  void initState() {
+    super.initState();
+    _runtime.update(const LibraryName(<String>['core', 'widgets']), createCoreWidgets());
+    _runtime.update(const LibraryName(<String>['main']), _remoteWidgets);
+    _data.update('greet', <String, Object>{ 'name': 'World' });
+  }
+  
+  @override
+  Widget build(BuildContext context) {
+    return RemoteWidget(
+      runtime: _runtime,
+      data: _data,
+      widget: const FullyQualifiedWidgetName(LibraryName(<String>['main']), 'root'),
+      onEvent: (String name, DynamicMap arguments) {
+        // The example above does not have any way to trigger events, but if it
+        // did, they would result in this callback being invoked.
+        debugPrint('user triggered event "$name" with data: $arguments');
+      },
+    );
+  }
+}
+```
+
+In this example, the "remote" widgets are hard-coded into the application.
+
+## Usage
+
+In typical usage, the remote widgets come from a server at runtime,
+either through HTTP or some other network transport. Separately, the
+`DynamicContent` data is updated, either from the server or based on
+local data.
+
+It is recommended that servers send binary data, decoded using
+`decodeLibraryBlob` and `decodeDataBlob`, when providing updates for
+the remote widget libraries and data.
+
+Events (`onEvent`) are signalled by the user's interactions with the
+remote widgets. The client is responsible for handling them, either by
+sending the data to the server for the server to update the data, or
+directly, on the user's device.
+
+## Limitations
+
+Once you realize that you can ship UI (and maybe logic, e.g. using
+Wasm; see the example below) you will slowly be tempted to move your
+whole application to this model.
+
+This won't work.
+
+Flutter proper lets you create widgets for compelling UIs with
+gestures and animations and so forth. With RFW you can use those
+widgets, but it doesn't let you _create_ those widgets.
+
+For example, you don't want to use RFW to create a UI that involves
+page transitions. You don't want to use RFW to create new widgets that
+involve drag and drop. You don't want to use RFW to create widgets
+that involve custom painters.
+
+Rather, RFW is best suited for interfaces made out of prebuilt
+components. For example, a database front-end could use this to
+describe bespoke UIs for editing different types of objects in the
+database. Message-of-the-day announcements could be built using this
+mechanism. Search interfaces could use this mechanism for rich result
+cards.
+
+RFW is well-suited for describing custom UIs from a potentially
+infinite set of UIs that could not possibly have been known when the
+application was created. On the other hand, updating the application's
+look and feel, changing how navigation works in an application, or
+adding new features, are all changes that are best made in Flutter
+itself, creating a new application and shipping that through normal
+update channels.
+
+## Developing new local widget libraries
+
+A "local" widget library is one that describes the built-in widgets
+that your "remote" widgets are built out of. The RFW package comes
+with some preprepared libraries, available through [createCoreWidgets]
+and [createMaterialWidgets]. You can also create your own.
+
+When developing new local widget libraries, it is convenient to hook
+into the `reassemble` method to update the local widgets. That way,
+changes can be seen in real time when hot reloading.
+
+```dart
+// see example/local
+
+class Example extends StatefulWidget {
+  const Example({Key? key}) : super(key: key);
+
+  @override
+  State<Example> createState() => _ExampleState();
+}
+
+class _ExampleState extends State<Example> {
+  final Runtime _runtime = Runtime();
+  final DynamicContent _data = DynamicContent();
+  
+  @override
+  void initState() {
+    super.initState();
+    _update();
+  }
+
+  @override
+  void reassemble() {
+    super.reassemble();
+    _update();
+  }
+
+  static WidgetLibrary _createLocalWidgets() => LocalWidgetLibrary(<String, LocalWidgetBuilder>{
+    'GreenBox': (BuildContext context, DataSource source) {
+      return Container(color: const Color(0xFF002211), child: source.child(<Object>['child']));
+    },
+    'Hello': (BuildContext context, DataSource source) {
+      return Center(child: Text('Hello, ${source.v<String>(<Object>["name"])}!', textDirection: TextDirection.ltr));
+    },
+  });
+
+
+  void _update() {
+    _runtime.update(const LibraryName(<String>['local']), _createLocalWidgets());
+    _runtime.update(const LibraryName(<String>['remote']), parseLibraryFile('''
+      import local;
+      widget root = GreenBox(
+        child: Hello(name: "World"),
+      );
+    '''));
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return RemoteWidget(
+      runtime: _runtime,
+      data: _data,
+      widget: const FullyQualifiedWidgetName(LibraryName(<String>['remote']), 'root'),
+      onEvent: (String name, DynamicMap arguments) {
+        debugPrint('user triggered event "$name" with data: $arguments');
+      },
+    );
+  }
+}
+```
+
+## Fetching remote widget libraries remotely
+
+The example in `example/remote` shows how a program could fetch
+different user interfaces at runtime. In this example, the interface
+used on startup is the one last cached locally. Each time the program
+is run, after displaying the currently-cached interface, the
+application fetches a new interface over the network, overwriting the
+one in the cache, so that a different interface is used the next time
+the app is run.
+
+This example also shows how an application can implement custom local
+code for events; in this case, incrementing a counter (both of the
+"remote" widgets are just different ways of implementing a counter).
+
+## Integrating with scripting language runtimes
+
+The example in `example/wasm` shows how a program could fetch logic in
+addition to UI, in this case using Wasm compiled from C (and let us
+briefly appreciate the absurdity of using C as a scripting language
+for an application written in Dart).
+
+In this example, as written, the Dart client could support any
+application whose data model consisted of a single integer and whose
+logic could be expressed in C without external dependencies.
+
+This example could be extended to have the C program export data in
+the Remote Flutter Widgets binary data blob format which could be
+parsed using `decodeDataBlob` and passed to `DynamicContent.update`
+(thus allowing any structured data supported by RFW), and similarly
+arguments could be passed to the Wasm code using the same format
+(encoding using `encodeDataBlob`) to allow arbitrary structured data
+to be communicated from the interface to the Wasm logic. In addition,
+the Wasm logic could be provided with WASI interface bindings or with
+custom bindings that expose platform capabilities (e.g. from Flutter
+plugins), greatly extending the scope of what could be implemented in
+the Wasm logic.
+
+As of the time of writing, `package:wasm` does not support Android,
+iOS, or web, so this demo is limited to desktop environments. The
+underlying Wasmer runtime supports Android and iOS already, and
+obviously Wasm in general is supported by web browsers, so it is
+expected that these limitations are only temporary (modulo policy
+concerns on iOS, anyway).
+
+## Contributing
+
+Adding more widgets to `lib/flutter/material_widgets.dart` is welcome.
+
+When contributing code, ensure that `flutter test --coverage; lcov
+--list coverage/lcov.info` continues to show 100% test coverage, and
+update `run_tests.sh` with the appropriate expectations to prevent
+future coverage regressions.
diff --git a/packages/rfw/example/analysis_options.yaml b/packages/rfw/example/analysis_options.yaml
new file mode 100644
index 0000000..4f212b0
--- /dev/null
+++ b/packages/rfw/example/analysis_options.yaml
@@ -0,0 +1,8 @@
+# Use the analysis options settings from the top level of the repo (not
+# the ones from above, which include the `public_member_api_docs` rule).
+
+include: ../../../analysis_options.yaml
+
+linter:
+  rules:
+    public_member_api_docs: false
diff --git a/packages/rfw/example/hello/.gitignore b/packages/rfw/example/hello/.gitignore
new file mode 100644
index 0000000..0fa6b67
--- /dev/null
+++ b/packages/rfw/example/hello/.gitignore
@@ -0,0 +1,46 @@
+# Miscellaneous
+*.class
+*.log
+*.pyc
+*.swp
+.DS_Store
+.atom/
+.buildlog/
+.history
+.svn/
+
+# IntelliJ related
+*.iml
+*.ipr
+*.iws
+.idea/
+
+# The .vscode folder contains launch configuration and tasks you configure in
+# VS Code which you may wish to be included in version control, so this line
+# is commented out by default.
+#.vscode/
+
+# Flutter/Dart/Pub related
+**/doc/api/
+**/ios/Flutter/.last_build_id
+.dart_tool/
+.flutter-plugins
+.flutter-plugins-dependencies
+.packages
+.pub-cache/
+.pub/
+/build/
+
+# Web related
+lib/generated_plugin_registrant.dart
+
+# Symbolication related
+app.*.symbols
+
+# Obfuscation related
+app.*.map.json
+
+# Android Studio will place build artifacts here
+/android/app/debug
+/android/app/profile
+/android/app/release
diff --git a/packages/rfw/example/hello/.metadata b/packages/rfw/example/hello/.metadata
new file mode 100644
index 0000000..45c494e
--- /dev/null
+++ b/packages/rfw/example/hello/.metadata
@@ -0,0 +1,10 @@
+# This file tracks properties of this Flutter project.
+# Used by Flutter tool to assess capabilities and perform upgrades etc.
+#
+# This file should be version controlled and should not be manually edited.
+
+version:
+  revision: a1cd3f45e02c2429e14630d6cc6de120c3d24bd0
+  channel: master
+
+project_type: app
diff --git a/packages/rfw/example/hello/README.md b/packages/rfw/example/hello/README.md
new file mode 100644
index 0000000..7e2dcff
--- /dev/null
+++ b/packages/rfw/example/hello/README.md
@@ -0,0 +1,4 @@
+# Hello World for RFW
+
+This example shows the simplest use of the `RemoteWidget` widget. In
+this example, the "remote" widget is hard-coded into the application.
diff --git a/packages/rfw/example/hello/android/.gitignore b/packages/rfw/example/hello/android/.gitignore
new file mode 100644
index 0000000..6f56801
--- /dev/null
+++ b/packages/rfw/example/hello/android/.gitignore
@@ -0,0 +1,13 @@
+gradle-wrapper.jar
+/.gradle
+/captures/
+/gradlew
+/gradlew.bat
+/local.properties
+GeneratedPluginRegistrant.java
+
+# Remember to never publicly share your keystore.
+# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
+key.properties
+**/*.keystore
+**/*.jks
diff --git a/packages/rfw/example/hello/android/app/build.gradle b/packages/rfw/example/hello/android/app/build.gradle
new file mode 100644
index 0000000..7a06a22
--- /dev/null
+++ b/packages/rfw/example/hello/android/app/build.gradle
@@ -0,0 +1,68 @@
+def localProperties = new Properties()
+def localPropertiesFile = rootProject.file('local.properties')
+if (localPropertiesFile.exists()) {
+    localPropertiesFile.withReader('UTF-8') { reader ->
+        localProperties.load(reader)
+    }
+}
+
+def flutterRoot = localProperties.getProperty('flutter.sdk')
+if (flutterRoot == null) {
+    throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
+}
+
+def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
+if (flutterVersionCode == null) {
+    flutterVersionCode = '1'
+}
+
+def flutterVersionName = localProperties.getProperty('flutter.versionName')
+if (flutterVersionName == null) {
+    flutterVersionName = '1.0'
+}
+
+apply plugin: 'com.android.application'
+apply plugin: 'kotlin-android'
+apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
+
+android {
+    compileSdkVersion 30
+
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_8
+        targetCompatibility JavaVersion.VERSION_1_8
+    }
+
+    kotlinOptions {
+        jvmTarget = '1.8'
+    }
+
+    sourceSets {
+        main.java.srcDirs += 'src/main/kotlin'
+    }
+
+    defaultConfig {
+        // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
+        applicationId "dev.flutter.rfw.examples.hello"
+        minSdkVersion 16
+        targetSdkVersion 30
+        versionCode flutterVersionCode.toInteger()
+        versionName flutterVersionName
+    }
+
+    buildTypes {
+        release {
+            // TODO: Add your own signing config for the release build.
+            // Signing with the debug keys for now, so `flutter run --release` works.
+            signingConfig signingConfigs.debug
+        }
+    }
+}
+
+flutter {
+    source '../..'
+}
+
+dependencies {
+    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
+}
diff --git a/packages/rfw/example/hello/android/app/src/debug/AndroidManifest.xml b/packages/rfw/example/hello/android/app/src/debug/AndroidManifest.xml
new file mode 100644
index 0000000..91a24f1
--- /dev/null
+++ b/packages/rfw/example/hello/android/app/src/debug/AndroidManifest.xml
@@ -0,0 +1,7 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="dev.flutter.rfw.examples.hello">
+    <!-- Flutter needs it to communicate with the running application
+         to allow setting breakpoints, to provide hot reload, etc.
+    -->
+    <uses-permission android:name="android.permission.INTERNET"/>
+</manifest>
diff --git a/packages/rfw/example/hello/android/app/src/main/AndroidManifest.xml b/packages/rfw/example/hello/android/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..a528520
--- /dev/null
+++ b/packages/rfw/example/hello/android/app/src/main/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="dev.flutter.rfw.examples.hello">
+   <application
+        android:label="hello"
+        android:icon="@mipmap/ic_launcher">
+        <activity
+            android:name=".MainActivity"
+            android:launchMode="singleTop"
+            android:theme="@style/LaunchTheme"
+            android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
+            android:hardwareAccelerated="true"
+            android:windowSoftInputMode="adjustResize">
+            <!-- Specifies an Android theme to apply to this Activity as soon as
+                 the Android process has started. This theme is visible to the user
+                 while the Flutter UI initializes. After that, this theme continues
+                 to determine the Window background behind the Flutter UI. -->
+            <meta-data
+              android:name="io.flutter.embedding.android.NormalTheme"
+              android:resource="@style/NormalTheme"
+              />
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+        <!-- Don't delete the meta-data below.
+             This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
+        <meta-data
+            android:name="flutterEmbedding"
+            android:value="2" />
+    </application>
+</manifest>
diff --git a/packages/rfw/example/hello/android/app/src/main/kotlin/dev/flutter/rfw/examples/hello/MainActivity.kt b/packages/rfw/example/hello/android/app/src/main/kotlin/dev/flutter/rfw/examples/hello/MainActivity.kt
new file mode 100644
index 0000000..7f9aee8
--- /dev/null
+++ b/packages/rfw/example/hello/android/app/src/main/kotlin/dev/flutter/rfw/examples/hello/MainActivity.kt
@@ -0,0 +1,6 @@
+package dev.flutter.rfw.examples.hello
+
+import io.flutter.embedding.android.FlutterActivity
+
+class MainActivity: FlutterActivity() {
+}
diff --git a/packages/rfw/example/hello/android/app/src/main/res/drawable-v21/launch_background.xml b/packages/rfw/example/hello/android/app/src/main/res/drawable-v21/launch_background.xml
new file mode 100644
index 0000000..f74085f
--- /dev/null
+++ b/packages/rfw/example/hello/android/app/src/main/res/drawable-v21/launch_background.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Modify this file to customize your launch splash screen -->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:drawable="?android:colorBackground" />
+
+    <!-- You can insert your own image assets here -->
+    <!-- <item>
+        <bitmap
+            android:gravity="center"
+            android:src="@mipmap/launch_image" />
+    </item> -->
+</layer-list>
diff --git a/packages/rfw/example/hello/android/app/src/main/res/drawable/launch_background.xml b/packages/rfw/example/hello/android/app/src/main/res/drawable/launch_background.xml
new file mode 100644
index 0000000..304732f
--- /dev/null
+++ b/packages/rfw/example/hello/android/app/src/main/res/drawable/launch_background.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Modify this file to customize your launch splash screen -->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:drawable="@android:color/white" />
+
+    <!-- You can insert your own image assets here -->
+    <!-- <item>
+        <bitmap
+            android:gravity="center"
+            android:src="@mipmap/launch_image" />
+    </item> -->
+</layer-list>
diff --git a/packages/rfw/example/hello/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/packages/rfw/example/hello/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..db77bb4
--- /dev/null
+++ b/packages/rfw/example/hello/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
Binary files differ
diff --git a/packages/rfw/example/hello/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/packages/rfw/example/hello/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..17987b7
--- /dev/null
+++ b/packages/rfw/example/hello/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
Binary files differ
diff --git a/packages/rfw/example/hello/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/packages/rfw/example/hello/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..09d4391
--- /dev/null
+++ b/packages/rfw/example/hello/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
Binary files differ
diff --git a/packages/rfw/example/hello/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/packages/rfw/example/hello/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..d5f1c8d
--- /dev/null
+++ b/packages/rfw/example/hello/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/packages/rfw/example/hello/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/packages/rfw/example/hello/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..4d6372e
--- /dev/null
+++ b/packages/rfw/example/hello/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Binary files differ
diff --git a/packages/rfw/example/hello/android/app/src/main/res/values-night/styles.xml b/packages/rfw/example/hello/android/app/src/main/res/values-night/styles.xml
new file mode 100644
index 0000000..449a9f9
--- /dev/null
+++ b/packages/rfw/example/hello/android/app/src/main/res/values-night/styles.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
+    <style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
+        <!-- Show a splash screen on the activity. Automatically removed when
+             Flutter draws its first frame -->
+        <item name="android:windowBackground">@drawable/launch_background</item>
+    </style>
+    <!-- Theme applied to the Android Window as soon as the process has started.
+         This theme determines the color of the Android Window while your
+         Flutter UI initializes, as well as behind your Flutter UI while its
+         running.
+         
+         This Theme is only used starting with V2 of Flutter's Android embedding. -->
+    <style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
+        <item name="android:windowBackground">?android:colorBackground</item>
+    </style>
+</resources>
diff --git a/packages/rfw/example/hello/android/app/src/main/res/values/styles.xml b/packages/rfw/example/hello/android/app/src/main/res/values/styles.xml
new file mode 100644
index 0000000..d74aa35
--- /dev/null
+++ b/packages/rfw/example/hello/android/app/src/main/res/values/styles.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
+    <style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
+        <!-- Show a splash screen on the activity. Automatically removed when
+             Flutter draws its first frame -->
+        <item name="android:windowBackground">@drawable/launch_background</item>
+    </style>
+    <!-- Theme applied to the Android Window as soon as the process has started.
+         This theme determines the color of the Android Window while your
+         Flutter UI initializes, as well as behind your Flutter UI while its
+         running.
+         
+         This Theme is only used starting with V2 of Flutter's Android embedding. -->
+    <style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
+        <item name="android:windowBackground">?android:colorBackground</item>
+    </style>
+</resources>
diff --git a/packages/rfw/example/hello/android/app/src/profile/AndroidManifest.xml b/packages/rfw/example/hello/android/app/src/profile/AndroidManifest.xml
new file mode 100644
index 0000000..91a24f1
--- /dev/null
+++ b/packages/rfw/example/hello/android/app/src/profile/AndroidManifest.xml
@@ -0,0 +1,7 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="dev.flutter.rfw.examples.hello">
+    <!-- Flutter needs it to communicate with the running application
+         to allow setting breakpoints, to provide hot reload, etc.
+    -->
+    <uses-permission android:name="android.permission.INTERNET"/>
+</manifest>
diff --git a/packages/rfw/example/hello/android/build.gradle b/packages/rfw/example/hello/android/build.gradle
new file mode 100644
index 0000000..ed45c65
--- /dev/null
+++ b/packages/rfw/example/hello/android/build.gradle
@@ -0,0 +1,29 @@
+buildscript {
+    ext.kotlin_version = '1.3.50'
+    repositories {
+        google()
+        mavenCentral()
+    }
+
+    dependencies {
+        classpath 'com.android.tools.build:gradle:4.1.0'
+        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
+    }
+}
+
+allprojects {
+    repositories {
+        google()
+        mavenCentral()
+    }
+}
+
+rootProject.buildDir = '../build'
+subprojects {
+    project.buildDir = "${rootProject.buildDir}/${project.name}"
+    project.evaluationDependsOn(':app')
+}
+
+task clean(type: Delete) {
+    delete rootProject.buildDir
+}
diff --git a/packages/rfw/example/hello/android/gradle.properties b/packages/rfw/example/hello/android/gradle.properties
new file mode 100644
index 0000000..94adc3a
--- /dev/null
+++ b/packages/rfw/example/hello/android/gradle.properties
@@ -0,0 +1,3 @@
+org.gradle.jvmargs=-Xmx1536M
+android.useAndroidX=true
+android.enableJetifier=true
diff --git a/packages/rfw/example/hello/android/gradle/wrapper/gradle-wrapper.properties b/packages/rfw/example/hello/android/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..bc6a58a
--- /dev/null
+++ b/packages/rfw/example/hello/android/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Fri Jun 23 08:50:38 CEST 2017
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip
diff --git a/packages/rfw/example/hello/android/settings.gradle b/packages/rfw/example/hello/android/settings.gradle
new file mode 100644
index 0000000..44e62bc
--- /dev/null
+++ b/packages/rfw/example/hello/android/settings.gradle
@@ -0,0 +1,11 @@
+include ':app'
+
+def localPropertiesFile = new File(rootProject.projectDir, "local.properties")
+def properties = new Properties()
+
+assert localPropertiesFile.exists()
+localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }
+
+def flutterSdkPath = properties.getProperty("flutter.sdk")
+assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
+apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"
diff --git a/packages/rfw/example/hello/ios/.gitignore b/packages/rfw/example/hello/ios/.gitignore
new file mode 100644
index 0000000..151026b
--- /dev/null
+++ b/packages/rfw/example/hello/ios/.gitignore
@@ -0,0 +1,33 @@
+*.mode1v3
+*.mode2v3
+*.moved-aside
+*.pbxuser
+*.perspectivev3
+**/*sync/
+.sconsign.dblite
+.tags*
+**/.vagrant/
+**/DerivedData/
+Icon?
+**/Pods/
+**/.symlinks/
+profile
+xcuserdata
+**/.generated/
+Flutter/App.framework
+Flutter/Flutter.framework
+Flutter/Flutter.podspec
+Flutter/Generated.xcconfig
+Flutter/ephemeral/
+Flutter/app.flx
+Flutter/app.zip
+Flutter/flutter_assets/
+Flutter/flutter_export_environment.sh
+ServiceDefinitions.json
+Runner/GeneratedPluginRegistrant.*
+
+# Exceptions to above rules.
+!default.mode1v3
+!default.mode2v3
+!default.pbxuser
+!default.perspectivev3
diff --git a/packages/rfw/example/hello/ios/Flutter/AppFrameworkInfo.plist b/packages/rfw/example/hello/ios/Flutter/AppFrameworkInfo.plist
new file mode 100644
index 0000000..8d4492f
--- /dev/null
+++ b/packages/rfw/example/hello/ios/Flutter/AppFrameworkInfo.plist
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+  <key>CFBundleDevelopmentRegion</key>
+  <string>en</string>
+  <key>CFBundleExecutable</key>
+  <string>App</string>
+  <key>CFBundleIdentifier</key>
+  <string>io.flutter.flutter.app</string>
+  <key>CFBundleInfoDictionaryVersion</key>
+  <string>6.0</string>
+  <key>CFBundleName</key>
+  <string>App</string>
+  <key>CFBundlePackageType</key>
+  <string>FMWK</string>
+  <key>CFBundleShortVersionString</key>
+  <string>1.0</string>
+  <key>CFBundleSignature</key>
+  <string>????</string>
+  <key>CFBundleVersion</key>
+  <string>1.0</string>
+  <key>MinimumOSVersion</key>
+  <string>9.0</string>
+</dict>
+</plist>
diff --git a/packages/rfw/example/hello/ios/Flutter/Debug.xcconfig b/packages/rfw/example/hello/ios/Flutter/Debug.xcconfig
new file mode 100644
index 0000000..592ceee
--- /dev/null
+++ b/packages/rfw/example/hello/ios/Flutter/Debug.xcconfig
@@ -0,0 +1 @@
+#include "Generated.xcconfig"
diff --git a/packages/rfw/example/hello/ios/Flutter/Release.xcconfig b/packages/rfw/example/hello/ios/Flutter/Release.xcconfig
new file mode 100644
index 0000000..592ceee
--- /dev/null
+++ b/packages/rfw/example/hello/ios/Flutter/Release.xcconfig
@@ -0,0 +1 @@
+#include "Generated.xcconfig"
diff --git a/packages/rfw/example/hello/ios/Runner.xcodeproj/project.pbxproj b/packages/rfw/example/hello/ios/Runner.xcodeproj/project.pbxproj
new file mode 100644
index 0000000..e77cf43
--- /dev/null
+++ b/packages/rfw/example/hello/ios/Runner.xcodeproj/project.pbxproj
@@ -0,0 +1,481 @@
+// !$*UTF8*$!
+{
+	archiveVersion = 1;
+	classes = {
+	};
+	objectVersion = 50;
+	objects = {
+
+/* Begin PBXBuildFile section */
+		1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
+		3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
+		74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
+		97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
+		97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
+		97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXCopyFilesBuildPhase section */
+		9705A1C41CF9048500538489 /* Embed Frameworks */ = {
+			isa = PBXCopyFilesBuildPhase;
+			buildActionMask = 2147483647;
+			dstPath = "";
+			dstSubfolderSpec = 10;
+			files = (
+			);
+			name = "Embed Frameworks";
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXCopyFilesBuildPhase section */
+
+/* Begin PBXFileReference section */
+		1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
+		1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
+		3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
+		74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
+		74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
+		7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
+		9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
+		9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
+		97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
+		97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
+		97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
+		97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
+		97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+		97C146EB1CF9000F007C117D /* Frameworks */ = {
+			isa = PBXFrameworksBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+		9740EEB11CF90186004384FC /* Flutter */ = {
+			isa = PBXGroup;
+			children = (
+				3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
+				9740EEB21CF90195004384FC /* Debug.xcconfig */,
+				7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
+				9740EEB31CF90195004384FC /* Generated.xcconfig */,
+			);
+			name = Flutter;
+			sourceTree = "<group>";
+		};
+		97C146E51CF9000F007C117D = {
+			isa = PBXGroup;
+			children = (
+				9740EEB11CF90186004384FC /* Flutter */,
+				97C146F01CF9000F007C117D /* Runner */,
+				97C146EF1CF9000F007C117D /* Products */,
+			);
+			sourceTree = "<group>";
+		};
+		97C146EF1CF9000F007C117D /* Products */ = {
+			isa = PBXGroup;
+			children = (
+				97C146EE1CF9000F007C117D /* Runner.app */,
+			);
+			name = Products;
+			sourceTree = "<group>";
+		};
+		97C146F01CF9000F007C117D /* Runner */ = {
+			isa = PBXGroup;
+			children = (
+				97C146FA1CF9000F007C117D /* Main.storyboard */,
+				97C146FD1CF9000F007C117D /* Assets.xcassets */,
+				97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
+				97C147021CF9000F007C117D /* Info.plist */,
+				1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
+				1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
+				74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
+				74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
+			);
+			path = Runner;
+			sourceTree = "<group>";
+		};
+/* End PBXGroup section */
+
+/* Begin PBXNativeTarget section */
+		97C146ED1CF9000F007C117D /* Runner */ = {
+			isa = PBXNativeTarget;
+			buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
+			buildPhases = (
+				9740EEB61CF901F6004384FC /* Run Script */,
+				97C146EA1CF9000F007C117D /* Sources */,
+				97C146EB1CF9000F007C117D /* Frameworks */,
+				97C146EC1CF9000F007C117D /* Resources */,
+				9705A1C41CF9048500538489 /* Embed Frameworks */,
+				3B06AD1E1E4923F5004D2608 /* Thin Binary */,
+			);
+			buildRules = (
+			);
+			dependencies = (
+			);
+			name = Runner;
+			productName = Runner;
+			productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
+			productType = "com.apple.product-type.application";
+		};
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+		97C146E61CF9000F007C117D /* Project object */ = {
+			isa = PBXProject;
+			attributes = {
+				LastUpgradeCheck = 1300;
+				ORGANIZATIONNAME = "";
+				TargetAttributes = {
+					97C146ED1CF9000F007C117D = {
+						CreatedOnToolsVersion = 7.3.1;
+						LastSwiftMigration = 1100;
+					};
+				};
+			};
+			buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
+			compatibilityVersion = "Xcode 9.3";
+			developmentRegion = en;
+			hasScannedForEncodings = 0;
+			knownRegions = (
+				en,
+				Base,
+			);
+			mainGroup = 97C146E51CF9000F007C117D;
+			productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
+			projectDirPath = "";
+			projectRoot = "";
+			targets = (
+				97C146ED1CF9000F007C117D /* Runner */,
+			);
+		};
+/* End PBXProject section */
+
+/* Begin PBXResourcesBuildPhase section */
+		97C146EC1CF9000F007C117D /* Resources */ = {
+			isa = PBXResourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
+				3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
+				97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
+				97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXResourcesBuildPhase section */
+
+/* Begin PBXShellScriptBuildPhase section */
+		3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
+			isa = PBXShellScriptBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			inputPaths = (
+			);
+			name = "Thin Binary";
+			outputPaths = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+			shellPath = /bin/sh;
+			shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
+		};
+		9740EEB61CF901F6004384FC /* Run Script */ = {
+			isa = PBXShellScriptBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			inputPaths = (
+			);
+			name = "Run Script";
+			outputPaths = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+			shellPath = /bin/sh;
+			shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
+		};
+/* End PBXShellScriptBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+		97C146EA1CF9000F007C117D /* Sources */ = {
+			isa = PBXSourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
+				1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXSourcesBuildPhase section */
+
+/* Begin PBXVariantGroup section */
+		97C146FA1CF9000F007C117D /* Main.storyboard */ = {
+			isa = PBXVariantGroup;
+			children = (
+				97C146FB1CF9000F007C117D /* Base */,
+			);
+			name = Main.storyboard;
+			sourceTree = "<group>";
+		};
+		97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
+			isa = PBXVariantGroup;
+			children = (
+				97C147001CF9000F007C117D /* Base */,
+			);
+			name = LaunchScreen.storyboard;
+			sourceTree = "<group>";
+		};
+/* End PBXVariantGroup section */
+
+/* Begin XCBuildConfiguration section */
+		249021D3217E4FDB00AE95B9 /* Profile */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				ALWAYS_SEARCH_USER_PATHS = NO;
+				CLANG_ANALYZER_NONNULL = YES;
+				CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+				CLANG_CXX_LIBRARY = "libc++";
+				CLANG_ENABLE_MODULES = YES;
+				CLANG_ENABLE_OBJC_ARC = YES;
+				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+				CLANG_WARN_BOOL_CONVERSION = YES;
+				CLANG_WARN_COMMA = YES;
+				CLANG_WARN_CONSTANT_CONVERSION = YES;
+				CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+				CLANG_WARN_EMPTY_BODY = YES;
+				CLANG_WARN_ENUM_CONVERSION = YES;
+				CLANG_WARN_INFINITE_RECURSION = YES;
+				CLANG_WARN_INT_CONVERSION = YES;
+				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+				CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+				CLANG_WARN_STRICT_PROTOTYPES = YES;
+				CLANG_WARN_SUSPICIOUS_MOVE = YES;
+				CLANG_WARN_UNREACHABLE_CODE = YES;
+				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+				"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+				COPY_PHASE_STRIP = NO;
+				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+				ENABLE_NS_ASSERTIONS = NO;
+				ENABLE_STRICT_OBJC_MSGSEND = YES;
+				GCC_C_LANGUAGE_STANDARD = gnu99;
+				GCC_NO_COMMON_BLOCKS = YES;
+				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+				GCC_WARN_UNDECLARED_SELECTOR = YES;
+				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+				GCC_WARN_UNUSED_FUNCTION = YES;
+				GCC_WARN_UNUSED_VARIABLE = YES;
+				IPHONEOS_DEPLOYMENT_TARGET = 9.0;
+				MTL_ENABLE_DEBUG_INFO = NO;
+				SDKROOT = iphoneos;
+				SUPPORTED_PLATFORMS = iphoneos;
+				TARGETED_DEVICE_FAMILY = "1,2";
+				VALIDATE_PRODUCT = YES;
+			};
+			name = Profile;
+		};
+		249021D4217E4FDB00AE95B9 /* Profile */ = {
+			isa = XCBuildConfiguration;
+			baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
+			buildSettings = {
+				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+				CLANG_ENABLE_MODULES = YES;
+				CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
+				ENABLE_BITCODE = NO;
+				INFOPLIST_FILE = Runner/Info.plist;
+				LD_RUNPATH_SEARCH_PATHS = (
+					"$(inherited)",
+					"@executable_path/Frameworks",
+				);
+				PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.rfw.examples.hello;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+				SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
+				SWIFT_VERSION = 5.0;
+				VERSIONING_SYSTEM = "apple-generic";
+			};
+			name = Profile;
+		};
+		97C147031CF9000F007C117D /* Debug */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				ALWAYS_SEARCH_USER_PATHS = NO;
+				CLANG_ANALYZER_NONNULL = YES;
+				CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+				CLANG_CXX_LIBRARY = "libc++";
+				CLANG_ENABLE_MODULES = YES;
+				CLANG_ENABLE_OBJC_ARC = YES;
+				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+				CLANG_WARN_BOOL_CONVERSION = YES;
+				CLANG_WARN_COMMA = YES;
+				CLANG_WARN_CONSTANT_CONVERSION = YES;
+				CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+				CLANG_WARN_EMPTY_BODY = YES;
+				CLANG_WARN_ENUM_CONVERSION = YES;
+				CLANG_WARN_INFINITE_RECURSION = YES;
+				CLANG_WARN_INT_CONVERSION = YES;
+				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+				CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+				CLANG_WARN_STRICT_PROTOTYPES = YES;
+				CLANG_WARN_SUSPICIOUS_MOVE = YES;
+				CLANG_WARN_UNREACHABLE_CODE = YES;
+				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+				"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+				COPY_PHASE_STRIP = NO;
+				DEBUG_INFORMATION_FORMAT = dwarf;
+				ENABLE_STRICT_OBJC_MSGSEND = YES;
+				ENABLE_TESTABILITY = YES;
+				GCC_C_LANGUAGE_STANDARD = gnu99;
+				GCC_DYNAMIC_NO_PIC = NO;
+				GCC_NO_COMMON_BLOCKS = YES;
+				GCC_OPTIMIZATION_LEVEL = 0;
+				GCC_PREPROCESSOR_DEFINITIONS = (
+					"DEBUG=1",
+					"$(inherited)",
+				);
+				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+				GCC_WARN_UNDECLARED_SELECTOR = YES;
+				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+				GCC_WARN_UNUSED_FUNCTION = YES;
+				GCC_WARN_UNUSED_VARIABLE = YES;
+				IPHONEOS_DEPLOYMENT_TARGET = 9.0;
+				MTL_ENABLE_DEBUG_INFO = YES;
+				ONLY_ACTIVE_ARCH = YES;
+				SDKROOT = iphoneos;
+				TARGETED_DEVICE_FAMILY = "1,2";
+			};
+			name = Debug;
+		};
+		97C147041CF9000F007C117D /* Release */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				ALWAYS_SEARCH_USER_PATHS = NO;
+				CLANG_ANALYZER_NONNULL = YES;
+				CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+				CLANG_CXX_LIBRARY = "libc++";
+				CLANG_ENABLE_MODULES = YES;
+				CLANG_ENABLE_OBJC_ARC = YES;
+				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+				CLANG_WARN_BOOL_CONVERSION = YES;
+				CLANG_WARN_COMMA = YES;
+				CLANG_WARN_CONSTANT_CONVERSION = YES;
+				CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+				CLANG_WARN_EMPTY_BODY = YES;
+				CLANG_WARN_ENUM_CONVERSION = YES;
+				CLANG_WARN_INFINITE_RECURSION = YES;
+				CLANG_WARN_INT_CONVERSION = YES;
+				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+				CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+				CLANG_WARN_STRICT_PROTOTYPES = YES;
+				CLANG_WARN_SUSPICIOUS_MOVE = YES;
+				CLANG_WARN_UNREACHABLE_CODE = YES;
+				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+				"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+				COPY_PHASE_STRIP = NO;
+				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+				ENABLE_NS_ASSERTIONS = NO;
+				ENABLE_STRICT_OBJC_MSGSEND = YES;
+				GCC_C_LANGUAGE_STANDARD = gnu99;
+				GCC_NO_COMMON_BLOCKS = YES;
+				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+				GCC_WARN_UNDECLARED_SELECTOR = YES;
+				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+				GCC_WARN_UNUSED_FUNCTION = YES;
+				GCC_WARN_UNUSED_VARIABLE = YES;
+				IPHONEOS_DEPLOYMENT_TARGET = 9.0;
+				MTL_ENABLE_DEBUG_INFO = NO;
+				SDKROOT = iphoneos;
+				SUPPORTED_PLATFORMS = iphoneos;
+				SWIFT_COMPILATION_MODE = wholemodule;
+				SWIFT_OPTIMIZATION_LEVEL = "-O";
+				TARGETED_DEVICE_FAMILY = "1,2";
+				VALIDATE_PRODUCT = YES;
+			};
+			name = Release;
+		};
+		97C147061CF9000F007C117D /* Debug */ = {
+			isa = XCBuildConfiguration;
+			baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
+			buildSettings = {
+				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+				CLANG_ENABLE_MODULES = YES;
+				CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
+				ENABLE_BITCODE = NO;
+				INFOPLIST_FILE = Runner/Info.plist;
+				LD_RUNPATH_SEARCH_PATHS = (
+					"$(inherited)",
+					"@executable_path/Frameworks",
+				);
+				PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.rfw.examples.hello;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+				SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
+				SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+				SWIFT_VERSION = 5.0;
+				VERSIONING_SYSTEM = "apple-generic";
+			};
+			name = Debug;
+		};
+		97C147071CF9000F007C117D /* Release */ = {
+			isa = XCBuildConfiguration;
+			baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
+			buildSettings = {
+				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+				CLANG_ENABLE_MODULES = YES;
+				CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
+				ENABLE_BITCODE = NO;
+				INFOPLIST_FILE = Runner/Info.plist;
+				LD_RUNPATH_SEARCH_PATHS = (
+					"$(inherited)",
+					"@executable_path/Frameworks",
+				);
+				PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.rfw.examples.hello;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+				SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
+				SWIFT_VERSION = 5.0;
+				VERSIONING_SYSTEM = "apple-generic";
+			};
+			name = Release;
+		};
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+		97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				97C147031CF9000F007C117D /* Debug */,
+				97C147041CF9000F007C117D /* Release */,
+				249021D3217E4FDB00AE95B9 /* Profile */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
+		};
+		97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				97C147061CF9000F007C117D /* Debug */,
+				97C147071CF9000F007C117D /* Release */,
+				249021D4217E4FDB00AE95B9 /* Profile */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
+		};
+/* End XCConfigurationList section */
+	};
+	rootObject = 97C146E61CF9000F007C117D /* Project object */;
+}
diff --git a/packages/rfw/example/hello/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/packages/rfw/example/hello/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 0000000..919434a
--- /dev/null
+++ b/packages/rfw/example/hello/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Workspace
+   version = "1.0">
+   <FileRef
+      location = "self:">
+   </FileRef>
+</Workspace>
diff --git a/packages/rfw/example/hello/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/rfw/example/hello/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
new file mode 100644
index 0000000..18d9810
--- /dev/null
+++ b/packages/rfw/example/hello/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>IDEDidComputeMac32BitWarning</key>
+	<true/>
+</dict>
+</plist>
diff --git a/packages/rfw/example/hello/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/rfw/example/hello/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
new file mode 100644
index 0000000..f9b0d7c
--- /dev/null
+++ b/packages/rfw/example/hello/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>PreviewsEnabled</key>
+	<false/>
+</dict>
+</plist>
diff --git a/packages/rfw/example/hello/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/rfw/example/hello/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
new file mode 100644
index 0000000..c87d15a
--- /dev/null
+++ b/packages/rfw/example/hello/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
@@ -0,0 +1,87 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Scheme
+   LastUpgradeVersion = "1300"
+   version = "1.3">
+   <BuildAction
+      parallelizeBuildables = "YES"
+      buildImplicitDependencies = "YES">
+      <BuildActionEntries>
+         <BuildActionEntry
+            buildForTesting = "YES"
+            buildForRunning = "YES"
+            buildForProfiling = "YES"
+            buildForArchiving = "YES"
+            buildForAnalyzing = "YES">
+            <BuildableReference
+               BuildableIdentifier = "primary"
+               BlueprintIdentifier = "97C146ED1CF9000F007C117D"
+               BuildableName = "Runner.app"
+               BlueprintName = "Runner"
+               ReferencedContainer = "container:Runner.xcodeproj">
+            </BuildableReference>
+         </BuildActionEntry>
+      </BuildActionEntries>
+   </BuildAction>
+   <TestAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      shouldUseLaunchSchemeArgsEnv = "YES">
+      <MacroExpansion>
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "97C146ED1CF9000F007C117D"
+            BuildableName = "Runner.app"
+            BlueprintName = "Runner"
+            ReferencedContainer = "container:Runner.xcodeproj">
+         </BuildableReference>
+      </MacroExpansion>
+      <Testables>
+      </Testables>
+   </TestAction>
+   <LaunchAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      launchStyle = "0"
+      useCustomWorkingDirectory = "NO"
+      ignoresPersistentStateOnLaunch = "NO"
+      debugDocumentVersioning = "YES"
+      debugServiceExtension = "internal"
+      allowLocationSimulation = "YES">
+      <BuildableProductRunnable
+         runnableDebuggingMode = "0">
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "97C146ED1CF9000F007C117D"
+            BuildableName = "Runner.app"
+            BlueprintName = "Runner"
+            ReferencedContainer = "container:Runner.xcodeproj">
+         </BuildableReference>
+      </BuildableProductRunnable>
+   </LaunchAction>
+   <ProfileAction
+      buildConfiguration = "Profile"
+      shouldUseLaunchSchemeArgsEnv = "YES"
+      savedToolIdentifier = ""
+      useCustomWorkingDirectory = "NO"
+      debugDocumentVersioning = "YES">
+      <BuildableProductRunnable
+         runnableDebuggingMode = "0">
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "97C146ED1CF9000F007C117D"
+            BuildableName = "Runner.app"
+            BlueprintName = "Runner"
+            ReferencedContainer = "container:Runner.xcodeproj">
+         </BuildableReference>
+      </BuildableProductRunnable>
+   </ProfileAction>
+   <AnalyzeAction
+      buildConfiguration = "Debug">
+   </AnalyzeAction>
+   <ArchiveAction
+      buildConfiguration = "Release"
+      revealArchiveInOrganizer = "YES">
+   </ArchiveAction>
+</Scheme>
diff --git a/packages/rfw/example/hello/ios/Runner.xcworkspace/contents.xcworkspacedata b/packages/rfw/example/hello/ios/Runner.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 0000000..1d526a1
--- /dev/null
+++ b/packages/rfw/example/hello/ios/Runner.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Workspace
+   version = "1.0">
+   <FileRef
+      location = "group:Runner.xcodeproj">
+   </FileRef>
+</Workspace>
diff --git a/packages/rfw/example/hello/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/rfw/example/hello/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
new file mode 100644
index 0000000..18d9810
--- /dev/null
+++ b/packages/rfw/example/hello/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>IDEDidComputeMac32BitWarning</key>
+	<true/>
+</dict>
+</plist>
diff --git a/packages/rfw/example/hello/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/rfw/example/hello/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
new file mode 100644
index 0000000..f9b0d7c
--- /dev/null
+++ b/packages/rfw/example/hello/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>PreviewsEnabled</key>
+	<false/>
+</dict>
+</plist>
diff --git a/packages/rfw/example/hello/ios/Runner/AppDelegate.swift b/packages/rfw/example/hello/ios/Runner/AppDelegate.swift
new file mode 100644
index 0000000..caf9983
--- /dev/null
+++ b/packages/rfw/example/hello/ios/Runner/AppDelegate.swift
@@ -0,0 +1,17 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import UIKit
+import Flutter
+
+@UIApplicationMain
+@objc class AppDelegate: FlutterAppDelegate {
+  override func application(
+    _ application: UIApplication,
+    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
+  ) -> Bool {
+    GeneratedPluginRegistrant.register(with: self)
+    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
+  }
+}
diff --git a/packages/rfw/example/hello/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/packages/rfw/example/hello/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
new file mode 100644
index 0000000..d36b1fa
--- /dev/null
+++ b/packages/rfw/example/hello/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -0,0 +1,122 @@
+{
+  "images" : [
+    {
+      "size" : "20x20",
+      "idiom" : "iphone",
+      "filename" : "Icon-App-20x20@2x.png",
+      "scale" : "2x"
+    },
+    {
+      "size" : "20x20",
+      "idiom" : "iphone",
+      "filename" : "Icon-App-20x20@3x.png",
+      "scale" : "3x"
+    },
+    {
+      "size" : "29x29",
+      "idiom" : "iphone",
+      "filename" : "Icon-App-29x29@1x.png",
+      "scale" : "1x"
+    },
+    {
+      "size" : "29x29",
+      "idiom" : "iphone",
+      "filename" : "Icon-App-29x29@2x.png",
+      "scale" : "2x"
+    },
+    {
+      "size" : "29x29",
+      "idiom" : "iphone",
+      "filename" : "Icon-App-29x29@3x.png",
+      "scale" : "3x"
+    },
+    {
+      "size" : "40x40",
+      "idiom" : "iphone",
+      "filename" : "Icon-App-40x40@2x.png",
+      "scale" : "2x"
+    },
+    {
+      "size" : "40x40",
+      "idiom" : "iphone",
+      "filename" : "Icon-App-40x40@3x.png",
+      "scale" : "3x"
+    },
+    {
+      "size" : "60x60",
+      "idiom" : "iphone",
+      "filename" : "Icon-App-60x60@2x.png",
+      "scale" : "2x"
+    },
+    {
+      "size" : "60x60",
+      "idiom" : "iphone",
+      "filename" : "Icon-App-60x60@3x.png",
+      "scale" : "3x"
+    },
+    {
+      "size" : "20x20",
+      "idiom" : "ipad",
+      "filename" : "Icon-App-20x20@1x.png",
+      "scale" : "1x"
+    },
+    {
+      "size" : "20x20",
+      "idiom" : "ipad",
+      "filename" : "Icon-App-20x20@2x.png",
+      "scale" : "2x"
+    },
+    {
+      "size" : "29x29",
+      "idiom" : "ipad",
+      "filename" : "Icon-App-29x29@1x.png",
+      "scale" : "1x"
+    },
+    {
+      "size" : "29x29",
+      "idiom" : "ipad",
+      "filename" : "Icon-App-29x29@2x.png",
+      "scale" : "2x"
+    },
+    {
+      "size" : "40x40",
+      "idiom" : "ipad",
+      "filename" : "Icon-App-40x40@1x.png",
+      "scale" : "1x"
+    },
+    {
+      "size" : "40x40",
+      "idiom" : "ipad",
+      "filename" : "Icon-App-40x40@2x.png",
+      "scale" : "2x"
+    },
+    {
+      "size" : "76x76",
+      "idiom" : "ipad",
+      "filename" : "Icon-App-76x76@1x.png",
+      "scale" : "1x"
+    },
+    {
+      "size" : "76x76",
+      "idiom" : "ipad",
+      "filename" : "Icon-App-76x76@2x.png",
+      "scale" : "2x"
+    },
+    {
+      "size" : "83.5x83.5",
+      "idiom" : "ipad",
+      "filename" : "Icon-App-83.5x83.5@2x.png",
+      "scale" : "2x"
+    },
+    {
+      "size" : "1024x1024",
+      "idiom" : "ios-marketing",
+      "filename" : "Icon-App-1024x1024@1x.png",
+      "scale" : "1x"
+    }
+  ],
+  "info" : {
+    "version" : 1,
+    "author" : "xcode"
+  }
+}
diff --git a/packages/rfw/example/hello/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/packages/rfw/example/hello/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
new file mode 100644
index 0000000..dc9ada4
--- /dev/null
+++ b/packages/rfw/example/hello/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
Binary files differ
diff --git a/packages/rfw/example/hello/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/packages/rfw/example/hello/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
new file mode 100644
index 0000000..28c6bf0
--- /dev/null
+++ b/packages/rfw/example/hello/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
Binary files differ
diff --git a/packages/rfw/example/hello/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/packages/rfw/example/hello/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
new file mode 100644
index 0000000..2ccbfd9
--- /dev/null
+++ b/packages/rfw/example/hello/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
Binary files differ
diff --git a/packages/rfw/example/hello/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/packages/rfw/example/hello/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
new file mode 100644
index 0000000..f091b6b
--- /dev/null
+++ b/packages/rfw/example/hello/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
Binary files differ
diff --git a/packages/rfw/example/hello/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/packages/rfw/example/hello/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
new file mode 100644
index 0000000..4cde121
--- /dev/null
+++ b/packages/rfw/example/hello/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
Binary files differ
diff --git a/packages/rfw/example/hello/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/packages/rfw/example/hello/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
new file mode 100644
index 0000000..d0ef06e
--- /dev/null
+++ b/packages/rfw/example/hello/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
Binary files differ
diff --git a/packages/rfw/example/hello/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/packages/rfw/example/hello/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
new file mode 100644
index 0000000..dcdc230
--- /dev/null
+++ b/packages/rfw/example/hello/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
Binary files differ
diff --git a/packages/rfw/example/hello/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/packages/rfw/example/hello/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
new file mode 100644
index 0000000..2ccbfd9
--- /dev/null
+++ b/packages/rfw/example/hello/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
Binary files differ
diff --git a/packages/rfw/example/hello/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/packages/rfw/example/hello/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
new file mode 100644
index 0000000..c8f9ed8
--- /dev/null
+++ b/packages/rfw/example/hello/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
Binary files differ
diff --git a/packages/rfw/example/hello/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/packages/rfw/example/hello/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
new file mode 100644
index 0000000..a6d6b86
--- /dev/null
+++ b/packages/rfw/example/hello/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
Binary files differ
diff --git a/packages/rfw/example/hello/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/packages/rfw/example/hello/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
new file mode 100644
index 0000000..a6d6b86
--- /dev/null
+++ b/packages/rfw/example/hello/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
Binary files differ
diff --git a/packages/rfw/example/hello/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/packages/rfw/example/hello/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
new file mode 100644
index 0000000..75b2d16
--- /dev/null
+++ b/packages/rfw/example/hello/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
Binary files differ
diff --git a/packages/rfw/example/hello/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/packages/rfw/example/hello/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
new file mode 100644
index 0000000..c4df70d
--- /dev/null
+++ b/packages/rfw/example/hello/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
Binary files differ
diff --git a/packages/rfw/example/hello/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/packages/rfw/example/hello/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
new file mode 100644
index 0000000..6a84f41
--- /dev/null
+++ b/packages/rfw/example/hello/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
Binary files differ
diff --git a/packages/rfw/example/hello/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/packages/rfw/example/hello/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
new file mode 100644
index 0000000..d0e1f58
--- /dev/null
+++ b/packages/rfw/example/hello/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
Binary files differ
diff --git a/packages/rfw/example/hello/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/packages/rfw/example/hello/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json
new file mode 100644
index 0000000..0bedcf2
--- /dev/null
+++ b/packages/rfw/example/hello/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json
@@ -0,0 +1,23 @@
+{
+  "images" : [
+    {
+      "idiom" : "universal",
+      "filename" : "LaunchImage.png",
+      "scale" : "1x"
+    },
+    {
+      "idiom" : "universal",
+      "filename" : "LaunchImage@2x.png",
+      "scale" : "2x"
+    },
+    {
+      "idiom" : "universal",
+      "filename" : "LaunchImage@3x.png",
+      "scale" : "3x"
+    }
+  ],
+  "info" : {
+    "version" : 1,
+    "author" : "xcode"
+  }
+}
diff --git a/packages/rfw/example/hello/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/packages/rfw/example/hello/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
new file mode 100644
index 0000000..9da19ea
--- /dev/null
+++ b/packages/rfw/example/hello/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
Binary files differ
diff --git a/packages/rfw/example/hello/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/packages/rfw/example/hello/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
new file mode 100644
index 0000000..9da19ea
--- /dev/null
+++ b/packages/rfw/example/hello/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
Binary files differ
diff --git a/packages/rfw/example/hello/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/packages/rfw/example/hello/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
new file mode 100644
index 0000000..9da19ea
--- /dev/null
+++ b/packages/rfw/example/hello/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
Binary files differ
diff --git a/packages/rfw/example/hello/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/packages/rfw/example/hello/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md
new file mode 100644
index 0000000..89c2725
--- /dev/null
+++ b/packages/rfw/example/hello/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md
@@ -0,0 +1,5 @@
+# Launch Screen Assets
+
+You can customize the launch screen with your own desired assets by replacing the image files in this directory.
+
+You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.
\ No newline at end of file
diff --git a/packages/rfw/example/hello/ios/Runner/Base.lproj/LaunchScreen.storyboard b/packages/rfw/example/hello/ios/Runner/Base.lproj/LaunchScreen.storyboard
new file mode 100644
index 0000000..f2e259c
--- /dev/null
+++ b/packages/rfw/example/hello/ios/Runner/Base.lproj/LaunchScreen.storyboard
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16G29" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
+    <dependencies>
+        <deployment identifier="iOS"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/>
+    </dependencies>
+    <scenes>
+        <!--View Controller-->
+        <scene sceneID="EHf-IW-A2E">
+            <objects>
+                <viewController id="01J-lp-oVM" sceneMemberID="viewController">
+                    <layoutGuides>
+                        <viewControllerLayoutGuide type="top" id="Ydg-fD-yQy"/>
+                        <viewControllerLayoutGuide type="bottom" id="xbc-2k-c8Z"/>
+                    </layoutGuides>
+                    <view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
+                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+                        <subviews>
+                            <imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4">
+                            </imageView>
+                        </subviews>
+                        <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+                        <constraints>
+                            <constraint firstItem="YRO-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/>
+                            <constraint firstItem="YRO-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4X2-HB-R7a"/>
+                        </constraints>
+                    </view>
+                </viewController>
+                <placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
+            </objects>
+            <point key="canvasLocation" x="53" y="375"/>
+        </scene>
+    </scenes>
+    <resources>
+        <image name="LaunchImage" width="168" height="185"/>
+    </resources>
+</document>
diff --git a/packages/rfw/example/hello/ios/Runner/Base.lproj/Main.storyboard b/packages/rfw/example/hello/ios/Runner/Base.lproj/Main.storyboard
new file mode 100644
index 0000000..f3c2851
--- /dev/null
+++ b/packages/rfw/example/hello/ios/Runner/Base.lproj/Main.storyboard
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
+    <dependencies>
+        <deployment identifier="iOS"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
+    </dependencies>
+    <scenes>
+        <!--Flutter View Controller-->
+        <scene sceneID="tne-QT-ifu">
+            <objects>
+                <viewController id="BYZ-38-t0r" customClass="FlutterViewController" sceneMemberID="viewController">
+                    <layoutGuides>
+                        <viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
+                        <viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
+                    </layoutGuides>
+                    <view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
+                        <rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
+                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+                        <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
+                    </view>
+                </viewController>
+                <placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
+            </objects>
+        </scene>
+    </scenes>
+</document>
diff --git a/packages/rfw/example/hello/ios/Runner/Info.plist b/packages/rfw/example/hello/ios/Runner/Info.plist
new file mode 100644
index 0000000..cd36e6d
--- /dev/null
+++ b/packages/rfw/example/hello/ios/Runner/Info.plist
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>CFBundleDevelopmentRegion</key>
+	<string>$(DEVELOPMENT_LANGUAGE)</string>
+	<key>CFBundleExecutable</key>
+	<string>$(EXECUTABLE_NAME)</string>
+	<key>CFBundleIdentifier</key>
+	<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
+	<key>CFBundleInfoDictionaryVersion</key>
+	<string>6.0</string>
+	<key>CFBundleName</key>
+	<string>hello</string>
+	<key>CFBundlePackageType</key>
+	<string>APPL</string>
+	<key>CFBundleShortVersionString</key>
+	<string>$(FLUTTER_BUILD_NAME)</string>
+	<key>CFBundleSignature</key>
+	<string>????</string>
+	<key>CFBundleVersion</key>
+	<string>$(FLUTTER_BUILD_NUMBER)</string>
+	<key>LSRequiresIPhoneOS</key>
+	<true/>
+	<key>UILaunchStoryboardName</key>
+	<string>LaunchScreen</string>
+	<key>UIMainStoryboardFile</key>
+	<string>Main</string>
+	<key>UISupportedInterfaceOrientations</key>
+	<array>
+		<string>UIInterfaceOrientationPortrait</string>
+		<string>UIInterfaceOrientationLandscapeLeft</string>
+		<string>UIInterfaceOrientationLandscapeRight</string>
+	</array>
+	<key>UISupportedInterfaceOrientations~ipad</key>
+	<array>
+		<string>UIInterfaceOrientationPortrait</string>
+		<string>UIInterfaceOrientationPortraitUpsideDown</string>
+		<string>UIInterfaceOrientationLandscapeLeft</string>
+		<string>UIInterfaceOrientationLandscapeRight</string>
+	</array>
+	<key>UIViewControllerBasedStatusBarAppearance</key>
+	<false/>
+</dict>
+</plist>
diff --git a/packages/rfw/example/hello/ios/Runner/Runner-Bridging-Header.h b/packages/rfw/example/hello/ios/Runner/Runner-Bridging-Header.h
new file mode 100644
index 0000000..eb7e8ba
--- /dev/null
+++ b/packages/rfw/example/hello/ios/Runner/Runner-Bridging-Header.h
@@ -0,0 +1,5 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "GeneratedPluginRegistrant.h"
diff --git a/packages/rfw/example/hello/lib/main.dart b/packages/rfw/example/hello/lib/main.dart
new file mode 100644
index 0000000..dd035a0
--- /dev/null
+++ b/packages/rfw/example/hello/lib/main.dart
@@ -0,0 +1,75 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:flutter/widgets.dart';
+
+/// For clarity, this example uses the text representation of the sample remote
+/// widget library, and parses it locally. To do this, [parseLibraryFile] is
+/// used. In production, this is strongly discouraged since it is 10x slower
+/// than using [decodeLibraryBlob] to parse the binary version of the format.
+import 'package:rfw/formats.dart' show parseLibraryFile;
+
+import 'package:rfw/rfw.dart';
+
+void main() {
+  runApp(const Example());
+}
+
+class Example extends StatefulWidget {
+  const Example({Key? key}) : super(key: key);
+
+  @override
+  State<Example> createState() => _ExampleState();
+}
+
+class _ExampleState extends State<Example> {
+  final Runtime _runtime = Runtime();
+  final DynamicContent _data = DynamicContent();
+
+  // Normally this would be obtained dynamically, but for this example
+  // we hard-code the "remote" widgets into the app.
+  //
+  // Also, normally we would decode this with [decodeLibraryBlob] rather than
+  // parsing the text version using [parseLibraryFile]. However, to make it
+  // easier to demo, this uses the slower text format.
+  static final RemoteWidgetLibrary _remoteWidgets = parseLibraryFile('''
+    // The "import" keyword is used to specify dependencies, in this case,
+    // the built-in widgets that are added by initState below.
+    import core.widgets;
+    // The "widget" keyword is used to define a new widget constructor.
+    // The "root" widget is specified as the one to render in the build
+    // method below.
+    widget root = Container(
+      color: 0xFF002211,
+      child: Center(
+        child: Text(text: ["Hello, ", data.greet.name, "!"], textDirection: "ltr"),
+      ),
+    );
+  ''');
+
+  static const LibraryName coreName = LibraryName(<String>['core', 'widgets']);
+  static const LibraryName mainName = LibraryName(<String>['main']);
+
+  @override
+  void initState() {
+    super.initState();
+    _runtime.update(coreName, createCoreWidgets());
+    _runtime.update(mainName, _remoteWidgets);
+    _data.update('greet', <String, Object>{'name': 'World'});
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return RemoteWidget(
+      runtime: _runtime,
+      data: _data,
+      widget: const FullyQualifiedWidgetName(mainName, 'root'),
+      onEvent: (String name, DynamicMap arguments) {
+        // The example above does not have any way to trigger events, but if it
+        // did, they would result in this callback being invoked.
+        debugPrint('user triggered event "$name" with data: $arguments');
+      },
+    );
+  }
+}
diff --git a/packages/rfw/example/hello/linux/.gitignore b/packages/rfw/example/hello/linux/.gitignore
new file mode 100644
index 0000000..d3896c9
--- /dev/null
+++ b/packages/rfw/example/hello/linux/.gitignore
@@ -0,0 +1 @@
+flutter/ephemeral
diff --git a/packages/rfw/example/hello/linux/CMakeLists.txt b/packages/rfw/example/hello/linux/CMakeLists.txt
new file mode 100644
index 0000000..e298375
--- /dev/null
+++ b/packages/rfw/example/hello/linux/CMakeLists.txt
@@ -0,0 +1,116 @@
+cmake_minimum_required(VERSION 3.10)
+project(runner LANGUAGES CXX)
+
+set(BINARY_NAME "hello")
+set(APPLICATION_ID "dev.flutter.rfw.examples.hello")
+
+cmake_policy(SET CMP0063 NEW)
+
+set(CMAKE_INSTALL_RPATH "$ORIGIN/lib")
+
+# Root filesystem for cross-building.
+if(FLUTTER_TARGET_PLATFORM_SYSROOT)
+  set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT})
+  set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT})
+  set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
+  set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
+  set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
+  set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
+endif()
+
+# Configure build options.
+if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
+  set(CMAKE_BUILD_TYPE "Debug" CACHE
+    STRING "Flutter build mode" FORCE)
+  set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
+    "Debug" "Profile" "Release")
+endif()
+
+# Compilation settings that should be applied to most targets.
+function(APPLY_STANDARD_SETTINGS TARGET)
+  target_compile_features(${TARGET} PUBLIC cxx_std_14)
+  target_compile_options(${TARGET} PRIVATE -Wall -Werror)
+  target_compile_options(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:-O3>")
+  target_compile_definitions(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:NDEBUG>")
+endfunction()
+
+set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter")
+
+# Flutter library and tool build rules.
+add_subdirectory(${FLUTTER_MANAGED_DIR})
+
+# System-level dependencies.
+find_package(PkgConfig REQUIRED)
+pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
+
+add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}")
+
+# Application build
+add_executable(${BINARY_NAME}
+  "main.cc"
+  "my_application.cc"
+  "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc"
+)
+apply_standard_settings(${BINARY_NAME})
+target_link_libraries(${BINARY_NAME} PRIVATE flutter)
+target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK)
+add_dependencies(${BINARY_NAME} flutter_assemble)
+# Only the install-generated bundle's copy of the executable will launch
+# correctly, since the resources must in the right relative locations. To avoid
+# people trying to run the unbundled copy, put it in a subdirectory instead of
+# the default top-level location.
+set_target_properties(${BINARY_NAME}
+  PROPERTIES
+  RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run"
+)
+
+# Generated plugin build rules, which manage building the plugins and adding
+# them to the application.
+include(flutter/generated_plugins.cmake)
+
+
+# === Installation ===
+# By default, "installing" just makes a relocatable bundle in the build
+# directory.
+set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle")
+if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
+  set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE)
+endif()
+
+# Start with a clean build bundle directory every time.
+install(CODE "
+  file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\")
+  " COMPONENT Runtime)
+
+set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data")
+set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib")
+
+install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}"
+  COMPONENT Runtime)
+
+install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
+  COMPONENT Runtime)
+
+install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
+  COMPONENT Runtime)
+
+if(PLUGIN_BUNDLED_LIBRARIES)
+  install(FILES "${PLUGIN_BUNDLED_LIBRARIES}"
+    DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
+    COMPONENT Runtime)
+endif()
+
+# Fully re-copy the assets directory on each build to avoid having stale files
+# from a previous install.
+set(FLUTTER_ASSET_DIR_NAME "flutter_assets")
+install(CODE "
+  file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\")
+  " COMPONENT Runtime)
+install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}"
+  DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime)
+
+# Install the AOT library on non-Debug builds only.
+if(NOT CMAKE_BUILD_TYPE MATCHES "Debug")
+  install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
+    COMPONENT Runtime)
+endif()
diff --git a/packages/rfw/example/hello/linux/flutter/CMakeLists.txt b/packages/rfw/example/hello/linux/flutter/CMakeLists.txt
new file mode 100644
index 0000000..33fd580
--- /dev/null
+++ b/packages/rfw/example/hello/linux/flutter/CMakeLists.txt
@@ -0,0 +1,87 @@
+cmake_minimum_required(VERSION 3.10)
+
+set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral")
+
+# Configuration provided via flutter tool.
+include(${EPHEMERAL_DIR}/generated_config.cmake)
+
+# TODO: Move the rest of this into files in ephemeral. See
+# https://github.com/flutter/flutter/issues/57146.
+
+# Serves the same purpose as list(TRANSFORM ... PREPEND ...),
+# which isn't available in 3.10.
+function(list_prepend LIST_NAME PREFIX)
+    set(NEW_LIST "")
+    foreach(element ${${LIST_NAME}})
+        list(APPEND NEW_LIST "${PREFIX}${element}")
+    endforeach(element)
+    set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE)
+endfunction()
+
+# === Flutter Library ===
+# System-level dependencies.
+find_package(PkgConfig REQUIRED)
+pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
+pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0)
+pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0)
+
+set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so")
+
+# Published to parent scope for install step.
+set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE)
+set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE)
+set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE)
+set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE)
+
+list(APPEND FLUTTER_LIBRARY_HEADERS
+  "fl_basic_message_channel.h"
+  "fl_binary_codec.h"
+  "fl_binary_messenger.h"
+  "fl_dart_project.h"
+  "fl_engine.h"
+  "fl_json_message_codec.h"
+  "fl_json_method_codec.h"
+  "fl_message_codec.h"
+  "fl_method_call.h"
+  "fl_method_channel.h"
+  "fl_method_codec.h"
+  "fl_method_response.h"
+  "fl_plugin_registrar.h"
+  "fl_plugin_registry.h"
+  "fl_standard_message_codec.h"
+  "fl_standard_method_codec.h"
+  "fl_string_codec.h"
+  "fl_value.h"
+  "fl_view.h"
+  "flutter_linux.h"
+)
+list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/")
+add_library(flutter INTERFACE)
+target_include_directories(flutter INTERFACE
+  "${EPHEMERAL_DIR}"
+)
+target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}")
+target_link_libraries(flutter INTERFACE
+  PkgConfig::GTK
+  PkgConfig::GLIB
+  PkgConfig::GIO
+)
+add_dependencies(flutter flutter_assemble)
+
+# === Flutter tool backend ===
+# _phony_ is a non-existent file to force this command to run every time,
+# since currently there's no way to get a full input/output list from the
+# flutter tool.
+add_custom_command(
+  OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS}
+    ${CMAKE_CURRENT_BINARY_DIR}/_phony_
+  COMMAND ${CMAKE_COMMAND} -E env
+    ${FLUTTER_TOOL_ENVIRONMENT}
+    "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh"
+      ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE}
+  VERBATIM
+)
+add_custom_target(flutter_assemble DEPENDS
+  "${FLUTTER_LIBRARY}"
+  ${FLUTTER_LIBRARY_HEADERS}
+)
diff --git a/packages/rfw/example/hello/linux/flutter/generated_plugins.cmake b/packages/rfw/example/hello/linux/flutter/generated_plugins.cmake
new file mode 100644
index 0000000..51436ae
--- /dev/null
+++ b/packages/rfw/example/hello/linux/flutter/generated_plugins.cmake
@@ -0,0 +1,15 @@
+#
+# Generated file, do not edit.
+#
+
+list(APPEND FLUTTER_PLUGIN_LIST
+)
+
+set(PLUGIN_BUNDLED_LIBRARIES)
+
+foreach(plugin ${FLUTTER_PLUGIN_LIST})
+  add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin})
+  target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)
+  list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>)
+  list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
+endforeach(plugin)
diff --git a/packages/rfw/example/hello/linux/main.cc b/packages/rfw/example/hello/linux/main.cc
new file mode 100644
index 0000000..1507d02
--- /dev/null
+++ b/packages/rfw/example/hello/linux/main.cc
@@ -0,0 +1,10 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "my_application.h"
+
+int main(int argc, char** argv) {
+  g_autoptr(MyApplication) app = my_application_new();
+  return g_application_run(G_APPLICATION(app), argc, argv);
+}
diff --git a/packages/rfw/example/hello/linux/my_application.cc b/packages/rfw/example/hello/linux/my_application.cc
new file mode 100644
index 0000000..f33831f
--- /dev/null
+++ b/packages/rfw/example/hello/linux/my_application.cc
@@ -0,0 +1,111 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "my_application.h"
+
+#include <flutter_linux/flutter_linux.h>
+#ifdef GDK_WINDOWING_X11
+#include <gdk/gdkx.h>
+#endif
+
+#include "flutter/generated_plugin_registrant.h"
+
+struct _MyApplication {
+  GtkApplication parent_instance;
+  char** dart_entrypoint_arguments;
+};
+
+G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION)
+
+// Implements GApplication::activate.
+static void my_application_activate(GApplication* application) {
+  MyApplication* self = MY_APPLICATION(application);
+  GtkWindow* window =
+      GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application)));
+
+  // Use a header bar when running in GNOME as this is the common style used
+  // by applications and is the setup most users will be using (e.g. Ubuntu
+  // desktop).
+  // If running on X and not using GNOME then just use a traditional title bar
+  // in case the window manager does more exotic layout, e.g. tiling.
+  // If running on Wayland assume the header bar will work (may need changing
+  // if future cases occur).
+  gboolean use_header_bar = TRUE;
+#ifdef GDK_WINDOWING_X11
+  GdkScreen* screen = gtk_window_get_screen(window);
+  if (GDK_IS_X11_SCREEN(screen)) {
+    const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen);
+    if (g_strcmp0(wm_name, "GNOME Shell") != 0) {
+      use_header_bar = FALSE;
+    }
+  }
+#endif
+  if (use_header_bar) {
+    GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new());
+    gtk_widget_show(GTK_WIDGET(header_bar));
+    gtk_header_bar_set_title(header_bar, "hello");
+    gtk_header_bar_set_show_close_button(header_bar, TRUE);
+    gtk_window_set_titlebar(window, GTK_WIDGET(header_bar));
+  } else {
+    gtk_window_set_title(window, "hello");
+  }
+
+  gtk_window_set_default_size(window, 1280, 720);
+  gtk_widget_show(GTK_WIDGET(window));
+
+  g_autoptr(FlDartProject) project = fl_dart_project_new();
+  fl_dart_project_set_dart_entrypoint_arguments(
+      project, self->dart_entrypoint_arguments);
+
+  FlView* view = fl_view_new(project);
+  gtk_widget_show(GTK_WIDGET(view));
+  gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view));
+
+  fl_register_plugins(FL_PLUGIN_REGISTRY(view));
+
+  gtk_widget_grab_focus(GTK_WIDGET(view));
+}
+
+// Implements GApplication::local_command_line.
+static gboolean my_application_local_command_line(GApplication* application,
+                                                  gchar*** arguments,
+                                                  int* exit_status) {
+  MyApplication* self = MY_APPLICATION(application);
+  // Strip out the first argument as it is the binary name.
+  self->dart_entrypoint_arguments = g_strdupv(*arguments + 1);
+
+  g_autoptr(GError) error = nullptr;
+  if (!g_application_register(application, nullptr, &error)) {
+    g_warning("Failed to register: %s", error->message);
+    *exit_status = 1;
+    return TRUE;
+  }
+
+  g_application_activate(application);
+  *exit_status = 0;
+
+  return TRUE;
+}
+
+// Implements GObject::dispose.
+static void my_application_dispose(GObject* object) {
+  MyApplication* self = MY_APPLICATION(object);
+  g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev);
+  G_OBJECT_CLASS(my_application_parent_class)->dispose(object);
+}
+
+static void my_application_class_init(MyApplicationClass* klass) {
+  G_APPLICATION_CLASS(klass)->activate = my_application_activate;
+  G_APPLICATION_CLASS(klass)->local_command_line =
+      my_application_local_command_line;
+  G_OBJECT_CLASS(klass)->dispose = my_application_dispose;
+}
+
+static void my_application_init(MyApplication* self) {}
+
+MyApplication* my_application_new() {
+  return MY_APPLICATION(g_object_new(my_application_get_type(),
+                                     "application-id", APPLICATION_ID, "flags",
+                                     G_APPLICATION_NON_UNIQUE, nullptr));
+}
diff --git a/packages/rfw/example/hello/linux/my_application.h b/packages/rfw/example/hello/linux/my_application.h
new file mode 100644
index 0000000..6e9f0c3
--- /dev/null
+++ b/packages/rfw/example/hello/linux/my_application.h
@@ -0,0 +1,22 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef FLUTTER_MY_APPLICATION_H_
+#define FLUTTER_MY_APPLICATION_H_
+
+#include <gtk/gtk.h>
+
+G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION,
+                     GtkApplication)
+
+/**
+ * my_application_new:
+ *
+ * Creates a new Flutter-based application.
+ *
+ * Returns: a new #MyApplication.
+ */
+MyApplication* my_application_new();
+
+#endif  // FLUTTER_MY_APPLICATION_H_
diff --git a/packages/rfw/example/hello/macos/.gitignore b/packages/rfw/example/hello/macos/.gitignore
new file mode 100644
index 0000000..d2fd377
--- /dev/null
+++ b/packages/rfw/example/hello/macos/.gitignore
@@ -0,0 +1,6 @@
+# Flutter-related
+**/Flutter/ephemeral/
+**/Pods/
+
+# Xcode-related
+**/xcuserdata/
diff --git a/packages/rfw/example/hello/macos/Flutter/Flutter-Debug.xcconfig b/packages/rfw/example/hello/macos/Flutter/Flutter-Debug.xcconfig
new file mode 100644
index 0000000..c2efd0b
--- /dev/null
+++ b/packages/rfw/example/hello/macos/Flutter/Flutter-Debug.xcconfig
@@ -0,0 +1 @@
+#include "ephemeral/Flutter-Generated.xcconfig"
diff --git a/packages/rfw/example/hello/macos/Flutter/Flutter-Release.xcconfig b/packages/rfw/example/hello/macos/Flutter/Flutter-Release.xcconfig
new file mode 100644
index 0000000..c2efd0b
--- /dev/null
+++ b/packages/rfw/example/hello/macos/Flutter/Flutter-Release.xcconfig
@@ -0,0 +1 @@
+#include "ephemeral/Flutter-Generated.xcconfig"
diff --git a/packages/rfw/example/hello/macos/Runner.xcodeproj/project.pbxproj b/packages/rfw/example/hello/macos/Runner.xcodeproj/project.pbxproj
new file mode 100644
index 0000000..e3be739
--- /dev/null
+++ b/packages/rfw/example/hello/macos/Runner.xcodeproj/project.pbxproj
@@ -0,0 +1,572 @@
+// !$*UTF8*$!
+{
+	archiveVersion = 1;
+	classes = {
+	};
+	objectVersion = 51;
+	objects = {
+
+/* Begin PBXAggregateTarget section */
+		33CC111A2044C6BA0003C045 /* Flutter Assemble */ = {
+			isa = PBXAggregateTarget;
+			buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */;
+			buildPhases = (
+				33CC111E2044C6BF0003C045 /* ShellScript */,
+			);
+			dependencies = (
+			);
+			name = "Flutter Assemble";
+			productName = FLX;
+		};
+/* End PBXAggregateTarget section */
+
+/* Begin PBXBuildFile section */
+		335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; };
+		33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; };
+		33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; };
+		33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; };
+		33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXContainerItemProxy section */
+		33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = {
+			isa = PBXContainerItemProxy;
+			containerPortal = 33CC10E52044A3C60003C045 /* Project object */;
+			proxyType = 1;
+			remoteGlobalIDString = 33CC111A2044C6BA0003C045;
+			remoteInfo = FLX;
+		};
+/* End PBXContainerItemProxy section */
+
+/* Begin PBXCopyFilesBuildPhase section */
+		33CC110E2044A8840003C045 /* Bundle Framework */ = {
+			isa = PBXCopyFilesBuildPhase;
+			buildActionMask = 2147483647;
+			dstPath = "";
+			dstSubfolderSpec = 10;
+			files = (
+			);
+			name = "Bundle Framework";
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXCopyFilesBuildPhase section */
+
+/* Begin PBXFileReference section */
+		333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = "<group>"; };
+		335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = "<group>"; };
+		33CC10ED2044A3C60003C045 /* hello.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "hello.app"; sourceTree = BUILT_PRODUCTS_DIR; };
+		33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
+		33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = "<group>"; };
+		33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = "<group>"; };
+		33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = "<group>"; };
+		33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = "<group>"; };
+		33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = "<group>"; };
+		33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = "<group>"; };
+		33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = "<group>"; };
+		33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = "<group>"; };
+		33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = "<group>"; };
+		33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = "<group>"; };
+		7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = "<group>"; };
+		9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = "<group>"; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+		33CC10EA2044A3C60003C045 /* Frameworks */ = {
+			isa = PBXFrameworksBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+		33BA886A226E78AF003329D5 /* Configs */ = {
+			isa = PBXGroup;
+			children = (
+				33E5194F232828860026EE4D /* AppInfo.xcconfig */,
+				9740EEB21CF90195004384FC /* Debug.xcconfig */,
+				7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
+				333000ED22D3DE5D00554162 /* Warnings.xcconfig */,
+			);
+			path = Configs;
+			sourceTree = "<group>";
+		};
+		33CC10E42044A3C60003C045 = {
+			isa = PBXGroup;
+			children = (
+				33FAB671232836740065AC1E /* Runner */,
+				33CEB47122A05771004F2AC0 /* Flutter */,
+				33CC10EE2044A3C60003C045 /* Products */,
+				D73912EC22F37F3D000D13A0 /* Frameworks */,
+			);
+			sourceTree = "<group>";
+		};
+		33CC10EE2044A3C60003C045 /* Products */ = {
+			isa = PBXGroup;
+			children = (
+				33CC10ED2044A3C60003C045 /* hello.app */,
+			);
+			name = Products;
+			sourceTree = "<group>";
+		};
+		33CC11242044D66E0003C045 /* Resources */ = {
+			isa = PBXGroup;
+			children = (
+				33CC10F22044A3C60003C045 /* Assets.xcassets */,
+				33CC10F42044A3C60003C045 /* MainMenu.xib */,
+				33CC10F72044A3C60003C045 /* Info.plist */,
+			);
+			name = Resources;
+			path = ..;
+			sourceTree = "<group>";
+		};
+		33CEB47122A05771004F2AC0 /* Flutter */ = {
+			isa = PBXGroup;
+			children = (
+				335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */,
+				33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */,
+				33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */,
+				33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */,
+			);
+			path = Flutter;
+			sourceTree = "<group>";
+		};
+		33FAB671232836740065AC1E /* Runner */ = {
+			isa = PBXGroup;
+			children = (
+				33CC10F02044A3C60003C045 /* AppDelegate.swift */,
+				33CC11122044BFA00003C045 /* MainFlutterWindow.swift */,
+				33E51913231747F40026EE4D /* DebugProfile.entitlements */,
+				33E51914231749380026EE4D /* Release.entitlements */,
+				33CC11242044D66E0003C045 /* Resources */,
+				33BA886A226E78AF003329D5 /* Configs */,
+			);
+			path = Runner;
+			sourceTree = "<group>";
+		};
+		D73912EC22F37F3D000D13A0 /* Frameworks */ = {
+			isa = PBXGroup;
+			children = (
+			);
+			name = Frameworks;
+			sourceTree = "<group>";
+		};
+/* End PBXGroup section */
+
+/* Begin PBXNativeTarget section */
+		33CC10EC2044A3C60003C045 /* Runner */ = {
+			isa = PBXNativeTarget;
+			buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */;
+			buildPhases = (
+				33CC10E92044A3C60003C045 /* Sources */,
+				33CC10EA2044A3C60003C045 /* Frameworks */,
+				33CC10EB2044A3C60003C045 /* Resources */,
+				33CC110E2044A8840003C045 /* Bundle Framework */,
+				3399D490228B24CF009A79C7 /* ShellScript */,
+			);
+			buildRules = (
+			);
+			dependencies = (
+				33CC11202044C79F0003C045 /* PBXTargetDependency */,
+			);
+			name = Runner;
+			productName = Runner;
+			productReference = 33CC10ED2044A3C60003C045 /* hello.app */;
+			productType = "com.apple.product-type.application";
+		};
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+		33CC10E52044A3C60003C045 /* Project object */ = {
+			isa = PBXProject;
+			attributes = {
+				LastSwiftUpdateCheck = 0920;
+				LastUpgradeCheck = 1300;
+				ORGANIZATIONNAME = "";
+				TargetAttributes = {
+					33CC10EC2044A3C60003C045 = {
+						CreatedOnToolsVersion = 9.2;
+						LastSwiftMigration = 1100;
+						ProvisioningStyle = Automatic;
+						SystemCapabilities = {
+							com.apple.Sandbox = {
+								enabled = 1;
+							};
+						};
+					};
+					33CC111A2044C6BA0003C045 = {
+						CreatedOnToolsVersion = 9.2;
+						ProvisioningStyle = Manual;
+					};
+				};
+			};
+			buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */;
+			compatibilityVersion = "Xcode 9.3";
+			developmentRegion = en;
+			hasScannedForEncodings = 0;
+			knownRegions = (
+				en,
+				Base,
+			);
+			mainGroup = 33CC10E42044A3C60003C045;
+			productRefGroup = 33CC10EE2044A3C60003C045 /* Products */;
+			projectDirPath = "";
+			projectRoot = "";
+			targets = (
+				33CC10EC2044A3C60003C045 /* Runner */,
+				33CC111A2044C6BA0003C045 /* Flutter Assemble */,
+			);
+		};
+/* End PBXProject section */
+
+/* Begin PBXResourcesBuildPhase section */
+		33CC10EB2044A3C60003C045 /* Resources */ = {
+			isa = PBXResourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */,
+				33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXResourcesBuildPhase section */
+
+/* Begin PBXShellScriptBuildPhase section */
+		3399D490228B24CF009A79C7 /* ShellScript */ = {
+			isa = PBXShellScriptBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			inputFileListPaths = (
+			);
+			inputPaths = (
+			);
+			outputFileListPaths = (
+			);
+			outputPaths = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+			shellPath = /bin/sh;
+			shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n";
+		};
+		33CC111E2044C6BF0003C045 /* ShellScript */ = {
+			isa = PBXShellScriptBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			inputFileListPaths = (
+				Flutter/ephemeral/FlutterInputs.xcfilelist,
+			);
+			inputPaths = (
+				Flutter/ephemeral/tripwire,
+			);
+			outputFileListPaths = (
+				Flutter/ephemeral/FlutterOutputs.xcfilelist,
+			);
+			outputPaths = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+			shellPath = /bin/sh;
+			shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire";
+		};
+/* End PBXShellScriptBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+		33CC10E92044A3C60003C045 /* Sources */ = {
+			isa = PBXSourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */,
+				33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */,
+				335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXSourcesBuildPhase section */
+
+/* Begin PBXTargetDependency section */
+		33CC11202044C79F0003C045 /* PBXTargetDependency */ = {
+			isa = PBXTargetDependency;
+			target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */;
+			targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */;
+		};
+/* End PBXTargetDependency section */
+
+/* Begin PBXVariantGroup section */
+		33CC10F42044A3C60003C045 /* MainMenu.xib */ = {
+			isa = PBXVariantGroup;
+			children = (
+				33CC10F52044A3C60003C045 /* Base */,
+			);
+			name = MainMenu.xib;
+			path = Runner;
+			sourceTree = "<group>";
+		};
+/* End PBXVariantGroup section */
+
+/* Begin XCBuildConfiguration section */
+		338D0CE9231458BD00FA5F75 /* Profile */ = {
+			isa = XCBuildConfiguration;
+			baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
+			buildSettings = {
+				ALWAYS_SEARCH_USER_PATHS = NO;
+				CLANG_ANALYZER_NONNULL = YES;
+				CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+				CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+				CLANG_CXX_LIBRARY = "libc++";
+				CLANG_ENABLE_MODULES = YES;
+				CLANG_ENABLE_OBJC_ARC = YES;
+				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+				CLANG_WARN_BOOL_CONVERSION = YES;
+				CLANG_WARN_CONSTANT_CONVERSION = YES;
+				CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+				CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+				CLANG_WARN_EMPTY_BODY = YES;
+				CLANG_WARN_ENUM_CONVERSION = YES;
+				CLANG_WARN_INFINITE_RECURSION = YES;
+				CLANG_WARN_INT_CONVERSION = YES;
+				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+				CLANG_WARN_SUSPICIOUS_MOVE = YES;
+				CODE_SIGN_IDENTITY = "-";
+				COPY_PHASE_STRIP = NO;
+				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+				ENABLE_NS_ASSERTIONS = NO;
+				ENABLE_STRICT_OBJC_MSGSEND = YES;
+				GCC_C_LANGUAGE_STANDARD = gnu11;
+				GCC_NO_COMMON_BLOCKS = YES;
+				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+				GCC_WARN_UNUSED_FUNCTION = YES;
+				GCC_WARN_UNUSED_VARIABLE = YES;
+				MACOSX_DEPLOYMENT_TARGET = 10.11;
+				MTL_ENABLE_DEBUG_INFO = NO;
+				SDKROOT = macosx;
+				SWIFT_COMPILATION_MODE = wholemodule;
+				SWIFT_OPTIMIZATION_LEVEL = "-O";
+			};
+			name = Profile;
+		};
+		338D0CEA231458BD00FA5F75 /* Profile */ = {
+			isa = XCBuildConfiguration;
+			baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */;
+			buildSettings = {
+				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+				CLANG_ENABLE_MODULES = YES;
+				CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements;
+				CODE_SIGN_STYLE = Automatic;
+				COMBINE_HIDPI_IMAGES = YES;
+				INFOPLIST_FILE = Runner/Info.plist;
+				LD_RUNPATH_SEARCH_PATHS = (
+					"$(inherited)",
+					"@executable_path/../Frameworks",
+				);
+				PROVISIONING_PROFILE_SPECIFIER = "";
+				SWIFT_VERSION = 5.0;
+			};
+			name = Profile;
+		};
+		338D0CEB231458BD00FA5F75 /* Profile */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				CODE_SIGN_STYLE = Manual;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+			};
+			name = Profile;
+		};
+		33CC10F92044A3C60003C045 /* Debug */ = {
+			isa = XCBuildConfiguration;
+			baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
+			buildSettings = {
+				ALWAYS_SEARCH_USER_PATHS = NO;
+				CLANG_ANALYZER_NONNULL = YES;
+				CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+				CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+				CLANG_CXX_LIBRARY = "libc++";
+				CLANG_ENABLE_MODULES = YES;
+				CLANG_ENABLE_OBJC_ARC = YES;
+				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+				CLANG_WARN_BOOL_CONVERSION = YES;
+				CLANG_WARN_CONSTANT_CONVERSION = YES;
+				CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+				CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+				CLANG_WARN_EMPTY_BODY = YES;
+				CLANG_WARN_ENUM_CONVERSION = YES;
+				CLANG_WARN_INFINITE_RECURSION = YES;
+				CLANG_WARN_INT_CONVERSION = YES;
+				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+				CLANG_WARN_SUSPICIOUS_MOVE = YES;
+				CODE_SIGN_IDENTITY = "-";
+				COPY_PHASE_STRIP = NO;
+				DEBUG_INFORMATION_FORMAT = dwarf;
+				ENABLE_STRICT_OBJC_MSGSEND = YES;
+				ENABLE_TESTABILITY = YES;
+				GCC_C_LANGUAGE_STANDARD = gnu11;
+				GCC_DYNAMIC_NO_PIC = NO;
+				GCC_NO_COMMON_BLOCKS = YES;
+				GCC_OPTIMIZATION_LEVEL = 0;
+				GCC_PREPROCESSOR_DEFINITIONS = (
+					"DEBUG=1",
+					"$(inherited)",
+				);
+				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+				GCC_WARN_UNUSED_FUNCTION = YES;
+				GCC_WARN_UNUSED_VARIABLE = YES;
+				MACOSX_DEPLOYMENT_TARGET = 10.11;
+				MTL_ENABLE_DEBUG_INFO = YES;
+				ONLY_ACTIVE_ARCH = YES;
+				SDKROOT = macosx;
+				SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
+				SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+			};
+			name = Debug;
+		};
+		33CC10FA2044A3C60003C045 /* Release */ = {
+			isa = XCBuildConfiguration;
+			baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
+			buildSettings = {
+				ALWAYS_SEARCH_USER_PATHS = NO;
+				CLANG_ANALYZER_NONNULL = YES;
+				CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+				CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+				CLANG_CXX_LIBRARY = "libc++";
+				CLANG_ENABLE_MODULES = YES;
+				CLANG_ENABLE_OBJC_ARC = YES;
+				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+				CLANG_WARN_BOOL_CONVERSION = YES;
+				CLANG_WARN_CONSTANT_CONVERSION = YES;
+				CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+				CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+				CLANG_WARN_EMPTY_BODY = YES;
+				CLANG_WARN_ENUM_CONVERSION = YES;
+				CLANG_WARN_INFINITE_RECURSION = YES;
+				CLANG_WARN_INT_CONVERSION = YES;
+				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+				CLANG_WARN_SUSPICIOUS_MOVE = YES;
+				CODE_SIGN_IDENTITY = "-";
+				COPY_PHASE_STRIP = NO;
+				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+				ENABLE_NS_ASSERTIONS = NO;
+				ENABLE_STRICT_OBJC_MSGSEND = YES;
+				GCC_C_LANGUAGE_STANDARD = gnu11;
+				GCC_NO_COMMON_BLOCKS = YES;
+				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+				GCC_WARN_UNUSED_FUNCTION = YES;
+				GCC_WARN_UNUSED_VARIABLE = YES;
+				MACOSX_DEPLOYMENT_TARGET = 10.11;
+				MTL_ENABLE_DEBUG_INFO = NO;
+				SDKROOT = macosx;
+				SWIFT_COMPILATION_MODE = wholemodule;
+				SWIFT_OPTIMIZATION_LEVEL = "-O";
+			};
+			name = Release;
+		};
+		33CC10FC2044A3C60003C045 /* Debug */ = {
+			isa = XCBuildConfiguration;
+			baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */;
+			buildSettings = {
+				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+				CLANG_ENABLE_MODULES = YES;
+				CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements;
+				CODE_SIGN_STYLE = Automatic;
+				COMBINE_HIDPI_IMAGES = YES;
+				INFOPLIST_FILE = Runner/Info.plist;
+				LD_RUNPATH_SEARCH_PATHS = (
+					"$(inherited)",
+					"@executable_path/../Frameworks",
+				);
+				PROVISIONING_PROFILE_SPECIFIER = "";
+				SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+				SWIFT_VERSION = 5.0;
+			};
+			name = Debug;
+		};
+		33CC10FD2044A3C60003C045 /* Release */ = {
+			isa = XCBuildConfiguration;
+			baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */;
+			buildSettings = {
+				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+				CLANG_ENABLE_MODULES = YES;
+				CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements;
+				CODE_SIGN_STYLE = Automatic;
+				COMBINE_HIDPI_IMAGES = YES;
+				INFOPLIST_FILE = Runner/Info.plist;
+				LD_RUNPATH_SEARCH_PATHS = (
+					"$(inherited)",
+					"@executable_path/../Frameworks",
+				);
+				PROVISIONING_PROFILE_SPECIFIER = "";
+				SWIFT_VERSION = 5.0;
+			};
+			name = Release;
+		};
+		33CC111C2044C6BA0003C045 /* Debug */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				CODE_SIGN_STYLE = Manual;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+			};
+			name = Debug;
+		};
+		33CC111D2044C6BA0003C045 /* Release */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				CODE_SIGN_STYLE = Automatic;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+			};
+			name = Release;
+		};
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+		33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				33CC10F92044A3C60003C045 /* Debug */,
+				33CC10FA2044A3C60003C045 /* Release */,
+				338D0CE9231458BD00FA5F75 /* Profile */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
+		};
+		33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				33CC10FC2044A3C60003C045 /* Debug */,
+				33CC10FD2044A3C60003C045 /* Release */,
+				338D0CEA231458BD00FA5F75 /* Profile */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
+		};
+		33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				33CC111C2044C6BA0003C045 /* Debug */,
+				33CC111D2044C6BA0003C045 /* Release */,
+				338D0CEB231458BD00FA5F75 /* Profile */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
+		};
+/* End XCConfigurationList section */
+	};
+	rootObject = 33CC10E52044A3C60003C045 /* Project object */;
+}
diff --git a/packages/rfw/example/hello/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/rfw/example/hello/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
new file mode 100644
index 0000000..18d9810
--- /dev/null
+++ b/packages/rfw/example/hello/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>IDEDidComputeMac32BitWarning</key>
+	<true/>
+</dict>
+</plist>
diff --git a/packages/rfw/example/hello/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/rfw/example/hello/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
new file mode 100644
index 0000000..8347df2
--- /dev/null
+++ b/packages/rfw/example/hello/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
@@ -0,0 +1,87 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Scheme
+   LastUpgradeVersion = "1300"
+   version = "1.3">
+   <BuildAction
+      parallelizeBuildables = "YES"
+      buildImplicitDependencies = "YES">
+      <BuildActionEntries>
+         <BuildActionEntry
+            buildForTesting = "YES"
+            buildForRunning = "YES"
+            buildForProfiling = "YES"
+            buildForArchiving = "YES"
+            buildForAnalyzing = "YES">
+            <BuildableReference
+               BuildableIdentifier = "primary"
+               BlueprintIdentifier = "33CC10EC2044A3C60003C045"
+               BuildableName = "hello.app"
+               BlueprintName = "Runner"
+               ReferencedContainer = "container:Runner.xcodeproj">
+            </BuildableReference>
+         </BuildActionEntry>
+      </BuildActionEntries>
+   </BuildAction>
+   <TestAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      shouldUseLaunchSchemeArgsEnv = "YES">
+      <MacroExpansion>
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "33CC10EC2044A3C60003C045"
+            BuildableName = "hello.app"
+            BlueprintName = "Runner"
+            ReferencedContainer = "container:Runner.xcodeproj">
+         </BuildableReference>
+      </MacroExpansion>
+      <Testables>
+      </Testables>
+   </TestAction>
+   <LaunchAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      launchStyle = "0"
+      useCustomWorkingDirectory = "NO"
+      ignoresPersistentStateOnLaunch = "NO"
+      debugDocumentVersioning = "YES"
+      debugServiceExtension = "internal"
+      allowLocationSimulation = "YES">
+      <BuildableProductRunnable
+         runnableDebuggingMode = "0">
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "33CC10EC2044A3C60003C045"
+            BuildableName = "hello.app"
+            BlueprintName = "Runner"
+            ReferencedContainer = "container:Runner.xcodeproj">
+         </BuildableReference>
+      </BuildableProductRunnable>
+   </LaunchAction>
+   <ProfileAction
+      buildConfiguration = "Profile"
+      shouldUseLaunchSchemeArgsEnv = "YES"
+      savedToolIdentifier = ""
+      useCustomWorkingDirectory = "NO"
+      debugDocumentVersioning = "YES">
+      <BuildableProductRunnable
+         runnableDebuggingMode = "0">
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "33CC10EC2044A3C60003C045"
+            BuildableName = "hello.app"
+            BlueprintName = "Runner"
+            ReferencedContainer = "container:Runner.xcodeproj">
+         </BuildableReference>
+      </BuildableProductRunnable>
+   </ProfileAction>
+   <AnalyzeAction
+      buildConfiguration = "Debug">
+   </AnalyzeAction>
+   <ArchiveAction
+      buildConfiguration = "Release"
+      revealArchiveInOrganizer = "YES">
+   </ArchiveAction>
+</Scheme>
diff --git a/packages/rfw/example/hello/macos/Runner.xcworkspace/contents.xcworkspacedata b/packages/rfw/example/hello/macos/Runner.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 0000000..1d526a1
--- /dev/null
+++ b/packages/rfw/example/hello/macos/Runner.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Workspace
+   version = "1.0">
+   <FileRef
+      location = "group:Runner.xcodeproj">
+   </FileRef>
+</Workspace>
diff --git a/packages/rfw/example/hello/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/rfw/example/hello/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
new file mode 100644
index 0000000..18d9810
--- /dev/null
+++ b/packages/rfw/example/hello/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>IDEDidComputeMac32BitWarning</key>
+	<true/>
+</dict>
+</plist>
diff --git a/packages/rfw/example/hello/macos/Runner/AppDelegate.swift b/packages/rfw/example/hello/macos/Runner/AppDelegate.swift
new file mode 100644
index 0000000..5cec4c4
--- /dev/null
+++ b/packages/rfw/example/hello/macos/Runner/AppDelegate.swift
@@ -0,0 +1,13 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import Cocoa
+import FlutterMacOS
+
+@NSApplicationMain
+class AppDelegate: FlutterAppDelegate {
+  override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
+    return true
+  }
+}
diff --git a/packages/rfw/example/hello/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/packages/rfw/example/hello/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
new file mode 100644
index 0000000..a2ec33f
--- /dev/null
+++ b/packages/rfw/example/hello/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -0,0 +1,68 @@
+{
+  "images" : [
+    {
+      "size" : "16x16",
+      "idiom" : "mac",
+      "filename" : "app_icon_16.png",
+      "scale" : "1x"
+    },
+    {
+      "size" : "16x16",
+      "idiom" : "mac",
+      "filename" : "app_icon_32.png",
+      "scale" : "2x"
+    },
+    {
+      "size" : "32x32",
+      "idiom" : "mac",
+      "filename" : "app_icon_32.png",
+      "scale" : "1x"
+    },
+    {
+      "size" : "32x32",
+      "idiom" : "mac",
+      "filename" : "app_icon_64.png",
+      "scale" : "2x"
+    },
+    {
+      "size" : "128x128",
+      "idiom" : "mac",
+      "filename" : "app_icon_128.png",
+      "scale" : "1x"
+    },
+    {
+      "size" : "128x128",
+      "idiom" : "mac",
+      "filename" : "app_icon_256.png",
+      "scale" : "2x"
+    },
+    {
+      "size" : "256x256",
+      "idiom" : "mac",
+      "filename" : "app_icon_256.png",
+      "scale" : "1x"
+    },
+    {
+      "size" : "256x256",
+      "idiom" : "mac",
+      "filename" : "app_icon_512.png",
+      "scale" : "2x"
+    },
+    {
+      "size" : "512x512",
+      "idiom" : "mac",
+      "filename" : "app_icon_512.png",
+      "scale" : "1x"
+    },
+    {
+      "size" : "512x512",
+      "idiom" : "mac",
+      "filename" : "app_icon_1024.png",
+      "scale" : "2x"
+    }
+  ],
+  "info" : {
+    "version" : 1,
+    "author" : "xcode"
+  }
+}
diff --git a/packages/rfw/example/hello/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/packages/rfw/example/hello/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png
new file mode 100644
index 0000000..3c4935a
--- /dev/null
+++ b/packages/rfw/example/hello/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png
Binary files differ
diff --git a/packages/rfw/example/hello/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/packages/rfw/example/hello/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png
new file mode 100644
index 0000000..ed4cc16
--- /dev/null
+++ b/packages/rfw/example/hello/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png
Binary files differ
diff --git a/packages/rfw/example/hello/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/packages/rfw/example/hello/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png
new file mode 100644
index 0000000..483be61
--- /dev/null
+++ b/packages/rfw/example/hello/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png
Binary files differ
diff --git a/packages/rfw/example/hello/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/packages/rfw/example/hello/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png
new file mode 100644
index 0000000..bcbf36d
--- /dev/null
+++ b/packages/rfw/example/hello/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png
Binary files differ
diff --git a/packages/rfw/example/hello/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/packages/rfw/example/hello/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png
new file mode 100644
index 0000000..9c0a652
--- /dev/null
+++ b/packages/rfw/example/hello/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png
Binary files differ
diff --git a/packages/rfw/example/hello/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/packages/rfw/example/hello/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png
new file mode 100644
index 0000000..e71a726
--- /dev/null
+++ b/packages/rfw/example/hello/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png
Binary files differ
diff --git a/packages/rfw/example/hello/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/packages/rfw/example/hello/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png
new file mode 100644
index 0000000..8a31fe2
--- /dev/null
+++ b/packages/rfw/example/hello/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png
Binary files differ
diff --git a/packages/rfw/example/hello/macos/Runner/Base.lproj/MainMenu.xib b/packages/rfw/example/hello/macos/Runner/Base.lproj/MainMenu.xib
new file mode 100644
index 0000000..537341a
--- /dev/null
+++ b/packages/rfw/example/hello/macos/Runner/Base.lproj/MainMenu.xib
@@ -0,0 +1,339 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
+    <dependencies>
+        <deployment identifier="macosx"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14490.70"/>
+        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
+    </dependencies>
+    <objects>
+        <customObject id="-2" userLabel="File's Owner" customClass="NSApplication">
+            <connections>
+                <outlet property="delegate" destination="Voe-Tx-rLC" id="GzC-gU-4Uq"/>
+            </connections>
+        </customObject>
+        <customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
+        <customObject id="-3" userLabel="Application" customClass="NSObject"/>
+        <customObject id="Voe-Tx-rLC" customClass="AppDelegate" customModule="Runner" customModuleProvider="target">
+            <connections>
+                <outlet property="applicationMenu" destination="uQy-DD-JDr" id="XBo-yE-nKs"/>
+                <outlet property="mainFlutterWindow" destination="QvC-M9-y7g" id="gIp-Ho-8D9"/>
+            </connections>
+        </customObject>
+        <customObject id="YLy-65-1bz" customClass="NSFontManager"/>
+        <menu title="Main Menu" systemMenu="main" id="AYu-sK-qS6">
+            <items>
+                <menuItem title="APP_NAME" id="1Xt-HY-uBw">
+                    <modifierMask key="keyEquivalentModifierMask"/>
+                    <menu key="submenu" title="APP_NAME" systemMenu="apple" id="uQy-DD-JDr">
+                        <items>
+                            <menuItem title="About APP_NAME" id="5kV-Vb-QxS">
+                                <modifierMask key="keyEquivalentModifierMask"/>
+                                <connections>
+                                    <action selector="orderFrontStandardAboutPanel:" target="-1" id="Exp-CZ-Vem"/>
+                                </connections>
+                            </menuItem>
+                            <menuItem isSeparatorItem="YES" id="VOq-y0-SEH"/>
+                            <menuItem title="Preferences…" keyEquivalent="," id="BOF-NM-1cW"/>
+                            <menuItem isSeparatorItem="YES" id="wFC-TO-SCJ"/>
+                            <menuItem title="Services" id="NMo-om-nkz">
+                                <modifierMask key="keyEquivalentModifierMask"/>
+                                <menu key="submenu" title="Services" systemMenu="services" id="hz9-B4-Xy5"/>
+                            </menuItem>
+                            <menuItem isSeparatorItem="YES" id="4je-JR-u6R"/>
+                            <menuItem title="Hide APP_NAME" keyEquivalent="h" id="Olw-nP-bQN">
+                                <connections>
+                                    <action selector="hide:" target="-1" id="PnN-Uc-m68"/>
+                                </connections>
+                            </menuItem>
+                            <menuItem title="Hide Others" keyEquivalent="h" id="Vdr-fp-XzO">
+                                <modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
+                                <connections>
+                                    <action selector="hideOtherApplications:" target="-1" id="VT4-aY-XCT"/>
+                                </connections>
+                            </menuItem>
+                            <menuItem title="Show All" id="Kd2-mp-pUS">
+                                <modifierMask key="keyEquivalentModifierMask"/>
+                                <connections>
+                                    <action selector="unhideAllApplications:" target="-1" id="Dhg-Le-xox"/>
+                                </connections>
+                            </menuItem>
+                            <menuItem isSeparatorItem="YES" id="kCx-OE-vgT"/>
+                            <menuItem title="Quit APP_NAME" keyEquivalent="q" id="4sb-4s-VLi">
+                                <connections>
+                                    <action selector="terminate:" target="-1" id="Te7-pn-YzF"/>
+                                </connections>
+                            </menuItem>
+                        </items>
+                    </menu>
+                </menuItem>
+                <menuItem title="Edit" id="5QF-Oa-p0T">
+                    <modifierMask key="keyEquivalentModifierMask"/>
+                    <menu key="submenu" title="Edit" id="W48-6f-4Dl">
+                        <items>
+                            <menuItem title="Undo" keyEquivalent="z" id="dRJ-4n-Yzg">
+                                <connections>
+                                    <action selector="undo:" target="-1" id="M6e-cu-g7V"/>
+                                </connections>
+                            </menuItem>
+                            <menuItem title="Redo" keyEquivalent="Z" id="6dh-zS-Vam">
+                                <connections>
+                                    <action selector="redo:" target="-1" id="oIA-Rs-6OD"/>
+                                </connections>
+                            </menuItem>
+                            <menuItem isSeparatorItem="YES" id="WRV-NI-Exz"/>
+                            <menuItem title="Cut" keyEquivalent="x" id="uRl-iY-unG">
+                                <connections>
+                                    <action selector="cut:" target="-1" id="YJe-68-I9s"/>
+                                </connections>
+                            </menuItem>
+                            <menuItem title="Copy" keyEquivalent="c" id="x3v-GG-iWU">
+                                <connections>
+                                    <action selector="copy:" target="-1" id="G1f-GL-Joy"/>
+                                </connections>
+                            </menuItem>
+                            <menuItem title="Paste" keyEquivalent="v" id="gVA-U4-sdL">
+                                <connections>
+                                    <action selector="paste:" target="-1" id="UvS-8e-Qdg"/>
+                                </connections>
+                            </menuItem>
+                            <menuItem title="Paste and Match Style" keyEquivalent="V" id="WeT-3V-zwk">
+                                <modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
+                                <connections>
+                                    <action selector="pasteAsPlainText:" target="-1" id="cEh-KX-wJQ"/>
+                                </connections>
+                            </menuItem>
+                            <menuItem title="Delete" id="pa3-QI-u2k">
+                                <modifierMask key="keyEquivalentModifierMask"/>
+                                <connections>
+                                    <action selector="delete:" target="-1" id="0Mk-Ml-PaM"/>
+                                </connections>
+                            </menuItem>
+                            <menuItem title="Select All" keyEquivalent="a" id="Ruw-6m-B2m">
+                                <connections>
+                                    <action selector="selectAll:" target="-1" id="VNm-Mi-diN"/>
+                                </connections>
+                            </menuItem>
+                            <menuItem isSeparatorItem="YES" id="uyl-h8-XO2"/>
+                            <menuItem title="Find" id="4EN-yA-p0u">
+                                <modifierMask key="keyEquivalentModifierMask"/>
+                                <menu key="submenu" title="Find" id="1b7-l0-nxx">
+                                    <items>
+                                        <menuItem title="Find…" tag="1" keyEquivalent="f" id="Xz5-n4-O0W">
+                                            <connections>
+                                                <action selector="performFindPanelAction:" target="-1" id="cD7-Qs-BN4"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem title="Find and Replace…" tag="12" keyEquivalent="f" id="YEy-JH-Tfz">
+                                            <modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
+                                            <connections>
+                                                <action selector="performFindPanelAction:" target="-1" id="WD3-Gg-5AJ"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem title="Find Next" tag="2" keyEquivalent="g" id="q09-fT-Sye">
+                                            <connections>
+                                                <action selector="performFindPanelAction:" target="-1" id="NDo-RZ-v9R"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem title="Find Previous" tag="3" keyEquivalent="G" id="OwM-mh-QMV">
+                                            <connections>
+                                                <action selector="performFindPanelAction:" target="-1" id="HOh-sY-3ay"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem title="Use Selection for Find" tag="7" keyEquivalent="e" id="buJ-ug-pKt">
+                                            <connections>
+                                                <action selector="performFindPanelAction:" target="-1" id="U76-nv-p5D"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem title="Jump to Selection" keyEquivalent="j" id="S0p-oC-mLd">
+                                            <connections>
+                                                <action selector="centerSelectionInVisibleArea:" target="-1" id="IOG-6D-g5B"/>
+                                            </connections>
+                                        </menuItem>
+                                    </items>
+                                </menu>
+                            </menuItem>
+                            <menuItem title="Spelling and Grammar" id="Dv1-io-Yv7">
+                                <modifierMask key="keyEquivalentModifierMask"/>
+                                <menu key="submenu" title="Spelling" id="3IN-sU-3Bg">
+                                    <items>
+                                        <menuItem title="Show Spelling and Grammar" keyEquivalent=":" id="HFo-cy-zxI">
+                                            <connections>
+                                                <action selector="showGuessPanel:" target="-1" id="vFj-Ks-hy3"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem title="Check Document Now" keyEquivalent=";" id="hz2-CU-CR7">
+                                            <connections>
+                                                <action selector="checkSpelling:" target="-1" id="fz7-VC-reM"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem isSeparatorItem="YES" id="bNw-od-mp5"/>
+                                        <menuItem title="Check Spelling While Typing" id="rbD-Rh-wIN">
+                                            <modifierMask key="keyEquivalentModifierMask"/>
+                                            <connections>
+                                                <action selector="toggleContinuousSpellChecking:" target="-1" id="7w6-Qz-0kB"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem title="Check Grammar With Spelling" id="mK6-2p-4JG">
+                                            <modifierMask key="keyEquivalentModifierMask"/>
+                                            <connections>
+                                                <action selector="toggleGrammarChecking:" target="-1" id="muD-Qn-j4w"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem title="Correct Spelling Automatically" id="78Y-hA-62v">
+                                            <modifierMask key="keyEquivalentModifierMask"/>
+                                            <connections>
+                                                <action selector="toggleAutomaticSpellingCorrection:" target="-1" id="2lM-Qi-WAP"/>
+                                            </connections>
+                                        </menuItem>
+                                    </items>
+                                </menu>
+                            </menuItem>
+                            <menuItem title="Substitutions" id="9ic-FL-obx">
+                                <modifierMask key="keyEquivalentModifierMask"/>
+                                <menu key="submenu" title="Substitutions" id="FeM-D8-WVr">
+                                    <items>
+                                        <menuItem title="Show Substitutions" id="z6F-FW-3nz">
+                                            <modifierMask key="keyEquivalentModifierMask"/>
+                                            <connections>
+                                                <action selector="orderFrontSubstitutionsPanel:" target="-1" id="oku-mr-iSq"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem isSeparatorItem="YES" id="gPx-C9-uUO"/>
+                                        <menuItem title="Smart Copy/Paste" id="9yt-4B-nSM">
+                                            <modifierMask key="keyEquivalentModifierMask"/>
+                                            <connections>
+                                                <action selector="toggleSmartInsertDelete:" target="-1" id="3IJ-Se-DZD"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem title="Smart Quotes" id="hQb-2v-fYv">
+                                            <modifierMask key="keyEquivalentModifierMask"/>
+                                            <connections>
+                                                <action selector="toggleAutomaticQuoteSubstitution:" target="-1" id="ptq-xd-QOA"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem title="Smart Dashes" id="rgM-f4-ycn">
+                                            <modifierMask key="keyEquivalentModifierMask"/>
+                                            <connections>
+                                                <action selector="toggleAutomaticDashSubstitution:" target="-1" id="oCt-pO-9gS"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem title="Smart Links" id="cwL-P1-jid">
+                                            <modifierMask key="keyEquivalentModifierMask"/>
+                                            <connections>
+                                                <action selector="toggleAutomaticLinkDetection:" target="-1" id="Gip-E3-Fov"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem title="Data Detectors" id="tRr-pd-1PS">
+                                            <modifierMask key="keyEquivalentModifierMask"/>
+                                            <connections>
+                                                <action selector="toggleAutomaticDataDetection:" target="-1" id="R1I-Nq-Kbl"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem title="Text Replacement" id="HFQ-gK-NFA">
+                                            <modifierMask key="keyEquivalentModifierMask"/>
+                                            <connections>
+                                                <action selector="toggleAutomaticTextReplacement:" target="-1" id="DvP-Fe-Py6"/>
+                                            </connections>
+                                        </menuItem>
+                                    </items>
+                                </menu>
+                            </menuItem>
+                            <menuItem title="Transformations" id="2oI-Rn-ZJC">
+                                <modifierMask key="keyEquivalentModifierMask"/>
+                                <menu key="submenu" title="Transformations" id="c8a-y6-VQd">
+                                    <items>
+                                        <menuItem title="Make Upper Case" id="vmV-6d-7jI">
+                                            <modifierMask key="keyEquivalentModifierMask"/>
+                                            <connections>
+                                                <action selector="uppercaseWord:" target="-1" id="sPh-Tk-edu"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem title="Make Lower Case" id="d9M-CD-aMd">
+                                            <modifierMask key="keyEquivalentModifierMask"/>
+                                            <connections>
+                                                <action selector="lowercaseWord:" target="-1" id="iUZ-b5-hil"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem title="Capitalize" id="UEZ-Bs-lqG">
+                                            <modifierMask key="keyEquivalentModifierMask"/>
+                                            <connections>
+                                                <action selector="capitalizeWord:" target="-1" id="26H-TL-nsh"/>
+                                            </connections>
+                                        </menuItem>
+                                    </items>
+                                </menu>
+                            </menuItem>
+                            <menuItem title="Speech" id="xrE-MZ-jX0">
+                                <modifierMask key="keyEquivalentModifierMask"/>
+                                <menu key="submenu" title="Speech" id="3rS-ZA-NoH">
+                                    <items>
+                                        <menuItem title="Start Speaking" id="Ynk-f8-cLZ">
+                                            <modifierMask key="keyEquivalentModifierMask"/>
+                                            <connections>
+                                                <action selector="startSpeaking:" target="-1" id="654-Ng-kyl"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem title="Stop Speaking" id="Oyz-dy-DGm">
+                                            <modifierMask key="keyEquivalentModifierMask"/>
+                                            <connections>
+                                                <action selector="stopSpeaking:" target="-1" id="dX8-6p-jy9"/>
+                                            </connections>
+                                        </menuItem>
+                                    </items>
+                                </menu>
+                            </menuItem>
+                        </items>
+                    </menu>
+                </menuItem>
+                <menuItem title="View" id="H8h-7b-M4v">
+                    <modifierMask key="keyEquivalentModifierMask"/>
+                    <menu key="submenu" title="View" id="HyV-fh-RgO">
+                        <items>
+                            <menuItem title="Enter Full Screen" keyEquivalent="f" id="4J7-dP-txa">
+                                <modifierMask key="keyEquivalentModifierMask" control="YES" command="YES"/>
+                                <connections>
+                                    <action selector="toggleFullScreen:" target="-1" id="dU3-MA-1Rq"/>
+                                </connections>
+                            </menuItem>
+                        </items>
+                    </menu>
+                </menuItem>
+                <menuItem title="Window" id="aUF-d1-5bR">
+                    <modifierMask key="keyEquivalentModifierMask"/>
+                    <menu key="submenu" title="Window" systemMenu="window" id="Td7-aD-5lo">
+                        <items>
+                            <menuItem title="Minimize" keyEquivalent="m" id="OY7-WF-poV">
+                                <connections>
+                                    <action selector="performMiniaturize:" target="-1" id="VwT-WD-YPe"/>
+                                </connections>
+                            </menuItem>
+                            <menuItem title="Zoom" id="R4o-n2-Eq4">
+                                <modifierMask key="keyEquivalentModifierMask"/>
+                                <connections>
+                                    <action selector="performZoom:" target="-1" id="DIl-cC-cCs"/>
+                                </connections>
+                            </menuItem>
+                            <menuItem isSeparatorItem="YES" id="eu3-7i-yIM"/>
+                            <menuItem title="Bring All to Front" id="LE2-aR-0XJ">
+                                <modifierMask key="keyEquivalentModifierMask"/>
+                                <connections>
+                                    <action selector="arrangeInFront:" target="-1" id="DRN-fu-gQh"/>
+                                </connections>
+                            </menuItem>
+                        </items>
+                    </menu>
+                </menuItem>
+            </items>
+            <point key="canvasLocation" x="142" y="-258"/>
+        </menu>
+        <window title="APP_NAME" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" animationBehavior="default" id="QvC-M9-y7g" customClass="MainFlutterWindow" customModule="Runner" customModuleProvider="target">
+            <windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
+            <rect key="contentRect" x="335" y="390" width="800" height="600"/>
+            <rect key="screenRect" x="0.0" y="0.0" width="2560" height="1577"/>
+            <view key="contentView" wantsLayer="YES" id="EiT-Mj-1SZ">
+                <rect key="frame" x="0.0" y="0.0" width="800" height="600"/>
+                <autoresizingMask key="autoresizingMask"/>
+            </view>
+        </window>
+    </objects>
+</document>
diff --git a/packages/rfw/example/hello/macos/Runner/Configs/AppInfo.xcconfig b/packages/rfw/example/hello/macos/Runner/Configs/AppInfo.xcconfig
new file mode 100644
index 0000000..549e362
--- /dev/null
+++ b/packages/rfw/example/hello/macos/Runner/Configs/AppInfo.xcconfig
@@ -0,0 +1,14 @@
+// Application-level settings for the Runner target.
+//
+// This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the
+// future. If not, the values below would default to using the project name when this becomes a
+// 'flutter create' template.
+
+// The application's name. By default this is also the title of the Flutter window.
+PRODUCT_NAME = hello
+
+// The application's bundle identifier
+PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.rfw.examples.hello
+
+// The copyright displayed in application information
+PRODUCT_COPYRIGHT = Copyright © 2021 dev.flutter.rfw.examples. All rights reserved.
diff --git a/packages/rfw/example/hello/macos/Runner/Configs/Debug.xcconfig b/packages/rfw/example/hello/macos/Runner/Configs/Debug.xcconfig
new file mode 100644
index 0000000..36b0fd9
--- /dev/null
+++ b/packages/rfw/example/hello/macos/Runner/Configs/Debug.xcconfig
@@ -0,0 +1,2 @@
+#include "../../Flutter/Flutter-Debug.xcconfig"
+#include "Warnings.xcconfig"
diff --git a/packages/rfw/example/hello/macos/Runner/Configs/Release.xcconfig b/packages/rfw/example/hello/macos/Runner/Configs/Release.xcconfig
new file mode 100644
index 0000000..dff4f49
--- /dev/null
+++ b/packages/rfw/example/hello/macos/Runner/Configs/Release.xcconfig
@@ -0,0 +1,2 @@
+#include "../../Flutter/Flutter-Release.xcconfig"
+#include "Warnings.xcconfig"
diff --git a/packages/rfw/example/hello/macos/Runner/Configs/Warnings.xcconfig b/packages/rfw/example/hello/macos/Runner/Configs/Warnings.xcconfig
new file mode 100644
index 0000000..42bcbf4
--- /dev/null
+++ b/packages/rfw/example/hello/macos/Runner/Configs/Warnings.xcconfig
@@ -0,0 +1,13 @@
+WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings
+GCC_WARN_UNDECLARED_SELECTOR = YES
+CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES
+CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE
+CLANG_WARN__DUPLICATE_METHOD_MATCH = YES
+CLANG_WARN_PRAGMA_PACK = YES
+CLANG_WARN_STRICT_PROTOTYPES = YES
+CLANG_WARN_COMMA = YES
+GCC_WARN_STRICT_SELECTOR_MATCH = YES
+CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES
+CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES
+GCC_WARN_SHADOW = YES
+CLANG_WARN_UNREACHABLE_CODE = YES
diff --git a/packages/rfw/example/hello/macos/Runner/DebugProfile.entitlements b/packages/rfw/example/hello/macos/Runner/DebugProfile.entitlements
new file mode 100644
index 0000000..dddb8a3
--- /dev/null
+++ b/packages/rfw/example/hello/macos/Runner/DebugProfile.entitlements
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>com.apple.security.app-sandbox</key>
+	<true/>
+	<key>com.apple.security.cs.allow-jit</key>
+	<true/>
+	<key>com.apple.security.network.server</key>
+	<true/>
+</dict>
+</plist>
diff --git a/packages/rfw/example/hello/macos/Runner/Info.plist b/packages/rfw/example/hello/macos/Runner/Info.plist
new file mode 100644
index 0000000..4789daa
--- /dev/null
+++ b/packages/rfw/example/hello/macos/Runner/Info.plist
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>CFBundleDevelopmentRegion</key>
+	<string>$(DEVELOPMENT_LANGUAGE)</string>
+	<key>CFBundleExecutable</key>
+	<string>$(EXECUTABLE_NAME)</string>
+	<key>CFBundleIconFile</key>
+	<string></string>
+	<key>CFBundleIdentifier</key>
+	<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
+	<key>CFBundleInfoDictionaryVersion</key>
+	<string>6.0</string>
+	<key>CFBundleName</key>
+	<string>$(PRODUCT_NAME)</string>
+	<key>CFBundlePackageType</key>
+	<string>APPL</string>
+	<key>CFBundleShortVersionString</key>
+	<string>$(FLUTTER_BUILD_NAME)</string>
+	<key>CFBundleVersion</key>
+	<string>$(FLUTTER_BUILD_NUMBER)</string>
+	<key>LSMinimumSystemVersion</key>
+	<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
+	<key>NSHumanReadableCopyright</key>
+	<string>$(PRODUCT_COPYRIGHT)</string>
+	<key>NSMainNibFile</key>
+	<string>MainMenu</string>
+	<key>NSPrincipalClass</key>
+	<string>NSApplication</string>
+</dict>
+</plist>
diff --git a/packages/rfw/example/hello/macos/Runner/MainFlutterWindow.swift b/packages/rfw/example/hello/macos/Runner/MainFlutterWindow.swift
new file mode 100644
index 0000000..32aaeed
--- /dev/null
+++ b/packages/rfw/example/hello/macos/Runner/MainFlutterWindow.swift
@@ -0,0 +1,19 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import Cocoa
+import FlutterMacOS
+
+class MainFlutterWindow: NSWindow {
+  override func awakeFromNib() {
+    let flutterViewController = FlutterViewController.init()
+    let windowFrame = self.frame
+    self.contentViewController = flutterViewController
+    self.setFrame(windowFrame, display: true)
+
+    RegisterGeneratedPlugins(registry: flutterViewController)
+
+    super.awakeFromNib()
+  }
+}
diff --git a/packages/rfw/example/hello/macos/Runner/Release.entitlements b/packages/rfw/example/hello/macos/Runner/Release.entitlements
new file mode 100644
index 0000000..852fa1a
--- /dev/null
+++ b/packages/rfw/example/hello/macos/Runner/Release.entitlements
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>com.apple.security.app-sandbox</key>
+	<true/>
+</dict>
+</plist>
diff --git a/packages/rfw/example/hello/pubspec.yaml b/packages/rfw/example/hello/pubspec.yaml
new file mode 100644
index 0000000..5bb2930
--- /dev/null
+++ b/packages/rfw/example/hello/pubspec.yaml
@@ -0,0 +1,13 @@
+name: hello
+description: Hello World example for RFW
+publish_to: 'none' # Remove this line if you wish to publish to pub.dev
+version: 1.0.0+1
+
+environment:
+  sdk: ">=2.13.0 <3.0.0"
+
+dependencies:
+  flutter:
+    sdk: flutter
+  rfw:
+    path: ../../
diff --git a/packages/rfw/example/hello/web/favicon.png b/packages/rfw/example/hello/web/favicon.png
new file mode 100644
index 0000000..8aaa46a
--- /dev/null
+++ b/packages/rfw/example/hello/web/favicon.png
Binary files differ
diff --git a/packages/rfw/example/hello/web/icons/Icon-192.png b/packages/rfw/example/hello/web/icons/Icon-192.png
new file mode 100644
index 0000000..b749bfe
--- /dev/null
+++ b/packages/rfw/example/hello/web/icons/Icon-192.png
Binary files differ
diff --git a/packages/rfw/example/hello/web/icons/Icon-512.png b/packages/rfw/example/hello/web/icons/Icon-512.png
new file mode 100644
index 0000000..88cfd48
--- /dev/null
+++ b/packages/rfw/example/hello/web/icons/Icon-512.png
Binary files differ
diff --git a/packages/rfw/example/hello/web/icons/Icon-maskable-192.png b/packages/rfw/example/hello/web/icons/Icon-maskable-192.png
new file mode 100644
index 0000000..eb9b4d7
--- /dev/null
+++ b/packages/rfw/example/hello/web/icons/Icon-maskable-192.png
Binary files differ
diff --git a/packages/rfw/example/hello/web/icons/Icon-maskable-512.png b/packages/rfw/example/hello/web/icons/Icon-maskable-512.png
new file mode 100644
index 0000000..d69c566
--- /dev/null
+++ b/packages/rfw/example/hello/web/icons/Icon-maskable-512.png
Binary files differ
diff --git a/packages/rfw/example/hello/web/index.html b/packages/rfw/example/hello/web/index.html
new file mode 100644
index 0000000..f8a21fb
--- /dev/null
+++ b/packages/rfw/example/hello/web/index.html
@@ -0,0 +1,107 @@
+<!DOCTYPE html>
+<!-- Copyright 2013 The Flutter Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file. -->
+<html>
+<head>
+  <!--
+    If you are serving your web app in a path other than the root, change the
+    href value below to reflect the base path you are serving from.
+
+    The path provided below has to start and end with a slash "/" in order for
+    it to work correctly.
+
+    For more details:
+    * https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base
+
+    This is a placeholder for base href that will be replaced by the value of
+    the `--base-href` argument provided to `flutter build`.
+  -->
+  <base href="$FLUTTER_BASE_HREF">
+
+  <meta charset="UTF-8">
+  <meta content="IE=Edge" http-equiv="X-UA-Compatible">
+  <meta name="description" content="Hello World example for RFW">
+
+  <!-- iOS meta tags & icons -->
+  <meta name="apple-mobile-web-app-capable" content="yes">
+  <meta name="apple-mobile-web-app-status-bar-style" content="black">
+  <meta name="apple-mobile-web-app-title" content="hello">
+  <link rel="apple-touch-icon" href="icons/Icon-192.png">
+
+  <!-- Favicon -->
+  <link rel="icon" type="image/png" href="favicon.png"/>
+
+  <title>hello</title>
+  <link rel="manifest" href="manifest.json">
+</head>
+<body>
+  <!-- This script installs service_worker.js to provide PWA functionality to
+       application. For more information, see:
+       https://developers.google.com/web/fundamentals/primers/service-workers -->
+  <script>
+    var serviceWorkerVersion = null;
+    var scriptLoaded = false;
+    function loadMainDartJs() {
+      if (scriptLoaded) {
+        return;
+      }
+      scriptLoaded = true;
+      var scriptTag = document.createElement('script');
+      scriptTag.src = 'main.dart.js';
+      scriptTag.type = 'application/javascript';
+      document.body.append(scriptTag);
+    }
+
+    if ('serviceWorker' in navigator) {
+      // Service workers are supported. Use them.
+      window.addEventListener('load', function () {
+        // Wait for registration to finish before dropping the <script> tag.
+        // Otherwise, the browser will load the script multiple times,
+        // potentially different versions.
+        var serviceWorkerUrl = 'flutter_service_worker.js?v=' + serviceWorkerVersion;
+        navigator.serviceWorker.register(serviceWorkerUrl)
+          .then((reg) => {
+            function waitForActivation(serviceWorker) {
+              serviceWorker.addEventListener('statechange', () => {
+                if (serviceWorker.state == 'activated') {
+                  console.log('Installed new service worker.');
+                  loadMainDartJs();
+                }
+              });
+            }
+            if (!reg.active && (reg.installing || reg.waiting)) {
+              // No active web worker and we have installed or are installing
+              // one for the first time. Simply wait for it to activate.
+              waitForActivation(reg.installing || reg.waiting);
+            } else if (!reg.active.scriptURL.endsWith(serviceWorkerVersion)) {
+              // When the app updates the serviceWorkerVersion changes, so we
+              // need to ask the service worker to update.
+              console.log('New service worker available.');
+              reg.update();
+              waitForActivation(reg.installing);
+            } else {
+              // Existing service worker is still good.
+              console.log('Loading app from service worker.');
+              loadMainDartJs();
+            }
+          });
+
+        // If service worker doesn't succeed in a reasonable amount of time,
+        // fallback to plaint <script> tag.
+        setTimeout(() => {
+          if (!scriptLoaded) {
+            console.warn(
+              'Failed to load app from service worker. Falling back to plain <script> tag.',
+            );
+            loadMainDartJs();
+          }
+        }, 4000);
+      });
+    } else {
+      // Service workers not supported. Just drop the <script> tag.
+      loadMainDartJs();
+    }
+  </script>
+</body>
+</html>
diff --git a/packages/rfw/example/hello/web/manifest.json b/packages/rfw/example/hello/web/manifest.json
new file mode 100644
index 0000000..9b3857a
--- /dev/null
+++ b/packages/rfw/example/hello/web/manifest.json
@@ -0,0 +1,35 @@
+{
+    "name": "hello",
+    "short_name": "hello",
+    "start_url": ".",
+    "display": "standalone",
+    "background_color": "#0175C2",
+    "theme_color": "#0175C2",
+    "description": "Hello World example for RFW",
+    "orientation": "portrait-primary",
+    "prefer_related_applications": false,
+    "icons": [
+        {
+            "src": "icons/Icon-192.png",
+            "sizes": "192x192",
+            "type": "image/png"
+        },
+        {
+            "src": "icons/Icon-512.png",
+            "sizes": "512x512",
+            "type": "image/png"
+        },
+        {
+            "src": "icons/Icon-maskable-192.png",
+            "sizes": "192x192",
+            "type": "image/png",
+            "purpose": "maskable"
+        },
+        {
+            "src": "icons/Icon-maskable-512.png",
+            "sizes": "512x512",
+            "type": "image/png",
+            "purpose": "maskable"
+        }
+    ]
+}
diff --git a/packages/rfw/example/hello/windows/.gitignore b/packages/rfw/example/hello/windows/.gitignore
new file mode 100644
index 0000000..d492d0d
--- /dev/null
+++ b/packages/rfw/example/hello/windows/.gitignore
@@ -0,0 +1,17 @@
+flutter/ephemeral/
+
+# Visual Studio user-specific files.
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# Visual Studio build-related files.
+x64/
+x86/
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!*.[Cc]ache/
diff --git a/packages/rfw/example/hello/windows/CMakeLists.txt b/packages/rfw/example/hello/windows/CMakeLists.txt
new file mode 100644
index 0000000..2a5a61c
--- /dev/null
+++ b/packages/rfw/example/hello/windows/CMakeLists.txt
@@ -0,0 +1,95 @@
+cmake_minimum_required(VERSION 3.15)
+project(hello LANGUAGES CXX)
+
+set(BINARY_NAME "hello")
+
+cmake_policy(SET CMP0063 NEW)
+
+set(CMAKE_INSTALL_RPATH "$ORIGIN/lib")
+
+# Configure build options.
+get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
+if(IS_MULTICONFIG)
+  set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release"
+    CACHE STRING "" FORCE)
+else()
+  if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
+    set(CMAKE_BUILD_TYPE "Debug" CACHE
+      STRING "Flutter build mode" FORCE)
+    set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
+      "Debug" "Profile" "Release")
+  endif()
+endif()
+
+set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}")
+set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}")
+set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}")
+set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}")
+
+# Use Unicode for all projects.
+add_definitions(-DUNICODE -D_UNICODE)
+
+# Compilation settings that should be applied to most targets.
+function(APPLY_STANDARD_SETTINGS TARGET)
+  target_compile_features(${TARGET} PUBLIC cxx_std_17)
+  target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100")
+  target_compile_options(${TARGET} PRIVATE /EHsc)
+  target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0")
+  target_compile_definitions(${TARGET} PRIVATE "$<$<CONFIG:Debug>:_DEBUG>")
+endfunction()
+
+set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter")
+
+# Flutter library and tool build rules.
+add_subdirectory(${FLUTTER_MANAGED_DIR})
+
+# Application build
+add_subdirectory("runner")
+
+# Generated plugin build rules, which manage building the plugins and adding
+# them to the application.
+include(flutter/generated_plugins.cmake)
+
+
+# === Installation ===
+# Support files are copied into place next to the executable, so that it can
+# run in place. This is done instead of making a separate bundle (as on Linux)
+# so that building and running from within Visual Studio will work.
+set(BUILD_BUNDLE_DIR "$<TARGET_FILE_DIR:${BINARY_NAME}>")
+# Make the "install" step default, as it's required to run.
+set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1)
+if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
+  set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE)
+endif()
+
+set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data")
+set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}")
+
+install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}"
+  COMPONENT Runtime)
+
+install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
+  COMPONENT Runtime)
+
+install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
+  COMPONENT Runtime)
+
+if(PLUGIN_BUNDLED_LIBRARIES)
+  install(FILES "${PLUGIN_BUNDLED_LIBRARIES}"
+    DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
+    COMPONENT Runtime)
+endif()
+
+# Fully re-copy the assets directory on each build to avoid having stale files
+# from a previous install.
+set(FLUTTER_ASSET_DIR_NAME "flutter_assets")
+install(CODE "
+  file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\")
+  " COMPONENT Runtime)
+install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}"
+  DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime)
+
+# Install the AOT library on non-Debug builds only.
+install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
+  CONFIGURATIONS Profile;Release
+  COMPONENT Runtime)
diff --git a/packages/rfw/example/hello/windows/flutter/CMakeLists.txt b/packages/rfw/example/hello/windows/flutter/CMakeLists.txt
new file mode 100644
index 0000000..b02c548
--- /dev/null
+++ b/packages/rfw/example/hello/windows/flutter/CMakeLists.txt
@@ -0,0 +1,103 @@
+cmake_minimum_required(VERSION 3.15)
+
+set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral")
+
+# Configuration provided via flutter tool.
+include(${EPHEMERAL_DIR}/generated_config.cmake)
+
+# TODO: Move the rest of this into files in ephemeral. See
+# https://github.com/flutter/flutter/issues/57146.
+set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper")
+
+# === Flutter Library ===
+set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll")
+
+# Published to parent scope for install step.
+set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE)
+set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE)
+set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE)
+set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE)
+
+list(APPEND FLUTTER_LIBRARY_HEADERS
+  "flutter_export.h"
+  "flutter_windows.h"
+  "flutter_messenger.h"
+  "flutter_plugin_registrar.h"
+  "flutter_texture_registrar.h"
+)
+list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/")
+add_library(flutter INTERFACE)
+target_include_directories(flutter INTERFACE
+  "${EPHEMERAL_DIR}"
+)
+target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib")
+add_dependencies(flutter flutter_assemble)
+
+# === Wrapper ===
+list(APPEND CPP_WRAPPER_SOURCES_CORE
+  "core_implementations.cc"
+  "standard_codec.cc"
+)
+list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/")
+list(APPEND CPP_WRAPPER_SOURCES_PLUGIN
+  "plugin_registrar.cc"
+)
+list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/")
+list(APPEND CPP_WRAPPER_SOURCES_APP
+  "flutter_engine.cc"
+  "flutter_view_controller.cc"
+)
+list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/")
+
+# Wrapper sources needed for a plugin.
+add_library(flutter_wrapper_plugin STATIC
+  ${CPP_WRAPPER_SOURCES_CORE}
+  ${CPP_WRAPPER_SOURCES_PLUGIN}
+)
+apply_standard_settings(flutter_wrapper_plugin)
+set_target_properties(flutter_wrapper_plugin PROPERTIES
+  POSITION_INDEPENDENT_CODE ON)
+set_target_properties(flutter_wrapper_plugin PROPERTIES
+  CXX_VISIBILITY_PRESET hidden)
+target_link_libraries(flutter_wrapper_plugin PUBLIC flutter)
+target_include_directories(flutter_wrapper_plugin PUBLIC
+  "${WRAPPER_ROOT}/include"
+)
+add_dependencies(flutter_wrapper_plugin flutter_assemble)
+
+# Wrapper sources needed for the runner.
+add_library(flutter_wrapper_app STATIC
+  ${CPP_WRAPPER_SOURCES_CORE}
+  ${CPP_WRAPPER_SOURCES_APP}
+)
+apply_standard_settings(flutter_wrapper_app)
+target_link_libraries(flutter_wrapper_app PUBLIC flutter)
+target_include_directories(flutter_wrapper_app PUBLIC
+  "${WRAPPER_ROOT}/include"
+)
+add_dependencies(flutter_wrapper_app flutter_assemble)
+
+# === Flutter tool backend ===
+# _phony_ is a non-existent file to force this command to run every time,
+# since currently there's no way to get a full input/output list from the
+# flutter tool.
+set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_")
+set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE)
+add_custom_command(
+  OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS}
+    ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN}
+    ${CPP_WRAPPER_SOURCES_APP}
+    ${PHONY_OUTPUT}
+  COMMAND ${CMAKE_COMMAND} -E env
+    ${FLUTTER_TOOL_ENVIRONMENT}
+    "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat"
+      windows-x64 $<CONFIG>
+  VERBATIM
+)
+add_custom_target(flutter_assemble DEPENDS
+  "${FLUTTER_LIBRARY}"
+  ${FLUTTER_LIBRARY_HEADERS}
+  ${CPP_WRAPPER_SOURCES_CORE}
+  ${CPP_WRAPPER_SOURCES_PLUGIN}
+  ${CPP_WRAPPER_SOURCES_APP}
+)
diff --git a/packages/rfw/example/hello/windows/flutter/generated_plugins.cmake b/packages/rfw/example/hello/windows/flutter/generated_plugins.cmake
new file mode 100644
index 0000000..4d10c25
--- /dev/null
+++ b/packages/rfw/example/hello/windows/flutter/generated_plugins.cmake
@@ -0,0 +1,15 @@
+#
+# Generated file, do not edit.
+#
+
+list(APPEND FLUTTER_PLUGIN_LIST
+)
+
+set(PLUGIN_BUNDLED_LIBRARIES)
+
+foreach(plugin ${FLUTTER_PLUGIN_LIST})
+  add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin})
+  target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)
+  list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>)
+  list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
+endforeach(plugin)
diff --git a/packages/rfw/example/hello/windows/runner/CMakeLists.txt b/packages/rfw/example/hello/windows/runner/CMakeLists.txt
new file mode 100644
index 0000000..0b899a0
--- /dev/null
+++ b/packages/rfw/example/hello/windows/runner/CMakeLists.txt
@@ -0,0 +1,17 @@
+cmake_minimum_required(VERSION 3.15)
+project(runner LANGUAGES CXX)
+
+add_executable(${BINARY_NAME} WIN32
+  "flutter_window.cpp"
+  "main.cpp"
+  "utils.cpp"
+  "win32_window.cpp"
+  "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc"
+  "Runner.rc"
+  "runner.exe.manifest"
+)
+apply_standard_settings(${BINARY_NAME})
+target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX")
+target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app)
+target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}")
+add_dependencies(${BINARY_NAME} flutter_assemble)
diff --git a/packages/rfw/example/hello/windows/runner/Runner.rc b/packages/rfw/example/hello/windows/runner/Runner.rc
new file mode 100644
index 0000000..c9091ea
--- /dev/null
+++ b/packages/rfw/example/hello/windows/runner/Runner.rc
@@ -0,0 +1,121 @@
+// Microsoft Visual C++ generated resource script.
+//
+#pragma code_page(65001)
+#include "resource.h"
+
+#define APSTUDIO_READONLY_SYMBOLS
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 2 resource.
+//
+#include "winres.h"
+
+/////////////////////////////////////////////////////////////////////////////
+#undef APSTUDIO_READONLY_SYMBOLS
+
+/////////////////////////////////////////////////////////////////////////////
+// English (United States) resources
+
+#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
+LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
+
+#ifdef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// TEXTINCLUDE
+//
+
+1 TEXTINCLUDE
+BEGIN
+    "resource.h\0"
+END
+
+2 TEXTINCLUDE
+BEGIN
+    "#include ""winres.h""\r\n"
+    "\0"
+END
+
+3 TEXTINCLUDE
+BEGIN
+    "\r\n"
+    "\0"
+END
+
+#endif    // APSTUDIO_INVOKED
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Icon
+//
+
+// Icon with lowest ID value placed first to ensure application icon
+// remains consistent on all systems.
+IDI_APP_ICON            ICON                    "resources\\app_icon.ico"
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Version
+//
+
+#ifdef FLUTTER_BUILD_NUMBER
+#define VERSION_AS_NUMBER FLUTTER_BUILD_NUMBER
+#else
+#define VERSION_AS_NUMBER 1,0,0
+#endif
+
+#ifdef FLUTTER_BUILD_NAME
+#define VERSION_AS_STRING #FLUTTER_BUILD_NAME
+#else
+#define VERSION_AS_STRING "1.0.0"
+#endif
+
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION VERSION_AS_NUMBER
+ PRODUCTVERSION VERSION_AS_NUMBER
+ FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
+#ifdef _DEBUG
+ FILEFLAGS VS_FF_DEBUG
+#else
+ FILEFLAGS 0x0L
+#endif
+ FILEOS VOS__WINDOWS32
+ FILETYPE VFT_APP
+ FILESUBTYPE 0x0L
+BEGIN
+    BLOCK "StringFileInfo"
+    BEGIN
+        BLOCK "040904e4"
+        BEGIN
+            VALUE "CompanyName", "dev.flutter.rfw.examples" "\0"
+            VALUE "FileDescription", "Hello World example for RFW" "\0"
+            VALUE "FileVersion", VERSION_AS_STRING "\0"
+            VALUE "InternalName", "hello" "\0"
+            VALUE "LegalCopyright", "Copyright (C) 2021 dev.flutter.rfw.examples. All rights reserved." "\0"
+            VALUE "OriginalFilename", "hello.exe" "\0"
+            VALUE "ProductName", "hello" "\0"
+            VALUE "ProductVersion", VERSION_AS_STRING "\0"
+        END
+    END
+    BLOCK "VarFileInfo"
+    BEGIN
+        VALUE "Translation", 0x409, 1252
+    END
+END
+
+#endif    // English (United States) resources
+/////////////////////////////////////////////////////////////////////////////
+
+
+
+#ifndef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 3 resource.
+//
+
+
+/////////////////////////////////////////////////////////////////////////////
+#endif    // not APSTUDIO_INVOKED
diff --git a/packages/rfw/example/hello/windows/runner/flutter_window.cpp b/packages/rfw/example/hello/windows/runner/flutter_window.cpp
new file mode 100644
index 0000000..8254bd9
--- /dev/null
+++ b/packages/rfw/example/hello/windows/runner/flutter_window.cpp
@@ -0,0 +1,65 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "flutter_window.h"
+
+#include <optional>
+
+#include "flutter/generated_plugin_registrant.h"
+
+FlutterWindow::FlutterWindow(const flutter::DartProject& project)
+    : project_(project) {}
+
+FlutterWindow::~FlutterWindow() {}
+
+bool FlutterWindow::OnCreate() {
+  if (!Win32Window::OnCreate()) {
+    return false;
+  }
+
+  RECT frame = GetClientArea();
+
+  // The size here must match the window dimensions to avoid unnecessary surface
+  // creation / destruction in the startup path.
+  flutter_controller_ = std::make_unique<flutter::FlutterViewController>(
+      frame.right - frame.left, frame.bottom - frame.top, project_);
+  // Ensure that basic setup of the controller was successful.
+  if (!flutter_controller_->engine() || !flutter_controller_->view()) {
+    return false;
+  }
+  RegisterPlugins(flutter_controller_->engine());
+  SetChildContent(flutter_controller_->view()->GetNativeWindow());
+  return true;
+}
+
+void FlutterWindow::OnDestroy() {
+  if (flutter_controller_) {
+    flutter_controller_ = nullptr;
+  }
+
+  Win32Window::OnDestroy();
+}
+
+LRESULT
+FlutterWindow::MessageHandler(HWND hwnd, UINT const message,
+                              WPARAM const wparam,
+                              LPARAM const lparam) noexcept {
+  // Give Flutter, including plugins, an opportunity to handle window messages.
+  if (flutter_controller_) {
+    std::optional<LRESULT> result =
+        flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam,
+                                                      lparam);
+    if (result) {
+      return *result;
+    }
+  }
+
+  switch (message) {
+    case WM_FONTCHANGE:
+      flutter_controller_->engine()->ReloadSystemFonts();
+      break;
+  }
+
+  return Win32Window::MessageHandler(hwnd, message, wparam, lparam);
+}
diff --git a/packages/rfw/example/hello/windows/runner/flutter_window.h b/packages/rfw/example/hello/windows/runner/flutter_window.h
new file mode 100644
index 0000000..f1fc669
--- /dev/null
+++ b/packages/rfw/example/hello/windows/runner/flutter_window.h
@@ -0,0 +1,37 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef RUNNER_FLUTTER_WINDOW_H_
+#define RUNNER_FLUTTER_WINDOW_H_
+
+#include <flutter/dart_project.h>
+#include <flutter/flutter_view_controller.h>
+
+#include <memory>
+
+#include "win32_window.h"
+
+// A window that does nothing but host a Flutter view.
+class FlutterWindow : public Win32Window {
+ public:
+  // Creates a new FlutterWindow hosting a Flutter view running |project|.
+  explicit FlutterWindow(const flutter::DartProject& project);
+  virtual ~FlutterWindow();
+
+ protected:
+  // Win32Window:
+  bool OnCreate() override;
+  void OnDestroy() override;
+  LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam,
+                         LPARAM const lparam) noexcept override;
+
+ private:
+  // The project to run.
+  flutter::DartProject project_;
+
+  // The Flutter instance hosted by this window.
+  std::unique_ptr<flutter::FlutterViewController> flutter_controller_;
+};
+
+#endif  // RUNNER_FLUTTER_WINDOW_H_
diff --git a/packages/rfw/example/hello/windows/runner/main.cpp b/packages/rfw/example/hello/windows/runner/main.cpp
new file mode 100644
index 0000000..7ba735d
--- /dev/null
+++ b/packages/rfw/example/hello/windows/runner/main.cpp
@@ -0,0 +1,46 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <flutter/dart_project.h>
+#include <flutter/flutter_view_controller.h>
+#include <windows.h>
+
+#include "flutter_window.h"
+#include "utils.h"
+
+int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
+                      _In_ wchar_t *command_line, _In_ int show_command) {
+  // Attach to console when present (e.g., 'flutter run') or create a
+  // new console when running with a debugger.
+  if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) {
+    CreateAndAttachConsole();
+  }
+
+  // Initialize COM, so that it is available for use in the library and/or
+  // plugins.
+  ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
+
+  flutter::DartProject project(L"data");
+
+  std::vector<std::string> command_line_arguments = GetCommandLineArguments();
+
+  project.set_dart_entrypoint_arguments(std::move(command_line_arguments));
+
+  FlutterWindow window(project);
+  Win32Window::Point origin(10, 10);
+  Win32Window::Size size(1280, 720);
+  if (!window.CreateAndShow(L"hello", origin, size)) {
+    return EXIT_FAILURE;
+  }
+  window.SetQuitOnClose(true);
+
+  ::MSG msg;
+  while (::GetMessage(&msg, nullptr, 0, 0)) {
+    ::TranslateMessage(&msg);
+    ::DispatchMessage(&msg);
+  }
+
+  ::CoUninitialize();
+  return EXIT_SUCCESS;
+}
diff --git a/packages/rfw/example/hello/windows/runner/resource.h b/packages/rfw/example/hello/windows/runner/resource.h
new file mode 100644
index 0000000..d5d958d
--- /dev/null
+++ b/packages/rfw/example/hello/windows/runner/resource.h
@@ -0,0 +1,16 @@
+//{{NO_DEPENDENCIES}}
+// Microsoft Visual C++ generated include file.
+// Used by Runner.rc
+//
+#define IDI_APP_ICON 101
+
+// Next default values for new objects
+//
+#ifdef APSTUDIO_INVOKED
+#ifndef APSTUDIO_READONLY_SYMBOLS
+#define _APS_NEXT_RESOURCE_VALUE 102
+#define _APS_NEXT_COMMAND_VALUE 40001
+#define _APS_NEXT_CONTROL_VALUE 1001
+#define _APS_NEXT_SYMED_VALUE 101
+#endif
+#endif
diff --git a/packages/rfw/example/hello/windows/runner/resources/app_icon.ico b/packages/rfw/example/hello/windows/runner/resources/app_icon.ico
new file mode 100644
index 0000000..c04e20c
--- /dev/null
+++ b/packages/rfw/example/hello/windows/runner/resources/app_icon.ico
Binary files differ
diff --git a/packages/rfw/example/hello/windows/runner/runner.exe.manifest b/packages/rfw/example/hello/windows/runner/runner.exe.manifest
new file mode 100644
index 0000000..c977c4a
--- /dev/null
+++ b/packages/rfw/example/hello/windows/runner/runner.exe.manifest
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
+  <application xmlns="urn:schemas-microsoft-com:asm.v3">
+    <windowsSettings>
+      <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
+    </windowsSettings>
+  </application>
+  <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
+    <application>
+      <!-- Windows 10 -->
+      <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
+      <!-- Windows 8.1 -->
+      <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
+      <!-- Windows 8 -->
+      <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
+      <!-- Windows 7 -->
+      <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
+    </application>
+  </compatibility>
+</assembly>
diff --git a/packages/rfw/example/hello/windows/runner/utils.cpp b/packages/rfw/example/hello/windows/runner/utils.cpp
new file mode 100644
index 0000000..fb7e945
--- /dev/null
+++ b/packages/rfw/example/hello/windows/runner/utils.cpp
@@ -0,0 +1,67 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "utils.h"
+
+#include <flutter_windows.h>
+#include <io.h>
+#include <stdio.h>
+#include <windows.h>
+
+#include <iostream>
+
+void CreateAndAttachConsole() {
+  if (::AllocConsole()) {
+    FILE* unused;
+    if (freopen_s(&unused, "CONOUT$", "w", stdout)) {
+      _dup2(_fileno(stdout), 1);
+    }
+    if (freopen_s(&unused, "CONOUT$", "w", stderr)) {
+      _dup2(_fileno(stdout), 2);
+    }
+    std::ios::sync_with_stdio();
+    FlutterDesktopResyncOutputStreams();
+  }
+}
+
+std::vector<std::string> GetCommandLineArguments() {
+  // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use.
+  int argc;
+  wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc);
+  if (argv == nullptr) {
+    return std::vector<std::string>();
+  }
+
+  std::vector<std::string> command_line_arguments;
+
+  // Skip the first argument as it's the binary name.
+  for (int i = 1; i < argc; i++) {
+    command_line_arguments.push_back(Utf8FromUtf16(argv[i]));
+  }
+
+  ::LocalFree(argv);
+
+  return command_line_arguments;
+}
+
+std::string Utf8FromUtf16(const wchar_t* utf16_string) {
+  if (utf16_string == nullptr) {
+    return std::string();
+  }
+  int target_length =
+      ::WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, -1,
+                            nullptr, 0, nullptr, nullptr);
+  if (target_length == 0) {
+    return std::string();
+  }
+  std::string utf8_string;
+  utf8_string.resize(target_length);
+  int converted_length = ::WideCharToMultiByte(
+      CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, -1, utf8_string.data(),
+      target_length, nullptr, nullptr);
+  if (converted_length == 0) {
+    return std::string();
+  }
+  return utf8_string;
+}
diff --git a/packages/rfw/example/hello/windows/runner/utils.h b/packages/rfw/example/hello/windows/runner/utils.h
new file mode 100644
index 0000000..bd81e1e
--- /dev/null
+++ b/packages/rfw/example/hello/windows/runner/utils.h
@@ -0,0 +1,23 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef RUNNER_UTILS_H_
+#define RUNNER_UTILS_H_
+
+#include <string>
+#include <vector>
+
+// Creates a console for the process, and redirects stdout and stderr to
+// it for both the runner and the Flutter library.
+void CreateAndAttachConsole();
+
+// Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string
+// encoded in UTF-8. Returns an empty std::string on failure.
+std::string Utf8FromUtf16(const wchar_t* utf16_string);
+
+// Gets the command line arguments passed in as a std::vector<std::string>,
+// encoded in UTF-8. Returns an empty std::vector<std::string> on failure.
+std::vector<std::string> GetCommandLineArguments();
+
+#endif  // RUNNER_UTILS_H_
diff --git a/packages/rfw/example/hello/windows/runner/win32_window.cpp b/packages/rfw/example/hello/windows/runner/win32_window.cpp
new file mode 100644
index 0000000..85aa361
--- /dev/null
+++ b/packages/rfw/example/hello/windows/runner/win32_window.cpp
@@ -0,0 +1,241 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "win32_window.h"
+
+#include <flutter_windows.h>
+
+#include "resource.h"
+
+namespace {
+
+constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW";
+
+// The number of Win32Window objects that currently exist.
+static int g_active_window_count = 0;
+
+using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd);
+
+// Scale helper to convert logical scaler values to physical using passed in
+// scale factor
+int Scale(int source, double scale_factor) {
+  return static_cast<int>(source * scale_factor);
+}
+
+// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module.
+// This API is only needed for PerMonitor V1 awareness mode.
+void EnableFullDpiSupportIfAvailable(HWND hwnd) {
+  HMODULE user32_module = LoadLibraryA("User32.dll");
+  if (!user32_module) {
+    return;
+  }
+  auto enable_non_client_dpi_scaling =
+      reinterpret_cast<EnableNonClientDpiScaling*>(
+          GetProcAddress(user32_module, "EnableNonClientDpiScaling"));
+  if (enable_non_client_dpi_scaling != nullptr) {
+    enable_non_client_dpi_scaling(hwnd);
+    FreeLibrary(user32_module);
+  }
+}
+
+}  // namespace
+
+// Manages the Win32Window's window class registration.
+class WindowClassRegistrar {
+ public:
+  ~WindowClassRegistrar() = default;
+
+  // Returns the singleton registar instance.
+  static WindowClassRegistrar* GetInstance() {
+    if (!instance_) {
+      instance_ = new WindowClassRegistrar();
+    }
+    return instance_;
+  }
+
+  // Returns the name of the window class, registering the class if it hasn't
+  // previously been registered.
+  const wchar_t* GetWindowClass();
+
+  // Unregisters the window class. Should only be called if there are no
+  // instances of the window.
+  void UnregisterWindowClass();
+
+ private:
+  WindowClassRegistrar() = default;
+
+  static WindowClassRegistrar* instance_;
+
+  bool class_registered_ = false;
+};
+
+WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr;
+
+const wchar_t* WindowClassRegistrar::GetWindowClass() {
+  if (!class_registered_) {
+    WNDCLASS window_class{};
+    window_class.hCursor = LoadCursor(nullptr, IDC_ARROW);
+    window_class.lpszClassName = kWindowClassName;
+    window_class.style = CS_HREDRAW | CS_VREDRAW;
+    window_class.cbClsExtra = 0;
+    window_class.cbWndExtra = 0;
+    window_class.hInstance = GetModuleHandle(nullptr);
+    window_class.hIcon =
+        LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON));
+    window_class.hbrBackground = 0;
+    window_class.lpszMenuName = nullptr;
+    window_class.lpfnWndProc = Win32Window::WndProc;
+    RegisterClass(&window_class);
+    class_registered_ = true;
+  }
+  return kWindowClassName;
+}
+
+void WindowClassRegistrar::UnregisterWindowClass() {
+  UnregisterClass(kWindowClassName, nullptr);
+  class_registered_ = false;
+}
+
+Win32Window::Win32Window() { ++g_active_window_count; }
+
+Win32Window::~Win32Window() {
+  --g_active_window_count;
+  Destroy();
+}
+
+bool Win32Window::CreateAndShow(const std::wstring& title, const Point& origin,
+                                const Size& size) {
+  Destroy();
+
+  const wchar_t* window_class =
+      WindowClassRegistrar::GetInstance()->GetWindowClass();
+
+  const POINT target_point = {static_cast<LONG>(origin.x),
+                              static_cast<LONG>(origin.y)};
+  HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST);
+  UINT dpi = FlutterDesktopGetDpiForMonitor(monitor);
+  double scale_factor = dpi / 96.0;
+
+  HWND window = CreateWindow(
+      window_class, title.c_str(), WS_OVERLAPPEDWINDOW | WS_VISIBLE,
+      Scale(origin.x, scale_factor), Scale(origin.y, scale_factor),
+      Scale(size.width, scale_factor), Scale(size.height, scale_factor),
+      nullptr, nullptr, GetModuleHandle(nullptr), this);
+
+  if (!window) {
+    return false;
+  }
+
+  return OnCreate();
+}
+
+// static
+LRESULT CALLBACK Win32Window::WndProc(HWND const window, UINT const message,
+                                      WPARAM const wparam,
+                                      LPARAM const lparam) noexcept {
+  if (message == WM_NCCREATE) {
+    auto window_struct = reinterpret_cast<CREATESTRUCT*>(lparam);
+    SetWindowLongPtr(window, GWLP_USERDATA,
+                     reinterpret_cast<LONG_PTR>(window_struct->lpCreateParams));
+
+    auto that = static_cast<Win32Window*>(window_struct->lpCreateParams);
+    EnableFullDpiSupportIfAvailable(window);
+    that->window_handle_ = window;
+  } else if (Win32Window* that = GetThisFromHandle(window)) {
+    return that->MessageHandler(window, message, wparam, lparam);
+  }
+
+  return DefWindowProc(window, message, wparam, lparam);
+}
+
+LRESULT
+Win32Window::MessageHandler(HWND hwnd, UINT const message, WPARAM const wparam,
+                            LPARAM const lparam) noexcept {
+  switch (message) {
+    case WM_DESTROY:
+      window_handle_ = nullptr;
+      Destroy();
+      if (quit_on_close_) {
+        PostQuitMessage(0);
+      }
+      return 0;
+
+    case WM_DPICHANGED: {
+      auto newRectSize = reinterpret_cast<RECT*>(lparam);
+      LONG newWidth = newRectSize->right - newRectSize->left;
+      LONG newHeight = newRectSize->bottom - newRectSize->top;
+
+      SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth,
+                   newHeight, SWP_NOZORDER | SWP_NOACTIVATE);
+
+      return 0;
+    }
+    case WM_SIZE: {
+      RECT rect = GetClientArea();
+      if (child_content_ != nullptr) {
+        // Size and position the child window.
+        MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left,
+                   rect.bottom - rect.top, TRUE);
+      }
+      return 0;
+    }
+
+    case WM_ACTIVATE:
+      if (child_content_ != nullptr) {
+        SetFocus(child_content_);
+      }
+      return 0;
+  }
+
+  return DefWindowProc(window_handle_, message, wparam, lparam);
+}
+
+void Win32Window::Destroy() {
+  OnDestroy();
+
+  if (window_handle_) {
+    DestroyWindow(window_handle_);
+    window_handle_ = nullptr;
+  }
+  if (g_active_window_count == 0) {
+    WindowClassRegistrar::GetInstance()->UnregisterWindowClass();
+  }
+}
+
+Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept {
+  return reinterpret_cast<Win32Window*>(
+      GetWindowLongPtr(window, GWLP_USERDATA));
+}
+
+void Win32Window::SetChildContent(HWND content) {
+  child_content_ = content;
+  SetParent(content, window_handle_);
+  RECT frame = GetClientArea();
+
+  MoveWindow(content, frame.left, frame.top, frame.right - frame.left,
+             frame.bottom - frame.top, true);
+
+  SetFocus(child_content_);
+}
+
+RECT Win32Window::GetClientArea() {
+  RECT frame;
+  GetClientRect(window_handle_, &frame);
+  return frame;
+}
+
+HWND Win32Window::GetHandle() { return window_handle_; }
+
+void Win32Window::SetQuitOnClose(bool quit_on_close) {
+  quit_on_close_ = quit_on_close;
+}
+
+bool Win32Window::OnCreate() {
+  // No-op; provided for subclasses.
+  return true;
+}
+
+void Win32Window::OnDestroy() {
+  // No-op; provided for subclasses.
+}
diff --git a/packages/rfw/example/hello/windows/runner/win32_window.h b/packages/rfw/example/hello/windows/runner/win32_window.h
new file mode 100644
index 0000000..d2a7300
--- /dev/null
+++ b/packages/rfw/example/hello/windows/runner/win32_window.h
@@ -0,0 +1,99 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef RUNNER_WIN32_WINDOW_H_
+#define RUNNER_WIN32_WINDOW_H_
+
+#include <windows.h>
+
+#include <functional>
+#include <memory>
+#include <string>
+
+// A class abstraction for a high DPI-aware Win32 Window. Intended to be
+// inherited from by classes that wish to specialize with custom
+// rendering and input handling
+class Win32Window {
+ public:
+  struct Point {
+    unsigned int x;
+    unsigned int y;
+    Point(unsigned int x, unsigned int y) : x(x), y(y) {}
+  };
+
+  struct Size {
+    unsigned int width;
+    unsigned int height;
+    Size(unsigned int width, unsigned int height)
+        : width(width), height(height) {}
+  };
+
+  Win32Window();
+  virtual ~Win32Window();
+
+  // Creates and shows a win32 window with |title| and position and size using
+  // |origin| and |size|. New windows are created on the default monitor. Window
+  // sizes are specified to the OS in physical pixels, hence to ensure a
+  // consistent size to will treat the width height passed in to this function
+  // as logical pixels and scale to appropriate for the default monitor. Returns
+  // true if the window was created successfully.
+  bool CreateAndShow(const std::wstring& title, const Point& origin,
+                     const Size& size);
+
+  // Release OS resources associated with window.
+  void Destroy();
+
+  // Inserts |content| into the window tree.
+  void SetChildContent(HWND content);
+
+  // Returns the backing Window handle to enable clients to set icon and other
+  // window properties. Returns nullptr if the window has been destroyed.
+  HWND GetHandle();
+
+  // If true, closing this window will quit the application.
+  void SetQuitOnClose(bool quit_on_close);
+
+  // Return a RECT representing the bounds of the current client area.
+  RECT GetClientArea();
+
+ protected:
+  // Processes and route salient window messages for mouse handling,
+  // size change and DPI. Delegates handling of these to member overloads that
+  // inheriting classes can handle.
+  virtual LRESULT MessageHandler(HWND window, UINT const message,
+                                 WPARAM const wparam,
+                                 LPARAM const lparam) noexcept;
+
+  // Called when CreateAndShow is called, allowing subclass window-related
+  // setup. Subclasses should return false if setup fails.
+  virtual bool OnCreate();
+
+  // Called when Destroy is called.
+  virtual void OnDestroy();
+
+ private:
+  friend class WindowClassRegistrar;
+
+  // OS callback called by message pump. Handles the WM_NCCREATE message which
+  // is passed when the non-client area is being created and enables automatic
+  // non-client DPI scaling so that the non-client area automatically
+  // responsponds to changes in DPI. All other messages are handled by
+  // MessageHandler.
+  static LRESULT CALLBACK WndProc(HWND const window, UINT const message,
+                                  WPARAM const wparam,
+                                  LPARAM const lparam) noexcept;
+
+  // Retrieves a class instance pointer for |window|
+  static Win32Window* GetThisFromHandle(HWND const window) noexcept;
+
+  bool quit_on_close_ = false;
+
+  // window handle for top level window.
+  HWND window_handle_ = nullptr;
+
+  // window handle for hosted content.
+  HWND child_content_ = nullptr;
+};
+
+#endif  // RUNNER_WIN32_WINDOW_H_
diff --git a/packages/rfw/example/local/.gitignore b/packages/rfw/example/local/.gitignore
new file mode 100644
index 0000000..0fa6b67
--- /dev/null
+++ b/packages/rfw/example/local/.gitignore
@@ -0,0 +1,46 @@
+# Miscellaneous
+*.class
+*.log
+*.pyc
+*.swp
+.DS_Store
+.atom/
+.buildlog/
+.history
+.svn/
+
+# IntelliJ related
+*.iml
+*.ipr
+*.iws
+.idea/
+
+# The .vscode folder contains launch configuration and tasks you configure in
+# VS Code which you may wish to be included in version control, so this line
+# is commented out by default.
+#.vscode/
+
+# Flutter/Dart/Pub related
+**/doc/api/
+**/ios/Flutter/.last_build_id
+.dart_tool/
+.flutter-plugins
+.flutter-plugins-dependencies
+.packages
+.pub-cache/
+.pub/
+/build/
+
+# Web related
+lib/generated_plugin_registrant.dart
+
+# Symbolication related
+app.*.symbols
+
+# Obfuscation related
+app.*.map.json
+
+# Android Studio will place build artifacts here
+/android/app/debug
+/android/app/profile
+/android/app/release
diff --git a/packages/rfw/example/local/.metadata b/packages/rfw/example/local/.metadata
new file mode 100644
index 0000000..45c494e
--- /dev/null
+++ b/packages/rfw/example/local/.metadata
@@ -0,0 +1,10 @@
+# This file tracks properties of this Flutter project.
+# Used by Flutter tool to assess capabilities and perform upgrades etc.
+#
+# This file should be version controlled and should not be manually edited.
+
+version:
+  revision: a1cd3f45e02c2429e14630d6cc6de120c3d24bd0
+  channel: master
+
+project_type: app
diff --git a/packages/rfw/example/local/README.md b/packages/rfw/example/local/README.md
new file mode 100644
index 0000000..f8dc992
--- /dev/null
+++ b/packages/rfw/example/local/README.md
@@ -0,0 +1,4 @@
+# Example of new custom local widgets for RFW
+
+This example shows how one can create custom widgets in an RFW client,
+for use by remote widgets.
diff --git a/packages/rfw/example/local/android/.gitignore b/packages/rfw/example/local/android/.gitignore
new file mode 100644
index 0000000..6f56801
--- /dev/null
+++ b/packages/rfw/example/local/android/.gitignore
@@ -0,0 +1,13 @@
+gradle-wrapper.jar
+/.gradle
+/captures/
+/gradlew
+/gradlew.bat
+/local.properties
+GeneratedPluginRegistrant.java
+
+# Remember to never publicly share your keystore.
+# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
+key.properties
+**/*.keystore
+**/*.jks
diff --git a/packages/rfw/example/local/android/app/build.gradle b/packages/rfw/example/local/android/app/build.gradle
new file mode 100644
index 0000000..de9e40e
--- /dev/null
+++ b/packages/rfw/example/local/android/app/build.gradle
@@ -0,0 +1,68 @@
+def localProperties = new Properties()
+def localPropertiesFile = rootProject.file('local.properties')
+if (localPropertiesFile.exists()) {
+    localPropertiesFile.withReader('UTF-8') { reader ->
+        localProperties.load(reader)
+    }
+}
+
+def flutterRoot = localProperties.getProperty('flutter.sdk')
+if (flutterRoot == null) {
+    throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
+}
+
+def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
+if (flutterVersionCode == null) {
+    flutterVersionCode = '1'
+}
+
+def flutterVersionName = localProperties.getProperty('flutter.versionName')
+if (flutterVersionName == null) {
+    flutterVersionName = '1.0'
+}
+
+apply plugin: 'com.android.application'
+apply plugin: 'kotlin-android'
+apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
+
+android {
+    compileSdkVersion 30
+
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_8
+        targetCompatibility JavaVersion.VERSION_1_8
+    }
+
+    kotlinOptions {
+        jvmTarget = '1.8'
+    }
+
+    sourceSets {
+        main.java.srcDirs += 'src/main/kotlin'
+    }
+
+    defaultConfig {
+        // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
+        applicationId "dev.flutter.rfw.examples.local"
+        minSdkVersion 16
+        targetSdkVersion 30
+        versionCode flutterVersionCode.toInteger()
+        versionName flutterVersionName
+    }
+
+    buildTypes {
+        release {
+            // TODO: Add your own signing config for the release build.
+            // Signing with the debug keys for now, so `flutter run --release` works.
+            signingConfig signingConfigs.debug
+        }
+    }
+}
+
+flutter {
+    source '../..'
+}
+
+dependencies {
+    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
+}
diff --git a/packages/rfw/example/local/android/app/src/debug/AndroidManifest.xml b/packages/rfw/example/local/android/app/src/debug/AndroidManifest.xml
new file mode 100644
index 0000000..2fd5378
--- /dev/null
+++ b/packages/rfw/example/local/android/app/src/debug/AndroidManifest.xml
@@ -0,0 +1,7 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="dev.flutter.rfw.examples.local">
+    <!-- Flutter needs it to communicate with the running application
+         to allow setting breakpoints, to provide hot reload, etc.
+    -->
+    <uses-permission android:name="android.permission.INTERNET"/>
+</manifest>
diff --git a/packages/rfw/example/local/android/app/src/main/AndroidManifest.xml b/packages/rfw/example/local/android/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..95ddb98
--- /dev/null
+++ b/packages/rfw/example/local/android/app/src/main/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="dev.flutter.rfw.examples.local">
+   <application
+        android:label="local"
+        android:icon="@mipmap/ic_launcher">
+        <activity
+            android:name=".MainActivity"
+            android:launchMode="singleTop"
+            android:theme="@style/LaunchTheme"
+            android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
+            android:hardwareAccelerated="true"
+            android:windowSoftInputMode="adjustResize">
+            <!-- Specifies an Android theme to apply to this Activity as soon as
+                 the Android process has started. This theme is visible to the user
+                 while the Flutter UI initializes. After that, this theme continues
+                 to determine the Window background behind the Flutter UI. -->
+            <meta-data
+              android:name="io.flutter.embedding.android.NormalTheme"
+              android:resource="@style/NormalTheme"
+              />
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+        <!-- Don't delete the meta-data below.
+             This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
+        <meta-data
+            android:name="flutterEmbedding"
+            android:value="2" />
+    </application>
+</manifest>
diff --git a/packages/rfw/example/local/android/app/src/main/kotlin/dev/flutter/rfw/examples/local/MainActivity.kt b/packages/rfw/example/local/android/app/src/main/kotlin/dev/flutter/rfw/examples/local/MainActivity.kt
new file mode 100644
index 0000000..2eb31b4
--- /dev/null
+++ b/packages/rfw/example/local/android/app/src/main/kotlin/dev/flutter/rfw/examples/local/MainActivity.kt
@@ -0,0 +1,6 @@
+package dev.flutter.rfw.examples.local
+
+import io.flutter.embedding.android.FlutterActivity
+
+class MainActivity: FlutterActivity() {
+}
diff --git a/packages/rfw/example/local/android/app/src/main/res/drawable-v21/launch_background.xml b/packages/rfw/example/local/android/app/src/main/res/drawable-v21/launch_background.xml
new file mode 100644
index 0000000..f74085f
--- /dev/null
+++ b/packages/rfw/example/local/android/app/src/main/res/drawable-v21/launch_background.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Modify this file to customize your launch splash screen -->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:drawable="?android:colorBackground" />
+
+    <!-- You can insert your own image assets here -->
+    <!-- <item>
+        <bitmap
+            android:gravity="center"
+            android:src="@mipmap/launch_image" />
+    </item> -->
+</layer-list>
diff --git a/packages/rfw/example/local/android/app/src/main/res/drawable/launch_background.xml b/packages/rfw/example/local/android/app/src/main/res/drawable/launch_background.xml
new file mode 100644
index 0000000..304732f
--- /dev/null
+++ b/packages/rfw/example/local/android/app/src/main/res/drawable/launch_background.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Modify this file to customize your launch splash screen -->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:drawable="@android:color/white" />
+
+    <!-- You can insert your own image assets here -->
+    <!-- <item>
+        <bitmap
+            android:gravity="center"
+            android:src="@mipmap/launch_image" />
+    </item> -->
+</layer-list>
diff --git a/packages/rfw/example/local/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/packages/rfw/example/local/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..db77bb4
--- /dev/null
+++ b/packages/rfw/example/local/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
Binary files differ
diff --git a/packages/rfw/example/local/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/packages/rfw/example/local/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..17987b7
--- /dev/null
+++ b/packages/rfw/example/local/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
Binary files differ
diff --git a/packages/rfw/example/local/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/packages/rfw/example/local/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..09d4391
--- /dev/null
+++ b/packages/rfw/example/local/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
Binary files differ
diff --git a/packages/rfw/example/local/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/packages/rfw/example/local/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..d5f1c8d
--- /dev/null
+++ b/packages/rfw/example/local/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/packages/rfw/example/local/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/packages/rfw/example/local/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..4d6372e
--- /dev/null
+++ b/packages/rfw/example/local/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Binary files differ
diff --git a/packages/rfw/example/local/android/app/src/main/res/values-night/styles.xml b/packages/rfw/example/local/android/app/src/main/res/values-night/styles.xml
new file mode 100644
index 0000000..449a9f9
--- /dev/null
+++ b/packages/rfw/example/local/android/app/src/main/res/values-night/styles.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
+    <style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
+        <!-- Show a splash screen on the activity. Automatically removed when
+             Flutter draws its first frame -->
+        <item name="android:windowBackground">@drawable/launch_background</item>
+    </style>
+    <!-- Theme applied to the Android Window as soon as the process has started.
+         This theme determines the color of the Android Window while your
+         Flutter UI initializes, as well as behind your Flutter UI while its
+         running.
+         
+         This Theme is only used starting with V2 of Flutter's Android embedding. -->
+    <style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
+        <item name="android:windowBackground">?android:colorBackground</item>
+    </style>
+</resources>
diff --git a/packages/rfw/example/local/android/app/src/main/res/values/styles.xml b/packages/rfw/example/local/android/app/src/main/res/values/styles.xml
new file mode 100644
index 0000000..d74aa35
--- /dev/null
+++ b/packages/rfw/example/local/android/app/src/main/res/values/styles.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
+    <style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
+        <!-- Show a splash screen on the activity. Automatically removed when
+             Flutter draws its first frame -->
+        <item name="android:windowBackground">@drawable/launch_background</item>
+    </style>
+    <!-- Theme applied to the Android Window as soon as the process has started.
+         This theme determines the color of the Android Window while your
+         Flutter UI initializes, as well as behind your Flutter UI while its
+         running.
+         
+         This Theme is only used starting with V2 of Flutter's Android embedding. -->
+    <style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
+        <item name="android:windowBackground">?android:colorBackground</item>
+    </style>
+</resources>
diff --git a/packages/rfw/example/local/android/app/src/profile/AndroidManifest.xml b/packages/rfw/example/local/android/app/src/profile/AndroidManifest.xml
new file mode 100644
index 0000000..2fd5378
--- /dev/null
+++ b/packages/rfw/example/local/android/app/src/profile/AndroidManifest.xml
@@ -0,0 +1,7 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="dev.flutter.rfw.examples.local">
+    <!-- Flutter needs it to communicate with the running application
+         to allow setting breakpoints, to provide hot reload, etc.
+    -->
+    <uses-permission android:name="android.permission.INTERNET"/>
+</manifest>
diff --git a/packages/rfw/example/local/android/build.gradle b/packages/rfw/example/local/android/build.gradle
new file mode 100644
index 0000000..ed45c65
--- /dev/null
+++ b/packages/rfw/example/local/android/build.gradle
@@ -0,0 +1,29 @@
+buildscript {
+    ext.kotlin_version = '1.3.50'
+    repositories {
+        google()
+        mavenCentral()
+    }
+
+    dependencies {
+        classpath 'com.android.tools.build:gradle:4.1.0'
+        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
+    }
+}
+
+allprojects {
+    repositories {
+        google()
+        mavenCentral()
+    }
+}
+
+rootProject.buildDir = '../build'
+subprojects {
+    project.buildDir = "${rootProject.buildDir}/${project.name}"
+    project.evaluationDependsOn(':app')
+}
+
+task clean(type: Delete) {
+    delete rootProject.buildDir
+}
diff --git a/packages/rfw/example/local/android/gradle.properties b/packages/rfw/example/local/android/gradle.properties
new file mode 100644
index 0000000..94adc3a
--- /dev/null
+++ b/packages/rfw/example/local/android/gradle.properties
@@ -0,0 +1,3 @@
+org.gradle.jvmargs=-Xmx1536M
+android.useAndroidX=true
+android.enableJetifier=true
diff --git a/packages/rfw/example/local/android/gradle/wrapper/gradle-wrapper.properties b/packages/rfw/example/local/android/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..bc6a58a
--- /dev/null
+++ b/packages/rfw/example/local/android/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Fri Jun 23 08:50:38 CEST 2017
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip
diff --git a/packages/rfw/example/local/android/settings.gradle b/packages/rfw/example/local/android/settings.gradle
new file mode 100644
index 0000000..44e62bc
--- /dev/null
+++ b/packages/rfw/example/local/android/settings.gradle
@@ -0,0 +1,11 @@
+include ':app'
+
+def localPropertiesFile = new File(rootProject.projectDir, "local.properties")
+def properties = new Properties()
+
+assert localPropertiesFile.exists()
+localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }
+
+def flutterSdkPath = properties.getProperty("flutter.sdk")
+assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
+apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"
diff --git a/packages/rfw/example/local/ios/.gitignore b/packages/rfw/example/local/ios/.gitignore
new file mode 100644
index 0000000..151026b
--- /dev/null
+++ b/packages/rfw/example/local/ios/.gitignore
@@ -0,0 +1,33 @@
+*.mode1v3
+*.mode2v3
+*.moved-aside
+*.pbxuser
+*.perspectivev3
+**/*sync/
+.sconsign.dblite
+.tags*
+**/.vagrant/
+**/DerivedData/
+Icon?
+**/Pods/
+**/.symlinks/
+profile
+xcuserdata
+**/.generated/
+Flutter/App.framework
+Flutter/Flutter.framework
+Flutter/Flutter.podspec
+Flutter/Generated.xcconfig
+Flutter/ephemeral/
+Flutter/app.flx
+Flutter/app.zip
+Flutter/flutter_assets/
+Flutter/flutter_export_environment.sh
+ServiceDefinitions.json
+Runner/GeneratedPluginRegistrant.*
+
+# Exceptions to above rules.
+!default.mode1v3
+!default.mode2v3
+!default.pbxuser
+!default.perspectivev3
diff --git a/packages/rfw/example/local/ios/Flutter/AppFrameworkInfo.plist b/packages/rfw/example/local/ios/Flutter/AppFrameworkInfo.plist
new file mode 100644
index 0000000..8d4492f
--- /dev/null
+++ b/packages/rfw/example/local/ios/Flutter/AppFrameworkInfo.plist
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+  <key>CFBundleDevelopmentRegion</key>
+  <string>en</string>
+  <key>CFBundleExecutable</key>
+  <string>App</string>
+  <key>CFBundleIdentifier</key>
+  <string>io.flutter.flutter.app</string>
+  <key>CFBundleInfoDictionaryVersion</key>
+  <string>6.0</string>
+  <key>CFBundleName</key>
+  <string>App</string>
+  <key>CFBundlePackageType</key>
+  <string>FMWK</string>
+  <key>CFBundleShortVersionString</key>
+  <string>1.0</string>
+  <key>CFBundleSignature</key>
+  <string>????</string>
+  <key>CFBundleVersion</key>
+  <string>1.0</string>
+  <key>MinimumOSVersion</key>
+  <string>9.0</string>
+</dict>
+</plist>
diff --git a/packages/rfw/example/local/ios/Flutter/Debug.xcconfig b/packages/rfw/example/local/ios/Flutter/Debug.xcconfig
new file mode 100644
index 0000000..592ceee
--- /dev/null
+++ b/packages/rfw/example/local/ios/Flutter/Debug.xcconfig
@@ -0,0 +1 @@
+#include "Generated.xcconfig"
diff --git a/packages/rfw/example/local/ios/Flutter/Release.xcconfig b/packages/rfw/example/local/ios/Flutter/Release.xcconfig
new file mode 100644
index 0000000..592ceee
--- /dev/null
+++ b/packages/rfw/example/local/ios/Flutter/Release.xcconfig
@@ -0,0 +1 @@
+#include "Generated.xcconfig"
diff --git a/packages/rfw/example/local/ios/Runner.xcodeproj/project.pbxproj b/packages/rfw/example/local/ios/Runner.xcodeproj/project.pbxproj
new file mode 100644
index 0000000..d231a20
--- /dev/null
+++ b/packages/rfw/example/local/ios/Runner.xcodeproj/project.pbxproj
@@ -0,0 +1,481 @@
+// !$*UTF8*$!
+{
+	archiveVersion = 1;
+	classes = {
+	};
+	objectVersion = 50;
+	objects = {
+
+/* Begin PBXBuildFile section */
+		1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
+		3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
+		74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
+		97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
+		97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
+		97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXCopyFilesBuildPhase section */
+		9705A1C41CF9048500538489 /* Embed Frameworks */ = {
+			isa = PBXCopyFilesBuildPhase;
+			buildActionMask = 2147483647;
+			dstPath = "";
+			dstSubfolderSpec = 10;
+			files = (
+			);
+			name = "Embed Frameworks";
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXCopyFilesBuildPhase section */
+
+/* Begin PBXFileReference section */
+		1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
+		1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
+		3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
+		74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
+		74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
+		7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
+		9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
+		9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
+		97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
+		97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
+		97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
+		97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
+		97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+		97C146EB1CF9000F007C117D /* Frameworks */ = {
+			isa = PBXFrameworksBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+		9740EEB11CF90186004384FC /* Flutter */ = {
+			isa = PBXGroup;
+			children = (
+				3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
+				9740EEB21CF90195004384FC /* Debug.xcconfig */,
+				7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
+				9740EEB31CF90195004384FC /* Generated.xcconfig */,
+			);
+			name = Flutter;
+			sourceTree = "<group>";
+		};
+		97C146E51CF9000F007C117D = {
+			isa = PBXGroup;
+			children = (
+				9740EEB11CF90186004384FC /* Flutter */,
+				97C146F01CF9000F007C117D /* Runner */,
+				97C146EF1CF9000F007C117D /* Products */,
+			);
+			sourceTree = "<group>";
+		};
+		97C146EF1CF9000F007C117D /* Products */ = {
+			isa = PBXGroup;
+			children = (
+				97C146EE1CF9000F007C117D /* Runner.app */,
+			);
+			name = Products;
+			sourceTree = "<group>";
+		};
+		97C146F01CF9000F007C117D /* Runner */ = {
+			isa = PBXGroup;
+			children = (
+				97C146FA1CF9000F007C117D /* Main.storyboard */,
+				97C146FD1CF9000F007C117D /* Assets.xcassets */,
+				97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
+				97C147021CF9000F007C117D /* Info.plist */,
+				1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
+				1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
+				74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
+				74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
+			);
+			path = Runner;
+			sourceTree = "<group>";
+		};
+/* End PBXGroup section */
+
+/* Begin PBXNativeTarget section */
+		97C146ED1CF9000F007C117D /* Runner */ = {
+			isa = PBXNativeTarget;
+			buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
+			buildPhases = (
+				9740EEB61CF901F6004384FC /* Run Script */,
+				97C146EA1CF9000F007C117D /* Sources */,
+				97C146EB1CF9000F007C117D /* Frameworks */,
+				97C146EC1CF9000F007C117D /* Resources */,
+				9705A1C41CF9048500538489 /* Embed Frameworks */,
+				3B06AD1E1E4923F5004D2608 /* Thin Binary */,
+			);
+			buildRules = (
+			);
+			dependencies = (
+			);
+			name = Runner;
+			productName = Runner;
+			productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
+			productType = "com.apple.product-type.application";
+		};
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+		97C146E61CF9000F007C117D /* Project object */ = {
+			isa = PBXProject;
+			attributes = {
+				LastUpgradeCheck = 1300;
+				ORGANIZATIONNAME = "";
+				TargetAttributes = {
+					97C146ED1CF9000F007C117D = {
+						CreatedOnToolsVersion = 7.3.1;
+						LastSwiftMigration = 1100;
+					};
+				};
+			};
+			buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
+			compatibilityVersion = "Xcode 9.3";
+			developmentRegion = en;
+			hasScannedForEncodings = 0;
+			knownRegions = (
+				en,
+				Base,
+			);
+			mainGroup = 97C146E51CF9000F007C117D;
+			productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
+			projectDirPath = "";
+			projectRoot = "";
+			targets = (
+				97C146ED1CF9000F007C117D /* Runner */,
+			);
+		};
+/* End PBXProject section */
+
+/* Begin PBXResourcesBuildPhase section */
+		97C146EC1CF9000F007C117D /* Resources */ = {
+			isa = PBXResourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
+				3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
+				97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
+				97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXResourcesBuildPhase section */
+
+/* Begin PBXShellScriptBuildPhase section */
+		3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
+			isa = PBXShellScriptBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			inputPaths = (
+			);
+			name = "Thin Binary";
+			outputPaths = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+			shellPath = /bin/sh;
+			shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
+		};
+		9740EEB61CF901F6004384FC /* Run Script */ = {
+			isa = PBXShellScriptBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			inputPaths = (
+			);
+			name = "Run Script";
+			outputPaths = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+			shellPath = /bin/sh;
+			shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
+		};
+/* End PBXShellScriptBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+		97C146EA1CF9000F007C117D /* Sources */ = {
+			isa = PBXSourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
+				1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXSourcesBuildPhase section */
+
+/* Begin PBXVariantGroup section */
+		97C146FA1CF9000F007C117D /* Main.storyboard */ = {
+			isa = PBXVariantGroup;
+			children = (
+				97C146FB1CF9000F007C117D /* Base */,
+			);
+			name = Main.storyboard;
+			sourceTree = "<group>";
+		};
+		97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
+			isa = PBXVariantGroup;
+			children = (
+				97C147001CF9000F007C117D /* Base */,
+			);
+			name = LaunchScreen.storyboard;
+			sourceTree = "<group>";
+		};
+/* End PBXVariantGroup section */
+
+/* Begin XCBuildConfiguration section */
+		249021D3217E4FDB00AE95B9 /* Profile */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				ALWAYS_SEARCH_USER_PATHS = NO;
+				CLANG_ANALYZER_NONNULL = YES;
+				CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+				CLANG_CXX_LIBRARY = "libc++";
+				CLANG_ENABLE_MODULES = YES;
+				CLANG_ENABLE_OBJC_ARC = YES;
+				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+				CLANG_WARN_BOOL_CONVERSION = YES;
+				CLANG_WARN_COMMA = YES;
+				CLANG_WARN_CONSTANT_CONVERSION = YES;
+				CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+				CLANG_WARN_EMPTY_BODY = YES;
+				CLANG_WARN_ENUM_CONVERSION = YES;
+				CLANG_WARN_INFINITE_RECURSION = YES;
+				CLANG_WARN_INT_CONVERSION = YES;
+				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+				CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+				CLANG_WARN_STRICT_PROTOTYPES = YES;
+				CLANG_WARN_SUSPICIOUS_MOVE = YES;
+				CLANG_WARN_UNREACHABLE_CODE = YES;
+				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+				"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+				COPY_PHASE_STRIP = NO;
+				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+				ENABLE_NS_ASSERTIONS = NO;
+				ENABLE_STRICT_OBJC_MSGSEND = YES;
+				GCC_C_LANGUAGE_STANDARD = gnu99;
+				GCC_NO_COMMON_BLOCKS = YES;
+				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+				GCC_WARN_UNDECLARED_SELECTOR = YES;
+				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+				GCC_WARN_UNUSED_FUNCTION = YES;
+				GCC_WARN_UNUSED_VARIABLE = YES;
+				IPHONEOS_DEPLOYMENT_TARGET = 9.0;
+				MTL_ENABLE_DEBUG_INFO = NO;
+				SDKROOT = iphoneos;
+				SUPPORTED_PLATFORMS = iphoneos;
+				TARGETED_DEVICE_FAMILY = "1,2";
+				VALIDATE_PRODUCT = YES;
+			};
+			name = Profile;
+		};
+		249021D4217E4FDB00AE95B9 /* Profile */ = {
+			isa = XCBuildConfiguration;
+			baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
+			buildSettings = {
+				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+				CLANG_ENABLE_MODULES = YES;
+				CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
+				ENABLE_BITCODE = NO;
+				INFOPLIST_FILE = Runner/Info.plist;
+				LD_RUNPATH_SEARCH_PATHS = (
+					"$(inherited)",
+					"@executable_path/Frameworks",
+				);
+				PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.rfw.examples.local;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+				SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
+				SWIFT_VERSION = 5.0;
+				VERSIONING_SYSTEM = "apple-generic";
+			};
+			name = Profile;
+		};
+		97C147031CF9000F007C117D /* Debug */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				ALWAYS_SEARCH_USER_PATHS = NO;
+				CLANG_ANALYZER_NONNULL = YES;
+				CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+				CLANG_CXX_LIBRARY = "libc++";
+				CLANG_ENABLE_MODULES = YES;
+				CLANG_ENABLE_OBJC_ARC = YES;
+				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+				CLANG_WARN_BOOL_CONVERSION = YES;
+				CLANG_WARN_COMMA = YES;
+				CLANG_WARN_CONSTANT_CONVERSION = YES;
+				CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+				CLANG_WARN_EMPTY_BODY = YES;
+				CLANG_WARN_ENUM_CONVERSION = YES;
+				CLANG_WARN_INFINITE_RECURSION = YES;
+				CLANG_WARN_INT_CONVERSION = YES;
+				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+				CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+				CLANG_WARN_STRICT_PROTOTYPES = YES;
+				CLANG_WARN_SUSPICIOUS_MOVE = YES;
+				CLANG_WARN_UNREACHABLE_CODE = YES;
+				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+				"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+				COPY_PHASE_STRIP = NO;
+				DEBUG_INFORMATION_FORMAT = dwarf;
+				ENABLE_STRICT_OBJC_MSGSEND = YES;
+				ENABLE_TESTABILITY = YES;
+				GCC_C_LANGUAGE_STANDARD = gnu99;
+				GCC_DYNAMIC_NO_PIC = NO;
+				GCC_NO_COMMON_BLOCKS = YES;
+				GCC_OPTIMIZATION_LEVEL = 0;
+				GCC_PREPROCESSOR_DEFINITIONS = (
+					"DEBUG=1",
+					"$(inherited)",
+				);
+				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+				GCC_WARN_UNDECLARED_SELECTOR = YES;
+				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+				GCC_WARN_UNUSED_FUNCTION = YES;
+				GCC_WARN_UNUSED_VARIABLE = YES;
+				IPHONEOS_DEPLOYMENT_TARGET = 9.0;
+				MTL_ENABLE_DEBUG_INFO = YES;
+				ONLY_ACTIVE_ARCH = YES;
+				SDKROOT = iphoneos;
+				TARGETED_DEVICE_FAMILY = "1,2";
+			};
+			name = Debug;
+		};
+		97C147041CF9000F007C117D /* Release */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				ALWAYS_SEARCH_USER_PATHS = NO;
+				CLANG_ANALYZER_NONNULL = YES;
+				CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+				CLANG_CXX_LIBRARY = "libc++";
+				CLANG_ENABLE_MODULES = YES;
+				CLANG_ENABLE_OBJC_ARC = YES;
+				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+				CLANG_WARN_BOOL_CONVERSION = YES;
+				CLANG_WARN_COMMA = YES;
+				CLANG_WARN_CONSTANT_CONVERSION = YES;
+				CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+				CLANG_WARN_EMPTY_BODY = YES;
+				CLANG_WARN_ENUM_CONVERSION = YES;
+				CLANG_WARN_INFINITE_RECURSION = YES;
+				CLANG_WARN_INT_CONVERSION = YES;
+				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+				CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+				CLANG_WARN_STRICT_PROTOTYPES = YES;
+				CLANG_WARN_SUSPICIOUS_MOVE = YES;
+				CLANG_WARN_UNREACHABLE_CODE = YES;
+				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+				"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+				COPY_PHASE_STRIP = NO;
+				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+				ENABLE_NS_ASSERTIONS = NO;
+				ENABLE_STRICT_OBJC_MSGSEND = YES;
+				GCC_C_LANGUAGE_STANDARD = gnu99;
+				GCC_NO_COMMON_BLOCKS = YES;
+				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+				GCC_WARN_UNDECLARED_SELECTOR = YES;
+				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+				GCC_WARN_UNUSED_FUNCTION = YES;
+				GCC_WARN_UNUSED_VARIABLE = YES;
+				IPHONEOS_DEPLOYMENT_TARGET = 9.0;
+				MTL_ENABLE_DEBUG_INFO = NO;
+				SDKROOT = iphoneos;
+				SUPPORTED_PLATFORMS = iphoneos;
+				SWIFT_COMPILATION_MODE = wholemodule;
+				SWIFT_OPTIMIZATION_LEVEL = "-O";
+				TARGETED_DEVICE_FAMILY = "1,2";
+				VALIDATE_PRODUCT = YES;
+			};
+			name = Release;
+		};
+		97C147061CF9000F007C117D /* Debug */ = {
+			isa = XCBuildConfiguration;
+			baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
+			buildSettings = {
+				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+				CLANG_ENABLE_MODULES = YES;
+				CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
+				ENABLE_BITCODE = NO;
+				INFOPLIST_FILE = Runner/Info.plist;
+				LD_RUNPATH_SEARCH_PATHS = (
+					"$(inherited)",
+					"@executable_path/Frameworks",
+				);
+				PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.rfw.examples.local;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+				SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
+				SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+				SWIFT_VERSION = 5.0;
+				VERSIONING_SYSTEM = "apple-generic";
+			};
+			name = Debug;
+		};
+		97C147071CF9000F007C117D /* Release */ = {
+			isa = XCBuildConfiguration;
+			baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
+			buildSettings = {
+				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+				CLANG_ENABLE_MODULES = YES;
+				CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
+				ENABLE_BITCODE = NO;
+				INFOPLIST_FILE = Runner/Info.plist;
+				LD_RUNPATH_SEARCH_PATHS = (
+					"$(inherited)",
+					"@executable_path/Frameworks",
+				);
+				PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.rfw.examples.local;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+				SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
+				SWIFT_VERSION = 5.0;
+				VERSIONING_SYSTEM = "apple-generic";
+			};
+			name = Release;
+		};
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+		97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				97C147031CF9000F007C117D /* Debug */,
+				97C147041CF9000F007C117D /* Release */,
+				249021D3217E4FDB00AE95B9 /* Profile */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
+		};
+		97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				97C147061CF9000F007C117D /* Debug */,
+				97C147071CF9000F007C117D /* Release */,
+				249021D4217E4FDB00AE95B9 /* Profile */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
+		};
+/* End XCConfigurationList section */
+	};
+	rootObject = 97C146E61CF9000F007C117D /* Project object */;
+}
diff --git a/packages/rfw/example/local/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/packages/rfw/example/local/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 0000000..919434a
--- /dev/null
+++ b/packages/rfw/example/local/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Workspace
+   version = "1.0">
+   <FileRef
+      location = "self:">
+   </FileRef>
+</Workspace>
diff --git a/packages/rfw/example/local/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/rfw/example/local/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
new file mode 100644
index 0000000..18d9810
--- /dev/null
+++ b/packages/rfw/example/local/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>IDEDidComputeMac32BitWarning</key>
+	<true/>
+</dict>
+</plist>
diff --git a/packages/rfw/example/local/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/rfw/example/local/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
new file mode 100644
index 0000000..f9b0d7c
--- /dev/null
+++ b/packages/rfw/example/local/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>PreviewsEnabled</key>
+	<false/>
+</dict>
+</plist>
diff --git a/packages/rfw/example/local/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/rfw/example/local/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
new file mode 100644
index 0000000..c87d15a
--- /dev/null
+++ b/packages/rfw/example/local/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
@@ -0,0 +1,87 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Scheme
+   LastUpgradeVersion = "1300"
+   version = "1.3">
+   <BuildAction
+      parallelizeBuildables = "YES"
+      buildImplicitDependencies = "YES">
+      <BuildActionEntries>
+         <BuildActionEntry
+            buildForTesting = "YES"
+            buildForRunning = "YES"
+            buildForProfiling = "YES"
+            buildForArchiving = "YES"
+            buildForAnalyzing = "YES">
+            <BuildableReference
+               BuildableIdentifier = "primary"
+               BlueprintIdentifier = "97C146ED1CF9000F007C117D"
+               BuildableName = "Runner.app"
+               BlueprintName = "Runner"
+               ReferencedContainer = "container:Runner.xcodeproj">
+            </BuildableReference>
+         </BuildActionEntry>
+      </BuildActionEntries>
+   </BuildAction>
+   <TestAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      shouldUseLaunchSchemeArgsEnv = "YES">
+      <MacroExpansion>
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "97C146ED1CF9000F007C117D"
+            BuildableName = "Runner.app"
+            BlueprintName = "Runner"
+            ReferencedContainer = "container:Runner.xcodeproj">
+         </BuildableReference>
+      </MacroExpansion>
+      <Testables>
+      </Testables>
+   </TestAction>
+   <LaunchAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      launchStyle = "0"
+      useCustomWorkingDirectory = "NO"
+      ignoresPersistentStateOnLaunch = "NO"
+      debugDocumentVersioning = "YES"
+      debugServiceExtension = "internal"
+      allowLocationSimulation = "YES">
+      <BuildableProductRunnable
+         runnableDebuggingMode = "0">
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "97C146ED1CF9000F007C117D"
+            BuildableName = "Runner.app"
+            BlueprintName = "Runner"
+            ReferencedContainer = "container:Runner.xcodeproj">
+         </BuildableReference>
+      </BuildableProductRunnable>
+   </LaunchAction>
+   <ProfileAction
+      buildConfiguration = "Profile"
+      shouldUseLaunchSchemeArgsEnv = "YES"
+      savedToolIdentifier = ""
+      useCustomWorkingDirectory = "NO"
+      debugDocumentVersioning = "YES">
+      <BuildableProductRunnable
+         runnableDebuggingMode = "0">
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "97C146ED1CF9000F007C117D"
+            BuildableName = "Runner.app"
+            BlueprintName = "Runner"
+            ReferencedContainer = "container:Runner.xcodeproj">
+         </BuildableReference>
+      </BuildableProductRunnable>
+   </ProfileAction>
+   <AnalyzeAction
+      buildConfiguration = "Debug">
+   </AnalyzeAction>
+   <ArchiveAction
+      buildConfiguration = "Release"
+      revealArchiveInOrganizer = "YES">
+   </ArchiveAction>
+</Scheme>
diff --git a/packages/rfw/example/local/ios/Runner.xcworkspace/contents.xcworkspacedata b/packages/rfw/example/local/ios/Runner.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 0000000..1d526a1
--- /dev/null
+++ b/packages/rfw/example/local/ios/Runner.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Workspace
+   version = "1.0">
+   <FileRef
+      location = "group:Runner.xcodeproj">
+   </FileRef>
+</Workspace>
diff --git a/packages/rfw/example/local/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/rfw/example/local/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
new file mode 100644
index 0000000..18d9810
--- /dev/null
+++ b/packages/rfw/example/local/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>IDEDidComputeMac32BitWarning</key>
+	<true/>
+</dict>
+</plist>
diff --git a/packages/rfw/example/local/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/rfw/example/local/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
new file mode 100644
index 0000000..f9b0d7c
--- /dev/null
+++ b/packages/rfw/example/local/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>PreviewsEnabled</key>
+	<false/>
+</dict>
+</plist>
diff --git a/packages/rfw/example/local/ios/Runner/AppDelegate.swift b/packages/rfw/example/local/ios/Runner/AppDelegate.swift
new file mode 100644
index 0000000..caf9983
--- /dev/null
+++ b/packages/rfw/example/local/ios/Runner/AppDelegate.swift
@@ -0,0 +1,17 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import UIKit
+import Flutter
+
+@UIApplicationMain
+@objc class AppDelegate: FlutterAppDelegate {
+  override func application(
+    _ application: UIApplication,
+    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
+  ) -> Bool {
+    GeneratedPluginRegistrant.register(with: self)
+    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
+  }
+}
diff --git a/packages/rfw/example/local/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/packages/rfw/example/local/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
new file mode 100644
index 0000000..d36b1fa
--- /dev/null
+++ b/packages/rfw/example/local/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -0,0 +1,122 @@
+{
+  "images" : [
+    {
+      "size" : "20x20",
+      "idiom" : "iphone",
+      "filename" : "Icon-App-20x20@2x.png",
+      "scale" : "2x"
+    },
+    {
+      "size" : "20x20",
+      "idiom" : "iphone",
+      "filename" : "Icon-App-20x20@3x.png",
+      "scale" : "3x"
+    },
+    {
+      "size" : "29x29",
+      "idiom" : "iphone",
+      "filename" : "Icon-App-29x29@1x.png",
+      "scale" : "1x"
+    },
+    {
+      "size" : "29x29",
+      "idiom" : "iphone",
+      "filename" : "Icon-App-29x29@2x.png",
+      "scale" : "2x"
+    },
+    {
+      "size" : "29x29",
+      "idiom" : "iphone",
+      "filename" : "Icon-App-29x29@3x.png",
+      "scale" : "3x"
+    },
+    {
+      "size" : "40x40",
+      "idiom" : "iphone",
+      "filename" : "Icon-App-40x40@2x.png",
+      "scale" : "2x"
+    },
+    {
+      "size" : "40x40",
+      "idiom" : "iphone",
+      "filename" : "Icon-App-40x40@3x.png",
+      "scale" : "3x"
+    },
+    {
+      "size" : "60x60",
+      "idiom" : "iphone",
+      "filename" : "Icon-App-60x60@2x.png",
+      "scale" : "2x"
+    },
+    {
+      "size" : "60x60",
+      "idiom" : "iphone",
+      "filename" : "Icon-App-60x60@3x.png",
+      "scale" : "3x"
+    },
+    {
+      "size" : "20x20",
+      "idiom" : "ipad",
+      "filename" : "Icon-App-20x20@1x.png",
+      "scale" : "1x"
+    },
+    {
+      "size" : "20x20",
+      "idiom" : "ipad",
+      "filename" : "Icon-App-20x20@2x.png",
+      "scale" : "2x"
+    },
+    {
+      "size" : "29x29",
+      "idiom" : "ipad",
+      "filename" : "Icon-App-29x29@1x.png",
+      "scale" : "1x"
+    },
+    {
+      "size" : "29x29",
+      "idiom" : "ipad",
+      "filename" : "Icon-App-29x29@2x.png",
+      "scale" : "2x"
+    },
+    {
+      "size" : "40x40",
+      "idiom" : "ipad",
+      "filename" : "Icon-App-40x40@1x.png",
+      "scale" : "1x"
+    },
+    {
+      "size" : "40x40",
+      "idiom" : "ipad",
+      "filename" : "Icon-App-40x40@2x.png",
+      "scale" : "2x"
+    },
+    {
+      "size" : "76x76",
+      "idiom" : "ipad",
+      "filename" : "Icon-App-76x76@1x.png",
+      "scale" : "1x"
+    },
+    {
+      "size" : "76x76",
+      "idiom" : "ipad",
+      "filename" : "Icon-App-76x76@2x.png",
+      "scale" : "2x"
+    },
+    {
+      "size" : "83.5x83.5",
+      "idiom" : "ipad",
+      "filename" : "Icon-App-83.5x83.5@2x.png",
+      "scale" : "2x"
+    },
+    {
+      "size" : "1024x1024",
+      "idiom" : "ios-marketing",
+      "filename" : "Icon-App-1024x1024@1x.png",
+      "scale" : "1x"
+    }
+  ],
+  "info" : {
+    "version" : 1,
+    "author" : "xcode"
+  }
+}
diff --git a/packages/rfw/example/local/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/packages/rfw/example/local/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
new file mode 100644
index 0000000..dc9ada4
--- /dev/null
+++ b/packages/rfw/example/local/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
Binary files differ
diff --git a/packages/rfw/example/local/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/packages/rfw/example/local/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
new file mode 100644
index 0000000..28c6bf0
--- /dev/null
+++ b/packages/rfw/example/local/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
Binary files differ
diff --git a/packages/rfw/example/local/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/packages/rfw/example/local/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
new file mode 100644
index 0000000..2ccbfd9
--- /dev/null
+++ b/packages/rfw/example/local/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
Binary files differ
diff --git a/packages/rfw/example/local/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/packages/rfw/example/local/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
new file mode 100644
index 0000000..f091b6b
--- /dev/null
+++ b/packages/rfw/example/local/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
Binary files differ
diff --git a/packages/rfw/example/local/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/packages/rfw/example/local/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
new file mode 100644
index 0000000..4cde121
--- /dev/null
+++ b/packages/rfw/example/local/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
Binary files differ
diff --git a/packages/rfw/example/local/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/packages/rfw/example/local/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
new file mode 100644
index 0000000..d0ef06e
--- /dev/null
+++ b/packages/rfw/example/local/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
Binary files differ
diff --git a/packages/rfw/example/local/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/packages/rfw/example/local/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
new file mode 100644
index 0000000..dcdc230
--- /dev/null
+++ b/packages/rfw/example/local/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
Binary files differ
diff --git a/packages/rfw/example/local/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/packages/rfw/example/local/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
new file mode 100644
index 0000000..2ccbfd9
--- /dev/null
+++ b/packages/rfw/example/local/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
Binary files differ
diff --git a/packages/rfw/example/local/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/packages/rfw/example/local/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
new file mode 100644
index 0000000..c8f9ed8
--- /dev/null
+++ b/packages/rfw/example/local/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
Binary files differ
diff --git a/packages/rfw/example/local/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/packages/rfw/example/local/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
new file mode 100644
index 0000000..a6d6b86
--- /dev/null
+++ b/packages/rfw/example/local/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
Binary files differ
diff --git a/packages/rfw/example/local/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/packages/rfw/example/local/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
new file mode 100644
index 0000000..a6d6b86
--- /dev/null
+++ b/packages/rfw/example/local/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
Binary files differ
diff --git a/packages/rfw/example/local/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/packages/rfw/example/local/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
new file mode 100644
index 0000000..75b2d16
--- /dev/null
+++ b/packages/rfw/example/local/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
Binary files differ
diff --git a/packages/rfw/example/local/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/packages/rfw/example/local/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
new file mode 100644
index 0000000..c4df70d
--- /dev/null
+++ b/packages/rfw/example/local/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
Binary files differ
diff --git a/packages/rfw/example/local/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/packages/rfw/example/local/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
new file mode 100644
index 0000000..6a84f41
--- /dev/null
+++ b/packages/rfw/example/local/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
Binary files differ
diff --git a/packages/rfw/example/local/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/packages/rfw/example/local/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
new file mode 100644
index 0000000..d0e1f58
--- /dev/null
+++ b/packages/rfw/example/local/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
Binary files differ
diff --git a/packages/rfw/example/local/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/packages/rfw/example/local/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json
new file mode 100644
index 0000000..0bedcf2
--- /dev/null
+++ b/packages/rfw/example/local/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json
@@ -0,0 +1,23 @@
+{
+  "images" : [
+    {
+      "idiom" : "universal",
+      "filename" : "LaunchImage.png",
+      "scale" : "1x"
+    },
+    {
+      "idiom" : "universal",
+      "filename" : "LaunchImage@2x.png",
+      "scale" : "2x"
+    },
+    {
+      "idiom" : "universal",
+      "filename" : "LaunchImage@3x.png",
+      "scale" : "3x"
+    }
+  ],
+  "info" : {
+    "version" : 1,
+    "author" : "xcode"
+  }
+}
diff --git a/packages/rfw/example/local/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/packages/rfw/example/local/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
new file mode 100644
index 0000000..9da19ea
--- /dev/null
+++ b/packages/rfw/example/local/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
Binary files differ
diff --git a/packages/rfw/example/local/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/packages/rfw/example/local/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
new file mode 100644
index 0000000..9da19ea
--- /dev/null
+++ b/packages/rfw/example/local/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
Binary files differ
diff --git a/packages/rfw/example/local/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/packages/rfw/example/local/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
new file mode 100644
index 0000000..9da19ea
--- /dev/null
+++ b/packages/rfw/example/local/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
Binary files differ
diff --git a/packages/rfw/example/local/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/packages/rfw/example/local/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md
new file mode 100644
index 0000000..89c2725
--- /dev/null
+++ b/packages/rfw/example/local/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md
@@ -0,0 +1,5 @@
+# Launch Screen Assets
+
+You can customize the launch screen with your own desired assets by replacing the image files in this directory.
+
+You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.
\ No newline at end of file
diff --git a/packages/rfw/example/local/ios/Runner/Base.lproj/LaunchScreen.storyboard b/packages/rfw/example/local/ios/Runner/Base.lproj/LaunchScreen.storyboard
new file mode 100644
index 0000000..f2e259c
--- /dev/null
+++ b/packages/rfw/example/local/ios/Runner/Base.lproj/LaunchScreen.storyboard
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16G29" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
+    <dependencies>
+        <deployment identifier="iOS"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/>
+    </dependencies>
+    <scenes>
+        <!--View Controller-->
+        <scene sceneID="EHf-IW-A2E">
+            <objects>
+                <viewController id="01J-lp-oVM" sceneMemberID="viewController">
+                    <layoutGuides>
+                        <viewControllerLayoutGuide type="top" id="Ydg-fD-yQy"/>
+                        <viewControllerLayoutGuide type="bottom" id="xbc-2k-c8Z"/>
+                    </layoutGuides>
+                    <view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
+                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+                        <subviews>
+                            <imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4">
+                            </imageView>
+                        </subviews>
+                        <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+                        <constraints>
+                            <constraint firstItem="YRO-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/>
+                            <constraint firstItem="YRO-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4X2-HB-R7a"/>
+                        </constraints>
+                    </view>
+                </viewController>
+                <placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
+            </objects>
+            <point key="canvasLocation" x="53" y="375"/>
+        </scene>
+    </scenes>
+    <resources>
+        <image name="LaunchImage" width="168" height="185"/>
+    </resources>
+</document>
diff --git a/packages/rfw/example/local/ios/Runner/Base.lproj/Main.storyboard b/packages/rfw/example/local/ios/Runner/Base.lproj/Main.storyboard
new file mode 100644
index 0000000..f3c2851
--- /dev/null
+++ b/packages/rfw/example/local/ios/Runner/Base.lproj/Main.storyboard
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
+    <dependencies>
+        <deployment identifier="iOS"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
+    </dependencies>
+    <scenes>
+        <!--Flutter View Controller-->
+        <scene sceneID="tne-QT-ifu">
+            <objects>
+                <viewController id="BYZ-38-t0r" customClass="FlutterViewController" sceneMemberID="viewController">
+                    <layoutGuides>
+                        <viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
+                        <viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
+                    </layoutGuides>
+                    <view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
+                        <rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
+                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+                        <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
+                    </view>
+                </viewController>
+                <placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
+            </objects>
+        </scene>
+    </scenes>
+</document>
diff --git a/packages/rfw/example/local/ios/Runner/Info.plist b/packages/rfw/example/local/ios/Runner/Info.plist
new file mode 100644
index 0000000..2150ab0
--- /dev/null
+++ b/packages/rfw/example/local/ios/Runner/Info.plist
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>CFBundleDevelopmentRegion</key>
+	<string>$(DEVELOPMENT_LANGUAGE)</string>
+	<key>CFBundleExecutable</key>
+	<string>$(EXECUTABLE_NAME)</string>
+	<key>CFBundleIdentifier</key>
+	<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
+	<key>CFBundleInfoDictionaryVersion</key>
+	<string>6.0</string>
+	<key>CFBundleName</key>
+	<string>local</string>
+	<key>CFBundlePackageType</key>
+	<string>APPL</string>
+	<key>CFBundleShortVersionString</key>
+	<string>$(FLUTTER_BUILD_NAME)</string>
+	<key>CFBundleSignature</key>
+	<string>????</string>
+	<key>CFBundleVersion</key>
+	<string>$(FLUTTER_BUILD_NUMBER)</string>
+	<key>LSRequiresIPhoneOS</key>
+	<true/>
+	<key>UILaunchStoryboardName</key>
+	<string>LaunchScreen</string>
+	<key>UIMainStoryboardFile</key>
+	<string>Main</string>
+	<key>UISupportedInterfaceOrientations</key>
+	<array>
+		<string>UIInterfaceOrientationPortrait</string>
+		<string>UIInterfaceOrientationLandscapeLeft</string>
+		<string>UIInterfaceOrientationLandscapeRight</string>
+	</array>
+	<key>UISupportedInterfaceOrientations~ipad</key>
+	<array>
+		<string>UIInterfaceOrientationPortrait</string>
+		<string>UIInterfaceOrientationPortraitUpsideDown</string>
+		<string>UIInterfaceOrientationLandscapeLeft</string>
+		<string>UIInterfaceOrientationLandscapeRight</string>
+	</array>
+	<key>UIViewControllerBasedStatusBarAppearance</key>
+	<false/>
+</dict>
+</plist>
diff --git a/packages/rfw/example/local/ios/Runner/Runner-Bridging-Header.h b/packages/rfw/example/local/ios/Runner/Runner-Bridging-Header.h
new file mode 100644
index 0000000..eb7e8ba
--- /dev/null
+++ b/packages/rfw/example/local/ios/Runner/Runner-Bridging-Header.h
@@ -0,0 +1,5 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "GeneratedPluginRegistrant.h"
diff --git a/packages/rfw/example/local/lib/main.dart b/packages/rfw/example/local/lib/main.dart
new file mode 100644
index 0000000..d9f36b7
--- /dev/null
+++ b/packages/rfw/example/local/lib/main.dart
@@ -0,0 +1,89 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:flutter/widgets.dart';
+
+/// For clarity, this example uses the text representation of the sample remote
+/// widget library, and parses it locally. To do this, [parseLibraryFile] is
+/// used. In production, this is strongly discouraged since it is 10x slower
+/// than using [decodeLibraryBlob] to parse the binary version of the format.
+import 'package:rfw/formats.dart' show parseLibraryFile;
+
+import 'package:rfw/rfw.dart';
+
+void main() {
+  runApp(const Example());
+}
+
+class Example extends StatefulWidget {
+  const Example({Key? key}) : super(key: key);
+
+  @override
+  State<Example> createState() => _ExampleState();
+}
+
+class _ExampleState extends State<Example> {
+  final Runtime _runtime = Runtime();
+  final DynamicContent _data = DynamicContent();
+
+  @override
+  void initState() {
+    super.initState();
+    _update();
+  }
+
+  @override
+  void reassemble() {
+    super.reassemble();
+    _update();
+  }
+
+  static WidgetLibrary _createLocalWidgets() {
+    return LocalWidgetLibrary(<String, LocalWidgetBuilder>{
+      'GreenBox': (BuildContext context, DataSource source) {
+        return Container(
+          color: const Color(0xFF002211),
+          child: source.child(<Object>['child']),
+        );
+      },
+      'Hello': (BuildContext context, DataSource source) {
+        return Center(
+          child: Text(
+            'Hello, ${source.v<String>(<Object>["name"])}!',
+            textDirection: TextDirection.ltr,
+          ),
+        );
+      },
+    });
+  }
+
+  static const LibraryName localName = LibraryName(<String>['local']);
+  static const LibraryName remoteName = LibraryName(<String>['remote']);
+
+  void _update() {
+    _runtime.update(localName, _createLocalWidgets());
+    // Normally we would obtain the remote widget library in binary form from a
+    // server, and decode it with [decodeLibraryBlob] rather than parsing the
+    // text version using [parseLibraryFile]. However, to make it easier to
+    // play with this sample, this uses the slower text format.
+    _runtime.update(remoteName, parseLibraryFile('''
+      import local;
+      widget root = GreenBox(
+        child: Hello(name: "World"),
+      );
+    '''));
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return RemoteWidget(
+      runtime: _runtime,
+      data: _data,
+      widget: const FullyQualifiedWidgetName(remoteName, 'root'),
+      onEvent: (String name, DynamicMap arguments) {
+        debugPrint('user triggered event "$name" with data: $arguments');
+      },
+    );
+  }
+}
diff --git a/packages/rfw/example/local/linux/.gitignore b/packages/rfw/example/local/linux/.gitignore
new file mode 100644
index 0000000..d3896c9
--- /dev/null
+++ b/packages/rfw/example/local/linux/.gitignore
@@ -0,0 +1 @@
+flutter/ephemeral
diff --git a/packages/rfw/example/local/linux/CMakeLists.txt b/packages/rfw/example/local/linux/CMakeLists.txt
new file mode 100644
index 0000000..a88ab3c
--- /dev/null
+++ b/packages/rfw/example/local/linux/CMakeLists.txt
@@ -0,0 +1,116 @@
+cmake_minimum_required(VERSION 3.10)
+project(runner LANGUAGES CXX)
+
+set(BINARY_NAME "local")
+set(APPLICATION_ID "dev.flutter.rfw.examples.local")
+
+cmake_policy(SET CMP0063 NEW)
+
+set(CMAKE_INSTALL_RPATH "$ORIGIN/lib")
+
+# Root filesystem for cross-building.
+if(FLUTTER_TARGET_PLATFORM_SYSROOT)
+  set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT})
+  set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT})
+  set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
+  set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
+  set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
+  set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
+endif()
+
+# Configure build options.
+if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
+  set(CMAKE_BUILD_TYPE "Debug" CACHE
+    STRING "Flutter build mode" FORCE)
+  set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
+    "Debug" "Profile" "Release")
+endif()
+
+# Compilation settings that should be applied to most targets.
+function(APPLY_STANDARD_SETTINGS TARGET)
+  target_compile_features(${TARGET} PUBLIC cxx_std_14)
+  target_compile_options(${TARGET} PRIVATE -Wall -Werror)
+  target_compile_options(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:-O3>")
+  target_compile_definitions(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:NDEBUG>")
+endfunction()
+
+set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter")
+
+# Flutter library and tool build rules.
+add_subdirectory(${FLUTTER_MANAGED_DIR})
+
+# System-level dependencies.
+find_package(PkgConfig REQUIRED)
+pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
+
+add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}")
+
+# Application build
+add_executable(${BINARY_NAME}
+  "main.cc"
+  "my_application.cc"
+  "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc"
+)
+apply_standard_settings(${BINARY_NAME})
+target_link_libraries(${BINARY_NAME} PRIVATE flutter)
+target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK)
+add_dependencies(${BINARY_NAME} flutter_assemble)
+# Only the install-generated bundle's copy of the executable will launch
+# correctly, since the resources must in the right relative locations. To avoid
+# people trying to run the unbundled copy, put it in a subdirectory instead of
+# the default top-level location.
+set_target_properties(${BINARY_NAME}
+  PROPERTIES
+  RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run"
+)
+
+# Generated plugin build rules, which manage building the plugins and adding
+# them to the application.
+include(flutter/generated_plugins.cmake)
+
+
+# === Installation ===
+# By default, "installing" just makes a relocatable bundle in the build
+# directory.
+set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle")
+if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
+  set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE)
+endif()
+
+# Start with a clean build bundle directory every time.
+install(CODE "
+  file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\")
+  " COMPONENT Runtime)
+
+set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data")
+set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib")
+
+install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}"
+  COMPONENT Runtime)
+
+install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
+  COMPONENT Runtime)
+
+install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
+  COMPONENT Runtime)
+
+if(PLUGIN_BUNDLED_LIBRARIES)
+  install(FILES "${PLUGIN_BUNDLED_LIBRARIES}"
+    DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
+    COMPONENT Runtime)
+endif()
+
+# Fully re-copy the assets directory on each build to avoid having stale files
+# from a previous install.
+set(FLUTTER_ASSET_DIR_NAME "flutter_assets")
+install(CODE "
+  file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\")
+  " COMPONENT Runtime)
+install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}"
+  DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime)
+
+# Install the AOT library on non-Debug builds only.
+if(NOT CMAKE_BUILD_TYPE MATCHES "Debug")
+  install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
+    COMPONENT Runtime)
+endif()
diff --git a/packages/rfw/example/local/linux/flutter/CMakeLists.txt b/packages/rfw/example/local/linux/flutter/CMakeLists.txt
new file mode 100644
index 0000000..33fd580
--- /dev/null
+++ b/packages/rfw/example/local/linux/flutter/CMakeLists.txt
@@ -0,0 +1,87 @@
+cmake_minimum_required(VERSION 3.10)
+
+set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral")
+
+# Configuration provided via flutter tool.
+include(${EPHEMERAL_DIR}/generated_config.cmake)
+
+# TODO: Move the rest of this into files in ephemeral. See
+# https://github.com/flutter/flutter/issues/57146.
+
+# Serves the same purpose as list(TRANSFORM ... PREPEND ...),
+# which isn't available in 3.10.
+function(list_prepend LIST_NAME PREFIX)
+    set(NEW_LIST "")
+    foreach(element ${${LIST_NAME}})
+        list(APPEND NEW_LIST "${PREFIX}${element}")
+    endforeach(element)
+    set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE)
+endfunction()
+
+# === Flutter Library ===
+# System-level dependencies.
+find_package(PkgConfig REQUIRED)
+pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
+pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0)
+pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0)
+
+set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so")
+
+# Published to parent scope for install step.
+set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE)
+set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE)
+set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE)
+set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE)
+
+list(APPEND FLUTTER_LIBRARY_HEADERS
+  "fl_basic_message_channel.h"
+  "fl_binary_codec.h"
+  "fl_binary_messenger.h"
+  "fl_dart_project.h"
+  "fl_engine.h"
+  "fl_json_message_codec.h"
+  "fl_json_method_codec.h"
+  "fl_message_codec.h"
+  "fl_method_call.h"
+  "fl_method_channel.h"
+  "fl_method_codec.h"
+  "fl_method_response.h"
+  "fl_plugin_registrar.h"
+  "fl_plugin_registry.h"
+  "fl_standard_message_codec.h"
+  "fl_standard_method_codec.h"
+  "fl_string_codec.h"
+  "fl_value.h"
+  "fl_view.h"
+  "flutter_linux.h"
+)
+list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/")
+add_library(flutter INTERFACE)
+target_include_directories(flutter INTERFACE
+  "${EPHEMERAL_DIR}"
+)
+target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}")
+target_link_libraries(flutter INTERFACE
+  PkgConfig::GTK
+  PkgConfig::GLIB
+  PkgConfig::GIO
+)
+add_dependencies(flutter flutter_assemble)
+
+# === Flutter tool backend ===
+# _phony_ is a non-existent file to force this command to run every time,
+# since currently there's no way to get a full input/output list from the
+# flutter tool.
+add_custom_command(
+  OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS}
+    ${CMAKE_CURRENT_BINARY_DIR}/_phony_
+  COMMAND ${CMAKE_COMMAND} -E env
+    ${FLUTTER_TOOL_ENVIRONMENT}
+    "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh"
+      ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE}
+  VERBATIM
+)
+add_custom_target(flutter_assemble DEPENDS
+  "${FLUTTER_LIBRARY}"
+  ${FLUTTER_LIBRARY_HEADERS}
+)
diff --git a/packages/rfw/example/local/linux/flutter/generated_plugins.cmake b/packages/rfw/example/local/linux/flutter/generated_plugins.cmake
new file mode 100644
index 0000000..51436ae
--- /dev/null
+++ b/packages/rfw/example/local/linux/flutter/generated_plugins.cmake
@@ -0,0 +1,15 @@
+#
+# Generated file, do not edit.
+#
+
+list(APPEND FLUTTER_PLUGIN_LIST
+)
+
+set(PLUGIN_BUNDLED_LIBRARIES)
+
+foreach(plugin ${FLUTTER_PLUGIN_LIST})
+  add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin})
+  target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)
+  list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>)
+  list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
+endforeach(plugin)
diff --git a/packages/rfw/example/local/linux/main.cc b/packages/rfw/example/local/linux/main.cc
new file mode 100644
index 0000000..1507d02
--- /dev/null
+++ b/packages/rfw/example/local/linux/main.cc
@@ -0,0 +1,10 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "my_application.h"
+
+int main(int argc, char** argv) {
+  g_autoptr(MyApplication) app = my_application_new();
+  return g_application_run(G_APPLICATION(app), argc, argv);
+}
diff --git a/packages/rfw/example/local/linux/my_application.cc b/packages/rfw/example/local/linux/my_application.cc
new file mode 100644
index 0000000..98a9b29
--- /dev/null
+++ b/packages/rfw/example/local/linux/my_application.cc
@@ -0,0 +1,111 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "my_application.h"
+
+#include <flutter_linux/flutter_linux.h>
+#ifdef GDK_WINDOWING_X11
+#include <gdk/gdkx.h>
+#endif
+
+#include "flutter/generated_plugin_registrant.h"
+
+struct _MyApplication {
+  GtkApplication parent_instance;
+  char** dart_entrypoint_arguments;
+};
+
+G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION)
+
+// Implements GApplication::activate.
+static void my_application_activate(GApplication* application) {
+  MyApplication* self = MY_APPLICATION(application);
+  GtkWindow* window =
+      GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application)));
+
+  // Use a header bar when running in GNOME as this is the common style used
+  // by applications and is the setup most users will be using (e.g. Ubuntu
+  // desktop).
+  // If running on X and not using GNOME then just use a traditional title bar
+  // in case the window manager does more exotic layout, e.g. tiling.
+  // If running on Wayland assume the header bar will work (may need changing
+  // if future cases occur).
+  gboolean use_header_bar = TRUE;
+#ifdef GDK_WINDOWING_X11
+  GdkScreen* screen = gtk_window_get_screen(window);
+  if (GDK_IS_X11_SCREEN(screen)) {
+    const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen);
+    if (g_strcmp0(wm_name, "GNOME Shell") != 0) {
+      use_header_bar = FALSE;
+    }
+  }
+#endif
+  if (use_header_bar) {
+    GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new());
+    gtk_widget_show(GTK_WIDGET(header_bar));
+    gtk_header_bar_set_title(header_bar, "local");
+    gtk_header_bar_set_show_close_button(header_bar, TRUE);
+    gtk_window_set_titlebar(window, GTK_WIDGET(header_bar));
+  } else {
+    gtk_window_set_title(window, "local");
+  }
+
+  gtk_window_set_default_size(window, 1280, 720);
+  gtk_widget_show(GTK_WIDGET(window));
+
+  g_autoptr(FlDartProject) project = fl_dart_project_new();
+  fl_dart_project_set_dart_entrypoint_arguments(
+      project, self->dart_entrypoint_arguments);
+
+  FlView* view = fl_view_new(project);
+  gtk_widget_show(GTK_WIDGET(view));
+  gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view));
+
+  fl_register_plugins(FL_PLUGIN_REGISTRY(view));
+
+  gtk_widget_grab_focus(GTK_WIDGET(view));
+}
+
+// Implements GApplication::local_command_line.
+static gboolean my_application_local_command_line(GApplication* application,
+                                                  gchar*** arguments,
+                                                  int* exit_status) {
+  MyApplication* self = MY_APPLICATION(application);
+  // Strip out the first argument as it is the binary name.
+  self->dart_entrypoint_arguments = g_strdupv(*arguments + 1);
+
+  g_autoptr(GError) error = nullptr;
+  if (!g_application_register(application, nullptr, &error)) {
+    g_warning("Failed to register: %s", error->message);
+    *exit_status = 1;
+    return TRUE;
+  }
+
+  g_application_activate(application);
+  *exit_status = 0;
+
+  return TRUE;
+}
+
+// Implements GObject::dispose.
+static void my_application_dispose(GObject* object) {
+  MyApplication* self = MY_APPLICATION(object);
+  g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev);
+  G_OBJECT_CLASS(my_application_parent_class)->dispose(object);
+}
+
+static void my_application_class_init(MyApplicationClass* klass) {
+  G_APPLICATION_CLASS(klass)->activate = my_application_activate;
+  G_APPLICATION_CLASS(klass)->local_command_line =
+      my_application_local_command_line;
+  G_OBJECT_CLASS(klass)->dispose = my_application_dispose;
+}
+
+static void my_application_init(MyApplication* self) {}
+
+MyApplication* my_application_new() {
+  return MY_APPLICATION(g_object_new(my_application_get_type(),
+                                     "application-id", APPLICATION_ID, "flags",
+                                     G_APPLICATION_NON_UNIQUE, nullptr));
+}
diff --git a/packages/rfw/example/local/linux/my_application.h b/packages/rfw/example/local/linux/my_application.h
new file mode 100644
index 0000000..6e9f0c3
--- /dev/null
+++ b/packages/rfw/example/local/linux/my_application.h
@@ -0,0 +1,22 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef FLUTTER_MY_APPLICATION_H_
+#define FLUTTER_MY_APPLICATION_H_
+
+#include <gtk/gtk.h>
+
+G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION,
+                     GtkApplication)
+
+/**
+ * my_application_new:
+ *
+ * Creates a new Flutter-based application.
+ *
+ * Returns: a new #MyApplication.
+ */
+MyApplication* my_application_new();
+
+#endif  // FLUTTER_MY_APPLICATION_H_
diff --git a/packages/rfw/example/local/macos/.gitignore b/packages/rfw/example/local/macos/.gitignore
new file mode 100644
index 0000000..d2fd377
--- /dev/null
+++ b/packages/rfw/example/local/macos/.gitignore
@@ -0,0 +1,6 @@
+# Flutter-related
+**/Flutter/ephemeral/
+**/Pods/
+
+# Xcode-related
+**/xcuserdata/
diff --git a/packages/rfw/example/local/macos/Flutter/Flutter-Debug.xcconfig b/packages/rfw/example/local/macos/Flutter/Flutter-Debug.xcconfig
new file mode 100644
index 0000000..c2efd0b
--- /dev/null
+++ b/packages/rfw/example/local/macos/Flutter/Flutter-Debug.xcconfig
@@ -0,0 +1 @@
+#include "ephemeral/Flutter-Generated.xcconfig"
diff --git a/packages/rfw/example/local/macos/Flutter/Flutter-Release.xcconfig b/packages/rfw/example/local/macos/Flutter/Flutter-Release.xcconfig
new file mode 100644
index 0000000..c2efd0b
--- /dev/null
+++ b/packages/rfw/example/local/macos/Flutter/Flutter-Release.xcconfig
@@ -0,0 +1 @@
+#include "ephemeral/Flutter-Generated.xcconfig"
diff --git a/packages/rfw/example/local/macos/Runner.xcodeproj/project.pbxproj b/packages/rfw/example/local/macos/Runner.xcodeproj/project.pbxproj
new file mode 100644
index 0000000..43e5e2e
--- /dev/null
+++ b/packages/rfw/example/local/macos/Runner.xcodeproj/project.pbxproj
@@ -0,0 +1,572 @@
+// !$*UTF8*$!
+{
+	archiveVersion = 1;
+	classes = {
+	};
+	objectVersion = 51;
+	objects = {
+
+/* Begin PBXAggregateTarget section */
+		33CC111A2044C6BA0003C045 /* Flutter Assemble */ = {
+			isa = PBXAggregateTarget;
+			buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */;
+			buildPhases = (
+				33CC111E2044C6BF0003C045 /* ShellScript */,
+			);
+			dependencies = (
+			);
+			name = "Flutter Assemble";
+			productName = FLX;
+		};
+/* End PBXAggregateTarget section */
+
+/* Begin PBXBuildFile section */
+		335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; };
+		33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; };
+		33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; };
+		33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; };
+		33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXContainerItemProxy section */
+		33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = {
+			isa = PBXContainerItemProxy;
+			containerPortal = 33CC10E52044A3C60003C045 /* Project object */;
+			proxyType = 1;
+			remoteGlobalIDString = 33CC111A2044C6BA0003C045;
+			remoteInfo = FLX;
+		};
+/* End PBXContainerItemProxy section */
+
+/* Begin PBXCopyFilesBuildPhase section */
+		33CC110E2044A8840003C045 /* Bundle Framework */ = {
+			isa = PBXCopyFilesBuildPhase;
+			buildActionMask = 2147483647;
+			dstPath = "";
+			dstSubfolderSpec = 10;
+			files = (
+			);
+			name = "Bundle Framework";
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXCopyFilesBuildPhase section */
+
+/* Begin PBXFileReference section */
+		333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = "<group>"; };
+		335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = "<group>"; };
+		33CC10ED2044A3C60003C045 /* local.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "local.app"; sourceTree = BUILT_PRODUCTS_DIR; };
+		33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
+		33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = "<group>"; };
+		33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = "<group>"; };
+		33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = "<group>"; };
+		33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = "<group>"; };
+		33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = "<group>"; };
+		33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = "<group>"; };
+		33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = "<group>"; };
+		33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = "<group>"; };
+		33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = "<group>"; };
+		33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = "<group>"; };
+		7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = "<group>"; };
+		9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = "<group>"; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+		33CC10EA2044A3C60003C045 /* Frameworks */ = {
+			isa = PBXFrameworksBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+		33BA886A226E78AF003329D5 /* Configs */ = {
+			isa = PBXGroup;
+			children = (
+				33E5194F232828860026EE4D /* AppInfo.xcconfig */,
+				9740EEB21CF90195004384FC /* Debug.xcconfig */,
+				7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
+				333000ED22D3DE5D00554162 /* Warnings.xcconfig */,
+			);
+			path = Configs;
+			sourceTree = "<group>";
+		};
+		33CC10E42044A3C60003C045 = {
+			isa = PBXGroup;
+			children = (
+				33FAB671232836740065AC1E /* Runner */,
+				33CEB47122A05771004F2AC0 /* Flutter */,
+				33CC10EE2044A3C60003C045 /* Products */,
+				D73912EC22F37F3D000D13A0 /* Frameworks */,
+			);
+			sourceTree = "<group>";
+		};
+		33CC10EE2044A3C60003C045 /* Products */ = {
+			isa = PBXGroup;
+			children = (
+				33CC10ED2044A3C60003C045 /* local.app */,
+			);
+			name = Products;
+			sourceTree = "<group>";
+		};
+		33CC11242044D66E0003C045 /* Resources */ = {
+			isa = PBXGroup;
+			children = (
+				33CC10F22044A3C60003C045 /* Assets.xcassets */,
+				33CC10F42044A3C60003C045 /* MainMenu.xib */,
+				33CC10F72044A3C60003C045 /* Info.plist */,
+			);
+			name = Resources;
+			path = ..;
+			sourceTree = "<group>";
+		};
+		33CEB47122A05771004F2AC0 /* Flutter */ = {
+			isa = PBXGroup;
+			children = (
+				335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */,
+				33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */,
+				33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */,
+				33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */,
+			);
+			path = Flutter;
+			sourceTree = "<group>";
+		};
+		33FAB671232836740065AC1E /* Runner */ = {
+			isa = PBXGroup;
+			children = (
+				33CC10F02044A3C60003C045 /* AppDelegate.swift */,
+				33CC11122044BFA00003C045 /* MainFlutterWindow.swift */,
+				33E51913231747F40026EE4D /* DebugProfile.entitlements */,
+				33E51914231749380026EE4D /* Release.entitlements */,
+				33CC11242044D66E0003C045 /* Resources */,
+				33BA886A226E78AF003329D5 /* Configs */,
+			);
+			path = Runner;
+			sourceTree = "<group>";
+		};
+		D73912EC22F37F3D000D13A0 /* Frameworks */ = {
+			isa = PBXGroup;
+			children = (
+			);
+			name = Frameworks;
+			sourceTree = "<group>";
+		};
+/* End PBXGroup section */
+
+/* Begin PBXNativeTarget section */
+		33CC10EC2044A3C60003C045 /* Runner */ = {
+			isa = PBXNativeTarget;
+			buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */;
+			buildPhases = (
+				33CC10E92044A3C60003C045 /* Sources */,
+				33CC10EA2044A3C60003C045 /* Frameworks */,
+				33CC10EB2044A3C60003C045 /* Resources */,
+				33CC110E2044A8840003C045 /* Bundle Framework */,
+				3399D490228B24CF009A79C7 /* ShellScript */,
+			);
+			buildRules = (
+			);
+			dependencies = (
+				33CC11202044C79F0003C045 /* PBXTargetDependency */,
+			);
+			name = Runner;
+			productName = Runner;
+			productReference = 33CC10ED2044A3C60003C045 /* local.app */;
+			productType = "com.apple.product-type.application";
+		};
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+		33CC10E52044A3C60003C045 /* Project object */ = {
+			isa = PBXProject;
+			attributes = {
+				LastSwiftUpdateCheck = 0920;
+				LastUpgradeCheck = 1300;
+				ORGANIZATIONNAME = "";
+				TargetAttributes = {
+					33CC10EC2044A3C60003C045 = {
+						CreatedOnToolsVersion = 9.2;
+						LastSwiftMigration = 1100;
+						ProvisioningStyle = Automatic;
+						SystemCapabilities = {
+							com.apple.Sandbox = {
+								enabled = 1;
+							};
+						};
+					};
+					33CC111A2044C6BA0003C045 = {
+						CreatedOnToolsVersion = 9.2;
+						ProvisioningStyle = Manual;
+					};
+				};
+			};
+			buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */;
+			compatibilityVersion = "Xcode 9.3";
+			developmentRegion = en;
+			hasScannedForEncodings = 0;
+			knownRegions = (
+				en,
+				Base,
+			);
+			mainGroup = 33CC10E42044A3C60003C045;
+			productRefGroup = 33CC10EE2044A3C60003C045 /* Products */;
+			projectDirPath = "";
+			projectRoot = "";
+			targets = (
+				33CC10EC2044A3C60003C045 /* Runner */,
+				33CC111A2044C6BA0003C045 /* Flutter Assemble */,
+			);
+		};
+/* End PBXProject section */
+
+/* Begin PBXResourcesBuildPhase section */
+		33CC10EB2044A3C60003C045 /* Resources */ = {
+			isa = PBXResourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */,
+				33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXResourcesBuildPhase section */
+
+/* Begin PBXShellScriptBuildPhase section */
+		3399D490228B24CF009A79C7 /* ShellScript */ = {
+			isa = PBXShellScriptBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			inputFileListPaths = (
+			);
+			inputPaths = (
+			);
+			outputFileListPaths = (
+			);
+			outputPaths = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+			shellPath = /bin/sh;
+			shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n";
+		};
+		33CC111E2044C6BF0003C045 /* ShellScript */ = {
+			isa = PBXShellScriptBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			inputFileListPaths = (
+				Flutter/ephemeral/FlutterInputs.xcfilelist,
+			);
+			inputPaths = (
+				Flutter/ephemeral/tripwire,
+			);
+			outputFileListPaths = (
+				Flutter/ephemeral/FlutterOutputs.xcfilelist,
+			);
+			outputPaths = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+			shellPath = /bin/sh;
+			shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire";
+		};
+/* End PBXShellScriptBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+		33CC10E92044A3C60003C045 /* Sources */ = {
+			isa = PBXSourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */,
+				33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */,
+				335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXSourcesBuildPhase section */
+
+/* Begin PBXTargetDependency section */
+		33CC11202044C79F0003C045 /* PBXTargetDependency */ = {
+			isa = PBXTargetDependency;
+			target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */;
+			targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */;
+		};
+/* End PBXTargetDependency section */
+
+/* Begin PBXVariantGroup section */
+		33CC10F42044A3C60003C045 /* MainMenu.xib */ = {
+			isa = PBXVariantGroup;
+			children = (
+				33CC10F52044A3C60003C045 /* Base */,
+			);
+			name = MainMenu.xib;
+			path = Runner;
+			sourceTree = "<group>";
+		};
+/* End PBXVariantGroup section */
+
+/* Begin XCBuildConfiguration section */
+		338D0CE9231458BD00FA5F75 /* Profile */ = {
+			isa = XCBuildConfiguration;
+			baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
+			buildSettings = {
+				ALWAYS_SEARCH_USER_PATHS = NO;
+				CLANG_ANALYZER_NONNULL = YES;
+				CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+				CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+				CLANG_CXX_LIBRARY = "libc++";
+				CLANG_ENABLE_MODULES = YES;
+				CLANG_ENABLE_OBJC_ARC = YES;
+				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+				CLANG_WARN_BOOL_CONVERSION = YES;
+				CLANG_WARN_CONSTANT_CONVERSION = YES;
+				CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+				CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+				CLANG_WARN_EMPTY_BODY = YES;
+				CLANG_WARN_ENUM_CONVERSION = YES;
+				CLANG_WARN_INFINITE_RECURSION = YES;
+				CLANG_WARN_INT_CONVERSION = YES;
+				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+				CLANG_WARN_SUSPICIOUS_MOVE = YES;
+				CODE_SIGN_IDENTITY = "-";
+				COPY_PHASE_STRIP = NO;
+				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+				ENABLE_NS_ASSERTIONS = NO;
+				ENABLE_STRICT_OBJC_MSGSEND = YES;
+				GCC_C_LANGUAGE_STANDARD = gnu11;
+				GCC_NO_COMMON_BLOCKS = YES;
+				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+				GCC_WARN_UNUSED_FUNCTION = YES;
+				GCC_WARN_UNUSED_VARIABLE = YES;
+				MACOSX_DEPLOYMENT_TARGET = 10.11;
+				MTL_ENABLE_DEBUG_INFO = NO;
+				SDKROOT = macosx;
+				SWIFT_COMPILATION_MODE = wholemodule;
+				SWIFT_OPTIMIZATION_LEVEL = "-O";
+			};
+			name = Profile;
+		};
+		338D0CEA231458BD00FA5F75 /* Profile */ = {
+			isa = XCBuildConfiguration;
+			baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */;
+			buildSettings = {
+				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+				CLANG_ENABLE_MODULES = YES;
+				CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements;
+				CODE_SIGN_STYLE = Automatic;
+				COMBINE_HIDPI_IMAGES = YES;
+				INFOPLIST_FILE = Runner/Info.plist;
+				LD_RUNPATH_SEARCH_PATHS = (
+					"$(inherited)",
+					"@executable_path/../Frameworks",
+				);
+				PROVISIONING_PROFILE_SPECIFIER = "";
+				SWIFT_VERSION = 5.0;
+			};
+			name = Profile;
+		};
+		338D0CEB231458BD00FA5F75 /* Profile */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				CODE_SIGN_STYLE = Manual;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+			};
+			name = Profile;
+		};
+		33CC10F92044A3C60003C045 /* Debug */ = {
+			isa = XCBuildConfiguration;
+			baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
+			buildSettings = {
+				ALWAYS_SEARCH_USER_PATHS = NO;
+				CLANG_ANALYZER_NONNULL = YES;
+				CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+				CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+				CLANG_CXX_LIBRARY = "libc++";
+				CLANG_ENABLE_MODULES = YES;
+				CLANG_ENABLE_OBJC_ARC = YES;
+				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+				CLANG_WARN_BOOL_CONVERSION = YES;
+				CLANG_WARN_CONSTANT_CONVERSION = YES;
+				CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+				CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+				CLANG_WARN_EMPTY_BODY = YES;
+				CLANG_WARN_ENUM_CONVERSION = YES;
+				CLANG_WARN_INFINITE_RECURSION = YES;
+				CLANG_WARN_INT_CONVERSION = YES;
+				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+				CLANG_WARN_SUSPICIOUS_MOVE = YES;
+				CODE_SIGN_IDENTITY = "-";
+				COPY_PHASE_STRIP = NO;
+				DEBUG_INFORMATION_FORMAT = dwarf;
+				ENABLE_STRICT_OBJC_MSGSEND = YES;
+				ENABLE_TESTABILITY = YES;
+				GCC_C_LANGUAGE_STANDARD = gnu11;
+				GCC_DYNAMIC_NO_PIC = NO;
+				GCC_NO_COMMON_BLOCKS = YES;
+				GCC_OPTIMIZATION_LEVEL = 0;
+				GCC_PREPROCESSOR_DEFINITIONS = (
+					"DEBUG=1",
+					"$(inherited)",
+				);
+				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+				GCC_WARN_UNUSED_FUNCTION = YES;
+				GCC_WARN_UNUSED_VARIABLE = YES;
+				MACOSX_DEPLOYMENT_TARGET = 10.11;
+				MTL_ENABLE_DEBUG_INFO = YES;
+				ONLY_ACTIVE_ARCH = YES;
+				SDKROOT = macosx;
+				SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
+				SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+			};
+			name = Debug;
+		};
+		33CC10FA2044A3C60003C045 /* Release */ = {
+			isa = XCBuildConfiguration;
+			baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
+			buildSettings = {
+				ALWAYS_SEARCH_USER_PATHS = NO;
+				CLANG_ANALYZER_NONNULL = YES;
+				CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+				CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+				CLANG_CXX_LIBRARY = "libc++";
+				CLANG_ENABLE_MODULES = YES;
+				CLANG_ENABLE_OBJC_ARC = YES;
+				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+				CLANG_WARN_BOOL_CONVERSION = YES;
+				CLANG_WARN_CONSTANT_CONVERSION = YES;
+				CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+				CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+				CLANG_WARN_EMPTY_BODY = YES;
+				CLANG_WARN_ENUM_CONVERSION = YES;
+				CLANG_WARN_INFINITE_RECURSION = YES;
+				CLANG_WARN_INT_CONVERSION = YES;
+				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+				CLANG_WARN_SUSPICIOUS_MOVE = YES;
+				CODE_SIGN_IDENTITY = "-";
+				COPY_PHASE_STRIP = NO;
+				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+				ENABLE_NS_ASSERTIONS = NO;
+				ENABLE_STRICT_OBJC_MSGSEND = YES;
+				GCC_C_LANGUAGE_STANDARD = gnu11;
+				GCC_NO_COMMON_BLOCKS = YES;
+				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+				GCC_WARN_UNUSED_FUNCTION = YES;
+				GCC_WARN_UNUSED_VARIABLE = YES;
+				MACOSX_DEPLOYMENT_TARGET = 10.11;
+				MTL_ENABLE_DEBUG_INFO = NO;
+				SDKROOT = macosx;
+				SWIFT_COMPILATION_MODE = wholemodule;
+				SWIFT_OPTIMIZATION_LEVEL = "-O";
+			};
+			name = Release;
+		};
+		33CC10FC2044A3C60003C045 /* Debug */ = {
+			isa = XCBuildConfiguration;
+			baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */;
+			buildSettings = {
+				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+				CLANG_ENABLE_MODULES = YES;
+				CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements;
+				CODE_SIGN_STYLE = Automatic;
+				COMBINE_HIDPI_IMAGES = YES;
+				INFOPLIST_FILE = Runner/Info.plist;
+				LD_RUNPATH_SEARCH_PATHS = (
+					"$(inherited)",
+					"@executable_path/../Frameworks",
+				);
+				PROVISIONING_PROFILE_SPECIFIER = "";
+				SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+				SWIFT_VERSION = 5.0;
+			};
+			name = Debug;
+		};
+		33CC10FD2044A3C60003C045 /* Release */ = {
+			isa = XCBuildConfiguration;
+			baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */;
+			buildSettings = {
+				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+				CLANG_ENABLE_MODULES = YES;
+				CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements;
+				CODE_SIGN_STYLE = Automatic;
+				COMBINE_HIDPI_IMAGES = YES;
+				INFOPLIST_FILE = Runner/Info.plist;
+				LD_RUNPATH_SEARCH_PATHS = (
+					"$(inherited)",
+					"@executable_path/../Frameworks",
+				);
+				PROVISIONING_PROFILE_SPECIFIER = "";
+				SWIFT_VERSION = 5.0;
+			};
+			name = Release;
+		};
+		33CC111C2044C6BA0003C045 /* Debug */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				CODE_SIGN_STYLE = Manual;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+			};
+			name = Debug;
+		};
+		33CC111D2044C6BA0003C045 /* Release */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				CODE_SIGN_STYLE = Automatic;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+			};
+			name = Release;
+		};
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+		33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				33CC10F92044A3C60003C045 /* Debug */,
+				33CC10FA2044A3C60003C045 /* Release */,
+				338D0CE9231458BD00FA5F75 /* Profile */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
+		};
+		33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				33CC10FC2044A3C60003C045 /* Debug */,
+				33CC10FD2044A3C60003C045 /* Release */,
+				338D0CEA231458BD00FA5F75 /* Profile */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
+		};
+		33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				33CC111C2044C6BA0003C045 /* Debug */,
+				33CC111D2044C6BA0003C045 /* Release */,
+				338D0CEB231458BD00FA5F75 /* Profile */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
+		};
+/* End XCConfigurationList section */
+	};
+	rootObject = 33CC10E52044A3C60003C045 /* Project object */;
+}
diff --git a/packages/rfw/example/local/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/rfw/example/local/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
new file mode 100644
index 0000000..18d9810
--- /dev/null
+++ b/packages/rfw/example/local/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>IDEDidComputeMac32BitWarning</key>
+	<true/>
+</dict>
+</plist>
diff --git a/packages/rfw/example/local/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/rfw/example/local/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
new file mode 100644
index 0000000..2aaf7c4
--- /dev/null
+++ b/packages/rfw/example/local/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
@@ -0,0 +1,87 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Scheme
+   LastUpgradeVersion = "1300"
+   version = "1.3">
+   <BuildAction
+      parallelizeBuildables = "YES"
+      buildImplicitDependencies = "YES">
+      <BuildActionEntries>
+         <BuildActionEntry
+            buildForTesting = "YES"
+            buildForRunning = "YES"
+            buildForProfiling = "YES"
+            buildForArchiving = "YES"
+            buildForAnalyzing = "YES">
+            <BuildableReference
+               BuildableIdentifier = "primary"
+               BlueprintIdentifier = "33CC10EC2044A3C60003C045"
+               BuildableName = "local.app"
+               BlueprintName = "Runner"
+               ReferencedContainer = "container:Runner.xcodeproj">
+            </BuildableReference>
+         </BuildActionEntry>
+      </BuildActionEntries>
+   </BuildAction>
+   <TestAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      shouldUseLaunchSchemeArgsEnv = "YES">
+      <MacroExpansion>
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "33CC10EC2044A3C60003C045"
+            BuildableName = "local.app"
+            BlueprintName = "Runner"
+            ReferencedContainer = "container:Runner.xcodeproj">
+         </BuildableReference>
+      </MacroExpansion>
+      <Testables>
+      </Testables>
+   </TestAction>
+   <LaunchAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      launchStyle = "0"
+      useCustomWorkingDirectory = "NO"
+      ignoresPersistentStateOnLaunch = "NO"
+      debugDocumentVersioning = "YES"
+      debugServiceExtension = "internal"
+      allowLocationSimulation = "YES">
+      <BuildableProductRunnable
+         runnableDebuggingMode = "0">
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "33CC10EC2044A3C60003C045"
+            BuildableName = "local.app"
+            BlueprintName = "Runner"
+            ReferencedContainer = "container:Runner.xcodeproj">
+         </BuildableReference>
+      </BuildableProductRunnable>
+   </LaunchAction>
+   <ProfileAction
+      buildConfiguration = "Profile"
+      shouldUseLaunchSchemeArgsEnv = "YES"
+      savedToolIdentifier = ""
+      useCustomWorkingDirectory = "NO"
+      debugDocumentVersioning = "YES">
+      <BuildableProductRunnable
+         runnableDebuggingMode = "0">
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "33CC10EC2044A3C60003C045"
+            BuildableName = "local.app"
+            BlueprintName = "Runner"
+            ReferencedContainer = "container:Runner.xcodeproj">
+         </BuildableReference>
+      </BuildableProductRunnable>
+   </ProfileAction>
+   <AnalyzeAction
+      buildConfiguration = "Debug">
+   </AnalyzeAction>
+   <ArchiveAction
+      buildConfiguration = "Release"
+      revealArchiveInOrganizer = "YES">
+   </ArchiveAction>
+</Scheme>
diff --git a/packages/rfw/example/local/macos/Runner.xcworkspace/contents.xcworkspacedata b/packages/rfw/example/local/macos/Runner.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 0000000..1d526a1
--- /dev/null
+++ b/packages/rfw/example/local/macos/Runner.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Workspace
+   version = "1.0">
+   <FileRef
+      location = "group:Runner.xcodeproj">
+   </FileRef>
+</Workspace>
diff --git a/packages/rfw/example/local/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/rfw/example/local/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
new file mode 100644
index 0000000..18d9810
--- /dev/null
+++ b/packages/rfw/example/local/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>IDEDidComputeMac32BitWarning</key>
+	<true/>
+</dict>
+</plist>
diff --git a/packages/rfw/example/local/macos/Runner/AppDelegate.swift b/packages/rfw/example/local/macos/Runner/AppDelegate.swift
new file mode 100644
index 0000000..5cec4c4
--- /dev/null
+++ b/packages/rfw/example/local/macos/Runner/AppDelegate.swift
@@ -0,0 +1,13 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import Cocoa
+import FlutterMacOS
+
+@NSApplicationMain
+class AppDelegate: FlutterAppDelegate {
+  override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
+    return true
+  }
+}
diff --git a/packages/rfw/example/local/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/packages/rfw/example/local/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
new file mode 100644
index 0000000..a2ec33f
--- /dev/null
+++ b/packages/rfw/example/local/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -0,0 +1,68 @@
+{
+  "images" : [
+    {
+      "size" : "16x16",
+      "idiom" : "mac",
+      "filename" : "app_icon_16.png",
+      "scale" : "1x"
+    },
+    {
+      "size" : "16x16",
+      "idiom" : "mac",
+      "filename" : "app_icon_32.png",
+      "scale" : "2x"
+    },
+    {
+      "size" : "32x32",
+      "idiom" : "mac",
+      "filename" : "app_icon_32.png",
+      "scale" : "1x"
+    },
+    {
+      "size" : "32x32",
+      "idiom" : "mac",
+      "filename" : "app_icon_64.png",
+      "scale" : "2x"
+    },
+    {
+      "size" : "128x128",
+      "idiom" : "mac",
+      "filename" : "app_icon_128.png",
+      "scale" : "1x"
+    },
+    {
+      "size" : "128x128",
+      "idiom" : "mac",
+      "filename" : "app_icon_256.png",
+      "scale" : "2x"
+    },
+    {
+      "size" : "256x256",
+      "idiom" : "mac",
+      "filename" : "app_icon_256.png",
+      "scale" : "1x"
+    },
+    {
+      "size" : "256x256",
+      "idiom" : "mac",
+      "filename" : "app_icon_512.png",
+      "scale" : "2x"
+    },
+    {
+      "size" : "512x512",
+      "idiom" : "mac",
+      "filename" : "app_icon_512.png",
+      "scale" : "1x"
+    },
+    {
+      "size" : "512x512",
+      "idiom" : "mac",
+      "filename" : "app_icon_1024.png",
+      "scale" : "2x"
+    }
+  ],
+  "info" : {
+    "version" : 1,
+    "author" : "xcode"
+  }
+}
diff --git a/packages/rfw/example/local/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/packages/rfw/example/local/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png
new file mode 100644
index 0000000..3c4935a
--- /dev/null
+++ b/packages/rfw/example/local/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png
Binary files differ
diff --git a/packages/rfw/example/local/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/packages/rfw/example/local/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png
new file mode 100644
index 0000000..ed4cc16
--- /dev/null
+++ b/packages/rfw/example/local/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png
Binary files differ
diff --git a/packages/rfw/example/local/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/packages/rfw/example/local/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png
new file mode 100644
index 0000000..483be61
--- /dev/null
+++ b/packages/rfw/example/local/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png
Binary files differ
diff --git a/packages/rfw/example/local/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/packages/rfw/example/local/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png
new file mode 100644
index 0000000..bcbf36d
--- /dev/null
+++ b/packages/rfw/example/local/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png
Binary files differ
diff --git a/packages/rfw/example/local/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/packages/rfw/example/local/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png
new file mode 100644
index 0000000..9c0a652
--- /dev/null
+++ b/packages/rfw/example/local/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png
Binary files differ
diff --git a/packages/rfw/example/local/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/packages/rfw/example/local/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png
new file mode 100644
index 0000000..e71a726
--- /dev/null
+++ b/packages/rfw/example/local/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png
Binary files differ
diff --git a/packages/rfw/example/local/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/packages/rfw/example/local/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png
new file mode 100644
index 0000000..8a31fe2
--- /dev/null
+++ b/packages/rfw/example/local/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png
Binary files differ
diff --git a/packages/rfw/example/local/macos/Runner/Base.lproj/MainMenu.xib b/packages/rfw/example/local/macos/Runner/Base.lproj/MainMenu.xib
new file mode 100644
index 0000000..537341a
--- /dev/null
+++ b/packages/rfw/example/local/macos/Runner/Base.lproj/MainMenu.xib
@@ -0,0 +1,339 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
+    <dependencies>
+        <deployment identifier="macosx"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14490.70"/>
+        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
+    </dependencies>
+    <objects>
+        <customObject id="-2" userLabel="File's Owner" customClass="NSApplication">
+            <connections>
+                <outlet property="delegate" destination="Voe-Tx-rLC" id="GzC-gU-4Uq"/>
+            </connections>
+        </customObject>
+        <customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
+        <customObject id="-3" userLabel="Application" customClass="NSObject"/>
+        <customObject id="Voe-Tx-rLC" customClass="AppDelegate" customModule="Runner" customModuleProvider="target">
+            <connections>
+                <outlet property="applicationMenu" destination="uQy-DD-JDr" id="XBo-yE-nKs"/>
+                <outlet property="mainFlutterWindow" destination="QvC-M9-y7g" id="gIp-Ho-8D9"/>
+            </connections>
+        </customObject>
+        <customObject id="YLy-65-1bz" customClass="NSFontManager"/>
+        <menu title="Main Menu" systemMenu="main" id="AYu-sK-qS6">
+            <items>
+                <menuItem title="APP_NAME" id="1Xt-HY-uBw">
+                    <modifierMask key="keyEquivalentModifierMask"/>
+                    <menu key="submenu" title="APP_NAME" systemMenu="apple" id="uQy-DD-JDr">
+                        <items>
+                            <menuItem title="About APP_NAME" id="5kV-Vb-QxS">
+                                <modifierMask key="keyEquivalentModifierMask"/>
+                                <connections>
+                                    <action selector="orderFrontStandardAboutPanel:" target="-1" id="Exp-CZ-Vem"/>
+                                </connections>
+                            </menuItem>
+                            <menuItem isSeparatorItem="YES" id="VOq-y0-SEH"/>
+                            <menuItem title="Preferences…" keyEquivalent="," id="BOF-NM-1cW"/>
+                            <menuItem isSeparatorItem="YES" id="wFC-TO-SCJ"/>
+                            <menuItem title="Services" id="NMo-om-nkz">
+                                <modifierMask key="keyEquivalentModifierMask"/>
+                                <menu key="submenu" title="Services" systemMenu="services" id="hz9-B4-Xy5"/>
+                            </menuItem>
+                            <menuItem isSeparatorItem="YES" id="4je-JR-u6R"/>
+                            <menuItem title="Hide APP_NAME" keyEquivalent="h" id="Olw-nP-bQN">
+                                <connections>
+                                    <action selector="hide:" target="-1" id="PnN-Uc-m68"/>
+                                </connections>
+                            </menuItem>
+                            <menuItem title="Hide Others" keyEquivalent="h" id="Vdr-fp-XzO">
+                                <modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
+                                <connections>
+                                    <action selector="hideOtherApplications:" target="-1" id="VT4-aY-XCT"/>
+                                </connections>
+                            </menuItem>
+                            <menuItem title="Show All" id="Kd2-mp-pUS">
+                                <modifierMask key="keyEquivalentModifierMask"/>
+                                <connections>
+                                    <action selector="unhideAllApplications:" target="-1" id="Dhg-Le-xox"/>
+                                </connections>
+                            </menuItem>
+                            <menuItem isSeparatorItem="YES" id="kCx-OE-vgT"/>
+                            <menuItem title="Quit APP_NAME" keyEquivalent="q" id="4sb-4s-VLi">
+                                <connections>
+                                    <action selector="terminate:" target="-1" id="Te7-pn-YzF"/>
+                                </connections>
+                            </menuItem>
+                        </items>
+                    </menu>
+                </menuItem>
+                <menuItem title="Edit" id="5QF-Oa-p0T">
+                    <modifierMask key="keyEquivalentModifierMask"/>
+                    <menu key="submenu" title="Edit" id="W48-6f-4Dl">
+                        <items>
+                            <menuItem title="Undo" keyEquivalent="z" id="dRJ-4n-Yzg">
+                                <connections>
+                                    <action selector="undo:" target="-1" id="M6e-cu-g7V"/>
+                                </connections>
+                            </menuItem>
+                            <menuItem title="Redo" keyEquivalent="Z" id="6dh-zS-Vam">
+                                <connections>
+                                    <action selector="redo:" target="-1" id="oIA-Rs-6OD"/>
+                                </connections>
+                            </menuItem>
+                            <menuItem isSeparatorItem="YES" id="WRV-NI-Exz"/>
+                            <menuItem title="Cut" keyEquivalent="x" id="uRl-iY-unG">
+                                <connections>
+                                    <action selector="cut:" target="-1" id="YJe-68-I9s"/>
+                                </connections>
+                            </menuItem>
+                            <menuItem title="Copy" keyEquivalent="c" id="x3v-GG-iWU">
+                                <connections>
+                                    <action selector="copy:" target="-1" id="G1f-GL-Joy"/>
+                                </connections>
+                            </menuItem>
+                            <menuItem title="Paste" keyEquivalent="v" id="gVA-U4-sdL">
+                                <connections>
+                                    <action selector="paste:" target="-1" id="UvS-8e-Qdg"/>
+                                </connections>
+                            </menuItem>
+                            <menuItem title="Paste and Match Style" keyEquivalent="V" id="WeT-3V-zwk">
+                                <modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
+                                <connections>
+                                    <action selector="pasteAsPlainText:" target="-1" id="cEh-KX-wJQ"/>
+                                </connections>
+                            </menuItem>
+                            <menuItem title="Delete" id="pa3-QI-u2k">
+                                <modifierMask key="keyEquivalentModifierMask"/>
+                                <connections>
+                                    <action selector="delete:" target="-1" id="0Mk-Ml-PaM"/>
+                                </connections>
+                            </menuItem>
+                            <menuItem title="Select All" keyEquivalent="a" id="Ruw-6m-B2m">
+                                <connections>
+                                    <action selector="selectAll:" target="-1" id="VNm-Mi-diN"/>
+                                </connections>
+                            </menuItem>
+                            <menuItem isSeparatorItem="YES" id="uyl-h8-XO2"/>
+                            <menuItem title="Find" id="4EN-yA-p0u">
+                                <modifierMask key="keyEquivalentModifierMask"/>
+                                <menu key="submenu" title="Find" id="1b7-l0-nxx">
+                                    <items>
+                                        <menuItem title="Find…" tag="1" keyEquivalent="f" id="Xz5-n4-O0W">
+                                            <connections>
+                                                <action selector="performFindPanelAction:" target="-1" id="cD7-Qs-BN4"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem title="Find and Replace…" tag="12" keyEquivalent="f" id="YEy-JH-Tfz">
+                                            <modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
+                                            <connections>
+                                                <action selector="performFindPanelAction:" target="-1" id="WD3-Gg-5AJ"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem title="Find Next" tag="2" keyEquivalent="g" id="q09-fT-Sye">
+                                            <connections>
+                                                <action selector="performFindPanelAction:" target="-1" id="NDo-RZ-v9R"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem title="Find Previous" tag="3" keyEquivalent="G" id="OwM-mh-QMV">
+                                            <connections>
+                                                <action selector="performFindPanelAction:" target="-1" id="HOh-sY-3ay"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem title="Use Selection for Find" tag="7" keyEquivalent="e" id="buJ-ug-pKt">
+                                            <connections>
+                                                <action selector="performFindPanelAction:" target="-1" id="U76-nv-p5D"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem title="Jump to Selection" keyEquivalent="j" id="S0p-oC-mLd">
+                                            <connections>
+                                                <action selector="centerSelectionInVisibleArea:" target="-1" id="IOG-6D-g5B"/>
+                                            </connections>
+                                        </menuItem>
+                                    </items>
+                                </menu>
+                            </menuItem>
+                            <menuItem title="Spelling and Grammar" id="Dv1-io-Yv7">
+                                <modifierMask key="keyEquivalentModifierMask"/>
+                                <menu key="submenu" title="Spelling" id="3IN-sU-3Bg">
+                                    <items>
+                                        <menuItem title="Show Spelling and Grammar" keyEquivalent=":" id="HFo-cy-zxI">
+                                            <connections>
+                                                <action selector="showGuessPanel:" target="-1" id="vFj-Ks-hy3"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem title="Check Document Now" keyEquivalent=";" id="hz2-CU-CR7">
+                                            <connections>
+                                                <action selector="checkSpelling:" target="-1" id="fz7-VC-reM"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem isSeparatorItem="YES" id="bNw-od-mp5"/>
+                                        <menuItem title="Check Spelling While Typing" id="rbD-Rh-wIN">
+                                            <modifierMask key="keyEquivalentModifierMask"/>
+                                            <connections>
+                                                <action selector="toggleContinuousSpellChecking:" target="-1" id="7w6-Qz-0kB"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem title="Check Grammar With Spelling" id="mK6-2p-4JG">
+                                            <modifierMask key="keyEquivalentModifierMask"/>
+                                            <connections>
+                                                <action selector="toggleGrammarChecking:" target="-1" id="muD-Qn-j4w"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem title="Correct Spelling Automatically" id="78Y-hA-62v">
+                                            <modifierMask key="keyEquivalentModifierMask"/>
+                                            <connections>
+                                                <action selector="toggleAutomaticSpellingCorrection:" target="-1" id="2lM-Qi-WAP"/>
+                                            </connections>
+                                        </menuItem>
+                                    </items>
+                                </menu>
+                            </menuItem>
+                            <menuItem title="Substitutions" id="9ic-FL-obx">
+                                <modifierMask key="keyEquivalentModifierMask"/>
+                                <menu key="submenu" title="Substitutions" id="FeM-D8-WVr">
+                                    <items>
+                                        <menuItem title="Show Substitutions" id="z6F-FW-3nz">
+                                            <modifierMask key="keyEquivalentModifierMask"/>
+                                            <connections>
+                                                <action selector="orderFrontSubstitutionsPanel:" target="-1" id="oku-mr-iSq"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem isSeparatorItem="YES" id="gPx-C9-uUO"/>
+                                        <menuItem title="Smart Copy/Paste" id="9yt-4B-nSM">
+                                            <modifierMask key="keyEquivalentModifierMask"/>
+                                            <connections>
+                                                <action selector="toggleSmartInsertDelete:" target="-1" id="3IJ-Se-DZD"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem title="Smart Quotes" id="hQb-2v-fYv">
+                                            <modifierMask key="keyEquivalentModifierMask"/>
+                                            <connections>
+                                                <action selector="toggleAutomaticQuoteSubstitution:" target="-1" id="ptq-xd-QOA"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem title="Smart Dashes" id="rgM-f4-ycn">
+                                            <modifierMask key="keyEquivalentModifierMask"/>
+                                            <connections>
+                                                <action selector="toggleAutomaticDashSubstitution:" target="-1" id="oCt-pO-9gS"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem title="Smart Links" id="cwL-P1-jid">
+                                            <modifierMask key="keyEquivalentModifierMask"/>
+                                            <connections>
+                                                <action selector="toggleAutomaticLinkDetection:" target="-1" id="Gip-E3-Fov"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem title="Data Detectors" id="tRr-pd-1PS">
+                                            <modifierMask key="keyEquivalentModifierMask"/>
+                                            <connections>
+                                                <action selector="toggleAutomaticDataDetection:" target="-1" id="R1I-Nq-Kbl"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem title="Text Replacement" id="HFQ-gK-NFA">
+                                            <modifierMask key="keyEquivalentModifierMask"/>
+                                            <connections>
+                                                <action selector="toggleAutomaticTextReplacement:" target="-1" id="DvP-Fe-Py6"/>
+                                            </connections>
+                                        </menuItem>
+                                    </items>
+                                </menu>
+                            </menuItem>
+                            <menuItem title="Transformations" id="2oI-Rn-ZJC">
+                                <modifierMask key="keyEquivalentModifierMask"/>
+                                <menu key="submenu" title="Transformations" id="c8a-y6-VQd">
+                                    <items>
+                                        <menuItem title="Make Upper Case" id="vmV-6d-7jI">
+                                            <modifierMask key="keyEquivalentModifierMask"/>
+                                            <connections>
+                                                <action selector="uppercaseWord:" target="-1" id="sPh-Tk-edu"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem title="Make Lower Case" id="d9M-CD-aMd">
+                                            <modifierMask key="keyEquivalentModifierMask"/>
+                                            <connections>
+                                                <action selector="lowercaseWord:" target="-1" id="iUZ-b5-hil"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem title="Capitalize" id="UEZ-Bs-lqG">
+                                            <modifierMask key="keyEquivalentModifierMask"/>
+                                            <connections>
+                                                <action selector="capitalizeWord:" target="-1" id="26H-TL-nsh"/>
+                                            </connections>
+                                        </menuItem>
+                                    </items>
+                                </menu>
+                            </menuItem>
+                            <menuItem title="Speech" id="xrE-MZ-jX0">
+                                <modifierMask key="keyEquivalentModifierMask"/>
+                                <menu key="submenu" title="Speech" id="3rS-ZA-NoH">
+                                    <items>
+                                        <menuItem title="Start Speaking" id="Ynk-f8-cLZ">
+                                            <modifierMask key="keyEquivalentModifierMask"/>
+                                            <connections>
+                                                <action selector="startSpeaking:" target="-1" id="654-Ng-kyl"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem title="Stop Speaking" id="Oyz-dy-DGm">
+                                            <modifierMask key="keyEquivalentModifierMask"/>
+                                            <connections>
+                                                <action selector="stopSpeaking:" target="-1" id="dX8-6p-jy9"/>
+                                            </connections>
+                                        </menuItem>
+                                    </items>
+                                </menu>
+                            </menuItem>
+                        </items>
+                    </menu>
+                </menuItem>
+                <menuItem title="View" id="H8h-7b-M4v">
+                    <modifierMask key="keyEquivalentModifierMask"/>
+                    <menu key="submenu" title="View" id="HyV-fh-RgO">
+                        <items>
+                            <menuItem title="Enter Full Screen" keyEquivalent="f" id="4J7-dP-txa">
+                                <modifierMask key="keyEquivalentModifierMask" control="YES" command="YES"/>
+                                <connections>
+                                    <action selector="toggleFullScreen:" target="-1" id="dU3-MA-1Rq"/>
+                                </connections>
+                            </menuItem>
+                        </items>
+                    </menu>
+                </menuItem>
+                <menuItem title="Window" id="aUF-d1-5bR">
+                    <modifierMask key="keyEquivalentModifierMask"/>
+                    <menu key="submenu" title="Window" systemMenu="window" id="Td7-aD-5lo">
+                        <items>
+                            <menuItem title="Minimize" keyEquivalent="m" id="OY7-WF-poV">
+                                <connections>
+                                    <action selector="performMiniaturize:" target="-1" id="VwT-WD-YPe"/>
+                                </connections>
+                            </menuItem>
+                            <menuItem title="Zoom" id="R4o-n2-Eq4">
+                                <modifierMask key="keyEquivalentModifierMask"/>
+                                <connections>
+                                    <action selector="performZoom:" target="-1" id="DIl-cC-cCs"/>
+                                </connections>
+                            </menuItem>
+                            <menuItem isSeparatorItem="YES" id="eu3-7i-yIM"/>
+                            <menuItem title="Bring All to Front" id="LE2-aR-0XJ">
+                                <modifierMask key="keyEquivalentModifierMask"/>
+                                <connections>
+                                    <action selector="arrangeInFront:" target="-1" id="DRN-fu-gQh"/>
+                                </connections>
+                            </menuItem>
+                        </items>
+                    </menu>
+                </menuItem>
+            </items>
+            <point key="canvasLocation" x="142" y="-258"/>
+        </menu>
+        <window title="APP_NAME" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" animationBehavior="default" id="QvC-M9-y7g" customClass="MainFlutterWindow" customModule="Runner" customModuleProvider="target">
+            <windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
+            <rect key="contentRect" x="335" y="390" width="800" height="600"/>
+            <rect key="screenRect" x="0.0" y="0.0" width="2560" height="1577"/>
+            <view key="contentView" wantsLayer="YES" id="EiT-Mj-1SZ">
+                <rect key="frame" x="0.0" y="0.0" width="800" height="600"/>
+                <autoresizingMask key="autoresizingMask"/>
+            </view>
+        </window>
+    </objects>
+</document>
diff --git a/packages/rfw/example/local/macos/Runner/Configs/AppInfo.xcconfig b/packages/rfw/example/local/macos/Runner/Configs/AppInfo.xcconfig
new file mode 100644
index 0000000..481593c
--- /dev/null
+++ b/packages/rfw/example/local/macos/Runner/Configs/AppInfo.xcconfig
@@ -0,0 +1,14 @@
+// Application-level settings for the Runner target.
+//
+// This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the
+// future. If not, the values below would default to using the project name when this becomes a
+// 'flutter create' template.
+
+// The application's name. By default this is also the title of the Flutter window.
+PRODUCT_NAME = local
+
+// The application's bundle identifier
+PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.rfw.examples.local
+
+// The copyright displayed in application information
+PRODUCT_COPYRIGHT = Copyright © 2021 dev.flutter.rfw.examples. All rights reserved.
diff --git a/packages/rfw/example/local/macos/Runner/Configs/Debug.xcconfig b/packages/rfw/example/local/macos/Runner/Configs/Debug.xcconfig
new file mode 100644
index 0000000..36b0fd9
--- /dev/null
+++ b/packages/rfw/example/local/macos/Runner/Configs/Debug.xcconfig
@@ -0,0 +1,2 @@
+#include "../../Flutter/Flutter-Debug.xcconfig"
+#include "Warnings.xcconfig"
diff --git a/packages/rfw/example/local/macos/Runner/Configs/Release.xcconfig b/packages/rfw/example/local/macos/Runner/Configs/Release.xcconfig
new file mode 100644
index 0000000..dff4f49
--- /dev/null
+++ b/packages/rfw/example/local/macos/Runner/Configs/Release.xcconfig
@@ -0,0 +1,2 @@
+#include "../../Flutter/Flutter-Release.xcconfig"
+#include "Warnings.xcconfig"
diff --git a/packages/rfw/example/local/macos/Runner/Configs/Warnings.xcconfig b/packages/rfw/example/local/macos/Runner/Configs/Warnings.xcconfig
new file mode 100644
index 0000000..42bcbf4
--- /dev/null
+++ b/packages/rfw/example/local/macos/Runner/Configs/Warnings.xcconfig
@@ -0,0 +1,13 @@
+WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings
+GCC_WARN_UNDECLARED_SELECTOR = YES
+CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES
+CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE
+CLANG_WARN__DUPLICATE_METHOD_MATCH = YES
+CLANG_WARN_PRAGMA_PACK = YES
+CLANG_WARN_STRICT_PROTOTYPES = YES
+CLANG_WARN_COMMA = YES
+GCC_WARN_STRICT_SELECTOR_MATCH = YES
+CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES
+CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES
+GCC_WARN_SHADOW = YES
+CLANG_WARN_UNREACHABLE_CODE = YES
diff --git a/packages/rfw/example/local/macos/Runner/DebugProfile.entitlements b/packages/rfw/example/local/macos/Runner/DebugProfile.entitlements
new file mode 100644
index 0000000..dddb8a3
--- /dev/null
+++ b/packages/rfw/example/local/macos/Runner/DebugProfile.entitlements
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>com.apple.security.app-sandbox</key>
+	<true/>
+	<key>com.apple.security.cs.allow-jit</key>
+	<true/>
+	<key>com.apple.security.network.server</key>
+	<true/>
+</dict>
+</plist>
diff --git a/packages/rfw/example/local/macos/Runner/Info.plist b/packages/rfw/example/local/macos/Runner/Info.plist
new file mode 100644
index 0000000..4789daa
--- /dev/null
+++ b/packages/rfw/example/local/macos/Runner/Info.plist
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>CFBundleDevelopmentRegion</key>
+	<string>$(DEVELOPMENT_LANGUAGE)</string>
+	<key>CFBundleExecutable</key>
+	<string>$(EXECUTABLE_NAME)</string>
+	<key>CFBundleIconFile</key>
+	<string></string>
+	<key>CFBundleIdentifier</key>
+	<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
+	<key>CFBundleInfoDictionaryVersion</key>
+	<string>6.0</string>
+	<key>CFBundleName</key>
+	<string>$(PRODUCT_NAME)</string>
+	<key>CFBundlePackageType</key>
+	<string>APPL</string>
+	<key>CFBundleShortVersionString</key>
+	<string>$(FLUTTER_BUILD_NAME)</string>
+	<key>CFBundleVersion</key>
+	<string>$(FLUTTER_BUILD_NUMBER)</string>
+	<key>LSMinimumSystemVersion</key>
+	<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
+	<key>NSHumanReadableCopyright</key>
+	<string>$(PRODUCT_COPYRIGHT)</string>
+	<key>NSMainNibFile</key>
+	<string>MainMenu</string>
+	<key>NSPrincipalClass</key>
+	<string>NSApplication</string>
+</dict>
+</plist>
diff --git a/packages/rfw/example/local/macos/Runner/MainFlutterWindow.swift b/packages/rfw/example/local/macos/Runner/MainFlutterWindow.swift
new file mode 100644
index 0000000..32aaeed
--- /dev/null
+++ b/packages/rfw/example/local/macos/Runner/MainFlutterWindow.swift
@@ -0,0 +1,19 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import Cocoa
+import FlutterMacOS
+
+class MainFlutterWindow: NSWindow {
+  override func awakeFromNib() {
+    let flutterViewController = FlutterViewController.init()
+    let windowFrame = self.frame
+    self.contentViewController = flutterViewController
+    self.setFrame(windowFrame, display: true)
+
+    RegisterGeneratedPlugins(registry: flutterViewController)
+
+    super.awakeFromNib()
+  }
+}
diff --git a/packages/rfw/example/local/macos/Runner/Release.entitlements b/packages/rfw/example/local/macos/Runner/Release.entitlements
new file mode 100644
index 0000000..852fa1a
--- /dev/null
+++ b/packages/rfw/example/local/macos/Runner/Release.entitlements
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>com.apple.security.app-sandbox</key>
+	<true/>
+</dict>
+</plist>
diff --git a/packages/rfw/example/local/pubspec.yaml b/packages/rfw/example/local/pubspec.yaml
new file mode 100644
index 0000000..1ee5e93
--- /dev/null
+++ b/packages/rfw/example/local/pubspec.yaml
@@ -0,0 +1,13 @@
+name: local
+description: Example of new custom local widgets for RFW
+publish_to: 'none' # Remove this line if you wish to publish to pub.dev
+version: 1.0.0+1
+
+environment:
+  sdk: ">=2.13.0 <3.0.0"
+
+dependencies:
+  flutter:
+    sdk: flutter
+  rfw:
+    path: ../../
diff --git a/packages/rfw/example/local/web/favicon.png b/packages/rfw/example/local/web/favicon.png
new file mode 100644
index 0000000..8aaa46a
--- /dev/null
+++ b/packages/rfw/example/local/web/favicon.png
Binary files differ
diff --git a/packages/rfw/example/local/web/icons/Icon-192.png b/packages/rfw/example/local/web/icons/Icon-192.png
new file mode 100644
index 0000000..b749bfe
--- /dev/null
+++ b/packages/rfw/example/local/web/icons/Icon-192.png
Binary files differ
diff --git a/packages/rfw/example/local/web/icons/Icon-512.png b/packages/rfw/example/local/web/icons/Icon-512.png
new file mode 100644
index 0000000..88cfd48
--- /dev/null
+++ b/packages/rfw/example/local/web/icons/Icon-512.png
Binary files differ
diff --git a/packages/rfw/example/local/web/icons/Icon-maskable-192.png b/packages/rfw/example/local/web/icons/Icon-maskable-192.png
new file mode 100644
index 0000000..eb9b4d7
--- /dev/null
+++ b/packages/rfw/example/local/web/icons/Icon-maskable-192.png
Binary files differ
diff --git a/packages/rfw/example/local/web/icons/Icon-maskable-512.png b/packages/rfw/example/local/web/icons/Icon-maskable-512.png
new file mode 100644
index 0000000..d69c566
--- /dev/null
+++ b/packages/rfw/example/local/web/icons/Icon-maskable-512.png
Binary files differ
diff --git a/packages/rfw/example/local/web/index.html b/packages/rfw/example/local/web/index.html
new file mode 100644
index 0000000..6872e67
--- /dev/null
+++ b/packages/rfw/example/local/web/index.html
@@ -0,0 +1,107 @@
+<!DOCTYPE html>
+<!-- Copyright 2013 The Flutter Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file. -->
+<html>
+<head>
+  <!--
+    If you are serving your web app in a path other than the root, change the
+    href value below to reflect the base path you are serving from.
+
+    The path provided below has to start and end with a slash "/" in order for
+    it to work correctly.
+
+    For more details:
+    * https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base
+
+    This is a placeholder for base href that will be replaced by the value of
+    the `--base-href` argument provided to `flutter build`.
+  -->
+  <base href="$FLUTTER_BASE_HREF">
+
+  <meta charset="UTF-8">
+  <meta content="IE=Edge" http-equiv="X-UA-Compatible">
+  <meta name="description" content="Example of new custom local widgets for RFW">
+
+  <!-- iOS meta tags & icons -->
+  <meta name="apple-mobile-web-app-capable" content="yes">
+  <meta name="apple-mobile-web-app-status-bar-style" content="black">
+  <meta name="apple-mobile-web-app-title" content="local">
+  <link rel="apple-touch-icon" href="icons/Icon-192.png">
+
+  <!-- Favicon -->
+  <link rel="icon" type="image/png" href="favicon.png"/>
+
+  <title>local</title>
+  <link rel="manifest" href="manifest.json">
+</head>
+<body>
+  <!-- This script installs service_worker.js to provide PWA functionality to
+       application. For more information, see:
+       https://developers.google.com/web/fundamentals/primers/service-workers -->
+  <script>
+    var serviceWorkerVersion = null;
+    var scriptLoaded = false;
+    function loadMainDartJs() {
+      if (scriptLoaded) {
+        return;
+      }
+      scriptLoaded = true;
+      var scriptTag = document.createElement('script');
+      scriptTag.src = 'main.dart.js';
+      scriptTag.type = 'application/javascript';
+      document.body.append(scriptTag);
+    }
+
+    if ('serviceWorker' in navigator) {
+      // Service workers are supported. Use them.
+      window.addEventListener('load', function () {
+        // Wait for registration to finish before dropping the <script> tag.
+        // Otherwise, the browser will load the script multiple times,
+        // potentially different versions.
+        var serviceWorkerUrl = 'flutter_service_worker.js?v=' + serviceWorkerVersion;
+        navigator.serviceWorker.register(serviceWorkerUrl)
+          .then((reg) => {
+            function waitForActivation(serviceWorker) {
+              serviceWorker.addEventListener('statechange', () => {
+                if (serviceWorker.state == 'activated') {
+                  console.log('Installed new service worker.');
+                  loadMainDartJs();
+                }
+              });
+            }
+            if (!reg.active && (reg.installing || reg.waiting)) {
+              // No active web worker and we have installed or are installing
+              // one for the first time. Simply wait for it to activate.
+              waitForActivation(reg.installing || reg.waiting);
+            } else if (!reg.active.scriptURL.endsWith(serviceWorkerVersion)) {
+              // When the app updates the serviceWorkerVersion changes, so we
+              // need to ask the service worker to update.
+              console.log('New service worker available.');
+              reg.update();
+              waitForActivation(reg.installing);
+            } else {
+              // Existing service worker is still good.
+              console.log('Loading app from service worker.');
+              loadMainDartJs();
+            }
+          });
+
+        // If service worker doesn't succeed in a reasonable amount of time,
+        // fallback to plaint <script> tag.
+        setTimeout(() => {
+          if (!scriptLoaded) {
+            console.warn(
+              'Failed to load app from service worker. Falling back to plain <script> tag.',
+            );
+            loadMainDartJs();
+          }
+        }, 4000);
+      });
+    } else {
+      // Service workers not supported. Just drop the <script> tag.
+      loadMainDartJs();
+    }
+  </script>
+</body>
+</html>
diff --git a/packages/rfw/example/local/web/manifest.json b/packages/rfw/example/local/web/manifest.json
new file mode 100644
index 0000000..9f9a575
--- /dev/null
+++ b/packages/rfw/example/local/web/manifest.json
@@ -0,0 +1,35 @@
+{
+    "name": "local",
+    "short_name": "local",
+    "start_url": ".",
+    "display": "standalone",
+    "background_color": "#0175C2",
+    "theme_color": "#0175C2",
+    "description": "Example of new custom local widgets for RFW",
+    "orientation": "portrait-primary",
+    "prefer_related_applications": false,
+    "icons": [
+        {
+            "src": "icons/Icon-192.png",
+            "sizes": "192x192",
+            "type": "image/png"
+        },
+        {
+            "src": "icons/Icon-512.png",
+            "sizes": "512x512",
+            "type": "image/png"
+        },
+        {
+            "src": "icons/Icon-maskable-192.png",
+            "sizes": "192x192",
+            "type": "image/png",
+            "purpose": "maskable"
+        },
+        {
+            "src": "icons/Icon-maskable-512.png",
+            "sizes": "512x512",
+            "type": "image/png",
+            "purpose": "maskable"
+        }
+    ]
+}
diff --git a/packages/rfw/example/local/windows/.gitignore b/packages/rfw/example/local/windows/.gitignore
new file mode 100644
index 0000000..d492d0d
--- /dev/null
+++ b/packages/rfw/example/local/windows/.gitignore
@@ -0,0 +1,17 @@
+flutter/ephemeral/
+
+# Visual Studio user-specific files.
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# Visual Studio build-related files.
+x64/
+x86/
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!*.[Cc]ache/
diff --git a/packages/rfw/example/local/windows/CMakeLists.txt b/packages/rfw/example/local/windows/CMakeLists.txt
new file mode 100644
index 0000000..5ab772a
--- /dev/null
+++ b/packages/rfw/example/local/windows/CMakeLists.txt
@@ -0,0 +1,95 @@
+cmake_minimum_required(VERSION 3.15)
+project(local LANGUAGES CXX)
+
+set(BINARY_NAME "local")
+
+cmake_policy(SET CMP0063 NEW)
+
+set(CMAKE_INSTALL_RPATH "$ORIGIN/lib")
+
+# Configure build options.
+get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
+if(IS_MULTICONFIG)
+  set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release"
+    CACHE STRING "" FORCE)
+else()
+  if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
+    set(CMAKE_BUILD_TYPE "Debug" CACHE
+      STRING "Flutter build mode" FORCE)
+    set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
+      "Debug" "Profile" "Release")
+  endif()
+endif()
+
+set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}")
+set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}")
+set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}")
+set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}")
+
+# Use Unicode for all projects.
+add_definitions(-DUNICODE -D_UNICODE)
+
+# Compilation settings that should be applied to most targets.
+function(APPLY_STANDARD_SETTINGS TARGET)
+  target_compile_features(${TARGET} PUBLIC cxx_std_17)
+  target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100")
+  target_compile_options(${TARGET} PRIVATE /EHsc)
+  target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0")
+  target_compile_definitions(${TARGET} PRIVATE "$<$<CONFIG:Debug>:_DEBUG>")
+endfunction()
+
+set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter")
+
+# Flutter library and tool build rules.
+add_subdirectory(${FLUTTER_MANAGED_DIR})
+
+# Application build
+add_subdirectory("runner")
+
+# Generated plugin build rules, which manage building the plugins and adding
+# them to the application.
+include(flutter/generated_plugins.cmake)
+
+
+# === Installation ===
+# Support files are copied into place next to the executable, so that it can
+# run in place. This is done instead of making a separate bundle (as on Linux)
+# so that building and running from within Visual Studio will work.
+set(BUILD_BUNDLE_DIR "$<TARGET_FILE_DIR:${BINARY_NAME}>")
+# Make the "install" step default, as it's required to run.
+set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1)
+if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
+  set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE)
+endif()
+
+set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data")
+set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}")
+
+install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}"
+  COMPONENT Runtime)
+
+install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
+  COMPONENT Runtime)
+
+install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
+  COMPONENT Runtime)
+
+if(PLUGIN_BUNDLED_LIBRARIES)
+  install(FILES "${PLUGIN_BUNDLED_LIBRARIES}"
+    DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
+    COMPONENT Runtime)
+endif()
+
+# Fully re-copy the assets directory on each build to avoid having stale files
+# from a previous install.
+set(FLUTTER_ASSET_DIR_NAME "flutter_assets")
+install(CODE "
+  file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\")
+  " COMPONENT Runtime)
+install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}"
+  DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime)
+
+# Install the AOT library on non-Debug builds only.
+install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
+  CONFIGURATIONS Profile;Release
+  COMPONENT Runtime)
diff --git a/packages/rfw/example/local/windows/flutter/CMakeLists.txt b/packages/rfw/example/local/windows/flutter/CMakeLists.txt
new file mode 100644
index 0000000..b02c548
--- /dev/null
+++ b/packages/rfw/example/local/windows/flutter/CMakeLists.txt
@@ -0,0 +1,103 @@
+cmake_minimum_required(VERSION 3.15)
+
+set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral")
+
+# Configuration provided via flutter tool.
+include(${EPHEMERAL_DIR}/generated_config.cmake)
+
+# TODO: Move the rest of this into files in ephemeral. See
+# https://github.com/flutter/flutter/issues/57146.
+set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper")
+
+# === Flutter Library ===
+set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll")
+
+# Published to parent scope for install step.
+set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE)
+set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE)
+set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE)
+set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE)
+
+list(APPEND FLUTTER_LIBRARY_HEADERS
+  "flutter_export.h"
+  "flutter_windows.h"
+  "flutter_messenger.h"
+  "flutter_plugin_registrar.h"
+  "flutter_texture_registrar.h"
+)
+list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/")
+add_library(flutter INTERFACE)
+target_include_directories(flutter INTERFACE
+  "${EPHEMERAL_DIR}"
+)
+target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib")
+add_dependencies(flutter flutter_assemble)
+
+# === Wrapper ===
+list(APPEND CPP_WRAPPER_SOURCES_CORE
+  "core_implementations.cc"
+  "standard_codec.cc"
+)
+list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/")
+list(APPEND CPP_WRAPPER_SOURCES_PLUGIN
+  "plugin_registrar.cc"
+)
+list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/")
+list(APPEND CPP_WRAPPER_SOURCES_APP
+  "flutter_engine.cc"
+  "flutter_view_controller.cc"
+)
+list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/")
+
+# Wrapper sources needed for a plugin.
+add_library(flutter_wrapper_plugin STATIC
+  ${CPP_WRAPPER_SOURCES_CORE}
+  ${CPP_WRAPPER_SOURCES_PLUGIN}
+)
+apply_standard_settings(flutter_wrapper_plugin)
+set_target_properties(flutter_wrapper_plugin PROPERTIES
+  POSITION_INDEPENDENT_CODE ON)
+set_target_properties(flutter_wrapper_plugin PROPERTIES
+  CXX_VISIBILITY_PRESET hidden)
+target_link_libraries(flutter_wrapper_plugin PUBLIC flutter)
+target_include_directories(flutter_wrapper_plugin PUBLIC
+  "${WRAPPER_ROOT}/include"
+)
+add_dependencies(flutter_wrapper_plugin flutter_assemble)
+
+# Wrapper sources needed for the runner.
+add_library(flutter_wrapper_app STATIC
+  ${CPP_WRAPPER_SOURCES_CORE}
+  ${CPP_WRAPPER_SOURCES_APP}
+)
+apply_standard_settings(flutter_wrapper_app)
+target_link_libraries(flutter_wrapper_app PUBLIC flutter)
+target_include_directories(flutter_wrapper_app PUBLIC
+  "${WRAPPER_ROOT}/include"
+)
+add_dependencies(flutter_wrapper_app flutter_assemble)
+
+# === Flutter tool backend ===
+# _phony_ is a non-existent file to force this command to run every time,
+# since currently there's no way to get a full input/output list from the
+# flutter tool.
+set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_")
+set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE)
+add_custom_command(
+  OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS}
+    ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN}
+    ${CPP_WRAPPER_SOURCES_APP}
+    ${PHONY_OUTPUT}
+  COMMAND ${CMAKE_COMMAND} -E env
+    ${FLUTTER_TOOL_ENVIRONMENT}
+    "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat"
+      windows-x64 $<CONFIG>
+  VERBATIM
+)
+add_custom_target(flutter_assemble DEPENDS
+  "${FLUTTER_LIBRARY}"
+  ${FLUTTER_LIBRARY_HEADERS}
+  ${CPP_WRAPPER_SOURCES_CORE}
+  ${CPP_WRAPPER_SOURCES_PLUGIN}
+  ${CPP_WRAPPER_SOURCES_APP}
+)
diff --git a/packages/rfw/example/local/windows/flutter/generated_plugins.cmake b/packages/rfw/example/local/windows/flutter/generated_plugins.cmake
new file mode 100644
index 0000000..4d10c25
--- /dev/null
+++ b/packages/rfw/example/local/windows/flutter/generated_plugins.cmake
@@ -0,0 +1,15 @@
+#
+# Generated file, do not edit.
+#
+
+list(APPEND FLUTTER_PLUGIN_LIST
+)
+
+set(PLUGIN_BUNDLED_LIBRARIES)
+
+foreach(plugin ${FLUTTER_PLUGIN_LIST})
+  add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin})
+  target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)
+  list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>)
+  list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
+endforeach(plugin)
diff --git a/packages/rfw/example/local/windows/runner/CMakeLists.txt b/packages/rfw/example/local/windows/runner/CMakeLists.txt
new file mode 100644
index 0000000..0b899a0
--- /dev/null
+++ b/packages/rfw/example/local/windows/runner/CMakeLists.txt
@@ -0,0 +1,17 @@
+cmake_minimum_required(VERSION 3.15)
+project(runner LANGUAGES CXX)
+
+add_executable(${BINARY_NAME} WIN32
+  "flutter_window.cpp"
+  "main.cpp"
+  "utils.cpp"
+  "win32_window.cpp"
+  "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc"
+  "Runner.rc"
+  "runner.exe.manifest"
+)
+apply_standard_settings(${BINARY_NAME})
+target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX")
+target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app)
+target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}")
+add_dependencies(${BINARY_NAME} flutter_assemble)
diff --git a/packages/rfw/example/local/windows/runner/Runner.rc b/packages/rfw/example/local/windows/runner/Runner.rc
new file mode 100644
index 0000000..0ca2e22
--- /dev/null
+++ b/packages/rfw/example/local/windows/runner/Runner.rc
@@ -0,0 +1,121 @@
+// Microsoft Visual C++ generated resource script.
+//
+#pragma code_page(65001)
+#include "resource.h"
+
+#define APSTUDIO_READONLY_SYMBOLS
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 2 resource.
+//
+#include "winres.h"
+
+/////////////////////////////////////////////////////////////////////////////
+#undef APSTUDIO_READONLY_SYMBOLS
+
+/////////////////////////////////////////////////////////////////////////////
+// English (United States) resources
+
+#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
+LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
+
+#ifdef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// TEXTINCLUDE
+//
+
+1 TEXTINCLUDE
+BEGIN
+    "resource.h\0"
+END
+
+2 TEXTINCLUDE
+BEGIN
+    "#include ""winres.h""\r\n"
+    "\0"
+END
+
+3 TEXTINCLUDE
+BEGIN
+    "\r\n"
+    "\0"
+END
+
+#endif    // APSTUDIO_INVOKED
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Icon
+//
+
+// Icon with lowest ID value placed first to ensure application icon
+// remains consistent on all systems.
+IDI_APP_ICON            ICON                    "resources\\app_icon.ico"
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Version
+//
+
+#ifdef FLUTTER_BUILD_NUMBER
+#define VERSION_AS_NUMBER FLUTTER_BUILD_NUMBER
+#else
+#define VERSION_AS_NUMBER 1,0,0
+#endif
+
+#ifdef FLUTTER_BUILD_NAME
+#define VERSION_AS_STRING #FLUTTER_BUILD_NAME
+#else
+#define VERSION_AS_STRING "1.0.0"
+#endif
+
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION VERSION_AS_NUMBER
+ PRODUCTVERSION VERSION_AS_NUMBER
+ FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
+#ifdef _DEBUG
+ FILEFLAGS VS_FF_DEBUG
+#else
+ FILEFLAGS 0x0L
+#endif
+ FILEOS VOS__WINDOWS32
+ FILETYPE VFT_APP
+ FILESUBTYPE 0x0L
+BEGIN
+    BLOCK "StringFileInfo"
+    BEGIN
+        BLOCK "040904e4"
+        BEGIN
+            VALUE "CompanyName", "dev.flutter.rfw.examples" "\0"
+            VALUE "FileDescription", "Example of new custom local widgets for RFW" "\0"
+            VALUE "FileVersion", VERSION_AS_STRING "\0"
+            VALUE "InternalName", "local" "\0"
+            VALUE "LegalCopyright", "Copyright (C) 2021 dev.flutter.rfw.examples. All rights reserved." "\0"
+            VALUE "OriginalFilename", "local.exe" "\0"
+            VALUE "ProductName", "local" "\0"
+            VALUE "ProductVersion", VERSION_AS_STRING "\0"
+        END
+    END
+    BLOCK "VarFileInfo"
+    BEGIN
+        VALUE "Translation", 0x409, 1252
+    END
+END
+
+#endif    // English (United States) resources
+/////////////////////////////////////////////////////////////////////////////
+
+
+
+#ifndef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 3 resource.
+//
+
+
+/////////////////////////////////////////////////////////////////////////////
+#endif    // not APSTUDIO_INVOKED
diff --git a/packages/rfw/example/local/windows/runner/flutter_window.cpp b/packages/rfw/example/local/windows/runner/flutter_window.cpp
new file mode 100644
index 0000000..8254bd9
--- /dev/null
+++ b/packages/rfw/example/local/windows/runner/flutter_window.cpp
@@ -0,0 +1,65 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "flutter_window.h"
+
+#include <optional>
+
+#include "flutter/generated_plugin_registrant.h"
+
+FlutterWindow::FlutterWindow(const flutter::DartProject& project)
+    : project_(project) {}
+
+FlutterWindow::~FlutterWindow() {}
+
+bool FlutterWindow::OnCreate() {
+  if (!Win32Window::OnCreate()) {
+    return false;
+  }
+
+  RECT frame = GetClientArea();
+
+  // The size here must match the window dimensions to avoid unnecessary surface
+  // creation / destruction in the startup path.
+  flutter_controller_ = std::make_unique<flutter::FlutterViewController>(
+      frame.right - frame.left, frame.bottom - frame.top, project_);
+  // Ensure that basic setup of the controller was successful.
+  if (!flutter_controller_->engine() || !flutter_controller_->view()) {
+    return false;
+  }
+  RegisterPlugins(flutter_controller_->engine());
+  SetChildContent(flutter_controller_->view()->GetNativeWindow());
+  return true;
+}
+
+void FlutterWindow::OnDestroy() {
+  if (flutter_controller_) {
+    flutter_controller_ = nullptr;
+  }
+
+  Win32Window::OnDestroy();
+}
+
+LRESULT
+FlutterWindow::MessageHandler(HWND hwnd, UINT const message,
+                              WPARAM const wparam,
+                              LPARAM const lparam) noexcept {
+  // Give Flutter, including plugins, an opportunity to handle window messages.
+  if (flutter_controller_) {
+    std::optional<LRESULT> result =
+        flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam,
+                                                      lparam);
+    if (result) {
+      return *result;
+    }
+  }
+
+  switch (message) {
+    case WM_FONTCHANGE:
+      flutter_controller_->engine()->ReloadSystemFonts();
+      break;
+  }
+
+  return Win32Window::MessageHandler(hwnd, message, wparam, lparam);
+}
diff --git a/packages/rfw/example/local/windows/runner/flutter_window.h b/packages/rfw/example/local/windows/runner/flutter_window.h
new file mode 100644
index 0000000..f1fc669
--- /dev/null
+++ b/packages/rfw/example/local/windows/runner/flutter_window.h
@@ -0,0 +1,37 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef RUNNER_FLUTTER_WINDOW_H_
+#define RUNNER_FLUTTER_WINDOW_H_
+
+#include <flutter/dart_project.h>
+#include <flutter/flutter_view_controller.h>
+
+#include <memory>
+
+#include "win32_window.h"
+
+// A window that does nothing but host a Flutter view.
+class FlutterWindow : public Win32Window {
+ public:
+  // Creates a new FlutterWindow hosting a Flutter view running |project|.
+  explicit FlutterWindow(const flutter::DartProject& project);
+  virtual ~FlutterWindow();
+
+ protected:
+  // Win32Window:
+  bool OnCreate() override;
+  void OnDestroy() override;
+  LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam,
+                         LPARAM const lparam) noexcept override;
+
+ private:
+  // The project to run.
+  flutter::DartProject project_;
+
+  // The Flutter instance hosted by this window.
+  std::unique_ptr<flutter::FlutterViewController> flutter_controller_;
+};
+
+#endif  // RUNNER_FLUTTER_WINDOW_H_
diff --git a/packages/rfw/example/local/windows/runner/main.cpp b/packages/rfw/example/local/windows/runner/main.cpp
new file mode 100644
index 0000000..2807ba9
--- /dev/null
+++ b/packages/rfw/example/local/windows/runner/main.cpp
@@ -0,0 +1,46 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <flutter/dart_project.h>
+#include <flutter/flutter_view_controller.h>
+#include <windows.h>
+
+#include "flutter_window.h"
+#include "utils.h"
+
+int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
+                      _In_ wchar_t *command_line, _In_ int show_command) {
+  // Attach to console when present (e.g., 'flutter run') or create a
+  // new console when running with a debugger.
+  if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) {
+    CreateAndAttachConsole();
+  }
+
+  // Initialize COM, so that it is available for use in the library and/or
+  // plugins.
+  ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
+
+  flutter::DartProject project(L"data");
+
+  std::vector<std::string> command_line_arguments = GetCommandLineArguments();
+
+  project.set_dart_entrypoint_arguments(std::move(command_line_arguments));
+
+  FlutterWindow window(project);
+  Win32Window::Point origin(10, 10);
+  Win32Window::Size size(1280, 720);
+  if (!window.CreateAndShow(L"local", origin, size)) {
+    return EXIT_FAILURE;
+  }
+  window.SetQuitOnClose(true);
+
+  ::MSG msg;
+  while (::GetMessage(&msg, nullptr, 0, 0)) {
+    ::TranslateMessage(&msg);
+    ::DispatchMessage(&msg);
+  }
+
+  ::CoUninitialize();
+  return EXIT_SUCCESS;
+}
diff --git a/packages/rfw/example/local/windows/runner/resource.h b/packages/rfw/example/local/windows/runner/resource.h
new file mode 100644
index 0000000..d5d958d
--- /dev/null
+++ b/packages/rfw/example/local/windows/runner/resource.h
@@ -0,0 +1,16 @@
+//{{NO_DEPENDENCIES}}
+// Microsoft Visual C++ generated include file.
+// Used by Runner.rc
+//
+#define IDI_APP_ICON 101
+
+// Next default values for new objects
+//
+#ifdef APSTUDIO_INVOKED
+#ifndef APSTUDIO_READONLY_SYMBOLS
+#define _APS_NEXT_RESOURCE_VALUE 102
+#define _APS_NEXT_COMMAND_VALUE 40001
+#define _APS_NEXT_CONTROL_VALUE 1001
+#define _APS_NEXT_SYMED_VALUE 101
+#endif
+#endif
diff --git a/packages/rfw/example/local/windows/runner/resources/app_icon.ico b/packages/rfw/example/local/windows/runner/resources/app_icon.ico
new file mode 100644
index 0000000..c04e20c
--- /dev/null
+++ b/packages/rfw/example/local/windows/runner/resources/app_icon.ico
Binary files differ
diff --git a/packages/rfw/example/local/windows/runner/runner.exe.manifest b/packages/rfw/example/local/windows/runner/runner.exe.manifest
new file mode 100644
index 0000000..c977c4a
--- /dev/null
+++ b/packages/rfw/example/local/windows/runner/runner.exe.manifest
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
+  <application xmlns="urn:schemas-microsoft-com:asm.v3">
+    <windowsSettings>
+      <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
+    </windowsSettings>
+  </application>
+  <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
+    <application>
+      <!-- Windows 10 -->
+      <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
+      <!-- Windows 8.1 -->
+      <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
+      <!-- Windows 8 -->
+      <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
+      <!-- Windows 7 -->
+      <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
+    </application>
+  </compatibility>
+</assembly>
diff --git a/packages/rfw/example/local/windows/runner/utils.cpp b/packages/rfw/example/local/windows/runner/utils.cpp
new file mode 100644
index 0000000..fb7e945
--- /dev/null
+++ b/packages/rfw/example/local/windows/runner/utils.cpp
@@ -0,0 +1,67 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "utils.h"
+
+#include <flutter_windows.h>
+#include <io.h>
+#include <stdio.h>
+#include <windows.h>
+
+#include <iostream>
+
+void CreateAndAttachConsole() {
+  if (::AllocConsole()) {
+    FILE* unused;
+    if (freopen_s(&unused, "CONOUT$", "w", stdout)) {
+      _dup2(_fileno(stdout), 1);
+    }
+    if (freopen_s(&unused, "CONOUT$", "w", stderr)) {
+      _dup2(_fileno(stdout), 2);
+    }
+    std::ios::sync_with_stdio();
+    FlutterDesktopResyncOutputStreams();
+  }
+}
+
+std::vector<std::string> GetCommandLineArguments() {
+  // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use.
+  int argc;
+  wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc);
+  if (argv == nullptr) {
+    return std::vector<std::string>();
+  }
+
+  std::vector<std::string> command_line_arguments;
+
+  // Skip the first argument as it's the binary name.
+  for (int i = 1; i < argc; i++) {
+    command_line_arguments.push_back(Utf8FromUtf16(argv[i]));
+  }
+
+  ::LocalFree(argv);
+
+  return command_line_arguments;
+}
+
+std::string Utf8FromUtf16(const wchar_t* utf16_string) {
+  if (utf16_string == nullptr) {
+    return std::string();
+  }
+  int target_length =
+      ::WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, -1,
+                            nullptr, 0, nullptr, nullptr);
+  if (target_length == 0) {
+    return std::string();
+  }
+  std::string utf8_string;
+  utf8_string.resize(target_length);
+  int converted_length = ::WideCharToMultiByte(
+      CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, -1, utf8_string.data(),
+      target_length, nullptr, nullptr);
+  if (converted_length == 0) {
+    return std::string();
+  }
+  return utf8_string;
+}
diff --git a/packages/rfw/example/local/windows/runner/utils.h b/packages/rfw/example/local/windows/runner/utils.h
new file mode 100644
index 0000000..bd81e1e
--- /dev/null
+++ b/packages/rfw/example/local/windows/runner/utils.h
@@ -0,0 +1,23 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef RUNNER_UTILS_H_
+#define RUNNER_UTILS_H_
+
+#include <string>
+#include <vector>
+
+// Creates a console for the process, and redirects stdout and stderr to
+// it for both the runner and the Flutter library.
+void CreateAndAttachConsole();
+
+// Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string
+// encoded in UTF-8. Returns an empty std::string on failure.
+std::string Utf8FromUtf16(const wchar_t* utf16_string);
+
+// Gets the command line arguments passed in as a std::vector<std::string>,
+// encoded in UTF-8. Returns an empty std::vector<std::string> on failure.
+std::vector<std::string> GetCommandLineArguments();
+
+#endif  // RUNNER_UTILS_H_
diff --git a/packages/rfw/example/local/windows/runner/win32_window.cpp b/packages/rfw/example/local/windows/runner/win32_window.cpp
new file mode 100644
index 0000000..85aa361
--- /dev/null
+++ b/packages/rfw/example/local/windows/runner/win32_window.cpp
@@ -0,0 +1,241 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "win32_window.h"
+
+#include <flutter_windows.h>
+
+#include "resource.h"
+
+namespace {
+
+constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW";
+
+// The number of Win32Window objects that currently exist.
+static int g_active_window_count = 0;
+
+using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd);
+
+// Scale helper to convert logical scaler values to physical using passed in
+// scale factor
+int Scale(int source, double scale_factor) {
+  return static_cast<int>(source * scale_factor);
+}
+
+// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module.
+// This API is only needed for PerMonitor V1 awareness mode.
+void EnableFullDpiSupportIfAvailable(HWND hwnd) {
+  HMODULE user32_module = LoadLibraryA("User32.dll");
+  if (!user32_module) {
+    return;
+  }
+  auto enable_non_client_dpi_scaling =
+      reinterpret_cast<EnableNonClientDpiScaling*>(
+          GetProcAddress(user32_module, "EnableNonClientDpiScaling"));
+  if (enable_non_client_dpi_scaling != nullptr) {
+    enable_non_client_dpi_scaling(hwnd);
+    FreeLibrary(user32_module);
+  }
+}
+
+}  // namespace
+
+// Manages the Win32Window's window class registration.
+class WindowClassRegistrar {
+ public:
+  ~WindowClassRegistrar() = default;
+
+  // Returns the singleton registar instance.
+  static WindowClassRegistrar* GetInstance() {
+    if (!instance_) {
+      instance_ = new WindowClassRegistrar();
+    }
+    return instance_;
+  }
+
+  // Returns the name of the window class, registering the class if it hasn't
+  // previously been registered.
+  const wchar_t* GetWindowClass();
+
+  // Unregisters the window class. Should only be called if there are no
+  // instances of the window.
+  void UnregisterWindowClass();
+
+ private:
+  WindowClassRegistrar() = default;
+
+  static WindowClassRegistrar* instance_;
+
+  bool class_registered_ = false;
+};
+
+WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr;
+
+const wchar_t* WindowClassRegistrar::GetWindowClass() {
+  if (!class_registered_) {
+    WNDCLASS window_class{};
+    window_class.hCursor = LoadCursor(nullptr, IDC_ARROW);
+    window_class.lpszClassName = kWindowClassName;
+    window_class.style = CS_HREDRAW | CS_VREDRAW;
+    window_class.cbClsExtra = 0;
+    window_class.cbWndExtra = 0;
+    window_class.hInstance = GetModuleHandle(nullptr);
+    window_class.hIcon =
+        LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON));
+    window_class.hbrBackground = 0;
+    window_class.lpszMenuName = nullptr;
+    window_class.lpfnWndProc = Win32Window::WndProc;
+    RegisterClass(&window_class);
+    class_registered_ = true;
+  }
+  return kWindowClassName;
+}
+
+void WindowClassRegistrar::UnregisterWindowClass() {
+  UnregisterClass(kWindowClassName, nullptr);
+  class_registered_ = false;
+}
+
+Win32Window::Win32Window() { ++g_active_window_count; }
+
+Win32Window::~Win32Window() {
+  --g_active_window_count;
+  Destroy();
+}
+
+bool Win32Window::CreateAndShow(const std::wstring& title, const Point& origin,
+                                const Size& size) {
+  Destroy();
+
+  const wchar_t* window_class =
+      WindowClassRegistrar::GetInstance()->GetWindowClass();
+
+  const POINT target_point = {static_cast<LONG>(origin.x),
+                              static_cast<LONG>(origin.y)};
+  HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST);
+  UINT dpi = FlutterDesktopGetDpiForMonitor(monitor);
+  double scale_factor = dpi / 96.0;
+
+  HWND window = CreateWindow(
+      window_class, title.c_str(), WS_OVERLAPPEDWINDOW | WS_VISIBLE,
+      Scale(origin.x, scale_factor), Scale(origin.y, scale_factor),
+      Scale(size.width, scale_factor), Scale(size.height, scale_factor),
+      nullptr, nullptr, GetModuleHandle(nullptr), this);
+
+  if (!window) {
+    return false;
+  }
+
+  return OnCreate();
+}
+
+// static
+LRESULT CALLBACK Win32Window::WndProc(HWND const window, UINT const message,
+                                      WPARAM const wparam,
+                                      LPARAM const lparam) noexcept {
+  if (message == WM_NCCREATE) {
+    auto window_struct = reinterpret_cast<CREATESTRUCT*>(lparam);
+    SetWindowLongPtr(window, GWLP_USERDATA,
+                     reinterpret_cast<LONG_PTR>(window_struct->lpCreateParams));
+
+    auto that = static_cast<Win32Window*>(window_struct->lpCreateParams);
+    EnableFullDpiSupportIfAvailable(window);
+    that->window_handle_ = window;
+  } else if (Win32Window* that = GetThisFromHandle(window)) {
+    return that->MessageHandler(window, message, wparam, lparam);
+  }
+
+  return DefWindowProc(window, message, wparam, lparam);
+}
+
+LRESULT
+Win32Window::MessageHandler(HWND hwnd, UINT const message, WPARAM const wparam,
+                            LPARAM const lparam) noexcept {
+  switch (message) {
+    case WM_DESTROY:
+      window_handle_ = nullptr;
+      Destroy();
+      if (quit_on_close_) {
+        PostQuitMessage(0);
+      }
+      return 0;
+
+    case WM_DPICHANGED: {
+      auto newRectSize = reinterpret_cast<RECT*>(lparam);
+      LONG newWidth = newRectSize->right - newRectSize->left;
+      LONG newHeight = newRectSize->bottom - newRectSize->top;
+
+      SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth,
+                   newHeight, SWP_NOZORDER | SWP_NOACTIVATE);
+
+      return 0;
+    }
+    case WM_SIZE: {
+      RECT rect = GetClientArea();
+      if (child_content_ != nullptr) {
+        // Size and position the child window.
+        MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left,
+                   rect.bottom - rect.top, TRUE);
+      }
+      return 0;
+    }
+
+    case WM_ACTIVATE:
+      if (child_content_ != nullptr) {
+        SetFocus(child_content_);
+      }
+      return 0;
+  }
+
+  return DefWindowProc(window_handle_, message, wparam, lparam);
+}
+
+void Win32Window::Destroy() {
+  OnDestroy();
+
+  if (window_handle_) {
+    DestroyWindow(window_handle_);
+    window_handle_ = nullptr;
+  }
+  if (g_active_window_count == 0) {
+    WindowClassRegistrar::GetInstance()->UnregisterWindowClass();
+  }
+}
+
+Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept {
+  return reinterpret_cast<Win32Window*>(
+      GetWindowLongPtr(window, GWLP_USERDATA));
+}
+
+void Win32Window::SetChildContent(HWND content) {
+  child_content_ = content;
+  SetParent(content, window_handle_);
+  RECT frame = GetClientArea();
+
+  MoveWindow(content, frame.left, frame.top, frame.right - frame.left,
+             frame.bottom - frame.top, true);
+
+  SetFocus(child_content_);
+}
+
+RECT Win32Window::GetClientArea() {
+  RECT frame;
+  GetClientRect(window_handle_, &frame);
+  return frame;
+}
+
+HWND Win32Window::GetHandle() { return window_handle_; }
+
+void Win32Window::SetQuitOnClose(bool quit_on_close) {
+  quit_on_close_ = quit_on_close;
+}
+
+bool Win32Window::OnCreate() {
+  // No-op; provided for subclasses.
+  return true;
+}
+
+void Win32Window::OnDestroy() {
+  // No-op; provided for subclasses.
+}
diff --git a/packages/rfw/example/local/windows/runner/win32_window.h b/packages/rfw/example/local/windows/runner/win32_window.h
new file mode 100644
index 0000000..d2a7300
--- /dev/null
+++ b/packages/rfw/example/local/windows/runner/win32_window.h
@@ -0,0 +1,99 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef RUNNER_WIN32_WINDOW_H_
+#define RUNNER_WIN32_WINDOW_H_
+
+#include <windows.h>
+
+#include <functional>
+#include <memory>
+#include <string>
+
+// A class abstraction for a high DPI-aware Win32 Window. Intended to be
+// inherited from by classes that wish to specialize with custom
+// rendering and input handling
+class Win32Window {
+ public:
+  struct Point {
+    unsigned int x;
+    unsigned int y;
+    Point(unsigned int x, unsigned int y) : x(x), y(y) {}
+  };
+
+  struct Size {
+    unsigned int width;
+    unsigned int height;
+    Size(unsigned int width, unsigned int height)
+        : width(width), height(height) {}
+  };
+
+  Win32Window();
+  virtual ~Win32Window();
+
+  // Creates and shows a win32 window with |title| and position and size using
+  // |origin| and |size|. New windows are created on the default monitor. Window
+  // sizes are specified to the OS in physical pixels, hence to ensure a
+  // consistent size to will treat the width height passed in to this function
+  // as logical pixels and scale to appropriate for the default monitor. Returns
+  // true if the window was created successfully.
+  bool CreateAndShow(const std::wstring& title, const Point& origin,
+                     const Size& size);
+
+  // Release OS resources associated with window.
+  void Destroy();
+
+  // Inserts |content| into the window tree.
+  void SetChildContent(HWND content);
+
+  // Returns the backing Window handle to enable clients to set icon and other
+  // window properties. Returns nullptr if the window has been destroyed.
+  HWND GetHandle();
+
+  // If true, closing this window will quit the application.
+  void SetQuitOnClose(bool quit_on_close);
+
+  // Return a RECT representing the bounds of the current client area.
+  RECT GetClientArea();
+
+ protected:
+  // Processes and route salient window messages for mouse handling,
+  // size change and DPI. Delegates handling of these to member overloads that
+  // inheriting classes can handle.
+  virtual LRESULT MessageHandler(HWND window, UINT const message,
+                                 WPARAM const wparam,
+                                 LPARAM const lparam) noexcept;
+
+  // Called when CreateAndShow is called, allowing subclass window-related
+  // setup. Subclasses should return false if setup fails.
+  virtual bool OnCreate();
+
+  // Called when Destroy is called.
+  virtual void OnDestroy();
+
+ private:
+  friend class WindowClassRegistrar;
+
+  // OS callback called by message pump. Handles the WM_NCCREATE message which
+  // is passed when the non-client area is being created and enables automatic
+  // non-client DPI scaling so that the non-client area automatically
+  // responsponds to changes in DPI. All other messages are handled by
+  // MessageHandler.
+  static LRESULT CALLBACK WndProc(HWND const window, UINT const message,
+                                  WPARAM const wparam,
+                                  LPARAM const lparam) noexcept;
+
+  // Retrieves a class instance pointer for |window|
+  static Win32Window* GetThisFromHandle(HWND const window) noexcept;
+
+  bool quit_on_close_ = false;
+
+  // window handle for top level window.
+  HWND window_handle_ = nullptr;
+
+  // window handle for hosted content.
+  HWND child_content_ = nullptr;
+};
+
+#endif  // RUNNER_WIN32_WINDOW_H_
diff --git a/packages/rfw/example/remote/.gitignore b/packages/rfw/example/remote/.gitignore
new file mode 100644
index 0000000..0fa6b67
--- /dev/null
+++ b/packages/rfw/example/remote/.gitignore
@@ -0,0 +1,46 @@
+# Miscellaneous
+*.class
+*.log
+*.pyc
+*.swp
+.DS_Store
+.atom/
+.buildlog/
+.history
+.svn/
+
+# IntelliJ related
+*.iml
+*.ipr
+*.iws
+.idea/
+
+# The .vscode folder contains launch configuration and tasks you configure in
+# VS Code which you may wish to be included in version control, so this line
+# is commented out by default.
+#.vscode/
+
+# Flutter/Dart/Pub related
+**/doc/api/
+**/ios/Flutter/.last_build_id
+.dart_tool/
+.flutter-plugins
+.flutter-plugins-dependencies
+.packages
+.pub-cache/
+.pub/
+/build/
+
+# Web related
+lib/generated_plugin_registrant.dart
+
+# Symbolication related
+app.*.symbols
+
+# Obfuscation related
+app.*.map.json
+
+# Android Studio will place build artifacts here
+/android/app/debug
+/android/app/profile
+/android/app/release
diff --git a/packages/rfw/example/remote/.metadata b/packages/rfw/example/remote/.metadata
new file mode 100644
index 0000000..45c494e
--- /dev/null
+++ b/packages/rfw/example/remote/.metadata
@@ -0,0 +1,10 @@
+# This file tracks properties of this Flutter project.
+# Used by Flutter tool to assess capabilities and perform upgrades etc.
+#
+# This file should be version controlled and should not be manually edited.
+
+version:
+  revision: a1cd3f45e02c2429e14630d6cc6de120c3d24bd0
+  channel: master
+
+project_type: app
diff --git a/packages/rfw/example/remote/.pluginToolsConfig.yaml b/packages/rfw/example/remote/.pluginToolsConfig.yaml
new file mode 100644
index 0000000..0be940a
--- /dev/null
+++ b/packages/rfw/example/remote/.pluginToolsConfig.yaml
@@ -0,0 +1,3 @@
+buildFlags:
+  global:
+    - "--no-tree-shake-icons"
diff --git a/packages/rfw/example/remote/README.md b/packages/rfw/example/remote/README.md
new file mode 100644
index 0000000..7db02e7
--- /dev/null
+++ b/packages/rfw/example/remote/README.md
@@ -0,0 +1,4 @@
+# Example of fetching remote widgets for RFW
+
+This example shows how one can fetch remote widget library files from
+a remote server.
diff --git a/packages/rfw/example/remote/android/.gitignore b/packages/rfw/example/remote/android/.gitignore
new file mode 100644
index 0000000..6f56801
--- /dev/null
+++ b/packages/rfw/example/remote/android/.gitignore
@@ -0,0 +1,13 @@
+gradle-wrapper.jar
+/.gradle
+/captures/
+/gradlew
+/gradlew.bat
+/local.properties
+GeneratedPluginRegistrant.java
+
+# Remember to never publicly share your keystore.
+# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
+key.properties
+**/*.keystore
+**/*.jks
diff --git a/packages/rfw/example/remote/android/app/build.gradle b/packages/rfw/example/remote/android/app/build.gradle
new file mode 100644
index 0000000..2797d2c
--- /dev/null
+++ b/packages/rfw/example/remote/android/app/build.gradle
@@ -0,0 +1,68 @@
+def localProperties = new Properties()
+def localPropertiesFile = rootProject.file('local.properties')
+if (localPropertiesFile.exists()) {
+    localPropertiesFile.withReader('UTF-8') { reader ->
+        localProperties.load(reader)
+    }
+}
+
+def flutterRoot = localProperties.getProperty('flutter.sdk')
+if (flutterRoot == null) {
+    throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
+}
+
+def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
+if (flutterVersionCode == null) {
+    flutterVersionCode = '1'
+}
+
+def flutterVersionName = localProperties.getProperty('flutter.versionName')
+if (flutterVersionName == null) {
+    flutterVersionName = '1.0'
+}
+
+apply plugin: 'com.android.application'
+apply plugin: 'kotlin-android'
+apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
+
+android {
+    compileSdkVersion 30
+
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_8
+        targetCompatibility JavaVersion.VERSION_1_8
+    }
+
+    kotlinOptions {
+        jvmTarget = '1.8'
+    }
+
+    sourceSets {
+        main.java.srcDirs += 'src/main/kotlin'
+    }
+
+    defaultConfig {
+        // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
+        applicationId "dev.flutter.rfw.examples.remote"
+        minSdkVersion 16
+        targetSdkVersion 30
+        versionCode flutterVersionCode.toInteger()
+        versionName flutterVersionName
+    }
+
+    buildTypes {
+        release {
+            // TODO: Add your own signing config for the release build.
+            // Signing with the debug keys for now, so `flutter run --release` works.
+            signingConfig signingConfigs.debug
+        }
+    }
+}
+
+flutter {
+    source '../..'
+}
+
+dependencies {
+    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
+}
diff --git a/packages/rfw/example/remote/android/app/src/debug/AndroidManifest.xml b/packages/rfw/example/remote/android/app/src/debug/AndroidManifest.xml
new file mode 100644
index 0000000..3b7fd23
--- /dev/null
+++ b/packages/rfw/example/remote/android/app/src/debug/AndroidManifest.xml
@@ -0,0 +1,7 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="dev.flutter.rfw.examples.remote">
+    <!-- Flutter needs it to communicate with the running application
+         to allow setting breakpoints, to provide hot reload, etc.
+    -->
+    <uses-permission android:name="android.permission.INTERNET"/>
+</manifest>
diff --git a/packages/rfw/example/remote/android/app/src/main/AndroidManifest.xml b/packages/rfw/example/remote/android/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..cdb8d36
--- /dev/null
+++ b/packages/rfw/example/remote/android/app/src/main/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="dev.flutter.rfw.examples.remote">
+   <application
+        android:label="remote"
+        android:icon="@mipmap/ic_launcher">
+        <activity
+            android:name=".MainActivity"
+            android:launchMode="singleTop"
+            android:theme="@style/LaunchTheme"
+            android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
+            android:hardwareAccelerated="true"
+            android:windowSoftInputMode="adjustResize">
+            <!-- Specifies an Android theme to apply to this Activity as soon as
+                 the Android process has started. This theme is visible to the user
+                 while the Flutter UI initializes. After that, this theme continues
+                 to determine the Window background behind the Flutter UI. -->
+            <meta-data
+              android:name="io.flutter.embedding.android.NormalTheme"
+              android:resource="@style/NormalTheme"
+              />
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+        <!-- Don't delete the meta-data below.
+             This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
+        <meta-data
+            android:name="flutterEmbedding"
+            android:value="2" />
+    </application>
+</manifest>
diff --git a/packages/rfw/example/remote/android/app/src/main/kotlin/dev/flutter/rfw/examples/remote/MainActivity.kt b/packages/rfw/example/remote/android/app/src/main/kotlin/dev/flutter/rfw/examples/remote/MainActivity.kt
new file mode 100644
index 0000000..7eb9506
--- /dev/null
+++ b/packages/rfw/example/remote/android/app/src/main/kotlin/dev/flutter/rfw/examples/remote/MainActivity.kt
@@ -0,0 +1,6 @@
+package dev.flutter.rfw.examples.remote
+
+import io.flutter.embedding.android.FlutterActivity
+
+class MainActivity: FlutterActivity() {
+}
diff --git a/packages/rfw/example/remote/android/app/src/main/res/drawable-v21/launch_background.xml b/packages/rfw/example/remote/android/app/src/main/res/drawable-v21/launch_background.xml
new file mode 100644
index 0000000..f74085f
--- /dev/null
+++ b/packages/rfw/example/remote/android/app/src/main/res/drawable-v21/launch_background.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Modify this file to customize your launch splash screen -->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:drawable="?android:colorBackground" />
+
+    <!-- You can insert your own image assets here -->
+    <!-- <item>
+        <bitmap
+            android:gravity="center"
+            android:src="@mipmap/launch_image" />
+    </item> -->
+</layer-list>
diff --git a/packages/rfw/example/remote/android/app/src/main/res/drawable/launch_background.xml b/packages/rfw/example/remote/android/app/src/main/res/drawable/launch_background.xml
new file mode 100644
index 0000000..304732f
--- /dev/null
+++ b/packages/rfw/example/remote/android/app/src/main/res/drawable/launch_background.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Modify this file to customize your launch splash screen -->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:drawable="@android:color/white" />
+
+    <!-- You can insert your own image assets here -->
+    <!-- <item>
+        <bitmap
+            android:gravity="center"
+            android:src="@mipmap/launch_image" />
+    </item> -->
+</layer-list>
diff --git a/packages/rfw/example/remote/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/packages/rfw/example/remote/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..db77bb4
--- /dev/null
+++ b/packages/rfw/example/remote/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
Binary files differ
diff --git a/packages/rfw/example/remote/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/packages/rfw/example/remote/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..17987b7
--- /dev/null
+++ b/packages/rfw/example/remote/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
Binary files differ
diff --git a/packages/rfw/example/remote/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/packages/rfw/example/remote/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..09d4391
--- /dev/null
+++ b/packages/rfw/example/remote/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
Binary files differ
diff --git a/packages/rfw/example/remote/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/packages/rfw/example/remote/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..d5f1c8d
--- /dev/null
+++ b/packages/rfw/example/remote/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/packages/rfw/example/remote/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/packages/rfw/example/remote/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..4d6372e
--- /dev/null
+++ b/packages/rfw/example/remote/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Binary files differ
diff --git a/packages/rfw/example/remote/android/app/src/main/res/values-night/styles.xml b/packages/rfw/example/remote/android/app/src/main/res/values-night/styles.xml
new file mode 100644
index 0000000..449a9f9
--- /dev/null
+++ b/packages/rfw/example/remote/android/app/src/main/res/values-night/styles.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
+    <style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
+        <!-- Show a splash screen on the activity. Automatically removed when
+             Flutter draws its first frame -->
+        <item name="android:windowBackground">@drawable/launch_background</item>
+    </style>
+    <!-- Theme applied to the Android Window as soon as the process has started.
+         This theme determines the color of the Android Window while your
+         Flutter UI initializes, as well as behind your Flutter UI while its
+         running.
+         
+         This Theme is only used starting with V2 of Flutter's Android embedding. -->
+    <style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
+        <item name="android:windowBackground">?android:colorBackground</item>
+    </style>
+</resources>
diff --git a/packages/rfw/example/remote/android/app/src/main/res/values/styles.xml b/packages/rfw/example/remote/android/app/src/main/res/values/styles.xml
new file mode 100644
index 0000000..d74aa35
--- /dev/null
+++ b/packages/rfw/example/remote/android/app/src/main/res/values/styles.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
+    <style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
+        <!-- Show a splash screen on the activity. Automatically removed when
+             Flutter draws its first frame -->
+        <item name="android:windowBackground">@drawable/launch_background</item>
+    </style>
+    <!-- Theme applied to the Android Window as soon as the process has started.
+         This theme determines the color of the Android Window while your
+         Flutter UI initializes, as well as behind your Flutter UI while its
+         running.
+         
+         This Theme is only used starting with V2 of Flutter's Android embedding. -->
+    <style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
+        <item name="android:windowBackground">?android:colorBackground</item>
+    </style>
+</resources>
diff --git a/packages/rfw/example/remote/android/app/src/profile/AndroidManifest.xml b/packages/rfw/example/remote/android/app/src/profile/AndroidManifest.xml
new file mode 100644
index 0000000..3b7fd23
--- /dev/null
+++ b/packages/rfw/example/remote/android/app/src/profile/AndroidManifest.xml
@@ -0,0 +1,7 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="dev.flutter.rfw.examples.remote">
+    <!-- Flutter needs it to communicate with the running application
+         to allow setting breakpoints, to provide hot reload, etc.
+    -->
+    <uses-permission android:name="android.permission.INTERNET"/>
+</manifest>
diff --git a/packages/rfw/example/remote/android/build.gradle b/packages/rfw/example/remote/android/build.gradle
new file mode 100644
index 0000000..ed45c65
--- /dev/null
+++ b/packages/rfw/example/remote/android/build.gradle
@@ -0,0 +1,29 @@
+buildscript {
+    ext.kotlin_version = '1.3.50'
+    repositories {
+        google()
+        mavenCentral()
+    }
+
+    dependencies {
+        classpath 'com.android.tools.build:gradle:4.1.0'
+        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
+    }
+}
+
+allprojects {
+    repositories {
+        google()
+        mavenCentral()
+    }
+}
+
+rootProject.buildDir = '../build'
+subprojects {
+    project.buildDir = "${rootProject.buildDir}/${project.name}"
+    project.evaluationDependsOn(':app')
+}
+
+task clean(type: Delete) {
+    delete rootProject.buildDir
+}
diff --git a/packages/rfw/example/remote/android/gradle.properties b/packages/rfw/example/remote/android/gradle.properties
new file mode 100644
index 0000000..94adc3a
--- /dev/null
+++ b/packages/rfw/example/remote/android/gradle.properties
@@ -0,0 +1,3 @@
+org.gradle.jvmargs=-Xmx1536M
+android.useAndroidX=true
+android.enableJetifier=true
diff --git a/packages/rfw/example/remote/android/gradle/wrapper/gradle-wrapper.properties b/packages/rfw/example/remote/android/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..bc6a58a
--- /dev/null
+++ b/packages/rfw/example/remote/android/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Fri Jun 23 08:50:38 CEST 2017
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip
diff --git a/packages/rfw/example/remote/android/settings.gradle b/packages/rfw/example/remote/android/settings.gradle
new file mode 100644
index 0000000..44e62bc
--- /dev/null
+++ b/packages/rfw/example/remote/android/settings.gradle
@@ -0,0 +1,11 @@
+include ':app'
+
+def localPropertiesFile = new File(rootProject.projectDir, "local.properties")
+def properties = new Properties()
+
+assert localPropertiesFile.exists()
+localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }
+
+def flutterSdkPath = properties.getProperty("flutter.sdk")
+assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
+apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"
diff --git a/packages/rfw/example/remote/ios/.gitignore b/packages/rfw/example/remote/ios/.gitignore
new file mode 100644
index 0000000..151026b
--- /dev/null
+++ b/packages/rfw/example/remote/ios/.gitignore
@@ -0,0 +1,33 @@
+*.mode1v3
+*.mode2v3
+*.moved-aside
+*.pbxuser
+*.perspectivev3
+**/*sync/
+.sconsign.dblite
+.tags*
+**/.vagrant/
+**/DerivedData/
+Icon?
+**/Pods/
+**/.symlinks/
+profile
+xcuserdata
+**/.generated/
+Flutter/App.framework
+Flutter/Flutter.framework
+Flutter/Flutter.podspec
+Flutter/Generated.xcconfig
+Flutter/ephemeral/
+Flutter/app.flx
+Flutter/app.zip
+Flutter/flutter_assets/
+Flutter/flutter_export_environment.sh
+ServiceDefinitions.json
+Runner/GeneratedPluginRegistrant.*
+
+# Exceptions to above rules.
+!default.mode1v3
+!default.mode2v3
+!default.pbxuser
+!default.perspectivev3
diff --git a/packages/rfw/example/remote/ios/Flutter/AppFrameworkInfo.plist b/packages/rfw/example/remote/ios/Flutter/AppFrameworkInfo.plist
new file mode 100644
index 0000000..8d4492f
--- /dev/null
+++ b/packages/rfw/example/remote/ios/Flutter/AppFrameworkInfo.plist
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+  <key>CFBundleDevelopmentRegion</key>
+  <string>en</string>
+  <key>CFBundleExecutable</key>
+  <string>App</string>
+  <key>CFBundleIdentifier</key>
+  <string>io.flutter.flutter.app</string>
+  <key>CFBundleInfoDictionaryVersion</key>
+  <string>6.0</string>
+  <key>CFBundleName</key>
+  <string>App</string>
+  <key>CFBundlePackageType</key>
+  <string>FMWK</string>
+  <key>CFBundleShortVersionString</key>
+  <string>1.0</string>
+  <key>CFBundleSignature</key>
+  <string>????</string>
+  <key>CFBundleVersion</key>
+  <string>1.0</string>
+  <key>MinimumOSVersion</key>
+  <string>9.0</string>
+</dict>
+</plist>
diff --git a/packages/rfw/example/remote/ios/Flutter/Debug.xcconfig b/packages/rfw/example/remote/ios/Flutter/Debug.xcconfig
new file mode 100644
index 0000000..592ceee
--- /dev/null
+++ b/packages/rfw/example/remote/ios/Flutter/Debug.xcconfig
@@ -0,0 +1 @@
+#include "Generated.xcconfig"
diff --git a/packages/rfw/example/remote/ios/Flutter/Release.xcconfig b/packages/rfw/example/remote/ios/Flutter/Release.xcconfig
new file mode 100644
index 0000000..592ceee
--- /dev/null
+++ b/packages/rfw/example/remote/ios/Flutter/Release.xcconfig
@@ -0,0 +1 @@
+#include "Generated.xcconfig"
diff --git a/packages/rfw/example/remote/ios/Runner.xcodeproj/project.pbxproj b/packages/rfw/example/remote/ios/Runner.xcodeproj/project.pbxproj
new file mode 100644
index 0000000..758c2d3
--- /dev/null
+++ b/packages/rfw/example/remote/ios/Runner.xcodeproj/project.pbxproj
@@ -0,0 +1,481 @@
+// !$*UTF8*$!
+{
+	archiveVersion = 1;
+	classes = {
+	};
+	objectVersion = 50;
+	objects = {
+
+/* Begin PBXBuildFile section */
+		1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
+		3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
+		74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
+		97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
+		97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
+		97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXCopyFilesBuildPhase section */
+		9705A1C41CF9048500538489 /* Embed Frameworks */ = {
+			isa = PBXCopyFilesBuildPhase;
+			buildActionMask = 2147483647;
+			dstPath = "";
+			dstSubfolderSpec = 10;
+			files = (
+			);
+			name = "Embed Frameworks";
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXCopyFilesBuildPhase section */
+
+/* Begin PBXFileReference section */
+		1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
+		1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
+		3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
+		74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
+		74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
+		7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
+		9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
+		9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
+		97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
+		97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
+		97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
+		97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
+		97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+		97C146EB1CF9000F007C117D /* Frameworks */ = {
+			isa = PBXFrameworksBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+		9740EEB11CF90186004384FC /* Flutter */ = {
+			isa = PBXGroup;
+			children = (
+				3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
+				9740EEB21CF90195004384FC /* Debug.xcconfig */,
+				7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
+				9740EEB31CF90195004384FC /* Generated.xcconfig */,
+			);
+			name = Flutter;
+			sourceTree = "<group>";
+		};
+		97C146E51CF9000F007C117D = {
+			isa = PBXGroup;
+			children = (
+				9740EEB11CF90186004384FC /* Flutter */,
+				97C146F01CF9000F007C117D /* Runner */,
+				97C146EF1CF9000F007C117D /* Products */,
+			);
+			sourceTree = "<group>";
+		};
+		97C146EF1CF9000F007C117D /* Products */ = {
+			isa = PBXGroup;
+			children = (
+				97C146EE1CF9000F007C117D /* Runner.app */,
+			);
+			name = Products;
+			sourceTree = "<group>";
+		};
+		97C146F01CF9000F007C117D /* Runner */ = {
+			isa = PBXGroup;
+			children = (
+				97C146FA1CF9000F007C117D /* Main.storyboard */,
+				97C146FD1CF9000F007C117D /* Assets.xcassets */,
+				97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
+				97C147021CF9000F007C117D /* Info.plist */,
+				1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
+				1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
+				74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
+				74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
+			);
+			path = Runner;
+			sourceTree = "<group>";
+		};
+/* End PBXGroup section */
+
+/* Begin PBXNativeTarget section */
+		97C146ED1CF9000F007C117D /* Runner */ = {
+			isa = PBXNativeTarget;
+			buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
+			buildPhases = (
+				9740EEB61CF901F6004384FC /* Run Script */,
+				97C146EA1CF9000F007C117D /* Sources */,
+				97C146EB1CF9000F007C117D /* Frameworks */,
+				97C146EC1CF9000F007C117D /* Resources */,
+				9705A1C41CF9048500538489 /* Embed Frameworks */,
+				3B06AD1E1E4923F5004D2608 /* Thin Binary */,
+			);
+			buildRules = (
+			);
+			dependencies = (
+			);
+			name = Runner;
+			productName = Runner;
+			productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
+			productType = "com.apple.product-type.application";
+		};
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+		97C146E61CF9000F007C117D /* Project object */ = {
+			isa = PBXProject;
+			attributes = {
+				LastUpgradeCheck = 1300;
+				ORGANIZATIONNAME = "";
+				TargetAttributes = {
+					97C146ED1CF9000F007C117D = {
+						CreatedOnToolsVersion = 7.3.1;
+						LastSwiftMigration = 1100;
+					};
+				};
+			};
+			buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
+			compatibilityVersion = "Xcode 9.3";
+			developmentRegion = en;
+			hasScannedForEncodings = 0;
+			knownRegions = (
+				en,
+				Base,
+			);
+			mainGroup = 97C146E51CF9000F007C117D;
+			productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
+			projectDirPath = "";
+			projectRoot = "";
+			targets = (
+				97C146ED1CF9000F007C117D /* Runner */,
+			);
+		};
+/* End PBXProject section */
+
+/* Begin PBXResourcesBuildPhase section */
+		97C146EC1CF9000F007C117D /* Resources */ = {
+			isa = PBXResourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
+				3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
+				97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
+				97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXResourcesBuildPhase section */
+
+/* Begin PBXShellScriptBuildPhase section */
+		3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
+			isa = PBXShellScriptBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			inputPaths = (
+			);
+			name = "Thin Binary";
+			outputPaths = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+			shellPath = /bin/sh;
+			shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
+		};
+		9740EEB61CF901F6004384FC /* Run Script */ = {
+			isa = PBXShellScriptBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			inputPaths = (
+			);
+			name = "Run Script";
+			outputPaths = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+			shellPath = /bin/sh;
+			shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
+		};
+/* End PBXShellScriptBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+		97C146EA1CF9000F007C117D /* Sources */ = {
+			isa = PBXSourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
+				1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXSourcesBuildPhase section */
+
+/* Begin PBXVariantGroup section */
+		97C146FA1CF9000F007C117D /* Main.storyboard */ = {
+			isa = PBXVariantGroup;
+			children = (
+				97C146FB1CF9000F007C117D /* Base */,
+			);
+			name = Main.storyboard;
+			sourceTree = "<group>";
+		};
+		97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
+			isa = PBXVariantGroup;
+			children = (
+				97C147001CF9000F007C117D /* Base */,
+			);
+			name = LaunchScreen.storyboard;
+			sourceTree = "<group>";
+		};
+/* End PBXVariantGroup section */
+
+/* Begin XCBuildConfiguration section */
+		249021D3217E4FDB00AE95B9 /* Profile */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				ALWAYS_SEARCH_USER_PATHS = NO;
+				CLANG_ANALYZER_NONNULL = YES;
+				CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+				CLANG_CXX_LIBRARY = "libc++";
+				CLANG_ENABLE_MODULES = YES;
+				CLANG_ENABLE_OBJC_ARC = YES;
+				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+				CLANG_WARN_BOOL_CONVERSION = YES;
+				CLANG_WARN_COMMA = YES;
+				CLANG_WARN_CONSTANT_CONVERSION = YES;
+				CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+				CLANG_WARN_EMPTY_BODY = YES;
+				CLANG_WARN_ENUM_CONVERSION = YES;
+				CLANG_WARN_INFINITE_RECURSION = YES;
+				CLANG_WARN_INT_CONVERSION = YES;
+				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+				CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+				CLANG_WARN_STRICT_PROTOTYPES = YES;
+				CLANG_WARN_SUSPICIOUS_MOVE = YES;
+				CLANG_WARN_UNREACHABLE_CODE = YES;
+				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+				"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+				COPY_PHASE_STRIP = NO;
+				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+				ENABLE_NS_ASSERTIONS = NO;
+				ENABLE_STRICT_OBJC_MSGSEND = YES;
+				GCC_C_LANGUAGE_STANDARD = gnu99;
+				GCC_NO_COMMON_BLOCKS = YES;
+				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+				GCC_WARN_UNDECLARED_SELECTOR = YES;
+				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+				GCC_WARN_UNUSED_FUNCTION = YES;
+				GCC_WARN_UNUSED_VARIABLE = YES;
+				IPHONEOS_DEPLOYMENT_TARGET = 9.0;
+				MTL_ENABLE_DEBUG_INFO = NO;
+				SDKROOT = iphoneos;
+				SUPPORTED_PLATFORMS = iphoneos;
+				TARGETED_DEVICE_FAMILY = "1,2";
+				VALIDATE_PRODUCT = YES;
+			};
+			name = Profile;
+		};
+		249021D4217E4FDB00AE95B9 /* Profile */ = {
+			isa = XCBuildConfiguration;
+			baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
+			buildSettings = {
+				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+				CLANG_ENABLE_MODULES = YES;
+				CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
+				ENABLE_BITCODE = NO;
+				INFOPLIST_FILE = Runner/Info.plist;
+				LD_RUNPATH_SEARCH_PATHS = (
+					"$(inherited)",
+					"@executable_path/Frameworks",
+				);
+				PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.rfw.examples.remote;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+				SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
+				SWIFT_VERSION = 5.0;
+				VERSIONING_SYSTEM = "apple-generic";
+			};
+			name = Profile;
+		};
+		97C147031CF9000F007C117D /* Debug */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				ALWAYS_SEARCH_USER_PATHS = NO;
+				CLANG_ANALYZER_NONNULL = YES;
+				CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+				CLANG_CXX_LIBRARY = "libc++";
+				CLANG_ENABLE_MODULES = YES;
+				CLANG_ENABLE_OBJC_ARC = YES;
+				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+				CLANG_WARN_BOOL_CONVERSION = YES;
+				CLANG_WARN_COMMA = YES;
+				CLANG_WARN_CONSTANT_CONVERSION = YES;
+				CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+				CLANG_WARN_EMPTY_BODY = YES;
+				CLANG_WARN_ENUM_CONVERSION = YES;
+				CLANG_WARN_INFINITE_RECURSION = YES;
+				CLANG_WARN_INT_CONVERSION = YES;
+				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+				CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+				CLANG_WARN_STRICT_PROTOTYPES = YES;
+				CLANG_WARN_SUSPICIOUS_MOVE = YES;
+				CLANG_WARN_UNREACHABLE_CODE = YES;
+				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+				"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+				COPY_PHASE_STRIP = NO;
+				DEBUG_INFORMATION_FORMAT = dwarf;
+				ENABLE_STRICT_OBJC_MSGSEND = YES;
+				ENABLE_TESTABILITY = YES;
+				GCC_C_LANGUAGE_STANDARD = gnu99;
+				GCC_DYNAMIC_NO_PIC = NO;
+				GCC_NO_COMMON_BLOCKS = YES;
+				GCC_OPTIMIZATION_LEVEL = 0;
+				GCC_PREPROCESSOR_DEFINITIONS = (
+					"DEBUG=1",
+					"$(inherited)",
+				);
+				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+				GCC_WARN_UNDECLARED_SELECTOR = YES;
+				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+				GCC_WARN_UNUSED_FUNCTION = YES;
+				GCC_WARN_UNUSED_VARIABLE = YES;
+				IPHONEOS_DEPLOYMENT_TARGET = 9.0;
+				MTL_ENABLE_DEBUG_INFO = YES;
+				ONLY_ACTIVE_ARCH = YES;
+				SDKROOT = iphoneos;
+				TARGETED_DEVICE_FAMILY = "1,2";
+			};
+			name = Debug;
+		};
+		97C147041CF9000F007C117D /* Release */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				ALWAYS_SEARCH_USER_PATHS = NO;
+				CLANG_ANALYZER_NONNULL = YES;
+				CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+				CLANG_CXX_LIBRARY = "libc++";
+				CLANG_ENABLE_MODULES = YES;
+				CLANG_ENABLE_OBJC_ARC = YES;
+				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+				CLANG_WARN_BOOL_CONVERSION = YES;
+				CLANG_WARN_COMMA = YES;
+				CLANG_WARN_CONSTANT_CONVERSION = YES;
+				CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+				CLANG_WARN_EMPTY_BODY = YES;
+				CLANG_WARN_ENUM_CONVERSION = YES;
+				CLANG_WARN_INFINITE_RECURSION = YES;
+				CLANG_WARN_INT_CONVERSION = YES;
+				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+				CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+				CLANG_WARN_STRICT_PROTOTYPES = YES;
+				CLANG_WARN_SUSPICIOUS_MOVE = YES;
+				CLANG_WARN_UNREACHABLE_CODE = YES;
+				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+				"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+				COPY_PHASE_STRIP = NO;
+				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+				ENABLE_NS_ASSERTIONS = NO;
+				ENABLE_STRICT_OBJC_MSGSEND = YES;
+				GCC_C_LANGUAGE_STANDARD = gnu99;
+				GCC_NO_COMMON_BLOCKS = YES;
+				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+				GCC_WARN_UNDECLARED_SELECTOR = YES;
+				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+				GCC_WARN_UNUSED_FUNCTION = YES;
+				GCC_WARN_UNUSED_VARIABLE = YES;
+				IPHONEOS_DEPLOYMENT_TARGET = 9.0;
+				MTL_ENABLE_DEBUG_INFO = NO;
+				SDKROOT = iphoneos;
+				SUPPORTED_PLATFORMS = iphoneos;
+				SWIFT_COMPILATION_MODE = wholemodule;
+				SWIFT_OPTIMIZATION_LEVEL = "-O";
+				TARGETED_DEVICE_FAMILY = "1,2";
+				VALIDATE_PRODUCT = YES;
+			};
+			name = Release;
+		};
+		97C147061CF9000F007C117D /* Debug */ = {
+			isa = XCBuildConfiguration;
+			baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
+			buildSettings = {
+				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+				CLANG_ENABLE_MODULES = YES;
+				CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
+				ENABLE_BITCODE = NO;
+				INFOPLIST_FILE = Runner/Info.plist;
+				LD_RUNPATH_SEARCH_PATHS = (
+					"$(inherited)",
+					"@executable_path/Frameworks",
+				);
+				PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.rfw.examples.remote;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+				SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
+				SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+				SWIFT_VERSION = 5.0;
+				VERSIONING_SYSTEM = "apple-generic";
+			};
+			name = Debug;
+		};
+		97C147071CF9000F007C117D /* Release */ = {
+			isa = XCBuildConfiguration;
+			baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
+			buildSettings = {
+				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+				CLANG_ENABLE_MODULES = YES;
+				CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
+				ENABLE_BITCODE = NO;
+				INFOPLIST_FILE = Runner/Info.plist;
+				LD_RUNPATH_SEARCH_PATHS = (
+					"$(inherited)",
+					"@executable_path/Frameworks",
+				);
+				PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.rfw.examples.remote;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+				SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
+				SWIFT_VERSION = 5.0;
+				VERSIONING_SYSTEM = "apple-generic";
+			};
+			name = Release;
+		};
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+		97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				97C147031CF9000F007C117D /* Debug */,
+				97C147041CF9000F007C117D /* Release */,
+				249021D3217E4FDB00AE95B9 /* Profile */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
+		};
+		97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				97C147061CF9000F007C117D /* Debug */,
+				97C147071CF9000F007C117D /* Release */,
+				249021D4217E4FDB00AE95B9 /* Profile */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
+		};
+/* End XCConfigurationList section */
+	};
+	rootObject = 97C146E61CF9000F007C117D /* Project object */;
+}
diff --git a/packages/rfw/example/remote/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/packages/rfw/example/remote/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 0000000..919434a
--- /dev/null
+++ b/packages/rfw/example/remote/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Workspace
+   version = "1.0">
+   <FileRef
+      location = "self:">
+   </FileRef>
+</Workspace>
diff --git a/packages/rfw/example/remote/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/rfw/example/remote/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
new file mode 100644
index 0000000..18d9810
--- /dev/null
+++ b/packages/rfw/example/remote/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>IDEDidComputeMac32BitWarning</key>
+	<true/>
+</dict>
+</plist>
diff --git a/packages/rfw/example/remote/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/rfw/example/remote/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
new file mode 100644
index 0000000..f9b0d7c
--- /dev/null
+++ b/packages/rfw/example/remote/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>PreviewsEnabled</key>
+	<false/>
+</dict>
+</plist>
diff --git a/packages/rfw/example/remote/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/rfw/example/remote/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
new file mode 100644
index 0000000..c87d15a
--- /dev/null
+++ b/packages/rfw/example/remote/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
@@ -0,0 +1,87 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Scheme
+   LastUpgradeVersion = "1300"
+   version = "1.3">
+   <BuildAction
+      parallelizeBuildables = "YES"
+      buildImplicitDependencies = "YES">
+      <BuildActionEntries>
+         <BuildActionEntry
+            buildForTesting = "YES"
+            buildForRunning = "YES"
+            buildForProfiling = "YES"
+            buildForArchiving = "YES"
+            buildForAnalyzing = "YES">
+            <BuildableReference
+               BuildableIdentifier = "primary"
+               BlueprintIdentifier = "97C146ED1CF9000F007C117D"
+               BuildableName = "Runner.app"
+               BlueprintName = "Runner"
+               ReferencedContainer = "container:Runner.xcodeproj">
+            </BuildableReference>
+         </BuildActionEntry>
+      </BuildActionEntries>
+   </BuildAction>
+   <TestAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      shouldUseLaunchSchemeArgsEnv = "YES">
+      <MacroExpansion>
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "97C146ED1CF9000F007C117D"
+            BuildableName = "Runner.app"
+            BlueprintName = "Runner"
+            ReferencedContainer = "container:Runner.xcodeproj">
+         </BuildableReference>
+      </MacroExpansion>
+      <Testables>
+      </Testables>
+   </TestAction>
+   <LaunchAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      launchStyle = "0"
+      useCustomWorkingDirectory = "NO"
+      ignoresPersistentStateOnLaunch = "NO"
+      debugDocumentVersioning = "YES"
+      debugServiceExtension = "internal"
+      allowLocationSimulation = "YES">
+      <BuildableProductRunnable
+         runnableDebuggingMode = "0">
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "97C146ED1CF9000F007C117D"
+            BuildableName = "Runner.app"
+            BlueprintName = "Runner"
+            ReferencedContainer = "container:Runner.xcodeproj">
+         </BuildableReference>
+      </BuildableProductRunnable>
+   </LaunchAction>
+   <ProfileAction
+      buildConfiguration = "Profile"
+      shouldUseLaunchSchemeArgsEnv = "YES"
+      savedToolIdentifier = ""
+      useCustomWorkingDirectory = "NO"
+      debugDocumentVersioning = "YES">
+      <BuildableProductRunnable
+         runnableDebuggingMode = "0">
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "97C146ED1CF9000F007C117D"
+            BuildableName = "Runner.app"
+            BlueprintName = "Runner"
+            ReferencedContainer = "container:Runner.xcodeproj">
+         </BuildableReference>
+      </BuildableProductRunnable>
+   </ProfileAction>
+   <AnalyzeAction
+      buildConfiguration = "Debug">
+   </AnalyzeAction>
+   <ArchiveAction
+      buildConfiguration = "Release"
+      revealArchiveInOrganizer = "YES">
+   </ArchiveAction>
+</Scheme>
diff --git a/packages/rfw/example/remote/ios/Runner.xcworkspace/contents.xcworkspacedata b/packages/rfw/example/remote/ios/Runner.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 0000000..1d526a1
--- /dev/null
+++ b/packages/rfw/example/remote/ios/Runner.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Workspace
+   version = "1.0">
+   <FileRef
+      location = "group:Runner.xcodeproj">
+   </FileRef>
+</Workspace>
diff --git a/packages/rfw/example/remote/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/rfw/example/remote/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
new file mode 100644
index 0000000..18d9810
--- /dev/null
+++ b/packages/rfw/example/remote/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>IDEDidComputeMac32BitWarning</key>
+	<true/>
+</dict>
+</plist>
diff --git a/packages/rfw/example/remote/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/rfw/example/remote/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
new file mode 100644
index 0000000..f9b0d7c
--- /dev/null
+++ b/packages/rfw/example/remote/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>PreviewsEnabled</key>
+	<false/>
+</dict>
+</plist>
diff --git a/packages/rfw/example/remote/ios/Runner/AppDelegate.swift b/packages/rfw/example/remote/ios/Runner/AppDelegate.swift
new file mode 100644
index 0000000..caf9983
--- /dev/null
+++ b/packages/rfw/example/remote/ios/Runner/AppDelegate.swift
@@ -0,0 +1,17 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import UIKit
+import Flutter
+
+@UIApplicationMain
+@objc class AppDelegate: FlutterAppDelegate {
+  override func application(
+    _ application: UIApplication,
+    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
+  ) -> Bool {
+    GeneratedPluginRegistrant.register(with: self)
+    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
+  }
+}
diff --git a/packages/rfw/example/remote/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/packages/rfw/example/remote/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
new file mode 100644
index 0000000..d36b1fa
--- /dev/null
+++ b/packages/rfw/example/remote/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -0,0 +1,122 @@
+{
+  "images" : [
+    {
+      "size" : "20x20",
+      "idiom" : "iphone",
+      "filename" : "Icon-App-20x20@2x.png",
+      "scale" : "2x"
+    },
+    {
+      "size" : "20x20",
+      "idiom" : "iphone",
+      "filename" : "Icon-App-20x20@3x.png",
+      "scale" : "3x"
+    },
+    {
+      "size" : "29x29",
+      "idiom" : "iphone",
+      "filename" : "Icon-App-29x29@1x.png",
+      "scale" : "1x"
+    },
+    {
+      "size" : "29x29",
+      "idiom" : "iphone",
+      "filename" : "Icon-App-29x29@2x.png",
+      "scale" : "2x"
+    },
+    {
+      "size" : "29x29",
+      "idiom" : "iphone",
+      "filename" : "Icon-App-29x29@3x.png",
+      "scale" : "3x"
+    },
+    {
+      "size" : "40x40",
+      "idiom" : "iphone",
+      "filename" : "Icon-App-40x40@2x.png",
+      "scale" : "2x"
+    },
+    {
+      "size" : "40x40",
+      "idiom" : "iphone",
+      "filename" : "Icon-App-40x40@3x.png",
+      "scale" : "3x"
+    },
+    {
+      "size" : "60x60",
+      "idiom" : "iphone",
+      "filename" : "Icon-App-60x60@2x.png",
+      "scale" : "2x"
+    },
+    {
+      "size" : "60x60",
+      "idiom" : "iphone",
+      "filename" : "Icon-App-60x60@3x.png",
+      "scale" : "3x"
+    },
+    {
+      "size" : "20x20",
+      "idiom" : "ipad",
+      "filename" : "Icon-App-20x20@1x.png",
+      "scale" : "1x"
+    },
+    {
+      "size" : "20x20",
+      "idiom" : "ipad",
+      "filename" : "Icon-App-20x20@2x.png",
+      "scale" : "2x"
+    },
+    {
+      "size" : "29x29",
+      "idiom" : "ipad",
+      "filename" : "Icon-App-29x29@1x.png",
+      "scale" : "1x"
+    },
+    {
+      "size" : "29x29",
+      "idiom" : "ipad",
+      "filename" : "Icon-App-29x29@2x.png",
+      "scale" : "2x"
+    },
+    {
+      "size" : "40x40",
+      "idiom" : "ipad",
+      "filename" : "Icon-App-40x40@1x.png",
+      "scale" : "1x"
+    },
+    {
+      "size" : "40x40",
+      "idiom" : "ipad",
+      "filename" : "Icon-App-40x40@2x.png",
+      "scale" : "2x"
+    },
+    {
+      "size" : "76x76",
+      "idiom" : "ipad",
+      "filename" : "Icon-App-76x76@1x.png",
+      "scale" : "1x"
+    },
+    {
+      "size" : "76x76",
+      "idiom" : "ipad",
+      "filename" : "Icon-App-76x76@2x.png",
+      "scale" : "2x"
+    },
+    {
+      "size" : "83.5x83.5",
+      "idiom" : "ipad",
+      "filename" : "Icon-App-83.5x83.5@2x.png",
+      "scale" : "2x"
+    },
+    {
+      "size" : "1024x1024",
+      "idiom" : "ios-marketing",
+      "filename" : "Icon-App-1024x1024@1x.png",
+      "scale" : "1x"
+    }
+  ],
+  "info" : {
+    "version" : 1,
+    "author" : "xcode"
+  }
+}
diff --git a/packages/rfw/example/remote/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/packages/rfw/example/remote/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
new file mode 100644
index 0000000..dc9ada4
--- /dev/null
+++ b/packages/rfw/example/remote/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
Binary files differ
diff --git a/packages/rfw/example/remote/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/packages/rfw/example/remote/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
new file mode 100644
index 0000000..28c6bf0
--- /dev/null
+++ b/packages/rfw/example/remote/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
Binary files differ
diff --git a/packages/rfw/example/remote/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/packages/rfw/example/remote/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
new file mode 100644
index 0000000..2ccbfd9
--- /dev/null
+++ b/packages/rfw/example/remote/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
Binary files differ
diff --git a/packages/rfw/example/remote/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/packages/rfw/example/remote/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
new file mode 100644
index 0000000..f091b6b
--- /dev/null
+++ b/packages/rfw/example/remote/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
Binary files differ
diff --git a/packages/rfw/example/remote/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/packages/rfw/example/remote/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
new file mode 100644
index 0000000..4cde121
--- /dev/null
+++ b/packages/rfw/example/remote/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
Binary files differ
diff --git a/packages/rfw/example/remote/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/packages/rfw/example/remote/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
new file mode 100644
index 0000000..d0ef06e
--- /dev/null
+++ b/packages/rfw/example/remote/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
Binary files differ
diff --git a/packages/rfw/example/remote/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/packages/rfw/example/remote/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
new file mode 100644
index 0000000..dcdc230
--- /dev/null
+++ b/packages/rfw/example/remote/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
Binary files differ
diff --git a/packages/rfw/example/remote/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/packages/rfw/example/remote/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
new file mode 100644
index 0000000..2ccbfd9
--- /dev/null
+++ b/packages/rfw/example/remote/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
Binary files differ
diff --git a/packages/rfw/example/remote/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/packages/rfw/example/remote/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
new file mode 100644
index 0000000..c8f9ed8
--- /dev/null
+++ b/packages/rfw/example/remote/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
Binary files differ
diff --git a/packages/rfw/example/remote/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/packages/rfw/example/remote/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
new file mode 100644
index 0000000..a6d6b86
--- /dev/null
+++ b/packages/rfw/example/remote/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
Binary files differ
diff --git a/packages/rfw/example/remote/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/packages/rfw/example/remote/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
new file mode 100644
index 0000000..a6d6b86
--- /dev/null
+++ b/packages/rfw/example/remote/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
Binary files differ
diff --git a/packages/rfw/example/remote/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/packages/rfw/example/remote/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
new file mode 100644
index 0000000..75b2d16
--- /dev/null
+++ b/packages/rfw/example/remote/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
Binary files differ
diff --git a/packages/rfw/example/remote/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/packages/rfw/example/remote/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
new file mode 100644
index 0000000..c4df70d
--- /dev/null
+++ b/packages/rfw/example/remote/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
Binary files differ
diff --git a/packages/rfw/example/remote/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/packages/rfw/example/remote/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
new file mode 100644
index 0000000..6a84f41
--- /dev/null
+++ b/packages/rfw/example/remote/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
Binary files differ
diff --git a/packages/rfw/example/remote/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/packages/rfw/example/remote/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
new file mode 100644
index 0000000..d0e1f58
--- /dev/null
+++ b/packages/rfw/example/remote/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
Binary files differ
diff --git a/packages/rfw/example/remote/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/packages/rfw/example/remote/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json
new file mode 100644
index 0000000..0bedcf2
--- /dev/null
+++ b/packages/rfw/example/remote/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json
@@ -0,0 +1,23 @@
+{
+  "images" : [
+    {
+      "idiom" : "universal",
+      "filename" : "LaunchImage.png",
+      "scale" : "1x"
+    },
+    {
+      "idiom" : "universal",
+      "filename" : "LaunchImage@2x.png",
+      "scale" : "2x"
+    },
+    {
+      "idiom" : "universal",
+      "filename" : "LaunchImage@3x.png",
+      "scale" : "3x"
+    }
+  ],
+  "info" : {
+    "version" : 1,
+    "author" : "xcode"
+  }
+}
diff --git a/packages/rfw/example/remote/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/packages/rfw/example/remote/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
new file mode 100644
index 0000000..9da19ea
--- /dev/null
+++ b/packages/rfw/example/remote/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
Binary files differ
diff --git a/packages/rfw/example/remote/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/packages/rfw/example/remote/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
new file mode 100644
index 0000000..9da19ea
--- /dev/null
+++ b/packages/rfw/example/remote/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
Binary files differ
diff --git a/packages/rfw/example/remote/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/packages/rfw/example/remote/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
new file mode 100644
index 0000000..9da19ea
--- /dev/null
+++ b/packages/rfw/example/remote/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
Binary files differ
diff --git a/packages/rfw/example/remote/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/packages/rfw/example/remote/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md
new file mode 100644
index 0000000..89c2725
--- /dev/null
+++ b/packages/rfw/example/remote/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md
@@ -0,0 +1,5 @@
+# Launch Screen Assets
+
+You can customize the launch screen with your own desired assets by replacing the image files in this directory.
+
+You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.
\ No newline at end of file
diff --git a/packages/rfw/example/remote/ios/Runner/Base.lproj/LaunchScreen.storyboard b/packages/rfw/example/remote/ios/Runner/Base.lproj/LaunchScreen.storyboard
new file mode 100644
index 0000000..f2e259c
--- /dev/null
+++ b/packages/rfw/example/remote/ios/Runner/Base.lproj/LaunchScreen.storyboard
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16G29" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
+    <dependencies>
+        <deployment identifier="iOS"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/>
+    </dependencies>
+    <scenes>
+        <!--View Controller-->
+        <scene sceneID="EHf-IW-A2E">
+            <objects>
+                <viewController id="01J-lp-oVM" sceneMemberID="viewController">
+                    <layoutGuides>
+                        <viewControllerLayoutGuide type="top" id="Ydg-fD-yQy"/>
+                        <viewControllerLayoutGuide type="bottom" id="xbc-2k-c8Z"/>
+                    </layoutGuides>
+                    <view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
+                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+                        <subviews>
+                            <imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4">
+                            </imageView>
+                        </subviews>
+                        <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+                        <constraints>
+                            <constraint firstItem="YRO-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/>
+                            <constraint firstItem="YRO-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4X2-HB-R7a"/>
+                        </constraints>
+                    </view>
+                </viewController>
+                <placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
+            </objects>
+            <point key="canvasLocation" x="53" y="375"/>
+        </scene>
+    </scenes>
+    <resources>
+        <image name="LaunchImage" width="168" height="185"/>
+    </resources>
+</document>
diff --git a/packages/rfw/example/remote/ios/Runner/Base.lproj/Main.storyboard b/packages/rfw/example/remote/ios/Runner/Base.lproj/Main.storyboard
new file mode 100644
index 0000000..f3c2851
--- /dev/null
+++ b/packages/rfw/example/remote/ios/Runner/Base.lproj/Main.storyboard
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
+    <dependencies>
+        <deployment identifier="iOS"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
+    </dependencies>
+    <scenes>
+        <!--Flutter View Controller-->
+        <scene sceneID="tne-QT-ifu">
+            <objects>
+                <viewController id="BYZ-38-t0r" customClass="FlutterViewController" sceneMemberID="viewController">
+                    <layoutGuides>
+                        <viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
+                        <viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
+                    </layoutGuides>
+                    <view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
+                        <rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
+                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+                        <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
+                    </view>
+                </viewController>
+                <placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
+            </objects>
+        </scene>
+    </scenes>
+</document>
diff --git a/packages/rfw/example/remote/ios/Runner/Info.plist b/packages/rfw/example/remote/ios/Runner/Info.plist
new file mode 100644
index 0000000..dcfd841
--- /dev/null
+++ b/packages/rfw/example/remote/ios/Runner/Info.plist
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>CFBundleDevelopmentRegion</key>
+	<string>$(DEVELOPMENT_LANGUAGE)</string>
+	<key>CFBundleExecutable</key>
+	<string>$(EXECUTABLE_NAME)</string>
+	<key>CFBundleIdentifier</key>
+	<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
+	<key>CFBundleInfoDictionaryVersion</key>
+	<string>6.0</string>
+	<key>CFBundleName</key>
+	<string>remote</string>
+	<key>CFBundlePackageType</key>
+	<string>APPL</string>
+	<key>CFBundleShortVersionString</key>
+	<string>$(FLUTTER_BUILD_NAME)</string>
+	<key>CFBundleSignature</key>
+	<string>????</string>
+	<key>CFBundleVersion</key>
+	<string>$(FLUTTER_BUILD_NUMBER)</string>
+	<key>LSRequiresIPhoneOS</key>
+	<true/>
+	<key>UILaunchStoryboardName</key>
+	<string>LaunchScreen</string>
+	<key>UIMainStoryboardFile</key>
+	<string>Main</string>
+	<key>UISupportedInterfaceOrientations</key>
+	<array>
+		<string>UIInterfaceOrientationPortrait</string>
+		<string>UIInterfaceOrientationLandscapeLeft</string>
+		<string>UIInterfaceOrientationLandscapeRight</string>
+	</array>
+	<key>UISupportedInterfaceOrientations~ipad</key>
+	<array>
+		<string>UIInterfaceOrientationPortrait</string>
+		<string>UIInterfaceOrientationPortraitUpsideDown</string>
+		<string>UIInterfaceOrientationLandscapeLeft</string>
+		<string>UIInterfaceOrientationLandscapeRight</string>
+	</array>
+	<key>UIViewControllerBasedStatusBarAppearance</key>
+	<false/>
+</dict>
+</plist>
diff --git a/packages/rfw/example/remote/ios/Runner/Runner-Bridging-Header.h b/packages/rfw/example/remote/ios/Runner/Runner-Bridging-Header.h
new file mode 100644
index 0000000..eb7e8ba
--- /dev/null
+++ b/packages/rfw/example/remote/ios/Runner/Runner-Bridging-Header.h
@@ -0,0 +1,5 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "GeneratedPluginRegistrant.h"
diff --git a/packages/rfw/example/remote/lib/main.dart b/packages/rfw/example/remote/lib/main.dart
new file mode 100644
index 0000000..7255d42
--- /dev/null
+++ b/packages/rfw/example/remote/lib/main.dart
@@ -0,0 +1,112 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This file is hand-formatted.
+
+import 'dart:io';
+
+import 'package:flutter/material.dart';
+import 'package:path/path.dart' as path;
+import 'package:path_provider/path_provider.dart';
+import 'package:rfw/rfw.dart';
+
+const String urlPrefix = 'https://raw.githubusercontent.com/flutter/packages/master/packages/rfw/example/remote/remote_widget_libraries';
+
+void main() {
+  runApp(const MaterialApp(home: Example()));
+}
+
+class Example extends StatefulWidget {
+  const Example({Key? key}) : super(key: key);
+
+  @override
+  State<Example> createState() => _ExampleState();
+}
+
+class _ExampleState extends State<Example> {
+  final Runtime _runtime = Runtime();
+  final DynamicContent _data = DynamicContent();
+
+  bool _ready = false;
+  int _counter = 0;
+
+  @override
+  void initState() {
+    super.initState();
+    _runtime.update(const LibraryName(<String>['core', 'widgets']), createCoreWidgets());
+    _runtime.update(const LibraryName(<String>['core', 'material']), createMaterialWidgets());
+    _updateData();
+    _updateWidgets();
+  }
+
+  void _updateData() {
+    _data.update('counter', _counter.toString());
+  }
+
+  Future<void> _updateWidgets() async {
+    final Directory home = await getApplicationSupportDirectory();
+    final File settingsFile = File(path.join(home.path, 'settings.txt'));
+    String nextFile = 'counter_app1.rfw';
+    if (settingsFile.existsSync()) {
+      final String settings = await settingsFile.readAsString();
+      if (settings == nextFile) {
+        nextFile = 'counter_app2.rfw';
+      }
+    }
+    final File currentFile = File(path.join(home.path, 'current.rfw'));
+    if (currentFile.existsSync()) {
+      try {
+        _runtime.update(const LibraryName(<String>['main']), decodeLibraryBlob(await currentFile.readAsBytes()));
+        setState(() {
+          _ready = true;
+        });
+      } catch (e, stack) {
+        FlutterError.reportError(FlutterErrorDetails(exception: e, stack: stack));
+      }
+    }
+    print('Fetching: $urlPrefix/$nextFile');
+    final HttpClientResponse client = await (await HttpClient().getUrl(Uri.parse('$urlPrefix/$nextFile'))).close();
+    await currentFile.writeAsBytes(await client.expand((List<int> chunk) => chunk).toList());
+    await settingsFile.writeAsString(nextFile);
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    final Widget result;
+    if (_ready) {
+      result = RemoteWidget(
+        runtime: _runtime,
+        data: _data,
+        widget: const FullyQualifiedWidgetName(LibraryName(<String>['main']), 'Counter'),
+        onEvent: (String name, DynamicMap arguments) {
+          if (name == 'increment') {
+            _counter += 1;
+            _updateData();
+          }
+        },
+      );
+    } else {
+      result = Material(
+        child: SafeArea(
+          child: Padding(
+            padding: const EdgeInsets.all(20.0),
+            child: Column(
+              crossAxisAlignment: CrossAxisAlignment.stretch,
+              children: const <Widget>[
+                Padding(padding: EdgeInsets.only(right: 100.0), child: Text('REMOTE', textAlign: TextAlign.center, style: TextStyle(letterSpacing: 12.0))),
+                Expanded(child: DecoratedBox(decoration: FlutterLogoDecoration(style: FlutterLogoStyle.horizontal))),
+                Padding(padding: EdgeInsets.only(left: 100.0), child: Text('WIDGETS', textAlign: TextAlign.center, style: TextStyle(letterSpacing: 12.0))),
+                Spacer(),
+                Expanded(child: Text('Every time this program is run, it fetches a new remote widgets library.', textAlign: TextAlign.center)),
+                Expanded(child: Text('The interface that it shows is whatever library was last fetched.', textAlign: TextAlign.center)),
+                Expanded(child: Text('Restart this application to see the new interface!', textAlign: TextAlign.center)),
+              ],
+            ),
+          ),
+        ),
+      );
+    }
+    return AnimatedSwitcher(duration: const Duration(milliseconds: 1250), switchOutCurve: Curves.easeOut, switchInCurve: Curves.easeOut, child: result);
+  }
+}
diff --git a/packages/rfw/example/remote/linux/.gitignore b/packages/rfw/example/remote/linux/.gitignore
new file mode 100644
index 0000000..d3896c9
--- /dev/null
+++ b/packages/rfw/example/remote/linux/.gitignore
@@ -0,0 +1 @@
+flutter/ephemeral
diff --git a/packages/rfw/example/remote/linux/CMakeLists.txt b/packages/rfw/example/remote/linux/CMakeLists.txt
new file mode 100644
index 0000000..2f6f76f
--- /dev/null
+++ b/packages/rfw/example/remote/linux/CMakeLists.txt
@@ -0,0 +1,116 @@
+cmake_minimum_required(VERSION 3.10)
+project(runner LANGUAGES CXX)
+
+set(BINARY_NAME "remote")
+set(APPLICATION_ID "dev.flutter.rfw.examples.remote")
+
+cmake_policy(SET CMP0063 NEW)
+
+set(CMAKE_INSTALL_RPATH "$ORIGIN/lib")
+
+# Root filesystem for cross-building.
+if(FLUTTER_TARGET_PLATFORM_SYSROOT)
+  set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT})
+  set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT})
+  set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
+  set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
+  set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
+  set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
+endif()
+
+# Configure build options.
+if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
+  set(CMAKE_BUILD_TYPE "Debug" CACHE
+    STRING "Flutter build mode" FORCE)
+  set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
+    "Debug" "Profile" "Release")
+endif()
+
+# Compilation settings that should be applied to most targets.
+function(APPLY_STANDARD_SETTINGS TARGET)
+  target_compile_features(${TARGET} PUBLIC cxx_std_14)
+  target_compile_options(${TARGET} PRIVATE -Wall -Werror)
+  target_compile_options(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:-O3>")
+  target_compile_definitions(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:NDEBUG>")
+endfunction()
+
+set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter")
+
+# Flutter library and tool build rules.
+add_subdirectory(${FLUTTER_MANAGED_DIR})
+
+# System-level dependencies.
+find_package(PkgConfig REQUIRED)
+pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
+
+add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}")
+
+# Application build
+add_executable(${BINARY_NAME}
+  "main.cc"
+  "my_application.cc"
+  "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc"
+)
+apply_standard_settings(${BINARY_NAME})
+target_link_libraries(${BINARY_NAME} PRIVATE flutter)
+target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK)
+add_dependencies(${BINARY_NAME} flutter_assemble)
+# Only the install-generated bundle's copy of the executable will launch
+# correctly, since the resources must in the right relative locations. To avoid
+# people trying to run the unbundled copy, put it in a subdirectory instead of
+# the default top-level location.
+set_target_properties(${BINARY_NAME}
+  PROPERTIES
+  RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run"
+)
+
+# Generated plugin build rules, which manage building the plugins and adding
+# them to the application.
+include(flutter/generated_plugins.cmake)
+
+
+# === Installation ===
+# By default, "installing" just makes a relocatable bundle in the build
+# directory.
+set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle")
+if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
+  set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE)
+endif()
+
+# Start with a clean build bundle directory every time.
+install(CODE "
+  file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\")
+  " COMPONENT Runtime)
+
+set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data")
+set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib")
+
+install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}"
+  COMPONENT Runtime)
+
+install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
+  COMPONENT Runtime)
+
+install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
+  COMPONENT Runtime)
+
+if(PLUGIN_BUNDLED_LIBRARIES)
+  install(FILES "${PLUGIN_BUNDLED_LIBRARIES}"
+    DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
+    COMPONENT Runtime)
+endif()
+
+# Fully re-copy the assets directory on each build to avoid having stale files
+# from a previous install.
+set(FLUTTER_ASSET_DIR_NAME "flutter_assets")
+install(CODE "
+  file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\")
+  " COMPONENT Runtime)
+install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}"
+  DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime)
+
+# Install the AOT library on non-Debug builds only.
+if(NOT CMAKE_BUILD_TYPE MATCHES "Debug")
+  install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
+    COMPONENT Runtime)
+endif()
diff --git a/packages/rfw/example/remote/linux/flutter/CMakeLists.txt b/packages/rfw/example/remote/linux/flutter/CMakeLists.txt
new file mode 100644
index 0000000..33fd580
--- /dev/null
+++ b/packages/rfw/example/remote/linux/flutter/CMakeLists.txt
@@ -0,0 +1,87 @@
+cmake_minimum_required(VERSION 3.10)
+
+set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral")
+
+# Configuration provided via flutter tool.
+include(${EPHEMERAL_DIR}/generated_config.cmake)
+
+# TODO: Move the rest of this into files in ephemeral. See
+# https://github.com/flutter/flutter/issues/57146.
+
+# Serves the same purpose as list(TRANSFORM ... PREPEND ...),
+# which isn't available in 3.10.
+function(list_prepend LIST_NAME PREFIX)
+    set(NEW_LIST "")
+    foreach(element ${${LIST_NAME}})
+        list(APPEND NEW_LIST "${PREFIX}${element}")
+    endforeach(element)
+    set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE)
+endfunction()
+
+# === Flutter Library ===
+# System-level dependencies.
+find_package(PkgConfig REQUIRED)
+pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
+pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0)
+pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0)
+
+set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so")
+
+# Published to parent scope for install step.
+set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE)
+set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE)
+set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE)
+set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE)
+
+list(APPEND FLUTTER_LIBRARY_HEADERS
+  "fl_basic_message_channel.h"
+  "fl_binary_codec.h"
+  "fl_binary_messenger.h"
+  "fl_dart_project.h"
+  "fl_engine.h"
+  "fl_json_message_codec.h"
+  "fl_json_method_codec.h"
+  "fl_message_codec.h"
+  "fl_method_call.h"
+  "fl_method_channel.h"
+  "fl_method_codec.h"
+  "fl_method_response.h"
+  "fl_plugin_registrar.h"
+  "fl_plugin_registry.h"
+  "fl_standard_message_codec.h"
+  "fl_standard_method_codec.h"
+  "fl_string_codec.h"
+  "fl_value.h"
+  "fl_view.h"
+  "flutter_linux.h"
+)
+list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/")
+add_library(flutter INTERFACE)
+target_include_directories(flutter INTERFACE
+  "${EPHEMERAL_DIR}"
+)
+target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}")
+target_link_libraries(flutter INTERFACE
+  PkgConfig::GTK
+  PkgConfig::GLIB
+  PkgConfig::GIO
+)
+add_dependencies(flutter flutter_assemble)
+
+# === Flutter tool backend ===
+# _phony_ is a non-existent file to force this command to run every time,
+# since currently there's no way to get a full input/output list from the
+# flutter tool.
+add_custom_command(
+  OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS}
+    ${CMAKE_CURRENT_BINARY_DIR}/_phony_
+  COMMAND ${CMAKE_COMMAND} -E env
+    ${FLUTTER_TOOL_ENVIRONMENT}
+    "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh"
+      ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE}
+  VERBATIM
+)
+add_custom_target(flutter_assemble DEPENDS
+  "${FLUTTER_LIBRARY}"
+  ${FLUTTER_LIBRARY_HEADERS}
+)
diff --git a/packages/rfw/example/remote/linux/flutter/generated_plugins.cmake b/packages/rfw/example/remote/linux/flutter/generated_plugins.cmake
new file mode 100644
index 0000000..51436ae
--- /dev/null
+++ b/packages/rfw/example/remote/linux/flutter/generated_plugins.cmake
@@ -0,0 +1,15 @@
+#
+# Generated file, do not edit.
+#
+
+list(APPEND FLUTTER_PLUGIN_LIST
+)
+
+set(PLUGIN_BUNDLED_LIBRARIES)
+
+foreach(plugin ${FLUTTER_PLUGIN_LIST})
+  add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin})
+  target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)
+  list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>)
+  list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
+endforeach(plugin)
diff --git a/packages/rfw/example/remote/linux/main.cc b/packages/rfw/example/remote/linux/main.cc
new file mode 100644
index 0000000..1507d02
--- /dev/null
+++ b/packages/rfw/example/remote/linux/main.cc
@@ -0,0 +1,10 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "my_application.h"
+
+int main(int argc, char** argv) {
+  g_autoptr(MyApplication) app = my_application_new();
+  return g_application_run(G_APPLICATION(app), argc, argv);
+}
diff --git a/packages/rfw/example/remote/linux/my_application.cc b/packages/rfw/example/remote/linux/my_application.cc
new file mode 100644
index 0000000..168ef0d
--- /dev/null
+++ b/packages/rfw/example/remote/linux/my_application.cc
@@ -0,0 +1,111 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "my_application.h"
+
+#include <flutter_linux/flutter_linux.h>
+#ifdef GDK_WINDOWING_X11
+#include <gdk/gdkx.h>
+#endif
+
+#include "flutter/generated_plugin_registrant.h"
+
+struct _MyApplication {
+  GtkApplication parent_instance;
+  char** dart_entrypoint_arguments;
+};
+
+G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION)
+
+// Implements GApplication::activate.
+static void my_application_activate(GApplication* application) {
+  MyApplication* self = MY_APPLICATION(application);
+  GtkWindow* window =
+      GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application)));
+
+  // Use a header bar when running in GNOME as this is the common style used
+  // by applications and is the setup most users will be using (e.g. Ubuntu
+  // desktop).
+  // If running on X and not using GNOME then just use a traditional title bar
+  // in case the window manager does more exotic layout, e.g. tiling.
+  // If running on Wayland assume the header bar will work (may need changing
+  // if future cases occur).
+  gboolean use_header_bar = TRUE;
+#ifdef GDK_WINDOWING_X11
+  GdkScreen* screen = gtk_window_get_screen(window);
+  if (GDK_IS_X11_SCREEN(screen)) {
+    const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen);
+    if (g_strcmp0(wm_name, "GNOME Shell") != 0) {
+      use_header_bar = FALSE;
+    }
+  }
+#endif
+  if (use_header_bar) {
+    GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new());
+    gtk_widget_show(GTK_WIDGET(header_bar));
+    gtk_header_bar_set_title(header_bar, "remote");
+    gtk_header_bar_set_show_close_button(header_bar, TRUE);
+    gtk_window_set_titlebar(window, GTK_WIDGET(header_bar));
+  } else {
+    gtk_window_set_title(window, "remote");
+  }
+
+  gtk_window_set_default_size(window, 1280, 720);
+  gtk_widget_show(GTK_WIDGET(window));
+
+  g_autoptr(FlDartProject) project = fl_dart_project_new();
+  fl_dart_project_set_dart_entrypoint_arguments(
+      project, self->dart_entrypoint_arguments);
+
+  FlView* view = fl_view_new(project);
+  gtk_widget_show(GTK_WIDGET(view));
+  gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view));
+
+  fl_register_plugins(FL_PLUGIN_REGISTRY(view));
+
+  gtk_widget_grab_focus(GTK_WIDGET(view));
+}
+
+// Implements GApplication::local_command_line.
+static gboolean my_application_local_command_line(GApplication* application,
+                                                  gchar*** arguments,
+                                                  int* exit_status) {
+  MyApplication* self = MY_APPLICATION(application);
+  // Strip out the first argument as it is the binary name.
+  self->dart_entrypoint_arguments = g_strdupv(*arguments + 1);
+
+  g_autoptr(GError) error = nullptr;
+  if (!g_application_register(application, nullptr, &error)) {
+    g_warning("Failed to register: %s", error->message);
+    *exit_status = 1;
+    return TRUE;
+  }
+
+  g_application_activate(application);
+  *exit_status = 0;
+
+  return TRUE;
+}
+
+// Implements GObject::dispose.
+static void my_application_dispose(GObject* object) {
+  MyApplication* self = MY_APPLICATION(object);
+  g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev);
+  G_OBJECT_CLASS(my_application_parent_class)->dispose(object);
+}
+
+static void my_application_class_init(MyApplicationClass* klass) {
+  G_APPLICATION_CLASS(klass)->activate = my_application_activate;
+  G_APPLICATION_CLASS(klass)->local_command_line =
+      my_application_local_command_line;
+  G_OBJECT_CLASS(klass)->dispose = my_application_dispose;
+}
+
+static void my_application_init(MyApplication* self) {}
+
+MyApplication* my_application_new() {
+  return MY_APPLICATION(g_object_new(my_application_get_type(),
+                                     "application-id", APPLICATION_ID, "flags",
+                                     G_APPLICATION_NON_UNIQUE, nullptr));
+}
diff --git a/packages/rfw/example/remote/linux/my_application.h b/packages/rfw/example/remote/linux/my_application.h
new file mode 100644
index 0000000..6e9f0c3
--- /dev/null
+++ b/packages/rfw/example/remote/linux/my_application.h
@@ -0,0 +1,22 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef FLUTTER_MY_APPLICATION_H_
+#define FLUTTER_MY_APPLICATION_H_
+
+#include <gtk/gtk.h>
+
+G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION,
+                     GtkApplication)
+
+/**
+ * my_application_new:
+ *
+ * Creates a new Flutter-based application.
+ *
+ * Returns: a new #MyApplication.
+ */
+MyApplication* my_application_new();
+
+#endif  // FLUTTER_MY_APPLICATION_H_
diff --git a/packages/rfw/example/remote/macos/.gitignore b/packages/rfw/example/remote/macos/.gitignore
new file mode 100644
index 0000000..d2fd377
--- /dev/null
+++ b/packages/rfw/example/remote/macos/.gitignore
@@ -0,0 +1,6 @@
+# Flutter-related
+**/Flutter/ephemeral/
+**/Pods/
+
+# Xcode-related
+**/xcuserdata/
diff --git a/packages/rfw/example/remote/macos/Flutter/Flutter-Debug.xcconfig b/packages/rfw/example/remote/macos/Flutter/Flutter-Debug.xcconfig
new file mode 100644
index 0000000..c2efd0b
--- /dev/null
+++ b/packages/rfw/example/remote/macos/Flutter/Flutter-Debug.xcconfig
@@ -0,0 +1 @@
+#include "ephemeral/Flutter-Generated.xcconfig"
diff --git a/packages/rfw/example/remote/macos/Flutter/Flutter-Release.xcconfig b/packages/rfw/example/remote/macos/Flutter/Flutter-Release.xcconfig
new file mode 100644
index 0000000..c2efd0b
--- /dev/null
+++ b/packages/rfw/example/remote/macos/Flutter/Flutter-Release.xcconfig
@@ -0,0 +1 @@
+#include "ephemeral/Flutter-Generated.xcconfig"
diff --git a/packages/rfw/example/remote/macos/Runner.xcodeproj/project.pbxproj b/packages/rfw/example/remote/macos/Runner.xcodeproj/project.pbxproj
new file mode 100644
index 0000000..2f5eaa9
--- /dev/null
+++ b/packages/rfw/example/remote/macos/Runner.xcodeproj/project.pbxproj
@@ -0,0 +1,572 @@
+// !$*UTF8*$!
+{
+	archiveVersion = 1;
+	classes = {
+	};
+	objectVersion = 51;
+	objects = {
+
+/* Begin PBXAggregateTarget section */
+		33CC111A2044C6BA0003C045 /* Flutter Assemble */ = {
+			isa = PBXAggregateTarget;
+			buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */;
+			buildPhases = (
+				33CC111E2044C6BF0003C045 /* ShellScript */,
+			);
+			dependencies = (
+			);
+			name = "Flutter Assemble";
+			productName = FLX;
+		};
+/* End PBXAggregateTarget section */
+
+/* Begin PBXBuildFile section */
+		335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; };
+		33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; };
+		33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; };
+		33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; };
+		33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXContainerItemProxy section */
+		33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = {
+			isa = PBXContainerItemProxy;
+			containerPortal = 33CC10E52044A3C60003C045 /* Project object */;
+			proxyType = 1;
+			remoteGlobalIDString = 33CC111A2044C6BA0003C045;
+			remoteInfo = FLX;
+		};
+/* End PBXContainerItemProxy section */
+
+/* Begin PBXCopyFilesBuildPhase section */
+		33CC110E2044A8840003C045 /* Bundle Framework */ = {
+			isa = PBXCopyFilesBuildPhase;
+			buildActionMask = 2147483647;
+			dstPath = "";
+			dstSubfolderSpec = 10;
+			files = (
+			);
+			name = "Bundle Framework";
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXCopyFilesBuildPhase section */
+
+/* Begin PBXFileReference section */
+		333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = "<group>"; };
+		335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = "<group>"; };
+		33CC10ED2044A3C60003C045 /* remote.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "remote.app"; sourceTree = BUILT_PRODUCTS_DIR; };
+		33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
+		33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = "<group>"; };
+		33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = "<group>"; };
+		33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = "<group>"; };
+		33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = "<group>"; };
+		33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = "<group>"; };
+		33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = "<group>"; };
+		33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = "<group>"; };
+		33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = "<group>"; };
+		33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = "<group>"; };
+		33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = "<group>"; };
+		7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = "<group>"; };
+		9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = "<group>"; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+		33CC10EA2044A3C60003C045 /* Frameworks */ = {
+			isa = PBXFrameworksBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+		33BA886A226E78AF003329D5 /* Configs */ = {
+			isa = PBXGroup;
+			children = (
+				33E5194F232828860026EE4D /* AppInfo.xcconfig */,
+				9740EEB21CF90195004384FC /* Debug.xcconfig */,
+				7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
+				333000ED22D3DE5D00554162 /* Warnings.xcconfig */,
+			);
+			path = Configs;
+			sourceTree = "<group>";
+		};
+		33CC10E42044A3C60003C045 = {
+			isa = PBXGroup;
+			children = (
+				33FAB671232836740065AC1E /* Runner */,
+				33CEB47122A05771004F2AC0 /* Flutter */,
+				33CC10EE2044A3C60003C045 /* Products */,
+				D73912EC22F37F3D000D13A0 /* Frameworks */,
+			);
+			sourceTree = "<group>";
+		};
+		33CC10EE2044A3C60003C045 /* Products */ = {
+			isa = PBXGroup;
+			children = (
+				33CC10ED2044A3C60003C045 /* remote.app */,
+			);
+			name = Products;
+			sourceTree = "<group>";
+		};
+		33CC11242044D66E0003C045 /* Resources */ = {
+			isa = PBXGroup;
+			children = (
+				33CC10F22044A3C60003C045 /* Assets.xcassets */,
+				33CC10F42044A3C60003C045 /* MainMenu.xib */,
+				33CC10F72044A3C60003C045 /* Info.plist */,
+			);
+			name = Resources;
+			path = ..;
+			sourceTree = "<group>";
+		};
+		33CEB47122A05771004F2AC0 /* Flutter */ = {
+			isa = PBXGroup;
+			children = (
+				335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */,
+				33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */,
+				33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */,
+				33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */,
+			);
+			path = Flutter;
+			sourceTree = "<group>";
+		};
+		33FAB671232836740065AC1E /* Runner */ = {
+			isa = PBXGroup;
+			children = (
+				33CC10F02044A3C60003C045 /* AppDelegate.swift */,
+				33CC11122044BFA00003C045 /* MainFlutterWindow.swift */,
+				33E51913231747F40026EE4D /* DebugProfile.entitlements */,
+				33E51914231749380026EE4D /* Release.entitlements */,
+				33CC11242044D66E0003C045 /* Resources */,
+				33BA886A226E78AF003329D5 /* Configs */,
+			);
+			path = Runner;
+			sourceTree = "<group>";
+		};
+		D73912EC22F37F3D000D13A0 /* Frameworks */ = {
+			isa = PBXGroup;
+			children = (
+			);
+			name = Frameworks;
+			sourceTree = "<group>";
+		};
+/* End PBXGroup section */
+
+/* Begin PBXNativeTarget section */
+		33CC10EC2044A3C60003C045 /* Runner */ = {
+			isa = PBXNativeTarget;
+			buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */;
+			buildPhases = (
+				33CC10E92044A3C60003C045 /* Sources */,
+				33CC10EA2044A3C60003C045 /* Frameworks */,
+				33CC10EB2044A3C60003C045 /* Resources */,
+				33CC110E2044A8840003C045 /* Bundle Framework */,
+				3399D490228B24CF009A79C7 /* ShellScript */,
+			);
+			buildRules = (
+			);
+			dependencies = (
+				33CC11202044C79F0003C045 /* PBXTargetDependency */,
+			);
+			name = Runner;
+			productName = Runner;
+			productReference = 33CC10ED2044A3C60003C045 /* remote.app */;
+			productType = "com.apple.product-type.application";
+		};
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+		33CC10E52044A3C60003C045 /* Project object */ = {
+			isa = PBXProject;
+			attributes = {
+				LastSwiftUpdateCheck = 0920;
+				LastUpgradeCheck = 1300;
+				ORGANIZATIONNAME = "";
+				TargetAttributes = {
+					33CC10EC2044A3C60003C045 = {
+						CreatedOnToolsVersion = 9.2;
+						LastSwiftMigration = 1100;
+						ProvisioningStyle = Automatic;
+						SystemCapabilities = {
+							com.apple.Sandbox = {
+								enabled = 1;
+							};
+						};
+					};
+					33CC111A2044C6BA0003C045 = {
+						CreatedOnToolsVersion = 9.2;
+						ProvisioningStyle = Manual;
+					};
+				};
+			};
+			buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */;
+			compatibilityVersion = "Xcode 9.3";
+			developmentRegion = en;
+			hasScannedForEncodings = 0;
+			knownRegions = (
+				en,
+				Base,
+			);
+			mainGroup = 33CC10E42044A3C60003C045;
+			productRefGroup = 33CC10EE2044A3C60003C045 /* Products */;
+			projectDirPath = "";
+			projectRoot = "";
+			targets = (
+				33CC10EC2044A3C60003C045 /* Runner */,
+				33CC111A2044C6BA0003C045 /* Flutter Assemble */,
+			);
+		};
+/* End PBXProject section */
+
+/* Begin PBXResourcesBuildPhase section */
+		33CC10EB2044A3C60003C045 /* Resources */ = {
+			isa = PBXResourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */,
+				33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXResourcesBuildPhase section */
+
+/* Begin PBXShellScriptBuildPhase section */
+		3399D490228B24CF009A79C7 /* ShellScript */ = {
+			isa = PBXShellScriptBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			inputFileListPaths = (
+			);
+			inputPaths = (
+			);
+			outputFileListPaths = (
+			);
+			outputPaths = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+			shellPath = /bin/sh;
+			shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n";
+		};
+		33CC111E2044C6BF0003C045 /* ShellScript */ = {
+			isa = PBXShellScriptBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			inputFileListPaths = (
+				Flutter/ephemeral/FlutterInputs.xcfilelist,
+			);
+			inputPaths = (
+				Flutter/ephemeral/tripwire,
+			);
+			outputFileListPaths = (
+				Flutter/ephemeral/FlutterOutputs.xcfilelist,
+			);
+			outputPaths = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+			shellPath = /bin/sh;
+			shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire";
+		};
+/* End PBXShellScriptBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+		33CC10E92044A3C60003C045 /* Sources */ = {
+			isa = PBXSourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */,
+				33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */,
+				335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXSourcesBuildPhase section */
+
+/* Begin PBXTargetDependency section */
+		33CC11202044C79F0003C045 /* PBXTargetDependency */ = {
+			isa = PBXTargetDependency;
+			target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */;
+			targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */;
+		};
+/* End PBXTargetDependency section */
+
+/* Begin PBXVariantGroup section */
+		33CC10F42044A3C60003C045 /* MainMenu.xib */ = {
+			isa = PBXVariantGroup;
+			children = (
+				33CC10F52044A3C60003C045 /* Base */,
+			);
+			name = MainMenu.xib;
+			path = Runner;
+			sourceTree = "<group>";
+		};
+/* End PBXVariantGroup section */
+
+/* Begin XCBuildConfiguration section */
+		338D0CE9231458BD00FA5F75 /* Profile */ = {
+			isa = XCBuildConfiguration;
+			baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
+			buildSettings = {
+				ALWAYS_SEARCH_USER_PATHS = NO;
+				CLANG_ANALYZER_NONNULL = YES;
+				CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+				CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+				CLANG_CXX_LIBRARY = "libc++";
+				CLANG_ENABLE_MODULES = YES;
+				CLANG_ENABLE_OBJC_ARC = YES;
+				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+				CLANG_WARN_BOOL_CONVERSION = YES;
+				CLANG_WARN_CONSTANT_CONVERSION = YES;
+				CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+				CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+				CLANG_WARN_EMPTY_BODY = YES;
+				CLANG_WARN_ENUM_CONVERSION = YES;
+				CLANG_WARN_INFINITE_RECURSION = YES;
+				CLANG_WARN_INT_CONVERSION = YES;
+				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+				CLANG_WARN_SUSPICIOUS_MOVE = YES;
+				CODE_SIGN_IDENTITY = "-";
+				COPY_PHASE_STRIP = NO;
+				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+				ENABLE_NS_ASSERTIONS = NO;
+				ENABLE_STRICT_OBJC_MSGSEND = YES;
+				GCC_C_LANGUAGE_STANDARD = gnu11;
+				GCC_NO_COMMON_BLOCKS = YES;
+				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+				GCC_WARN_UNUSED_FUNCTION = YES;
+				GCC_WARN_UNUSED_VARIABLE = YES;
+				MACOSX_DEPLOYMENT_TARGET = 10.11;
+				MTL_ENABLE_DEBUG_INFO = NO;
+				SDKROOT = macosx;
+				SWIFT_COMPILATION_MODE = wholemodule;
+				SWIFT_OPTIMIZATION_LEVEL = "-O";
+			};
+			name = Profile;
+		};
+		338D0CEA231458BD00FA5F75 /* Profile */ = {
+			isa = XCBuildConfiguration;
+			baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */;
+			buildSettings = {
+				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+				CLANG_ENABLE_MODULES = YES;
+				CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements;
+				CODE_SIGN_STYLE = Automatic;
+				COMBINE_HIDPI_IMAGES = YES;
+				INFOPLIST_FILE = Runner/Info.plist;
+				LD_RUNPATH_SEARCH_PATHS = (
+					"$(inherited)",
+					"@executable_path/../Frameworks",
+				);
+				PROVISIONING_PROFILE_SPECIFIER = "";
+				SWIFT_VERSION = 5.0;
+			};
+			name = Profile;
+		};
+		338D0CEB231458BD00FA5F75 /* Profile */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				CODE_SIGN_STYLE = Manual;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+			};
+			name = Profile;
+		};
+		33CC10F92044A3C60003C045 /* Debug */ = {
+			isa = XCBuildConfiguration;
+			baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
+			buildSettings = {
+				ALWAYS_SEARCH_USER_PATHS = NO;
+				CLANG_ANALYZER_NONNULL = YES;
+				CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+				CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+				CLANG_CXX_LIBRARY = "libc++";
+				CLANG_ENABLE_MODULES = YES;
+				CLANG_ENABLE_OBJC_ARC = YES;
+				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+				CLANG_WARN_BOOL_CONVERSION = YES;
+				CLANG_WARN_CONSTANT_CONVERSION = YES;
+				CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+				CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+				CLANG_WARN_EMPTY_BODY = YES;
+				CLANG_WARN_ENUM_CONVERSION = YES;
+				CLANG_WARN_INFINITE_RECURSION = YES;
+				CLANG_WARN_INT_CONVERSION = YES;
+				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+				CLANG_WARN_SUSPICIOUS_MOVE = YES;
+				CODE_SIGN_IDENTITY = "-";
+				COPY_PHASE_STRIP = NO;
+				DEBUG_INFORMATION_FORMAT = dwarf;
+				ENABLE_STRICT_OBJC_MSGSEND = YES;
+				ENABLE_TESTABILITY = YES;
+				GCC_C_LANGUAGE_STANDARD = gnu11;
+				GCC_DYNAMIC_NO_PIC = NO;
+				GCC_NO_COMMON_BLOCKS = YES;
+				GCC_OPTIMIZATION_LEVEL = 0;
+				GCC_PREPROCESSOR_DEFINITIONS = (
+					"DEBUG=1",
+					"$(inherited)",
+				);
+				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+				GCC_WARN_UNUSED_FUNCTION = YES;
+				GCC_WARN_UNUSED_VARIABLE = YES;
+				MACOSX_DEPLOYMENT_TARGET = 10.11;
+				MTL_ENABLE_DEBUG_INFO = YES;
+				ONLY_ACTIVE_ARCH = YES;
+				SDKROOT = macosx;
+				SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
+				SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+			};
+			name = Debug;
+		};
+		33CC10FA2044A3C60003C045 /* Release */ = {
+			isa = XCBuildConfiguration;
+			baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
+			buildSettings = {
+				ALWAYS_SEARCH_USER_PATHS = NO;
+				CLANG_ANALYZER_NONNULL = YES;
+				CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+				CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+				CLANG_CXX_LIBRARY = "libc++";
+				CLANG_ENABLE_MODULES = YES;
+				CLANG_ENABLE_OBJC_ARC = YES;
+				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+				CLANG_WARN_BOOL_CONVERSION = YES;
+				CLANG_WARN_CONSTANT_CONVERSION = YES;
+				CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+				CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+				CLANG_WARN_EMPTY_BODY = YES;
+				CLANG_WARN_ENUM_CONVERSION = YES;
+				CLANG_WARN_INFINITE_RECURSION = YES;
+				CLANG_WARN_INT_CONVERSION = YES;
+				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+				CLANG_WARN_SUSPICIOUS_MOVE = YES;
+				CODE_SIGN_IDENTITY = "-";
+				COPY_PHASE_STRIP = NO;
+				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+				ENABLE_NS_ASSERTIONS = NO;
+				ENABLE_STRICT_OBJC_MSGSEND = YES;
+				GCC_C_LANGUAGE_STANDARD = gnu11;
+				GCC_NO_COMMON_BLOCKS = YES;
+				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+				GCC_WARN_UNUSED_FUNCTION = YES;
+				GCC_WARN_UNUSED_VARIABLE = YES;
+				MACOSX_DEPLOYMENT_TARGET = 10.11;
+				MTL_ENABLE_DEBUG_INFO = NO;
+				SDKROOT = macosx;
+				SWIFT_COMPILATION_MODE = wholemodule;
+				SWIFT_OPTIMIZATION_LEVEL = "-O";
+			};
+			name = Release;
+		};
+		33CC10FC2044A3C60003C045 /* Debug */ = {
+			isa = XCBuildConfiguration;
+			baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */;
+			buildSettings = {
+				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+				CLANG_ENABLE_MODULES = YES;
+				CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements;
+				CODE_SIGN_STYLE = Automatic;
+				COMBINE_HIDPI_IMAGES = YES;
+				INFOPLIST_FILE = Runner/Info.plist;
+				LD_RUNPATH_SEARCH_PATHS = (
+					"$(inherited)",
+					"@executable_path/../Frameworks",
+				);
+				PROVISIONING_PROFILE_SPECIFIER = "";
+				SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+				SWIFT_VERSION = 5.0;
+			};
+			name = Debug;
+		};
+		33CC10FD2044A3C60003C045 /* Release */ = {
+			isa = XCBuildConfiguration;
+			baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */;
+			buildSettings = {
+				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+				CLANG_ENABLE_MODULES = YES;
+				CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements;
+				CODE_SIGN_STYLE = Automatic;
+				COMBINE_HIDPI_IMAGES = YES;
+				INFOPLIST_FILE = Runner/Info.plist;
+				LD_RUNPATH_SEARCH_PATHS = (
+					"$(inherited)",
+					"@executable_path/../Frameworks",
+				);
+				PROVISIONING_PROFILE_SPECIFIER = "";
+				SWIFT_VERSION = 5.0;
+			};
+			name = Release;
+		};
+		33CC111C2044C6BA0003C045 /* Debug */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				CODE_SIGN_STYLE = Manual;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+			};
+			name = Debug;
+		};
+		33CC111D2044C6BA0003C045 /* Release */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				CODE_SIGN_STYLE = Automatic;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+			};
+			name = Release;
+		};
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+		33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				33CC10F92044A3C60003C045 /* Debug */,
+				33CC10FA2044A3C60003C045 /* Release */,
+				338D0CE9231458BD00FA5F75 /* Profile */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
+		};
+		33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				33CC10FC2044A3C60003C045 /* Debug */,
+				33CC10FD2044A3C60003C045 /* Release */,
+				338D0CEA231458BD00FA5F75 /* Profile */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
+		};
+		33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				33CC111C2044C6BA0003C045 /* Debug */,
+				33CC111D2044C6BA0003C045 /* Release */,
+				338D0CEB231458BD00FA5F75 /* Profile */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
+		};
+/* End XCConfigurationList section */
+	};
+	rootObject = 33CC10E52044A3C60003C045 /* Project object */;
+}
diff --git a/packages/rfw/example/remote/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/rfw/example/remote/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
new file mode 100644
index 0000000..18d9810
--- /dev/null
+++ b/packages/rfw/example/remote/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>IDEDidComputeMac32BitWarning</key>
+	<true/>
+</dict>
+</plist>
diff --git a/packages/rfw/example/remote/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/rfw/example/remote/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
new file mode 100644
index 0000000..cccb242
--- /dev/null
+++ b/packages/rfw/example/remote/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
@@ -0,0 +1,87 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Scheme
+   LastUpgradeVersion = "1300"
+   version = "1.3">
+   <BuildAction
+      parallelizeBuildables = "YES"
+      buildImplicitDependencies = "YES">
+      <BuildActionEntries>
+         <BuildActionEntry
+            buildForTesting = "YES"
+            buildForRunning = "YES"
+            buildForProfiling = "YES"
+            buildForArchiving = "YES"
+            buildForAnalyzing = "YES">
+            <BuildableReference
+               BuildableIdentifier = "primary"
+               BlueprintIdentifier = "33CC10EC2044A3C60003C045"
+               BuildableName = "remote.app"
+               BlueprintName = "Runner"
+               ReferencedContainer = "container:Runner.xcodeproj">
+            </BuildableReference>
+         </BuildActionEntry>
+      </BuildActionEntries>
+   </BuildAction>
+   <TestAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      shouldUseLaunchSchemeArgsEnv = "YES">
+      <MacroExpansion>
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "33CC10EC2044A3C60003C045"
+            BuildableName = "remote.app"
+            BlueprintName = "Runner"
+            ReferencedContainer = "container:Runner.xcodeproj">
+         </BuildableReference>
+      </MacroExpansion>
+      <Testables>
+      </Testables>
+   </TestAction>
+   <LaunchAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      launchStyle = "0"
+      useCustomWorkingDirectory = "NO"
+      ignoresPersistentStateOnLaunch = "NO"
+      debugDocumentVersioning = "YES"
+      debugServiceExtension = "internal"
+      allowLocationSimulation = "YES">
+      <BuildableProductRunnable
+         runnableDebuggingMode = "0">
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "33CC10EC2044A3C60003C045"
+            BuildableName = "remote.app"
+            BlueprintName = "Runner"
+            ReferencedContainer = "container:Runner.xcodeproj">
+         </BuildableReference>
+      </BuildableProductRunnable>
+   </LaunchAction>
+   <ProfileAction
+      buildConfiguration = "Profile"
+      shouldUseLaunchSchemeArgsEnv = "YES"
+      savedToolIdentifier = ""
+      useCustomWorkingDirectory = "NO"
+      debugDocumentVersioning = "YES">
+      <BuildableProductRunnable
+         runnableDebuggingMode = "0">
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "33CC10EC2044A3C60003C045"
+            BuildableName = "remote.app"
+            BlueprintName = "Runner"
+            ReferencedContainer = "container:Runner.xcodeproj">
+         </BuildableReference>
+      </BuildableProductRunnable>
+   </ProfileAction>
+   <AnalyzeAction
+      buildConfiguration = "Debug">
+   </AnalyzeAction>
+   <ArchiveAction
+      buildConfiguration = "Release"
+      revealArchiveInOrganizer = "YES">
+   </ArchiveAction>
+</Scheme>
diff --git a/packages/rfw/example/remote/macos/Runner.xcworkspace/contents.xcworkspacedata b/packages/rfw/example/remote/macos/Runner.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 0000000..1d526a1
--- /dev/null
+++ b/packages/rfw/example/remote/macos/Runner.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Workspace
+   version = "1.0">
+   <FileRef
+      location = "group:Runner.xcodeproj">
+   </FileRef>
+</Workspace>
diff --git a/packages/rfw/example/remote/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/rfw/example/remote/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
new file mode 100644
index 0000000..18d9810
--- /dev/null
+++ b/packages/rfw/example/remote/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>IDEDidComputeMac32BitWarning</key>
+	<true/>
+</dict>
+</plist>
diff --git a/packages/rfw/example/remote/macos/Runner/AppDelegate.swift b/packages/rfw/example/remote/macos/Runner/AppDelegate.swift
new file mode 100644
index 0000000..5cec4c4
--- /dev/null
+++ b/packages/rfw/example/remote/macos/Runner/AppDelegate.swift
@@ -0,0 +1,13 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import Cocoa
+import FlutterMacOS
+
+@NSApplicationMain
+class AppDelegate: FlutterAppDelegate {
+  override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
+    return true
+  }
+}
diff --git a/packages/rfw/example/remote/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/packages/rfw/example/remote/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
new file mode 100644
index 0000000..a2ec33f
--- /dev/null
+++ b/packages/rfw/example/remote/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -0,0 +1,68 @@
+{
+  "images" : [
+    {
+      "size" : "16x16",
+      "idiom" : "mac",
+      "filename" : "app_icon_16.png",
+      "scale" : "1x"
+    },
+    {
+      "size" : "16x16",
+      "idiom" : "mac",
+      "filename" : "app_icon_32.png",
+      "scale" : "2x"
+    },
+    {
+      "size" : "32x32",
+      "idiom" : "mac",
+      "filename" : "app_icon_32.png",
+      "scale" : "1x"
+    },
+    {
+      "size" : "32x32",
+      "idiom" : "mac",
+      "filename" : "app_icon_64.png",
+      "scale" : "2x"
+    },
+    {
+      "size" : "128x128",
+      "idiom" : "mac",
+      "filename" : "app_icon_128.png",
+      "scale" : "1x"
+    },
+    {
+      "size" : "128x128",
+      "idiom" : "mac",
+      "filename" : "app_icon_256.png",
+      "scale" : "2x"
+    },
+    {
+      "size" : "256x256",
+      "idiom" : "mac",
+      "filename" : "app_icon_256.png",
+      "scale" : "1x"
+    },
+    {
+      "size" : "256x256",
+      "idiom" : "mac",
+      "filename" : "app_icon_512.png",
+      "scale" : "2x"
+    },
+    {
+      "size" : "512x512",
+      "idiom" : "mac",
+      "filename" : "app_icon_512.png",
+      "scale" : "1x"
+    },
+    {
+      "size" : "512x512",
+      "idiom" : "mac",
+      "filename" : "app_icon_1024.png",
+      "scale" : "2x"
+    }
+  ],
+  "info" : {
+    "version" : 1,
+    "author" : "xcode"
+  }
+}
diff --git a/packages/rfw/example/remote/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/packages/rfw/example/remote/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png
new file mode 100644
index 0000000..3c4935a
--- /dev/null
+++ b/packages/rfw/example/remote/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png
Binary files differ
diff --git a/packages/rfw/example/remote/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/packages/rfw/example/remote/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png
new file mode 100644
index 0000000..ed4cc16
--- /dev/null
+++ b/packages/rfw/example/remote/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png
Binary files differ
diff --git a/packages/rfw/example/remote/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/packages/rfw/example/remote/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png
new file mode 100644
index 0000000..483be61
--- /dev/null
+++ b/packages/rfw/example/remote/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png
Binary files differ
diff --git a/packages/rfw/example/remote/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/packages/rfw/example/remote/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png
new file mode 100644
index 0000000..bcbf36d
--- /dev/null
+++ b/packages/rfw/example/remote/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png
Binary files differ
diff --git a/packages/rfw/example/remote/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/packages/rfw/example/remote/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png
new file mode 100644
index 0000000..9c0a652
--- /dev/null
+++ b/packages/rfw/example/remote/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png
Binary files differ
diff --git a/packages/rfw/example/remote/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/packages/rfw/example/remote/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png
new file mode 100644
index 0000000..e71a726
--- /dev/null
+++ b/packages/rfw/example/remote/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png
Binary files differ
diff --git a/packages/rfw/example/remote/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/packages/rfw/example/remote/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png
new file mode 100644
index 0000000..8a31fe2
--- /dev/null
+++ b/packages/rfw/example/remote/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png
Binary files differ
diff --git a/packages/rfw/example/remote/macos/Runner/Base.lproj/MainMenu.xib b/packages/rfw/example/remote/macos/Runner/Base.lproj/MainMenu.xib
new file mode 100644
index 0000000..537341a
--- /dev/null
+++ b/packages/rfw/example/remote/macos/Runner/Base.lproj/MainMenu.xib
@@ -0,0 +1,339 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
+    <dependencies>
+        <deployment identifier="macosx"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14490.70"/>
+        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
+    </dependencies>
+    <objects>
+        <customObject id="-2" userLabel="File's Owner" customClass="NSApplication">
+            <connections>
+                <outlet property="delegate" destination="Voe-Tx-rLC" id="GzC-gU-4Uq"/>
+            </connections>
+        </customObject>
+        <customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
+        <customObject id="-3" userLabel="Application" customClass="NSObject"/>
+        <customObject id="Voe-Tx-rLC" customClass="AppDelegate" customModule="Runner" customModuleProvider="target">
+            <connections>
+                <outlet property="applicationMenu" destination="uQy-DD-JDr" id="XBo-yE-nKs"/>
+                <outlet property="mainFlutterWindow" destination="QvC-M9-y7g" id="gIp-Ho-8D9"/>
+            </connections>
+        </customObject>
+        <customObject id="YLy-65-1bz" customClass="NSFontManager"/>
+        <menu title="Main Menu" systemMenu="main" id="AYu-sK-qS6">
+            <items>
+                <menuItem title="APP_NAME" id="1Xt-HY-uBw">
+                    <modifierMask key="keyEquivalentModifierMask"/>
+                    <menu key="submenu" title="APP_NAME" systemMenu="apple" id="uQy-DD-JDr">
+                        <items>
+                            <menuItem title="About APP_NAME" id="5kV-Vb-QxS">
+                                <modifierMask key="keyEquivalentModifierMask"/>
+                                <connections>
+                                    <action selector="orderFrontStandardAboutPanel:" target="-1" id="Exp-CZ-Vem"/>
+                                </connections>
+                            </menuItem>
+                            <menuItem isSeparatorItem="YES" id="VOq-y0-SEH"/>
+                            <menuItem title="Preferences…" keyEquivalent="," id="BOF-NM-1cW"/>
+                            <menuItem isSeparatorItem="YES" id="wFC-TO-SCJ"/>
+                            <menuItem title="Services" id="NMo-om-nkz">
+                                <modifierMask key="keyEquivalentModifierMask"/>
+                                <menu key="submenu" title="Services" systemMenu="services" id="hz9-B4-Xy5"/>
+                            </menuItem>
+                            <menuItem isSeparatorItem="YES" id="4je-JR-u6R"/>
+                            <menuItem title="Hide APP_NAME" keyEquivalent="h" id="Olw-nP-bQN">
+                                <connections>
+                                    <action selector="hide:" target="-1" id="PnN-Uc-m68"/>
+                                </connections>
+                            </menuItem>
+                            <menuItem title="Hide Others" keyEquivalent="h" id="Vdr-fp-XzO">
+                                <modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
+                                <connections>
+                                    <action selector="hideOtherApplications:" target="-1" id="VT4-aY-XCT"/>
+                                </connections>
+                            </menuItem>
+                            <menuItem title="Show All" id="Kd2-mp-pUS">
+                                <modifierMask key="keyEquivalentModifierMask"/>
+                                <connections>
+                                    <action selector="unhideAllApplications:" target="-1" id="Dhg-Le-xox"/>
+                                </connections>
+                            </menuItem>
+                            <menuItem isSeparatorItem="YES" id="kCx-OE-vgT"/>
+                            <menuItem title="Quit APP_NAME" keyEquivalent="q" id="4sb-4s-VLi">
+                                <connections>
+                                    <action selector="terminate:" target="-1" id="Te7-pn-YzF"/>
+                                </connections>
+                            </menuItem>
+                        </items>
+                    </menu>
+                </menuItem>
+                <menuItem title="Edit" id="5QF-Oa-p0T">
+                    <modifierMask key="keyEquivalentModifierMask"/>
+                    <menu key="submenu" title="Edit" id="W48-6f-4Dl">
+                        <items>
+                            <menuItem title="Undo" keyEquivalent="z" id="dRJ-4n-Yzg">
+                                <connections>
+                                    <action selector="undo:" target="-1" id="M6e-cu-g7V"/>
+                                </connections>
+                            </menuItem>
+                            <menuItem title="Redo" keyEquivalent="Z" id="6dh-zS-Vam">
+                                <connections>
+                                    <action selector="redo:" target="-1" id="oIA-Rs-6OD"/>
+                                </connections>
+                            </menuItem>
+                            <menuItem isSeparatorItem="YES" id="WRV-NI-Exz"/>
+                            <menuItem title="Cut" keyEquivalent="x" id="uRl-iY-unG">
+                                <connections>
+                                    <action selector="cut:" target="-1" id="YJe-68-I9s"/>
+                                </connections>
+                            </menuItem>
+                            <menuItem title="Copy" keyEquivalent="c" id="x3v-GG-iWU">
+                                <connections>
+                                    <action selector="copy:" target="-1" id="G1f-GL-Joy"/>
+                                </connections>
+                            </menuItem>
+                            <menuItem title="Paste" keyEquivalent="v" id="gVA-U4-sdL">
+                                <connections>
+                                    <action selector="paste:" target="-1" id="UvS-8e-Qdg"/>
+                                </connections>
+                            </menuItem>
+                            <menuItem title="Paste and Match Style" keyEquivalent="V" id="WeT-3V-zwk">
+                                <modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
+                                <connections>
+                                    <action selector="pasteAsPlainText:" target="-1" id="cEh-KX-wJQ"/>
+                                </connections>
+                            </menuItem>
+                            <menuItem title="Delete" id="pa3-QI-u2k">
+                                <modifierMask key="keyEquivalentModifierMask"/>
+                                <connections>
+                                    <action selector="delete:" target="-1" id="0Mk-Ml-PaM"/>
+                                </connections>
+                            </menuItem>
+                            <menuItem title="Select All" keyEquivalent="a" id="Ruw-6m-B2m">
+                                <connections>
+                                    <action selector="selectAll:" target="-1" id="VNm-Mi-diN"/>
+                                </connections>
+                            </menuItem>
+                            <menuItem isSeparatorItem="YES" id="uyl-h8-XO2"/>
+                            <menuItem title="Find" id="4EN-yA-p0u">
+                                <modifierMask key="keyEquivalentModifierMask"/>
+                                <menu key="submenu" title="Find" id="1b7-l0-nxx">
+                                    <items>
+                                        <menuItem title="Find…" tag="1" keyEquivalent="f" id="Xz5-n4-O0W">
+                                            <connections>
+                                                <action selector="performFindPanelAction:" target="-1" id="cD7-Qs-BN4"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem title="Find and Replace…" tag="12" keyEquivalent="f" id="YEy-JH-Tfz">
+                                            <modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
+                                            <connections>
+                                                <action selector="performFindPanelAction:" target="-1" id="WD3-Gg-5AJ"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem title="Find Next" tag="2" keyEquivalent="g" id="q09-fT-Sye">
+                                            <connections>
+                                                <action selector="performFindPanelAction:" target="-1" id="NDo-RZ-v9R"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem title="Find Previous" tag="3" keyEquivalent="G" id="OwM-mh-QMV">
+                                            <connections>
+                                                <action selector="performFindPanelAction:" target="-1" id="HOh-sY-3ay"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem title="Use Selection for Find" tag="7" keyEquivalent="e" id="buJ-ug-pKt">
+                                            <connections>
+                                                <action selector="performFindPanelAction:" target="-1" id="U76-nv-p5D"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem title="Jump to Selection" keyEquivalent="j" id="S0p-oC-mLd">
+                                            <connections>
+                                                <action selector="centerSelectionInVisibleArea:" target="-1" id="IOG-6D-g5B"/>
+                                            </connections>
+                                        </menuItem>
+                                    </items>
+                                </menu>
+                            </menuItem>
+                            <menuItem title="Spelling and Grammar" id="Dv1-io-Yv7">
+                                <modifierMask key="keyEquivalentModifierMask"/>
+                                <menu key="submenu" title="Spelling" id="3IN-sU-3Bg">
+                                    <items>
+                                        <menuItem title="Show Spelling and Grammar" keyEquivalent=":" id="HFo-cy-zxI">
+                                            <connections>
+                                                <action selector="showGuessPanel:" target="-1" id="vFj-Ks-hy3"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem title="Check Document Now" keyEquivalent=";" id="hz2-CU-CR7">
+                                            <connections>
+                                                <action selector="checkSpelling:" target="-1" id="fz7-VC-reM"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem isSeparatorItem="YES" id="bNw-od-mp5"/>
+                                        <menuItem title="Check Spelling While Typing" id="rbD-Rh-wIN">
+                                            <modifierMask key="keyEquivalentModifierMask"/>
+                                            <connections>
+                                                <action selector="toggleContinuousSpellChecking:" target="-1" id="7w6-Qz-0kB"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem title="Check Grammar With Spelling" id="mK6-2p-4JG">
+                                            <modifierMask key="keyEquivalentModifierMask"/>
+                                            <connections>
+                                                <action selector="toggleGrammarChecking:" target="-1" id="muD-Qn-j4w"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem title="Correct Spelling Automatically" id="78Y-hA-62v">
+                                            <modifierMask key="keyEquivalentModifierMask"/>
+                                            <connections>
+                                                <action selector="toggleAutomaticSpellingCorrection:" target="-1" id="2lM-Qi-WAP"/>
+                                            </connections>
+                                        </menuItem>
+                                    </items>
+                                </menu>
+                            </menuItem>
+                            <menuItem title="Substitutions" id="9ic-FL-obx">
+                                <modifierMask key="keyEquivalentModifierMask"/>
+                                <menu key="submenu" title="Substitutions" id="FeM-D8-WVr">
+                                    <items>
+                                        <menuItem title="Show Substitutions" id="z6F-FW-3nz">
+                                            <modifierMask key="keyEquivalentModifierMask"/>
+                                            <connections>
+                                                <action selector="orderFrontSubstitutionsPanel:" target="-1" id="oku-mr-iSq"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem isSeparatorItem="YES" id="gPx-C9-uUO"/>
+                                        <menuItem title="Smart Copy/Paste" id="9yt-4B-nSM">
+                                            <modifierMask key="keyEquivalentModifierMask"/>
+                                            <connections>
+                                                <action selector="toggleSmartInsertDelete:" target="-1" id="3IJ-Se-DZD"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem title="Smart Quotes" id="hQb-2v-fYv">
+                                            <modifierMask key="keyEquivalentModifierMask"/>
+                                            <connections>
+                                                <action selector="toggleAutomaticQuoteSubstitution:" target="-1" id="ptq-xd-QOA"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem title="Smart Dashes" id="rgM-f4-ycn">
+                                            <modifierMask key="keyEquivalentModifierMask"/>
+                                            <connections>
+                                                <action selector="toggleAutomaticDashSubstitution:" target="-1" id="oCt-pO-9gS"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem title="Smart Links" id="cwL-P1-jid">
+                                            <modifierMask key="keyEquivalentModifierMask"/>
+                                            <connections>
+                                                <action selector="toggleAutomaticLinkDetection:" target="-1" id="Gip-E3-Fov"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem title="Data Detectors" id="tRr-pd-1PS">
+                                            <modifierMask key="keyEquivalentModifierMask"/>
+                                            <connections>
+                                                <action selector="toggleAutomaticDataDetection:" target="-1" id="R1I-Nq-Kbl"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem title="Text Replacement" id="HFQ-gK-NFA">
+                                            <modifierMask key="keyEquivalentModifierMask"/>
+                                            <connections>
+                                                <action selector="toggleAutomaticTextReplacement:" target="-1" id="DvP-Fe-Py6"/>
+                                            </connections>
+                                        </menuItem>
+                                    </items>
+                                </menu>
+                            </menuItem>
+                            <menuItem title="Transformations" id="2oI-Rn-ZJC">
+                                <modifierMask key="keyEquivalentModifierMask"/>
+                                <menu key="submenu" title="Transformations" id="c8a-y6-VQd">
+                                    <items>
+                                        <menuItem title="Make Upper Case" id="vmV-6d-7jI">
+                                            <modifierMask key="keyEquivalentModifierMask"/>
+                                            <connections>
+                                                <action selector="uppercaseWord:" target="-1" id="sPh-Tk-edu"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem title="Make Lower Case" id="d9M-CD-aMd">
+                                            <modifierMask key="keyEquivalentModifierMask"/>
+                                            <connections>
+                                                <action selector="lowercaseWord:" target="-1" id="iUZ-b5-hil"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem title="Capitalize" id="UEZ-Bs-lqG">
+                                            <modifierMask key="keyEquivalentModifierMask"/>
+                                            <connections>
+                                                <action selector="capitalizeWord:" target="-1" id="26H-TL-nsh"/>
+                                            </connections>
+                                        </menuItem>
+                                    </items>
+                                </menu>
+                            </menuItem>
+                            <menuItem title="Speech" id="xrE-MZ-jX0">
+                                <modifierMask key="keyEquivalentModifierMask"/>
+                                <menu key="submenu" title="Speech" id="3rS-ZA-NoH">
+                                    <items>
+                                        <menuItem title="Start Speaking" id="Ynk-f8-cLZ">
+                                            <modifierMask key="keyEquivalentModifierMask"/>
+                                            <connections>
+                                                <action selector="startSpeaking:" target="-1" id="654-Ng-kyl"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem title="Stop Speaking" id="Oyz-dy-DGm">
+                                            <modifierMask key="keyEquivalentModifierMask"/>
+                                            <connections>
+                                                <action selector="stopSpeaking:" target="-1" id="dX8-6p-jy9"/>
+                                            </connections>
+                                        </menuItem>
+                                    </items>
+                                </menu>
+                            </menuItem>
+                        </items>
+                    </menu>
+                </menuItem>
+                <menuItem title="View" id="H8h-7b-M4v">
+                    <modifierMask key="keyEquivalentModifierMask"/>
+                    <menu key="submenu" title="View" id="HyV-fh-RgO">
+                        <items>
+                            <menuItem title="Enter Full Screen" keyEquivalent="f" id="4J7-dP-txa">
+                                <modifierMask key="keyEquivalentModifierMask" control="YES" command="YES"/>
+                                <connections>
+                                    <action selector="toggleFullScreen:" target="-1" id="dU3-MA-1Rq"/>
+                                </connections>
+                            </menuItem>
+                        </items>
+                    </menu>
+                </menuItem>
+                <menuItem title="Window" id="aUF-d1-5bR">
+                    <modifierMask key="keyEquivalentModifierMask"/>
+                    <menu key="submenu" title="Window" systemMenu="window" id="Td7-aD-5lo">
+                        <items>
+                            <menuItem title="Minimize" keyEquivalent="m" id="OY7-WF-poV">
+                                <connections>
+                                    <action selector="performMiniaturize:" target="-1" id="VwT-WD-YPe"/>
+                                </connections>
+                            </menuItem>
+                            <menuItem title="Zoom" id="R4o-n2-Eq4">
+                                <modifierMask key="keyEquivalentModifierMask"/>
+                                <connections>
+                                    <action selector="performZoom:" target="-1" id="DIl-cC-cCs"/>
+                                </connections>
+                            </menuItem>
+                            <menuItem isSeparatorItem="YES" id="eu3-7i-yIM"/>
+                            <menuItem title="Bring All to Front" id="LE2-aR-0XJ">
+                                <modifierMask key="keyEquivalentModifierMask"/>
+                                <connections>
+                                    <action selector="arrangeInFront:" target="-1" id="DRN-fu-gQh"/>
+                                </connections>
+                            </menuItem>
+                        </items>
+                    </menu>
+                </menuItem>
+            </items>
+            <point key="canvasLocation" x="142" y="-258"/>
+        </menu>
+        <window title="APP_NAME" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" animationBehavior="default" id="QvC-M9-y7g" customClass="MainFlutterWindow" customModule="Runner" customModuleProvider="target">
+            <windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
+            <rect key="contentRect" x="335" y="390" width="800" height="600"/>
+            <rect key="screenRect" x="0.0" y="0.0" width="2560" height="1577"/>
+            <view key="contentView" wantsLayer="YES" id="EiT-Mj-1SZ">
+                <rect key="frame" x="0.0" y="0.0" width="800" height="600"/>
+                <autoresizingMask key="autoresizingMask"/>
+            </view>
+        </window>
+    </objects>
+</document>
diff --git a/packages/rfw/example/remote/macos/Runner/Configs/AppInfo.xcconfig b/packages/rfw/example/remote/macos/Runner/Configs/AppInfo.xcconfig
new file mode 100644
index 0000000..6d859b0
--- /dev/null
+++ b/packages/rfw/example/remote/macos/Runner/Configs/AppInfo.xcconfig
@@ -0,0 +1,14 @@
+// Application-level settings for the Runner target.
+//
+// This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the
+// future. If not, the values below would default to using the project name when this becomes a
+// 'flutter create' template.
+
+// The application's name. By default this is also the title of the Flutter window.
+PRODUCT_NAME = remote
+
+// The application's bundle identifier
+PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.rfw.examples.remote
+
+// The copyright displayed in application information
+PRODUCT_COPYRIGHT = Copyright © 2021 dev.flutter.rfw.examples. All rights reserved.
diff --git a/packages/rfw/example/remote/macos/Runner/Configs/Debug.xcconfig b/packages/rfw/example/remote/macos/Runner/Configs/Debug.xcconfig
new file mode 100644
index 0000000..36b0fd9
--- /dev/null
+++ b/packages/rfw/example/remote/macos/Runner/Configs/Debug.xcconfig
@@ -0,0 +1,2 @@
+#include "../../Flutter/Flutter-Debug.xcconfig"
+#include "Warnings.xcconfig"
diff --git a/packages/rfw/example/remote/macos/Runner/Configs/Release.xcconfig b/packages/rfw/example/remote/macos/Runner/Configs/Release.xcconfig
new file mode 100644
index 0000000..dff4f49
--- /dev/null
+++ b/packages/rfw/example/remote/macos/Runner/Configs/Release.xcconfig
@@ -0,0 +1,2 @@
+#include "../../Flutter/Flutter-Release.xcconfig"
+#include "Warnings.xcconfig"
diff --git a/packages/rfw/example/remote/macos/Runner/Configs/Warnings.xcconfig b/packages/rfw/example/remote/macos/Runner/Configs/Warnings.xcconfig
new file mode 100644
index 0000000..42bcbf4
--- /dev/null
+++ b/packages/rfw/example/remote/macos/Runner/Configs/Warnings.xcconfig
@@ -0,0 +1,13 @@
+WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings
+GCC_WARN_UNDECLARED_SELECTOR = YES
+CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES
+CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE
+CLANG_WARN__DUPLICATE_METHOD_MATCH = YES
+CLANG_WARN_PRAGMA_PACK = YES
+CLANG_WARN_STRICT_PROTOTYPES = YES
+CLANG_WARN_COMMA = YES
+GCC_WARN_STRICT_SELECTOR_MATCH = YES
+CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES
+CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES
+GCC_WARN_SHADOW = YES
+CLANG_WARN_UNREACHABLE_CODE = YES
diff --git a/packages/rfw/example/remote/macos/Runner/DebugProfile.entitlements b/packages/rfw/example/remote/macos/Runner/DebugProfile.entitlements
new file mode 100644
index 0000000..dddb8a3
--- /dev/null
+++ b/packages/rfw/example/remote/macos/Runner/DebugProfile.entitlements
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>com.apple.security.app-sandbox</key>
+	<true/>
+	<key>com.apple.security.cs.allow-jit</key>
+	<true/>
+	<key>com.apple.security.network.server</key>
+	<true/>
+</dict>
+</plist>
diff --git a/packages/rfw/example/remote/macos/Runner/Info.plist b/packages/rfw/example/remote/macos/Runner/Info.plist
new file mode 100644
index 0000000..4789daa
--- /dev/null
+++ b/packages/rfw/example/remote/macos/Runner/Info.plist
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>CFBundleDevelopmentRegion</key>
+	<string>$(DEVELOPMENT_LANGUAGE)</string>
+	<key>CFBundleExecutable</key>
+	<string>$(EXECUTABLE_NAME)</string>
+	<key>CFBundleIconFile</key>
+	<string></string>
+	<key>CFBundleIdentifier</key>
+	<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
+	<key>CFBundleInfoDictionaryVersion</key>
+	<string>6.0</string>
+	<key>CFBundleName</key>
+	<string>$(PRODUCT_NAME)</string>
+	<key>CFBundlePackageType</key>
+	<string>APPL</string>
+	<key>CFBundleShortVersionString</key>
+	<string>$(FLUTTER_BUILD_NAME)</string>
+	<key>CFBundleVersion</key>
+	<string>$(FLUTTER_BUILD_NUMBER)</string>
+	<key>LSMinimumSystemVersion</key>
+	<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
+	<key>NSHumanReadableCopyright</key>
+	<string>$(PRODUCT_COPYRIGHT)</string>
+	<key>NSMainNibFile</key>
+	<string>MainMenu</string>
+	<key>NSPrincipalClass</key>
+	<string>NSApplication</string>
+</dict>
+</plist>
diff --git a/packages/rfw/example/remote/macos/Runner/MainFlutterWindow.swift b/packages/rfw/example/remote/macos/Runner/MainFlutterWindow.swift
new file mode 100644
index 0000000..32aaeed
--- /dev/null
+++ b/packages/rfw/example/remote/macos/Runner/MainFlutterWindow.swift
@@ -0,0 +1,19 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import Cocoa
+import FlutterMacOS
+
+class MainFlutterWindow: NSWindow {
+  override func awakeFromNib() {
+    let flutterViewController = FlutterViewController.init()
+    let windowFrame = self.frame
+    self.contentViewController = flutterViewController
+    self.setFrame(windowFrame, display: true)
+
+    RegisterGeneratedPlugins(registry: flutterViewController)
+
+    super.awakeFromNib()
+  }
+}
diff --git a/packages/rfw/example/remote/macos/Runner/Release.entitlements b/packages/rfw/example/remote/macos/Runner/Release.entitlements
new file mode 100644
index 0000000..852fa1a
--- /dev/null
+++ b/packages/rfw/example/remote/macos/Runner/Release.entitlements
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>com.apple.security.app-sandbox</key>
+	<true/>
+</dict>
+</plist>
diff --git a/packages/rfw/example/remote/pubspec.yaml b/packages/rfw/example/remote/pubspec.yaml
new file mode 100644
index 0000000..82f5890
--- /dev/null
+++ b/packages/rfw/example/remote/pubspec.yaml
@@ -0,0 +1,18 @@
+name: remote
+description: Example of fetching remote widgets for RFW
+publish_to: 'none' # Remove this line if you wish to publish to pub.dev
+version: 1.0.0+1
+
+environment:
+  sdk: ">=2.13.0 <3.0.0"
+
+dependencies:
+  flutter:
+    sdk: flutter
+  path: ^1.8.0
+  path_provider: ^2.0.2
+  rfw:
+    path: ../../
+
+flutter:
+  uses-material-design: true
diff --git a/packages/rfw/example/remote/remote_widget_libraries/README.md b/packages/rfw/example/remote/remote_widget_libraries/README.md
new file mode 100644
index 0000000..3febefa
--- /dev/null
+++ b/packages/rfw/example/remote/remote_widget_libraries/README.md
@@ -0,0 +1,5 @@
+This directory contains the source files (.rfwtxt) for the binary UI
+descriptions (.rfw) that are used by the demo.
+
+To convert the source files to binary files run `encode.dart` in this
+directory.
\ No newline at end of file
diff --git a/packages/rfw/example/remote/remote_widget_libraries/counter_app1.rfw b/packages/rfw/example/remote/remote_widget_libraries/counter_app1.rfw
new file mode 100644
index 0000000..b39a0a1
--- /dev/null
+++ b/packages/rfw/example/remote/remote_widget_libraries/counter_app1.rfw
Binary files differ
diff --git a/packages/rfw/example/remote/remote_widget_libraries/counter_app1.rfwtxt b/packages/rfw/example/remote/remote_widget_libraries/counter_app1.rfwtxt
new file mode 100644
index 0000000..b6ffd07
--- /dev/null
+++ b/packages/rfw/example/remote/remote_widget_libraries/counter_app1.rfwtxt
@@ -0,0 +1,22 @@
+import core.widgets;
+import core.material;
+
+widget Counter = Scaffold(
+  appBar: AppBar(title: Text(text: "Counter Demo")),
+  body: Center(
+    child: Column(
+      mainAxisAlignment: "center",
+      children: [
+        Text(text: 'You have pushed the button this many times:', textAlign: "center"),
+        Text(text: data.counter, style: {
+          fontSize: 36.0,
+        }),
+      ],
+    ),
+  ),
+  floatingActionButton: FloatingActionButton(
+    onPressed: event "increment" { },
+    tooltip: "Increment",
+    child: Icon(icon: 0xE047, fontFamily: "MaterialIcons"),
+  ),
+);
diff --git a/packages/rfw/example/remote/remote_widget_libraries/counter_app2.rfw b/packages/rfw/example/remote/remote_widget_libraries/counter_app2.rfw
new file mode 100644
index 0000000..b7d112b
--- /dev/null
+++ b/packages/rfw/example/remote/remote_widget_libraries/counter_app2.rfw
Binary files differ
diff --git a/packages/rfw/example/remote/remote_widget_libraries/counter_app2.rfwtxt b/packages/rfw/example/remote/remote_widget_libraries/counter_app2.rfwtxt
new file mode 100644
index 0000000..a4fbcee
--- /dev/null
+++ b/packages/rfw/example/remote/remote_widget_libraries/counter_app2.rfwtxt
@@ -0,0 +1,59 @@
+import core.widgets;
+import core.material;
+
+widget Counter = Container(
+  color: 0xFF66AACC,
+  child: Center(
+    child: Button(
+      child: Padding(
+        padding: [ 20.0 ],
+        child: Text(text: data.counter, style: {
+          fontSize: 56.0,
+          color: 0xFF000000,
+        }),
+      ),
+      onPressed: event 'increment' { },
+    ),
+  ),
+);
+
+widget Button { down: false } = GestureDetector(
+  onTap: args.onPressed,
+  onTapDown: set state.down = true,
+  onTapUp: set state.down = false,
+  onTapCancel: set state.down = false,
+  child: Container(
+    duration: 50,
+    margin: switch state.down {
+      false: [ 0.0, 0.0, 2.0, 2.0 ],
+      true: [ 2.0, 2.0, 0.0, 0.0 ],
+    },
+    padding: [ 12.0, 8.0 ],
+    decoration: {
+      type: "shape",
+      shape: {
+        type: "stadium",
+        side: { width: 1.0 },
+      },
+      gradient: {
+        type: "linear",
+        begin: { x: -0.5, y: -0.25 },
+        end: { x: 0.0, y: 0.5 },
+        colors: [ 0xFFFFFF99, 0xFFEEDD00 ],
+        stops: [ 0.0, 1.0 ],
+        tileMode: "mirror",
+      },
+      shadows: switch state.down {
+        false: [ { blurRadius: 4.0, spreadRadius: 0.5, offset: { x: 1.0, y: 1.0, } } ],
+        default: [],
+      },
+    },
+    child: DefaultTextStyle(
+      style: {
+        color: 0xFF000000,
+        fontSize: 32.0,
+      },
+      child: args.child,
+    ),
+  ),
+);
diff --git a/packages/rfw/example/remote/remote_widget_libraries/encode.dart b/packages/rfw/example/remote/remote_widget_libraries/encode.dart
new file mode 100644
index 0000000..a2be602
--- /dev/null
+++ b/packages/rfw/example/remote/remote_widget_libraries/encode.dart
@@ -0,0 +1,16 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This file is hand-formatted.
+
+import 'dart:io';
+
+import 'package:rfw/formats.dart';
+
+void main() {
+  final String counterApp1 = File('counter_app1.rfwtxt').readAsStringSync();
+  File('counter_app1.rfw').writeAsBytesSync(encodeLibraryBlob(parseLibraryFile(counterApp1)));
+  final String counterApp2 = File('counter_app2.rfwtxt').readAsStringSync();
+  File('counter_app2.rfw').writeAsBytesSync(encodeLibraryBlob(parseLibraryFile(counterApp2)));
+}
diff --git a/packages/rfw/example/remote/web/favicon.png b/packages/rfw/example/remote/web/favicon.png
new file mode 100644
index 0000000..8aaa46a
--- /dev/null
+++ b/packages/rfw/example/remote/web/favicon.png
Binary files differ
diff --git a/packages/rfw/example/remote/web/icons/Icon-192.png b/packages/rfw/example/remote/web/icons/Icon-192.png
new file mode 100644
index 0000000..b749bfe
--- /dev/null
+++ b/packages/rfw/example/remote/web/icons/Icon-192.png
Binary files differ
diff --git a/packages/rfw/example/remote/web/icons/Icon-512.png b/packages/rfw/example/remote/web/icons/Icon-512.png
new file mode 100644
index 0000000..88cfd48
--- /dev/null
+++ b/packages/rfw/example/remote/web/icons/Icon-512.png
Binary files differ
diff --git a/packages/rfw/example/remote/web/icons/Icon-maskable-192.png b/packages/rfw/example/remote/web/icons/Icon-maskable-192.png
new file mode 100644
index 0000000..eb9b4d7
--- /dev/null
+++ b/packages/rfw/example/remote/web/icons/Icon-maskable-192.png
Binary files differ
diff --git a/packages/rfw/example/remote/web/icons/Icon-maskable-512.png b/packages/rfw/example/remote/web/icons/Icon-maskable-512.png
new file mode 100644
index 0000000..d69c566
--- /dev/null
+++ b/packages/rfw/example/remote/web/icons/Icon-maskable-512.png
Binary files differ
diff --git a/packages/rfw/example/remote/web/index.html b/packages/rfw/example/remote/web/index.html
new file mode 100644
index 0000000..2bcc056
--- /dev/null
+++ b/packages/rfw/example/remote/web/index.html
@@ -0,0 +1,107 @@
+<!DOCTYPE html>
+<!-- Copyright 2013 The Flutter Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file. -->
+<html>
+<head>
+  <!--
+    If you are serving your web app in a path other than the root, change the
+    href value below to reflect the base path you are serving from.
+
+    The path provided below has to start and end with a slash "/" in order for
+    it to work correctly.
+
+    For more details:
+    * https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base
+
+    This is a placeholder for base href that will be replaced by the value of
+    the `--base-href` argument provided to `flutter build`.
+  -->
+  <base href="$FLUTTER_BASE_HREF">
+
+  <meta charset="UTF-8">
+  <meta content="IE=Edge" http-equiv="X-UA-Compatible">
+  <meta name="description" content="Example of fetching remote widgets for RFW">
+
+  <!-- iOS meta tags & icons -->
+  <meta name="apple-mobile-web-app-capable" content="yes">
+  <meta name="apple-mobile-web-app-status-bar-style" content="black">
+  <meta name="apple-mobile-web-app-title" content="remote">
+  <link rel="apple-touch-icon" href="icons/Icon-192.png">
+
+  <!-- Favicon -->
+  <link rel="icon" type="image/png" href="favicon.png"/>
+
+  <title>remote</title>
+  <link rel="manifest" href="manifest.json">
+</head>
+<body>
+  <!-- This script installs service_worker.js to provide PWA functionality to
+       application. For more information, see:
+       https://developers.google.com/web/fundamentals/primers/service-workers -->
+  <script>
+    var serviceWorkerVersion = null;
+    var scriptLoaded = false;
+    function loadMainDartJs() {
+      if (scriptLoaded) {
+        return;
+      }
+      scriptLoaded = true;
+      var scriptTag = document.createElement('script');
+      scriptTag.src = 'main.dart.js';
+      scriptTag.type = 'application/javascript';
+      document.body.append(scriptTag);
+    }
+
+    if ('serviceWorker' in navigator) {
+      // Service workers are supported. Use them.
+      window.addEventListener('load', function () {
+        // Wait for registration to finish before dropping the <script> tag.
+        // Otherwise, the browser will load the script multiple times,
+        // potentially different versions.
+        var serviceWorkerUrl = 'flutter_service_worker.js?v=' + serviceWorkerVersion;
+        navigator.serviceWorker.register(serviceWorkerUrl)
+          .then((reg) => {
+            function waitForActivation(serviceWorker) {
+              serviceWorker.addEventListener('statechange', () => {
+                if (serviceWorker.state == 'activated') {
+                  console.log('Installed new service worker.');
+                  loadMainDartJs();
+                }
+              });
+            }
+            if (!reg.active && (reg.installing || reg.waiting)) {
+              // No active web worker and we have installed or are installing
+              // one for the first time. Simply wait for it to activate.
+              waitForActivation(reg.installing || reg.waiting);
+            } else if (!reg.active.scriptURL.endsWith(serviceWorkerVersion)) {
+              // When the app updates the serviceWorkerVersion changes, so we
+              // need to ask the service worker to update.
+              console.log('New service worker available.');
+              reg.update();
+              waitForActivation(reg.installing);
+            } else {
+              // Existing service worker is still good.
+              console.log('Loading app from service worker.');
+              loadMainDartJs();
+            }
+          });
+
+        // If service worker doesn't succeed in a reasonable amount of time,
+        // fallback to plaint <script> tag.
+        setTimeout(() => {
+          if (!scriptLoaded) {
+            console.warn(
+              'Failed to load app from service worker. Falling back to plain <script> tag.',
+            );
+            loadMainDartJs();
+          }
+        }, 4000);
+      });
+    } else {
+      // Service workers not supported. Just drop the <script> tag.
+      loadMainDartJs();
+    }
+  </script>
+</body>
+</html>
diff --git a/packages/rfw/example/remote/web/manifest.json b/packages/rfw/example/remote/web/manifest.json
new file mode 100644
index 0000000..1ef5a87
--- /dev/null
+++ b/packages/rfw/example/remote/web/manifest.json
@@ -0,0 +1,35 @@
+{
+    "name": "remote",
+    "short_name": "remote",
+    "start_url": ".",
+    "display": "standalone",
+    "background_color": "#0175C2",
+    "theme_color": "#0175C2",
+    "description": "Example of fetching remote widgets for RFW",
+    "orientation": "portrait-primary",
+    "prefer_related_applications": false,
+    "icons": [
+        {
+            "src": "icons/Icon-192.png",
+            "sizes": "192x192",
+            "type": "image/png"
+        },
+        {
+            "src": "icons/Icon-512.png",
+            "sizes": "512x512",
+            "type": "image/png"
+        },
+        {
+            "src": "icons/Icon-maskable-192.png",
+            "sizes": "192x192",
+            "type": "image/png",
+            "purpose": "maskable"
+        },
+        {
+            "src": "icons/Icon-maskable-512.png",
+            "sizes": "512x512",
+            "type": "image/png",
+            "purpose": "maskable"
+        }
+    ]
+}
diff --git a/packages/rfw/example/remote/windows/.gitignore b/packages/rfw/example/remote/windows/.gitignore
new file mode 100644
index 0000000..d492d0d
--- /dev/null
+++ b/packages/rfw/example/remote/windows/.gitignore
@@ -0,0 +1,17 @@
+flutter/ephemeral/
+
+# Visual Studio user-specific files.
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# Visual Studio build-related files.
+x64/
+x86/
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!*.[Cc]ache/
diff --git a/packages/rfw/example/remote/windows/CMakeLists.txt b/packages/rfw/example/remote/windows/CMakeLists.txt
new file mode 100644
index 0000000..cb7ce77
--- /dev/null
+++ b/packages/rfw/example/remote/windows/CMakeLists.txt
@@ -0,0 +1,95 @@
+cmake_minimum_required(VERSION 3.15)
+project(remote LANGUAGES CXX)
+
+set(BINARY_NAME "remote")
+
+cmake_policy(SET CMP0063 NEW)
+
+set(CMAKE_INSTALL_RPATH "$ORIGIN/lib")
+
+# Configure build options.
+get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
+if(IS_MULTICONFIG)
+  set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release"
+    CACHE STRING "" FORCE)
+else()
+  if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
+    set(CMAKE_BUILD_TYPE "Debug" CACHE
+      STRING "Flutter build mode" FORCE)
+    set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
+      "Debug" "Profile" "Release")
+  endif()
+endif()
+
+set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}")
+set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}")
+set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}")
+set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}")
+
+# Use Unicode for all projects.
+add_definitions(-DUNICODE -D_UNICODE)
+
+# Compilation settings that should be applied to most targets.
+function(APPLY_STANDARD_SETTINGS TARGET)
+  target_compile_features(${TARGET} PUBLIC cxx_std_17)
+  target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100")
+  target_compile_options(${TARGET} PRIVATE /EHsc)
+  target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0")
+  target_compile_definitions(${TARGET} PRIVATE "$<$<CONFIG:Debug>:_DEBUG>")
+endfunction()
+
+set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter")
+
+# Flutter library and tool build rules.
+add_subdirectory(${FLUTTER_MANAGED_DIR})
+
+# Application build
+add_subdirectory("runner")
+
+# Generated plugin build rules, which manage building the plugins and adding
+# them to the application.
+include(flutter/generated_plugins.cmake)
+
+
+# === Installation ===
+# Support files are copied into place next to the executable, so that it can
+# run in place. This is done instead of making a separate bundle (as on Linux)
+# so that building and running from within Visual Studio will work.
+set(BUILD_BUNDLE_DIR "$<TARGET_FILE_DIR:${BINARY_NAME}>")
+# Make the "install" step default, as it's required to run.
+set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1)
+if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
+  set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE)
+endif()
+
+set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data")
+set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}")
+
+install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}"
+  COMPONENT Runtime)
+
+install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
+  COMPONENT Runtime)
+
+install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
+  COMPONENT Runtime)
+
+if(PLUGIN_BUNDLED_LIBRARIES)
+  install(FILES "${PLUGIN_BUNDLED_LIBRARIES}"
+    DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
+    COMPONENT Runtime)
+endif()
+
+# Fully re-copy the assets directory on each build to avoid having stale files
+# from a previous install.
+set(FLUTTER_ASSET_DIR_NAME "flutter_assets")
+install(CODE "
+  file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\")
+  " COMPONENT Runtime)
+install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}"
+  DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime)
+
+# Install the AOT library on non-Debug builds only.
+install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
+  CONFIGURATIONS Profile;Release
+  COMPONENT Runtime)
diff --git a/packages/rfw/example/remote/windows/flutter/CMakeLists.txt b/packages/rfw/example/remote/windows/flutter/CMakeLists.txt
new file mode 100644
index 0000000..b02c548
--- /dev/null
+++ b/packages/rfw/example/remote/windows/flutter/CMakeLists.txt
@@ -0,0 +1,103 @@
+cmake_minimum_required(VERSION 3.15)
+
+set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral")
+
+# Configuration provided via flutter tool.
+include(${EPHEMERAL_DIR}/generated_config.cmake)
+
+# TODO: Move the rest of this into files in ephemeral. See
+# https://github.com/flutter/flutter/issues/57146.
+set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper")
+
+# === Flutter Library ===
+set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll")
+
+# Published to parent scope for install step.
+set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE)
+set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE)
+set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE)
+set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE)
+
+list(APPEND FLUTTER_LIBRARY_HEADERS
+  "flutter_export.h"
+  "flutter_windows.h"
+  "flutter_messenger.h"
+  "flutter_plugin_registrar.h"
+  "flutter_texture_registrar.h"
+)
+list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/")
+add_library(flutter INTERFACE)
+target_include_directories(flutter INTERFACE
+  "${EPHEMERAL_DIR}"
+)
+target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib")
+add_dependencies(flutter flutter_assemble)
+
+# === Wrapper ===
+list(APPEND CPP_WRAPPER_SOURCES_CORE
+  "core_implementations.cc"
+  "standard_codec.cc"
+)
+list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/")
+list(APPEND CPP_WRAPPER_SOURCES_PLUGIN
+  "plugin_registrar.cc"
+)
+list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/")
+list(APPEND CPP_WRAPPER_SOURCES_APP
+  "flutter_engine.cc"
+  "flutter_view_controller.cc"
+)
+list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/")
+
+# Wrapper sources needed for a plugin.
+add_library(flutter_wrapper_plugin STATIC
+  ${CPP_WRAPPER_SOURCES_CORE}
+  ${CPP_WRAPPER_SOURCES_PLUGIN}
+)
+apply_standard_settings(flutter_wrapper_plugin)
+set_target_properties(flutter_wrapper_plugin PROPERTIES
+  POSITION_INDEPENDENT_CODE ON)
+set_target_properties(flutter_wrapper_plugin PROPERTIES
+  CXX_VISIBILITY_PRESET hidden)
+target_link_libraries(flutter_wrapper_plugin PUBLIC flutter)
+target_include_directories(flutter_wrapper_plugin PUBLIC
+  "${WRAPPER_ROOT}/include"
+)
+add_dependencies(flutter_wrapper_plugin flutter_assemble)
+
+# Wrapper sources needed for the runner.
+add_library(flutter_wrapper_app STATIC
+  ${CPP_WRAPPER_SOURCES_CORE}
+  ${CPP_WRAPPER_SOURCES_APP}
+)
+apply_standard_settings(flutter_wrapper_app)
+target_link_libraries(flutter_wrapper_app PUBLIC flutter)
+target_include_directories(flutter_wrapper_app PUBLIC
+  "${WRAPPER_ROOT}/include"
+)
+add_dependencies(flutter_wrapper_app flutter_assemble)
+
+# === Flutter tool backend ===
+# _phony_ is a non-existent file to force this command to run every time,
+# since currently there's no way to get a full input/output list from the
+# flutter tool.
+set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_")
+set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE)
+add_custom_command(
+  OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS}
+    ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN}
+    ${CPP_WRAPPER_SOURCES_APP}
+    ${PHONY_OUTPUT}
+  COMMAND ${CMAKE_COMMAND} -E env
+    ${FLUTTER_TOOL_ENVIRONMENT}
+    "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat"
+      windows-x64 $<CONFIG>
+  VERBATIM
+)
+add_custom_target(flutter_assemble DEPENDS
+  "${FLUTTER_LIBRARY}"
+  ${FLUTTER_LIBRARY_HEADERS}
+  ${CPP_WRAPPER_SOURCES_CORE}
+  ${CPP_WRAPPER_SOURCES_PLUGIN}
+  ${CPP_WRAPPER_SOURCES_APP}
+)
diff --git a/packages/rfw/example/remote/windows/flutter/generated_plugins.cmake b/packages/rfw/example/remote/windows/flutter/generated_plugins.cmake
new file mode 100644
index 0000000..4d10c25
--- /dev/null
+++ b/packages/rfw/example/remote/windows/flutter/generated_plugins.cmake
@@ -0,0 +1,15 @@
+#
+# Generated file, do not edit.
+#
+
+list(APPEND FLUTTER_PLUGIN_LIST
+)
+
+set(PLUGIN_BUNDLED_LIBRARIES)
+
+foreach(plugin ${FLUTTER_PLUGIN_LIST})
+  add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin})
+  target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)
+  list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>)
+  list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
+endforeach(plugin)
diff --git a/packages/rfw/example/remote/windows/runner/CMakeLists.txt b/packages/rfw/example/remote/windows/runner/CMakeLists.txt
new file mode 100644
index 0000000..0b899a0
--- /dev/null
+++ b/packages/rfw/example/remote/windows/runner/CMakeLists.txt
@@ -0,0 +1,17 @@
+cmake_minimum_required(VERSION 3.15)
+project(runner LANGUAGES CXX)
+
+add_executable(${BINARY_NAME} WIN32
+  "flutter_window.cpp"
+  "main.cpp"
+  "utils.cpp"
+  "win32_window.cpp"
+  "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc"
+  "Runner.rc"
+  "runner.exe.manifest"
+)
+apply_standard_settings(${BINARY_NAME})
+target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX")
+target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app)
+target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}")
+add_dependencies(${BINARY_NAME} flutter_assemble)
diff --git a/packages/rfw/example/remote/windows/runner/Runner.rc b/packages/rfw/example/remote/windows/runner/Runner.rc
new file mode 100644
index 0000000..991f8f5
--- /dev/null
+++ b/packages/rfw/example/remote/windows/runner/Runner.rc
@@ -0,0 +1,121 @@
+// Microsoft Visual C++ generated resource script.
+//
+#pragma code_page(65001)
+#include "resource.h"
+
+#define APSTUDIO_READONLY_SYMBOLS
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 2 resource.
+//
+#include "winres.h"
+
+/////////////////////////////////////////////////////////////////////////////
+#undef APSTUDIO_READONLY_SYMBOLS
+
+/////////////////////////////////////////////////////////////////////////////
+// English (United States) resources
+
+#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
+LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
+
+#ifdef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// TEXTINCLUDE
+//
+
+1 TEXTINCLUDE
+BEGIN
+    "resource.h\0"
+END
+
+2 TEXTINCLUDE
+BEGIN
+    "#include ""winres.h""\r\n"
+    "\0"
+END
+
+3 TEXTINCLUDE
+BEGIN
+    "\r\n"
+    "\0"
+END
+
+#endif    // APSTUDIO_INVOKED
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Icon
+//
+
+// Icon with lowest ID value placed first to ensure application icon
+// remains consistent on all systems.
+IDI_APP_ICON            ICON                    "resources\\app_icon.ico"
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Version
+//
+
+#ifdef FLUTTER_BUILD_NUMBER
+#define VERSION_AS_NUMBER FLUTTER_BUILD_NUMBER
+#else
+#define VERSION_AS_NUMBER 1,0,0
+#endif
+
+#ifdef FLUTTER_BUILD_NAME
+#define VERSION_AS_STRING #FLUTTER_BUILD_NAME
+#else
+#define VERSION_AS_STRING "1.0.0"
+#endif
+
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION VERSION_AS_NUMBER
+ PRODUCTVERSION VERSION_AS_NUMBER
+ FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
+#ifdef _DEBUG
+ FILEFLAGS VS_FF_DEBUG
+#else
+ FILEFLAGS 0x0L
+#endif
+ FILEOS VOS__WINDOWS32
+ FILETYPE VFT_APP
+ FILESUBTYPE 0x0L
+BEGIN
+    BLOCK "StringFileInfo"
+    BEGIN
+        BLOCK "040904e4"
+        BEGIN
+            VALUE "CompanyName", "dev.flutter.rfw.examples" "\0"
+            VALUE "FileDescription", "Example of fetching remote widgets for RFW" "\0"
+            VALUE "FileVersion", VERSION_AS_STRING "\0"
+            VALUE "InternalName", "remote" "\0"
+            VALUE "LegalCopyright", "Copyright (C) 2021 dev.flutter.rfw.examples. All rights reserved." "\0"
+            VALUE "OriginalFilename", "remote.exe" "\0"
+            VALUE "ProductName", "remote" "\0"
+            VALUE "ProductVersion", VERSION_AS_STRING "\0"
+        END
+    END
+    BLOCK "VarFileInfo"
+    BEGIN
+        VALUE "Translation", 0x409, 1252
+    END
+END
+
+#endif    // English (United States) resources
+/////////////////////////////////////////////////////////////////////////////
+
+
+
+#ifndef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 3 resource.
+//
+
+
+/////////////////////////////////////////////////////////////////////////////
+#endif    // not APSTUDIO_INVOKED
diff --git a/packages/rfw/example/remote/windows/runner/flutter_window.cpp b/packages/rfw/example/remote/windows/runner/flutter_window.cpp
new file mode 100644
index 0000000..8254bd9
--- /dev/null
+++ b/packages/rfw/example/remote/windows/runner/flutter_window.cpp
@@ -0,0 +1,65 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "flutter_window.h"
+
+#include <optional>
+
+#include "flutter/generated_plugin_registrant.h"
+
+FlutterWindow::FlutterWindow(const flutter::DartProject& project)
+    : project_(project) {}
+
+FlutterWindow::~FlutterWindow() {}
+
+bool FlutterWindow::OnCreate() {
+  if (!Win32Window::OnCreate()) {
+    return false;
+  }
+
+  RECT frame = GetClientArea();
+
+  // The size here must match the window dimensions to avoid unnecessary surface
+  // creation / destruction in the startup path.
+  flutter_controller_ = std::make_unique<flutter::FlutterViewController>(
+      frame.right - frame.left, frame.bottom - frame.top, project_);
+  // Ensure that basic setup of the controller was successful.
+  if (!flutter_controller_->engine() || !flutter_controller_->view()) {
+    return false;
+  }
+  RegisterPlugins(flutter_controller_->engine());
+  SetChildContent(flutter_controller_->view()->GetNativeWindow());
+  return true;
+}
+
+void FlutterWindow::OnDestroy() {
+  if (flutter_controller_) {
+    flutter_controller_ = nullptr;
+  }
+
+  Win32Window::OnDestroy();
+}
+
+LRESULT
+FlutterWindow::MessageHandler(HWND hwnd, UINT const message,
+                              WPARAM const wparam,
+                              LPARAM const lparam) noexcept {
+  // Give Flutter, including plugins, an opportunity to handle window messages.
+  if (flutter_controller_) {
+    std::optional<LRESULT> result =
+        flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam,
+                                                      lparam);
+    if (result) {
+      return *result;
+    }
+  }
+
+  switch (message) {
+    case WM_FONTCHANGE:
+      flutter_controller_->engine()->ReloadSystemFonts();
+      break;
+  }
+
+  return Win32Window::MessageHandler(hwnd, message, wparam, lparam);
+}
diff --git a/packages/rfw/example/remote/windows/runner/flutter_window.h b/packages/rfw/example/remote/windows/runner/flutter_window.h
new file mode 100644
index 0000000..f1fc669
--- /dev/null
+++ b/packages/rfw/example/remote/windows/runner/flutter_window.h
@@ -0,0 +1,37 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef RUNNER_FLUTTER_WINDOW_H_
+#define RUNNER_FLUTTER_WINDOW_H_
+
+#include <flutter/dart_project.h>
+#include <flutter/flutter_view_controller.h>
+
+#include <memory>
+
+#include "win32_window.h"
+
+// A window that does nothing but host a Flutter view.
+class FlutterWindow : public Win32Window {
+ public:
+  // Creates a new FlutterWindow hosting a Flutter view running |project|.
+  explicit FlutterWindow(const flutter::DartProject& project);
+  virtual ~FlutterWindow();
+
+ protected:
+  // Win32Window:
+  bool OnCreate() override;
+  void OnDestroy() override;
+  LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam,
+                         LPARAM const lparam) noexcept override;
+
+ private:
+  // The project to run.
+  flutter::DartProject project_;
+
+  // The Flutter instance hosted by this window.
+  std::unique_ptr<flutter::FlutterViewController> flutter_controller_;
+};
+
+#endif  // RUNNER_FLUTTER_WINDOW_H_
diff --git a/packages/rfw/example/remote/windows/runner/main.cpp b/packages/rfw/example/remote/windows/runner/main.cpp
new file mode 100644
index 0000000..cc84fb7
--- /dev/null
+++ b/packages/rfw/example/remote/windows/runner/main.cpp
@@ -0,0 +1,46 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <flutter/dart_project.h>
+#include <flutter/flutter_view_controller.h>
+#include <windows.h>
+
+#include "flutter_window.h"
+#include "utils.h"
+
+int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
+                      _In_ wchar_t *command_line, _In_ int show_command) {
+  // Attach to console when present (e.g., 'flutter run') or create a
+  // new console when running with a debugger.
+  if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) {
+    CreateAndAttachConsole();
+  }
+
+  // Initialize COM, so that it is available for use in the library and/or
+  // plugins.
+  ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
+
+  flutter::DartProject project(L"data");
+
+  std::vector<std::string> command_line_arguments = GetCommandLineArguments();
+
+  project.set_dart_entrypoint_arguments(std::move(command_line_arguments));
+
+  FlutterWindow window(project);
+  Win32Window::Point origin(10, 10);
+  Win32Window::Size size(1280, 720);
+  if (!window.CreateAndShow(L"remote", origin, size)) {
+    return EXIT_FAILURE;
+  }
+  window.SetQuitOnClose(true);
+
+  ::MSG msg;
+  while (::GetMessage(&msg, nullptr, 0, 0)) {
+    ::TranslateMessage(&msg);
+    ::DispatchMessage(&msg);
+  }
+
+  ::CoUninitialize();
+  return EXIT_SUCCESS;
+}
diff --git a/packages/rfw/example/remote/windows/runner/resource.h b/packages/rfw/example/remote/windows/runner/resource.h
new file mode 100644
index 0000000..d5d958d
--- /dev/null
+++ b/packages/rfw/example/remote/windows/runner/resource.h
@@ -0,0 +1,16 @@
+//{{NO_DEPENDENCIES}}
+// Microsoft Visual C++ generated include file.
+// Used by Runner.rc
+//
+#define IDI_APP_ICON 101
+
+// Next default values for new objects
+//
+#ifdef APSTUDIO_INVOKED
+#ifndef APSTUDIO_READONLY_SYMBOLS
+#define _APS_NEXT_RESOURCE_VALUE 102
+#define _APS_NEXT_COMMAND_VALUE 40001
+#define _APS_NEXT_CONTROL_VALUE 1001
+#define _APS_NEXT_SYMED_VALUE 101
+#endif
+#endif
diff --git a/packages/rfw/example/remote/windows/runner/resources/app_icon.ico b/packages/rfw/example/remote/windows/runner/resources/app_icon.ico
new file mode 100644
index 0000000..c04e20c
--- /dev/null
+++ b/packages/rfw/example/remote/windows/runner/resources/app_icon.ico
Binary files differ
diff --git a/packages/rfw/example/remote/windows/runner/runner.exe.manifest b/packages/rfw/example/remote/windows/runner/runner.exe.manifest
new file mode 100644
index 0000000..c977c4a
--- /dev/null
+++ b/packages/rfw/example/remote/windows/runner/runner.exe.manifest
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
+  <application xmlns="urn:schemas-microsoft-com:asm.v3">
+    <windowsSettings>
+      <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
+    </windowsSettings>
+  </application>
+  <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
+    <application>
+      <!-- Windows 10 -->
+      <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
+      <!-- Windows 8.1 -->
+      <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
+      <!-- Windows 8 -->
+      <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
+      <!-- Windows 7 -->
+      <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
+    </application>
+  </compatibility>
+</assembly>
diff --git a/packages/rfw/example/remote/windows/runner/utils.cpp b/packages/rfw/example/remote/windows/runner/utils.cpp
new file mode 100644
index 0000000..fb7e945
--- /dev/null
+++ b/packages/rfw/example/remote/windows/runner/utils.cpp
@@ -0,0 +1,67 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "utils.h"
+
+#include <flutter_windows.h>
+#include <io.h>
+#include <stdio.h>
+#include <windows.h>
+
+#include <iostream>
+
+void CreateAndAttachConsole() {
+  if (::AllocConsole()) {
+    FILE* unused;
+    if (freopen_s(&unused, "CONOUT$", "w", stdout)) {
+      _dup2(_fileno(stdout), 1);
+    }
+    if (freopen_s(&unused, "CONOUT$", "w", stderr)) {
+      _dup2(_fileno(stdout), 2);
+    }
+    std::ios::sync_with_stdio();
+    FlutterDesktopResyncOutputStreams();
+  }
+}
+
+std::vector<std::string> GetCommandLineArguments() {
+  // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use.
+  int argc;
+  wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc);
+  if (argv == nullptr) {
+    return std::vector<std::string>();
+  }
+
+  std::vector<std::string> command_line_arguments;
+
+  // Skip the first argument as it's the binary name.
+  for (int i = 1; i < argc; i++) {
+    command_line_arguments.push_back(Utf8FromUtf16(argv[i]));
+  }
+
+  ::LocalFree(argv);
+
+  return command_line_arguments;
+}
+
+std::string Utf8FromUtf16(const wchar_t* utf16_string) {
+  if (utf16_string == nullptr) {
+    return std::string();
+  }
+  int target_length =
+      ::WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, -1,
+                            nullptr, 0, nullptr, nullptr);
+  if (target_length == 0) {
+    return std::string();
+  }
+  std::string utf8_string;
+  utf8_string.resize(target_length);
+  int converted_length = ::WideCharToMultiByte(
+      CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, -1, utf8_string.data(),
+      target_length, nullptr, nullptr);
+  if (converted_length == 0) {
+    return std::string();
+  }
+  return utf8_string;
+}
diff --git a/packages/rfw/example/remote/windows/runner/utils.h b/packages/rfw/example/remote/windows/runner/utils.h
new file mode 100644
index 0000000..bd81e1e
--- /dev/null
+++ b/packages/rfw/example/remote/windows/runner/utils.h
@@ -0,0 +1,23 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef RUNNER_UTILS_H_
+#define RUNNER_UTILS_H_
+
+#include <string>
+#include <vector>
+
+// Creates a console for the process, and redirects stdout and stderr to
+// it for both the runner and the Flutter library.
+void CreateAndAttachConsole();
+
+// Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string
+// encoded in UTF-8. Returns an empty std::string on failure.
+std::string Utf8FromUtf16(const wchar_t* utf16_string);
+
+// Gets the command line arguments passed in as a std::vector<std::string>,
+// encoded in UTF-8. Returns an empty std::vector<std::string> on failure.
+std::vector<std::string> GetCommandLineArguments();
+
+#endif  // RUNNER_UTILS_H_
diff --git a/packages/rfw/example/remote/windows/runner/win32_window.cpp b/packages/rfw/example/remote/windows/runner/win32_window.cpp
new file mode 100644
index 0000000..85aa361
--- /dev/null
+++ b/packages/rfw/example/remote/windows/runner/win32_window.cpp
@@ -0,0 +1,241 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "win32_window.h"
+
+#include <flutter_windows.h>
+
+#include "resource.h"
+
+namespace {
+
+constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW";
+
+// The number of Win32Window objects that currently exist.
+static int g_active_window_count = 0;
+
+using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd);
+
+// Scale helper to convert logical scaler values to physical using passed in
+// scale factor
+int Scale(int source, double scale_factor) {
+  return static_cast<int>(source * scale_factor);
+}
+
+// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module.
+// This API is only needed for PerMonitor V1 awareness mode.
+void EnableFullDpiSupportIfAvailable(HWND hwnd) {
+  HMODULE user32_module = LoadLibraryA("User32.dll");
+  if (!user32_module) {
+    return;
+  }
+  auto enable_non_client_dpi_scaling =
+      reinterpret_cast<EnableNonClientDpiScaling*>(
+          GetProcAddress(user32_module, "EnableNonClientDpiScaling"));
+  if (enable_non_client_dpi_scaling != nullptr) {
+    enable_non_client_dpi_scaling(hwnd);
+    FreeLibrary(user32_module);
+  }
+}
+
+}  // namespace
+
+// Manages the Win32Window's window class registration.
+class WindowClassRegistrar {
+ public:
+  ~WindowClassRegistrar() = default;
+
+  // Returns the singleton registar instance.
+  static WindowClassRegistrar* GetInstance() {
+    if (!instance_) {
+      instance_ = new WindowClassRegistrar();
+    }
+    return instance_;
+  }
+
+  // Returns the name of the window class, registering the class if it hasn't
+  // previously been registered.
+  const wchar_t* GetWindowClass();
+
+  // Unregisters the window class. Should only be called if there are no
+  // instances of the window.
+  void UnregisterWindowClass();
+
+ private:
+  WindowClassRegistrar() = default;
+
+  static WindowClassRegistrar* instance_;
+
+  bool class_registered_ = false;
+};
+
+WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr;
+
+const wchar_t* WindowClassRegistrar::GetWindowClass() {
+  if (!class_registered_) {
+    WNDCLASS window_class{};
+    window_class.hCursor = LoadCursor(nullptr, IDC_ARROW);
+    window_class.lpszClassName = kWindowClassName;
+    window_class.style = CS_HREDRAW | CS_VREDRAW;
+    window_class.cbClsExtra = 0;
+    window_class.cbWndExtra = 0;
+    window_class.hInstance = GetModuleHandle(nullptr);
+    window_class.hIcon =
+        LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON));
+    window_class.hbrBackground = 0;
+    window_class.lpszMenuName = nullptr;
+    window_class.lpfnWndProc = Win32Window::WndProc;
+    RegisterClass(&window_class);
+    class_registered_ = true;
+  }
+  return kWindowClassName;
+}
+
+void WindowClassRegistrar::UnregisterWindowClass() {
+  UnregisterClass(kWindowClassName, nullptr);
+  class_registered_ = false;
+}
+
+Win32Window::Win32Window() { ++g_active_window_count; }
+
+Win32Window::~Win32Window() {
+  --g_active_window_count;
+  Destroy();
+}
+
+bool Win32Window::CreateAndShow(const std::wstring& title, const Point& origin,
+                                const Size& size) {
+  Destroy();
+
+  const wchar_t* window_class =
+      WindowClassRegistrar::GetInstance()->GetWindowClass();
+
+  const POINT target_point = {static_cast<LONG>(origin.x),
+                              static_cast<LONG>(origin.y)};
+  HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST);
+  UINT dpi = FlutterDesktopGetDpiForMonitor(monitor);
+  double scale_factor = dpi / 96.0;
+
+  HWND window = CreateWindow(
+      window_class, title.c_str(), WS_OVERLAPPEDWINDOW | WS_VISIBLE,
+      Scale(origin.x, scale_factor), Scale(origin.y, scale_factor),
+      Scale(size.width, scale_factor), Scale(size.height, scale_factor),
+      nullptr, nullptr, GetModuleHandle(nullptr), this);
+
+  if (!window) {
+    return false;
+  }
+
+  return OnCreate();
+}
+
+// static
+LRESULT CALLBACK Win32Window::WndProc(HWND const window, UINT const message,
+                                      WPARAM const wparam,
+                                      LPARAM const lparam) noexcept {
+  if (message == WM_NCCREATE) {
+    auto window_struct = reinterpret_cast<CREATESTRUCT*>(lparam);
+    SetWindowLongPtr(window, GWLP_USERDATA,
+                     reinterpret_cast<LONG_PTR>(window_struct->lpCreateParams));
+
+    auto that = static_cast<Win32Window*>(window_struct->lpCreateParams);
+    EnableFullDpiSupportIfAvailable(window);
+    that->window_handle_ = window;
+  } else if (Win32Window* that = GetThisFromHandle(window)) {
+    return that->MessageHandler(window, message, wparam, lparam);
+  }
+
+  return DefWindowProc(window, message, wparam, lparam);
+}
+
+LRESULT
+Win32Window::MessageHandler(HWND hwnd, UINT const message, WPARAM const wparam,
+                            LPARAM const lparam) noexcept {
+  switch (message) {
+    case WM_DESTROY:
+      window_handle_ = nullptr;
+      Destroy();
+      if (quit_on_close_) {
+        PostQuitMessage(0);
+      }
+      return 0;
+
+    case WM_DPICHANGED: {
+      auto newRectSize = reinterpret_cast<RECT*>(lparam);
+      LONG newWidth = newRectSize->right - newRectSize->left;
+      LONG newHeight = newRectSize->bottom - newRectSize->top;
+
+      SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth,
+                   newHeight, SWP_NOZORDER | SWP_NOACTIVATE);
+
+      return 0;
+    }
+    case WM_SIZE: {
+      RECT rect = GetClientArea();
+      if (child_content_ != nullptr) {
+        // Size and position the child window.
+        MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left,
+                   rect.bottom - rect.top, TRUE);
+      }
+      return 0;
+    }
+
+    case WM_ACTIVATE:
+      if (child_content_ != nullptr) {
+        SetFocus(child_content_);
+      }
+      return 0;
+  }
+
+  return DefWindowProc(window_handle_, message, wparam, lparam);
+}
+
+void Win32Window::Destroy() {
+  OnDestroy();
+
+  if (window_handle_) {
+    DestroyWindow(window_handle_);
+    window_handle_ = nullptr;
+  }
+  if (g_active_window_count == 0) {
+    WindowClassRegistrar::GetInstance()->UnregisterWindowClass();
+  }
+}
+
+Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept {
+  return reinterpret_cast<Win32Window*>(
+      GetWindowLongPtr(window, GWLP_USERDATA));
+}
+
+void Win32Window::SetChildContent(HWND content) {
+  child_content_ = content;
+  SetParent(content, window_handle_);
+  RECT frame = GetClientArea();
+
+  MoveWindow(content, frame.left, frame.top, frame.right - frame.left,
+             frame.bottom - frame.top, true);
+
+  SetFocus(child_content_);
+}
+
+RECT Win32Window::GetClientArea() {
+  RECT frame;
+  GetClientRect(window_handle_, &frame);
+  return frame;
+}
+
+HWND Win32Window::GetHandle() { return window_handle_; }
+
+void Win32Window::SetQuitOnClose(bool quit_on_close) {
+  quit_on_close_ = quit_on_close;
+}
+
+bool Win32Window::OnCreate() {
+  // No-op; provided for subclasses.
+  return true;
+}
+
+void Win32Window::OnDestroy() {
+  // No-op; provided for subclasses.
+}
diff --git a/packages/rfw/example/remote/windows/runner/win32_window.h b/packages/rfw/example/remote/windows/runner/win32_window.h
new file mode 100644
index 0000000..d2a7300
--- /dev/null
+++ b/packages/rfw/example/remote/windows/runner/win32_window.h
@@ -0,0 +1,99 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef RUNNER_WIN32_WINDOW_H_
+#define RUNNER_WIN32_WINDOW_H_
+
+#include <windows.h>
+
+#include <functional>
+#include <memory>
+#include <string>
+
+// A class abstraction for a high DPI-aware Win32 Window. Intended to be
+// inherited from by classes that wish to specialize with custom
+// rendering and input handling
+class Win32Window {
+ public:
+  struct Point {
+    unsigned int x;
+    unsigned int y;
+    Point(unsigned int x, unsigned int y) : x(x), y(y) {}
+  };
+
+  struct Size {
+    unsigned int width;
+    unsigned int height;
+    Size(unsigned int width, unsigned int height)
+        : width(width), height(height) {}
+  };
+
+  Win32Window();
+  virtual ~Win32Window();
+
+  // Creates and shows a win32 window with |title| and position and size using
+  // |origin| and |size|. New windows are created on the default monitor. Window
+  // sizes are specified to the OS in physical pixels, hence to ensure a
+  // consistent size to will treat the width height passed in to this function
+  // as logical pixels and scale to appropriate for the default monitor. Returns
+  // true if the window was created successfully.
+  bool CreateAndShow(const std::wstring& title, const Point& origin,
+                     const Size& size);
+
+  // Release OS resources associated with window.
+  void Destroy();
+
+  // Inserts |content| into the window tree.
+  void SetChildContent(HWND content);
+
+  // Returns the backing Window handle to enable clients to set icon and other
+  // window properties. Returns nullptr if the window has been destroyed.
+  HWND GetHandle();
+
+  // If true, closing this window will quit the application.
+  void SetQuitOnClose(bool quit_on_close);
+
+  // Return a RECT representing the bounds of the current client area.
+  RECT GetClientArea();
+
+ protected:
+  // Processes and route salient window messages for mouse handling,
+  // size change and DPI. Delegates handling of these to member overloads that
+  // inheriting classes can handle.
+  virtual LRESULT MessageHandler(HWND window, UINT const message,
+                                 WPARAM const wparam,
+                                 LPARAM const lparam) noexcept;
+
+  // Called when CreateAndShow is called, allowing subclass window-related
+  // setup. Subclasses should return false if setup fails.
+  virtual bool OnCreate();
+
+  // Called when Destroy is called.
+  virtual void OnDestroy();
+
+ private:
+  friend class WindowClassRegistrar;
+
+  // OS callback called by message pump. Handles the WM_NCCREATE message which
+  // is passed when the non-client area is being created and enables automatic
+  // non-client DPI scaling so that the non-client area automatically
+  // responsponds to changes in DPI. All other messages are handled by
+  // MessageHandler.
+  static LRESULT CALLBACK WndProc(HWND const window, UINT const message,
+                                  WPARAM const wparam,
+                                  LPARAM const lparam) noexcept;
+
+  // Retrieves a class instance pointer for |window|
+  static Win32Window* GetThisFromHandle(HWND const window) noexcept;
+
+  bool quit_on_close_ = false;
+
+  // window handle for top level window.
+  HWND window_handle_ = nullptr;
+
+  // window handle for hosted content.
+  HWND child_content_ = nullptr;
+};
+
+#endif  // RUNNER_WIN32_WINDOW_H_
diff --git a/packages/rfw/example/wasm/.gitignore b/packages/rfw/example/wasm/.gitignore
new file mode 100644
index 0000000..0fa6b67
--- /dev/null
+++ b/packages/rfw/example/wasm/.gitignore
@@ -0,0 +1,46 @@
+# Miscellaneous
+*.class
+*.log
+*.pyc
+*.swp
+.DS_Store
+.atom/
+.buildlog/
+.history
+.svn/
+
+# IntelliJ related
+*.iml
+*.ipr
+*.iws
+.idea/
+
+# The .vscode folder contains launch configuration and tasks you configure in
+# VS Code which you may wish to be included in version control, so this line
+# is commented out by default.
+#.vscode/
+
+# Flutter/Dart/Pub related
+**/doc/api/
+**/ios/Flutter/.last_build_id
+.dart_tool/
+.flutter-plugins
+.flutter-plugins-dependencies
+.packages
+.pub-cache/
+.pub/
+/build/
+
+# Web related
+lib/generated_plugin_registrant.dart
+
+# Symbolication related
+app.*.symbols
+
+# Obfuscation related
+app.*.map.json
+
+# Android Studio will place build artifacts here
+/android/app/debug
+/android/app/profile
+/android/app/release
diff --git a/packages/rfw/example/wasm/.metadata b/packages/rfw/example/wasm/.metadata
new file mode 100644
index 0000000..45c494e
--- /dev/null
+++ b/packages/rfw/example/wasm/.metadata
@@ -0,0 +1,10 @@
+# This file tracks properties of this Flutter project.
+# Used by Flutter tool to assess capabilities and perform upgrades etc.
+#
+# This file should be version controlled and should not be manually edited.
+
+version:
+  revision: a1cd3f45e02c2429e14630d6cc6de120c3d24bd0
+  channel: master
+
+project_type: app
diff --git a/packages/rfw/example/wasm/.pluginToolsConfig.yaml b/packages/rfw/example/wasm/.pluginToolsConfig.yaml
new file mode 100644
index 0000000..0be940a
--- /dev/null
+++ b/packages/rfw/example/wasm/.pluginToolsConfig.yaml
@@ -0,0 +1,3 @@
+buildFlags:
+  global:
+    - "--no-tree-shake-icons"
diff --git a/packages/rfw/example/wasm/README.md b/packages/rfw/example/wasm/README.md
new file mode 100644
index 0000000..af1fe39
--- /dev/null
+++ b/packages/rfw/example/wasm/README.md
@@ -0,0 +1,45 @@
+# Example of using Wasm with RFW
+
+In this example, the application downloads both RFW descriptions of UI
+and Wasm logic to drive it. The Flutter application itself does not
+contain any of the calculator UI or logic.
+
+Currently this only runs on macOS, Windows, and Linux, since
+`package:wasm` does not yet support Android, iOS, or web.
+
+## Building
+
+Before running this package, you must run `flutter pub run wasm:setup`
+in this directory. Before doing this, you will need to have installed
+Rust and clang.
+
+To rebuild the files in the logic/ directory (which are the files that
+the application downloads at runtime), you will additionally need to
+have installed clang and lld, and dart must be on your path.
+
+## Conventions
+
+The application renders the remote widget named `root` defined in the
+`logic/calculator.rfw` file, and loads the Wasm module defined in the
+`logic/calculator.wasm` file.
+
+The Wasm module must implement a function `value` that takes no
+arguments and returns an integer, which is the data to pass to the
+interface. This will be the first function called.
+
+That data is stored in the `value` key of the data exposed to the
+remote widgets, as a map with two values, `numeric` (which stores the
+integer as-is) and `string` (which stores its string representation,
+e.g. for display using a `Text` widget).
+
+The remote widgets can signal events. The names of such events are
+looked up as functions in the Wasm module. The `arguments` key, if
+present, must be a list of integers to pass to the function.
+
+Only the `core.widgets` local library is loaded into the runtime.
+
+## Application behavior
+
+The demo application fetches the RFW and Wasm files on startup, if it
+has not downloaded them before or if they were downloaded more than
+six hours earlier.
diff --git a/packages/rfw/example/wasm/android/.gitignore b/packages/rfw/example/wasm/android/.gitignore
new file mode 100644
index 0000000..6f56801
--- /dev/null
+++ b/packages/rfw/example/wasm/android/.gitignore
@@ -0,0 +1,13 @@
+gradle-wrapper.jar
+/.gradle
+/captures/
+/gradlew
+/gradlew.bat
+/local.properties
+GeneratedPluginRegistrant.java
+
+# Remember to never publicly share your keystore.
+# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
+key.properties
+**/*.keystore
+**/*.jks
diff --git a/packages/rfw/example/wasm/android/app/build.gradle b/packages/rfw/example/wasm/android/app/build.gradle
new file mode 100644
index 0000000..2b9cd29
--- /dev/null
+++ b/packages/rfw/example/wasm/android/app/build.gradle
@@ -0,0 +1,68 @@
+def localProperties = new Properties()
+def localPropertiesFile = rootProject.file('local.properties')
+if (localPropertiesFile.exists()) {
+    localPropertiesFile.withReader('UTF-8') { reader ->
+        localProperties.load(reader)
+    }
+}
+
+def flutterRoot = localProperties.getProperty('flutter.sdk')
+if (flutterRoot == null) {
+    throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
+}
+
+def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
+if (flutterVersionCode == null) {
+    flutterVersionCode = '1'
+}
+
+def flutterVersionName = localProperties.getProperty('flutter.versionName')
+if (flutterVersionName == null) {
+    flutterVersionName = '1.0'
+}
+
+apply plugin: 'com.android.application'
+apply plugin: 'kotlin-android'
+apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
+
+android {
+    compileSdkVersion 30
+
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_8
+        targetCompatibility JavaVersion.VERSION_1_8
+    }
+
+    kotlinOptions {
+        jvmTarget = '1.8'
+    }
+
+    sourceSets {
+        main.java.srcDirs += 'src/main/kotlin'
+    }
+
+    defaultConfig {
+        // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
+        applicationId "dev.flutter.rfw.examples.wasm"
+        minSdkVersion 16
+        targetSdkVersion 30
+        versionCode flutterVersionCode.toInteger()
+        versionName flutterVersionName
+    }
+
+    buildTypes {
+        release {
+            // TODO: Add your own signing config for the release build.
+            // Signing with the debug keys for now, so `flutter run --release` works.
+            signingConfig signingConfigs.debug
+        }
+    }
+}
+
+flutter {
+    source '../..'
+}
+
+dependencies {
+    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
+}
diff --git a/packages/rfw/example/wasm/android/app/src/debug/AndroidManifest.xml b/packages/rfw/example/wasm/android/app/src/debug/AndroidManifest.xml
new file mode 100644
index 0000000..bb039dc
--- /dev/null
+++ b/packages/rfw/example/wasm/android/app/src/debug/AndroidManifest.xml
@@ -0,0 +1,7 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="dev.flutter.rfw.examples.wasm">
+    <!-- Flutter needs it to communicate with the running application
+         to allow setting breakpoints, to provide hot reload, etc.
+    -->
+    <uses-permission android:name="android.permission.INTERNET"/>
+</manifest>
diff --git a/packages/rfw/example/wasm/android/app/src/main/AndroidManifest.xml b/packages/rfw/example/wasm/android/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..2867e4b
--- /dev/null
+++ b/packages/rfw/example/wasm/android/app/src/main/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="dev.flutter.rfw.examples.wasm">
+   <application
+        android:label="wasm"
+        android:icon="@mipmap/ic_launcher">
+        <activity
+            android:name=".MainActivity"
+            android:launchMode="singleTop"
+            android:theme="@style/LaunchTheme"
+            android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
+            android:hardwareAccelerated="true"
+            android:windowSoftInputMode="adjustResize">
+            <!-- Specifies an Android theme to apply to this Activity as soon as
+                 the Android process has started. This theme is visible to the user
+                 while the Flutter UI initializes. After that, this theme continues
+                 to determine the Window background behind the Flutter UI. -->
+            <meta-data
+              android:name="io.flutter.embedding.android.NormalTheme"
+              android:resource="@style/NormalTheme"
+              />
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+        <!-- Don't delete the meta-data below.
+             This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
+        <meta-data
+            android:name="flutterEmbedding"
+            android:value="2" />
+    </application>
+</manifest>
diff --git a/packages/rfw/example/wasm/android/app/src/main/kotlin/dev/flutter/rfw/examples/wasm/MainActivity.kt b/packages/rfw/example/wasm/android/app/src/main/kotlin/dev/flutter/rfw/examples/wasm/MainActivity.kt
new file mode 100644
index 0000000..1cd18a6
--- /dev/null
+++ b/packages/rfw/example/wasm/android/app/src/main/kotlin/dev/flutter/rfw/examples/wasm/MainActivity.kt
@@ -0,0 +1,6 @@
+package dev.flutter.rfw.examples.wasm
+
+import io.flutter.embedding.android.FlutterActivity
+
+class MainActivity: FlutterActivity() {
+}
diff --git a/packages/rfw/example/wasm/android/app/src/main/res/drawable-v21/launch_background.xml b/packages/rfw/example/wasm/android/app/src/main/res/drawable-v21/launch_background.xml
new file mode 100644
index 0000000..f74085f
--- /dev/null
+++ b/packages/rfw/example/wasm/android/app/src/main/res/drawable-v21/launch_background.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Modify this file to customize your launch splash screen -->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:drawable="?android:colorBackground" />
+
+    <!-- You can insert your own image assets here -->
+    <!-- <item>
+        <bitmap
+            android:gravity="center"
+            android:src="@mipmap/launch_image" />
+    </item> -->
+</layer-list>
diff --git a/packages/rfw/example/wasm/android/app/src/main/res/drawable/launch_background.xml b/packages/rfw/example/wasm/android/app/src/main/res/drawable/launch_background.xml
new file mode 100644
index 0000000..304732f
--- /dev/null
+++ b/packages/rfw/example/wasm/android/app/src/main/res/drawable/launch_background.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Modify this file to customize your launch splash screen -->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:drawable="@android:color/white" />
+
+    <!-- You can insert your own image assets here -->
+    <!-- <item>
+        <bitmap
+            android:gravity="center"
+            android:src="@mipmap/launch_image" />
+    </item> -->
+</layer-list>
diff --git a/packages/rfw/example/wasm/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/packages/rfw/example/wasm/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..db77bb4
--- /dev/null
+++ b/packages/rfw/example/wasm/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
Binary files differ
diff --git a/packages/rfw/example/wasm/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/packages/rfw/example/wasm/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..17987b7
--- /dev/null
+++ b/packages/rfw/example/wasm/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
Binary files differ
diff --git a/packages/rfw/example/wasm/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/packages/rfw/example/wasm/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..09d4391
--- /dev/null
+++ b/packages/rfw/example/wasm/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
Binary files differ
diff --git a/packages/rfw/example/wasm/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/packages/rfw/example/wasm/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..d5f1c8d
--- /dev/null
+++ b/packages/rfw/example/wasm/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/packages/rfw/example/wasm/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/packages/rfw/example/wasm/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..4d6372e
--- /dev/null
+++ b/packages/rfw/example/wasm/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Binary files differ
diff --git a/packages/rfw/example/wasm/android/app/src/main/res/values-night/styles.xml b/packages/rfw/example/wasm/android/app/src/main/res/values-night/styles.xml
new file mode 100644
index 0000000..449a9f9
--- /dev/null
+++ b/packages/rfw/example/wasm/android/app/src/main/res/values-night/styles.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
+    <style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
+        <!-- Show a splash screen on the activity. Automatically removed when
+             Flutter draws its first frame -->
+        <item name="android:windowBackground">@drawable/launch_background</item>
+    </style>
+    <!-- Theme applied to the Android Window as soon as the process has started.
+         This theme determines the color of the Android Window while your
+         Flutter UI initializes, as well as behind your Flutter UI while its
+         running.
+         
+         This Theme is only used starting with V2 of Flutter's Android embedding. -->
+    <style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
+        <item name="android:windowBackground">?android:colorBackground</item>
+    </style>
+</resources>
diff --git a/packages/rfw/example/wasm/android/app/src/main/res/values/styles.xml b/packages/rfw/example/wasm/android/app/src/main/res/values/styles.xml
new file mode 100644
index 0000000..d74aa35
--- /dev/null
+++ b/packages/rfw/example/wasm/android/app/src/main/res/values/styles.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
+    <style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
+        <!-- Show a splash screen on the activity. Automatically removed when
+             Flutter draws its first frame -->
+        <item name="android:windowBackground">@drawable/launch_background</item>
+    </style>
+    <!-- Theme applied to the Android Window as soon as the process has started.
+         This theme determines the color of the Android Window while your
+         Flutter UI initializes, as well as behind your Flutter UI while its
+         running.
+         
+         This Theme is only used starting with V2 of Flutter's Android embedding. -->
+    <style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
+        <item name="android:windowBackground">?android:colorBackground</item>
+    </style>
+</resources>
diff --git a/packages/rfw/example/wasm/android/app/src/profile/AndroidManifest.xml b/packages/rfw/example/wasm/android/app/src/profile/AndroidManifest.xml
new file mode 100644
index 0000000..bb039dc
--- /dev/null
+++ b/packages/rfw/example/wasm/android/app/src/profile/AndroidManifest.xml
@@ -0,0 +1,7 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="dev.flutter.rfw.examples.wasm">
+    <!-- Flutter needs it to communicate with the running application
+         to allow setting breakpoints, to provide hot reload, etc.
+    -->
+    <uses-permission android:name="android.permission.INTERNET"/>
+</manifest>
diff --git a/packages/rfw/example/wasm/android/build.gradle b/packages/rfw/example/wasm/android/build.gradle
new file mode 100644
index 0000000..ed45c65
--- /dev/null
+++ b/packages/rfw/example/wasm/android/build.gradle
@@ -0,0 +1,29 @@
+buildscript {
+    ext.kotlin_version = '1.3.50'
+    repositories {
+        google()
+        mavenCentral()
+    }
+
+    dependencies {
+        classpath 'com.android.tools.build:gradle:4.1.0'
+        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
+    }
+}
+
+allprojects {
+    repositories {
+        google()
+        mavenCentral()
+    }
+}
+
+rootProject.buildDir = '../build'
+subprojects {
+    project.buildDir = "${rootProject.buildDir}/${project.name}"
+    project.evaluationDependsOn(':app')
+}
+
+task clean(type: Delete) {
+    delete rootProject.buildDir
+}
diff --git a/packages/rfw/example/wasm/android/gradle.properties b/packages/rfw/example/wasm/android/gradle.properties
new file mode 100644
index 0000000..94adc3a
--- /dev/null
+++ b/packages/rfw/example/wasm/android/gradle.properties
@@ -0,0 +1,3 @@
+org.gradle.jvmargs=-Xmx1536M
+android.useAndroidX=true
+android.enableJetifier=true
diff --git a/packages/rfw/example/wasm/android/gradle/wrapper/gradle-wrapper.properties b/packages/rfw/example/wasm/android/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..bc6a58a
--- /dev/null
+++ b/packages/rfw/example/wasm/android/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Fri Jun 23 08:50:38 CEST 2017
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip
diff --git a/packages/rfw/example/wasm/android/settings.gradle b/packages/rfw/example/wasm/android/settings.gradle
new file mode 100644
index 0000000..44e62bc
--- /dev/null
+++ b/packages/rfw/example/wasm/android/settings.gradle
@@ -0,0 +1,11 @@
+include ':app'
+
+def localPropertiesFile = new File(rootProject.projectDir, "local.properties")
+def properties = new Properties()
+
+assert localPropertiesFile.exists()
+localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }
+
+def flutterSdkPath = properties.getProperty("flutter.sdk")
+assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
+apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"
diff --git a/packages/rfw/example/wasm/ios/.gitignore b/packages/rfw/example/wasm/ios/.gitignore
new file mode 100644
index 0000000..151026b
--- /dev/null
+++ b/packages/rfw/example/wasm/ios/.gitignore
@@ -0,0 +1,33 @@
+*.mode1v3
+*.mode2v3
+*.moved-aside
+*.pbxuser
+*.perspectivev3
+**/*sync/
+.sconsign.dblite
+.tags*
+**/.vagrant/
+**/DerivedData/
+Icon?
+**/Pods/
+**/.symlinks/
+profile
+xcuserdata
+**/.generated/
+Flutter/App.framework
+Flutter/Flutter.framework
+Flutter/Flutter.podspec
+Flutter/Generated.xcconfig
+Flutter/ephemeral/
+Flutter/app.flx
+Flutter/app.zip
+Flutter/flutter_assets/
+Flutter/flutter_export_environment.sh
+ServiceDefinitions.json
+Runner/GeneratedPluginRegistrant.*
+
+# Exceptions to above rules.
+!default.mode1v3
+!default.mode2v3
+!default.pbxuser
+!default.perspectivev3
diff --git a/packages/rfw/example/wasm/ios/Flutter/AppFrameworkInfo.plist b/packages/rfw/example/wasm/ios/Flutter/AppFrameworkInfo.plist
new file mode 100644
index 0000000..8d4492f
--- /dev/null
+++ b/packages/rfw/example/wasm/ios/Flutter/AppFrameworkInfo.plist
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+  <key>CFBundleDevelopmentRegion</key>
+  <string>en</string>
+  <key>CFBundleExecutable</key>
+  <string>App</string>
+  <key>CFBundleIdentifier</key>
+  <string>io.flutter.flutter.app</string>
+  <key>CFBundleInfoDictionaryVersion</key>
+  <string>6.0</string>
+  <key>CFBundleName</key>
+  <string>App</string>
+  <key>CFBundlePackageType</key>
+  <string>FMWK</string>
+  <key>CFBundleShortVersionString</key>
+  <string>1.0</string>
+  <key>CFBundleSignature</key>
+  <string>????</string>
+  <key>CFBundleVersion</key>
+  <string>1.0</string>
+  <key>MinimumOSVersion</key>
+  <string>9.0</string>
+</dict>
+</plist>
diff --git a/packages/rfw/example/wasm/ios/Flutter/Debug.xcconfig b/packages/rfw/example/wasm/ios/Flutter/Debug.xcconfig
new file mode 100644
index 0000000..592ceee
--- /dev/null
+++ b/packages/rfw/example/wasm/ios/Flutter/Debug.xcconfig
@@ -0,0 +1 @@
+#include "Generated.xcconfig"
diff --git a/packages/rfw/example/wasm/ios/Flutter/Release.xcconfig b/packages/rfw/example/wasm/ios/Flutter/Release.xcconfig
new file mode 100644
index 0000000..592ceee
--- /dev/null
+++ b/packages/rfw/example/wasm/ios/Flutter/Release.xcconfig
@@ -0,0 +1 @@
+#include "Generated.xcconfig"
diff --git a/packages/rfw/example/wasm/ios/Runner.xcodeproj/project.pbxproj b/packages/rfw/example/wasm/ios/Runner.xcodeproj/project.pbxproj
new file mode 100644
index 0000000..9558958
--- /dev/null
+++ b/packages/rfw/example/wasm/ios/Runner.xcodeproj/project.pbxproj
@@ -0,0 +1,481 @@
+// !$*UTF8*$!
+{
+	archiveVersion = 1;
+	classes = {
+	};
+	objectVersion = 50;
+	objects = {
+
+/* Begin PBXBuildFile section */
+		1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
+		3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
+		74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
+		97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
+		97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
+		97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXCopyFilesBuildPhase section */
+		9705A1C41CF9048500538489 /* Embed Frameworks */ = {
+			isa = PBXCopyFilesBuildPhase;
+			buildActionMask = 2147483647;
+			dstPath = "";
+			dstSubfolderSpec = 10;
+			files = (
+			);
+			name = "Embed Frameworks";
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXCopyFilesBuildPhase section */
+
+/* Begin PBXFileReference section */
+		1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
+		1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
+		3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
+		74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
+		74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
+		7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
+		9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
+		9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
+		97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
+		97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
+		97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
+		97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
+		97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+		97C146EB1CF9000F007C117D /* Frameworks */ = {
+			isa = PBXFrameworksBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+		9740EEB11CF90186004384FC /* Flutter */ = {
+			isa = PBXGroup;
+			children = (
+				3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
+				9740EEB21CF90195004384FC /* Debug.xcconfig */,
+				7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
+				9740EEB31CF90195004384FC /* Generated.xcconfig */,
+			);
+			name = Flutter;
+			sourceTree = "<group>";
+		};
+		97C146E51CF9000F007C117D = {
+			isa = PBXGroup;
+			children = (
+				9740EEB11CF90186004384FC /* Flutter */,
+				97C146F01CF9000F007C117D /* Runner */,
+				97C146EF1CF9000F007C117D /* Products */,
+			);
+			sourceTree = "<group>";
+		};
+		97C146EF1CF9000F007C117D /* Products */ = {
+			isa = PBXGroup;
+			children = (
+				97C146EE1CF9000F007C117D /* Runner.app */,
+			);
+			name = Products;
+			sourceTree = "<group>";
+		};
+		97C146F01CF9000F007C117D /* Runner */ = {
+			isa = PBXGroup;
+			children = (
+				97C146FA1CF9000F007C117D /* Main.storyboard */,
+				97C146FD1CF9000F007C117D /* Assets.xcassets */,
+				97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
+				97C147021CF9000F007C117D /* Info.plist */,
+				1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
+				1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
+				74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
+				74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
+			);
+			path = Runner;
+			sourceTree = "<group>";
+		};
+/* End PBXGroup section */
+
+/* Begin PBXNativeTarget section */
+		97C146ED1CF9000F007C117D /* Runner */ = {
+			isa = PBXNativeTarget;
+			buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
+			buildPhases = (
+				9740EEB61CF901F6004384FC /* Run Script */,
+				97C146EA1CF9000F007C117D /* Sources */,
+				97C146EB1CF9000F007C117D /* Frameworks */,
+				97C146EC1CF9000F007C117D /* Resources */,
+				9705A1C41CF9048500538489 /* Embed Frameworks */,
+				3B06AD1E1E4923F5004D2608 /* Thin Binary */,
+			);
+			buildRules = (
+			);
+			dependencies = (
+			);
+			name = Runner;
+			productName = Runner;
+			productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
+			productType = "com.apple.product-type.application";
+		};
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+		97C146E61CF9000F007C117D /* Project object */ = {
+			isa = PBXProject;
+			attributes = {
+				LastUpgradeCheck = 1300;
+				ORGANIZATIONNAME = "";
+				TargetAttributes = {
+					97C146ED1CF9000F007C117D = {
+						CreatedOnToolsVersion = 7.3.1;
+						LastSwiftMigration = 1100;
+					};
+				};
+			};
+			buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
+			compatibilityVersion = "Xcode 9.3";
+			developmentRegion = en;
+			hasScannedForEncodings = 0;
+			knownRegions = (
+				en,
+				Base,
+			);
+			mainGroup = 97C146E51CF9000F007C117D;
+			productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
+			projectDirPath = "";
+			projectRoot = "";
+			targets = (
+				97C146ED1CF9000F007C117D /* Runner */,
+			);
+		};
+/* End PBXProject section */
+
+/* Begin PBXResourcesBuildPhase section */
+		97C146EC1CF9000F007C117D /* Resources */ = {
+			isa = PBXResourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
+				3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
+				97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
+				97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXResourcesBuildPhase section */
+
+/* Begin PBXShellScriptBuildPhase section */
+		3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
+			isa = PBXShellScriptBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			inputPaths = (
+			);
+			name = "Thin Binary";
+			outputPaths = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+			shellPath = /bin/sh;
+			shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
+		};
+		9740EEB61CF901F6004384FC /* Run Script */ = {
+			isa = PBXShellScriptBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			inputPaths = (
+			);
+			name = "Run Script";
+			outputPaths = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+			shellPath = /bin/sh;
+			shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
+		};
+/* End PBXShellScriptBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+		97C146EA1CF9000F007C117D /* Sources */ = {
+			isa = PBXSourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
+				1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXSourcesBuildPhase section */
+
+/* Begin PBXVariantGroup section */
+		97C146FA1CF9000F007C117D /* Main.storyboard */ = {
+			isa = PBXVariantGroup;
+			children = (
+				97C146FB1CF9000F007C117D /* Base */,
+			);
+			name = Main.storyboard;
+			sourceTree = "<group>";
+		};
+		97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
+			isa = PBXVariantGroup;
+			children = (
+				97C147001CF9000F007C117D /* Base */,
+			);
+			name = LaunchScreen.storyboard;
+			sourceTree = "<group>";
+		};
+/* End PBXVariantGroup section */
+
+/* Begin XCBuildConfiguration section */
+		249021D3217E4FDB00AE95B9 /* Profile */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				ALWAYS_SEARCH_USER_PATHS = NO;
+				CLANG_ANALYZER_NONNULL = YES;
+				CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+				CLANG_CXX_LIBRARY = "libc++";
+				CLANG_ENABLE_MODULES = YES;
+				CLANG_ENABLE_OBJC_ARC = YES;
+				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+				CLANG_WARN_BOOL_CONVERSION = YES;
+				CLANG_WARN_COMMA = YES;
+				CLANG_WARN_CONSTANT_CONVERSION = YES;
+				CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+				CLANG_WARN_EMPTY_BODY = YES;
+				CLANG_WARN_ENUM_CONVERSION = YES;
+				CLANG_WARN_INFINITE_RECURSION = YES;
+				CLANG_WARN_INT_CONVERSION = YES;
+				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+				CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+				CLANG_WARN_STRICT_PROTOTYPES = YES;
+				CLANG_WARN_SUSPICIOUS_MOVE = YES;
+				CLANG_WARN_UNREACHABLE_CODE = YES;
+				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+				"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+				COPY_PHASE_STRIP = NO;
+				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+				ENABLE_NS_ASSERTIONS = NO;
+				ENABLE_STRICT_OBJC_MSGSEND = YES;
+				GCC_C_LANGUAGE_STANDARD = gnu99;
+				GCC_NO_COMMON_BLOCKS = YES;
+				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+				GCC_WARN_UNDECLARED_SELECTOR = YES;
+				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+				GCC_WARN_UNUSED_FUNCTION = YES;
+				GCC_WARN_UNUSED_VARIABLE = YES;
+				IPHONEOS_DEPLOYMENT_TARGET = 9.0;
+				MTL_ENABLE_DEBUG_INFO = NO;
+				SDKROOT = iphoneos;
+				SUPPORTED_PLATFORMS = iphoneos;
+				TARGETED_DEVICE_FAMILY = "1,2";
+				VALIDATE_PRODUCT = YES;
+			};
+			name = Profile;
+		};
+		249021D4217E4FDB00AE95B9 /* Profile */ = {
+			isa = XCBuildConfiguration;
+			baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
+			buildSettings = {
+				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+				CLANG_ENABLE_MODULES = YES;
+				CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
+				ENABLE_BITCODE = NO;
+				INFOPLIST_FILE = Runner/Info.plist;
+				LD_RUNPATH_SEARCH_PATHS = (
+					"$(inherited)",
+					"@executable_path/Frameworks",
+				);
+				PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.rfw.examples.wasm;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+				SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
+				SWIFT_VERSION = 5.0;
+				VERSIONING_SYSTEM = "apple-generic";
+			};
+			name = Profile;
+		};
+		97C147031CF9000F007C117D /* Debug */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				ALWAYS_SEARCH_USER_PATHS = NO;
+				CLANG_ANALYZER_NONNULL = YES;
+				CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+				CLANG_CXX_LIBRARY = "libc++";
+				CLANG_ENABLE_MODULES = YES;
+				CLANG_ENABLE_OBJC_ARC = YES;
+				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+				CLANG_WARN_BOOL_CONVERSION = YES;
+				CLANG_WARN_COMMA = YES;
+				CLANG_WARN_CONSTANT_CONVERSION = YES;
+				CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+				CLANG_WARN_EMPTY_BODY = YES;
+				CLANG_WARN_ENUM_CONVERSION = YES;
+				CLANG_WARN_INFINITE_RECURSION = YES;
+				CLANG_WARN_INT_CONVERSION = YES;
+				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+				CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+				CLANG_WARN_STRICT_PROTOTYPES = YES;
+				CLANG_WARN_SUSPICIOUS_MOVE = YES;
+				CLANG_WARN_UNREACHABLE_CODE = YES;
+				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+				"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+				COPY_PHASE_STRIP = NO;
+				DEBUG_INFORMATION_FORMAT = dwarf;
+				ENABLE_STRICT_OBJC_MSGSEND = YES;
+				ENABLE_TESTABILITY = YES;
+				GCC_C_LANGUAGE_STANDARD = gnu99;
+				GCC_DYNAMIC_NO_PIC = NO;
+				GCC_NO_COMMON_BLOCKS = YES;
+				GCC_OPTIMIZATION_LEVEL = 0;
+				GCC_PREPROCESSOR_DEFINITIONS = (
+					"DEBUG=1",
+					"$(inherited)",
+				);
+				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+				GCC_WARN_UNDECLARED_SELECTOR = YES;
+				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+				GCC_WARN_UNUSED_FUNCTION = YES;
+				GCC_WARN_UNUSED_VARIABLE = YES;
+				IPHONEOS_DEPLOYMENT_TARGET = 9.0;
+				MTL_ENABLE_DEBUG_INFO = YES;
+				ONLY_ACTIVE_ARCH = YES;
+				SDKROOT = iphoneos;
+				TARGETED_DEVICE_FAMILY = "1,2";
+			};
+			name = Debug;
+		};
+		97C147041CF9000F007C117D /* Release */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				ALWAYS_SEARCH_USER_PATHS = NO;
+				CLANG_ANALYZER_NONNULL = YES;
+				CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+				CLANG_CXX_LIBRARY = "libc++";
+				CLANG_ENABLE_MODULES = YES;
+				CLANG_ENABLE_OBJC_ARC = YES;
+				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+				CLANG_WARN_BOOL_CONVERSION = YES;
+				CLANG_WARN_COMMA = YES;
+				CLANG_WARN_CONSTANT_CONVERSION = YES;
+				CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+				CLANG_WARN_EMPTY_BODY = YES;
+				CLANG_WARN_ENUM_CONVERSION = YES;
+				CLANG_WARN_INFINITE_RECURSION = YES;
+				CLANG_WARN_INT_CONVERSION = YES;
+				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+				CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+				CLANG_WARN_STRICT_PROTOTYPES = YES;
+				CLANG_WARN_SUSPICIOUS_MOVE = YES;
+				CLANG_WARN_UNREACHABLE_CODE = YES;
+				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+				"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+				COPY_PHASE_STRIP = NO;
+				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+				ENABLE_NS_ASSERTIONS = NO;
+				ENABLE_STRICT_OBJC_MSGSEND = YES;
+				GCC_C_LANGUAGE_STANDARD = gnu99;
+				GCC_NO_COMMON_BLOCKS = YES;
+				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+				GCC_WARN_UNDECLARED_SELECTOR = YES;
+				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+				GCC_WARN_UNUSED_FUNCTION = YES;
+				GCC_WARN_UNUSED_VARIABLE = YES;
+				IPHONEOS_DEPLOYMENT_TARGET = 9.0;
+				MTL_ENABLE_DEBUG_INFO = NO;
+				SDKROOT = iphoneos;
+				SUPPORTED_PLATFORMS = iphoneos;
+				SWIFT_COMPILATION_MODE = wholemodule;
+				SWIFT_OPTIMIZATION_LEVEL = "-O";
+				TARGETED_DEVICE_FAMILY = "1,2";
+				VALIDATE_PRODUCT = YES;
+			};
+			name = Release;
+		};
+		97C147061CF9000F007C117D /* Debug */ = {
+			isa = XCBuildConfiguration;
+			baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
+			buildSettings = {
+				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+				CLANG_ENABLE_MODULES = YES;
+				CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
+				ENABLE_BITCODE = NO;
+				INFOPLIST_FILE = Runner/Info.plist;
+				LD_RUNPATH_SEARCH_PATHS = (
+					"$(inherited)",
+					"@executable_path/Frameworks",
+				);
+				PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.rfw.examples.wasm;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+				SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
+				SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+				SWIFT_VERSION = 5.0;
+				VERSIONING_SYSTEM = "apple-generic";
+			};
+			name = Debug;
+		};
+		97C147071CF9000F007C117D /* Release */ = {
+			isa = XCBuildConfiguration;
+			baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
+			buildSettings = {
+				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+				CLANG_ENABLE_MODULES = YES;
+				CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
+				ENABLE_BITCODE = NO;
+				INFOPLIST_FILE = Runner/Info.plist;
+				LD_RUNPATH_SEARCH_PATHS = (
+					"$(inherited)",
+					"@executable_path/Frameworks",
+				);
+				PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.rfw.examples.wasm;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+				SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
+				SWIFT_VERSION = 5.0;
+				VERSIONING_SYSTEM = "apple-generic";
+			};
+			name = Release;
+		};
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+		97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				97C147031CF9000F007C117D /* Debug */,
+				97C147041CF9000F007C117D /* Release */,
+				249021D3217E4FDB00AE95B9 /* Profile */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
+		};
+		97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				97C147061CF9000F007C117D /* Debug */,
+				97C147071CF9000F007C117D /* Release */,
+				249021D4217E4FDB00AE95B9 /* Profile */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
+		};
+/* End XCConfigurationList section */
+	};
+	rootObject = 97C146E61CF9000F007C117D /* Project object */;
+}
diff --git a/packages/rfw/example/wasm/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/packages/rfw/example/wasm/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 0000000..919434a
--- /dev/null
+++ b/packages/rfw/example/wasm/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Workspace
+   version = "1.0">
+   <FileRef
+      location = "self:">
+   </FileRef>
+</Workspace>
diff --git a/packages/rfw/example/wasm/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/rfw/example/wasm/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
new file mode 100644
index 0000000..18d9810
--- /dev/null
+++ b/packages/rfw/example/wasm/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>IDEDidComputeMac32BitWarning</key>
+	<true/>
+</dict>
+</plist>
diff --git a/packages/rfw/example/wasm/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/rfw/example/wasm/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
new file mode 100644
index 0000000..f9b0d7c
--- /dev/null
+++ b/packages/rfw/example/wasm/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>PreviewsEnabled</key>
+	<false/>
+</dict>
+</plist>
diff --git a/packages/rfw/example/wasm/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/rfw/example/wasm/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
new file mode 100644
index 0000000..c87d15a
--- /dev/null
+++ b/packages/rfw/example/wasm/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
@@ -0,0 +1,87 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Scheme
+   LastUpgradeVersion = "1300"
+   version = "1.3">
+   <BuildAction
+      parallelizeBuildables = "YES"
+      buildImplicitDependencies = "YES">
+      <BuildActionEntries>
+         <BuildActionEntry
+            buildForTesting = "YES"
+            buildForRunning = "YES"
+            buildForProfiling = "YES"
+            buildForArchiving = "YES"
+            buildForAnalyzing = "YES">
+            <BuildableReference
+               BuildableIdentifier = "primary"
+               BlueprintIdentifier = "97C146ED1CF9000F007C117D"
+               BuildableName = "Runner.app"
+               BlueprintName = "Runner"
+               ReferencedContainer = "container:Runner.xcodeproj">
+            </BuildableReference>
+         </BuildActionEntry>
+      </BuildActionEntries>
+   </BuildAction>
+   <TestAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      shouldUseLaunchSchemeArgsEnv = "YES">
+      <MacroExpansion>
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "97C146ED1CF9000F007C117D"
+            BuildableName = "Runner.app"
+            BlueprintName = "Runner"
+            ReferencedContainer = "container:Runner.xcodeproj">
+         </BuildableReference>
+      </MacroExpansion>
+      <Testables>
+      </Testables>
+   </TestAction>
+   <LaunchAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      launchStyle = "0"
+      useCustomWorkingDirectory = "NO"
+      ignoresPersistentStateOnLaunch = "NO"
+      debugDocumentVersioning = "YES"
+      debugServiceExtension = "internal"
+      allowLocationSimulation = "YES">
+      <BuildableProductRunnable
+         runnableDebuggingMode = "0">
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "97C146ED1CF9000F007C117D"
+            BuildableName = "Runner.app"
+            BlueprintName = "Runner"
+            ReferencedContainer = "container:Runner.xcodeproj">
+         </BuildableReference>
+      </BuildableProductRunnable>
+   </LaunchAction>
+   <ProfileAction
+      buildConfiguration = "Profile"
+      shouldUseLaunchSchemeArgsEnv = "YES"
+      savedToolIdentifier = ""
+      useCustomWorkingDirectory = "NO"
+      debugDocumentVersioning = "YES">
+      <BuildableProductRunnable
+         runnableDebuggingMode = "0">
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "97C146ED1CF9000F007C117D"
+            BuildableName = "Runner.app"
+            BlueprintName = "Runner"
+            ReferencedContainer = "container:Runner.xcodeproj">
+         </BuildableReference>
+      </BuildableProductRunnable>
+   </ProfileAction>
+   <AnalyzeAction
+      buildConfiguration = "Debug">
+   </AnalyzeAction>
+   <ArchiveAction
+      buildConfiguration = "Release"
+      revealArchiveInOrganizer = "YES">
+   </ArchiveAction>
+</Scheme>
diff --git a/packages/rfw/example/wasm/ios/Runner.xcworkspace/contents.xcworkspacedata b/packages/rfw/example/wasm/ios/Runner.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 0000000..1d526a1
--- /dev/null
+++ b/packages/rfw/example/wasm/ios/Runner.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Workspace
+   version = "1.0">
+   <FileRef
+      location = "group:Runner.xcodeproj">
+   </FileRef>
+</Workspace>
diff --git a/packages/rfw/example/wasm/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/rfw/example/wasm/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
new file mode 100644
index 0000000..18d9810
--- /dev/null
+++ b/packages/rfw/example/wasm/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>IDEDidComputeMac32BitWarning</key>
+	<true/>
+</dict>
+</plist>
diff --git a/packages/rfw/example/wasm/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/rfw/example/wasm/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
new file mode 100644
index 0000000..f9b0d7c
--- /dev/null
+++ b/packages/rfw/example/wasm/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>PreviewsEnabled</key>
+	<false/>
+</dict>
+</plist>
diff --git a/packages/rfw/example/wasm/ios/Runner/AppDelegate.swift b/packages/rfw/example/wasm/ios/Runner/AppDelegate.swift
new file mode 100644
index 0000000..caf9983
--- /dev/null
+++ b/packages/rfw/example/wasm/ios/Runner/AppDelegate.swift
@@ -0,0 +1,17 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import UIKit
+import Flutter
+
+@UIApplicationMain
+@objc class AppDelegate: FlutterAppDelegate {
+  override func application(
+    _ application: UIApplication,
+    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
+  ) -> Bool {
+    GeneratedPluginRegistrant.register(with: self)
+    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
+  }
+}
diff --git a/packages/rfw/example/wasm/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/packages/rfw/example/wasm/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
new file mode 100644
index 0000000..d36b1fa
--- /dev/null
+++ b/packages/rfw/example/wasm/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -0,0 +1,122 @@
+{
+  "images" : [
+    {
+      "size" : "20x20",
+      "idiom" : "iphone",
+      "filename" : "Icon-App-20x20@2x.png",
+      "scale" : "2x"
+    },
+    {
+      "size" : "20x20",
+      "idiom" : "iphone",
+      "filename" : "Icon-App-20x20@3x.png",
+      "scale" : "3x"
+    },
+    {
+      "size" : "29x29",
+      "idiom" : "iphone",
+      "filename" : "Icon-App-29x29@1x.png",
+      "scale" : "1x"
+    },
+    {
+      "size" : "29x29",
+      "idiom" : "iphone",
+      "filename" : "Icon-App-29x29@2x.png",
+      "scale" : "2x"
+    },
+    {
+      "size" : "29x29",
+      "idiom" : "iphone",
+      "filename" : "Icon-App-29x29@3x.png",
+      "scale" : "3x"
+    },
+    {
+      "size" : "40x40",
+      "idiom" : "iphone",
+      "filename" : "Icon-App-40x40@2x.png",
+      "scale" : "2x"
+    },
+    {
+      "size" : "40x40",
+      "idiom" : "iphone",
+      "filename" : "Icon-App-40x40@3x.png",
+      "scale" : "3x"
+    },
+    {
+      "size" : "60x60",
+      "idiom" : "iphone",
+      "filename" : "Icon-App-60x60@2x.png",
+      "scale" : "2x"
+    },
+    {
+      "size" : "60x60",
+      "idiom" : "iphone",
+      "filename" : "Icon-App-60x60@3x.png",
+      "scale" : "3x"
+    },
+    {
+      "size" : "20x20",
+      "idiom" : "ipad",
+      "filename" : "Icon-App-20x20@1x.png",
+      "scale" : "1x"
+    },
+    {
+      "size" : "20x20",
+      "idiom" : "ipad",
+      "filename" : "Icon-App-20x20@2x.png",
+      "scale" : "2x"
+    },
+    {
+      "size" : "29x29",
+      "idiom" : "ipad",
+      "filename" : "Icon-App-29x29@1x.png",
+      "scale" : "1x"
+    },
+    {
+      "size" : "29x29",
+      "idiom" : "ipad",
+      "filename" : "Icon-App-29x29@2x.png",
+      "scale" : "2x"
+    },
+    {
+      "size" : "40x40",
+      "idiom" : "ipad",
+      "filename" : "Icon-App-40x40@1x.png",
+      "scale" : "1x"
+    },
+    {
+      "size" : "40x40",
+      "idiom" : "ipad",
+      "filename" : "Icon-App-40x40@2x.png",
+      "scale" : "2x"
+    },
+    {
+      "size" : "76x76",
+      "idiom" : "ipad",
+      "filename" : "Icon-App-76x76@1x.png",
+      "scale" : "1x"
+    },
+    {
+      "size" : "76x76",
+      "idiom" : "ipad",
+      "filename" : "Icon-App-76x76@2x.png",
+      "scale" : "2x"
+    },
+    {
+      "size" : "83.5x83.5",
+      "idiom" : "ipad",
+      "filename" : "Icon-App-83.5x83.5@2x.png",
+      "scale" : "2x"
+    },
+    {
+      "size" : "1024x1024",
+      "idiom" : "ios-marketing",
+      "filename" : "Icon-App-1024x1024@1x.png",
+      "scale" : "1x"
+    }
+  ],
+  "info" : {
+    "version" : 1,
+    "author" : "xcode"
+  }
+}
diff --git a/packages/rfw/example/wasm/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/packages/rfw/example/wasm/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
new file mode 100644
index 0000000..dc9ada4
--- /dev/null
+++ b/packages/rfw/example/wasm/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
Binary files differ
diff --git a/packages/rfw/example/wasm/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/packages/rfw/example/wasm/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
new file mode 100644
index 0000000..28c6bf0
--- /dev/null
+++ b/packages/rfw/example/wasm/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
Binary files differ
diff --git a/packages/rfw/example/wasm/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/packages/rfw/example/wasm/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
new file mode 100644
index 0000000..2ccbfd9
--- /dev/null
+++ b/packages/rfw/example/wasm/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
Binary files differ
diff --git a/packages/rfw/example/wasm/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/packages/rfw/example/wasm/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
new file mode 100644
index 0000000..f091b6b
--- /dev/null
+++ b/packages/rfw/example/wasm/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
Binary files differ
diff --git a/packages/rfw/example/wasm/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/packages/rfw/example/wasm/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
new file mode 100644
index 0000000..4cde121
--- /dev/null
+++ b/packages/rfw/example/wasm/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
Binary files differ
diff --git a/packages/rfw/example/wasm/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/packages/rfw/example/wasm/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
new file mode 100644
index 0000000..d0ef06e
--- /dev/null
+++ b/packages/rfw/example/wasm/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
Binary files differ
diff --git a/packages/rfw/example/wasm/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/packages/rfw/example/wasm/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
new file mode 100644
index 0000000..dcdc230
--- /dev/null
+++ b/packages/rfw/example/wasm/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
Binary files differ
diff --git a/packages/rfw/example/wasm/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/packages/rfw/example/wasm/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
new file mode 100644
index 0000000..2ccbfd9
--- /dev/null
+++ b/packages/rfw/example/wasm/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
Binary files differ
diff --git a/packages/rfw/example/wasm/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/packages/rfw/example/wasm/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
new file mode 100644
index 0000000..c8f9ed8
--- /dev/null
+++ b/packages/rfw/example/wasm/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
Binary files differ
diff --git a/packages/rfw/example/wasm/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/packages/rfw/example/wasm/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
new file mode 100644
index 0000000..a6d6b86
--- /dev/null
+++ b/packages/rfw/example/wasm/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
Binary files differ
diff --git a/packages/rfw/example/wasm/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/packages/rfw/example/wasm/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
new file mode 100644
index 0000000..a6d6b86
--- /dev/null
+++ b/packages/rfw/example/wasm/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
Binary files differ
diff --git a/packages/rfw/example/wasm/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/packages/rfw/example/wasm/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
new file mode 100644
index 0000000..75b2d16
--- /dev/null
+++ b/packages/rfw/example/wasm/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
Binary files differ
diff --git a/packages/rfw/example/wasm/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/packages/rfw/example/wasm/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
new file mode 100644
index 0000000..c4df70d
--- /dev/null
+++ b/packages/rfw/example/wasm/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
Binary files differ
diff --git a/packages/rfw/example/wasm/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/packages/rfw/example/wasm/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
new file mode 100644
index 0000000..6a84f41
--- /dev/null
+++ b/packages/rfw/example/wasm/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
Binary files differ
diff --git a/packages/rfw/example/wasm/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/packages/rfw/example/wasm/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
new file mode 100644
index 0000000..d0e1f58
--- /dev/null
+++ b/packages/rfw/example/wasm/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
Binary files differ
diff --git a/packages/rfw/example/wasm/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/packages/rfw/example/wasm/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json
new file mode 100644
index 0000000..0bedcf2
--- /dev/null
+++ b/packages/rfw/example/wasm/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json
@@ -0,0 +1,23 @@
+{
+  "images" : [
+    {
+      "idiom" : "universal",
+      "filename" : "LaunchImage.png",
+      "scale" : "1x"
+    },
+    {
+      "idiom" : "universal",
+      "filename" : "LaunchImage@2x.png",
+      "scale" : "2x"
+    },
+    {
+      "idiom" : "universal",
+      "filename" : "LaunchImage@3x.png",
+      "scale" : "3x"
+    }
+  ],
+  "info" : {
+    "version" : 1,
+    "author" : "xcode"
+  }
+}
diff --git a/packages/rfw/example/wasm/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/packages/rfw/example/wasm/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
new file mode 100644
index 0000000..9da19ea
--- /dev/null
+++ b/packages/rfw/example/wasm/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
Binary files differ
diff --git a/packages/rfw/example/wasm/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/packages/rfw/example/wasm/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
new file mode 100644
index 0000000..9da19ea
--- /dev/null
+++ b/packages/rfw/example/wasm/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
Binary files differ
diff --git a/packages/rfw/example/wasm/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/packages/rfw/example/wasm/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
new file mode 100644
index 0000000..9da19ea
--- /dev/null
+++ b/packages/rfw/example/wasm/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
Binary files differ
diff --git a/packages/rfw/example/wasm/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/packages/rfw/example/wasm/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md
new file mode 100644
index 0000000..89c2725
--- /dev/null
+++ b/packages/rfw/example/wasm/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md
@@ -0,0 +1,5 @@
+# Launch Screen Assets
+
+You can customize the launch screen with your own desired assets by replacing the image files in this directory.
+
+You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.
\ No newline at end of file
diff --git a/packages/rfw/example/wasm/ios/Runner/Base.lproj/LaunchScreen.storyboard b/packages/rfw/example/wasm/ios/Runner/Base.lproj/LaunchScreen.storyboard
new file mode 100644
index 0000000..f2e259c
--- /dev/null
+++ b/packages/rfw/example/wasm/ios/Runner/Base.lproj/LaunchScreen.storyboard
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16G29" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
+    <dependencies>
+        <deployment identifier="iOS"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/>
+    </dependencies>
+    <scenes>
+        <!--View Controller-->
+        <scene sceneID="EHf-IW-A2E">
+            <objects>
+                <viewController id="01J-lp-oVM" sceneMemberID="viewController">
+                    <layoutGuides>
+                        <viewControllerLayoutGuide type="top" id="Ydg-fD-yQy"/>
+                        <viewControllerLayoutGuide type="bottom" id="xbc-2k-c8Z"/>
+                    </layoutGuides>
+                    <view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
+                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+                        <subviews>
+                            <imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4">
+                            </imageView>
+                        </subviews>
+                        <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+                        <constraints>
+                            <constraint firstItem="YRO-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/>
+                            <constraint firstItem="YRO-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4X2-HB-R7a"/>
+                        </constraints>
+                    </view>
+                </viewController>
+                <placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
+            </objects>
+            <point key="canvasLocation" x="53" y="375"/>
+        </scene>
+    </scenes>
+    <resources>
+        <image name="LaunchImage" width="168" height="185"/>
+    </resources>
+</document>
diff --git a/packages/rfw/example/wasm/ios/Runner/Base.lproj/Main.storyboard b/packages/rfw/example/wasm/ios/Runner/Base.lproj/Main.storyboard
new file mode 100644
index 0000000..f3c2851
--- /dev/null
+++ b/packages/rfw/example/wasm/ios/Runner/Base.lproj/Main.storyboard
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
+    <dependencies>
+        <deployment identifier="iOS"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
+    </dependencies>
+    <scenes>
+        <!--Flutter View Controller-->
+        <scene sceneID="tne-QT-ifu">
+            <objects>
+                <viewController id="BYZ-38-t0r" customClass="FlutterViewController" sceneMemberID="viewController">
+                    <layoutGuides>
+                        <viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
+                        <viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
+                    </layoutGuides>
+                    <view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
+                        <rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
+                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+                        <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
+                    </view>
+                </viewController>
+                <placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
+            </objects>
+        </scene>
+    </scenes>
+</document>
diff --git a/packages/rfw/example/wasm/ios/Runner/Info.plist b/packages/rfw/example/wasm/ios/Runner/Info.plist
new file mode 100644
index 0000000..5579c54
--- /dev/null
+++ b/packages/rfw/example/wasm/ios/Runner/Info.plist
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>CFBundleDevelopmentRegion</key>
+	<string>$(DEVELOPMENT_LANGUAGE)</string>
+	<key>CFBundleExecutable</key>
+	<string>$(EXECUTABLE_NAME)</string>
+	<key>CFBundleIdentifier</key>
+	<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
+	<key>CFBundleInfoDictionaryVersion</key>
+	<string>6.0</string>
+	<key>CFBundleName</key>
+	<string>wasm</string>
+	<key>CFBundlePackageType</key>
+	<string>APPL</string>
+	<key>CFBundleShortVersionString</key>
+	<string>$(FLUTTER_BUILD_NAME)</string>
+	<key>CFBundleSignature</key>
+	<string>????</string>
+	<key>CFBundleVersion</key>
+	<string>$(FLUTTER_BUILD_NUMBER)</string>
+	<key>LSRequiresIPhoneOS</key>
+	<true/>
+	<key>UILaunchStoryboardName</key>
+	<string>LaunchScreen</string>
+	<key>UIMainStoryboardFile</key>
+	<string>Main</string>
+	<key>UISupportedInterfaceOrientations</key>
+	<array>
+		<string>UIInterfaceOrientationPortrait</string>
+		<string>UIInterfaceOrientationLandscapeLeft</string>
+		<string>UIInterfaceOrientationLandscapeRight</string>
+	</array>
+	<key>UISupportedInterfaceOrientations~ipad</key>
+	<array>
+		<string>UIInterfaceOrientationPortrait</string>
+		<string>UIInterfaceOrientationPortraitUpsideDown</string>
+		<string>UIInterfaceOrientationLandscapeLeft</string>
+		<string>UIInterfaceOrientationLandscapeRight</string>
+	</array>
+	<key>UIViewControllerBasedStatusBarAppearance</key>
+	<false/>
+</dict>
+</plist>
diff --git a/packages/rfw/example/wasm/ios/Runner/Runner-Bridging-Header.h b/packages/rfw/example/wasm/ios/Runner/Runner-Bridging-Header.h
new file mode 100644
index 0000000..eb7e8ba
--- /dev/null
+++ b/packages/rfw/example/wasm/ios/Runner/Runner-Bridging-Header.h
@@ -0,0 +1,5 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "GeneratedPluginRegistrant.h"
diff --git a/packages/rfw/example/wasm/lib/main.dart b/packages/rfw/example/wasm/lib/main.dart
new file mode 100644
index 0000000..c91b4d0
--- /dev/null
+++ b/packages/rfw/example/wasm/lib/main.dart
@@ -0,0 +1,94 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This file is hand-formatted.
+
+import 'dart:async';
+import 'dart:io';
+
+import 'package:flutter/rendering.dart';
+import 'package:flutter/widgets.dart';
+import 'package:path/path.dart' as path;
+import 'package:path_provider/path_provider.dart';
+import 'package:rfw/rfw.dart';
+import 'package:wasm/wasm.dart';
+
+const String urlPrefix = 'https://raw.githubusercontent.com/flutter/packages/master/packages/rfw/example/wasm/logic';
+
+const String interfaceUrl = '$urlPrefix/calculator.rfw';
+const String logicUrl = '$urlPrefix/calculator.wasm';
+
+void main() {
+  runApp(WidgetsApp(color: const Color(0xFF000000), builder: (BuildContext context, Widget? navigator) => const Example()));
+}
+
+class Example extends StatefulWidget {
+  const Example({Key? key}) : super(key: key);
+
+  @override
+  State<Example> createState() => _ExampleState();
+}
+
+class _ExampleState extends State<Example> {
+  final Runtime _runtime = Runtime();
+  final DynamicContent _data = DynamicContent();
+  late final WasmInstance _logic;
+
+  @override
+  void initState() {
+    super.initState();
+    RendererBinding.instance!.deferFirstFrame();
+    _runtime.update(const LibraryName(<String>['core', 'widgets']), createCoreWidgets());
+    _loadLogic();
+  }
+
+  late final WasmFunction _dataFetcher;
+
+  Future<void> _loadLogic() async {
+    final DateTime expiryDate = DateTime.now().subtract(const Duration(hours: 6));
+    final Directory home = await getApplicationSupportDirectory();
+    final File interfaceFile = File(path.join(home.path, 'cache.rfw'));
+    if (!interfaceFile.existsSync() || interfaceFile.lastModifiedSync().isBefore(expiryDate)) {
+      final HttpClientResponse client = await (await HttpClient().getUrl(Uri.parse(interfaceUrl))).close();
+      await interfaceFile.writeAsBytes(await client.expand((List<int> chunk) => chunk).toList());
+    }
+    final File logicFile = File(path.join(home.path, 'cache.wasm'));
+    if (!logicFile.existsSync() || logicFile.lastModifiedSync().isBefore(expiryDate)) {
+      final HttpClientResponse client = await (await HttpClient().getUrl(Uri.parse(logicUrl))).close();
+      await logicFile.writeAsBytes(await client.expand((List<int> chunk) => chunk).toList());
+    }
+    _runtime.update(const LibraryName(<String>['main']), decodeLibraryBlob(await interfaceFile.readAsBytes()));
+    _logic = WasmModule(await logicFile.readAsBytes()).builder().build();
+    _dataFetcher = _logic.lookupFunction('value');
+    _updateData();
+    setState(() { RendererBinding.instance!.allowFirstFrame(); });
+  }
+
+  void _updateData() {
+    final dynamic value = _dataFetcher.apply(const <Object?>[]);
+    _data.update('value', <String, Object?>{ 'numeric': value, 'string': value.toString() });
+  }
+
+  List<Object?> _asList(Object? value) {
+    if (value is List<Object?>)
+      return value;
+    return const <Object?>[];
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    if (!RendererBinding.instance!.sendFramesToEngine)
+      return const SizedBox.shrink();
+    return RemoteWidget(
+      runtime: _runtime,
+      data: _data,
+      widget: const FullyQualifiedWidgetName(LibraryName(<String>['main']), 'root'),
+      onEvent: (String name, DynamicMap arguments) {
+        final WasmFunction function = _logic.lookupFunction(name);
+        function.apply(_asList(arguments['arguments']));
+        _updateData();
+      },
+    );
+  }
+}
diff --git a/packages/rfw/example/wasm/linux/.gitignore b/packages/rfw/example/wasm/linux/.gitignore
new file mode 100644
index 0000000..d3896c9
--- /dev/null
+++ b/packages/rfw/example/wasm/linux/.gitignore
@@ -0,0 +1 @@
+flutter/ephemeral
diff --git a/packages/rfw/example/wasm/linux/CMakeLists.txt b/packages/rfw/example/wasm/linux/CMakeLists.txt
new file mode 100644
index 0000000..cd53444
--- /dev/null
+++ b/packages/rfw/example/wasm/linux/CMakeLists.txt
@@ -0,0 +1,116 @@
+cmake_minimum_required(VERSION 3.10)
+project(runner LANGUAGES CXX)
+
+set(BINARY_NAME "wasm")
+set(APPLICATION_ID "dev.flutter.rfw.examples.wasm")
+
+cmake_policy(SET CMP0063 NEW)
+
+set(CMAKE_INSTALL_RPATH "$ORIGIN/lib")
+
+# Root filesystem for cross-building.
+if(FLUTTER_TARGET_PLATFORM_SYSROOT)
+  set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT})
+  set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT})
+  set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
+  set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
+  set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
+  set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
+endif()
+
+# Configure build options.
+if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
+  set(CMAKE_BUILD_TYPE "Debug" CACHE
+    STRING "Flutter build mode" FORCE)
+  set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
+    "Debug" "Profile" "Release")
+endif()
+
+# Compilation settings that should be applied to most targets.
+function(APPLY_STANDARD_SETTINGS TARGET)
+  target_compile_features(${TARGET} PUBLIC cxx_std_14)
+  target_compile_options(${TARGET} PRIVATE -Wall -Werror)
+  target_compile_options(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:-O3>")
+  target_compile_definitions(${TARGET} PRIVATE "$<$<NOT:$<CONFIG:Debug>>:NDEBUG>")
+endfunction()
+
+set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter")
+
+# Flutter library and tool build rules.
+add_subdirectory(${FLUTTER_MANAGED_DIR})
+
+# System-level dependencies.
+find_package(PkgConfig REQUIRED)
+pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
+
+add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}")
+
+# Application build
+add_executable(${BINARY_NAME}
+  "main.cc"
+  "my_application.cc"
+  "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc"
+)
+apply_standard_settings(${BINARY_NAME})
+target_link_libraries(${BINARY_NAME} PRIVATE flutter)
+target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK)
+add_dependencies(${BINARY_NAME} flutter_assemble)
+# Only the install-generated bundle's copy of the executable will launch
+# correctly, since the resources must in the right relative locations. To avoid
+# people trying to run the unbundled copy, put it in a subdirectory instead of
+# the default top-level location.
+set_target_properties(${BINARY_NAME}
+  PROPERTIES
+  RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run"
+)
+
+# Generated plugin build rules, which manage building the plugins and adding
+# them to the application.
+include(flutter/generated_plugins.cmake)
+
+
+# === Installation ===
+# By default, "installing" just makes a relocatable bundle in the build
+# directory.
+set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle")
+if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
+  set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE)
+endif()
+
+# Start with a clean build bundle directory every time.
+install(CODE "
+  file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\")
+  " COMPONENT Runtime)
+
+set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data")
+set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib")
+
+install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}"
+  COMPONENT Runtime)
+
+install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
+  COMPONENT Runtime)
+
+install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
+  COMPONENT Runtime)
+
+if(PLUGIN_BUNDLED_LIBRARIES)
+  install(FILES "${PLUGIN_BUNDLED_LIBRARIES}"
+    DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
+    COMPONENT Runtime)
+endif()
+
+# Fully re-copy the assets directory on each build to avoid having stale files
+# from a previous install.
+set(FLUTTER_ASSET_DIR_NAME "flutter_assets")
+install(CODE "
+  file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\")
+  " COMPONENT Runtime)
+install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}"
+  DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime)
+
+# Install the AOT library on non-Debug builds only.
+if(NOT CMAKE_BUILD_TYPE MATCHES "Debug")
+  install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
+    COMPONENT Runtime)
+endif()
diff --git a/packages/rfw/example/wasm/linux/flutter/CMakeLists.txt b/packages/rfw/example/wasm/linux/flutter/CMakeLists.txt
new file mode 100644
index 0000000..33fd580
--- /dev/null
+++ b/packages/rfw/example/wasm/linux/flutter/CMakeLists.txt
@@ -0,0 +1,87 @@
+cmake_minimum_required(VERSION 3.10)
+
+set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral")
+
+# Configuration provided via flutter tool.
+include(${EPHEMERAL_DIR}/generated_config.cmake)
+
+# TODO: Move the rest of this into files in ephemeral. See
+# https://github.com/flutter/flutter/issues/57146.
+
+# Serves the same purpose as list(TRANSFORM ... PREPEND ...),
+# which isn't available in 3.10.
+function(list_prepend LIST_NAME PREFIX)
+    set(NEW_LIST "")
+    foreach(element ${${LIST_NAME}})
+        list(APPEND NEW_LIST "${PREFIX}${element}")
+    endforeach(element)
+    set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE)
+endfunction()
+
+# === Flutter Library ===
+# System-level dependencies.
+find_package(PkgConfig REQUIRED)
+pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
+pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0)
+pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0)
+
+set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so")
+
+# Published to parent scope for install step.
+set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE)
+set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE)
+set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE)
+set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE)
+
+list(APPEND FLUTTER_LIBRARY_HEADERS
+  "fl_basic_message_channel.h"
+  "fl_binary_codec.h"
+  "fl_binary_messenger.h"
+  "fl_dart_project.h"
+  "fl_engine.h"
+  "fl_json_message_codec.h"
+  "fl_json_method_codec.h"
+  "fl_message_codec.h"
+  "fl_method_call.h"
+  "fl_method_channel.h"
+  "fl_method_codec.h"
+  "fl_method_response.h"
+  "fl_plugin_registrar.h"
+  "fl_plugin_registry.h"
+  "fl_standard_message_codec.h"
+  "fl_standard_method_codec.h"
+  "fl_string_codec.h"
+  "fl_value.h"
+  "fl_view.h"
+  "flutter_linux.h"
+)
+list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/")
+add_library(flutter INTERFACE)
+target_include_directories(flutter INTERFACE
+  "${EPHEMERAL_DIR}"
+)
+target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}")
+target_link_libraries(flutter INTERFACE
+  PkgConfig::GTK
+  PkgConfig::GLIB
+  PkgConfig::GIO
+)
+add_dependencies(flutter flutter_assemble)
+
+# === Flutter tool backend ===
+# _phony_ is a non-existent file to force this command to run every time,
+# since currently there's no way to get a full input/output list from the
+# flutter tool.
+add_custom_command(
+  OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS}
+    ${CMAKE_CURRENT_BINARY_DIR}/_phony_
+  COMMAND ${CMAKE_COMMAND} -E env
+    ${FLUTTER_TOOL_ENVIRONMENT}
+    "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh"
+      ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE}
+  VERBATIM
+)
+add_custom_target(flutter_assemble DEPENDS
+  "${FLUTTER_LIBRARY}"
+  ${FLUTTER_LIBRARY_HEADERS}
+)
diff --git a/packages/rfw/example/wasm/linux/flutter/generated_plugins.cmake b/packages/rfw/example/wasm/linux/flutter/generated_plugins.cmake
new file mode 100644
index 0000000..51436ae
--- /dev/null
+++ b/packages/rfw/example/wasm/linux/flutter/generated_plugins.cmake
@@ -0,0 +1,15 @@
+#
+# Generated file, do not edit.
+#
+
+list(APPEND FLUTTER_PLUGIN_LIST
+)
+
+set(PLUGIN_BUNDLED_LIBRARIES)
+
+foreach(plugin ${FLUTTER_PLUGIN_LIST})
+  add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin})
+  target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)
+  list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>)
+  list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
+endforeach(plugin)
diff --git a/packages/rfw/example/wasm/linux/main.cc b/packages/rfw/example/wasm/linux/main.cc
new file mode 100644
index 0000000..1507d02
--- /dev/null
+++ b/packages/rfw/example/wasm/linux/main.cc
@@ -0,0 +1,10 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "my_application.h"
+
+int main(int argc, char** argv) {
+  g_autoptr(MyApplication) app = my_application_new();
+  return g_application_run(G_APPLICATION(app), argc, argv);
+}
diff --git a/packages/rfw/example/wasm/linux/my_application.cc b/packages/rfw/example/wasm/linux/my_application.cc
new file mode 100644
index 0000000..ae90ddf
--- /dev/null
+++ b/packages/rfw/example/wasm/linux/my_application.cc
@@ -0,0 +1,111 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "my_application.h"
+
+#include <flutter_linux/flutter_linux.h>
+#ifdef GDK_WINDOWING_X11
+#include <gdk/gdkx.h>
+#endif
+
+#include "flutter/generated_plugin_registrant.h"
+
+struct _MyApplication {
+  GtkApplication parent_instance;
+  char** dart_entrypoint_arguments;
+};
+
+G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION)
+
+// Implements GApplication::activate.
+static void my_application_activate(GApplication* application) {
+  MyApplication* self = MY_APPLICATION(application);
+  GtkWindow* window =
+      GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application)));
+
+  // Use a header bar when running in GNOME as this is the common style used
+  // by applications and is the setup most users will be using (e.g. Ubuntu
+  // desktop).
+  // If running on X and not using GNOME then just use a traditional title bar
+  // in case the window manager does more exotic layout, e.g. tiling.
+  // If running on Wayland assume the header bar will work (may need changing
+  // if future cases occur).
+  gboolean use_header_bar = TRUE;
+#ifdef GDK_WINDOWING_X11
+  GdkScreen* screen = gtk_window_get_screen(window);
+  if (GDK_IS_X11_SCREEN(screen)) {
+    const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen);
+    if (g_strcmp0(wm_name, "GNOME Shell") != 0) {
+      use_header_bar = FALSE;
+    }
+  }
+#endif
+  if (use_header_bar) {
+    GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new());
+    gtk_widget_show(GTK_WIDGET(header_bar));
+    gtk_header_bar_set_title(header_bar, "wasm");
+    gtk_header_bar_set_show_close_button(header_bar, TRUE);
+    gtk_window_set_titlebar(window, GTK_WIDGET(header_bar));
+  } else {
+    gtk_window_set_title(window, "wasm");
+  }
+
+  gtk_window_set_default_size(window, 1280, 720);
+  gtk_widget_show(GTK_WIDGET(window));
+
+  g_autoptr(FlDartProject) project = fl_dart_project_new();
+  fl_dart_project_set_dart_entrypoint_arguments(
+      project, self->dart_entrypoint_arguments);
+
+  FlView* view = fl_view_new(project);
+  gtk_widget_show(GTK_WIDGET(view));
+  gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view));
+
+  fl_register_plugins(FL_PLUGIN_REGISTRY(view));
+
+  gtk_widget_grab_focus(GTK_WIDGET(view));
+}
+
+// Implements GApplication::local_command_line.
+static gboolean my_application_local_command_line(GApplication* application,
+                                                  gchar*** arguments,
+                                                  int* exit_status) {
+  MyApplication* self = MY_APPLICATION(application);
+  // Strip out the first argument as it is the binary name.
+  self->dart_entrypoint_arguments = g_strdupv(*arguments + 1);
+
+  g_autoptr(GError) error = nullptr;
+  if (!g_application_register(application, nullptr, &error)) {
+    g_warning("Failed to register: %s", error->message);
+    *exit_status = 1;
+    return TRUE;
+  }
+
+  g_application_activate(application);
+  *exit_status = 0;
+
+  return TRUE;
+}
+
+// Implements GObject::dispose.
+static void my_application_dispose(GObject* object) {
+  MyApplication* self = MY_APPLICATION(object);
+  g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev);
+  G_OBJECT_CLASS(my_application_parent_class)->dispose(object);
+}
+
+static void my_application_class_init(MyApplicationClass* klass) {
+  G_APPLICATION_CLASS(klass)->activate = my_application_activate;
+  G_APPLICATION_CLASS(klass)->local_command_line =
+      my_application_local_command_line;
+  G_OBJECT_CLASS(klass)->dispose = my_application_dispose;
+}
+
+static void my_application_init(MyApplication* self) {}
+
+MyApplication* my_application_new() {
+  return MY_APPLICATION(g_object_new(my_application_get_type(),
+                                     "application-id", APPLICATION_ID, "flags",
+                                     G_APPLICATION_NON_UNIQUE, nullptr));
+}
diff --git a/packages/rfw/example/wasm/linux/my_application.h b/packages/rfw/example/wasm/linux/my_application.h
new file mode 100644
index 0000000..6e9f0c3
--- /dev/null
+++ b/packages/rfw/example/wasm/linux/my_application.h
@@ -0,0 +1,22 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef FLUTTER_MY_APPLICATION_H_
+#define FLUTTER_MY_APPLICATION_H_
+
+#include <gtk/gtk.h>
+
+G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION,
+                     GtkApplication)
+
+/**
+ * my_application_new:
+ *
+ * Creates a new Flutter-based application.
+ *
+ * Returns: a new #MyApplication.
+ */
+MyApplication* my_application_new();
+
+#endif  // FLUTTER_MY_APPLICATION_H_
diff --git a/packages/rfw/example/wasm/logic/build.sh b/packages/rfw/example/wasm/logic/build.sh
new file mode 100755
index 0000000..6434381
--- /dev/null
+++ b/packages/rfw/example/wasm/logic/build.sh
@@ -0,0 +1,9 @@
+#!/bin/bash
+# Copyright 2013 The Flutter Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+set -ex
+
+clang++ --target=wasm32 -nostdlib "-Wl,--export-all" "-Wl,--no-entry" -o calculator.wasm calculator.cc
+dart encode.dart calculator.rfwtxt calculator.rfw
diff --git a/packages/rfw/example/wasm/logic/calculator.cc b/packages/rfw/example/wasm/logic/calculator.cc
new file mode 100644
index 0000000..b744a0f
--- /dev/null
+++ b/packages/rfw/example/wasm/logic/calculator.cc
@@ -0,0 +1,50 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// internals
+
+enum Ops { opNone, opAdd };
+
+int _pending = 0;
+Ops _pendingOp = opNone;
+int _display = 0;
+bool _displayLocked = false;
+
+void _resolve() {
+  switch (_pendingOp) {
+    case opNone:
+      break;
+    case opAdd:
+      _display += _pending;
+      break;
+  }
+}
+
+// public API
+
+extern "C" int value() { return _display; }
+
+extern "C" void digit(int n) {
+  if (_displayLocked) {
+    _display = 0;
+  }
+  _display *= 10;
+  _display += n;
+  _displayLocked = false;
+}
+
+extern "C" void add() {
+  _resolve();
+  _pending = _display;
+  _pendingOp = opAdd;
+  _display = 0;
+  _displayLocked = false;
+}
+
+extern "C" void equals() {
+  int current = _displayLocked ? _pending : _display;
+  _resolve();
+  _pending = current;
+  _displayLocked = true;
+}
diff --git a/packages/rfw/example/wasm/logic/calculator.rfw b/packages/rfw/example/wasm/logic/calculator.rfw
new file mode 100644
index 0000000..85166b3
--- /dev/null
+++ b/packages/rfw/example/wasm/logic/calculator.rfw
Binary files differ
diff --git a/packages/rfw/example/wasm/logic/calculator.rfwtxt b/packages/rfw/example/wasm/logic/calculator.rfwtxt
new file mode 100644
index 0000000..900d984
--- /dev/null
+++ b/packages/rfw/example/wasm/logic/calculator.rfwtxt
@@ -0,0 +1,140 @@
+import core.widgets;
+
+widget root = Calculator();
+
+widget Calculator = SafeArea(
+  child: Center(
+    child: IntrinsicWidth(
+      child: Column(
+        crossAxisAlignment: "stretch",
+        children: [
+          Display(text: data.value.string),
+          Expanded(
+            child: FittedBox(
+              alignment: { x: 0.0, y: 1.0 },
+              child: CalculatorPad(),
+            ),
+          ),
+        ],
+      ),
+    ),
+  ),
+);
+
+widget CalculatorPad = Column(
+  children: [
+    Row(
+      children: [
+        CalculatorButton(label: "7", onPressed: event "digit" { arguments: [7] }),
+        CalculatorButton(label: "8", onPressed: event "digit" { arguments: [8] }),
+        CalculatorButton(label: "9", onPressed: event "digit" { arguments: [9] }),
+        SizedBox(width: 116.0, height: 116.0),
+      ],
+    ),
+    Row(
+      children: [
+        CalculatorButton(label: "4", onPressed: event "digit" { arguments: [4] }),
+        CalculatorButton(label: "5", onPressed: event "digit" { arguments: [5] }),
+        CalculatorButton(label: "6", onPressed: event "digit" { arguments: [6] }),
+        CalculatorButton(label: "+", onPressed: event "add" { }),
+      ],
+    ),
+    Row(
+      children: [
+        CalculatorButton(label: "1", onPressed: event "digit" { arguments: [1] }),
+        CalculatorButton(label: "2", onPressed: event "digit" { arguments: [2] }),
+        CalculatorButton(label: "3", onPressed: event "digit" { arguments: [3] }),
+        CalculatorButton(label: "=", onPressed: event "equals" { }),
+      ],
+    ),
+    Row(
+      children: [
+        SizedBox(width: 116.0, height: 116.0),
+        CalculatorButton(label: "0", onPressed: event "digit" { arguments: [0] }),
+        SizedBox(width: 116.0, height: 116.0),
+        SizedBox(width: 116.0, height: 116.0),
+      ],
+    ),
+  ],
+);
+
+widget CalculatorButton = Padding(
+  padding: [8.0],
+  child: SizedBox(
+    width: 100.0,
+    height: 100.0,
+    child: Button(
+      child: FittedBox(child: Text(text: args.label)),
+      onPressed: args.onPressed,
+    ),
+  ),
+);
+
+widget Button { down: false } = GestureDetector(
+  onTap: args.onPressed,
+  onTapDown: set state.down = true,
+  onTapUp: set state.down = false,
+  onTapCancel: set state.down = false,
+  child: Container(
+    duration: 50,
+    margin: switch state.down {
+      false: [ 0.0, 0.0, 2.0, 2.0 ],
+      true: [ 2.0, 2.0, 0.0, 0.0 ],
+    },
+    padding: [ 12.0, 8.0 ],
+    decoration: {
+      type: "shape",
+      shape: {
+        type: "stadium",
+        side: { width: 1.0 },
+      },
+      gradient: {
+        type: "linear",
+        begin: { x: -0.5, y: -0.25 },
+        end: { x: 0.0, y: 0.5 },
+        colors: [ 0xFFFFFF99, 0xFFEEDD00 ],
+        stops: [ 0.0, 1.0 ],
+        tileMode: "mirror",
+      },
+      shadows: switch state.down {
+        false: [ { blurRadius: 4.0, spreadRadius: 0.5, offset: { x: 1.0, y: 1.0, } } ],
+        default: [],
+      },
+    },
+    child: DefaultTextStyle(
+      style: {
+        color: 0xFF000000,
+        fontSize: 32.0,
+      },
+      child: args.child,
+    ),
+  ),
+);
+
+widget Display = Container(
+  height: 80.0,
+  margin: [18.0],
+  padding: [20.0, 12.0],
+  decoration: {
+    type: 'shape',
+    gradient: {
+      type: "linear",
+      begin: { x: 0.0, y: -0.5 },
+      end: { x: 0.05, y: 1.0 },
+      colors: [ 0xFFFFFFFF, 0xFFDDDDDD ],
+      stops: [ 0.0, 1.0 ],
+      tileMode: "mirror",
+    },
+    shape: {
+      type: "stadium",
+      borderRadius: [ { x: 40.0 } ],
+      side: { color: 0xFFCCCCCC, },
+    }
+  },
+  child: SizedBoxExpand(
+    child: FittedBox(
+      alignment: { x: 1.0, y: 0.0, },
+      child: Text(text: args.text, style: { color: 0xFF000000 }),
+    ),
+  ),
+);
diff --git a/packages/rfw/example/wasm/logic/calculator.wasm b/packages/rfw/example/wasm/logic/calculator.wasm
new file mode 100755
index 0000000..685c41e
--- /dev/null
+++ b/packages/rfw/example/wasm/logic/calculator.wasm
Binary files differ
diff --git a/packages/rfw/example/wasm/logic/encode.dart b/packages/rfw/example/wasm/logic/encode.dart
new file mode 100644
index 0000000..610c0b6
--- /dev/null
+++ b/packages/rfw/example/wasm/logic/encode.dart
@@ -0,0 +1,19 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'dart:io';
+
+import 'package:rfw/formats.dart';
+
+void main(List<String> arguments) {
+  if (arguments.length != 2) {
+    print('usage: dart encode.dart source.rfwtxt output.rfw');
+    exit(1);
+  }
+  File(arguments[1]).writeAsBytesSync(
+    encodeLibraryBlob(
+      parseLibraryFile(File(arguments[0]).readAsStringSync()),
+    ),
+  );
+}
diff --git a/packages/rfw/example/wasm/macos/.gitignore b/packages/rfw/example/wasm/macos/.gitignore
new file mode 100644
index 0000000..d2fd377
--- /dev/null
+++ b/packages/rfw/example/wasm/macos/.gitignore
@@ -0,0 +1,6 @@
+# Flutter-related
+**/Flutter/ephemeral/
+**/Pods/
+
+# Xcode-related
+**/xcuserdata/
diff --git a/packages/rfw/example/wasm/macos/Flutter/Flutter-Debug.xcconfig b/packages/rfw/example/wasm/macos/Flutter/Flutter-Debug.xcconfig
new file mode 100644
index 0000000..c2efd0b
--- /dev/null
+++ b/packages/rfw/example/wasm/macos/Flutter/Flutter-Debug.xcconfig
@@ -0,0 +1 @@
+#include "ephemeral/Flutter-Generated.xcconfig"
diff --git a/packages/rfw/example/wasm/macos/Flutter/Flutter-Release.xcconfig b/packages/rfw/example/wasm/macos/Flutter/Flutter-Release.xcconfig
new file mode 100644
index 0000000..c2efd0b
--- /dev/null
+++ b/packages/rfw/example/wasm/macos/Flutter/Flutter-Release.xcconfig
@@ -0,0 +1 @@
+#include "ephemeral/Flutter-Generated.xcconfig"
diff --git a/packages/rfw/example/wasm/macos/Runner.xcodeproj/project.pbxproj b/packages/rfw/example/wasm/macos/Runner.xcodeproj/project.pbxproj
new file mode 100644
index 0000000..19d957a
--- /dev/null
+++ b/packages/rfw/example/wasm/macos/Runner.xcodeproj/project.pbxproj
@@ -0,0 +1,572 @@
+// !$*UTF8*$!
+{
+	archiveVersion = 1;
+	classes = {
+	};
+	objectVersion = 51;
+	objects = {
+
+/* Begin PBXAggregateTarget section */
+		33CC111A2044C6BA0003C045 /* Flutter Assemble */ = {
+			isa = PBXAggregateTarget;
+			buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */;
+			buildPhases = (
+				33CC111E2044C6BF0003C045 /* ShellScript */,
+			);
+			dependencies = (
+			);
+			name = "Flutter Assemble";
+			productName = FLX;
+		};
+/* End PBXAggregateTarget section */
+
+/* Begin PBXBuildFile section */
+		335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; };
+		33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; };
+		33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; };
+		33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; };
+		33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXContainerItemProxy section */
+		33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = {
+			isa = PBXContainerItemProxy;
+			containerPortal = 33CC10E52044A3C60003C045 /* Project object */;
+			proxyType = 1;
+			remoteGlobalIDString = 33CC111A2044C6BA0003C045;
+			remoteInfo = FLX;
+		};
+/* End PBXContainerItemProxy section */
+
+/* Begin PBXCopyFilesBuildPhase section */
+		33CC110E2044A8840003C045 /* Bundle Framework */ = {
+			isa = PBXCopyFilesBuildPhase;
+			buildActionMask = 2147483647;
+			dstPath = "";
+			dstSubfolderSpec = 10;
+			files = (
+			);
+			name = "Bundle Framework";
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXCopyFilesBuildPhase section */
+
+/* Begin PBXFileReference section */
+		333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = "<group>"; };
+		335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = "<group>"; };
+		33CC10ED2044A3C60003C045 /* wasm.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "wasm.app"; sourceTree = BUILT_PRODUCTS_DIR; };
+		33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
+		33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = "<group>"; };
+		33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = "<group>"; };
+		33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = "<group>"; };
+		33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = "<group>"; };
+		33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = "<group>"; };
+		33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = "<group>"; };
+		33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = "<group>"; };
+		33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = "<group>"; };
+		33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = "<group>"; };
+		33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = "<group>"; };
+		7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = "<group>"; };
+		9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = "<group>"; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+		33CC10EA2044A3C60003C045 /* Frameworks */ = {
+			isa = PBXFrameworksBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+		33BA886A226E78AF003329D5 /* Configs */ = {
+			isa = PBXGroup;
+			children = (
+				33E5194F232828860026EE4D /* AppInfo.xcconfig */,
+				9740EEB21CF90195004384FC /* Debug.xcconfig */,
+				7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
+				333000ED22D3DE5D00554162 /* Warnings.xcconfig */,
+			);
+			path = Configs;
+			sourceTree = "<group>";
+		};
+		33CC10E42044A3C60003C045 = {
+			isa = PBXGroup;
+			children = (
+				33FAB671232836740065AC1E /* Runner */,
+				33CEB47122A05771004F2AC0 /* Flutter */,
+				33CC10EE2044A3C60003C045 /* Products */,
+				D73912EC22F37F3D000D13A0 /* Frameworks */,
+			);
+			sourceTree = "<group>";
+		};
+		33CC10EE2044A3C60003C045 /* Products */ = {
+			isa = PBXGroup;
+			children = (
+				33CC10ED2044A3C60003C045 /* wasm.app */,
+			);
+			name = Products;
+			sourceTree = "<group>";
+		};
+		33CC11242044D66E0003C045 /* Resources */ = {
+			isa = PBXGroup;
+			children = (
+				33CC10F22044A3C60003C045 /* Assets.xcassets */,
+				33CC10F42044A3C60003C045 /* MainMenu.xib */,
+				33CC10F72044A3C60003C045 /* Info.plist */,
+			);
+			name = Resources;
+			path = ..;
+			sourceTree = "<group>";
+		};
+		33CEB47122A05771004F2AC0 /* Flutter */ = {
+			isa = PBXGroup;
+			children = (
+				335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */,
+				33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */,
+				33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */,
+				33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */,
+			);
+			path = Flutter;
+			sourceTree = "<group>";
+		};
+		33FAB671232836740065AC1E /* Runner */ = {
+			isa = PBXGroup;
+			children = (
+				33CC10F02044A3C60003C045 /* AppDelegate.swift */,
+				33CC11122044BFA00003C045 /* MainFlutterWindow.swift */,
+				33E51913231747F40026EE4D /* DebugProfile.entitlements */,
+				33E51914231749380026EE4D /* Release.entitlements */,
+				33CC11242044D66E0003C045 /* Resources */,
+				33BA886A226E78AF003329D5 /* Configs */,
+			);
+			path = Runner;
+			sourceTree = "<group>";
+		};
+		D73912EC22F37F3D000D13A0 /* Frameworks */ = {
+			isa = PBXGroup;
+			children = (
+			);
+			name = Frameworks;
+			sourceTree = "<group>";
+		};
+/* End PBXGroup section */
+
+/* Begin PBXNativeTarget section */
+		33CC10EC2044A3C60003C045 /* Runner */ = {
+			isa = PBXNativeTarget;
+			buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */;
+			buildPhases = (
+				33CC10E92044A3C60003C045 /* Sources */,
+				33CC10EA2044A3C60003C045 /* Frameworks */,
+				33CC10EB2044A3C60003C045 /* Resources */,
+				33CC110E2044A8840003C045 /* Bundle Framework */,
+				3399D490228B24CF009A79C7 /* ShellScript */,
+			);
+			buildRules = (
+			);
+			dependencies = (
+				33CC11202044C79F0003C045 /* PBXTargetDependency */,
+			);
+			name = Runner;
+			productName = Runner;
+			productReference = 33CC10ED2044A3C60003C045 /* wasm.app */;
+			productType = "com.apple.product-type.application";
+		};
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+		33CC10E52044A3C60003C045 /* Project object */ = {
+			isa = PBXProject;
+			attributes = {
+				LastSwiftUpdateCheck = 0920;
+				LastUpgradeCheck = 1300;
+				ORGANIZATIONNAME = "";
+				TargetAttributes = {
+					33CC10EC2044A3C60003C045 = {
+						CreatedOnToolsVersion = 9.2;
+						LastSwiftMigration = 1100;
+						ProvisioningStyle = Automatic;
+						SystemCapabilities = {
+							com.apple.Sandbox = {
+								enabled = 1;
+							};
+						};
+					};
+					33CC111A2044C6BA0003C045 = {
+						CreatedOnToolsVersion = 9.2;
+						ProvisioningStyle = Manual;
+					};
+				};
+			};
+			buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */;
+			compatibilityVersion = "Xcode 9.3";
+			developmentRegion = en;
+			hasScannedForEncodings = 0;
+			knownRegions = (
+				en,
+				Base,
+			);
+			mainGroup = 33CC10E42044A3C60003C045;
+			productRefGroup = 33CC10EE2044A3C60003C045 /* Products */;
+			projectDirPath = "";
+			projectRoot = "";
+			targets = (
+				33CC10EC2044A3C60003C045 /* Runner */,
+				33CC111A2044C6BA0003C045 /* Flutter Assemble */,
+			);
+		};
+/* End PBXProject section */
+
+/* Begin PBXResourcesBuildPhase section */
+		33CC10EB2044A3C60003C045 /* Resources */ = {
+			isa = PBXResourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */,
+				33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXResourcesBuildPhase section */
+
+/* Begin PBXShellScriptBuildPhase section */
+		3399D490228B24CF009A79C7 /* ShellScript */ = {
+			isa = PBXShellScriptBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			inputFileListPaths = (
+			);
+			inputPaths = (
+			);
+			outputFileListPaths = (
+			);
+			outputPaths = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+			shellPath = /bin/sh;
+			shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n";
+		};
+		33CC111E2044C6BF0003C045 /* ShellScript */ = {
+			isa = PBXShellScriptBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			inputFileListPaths = (
+				Flutter/ephemeral/FlutterInputs.xcfilelist,
+			);
+			inputPaths = (
+				Flutter/ephemeral/tripwire,
+			);
+			outputFileListPaths = (
+				Flutter/ephemeral/FlutterOutputs.xcfilelist,
+			);
+			outputPaths = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+			shellPath = /bin/sh;
+			shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire";
+		};
+/* End PBXShellScriptBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+		33CC10E92044A3C60003C045 /* Sources */ = {
+			isa = PBXSourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */,
+				33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */,
+				335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXSourcesBuildPhase section */
+
+/* Begin PBXTargetDependency section */
+		33CC11202044C79F0003C045 /* PBXTargetDependency */ = {
+			isa = PBXTargetDependency;
+			target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */;
+			targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */;
+		};
+/* End PBXTargetDependency section */
+
+/* Begin PBXVariantGroup section */
+		33CC10F42044A3C60003C045 /* MainMenu.xib */ = {
+			isa = PBXVariantGroup;
+			children = (
+				33CC10F52044A3C60003C045 /* Base */,
+			);
+			name = MainMenu.xib;
+			path = Runner;
+			sourceTree = "<group>";
+		};
+/* End PBXVariantGroup section */
+
+/* Begin XCBuildConfiguration section */
+		338D0CE9231458BD00FA5F75 /* Profile */ = {
+			isa = XCBuildConfiguration;
+			baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
+			buildSettings = {
+				ALWAYS_SEARCH_USER_PATHS = NO;
+				CLANG_ANALYZER_NONNULL = YES;
+				CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+				CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+				CLANG_CXX_LIBRARY = "libc++";
+				CLANG_ENABLE_MODULES = YES;
+				CLANG_ENABLE_OBJC_ARC = YES;
+				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+				CLANG_WARN_BOOL_CONVERSION = YES;
+				CLANG_WARN_CONSTANT_CONVERSION = YES;
+				CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+				CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+				CLANG_WARN_EMPTY_BODY = YES;
+				CLANG_WARN_ENUM_CONVERSION = YES;
+				CLANG_WARN_INFINITE_RECURSION = YES;
+				CLANG_WARN_INT_CONVERSION = YES;
+				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+				CLANG_WARN_SUSPICIOUS_MOVE = YES;
+				CODE_SIGN_IDENTITY = "-";
+				COPY_PHASE_STRIP = NO;
+				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+				ENABLE_NS_ASSERTIONS = NO;
+				ENABLE_STRICT_OBJC_MSGSEND = YES;
+				GCC_C_LANGUAGE_STANDARD = gnu11;
+				GCC_NO_COMMON_BLOCKS = YES;
+				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+				GCC_WARN_UNUSED_FUNCTION = YES;
+				GCC_WARN_UNUSED_VARIABLE = YES;
+				MACOSX_DEPLOYMENT_TARGET = 10.11;
+				MTL_ENABLE_DEBUG_INFO = NO;
+				SDKROOT = macosx;
+				SWIFT_COMPILATION_MODE = wholemodule;
+				SWIFT_OPTIMIZATION_LEVEL = "-O";
+			};
+			name = Profile;
+		};
+		338D0CEA231458BD00FA5F75 /* Profile */ = {
+			isa = XCBuildConfiguration;
+			baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */;
+			buildSettings = {
+				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+				CLANG_ENABLE_MODULES = YES;
+				CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements;
+				CODE_SIGN_STYLE = Automatic;
+				COMBINE_HIDPI_IMAGES = YES;
+				INFOPLIST_FILE = Runner/Info.plist;
+				LD_RUNPATH_SEARCH_PATHS = (
+					"$(inherited)",
+					"@executable_path/../Frameworks",
+				);
+				PROVISIONING_PROFILE_SPECIFIER = "";
+				SWIFT_VERSION = 5.0;
+			};
+			name = Profile;
+		};
+		338D0CEB231458BD00FA5F75 /* Profile */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				CODE_SIGN_STYLE = Manual;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+			};
+			name = Profile;
+		};
+		33CC10F92044A3C60003C045 /* Debug */ = {
+			isa = XCBuildConfiguration;
+			baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
+			buildSettings = {
+				ALWAYS_SEARCH_USER_PATHS = NO;
+				CLANG_ANALYZER_NONNULL = YES;
+				CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+				CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+				CLANG_CXX_LIBRARY = "libc++";
+				CLANG_ENABLE_MODULES = YES;
+				CLANG_ENABLE_OBJC_ARC = YES;
+				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+				CLANG_WARN_BOOL_CONVERSION = YES;
+				CLANG_WARN_CONSTANT_CONVERSION = YES;
+				CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+				CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+				CLANG_WARN_EMPTY_BODY = YES;
+				CLANG_WARN_ENUM_CONVERSION = YES;
+				CLANG_WARN_INFINITE_RECURSION = YES;
+				CLANG_WARN_INT_CONVERSION = YES;
+				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+				CLANG_WARN_SUSPICIOUS_MOVE = YES;
+				CODE_SIGN_IDENTITY = "-";
+				COPY_PHASE_STRIP = NO;
+				DEBUG_INFORMATION_FORMAT = dwarf;
+				ENABLE_STRICT_OBJC_MSGSEND = YES;
+				ENABLE_TESTABILITY = YES;
+				GCC_C_LANGUAGE_STANDARD = gnu11;
+				GCC_DYNAMIC_NO_PIC = NO;
+				GCC_NO_COMMON_BLOCKS = YES;
+				GCC_OPTIMIZATION_LEVEL = 0;
+				GCC_PREPROCESSOR_DEFINITIONS = (
+					"DEBUG=1",
+					"$(inherited)",
+				);
+				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+				GCC_WARN_UNUSED_FUNCTION = YES;
+				GCC_WARN_UNUSED_VARIABLE = YES;
+				MACOSX_DEPLOYMENT_TARGET = 10.11;
+				MTL_ENABLE_DEBUG_INFO = YES;
+				ONLY_ACTIVE_ARCH = YES;
+				SDKROOT = macosx;
+				SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
+				SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+			};
+			name = Debug;
+		};
+		33CC10FA2044A3C60003C045 /* Release */ = {
+			isa = XCBuildConfiguration;
+			baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
+			buildSettings = {
+				ALWAYS_SEARCH_USER_PATHS = NO;
+				CLANG_ANALYZER_NONNULL = YES;
+				CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+				CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+				CLANG_CXX_LIBRARY = "libc++";
+				CLANG_ENABLE_MODULES = YES;
+				CLANG_ENABLE_OBJC_ARC = YES;
+				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+				CLANG_WARN_BOOL_CONVERSION = YES;
+				CLANG_WARN_CONSTANT_CONVERSION = YES;
+				CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+				CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+				CLANG_WARN_EMPTY_BODY = YES;
+				CLANG_WARN_ENUM_CONVERSION = YES;
+				CLANG_WARN_INFINITE_RECURSION = YES;
+				CLANG_WARN_INT_CONVERSION = YES;
+				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+				CLANG_WARN_SUSPICIOUS_MOVE = YES;
+				CODE_SIGN_IDENTITY = "-";
+				COPY_PHASE_STRIP = NO;
+				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+				ENABLE_NS_ASSERTIONS = NO;
+				ENABLE_STRICT_OBJC_MSGSEND = YES;
+				GCC_C_LANGUAGE_STANDARD = gnu11;
+				GCC_NO_COMMON_BLOCKS = YES;
+				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+				GCC_WARN_UNUSED_FUNCTION = YES;
+				GCC_WARN_UNUSED_VARIABLE = YES;
+				MACOSX_DEPLOYMENT_TARGET = 10.11;
+				MTL_ENABLE_DEBUG_INFO = NO;
+				SDKROOT = macosx;
+				SWIFT_COMPILATION_MODE = wholemodule;
+				SWIFT_OPTIMIZATION_LEVEL = "-O";
+			};
+			name = Release;
+		};
+		33CC10FC2044A3C60003C045 /* Debug */ = {
+			isa = XCBuildConfiguration;
+			baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */;
+			buildSettings = {
+				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+				CLANG_ENABLE_MODULES = YES;
+				CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements;
+				CODE_SIGN_STYLE = Automatic;
+				COMBINE_HIDPI_IMAGES = YES;
+				INFOPLIST_FILE = Runner/Info.plist;
+				LD_RUNPATH_SEARCH_PATHS = (
+					"$(inherited)",
+					"@executable_path/../Frameworks",
+				);
+				PROVISIONING_PROFILE_SPECIFIER = "";
+				SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+				SWIFT_VERSION = 5.0;
+			};
+			name = Debug;
+		};
+		33CC10FD2044A3C60003C045 /* Release */ = {
+			isa = XCBuildConfiguration;
+			baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */;
+			buildSettings = {
+				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+				CLANG_ENABLE_MODULES = YES;
+				CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements;
+				CODE_SIGN_STYLE = Automatic;
+				COMBINE_HIDPI_IMAGES = YES;
+				INFOPLIST_FILE = Runner/Info.plist;
+				LD_RUNPATH_SEARCH_PATHS = (
+					"$(inherited)",
+					"@executable_path/../Frameworks",
+				);
+				PROVISIONING_PROFILE_SPECIFIER = "";
+				SWIFT_VERSION = 5.0;
+			};
+			name = Release;
+		};
+		33CC111C2044C6BA0003C045 /* Debug */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				CODE_SIGN_STYLE = Manual;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+			};
+			name = Debug;
+		};
+		33CC111D2044C6BA0003C045 /* Release */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				CODE_SIGN_STYLE = Automatic;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+			};
+			name = Release;
+		};
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+		33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				33CC10F92044A3C60003C045 /* Debug */,
+				33CC10FA2044A3C60003C045 /* Release */,
+				338D0CE9231458BD00FA5F75 /* Profile */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
+		};
+		33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				33CC10FC2044A3C60003C045 /* Debug */,
+				33CC10FD2044A3C60003C045 /* Release */,
+				338D0CEA231458BD00FA5F75 /* Profile */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
+		};
+		33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				33CC111C2044C6BA0003C045 /* Debug */,
+				33CC111D2044C6BA0003C045 /* Release */,
+				338D0CEB231458BD00FA5F75 /* Profile */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
+		};
+/* End XCConfigurationList section */
+	};
+	rootObject = 33CC10E52044A3C60003C045 /* Project object */;
+}
diff --git a/packages/rfw/example/wasm/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/rfw/example/wasm/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
new file mode 100644
index 0000000..18d9810
--- /dev/null
+++ b/packages/rfw/example/wasm/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>IDEDidComputeMac32BitWarning</key>
+	<true/>
+</dict>
+</plist>
diff --git a/packages/rfw/example/wasm/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/rfw/example/wasm/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
new file mode 100644
index 0000000..3ac0eab
--- /dev/null
+++ b/packages/rfw/example/wasm/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
@@ -0,0 +1,87 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Scheme
+   LastUpgradeVersion = "1300"
+   version = "1.3">
+   <BuildAction
+      parallelizeBuildables = "YES"
+      buildImplicitDependencies = "YES">
+      <BuildActionEntries>
+         <BuildActionEntry
+            buildForTesting = "YES"
+            buildForRunning = "YES"
+            buildForProfiling = "YES"
+            buildForArchiving = "YES"
+            buildForAnalyzing = "YES">
+            <BuildableReference
+               BuildableIdentifier = "primary"
+               BlueprintIdentifier = "33CC10EC2044A3C60003C045"
+               BuildableName = "wasm.app"
+               BlueprintName = "Runner"
+               ReferencedContainer = "container:Runner.xcodeproj">
+            </BuildableReference>
+         </BuildActionEntry>
+      </BuildActionEntries>
+   </BuildAction>
+   <TestAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      shouldUseLaunchSchemeArgsEnv = "YES">
+      <MacroExpansion>
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "33CC10EC2044A3C60003C045"
+            BuildableName = "wasm.app"
+            BlueprintName = "Runner"
+            ReferencedContainer = "container:Runner.xcodeproj">
+         </BuildableReference>
+      </MacroExpansion>
+      <Testables>
+      </Testables>
+   </TestAction>
+   <LaunchAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      launchStyle = "0"
+      useCustomWorkingDirectory = "NO"
+      ignoresPersistentStateOnLaunch = "NO"
+      debugDocumentVersioning = "YES"
+      debugServiceExtension = "internal"
+      allowLocationSimulation = "YES">
+      <BuildableProductRunnable
+         runnableDebuggingMode = "0">
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "33CC10EC2044A3C60003C045"
+            BuildableName = "wasm.app"
+            BlueprintName = "Runner"
+            ReferencedContainer = "container:Runner.xcodeproj">
+         </BuildableReference>
+      </BuildableProductRunnable>
+   </LaunchAction>
+   <ProfileAction
+      buildConfiguration = "Profile"
+      shouldUseLaunchSchemeArgsEnv = "YES"
+      savedToolIdentifier = ""
+      useCustomWorkingDirectory = "NO"
+      debugDocumentVersioning = "YES">
+      <BuildableProductRunnable
+         runnableDebuggingMode = "0">
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "33CC10EC2044A3C60003C045"
+            BuildableName = "wasm.app"
+            BlueprintName = "Runner"
+            ReferencedContainer = "container:Runner.xcodeproj">
+         </BuildableReference>
+      </BuildableProductRunnable>
+   </ProfileAction>
+   <AnalyzeAction
+      buildConfiguration = "Debug">
+   </AnalyzeAction>
+   <ArchiveAction
+      buildConfiguration = "Release"
+      revealArchiveInOrganizer = "YES">
+   </ArchiveAction>
+</Scheme>
diff --git a/packages/rfw/example/wasm/macos/Runner.xcworkspace/contents.xcworkspacedata b/packages/rfw/example/wasm/macos/Runner.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 0000000..1d526a1
--- /dev/null
+++ b/packages/rfw/example/wasm/macos/Runner.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Workspace
+   version = "1.0">
+   <FileRef
+      location = "group:Runner.xcodeproj">
+   </FileRef>
+</Workspace>
diff --git a/packages/rfw/example/wasm/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/rfw/example/wasm/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
new file mode 100644
index 0000000..18d9810
--- /dev/null
+++ b/packages/rfw/example/wasm/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>IDEDidComputeMac32BitWarning</key>
+	<true/>
+</dict>
+</plist>
diff --git a/packages/rfw/example/wasm/macos/Runner/AppDelegate.swift b/packages/rfw/example/wasm/macos/Runner/AppDelegate.swift
new file mode 100644
index 0000000..5cec4c4
--- /dev/null
+++ b/packages/rfw/example/wasm/macos/Runner/AppDelegate.swift
@@ -0,0 +1,13 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import Cocoa
+import FlutterMacOS
+
+@NSApplicationMain
+class AppDelegate: FlutterAppDelegate {
+  override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
+    return true
+  }
+}
diff --git a/packages/rfw/example/wasm/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/packages/rfw/example/wasm/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
new file mode 100644
index 0000000..a2ec33f
--- /dev/null
+++ b/packages/rfw/example/wasm/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -0,0 +1,68 @@
+{
+  "images" : [
+    {
+      "size" : "16x16",
+      "idiom" : "mac",
+      "filename" : "app_icon_16.png",
+      "scale" : "1x"
+    },
+    {
+      "size" : "16x16",
+      "idiom" : "mac",
+      "filename" : "app_icon_32.png",
+      "scale" : "2x"
+    },
+    {
+      "size" : "32x32",
+      "idiom" : "mac",
+      "filename" : "app_icon_32.png",
+      "scale" : "1x"
+    },
+    {
+      "size" : "32x32",
+      "idiom" : "mac",
+      "filename" : "app_icon_64.png",
+      "scale" : "2x"
+    },
+    {
+      "size" : "128x128",
+      "idiom" : "mac",
+      "filename" : "app_icon_128.png",
+      "scale" : "1x"
+    },
+    {
+      "size" : "128x128",
+      "idiom" : "mac",
+      "filename" : "app_icon_256.png",
+      "scale" : "2x"
+    },
+    {
+      "size" : "256x256",
+      "idiom" : "mac",
+      "filename" : "app_icon_256.png",
+      "scale" : "1x"
+    },
+    {
+      "size" : "256x256",
+      "idiom" : "mac",
+      "filename" : "app_icon_512.png",
+      "scale" : "2x"
+    },
+    {
+      "size" : "512x512",
+      "idiom" : "mac",
+      "filename" : "app_icon_512.png",
+      "scale" : "1x"
+    },
+    {
+      "size" : "512x512",
+      "idiom" : "mac",
+      "filename" : "app_icon_1024.png",
+      "scale" : "2x"
+    }
+  ],
+  "info" : {
+    "version" : 1,
+    "author" : "xcode"
+  }
+}
diff --git a/packages/rfw/example/wasm/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/packages/rfw/example/wasm/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png
new file mode 100644
index 0000000..3c4935a
--- /dev/null
+++ b/packages/rfw/example/wasm/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png
Binary files differ
diff --git a/packages/rfw/example/wasm/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/packages/rfw/example/wasm/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png
new file mode 100644
index 0000000..ed4cc16
--- /dev/null
+++ b/packages/rfw/example/wasm/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png
Binary files differ
diff --git a/packages/rfw/example/wasm/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/packages/rfw/example/wasm/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png
new file mode 100644
index 0000000..483be61
--- /dev/null
+++ b/packages/rfw/example/wasm/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png
Binary files differ
diff --git a/packages/rfw/example/wasm/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/packages/rfw/example/wasm/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png
new file mode 100644
index 0000000..bcbf36d
--- /dev/null
+++ b/packages/rfw/example/wasm/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png
Binary files differ
diff --git a/packages/rfw/example/wasm/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/packages/rfw/example/wasm/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png
new file mode 100644
index 0000000..9c0a652
--- /dev/null
+++ b/packages/rfw/example/wasm/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png
Binary files differ
diff --git a/packages/rfw/example/wasm/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/packages/rfw/example/wasm/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png
new file mode 100644
index 0000000..e71a726
--- /dev/null
+++ b/packages/rfw/example/wasm/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png
Binary files differ
diff --git a/packages/rfw/example/wasm/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/packages/rfw/example/wasm/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png
new file mode 100644
index 0000000..8a31fe2
--- /dev/null
+++ b/packages/rfw/example/wasm/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png
Binary files differ
diff --git a/packages/rfw/example/wasm/macos/Runner/Base.lproj/MainMenu.xib b/packages/rfw/example/wasm/macos/Runner/Base.lproj/MainMenu.xib
new file mode 100644
index 0000000..537341a
--- /dev/null
+++ b/packages/rfw/example/wasm/macos/Runner/Base.lproj/MainMenu.xib
@@ -0,0 +1,339 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
+    <dependencies>
+        <deployment identifier="macosx"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14490.70"/>
+        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
+    </dependencies>
+    <objects>
+        <customObject id="-2" userLabel="File's Owner" customClass="NSApplication">
+            <connections>
+                <outlet property="delegate" destination="Voe-Tx-rLC" id="GzC-gU-4Uq"/>
+            </connections>
+        </customObject>
+        <customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
+        <customObject id="-3" userLabel="Application" customClass="NSObject"/>
+        <customObject id="Voe-Tx-rLC" customClass="AppDelegate" customModule="Runner" customModuleProvider="target">
+            <connections>
+                <outlet property="applicationMenu" destination="uQy-DD-JDr" id="XBo-yE-nKs"/>
+                <outlet property="mainFlutterWindow" destination="QvC-M9-y7g" id="gIp-Ho-8D9"/>
+            </connections>
+        </customObject>
+        <customObject id="YLy-65-1bz" customClass="NSFontManager"/>
+        <menu title="Main Menu" systemMenu="main" id="AYu-sK-qS6">
+            <items>
+                <menuItem title="APP_NAME" id="1Xt-HY-uBw">
+                    <modifierMask key="keyEquivalentModifierMask"/>
+                    <menu key="submenu" title="APP_NAME" systemMenu="apple" id="uQy-DD-JDr">
+                        <items>
+                            <menuItem title="About APP_NAME" id="5kV-Vb-QxS">
+                                <modifierMask key="keyEquivalentModifierMask"/>
+                                <connections>
+                                    <action selector="orderFrontStandardAboutPanel:" target="-1" id="Exp-CZ-Vem"/>
+                                </connections>
+                            </menuItem>
+                            <menuItem isSeparatorItem="YES" id="VOq-y0-SEH"/>
+                            <menuItem title="Preferences…" keyEquivalent="," id="BOF-NM-1cW"/>
+                            <menuItem isSeparatorItem="YES" id="wFC-TO-SCJ"/>
+                            <menuItem title="Services" id="NMo-om-nkz">
+                                <modifierMask key="keyEquivalentModifierMask"/>
+                                <menu key="submenu" title="Services" systemMenu="services" id="hz9-B4-Xy5"/>
+                            </menuItem>
+                            <menuItem isSeparatorItem="YES" id="4je-JR-u6R"/>
+                            <menuItem title="Hide APP_NAME" keyEquivalent="h" id="Olw-nP-bQN">
+                                <connections>
+                                    <action selector="hide:" target="-1" id="PnN-Uc-m68"/>
+                                </connections>
+                            </menuItem>
+                            <menuItem title="Hide Others" keyEquivalent="h" id="Vdr-fp-XzO">
+                                <modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
+                                <connections>
+                                    <action selector="hideOtherApplications:" target="-1" id="VT4-aY-XCT"/>
+                                </connections>
+                            </menuItem>
+                            <menuItem title="Show All" id="Kd2-mp-pUS">
+                                <modifierMask key="keyEquivalentModifierMask"/>
+                                <connections>
+                                    <action selector="unhideAllApplications:" target="-1" id="Dhg-Le-xox"/>
+                                </connections>
+                            </menuItem>
+                            <menuItem isSeparatorItem="YES" id="kCx-OE-vgT"/>
+                            <menuItem title="Quit APP_NAME" keyEquivalent="q" id="4sb-4s-VLi">
+                                <connections>
+                                    <action selector="terminate:" target="-1" id="Te7-pn-YzF"/>
+                                </connections>
+                            </menuItem>
+                        </items>
+                    </menu>
+                </menuItem>
+                <menuItem title="Edit" id="5QF-Oa-p0T">
+                    <modifierMask key="keyEquivalentModifierMask"/>
+                    <menu key="submenu" title="Edit" id="W48-6f-4Dl">
+                        <items>
+                            <menuItem title="Undo" keyEquivalent="z" id="dRJ-4n-Yzg">
+                                <connections>
+                                    <action selector="undo:" target="-1" id="M6e-cu-g7V"/>
+                                </connections>
+                            </menuItem>
+                            <menuItem title="Redo" keyEquivalent="Z" id="6dh-zS-Vam">
+                                <connections>
+                                    <action selector="redo:" target="-1" id="oIA-Rs-6OD"/>
+                                </connections>
+                            </menuItem>
+                            <menuItem isSeparatorItem="YES" id="WRV-NI-Exz"/>
+                            <menuItem title="Cut" keyEquivalent="x" id="uRl-iY-unG">
+                                <connections>
+                                    <action selector="cut:" target="-1" id="YJe-68-I9s"/>
+                                </connections>
+                            </menuItem>
+                            <menuItem title="Copy" keyEquivalent="c" id="x3v-GG-iWU">
+                                <connections>
+                                    <action selector="copy:" target="-1" id="G1f-GL-Joy"/>
+                                </connections>
+                            </menuItem>
+                            <menuItem title="Paste" keyEquivalent="v" id="gVA-U4-sdL">
+                                <connections>
+                                    <action selector="paste:" target="-1" id="UvS-8e-Qdg"/>
+                                </connections>
+                            </menuItem>
+                            <menuItem title="Paste and Match Style" keyEquivalent="V" id="WeT-3V-zwk">
+                                <modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
+                                <connections>
+                                    <action selector="pasteAsPlainText:" target="-1" id="cEh-KX-wJQ"/>
+                                </connections>
+                            </menuItem>
+                            <menuItem title="Delete" id="pa3-QI-u2k">
+                                <modifierMask key="keyEquivalentModifierMask"/>
+                                <connections>
+                                    <action selector="delete:" target="-1" id="0Mk-Ml-PaM"/>
+                                </connections>
+                            </menuItem>
+                            <menuItem title="Select All" keyEquivalent="a" id="Ruw-6m-B2m">
+                                <connections>
+                                    <action selector="selectAll:" target="-1" id="VNm-Mi-diN"/>
+                                </connections>
+                            </menuItem>
+                            <menuItem isSeparatorItem="YES" id="uyl-h8-XO2"/>
+                            <menuItem title="Find" id="4EN-yA-p0u">
+                                <modifierMask key="keyEquivalentModifierMask"/>
+                                <menu key="submenu" title="Find" id="1b7-l0-nxx">
+                                    <items>
+                                        <menuItem title="Find…" tag="1" keyEquivalent="f" id="Xz5-n4-O0W">
+                                            <connections>
+                                                <action selector="performFindPanelAction:" target="-1" id="cD7-Qs-BN4"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem title="Find and Replace…" tag="12" keyEquivalent="f" id="YEy-JH-Tfz">
+                                            <modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
+                                            <connections>
+                                                <action selector="performFindPanelAction:" target="-1" id="WD3-Gg-5AJ"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem title="Find Next" tag="2" keyEquivalent="g" id="q09-fT-Sye">
+                                            <connections>
+                                                <action selector="performFindPanelAction:" target="-1" id="NDo-RZ-v9R"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem title="Find Previous" tag="3" keyEquivalent="G" id="OwM-mh-QMV">
+                                            <connections>
+                                                <action selector="performFindPanelAction:" target="-1" id="HOh-sY-3ay"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem title="Use Selection for Find" tag="7" keyEquivalent="e" id="buJ-ug-pKt">
+                                            <connections>
+                                                <action selector="performFindPanelAction:" target="-1" id="U76-nv-p5D"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem title="Jump to Selection" keyEquivalent="j" id="S0p-oC-mLd">
+                                            <connections>
+                                                <action selector="centerSelectionInVisibleArea:" target="-1" id="IOG-6D-g5B"/>
+                                            </connections>
+                                        </menuItem>
+                                    </items>
+                                </menu>
+                            </menuItem>
+                            <menuItem title="Spelling and Grammar" id="Dv1-io-Yv7">
+                                <modifierMask key="keyEquivalentModifierMask"/>
+                                <menu key="submenu" title="Spelling" id="3IN-sU-3Bg">
+                                    <items>
+                                        <menuItem title="Show Spelling and Grammar" keyEquivalent=":" id="HFo-cy-zxI">
+                                            <connections>
+                                                <action selector="showGuessPanel:" target="-1" id="vFj-Ks-hy3"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem title="Check Document Now" keyEquivalent=";" id="hz2-CU-CR7">
+                                            <connections>
+                                                <action selector="checkSpelling:" target="-1" id="fz7-VC-reM"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem isSeparatorItem="YES" id="bNw-od-mp5"/>
+                                        <menuItem title="Check Spelling While Typing" id="rbD-Rh-wIN">
+                                            <modifierMask key="keyEquivalentModifierMask"/>
+                                            <connections>
+                                                <action selector="toggleContinuousSpellChecking:" target="-1" id="7w6-Qz-0kB"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem title="Check Grammar With Spelling" id="mK6-2p-4JG">
+                                            <modifierMask key="keyEquivalentModifierMask"/>
+                                            <connections>
+                                                <action selector="toggleGrammarChecking:" target="-1" id="muD-Qn-j4w"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem title="Correct Spelling Automatically" id="78Y-hA-62v">
+                                            <modifierMask key="keyEquivalentModifierMask"/>
+                                            <connections>
+                                                <action selector="toggleAutomaticSpellingCorrection:" target="-1" id="2lM-Qi-WAP"/>
+                                            </connections>
+                                        </menuItem>
+                                    </items>
+                                </menu>
+                            </menuItem>
+                            <menuItem title="Substitutions" id="9ic-FL-obx">
+                                <modifierMask key="keyEquivalentModifierMask"/>
+                                <menu key="submenu" title="Substitutions" id="FeM-D8-WVr">
+                                    <items>
+                                        <menuItem title="Show Substitutions" id="z6F-FW-3nz">
+                                            <modifierMask key="keyEquivalentModifierMask"/>
+                                            <connections>
+                                                <action selector="orderFrontSubstitutionsPanel:" target="-1" id="oku-mr-iSq"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem isSeparatorItem="YES" id="gPx-C9-uUO"/>
+                                        <menuItem title="Smart Copy/Paste" id="9yt-4B-nSM">
+                                            <modifierMask key="keyEquivalentModifierMask"/>
+                                            <connections>
+                                                <action selector="toggleSmartInsertDelete:" target="-1" id="3IJ-Se-DZD"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem title="Smart Quotes" id="hQb-2v-fYv">
+                                            <modifierMask key="keyEquivalentModifierMask"/>
+                                            <connections>
+                                                <action selector="toggleAutomaticQuoteSubstitution:" target="-1" id="ptq-xd-QOA"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem title="Smart Dashes" id="rgM-f4-ycn">
+                                            <modifierMask key="keyEquivalentModifierMask"/>
+                                            <connections>
+                                                <action selector="toggleAutomaticDashSubstitution:" target="-1" id="oCt-pO-9gS"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem title="Smart Links" id="cwL-P1-jid">
+                                            <modifierMask key="keyEquivalentModifierMask"/>
+                                            <connections>
+                                                <action selector="toggleAutomaticLinkDetection:" target="-1" id="Gip-E3-Fov"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem title="Data Detectors" id="tRr-pd-1PS">
+                                            <modifierMask key="keyEquivalentModifierMask"/>
+                                            <connections>
+                                                <action selector="toggleAutomaticDataDetection:" target="-1" id="R1I-Nq-Kbl"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem title="Text Replacement" id="HFQ-gK-NFA">
+                                            <modifierMask key="keyEquivalentModifierMask"/>
+                                            <connections>
+                                                <action selector="toggleAutomaticTextReplacement:" target="-1" id="DvP-Fe-Py6"/>
+                                            </connections>
+                                        </menuItem>
+                                    </items>
+                                </menu>
+                            </menuItem>
+                            <menuItem title="Transformations" id="2oI-Rn-ZJC">
+                                <modifierMask key="keyEquivalentModifierMask"/>
+                                <menu key="submenu" title="Transformations" id="c8a-y6-VQd">
+                                    <items>
+                                        <menuItem title="Make Upper Case" id="vmV-6d-7jI">
+                                            <modifierMask key="keyEquivalentModifierMask"/>
+                                            <connections>
+                                                <action selector="uppercaseWord:" target="-1" id="sPh-Tk-edu"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem title="Make Lower Case" id="d9M-CD-aMd">
+                                            <modifierMask key="keyEquivalentModifierMask"/>
+                                            <connections>
+                                                <action selector="lowercaseWord:" target="-1" id="iUZ-b5-hil"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem title="Capitalize" id="UEZ-Bs-lqG">
+                                            <modifierMask key="keyEquivalentModifierMask"/>
+                                            <connections>
+                                                <action selector="capitalizeWord:" target="-1" id="26H-TL-nsh"/>
+                                            </connections>
+                                        </menuItem>
+                                    </items>
+                                </menu>
+                            </menuItem>
+                            <menuItem title="Speech" id="xrE-MZ-jX0">
+                                <modifierMask key="keyEquivalentModifierMask"/>
+                                <menu key="submenu" title="Speech" id="3rS-ZA-NoH">
+                                    <items>
+                                        <menuItem title="Start Speaking" id="Ynk-f8-cLZ">
+                                            <modifierMask key="keyEquivalentModifierMask"/>
+                                            <connections>
+                                                <action selector="startSpeaking:" target="-1" id="654-Ng-kyl"/>
+                                            </connections>
+                                        </menuItem>
+                                        <menuItem title="Stop Speaking" id="Oyz-dy-DGm">
+                                            <modifierMask key="keyEquivalentModifierMask"/>
+                                            <connections>
+                                                <action selector="stopSpeaking:" target="-1" id="dX8-6p-jy9"/>
+                                            </connections>
+                                        </menuItem>
+                                    </items>
+                                </menu>
+                            </menuItem>
+                        </items>
+                    </menu>
+                </menuItem>
+                <menuItem title="View" id="H8h-7b-M4v">
+                    <modifierMask key="keyEquivalentModifierMask"/>
+                    <menu key="submenu" title="View" id="HyV-fh-RgO">
+                        <items>
+                            <menuItem title="Enter Full Screen" keyEquivalent="f" id="4J7-dP-txa">
+                                <modifierMask key="keyEquivalentModifierMask" control="YES" command="YES"/>
+                                <connections>
+                                    <action selector="toggleFullScreen:" target="-1" id="dU3-MA-1Rq"/>
+                                </connections>
+                            </menuItem>
+                        </items>
+                    </menu>
+                </menuItem>
+                <menuItem title="Window" id="aUF-d1-5bR">
+                    <modifierMask key="keyEquivalentModifierMask"/>
+                    <menu key="submenu" title="Window" systemMenu="window" id="Td7-aD-5lo">
+                        <items>
+                            <menuItem title="Minimize" keyEquivalent="m" id="OY7-WF-poV">
+                                <connections>
+                                    <action selector="performMiniaturize:" target="-1" id="VwT-WD-YPe"/>
+                                </connections>
+                            </menuItem>
+                            <menuItem title="Zoom" id="R4o-n2-Eq4">
+                                <modifierMask key="keyEquivalentModifierMask"/>
+                                <connections>
+                                    <action selector="performZoom:" target="-1" id="DIl-cC-cCs"/>
+                                </connections>
+                            </menuItem>
+                            <menuItem isSeparatorItem="YES" id="eu3-7i-yIM"/>
+                            <menuItem title="Bring All to Front" id="LE2-aR-0XJ">
+                                <modifierMask key="keyEquivalentModifierMask"/>
+                                <connections>
+                                    <action selector="arrangeInFront:" target="-1" id="DRN-fu-gQh"/>
+                                </connections>
+                            </menuItem>
+                        </items>
+                    </menu>
+                </menuItem>
+            </items>
+            <point key="canvasLocation" x="142" y="-258"/>
+        </menu>
+        <window title="APP_NAME" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" animationBehavior="default" id="QvC-M9-y7g" customClass="MainFlutterWindow" customModule="Runner" customModuleProvider="target">
+            <windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
+            <rect key="contentRect" x="335" y="390" width="800" height="600"/>
+            <rect key="screenRect" x="0.0" y="0.0" width="2560" height="1577"/>
+            <view key="contentView" wantsLayer="YES" id="EiT-Mj-1SZ">
+                <rect key="frame" x="0.0" y="0.0" width="800" height="600"/>
+                <autoresizingMask key="autoresizingMask"/>
+            </view>
+        </window>
+    </objects>
+</document>
diff --git a/packages/rfw/example/wasm/macos/Runner/Configs/AppInfo.xcconfig b/packages/rfw/example/wasm/macos/Runner/Configs/AppInfo.xcconfig
new file mode 100644
index 0000000..76ca237
--- /dev/null
+++ b/packages/rfw/example/wasm/macos/Runner/Configs/AppInfo.xcconfig
@@ -0,0 +1,14 @@
+// Application-level settings for the Runner target.
+//
+// This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the
+// future. If not, the values below would default to using the project name when this becomes a
+// 'flutter create' template.
+
+// The application's name. By default this is also the title of the Flutter window.
+PRODUCT_NAME = wasm
+
+// The application's bundle identifier
+PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.rfw.examples.wasm
+
+// The copyright displayed in application information
+PRODUCT_COPYRIGHT = Copyright © 2021 dev.flutter.rfw.examples. All rights reserved.
diff --git a/packages/rfw/example/wasm/macos/Runner/Configs/Debug.xcconfig b/packages/rfw/example/wasm/macos/Runner/Configs/Debug.xcconfig
new file mode 100644
index 0000000..36b0fd9
--- /dev/null
+++ b/packages/rfw/example/wasm/macos/Runner/Configs/Debug.xcconfig
@@ -0,0 +1,2 @@
+#include "../../Flutter/Flutter-Debug.xcconfig"
+#include "Warnings.xcconfig"
diff --git a/packages/rfw/example/wasm/macos/Runner/Configs/Release.xcconfig b/packages/rfw/example/wasm/macos/Runner/Configs/Release.xcconfig
new file mode 100644
index 0000000..dff4f49
--- /dev/null
+++ b/packages/rfw/example/wasm/macos/Runner/Configs/Release.xcconfig
@@ -0,0 +1,2 @@
+#include "../../Flutter/Flutter-Release.xcconfig"
+#include "Warnings.xcconfig"
diff --git a/packages/rfw/example/wasm/macos/Runner/Configs/Warnings.xcconfig b/packages/rfw/example/wasm/macos/Runner/Configs/Warnings.xcconfig
new file mode 100644
index 0000000..42bcbf4
--- /dev/null
+++ b/packages/rfw/example/wasm/macos/Runner/Configs/Warnings.xcconfig
@@ -0,0 +1,13 @@
+WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings
+GCC_WARN_UNDECLARED_SELECTOR = YES
+CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES
+CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE
+CLANG_WARN__DUPLICATE_METHOD_MATCH = YES
+CLANG_WARN_PRAGMA_PACK = YES
+CLANG_WARN_STRICT_PROTOTYPES = YES
+CLANG_WARN_COMMA = YES
+GCC_WARN_STRICT_SELECTOR_MATCH = YES
+CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES
+CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES
+GCC_WARN_SHADOW = YES
+CLANG_WARN_UNREACHABLE_CODE = YES
diff --git a/packages/rfw/example/wasm/macos/Runner/DebugProfile.entitlements b/packages/rfw/example/wasm/macos/Runner/DebugProfile.entitlements
new file mode 100644
index 0000000..dddb8a3
--- /dev/null
+++ b/packages/rfw/example/wasm/macos/Runner/DebugProfile.entitlements
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>com.apple.security.app-sandbox</key>
+	<true/>
+	<key>com.apple.security.cs.allow-jit</key>
+	<true/>
+	<key>com.apple.security.network.server</key>
+	<true/>
+</dict>
+</plist>
diff --git a/packages/rfw/example/wasm/macos/Runner/Info.plist b/packages/rfw/example/wasm/macos/Runner/Info.plist
new file mode 100644
index 0000000..4789daa
--- /dev/null
+++ b/packages/rfw/example/wasm/macos/Runner/Info.plist
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>CFBundleDevelopmentRegion</key>
+	<string>$(DEVELOPMENT_LANGUAGE)</string>
+	<key>CFBundleExecutable</key>
+	<string>$(EXECUTABLE_NAME)</string>
+	<key>CFBundleIconFile</key>
+	<string></string>
+	<key>CFBundleIdentifier</key>
+	<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
+	<key>CFBundleInfoDictionaryVersion</key>
+	<string>6.0</string>
+	<key>CFBundleName</key>
+	<string>$(PRODUCT_NAME)</string>
+	<key>CFBundlePackageType</key>
+	<string>APPL</string>
+	<key>CFBundleShortVersionString</key>
+	<string>$(FLUTTER_BUILD_NAME)</string>
+	<key>CFBundleVersion</key>
+	<string>$(FLUTTER_BUILD_NUMBER)</string>
+	<key>LSMinimumSystemVersion</key>
+	<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
+	<key>NSHumanReadableCopyright</key>
+	<string>$(PRODUCT_COPYRIGHT)</string>
+	<key>NSMainNibFile</key>
+	<string>MainMenu</string>
+	<key>NSPrincipalClass</key>
+	<string>NSApplication</string>
+</dict>
+</plist>
diff --git a/packages/rfw/example/wasm/macos/Runner/MainFlutterWindow.swift b/packages/rfw/example/wasm/macos/Runner/MainFlutterWindow.swift
new file mode 100644
index 0000000..32aaeed
--- /dev/null
+++ b/packages/rfw/example/wasm/macos/Runner/MainFlutterWindow.swift
@@ -0,0 +1,19 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import Cocoa
+import FlutterMacOS
+
+class MainFlutterWindow: NSWindow {
+  override func awakeFromNib() {
+    let flutterViewController = FlutterViewController.init()
+    let windowFrame = self.frame
+    self.contentViewController = flutterViewController
+    self.setFrame(windowFrame, display: true)
+
+    RegisterGeneratedPlugins(registry: flutterViewController)
+
+    super.awakeFromNib()
+  }
+}
diff --git a/packages/rfw/example/wasm/macos/Runner/Release.entitlements b/packages/rfw/example/wasm/macos/Runner/Release.entitlements
new file mode 100644
index 0000000..852fa1a
--- /dev/null
+++ b/packages/rfw/example/wasm/macos/Runner/Release.entitlements
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>com.apple.security.app-sandbox</key>
+	<true/>
+</dict>
+</plist>
diff --git a/packages/rfw/example/wasm/pubspec.yaml b/packages/rfw/example/wasm/pubspec.yaml
new file mode 100644
index 0000000..96af1a5
--- /dev/null
+++ b/packages/rfw/example/wasm/pubspec.yaml
@@ -0,0 +1,19 @@
+name: rfw_wasm
+description: Example of using Wasm with RFW
+publish_to: 'none' # Remove this line if you wish to publish to pub.dev
+version: 1.0.0+1
+
+environment:
+  sdk: ">=2.13.0 <3.0.0"
+
+dependencies:
+  flutter:
+    sdk: flutter
+  path: ^1.8.0
+  path_provider: ^2.0.2
+  rfw:
+    path: ../../
+  wasm: ">=0.1.0+1 <=2.0.0"
+
+flutter:
+  uses-material-design: true
diff --git a/packages/rfw/example/wasm/web/favicon.png b/packages/rfw/example/wasm/web/favicon.png
new file mode 100644
index 0000000..8aaa46a
--- /dev/null
+++ b/packages/rfw/example/wasm/web/favicon.png
Binary files differ
diff --git a/packages/rfw/example/wasm/web/icons/Icon-192.png b/packages/rfw/example/wasm/web/icons/Icon-192.png
new file mode 100644
index 0000000..b749bfe
--- /dev/null
+++ b/packages/rfw/example/wasm/web/icons/Icon-192.png
Binary files differ
diff --git a/packages/rfw/example/wasm/web/icons/Icon-512.png b/packages/rfw/example/wasm/web/icons/Icon-512.png
new file mode 100644
index 0000000..88cfd48
--- /dev/null
+++ b/packages/rfw/example/wasm/web/icons/Icon-512.png
Binary files differ
diff --git a/packages/rfw/example/wasm/web/icons/Icon-maskable-192.png b/packages/rfw/example/wasm/web/icons/Icon-maskable-192.png
new file mode 100644
index 0000000..eb9b4d7
--- /dev/null
+++ b/packages/rfw/example/wasm/web/icons/Icon-maskable-192.png
Binary files differ
diff --git a/packages/rfw/example/wasm/web/icons/Icon-maskable-512.png b/packages/rfw/example/wasm/web/icons/Icon-maskable-512.png
new file mode 100644
index 0000000..d69c566
--- /dev/null
+++ b/packages/rfw/example/wasm/web/icons/Icon-maskable-512.png
Binary files differ
diff --git a/packages/rfw/example/wasm/web/index.html b/packages/rfw/example/wasm/web/index.html
new file mode 100644
index 0000000..da703c9
--- /dev/null
+++ b/packages/rfw/example/wasm/web/index.html
@@ -0,0 +1,107 @@
+<!DOCTYPE html>
+<!-- Copyright 2013 The Flutter Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file. -->
+<html>
+<head>
+  <!--
+    If you are serving your web app in a path other than the root, change the
+    href value below to reflect the base path you are serving from.
+
+    The path provided below has to start and end with a slash "/" in order for
+    it to work correctly.
+
+    For more details:
+    * https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base
+
+    This is a placeholder for base href that will be replaced by the value of
+    the `--base-href` argument provided to `flutter build`.
+  -->
+  <base href="$FLUTTER_BASE_HREF">
+
+  <meta charset="UTF-8">
+  <meta content="IE=Edge" http-equiv="X-UA-Compatible">
+  <meta name="description" content="Example of using Wasm with RFW">
+
+  <!-- iOS meta tags & icons -->
+  <meta name="apple-mobile-web-app-capable" content="yes">
+  <meta name="apple-mobile-web-app-status-bar-style" content="black">
+  <meta name="apple-mobile-web-app-title" content="wasm">
+  <link rel="apple-touch-icon" href="icons/Icon-192.png">
+
+  <!-- Favicon -->
+  <link rel="icon" type="image/png" href="favicon.png"/>
+
+  <title>wasm</title>
+  <link rel="manifest" href="manifest.json">
+</head>
+<body>
+  <!-- This script installs service_worker.js to provide PWA functionality to
+       application. For more information, see:
+       https://developers.google.com/web/fundamentals/primers/service-workers -->
+  <script>
+    var serviceWorkerVersion = null;
+    var scriptLoaded = false;
+    function loadMainDartJs() {
+      if (scriptLoaded) {
+        return;
+      }
+      scriptLoaded = true;
+      var scriptTag = document.createElement('script');
+      scriptTag.src = 'main.dart.js';
+      scriptTag.type = 'application/javascript';
+      document.body.append(scriptTag);
+    }
+
+    if ('serviceWorker' in navigator) {
+      // Service workers are supported. Use them.
+      window.addEventListener('load', function () {
+        // Wait for registration to finish before dropping the <script> tag.
+        // Otherwise, the browser will load the script multiple times,
+        // potentially different versions.
+        var serviceWorkerUrl = 'flutter_service_worker.js?v=' + serviceWorkerVersion;
+        navigator.serviceWorker.register(serviceWorkerUrl)
+          .then((reg) => {
+            function waitForActivation(serviceWorker) {
+              serviceWorker.addEventListener('statechange', () => {
+                if (serviceWorker.state == 'activated') {
+                  console.log('Installed new service worker.');
+                  loadMainDartJs();
+                }
+              });
+            }
+            if (!reg.active && (reg.installing || reg.waiting)) {
+              // No active web worker and we have installed or are installing
+              // one for the first time. Simply wait for it to activate.
+              waitForActivation(reg.installing || reg.waiting);
+            } else if (!reg.active.scriptURL.endsWith(serviceWorkerVersion)) {
+              // When the app updates the serviceWorkerVersion changes, so we
+              // need to ask the service worker to update.
+              console.log('New service worker available.');
+              reg.update();
+              waitForActivation(reg.installing);
+            } else {
+              // Existing service worker is still good.
+              console.log('Loading app from service worker.');
+              loadMainDartJs();
+            }
+          });
+
+        // If service worker doesn't succeed in a reasonable amount of time,
+        // fallback to plaint <script> tag.
+        setTimeout(() => {
+          if (!scriptLoaded) {
+            console.warn(
+              'Failed to load app from service worker. Falling back to plain <script> tag.',
+            );
+            loadMainDartJs();
+          }
+        }, 4000);
+      });
+    } else {
+      // Service workers not supported. Just drop the <script> tag.
+      loadMainDartJs();
+    }
+  </script>
+</body>
+</html>
diff --git a/packages/rfw/example/wasm/web/manifest.json b/packages/rfw/example/wasm/web/manifest.json
new file mode 100644
index 0000000..5f8a447
--- /dev/null
+++ b/packages/rfw/example/wasm/web/manifest.json
@@ -0,0 +1,35 @@
+{
+    "name": "wasm",
+    "short_name": "wasm",
+    "start_url": ".",
+    "display": "standalone",
+    "background_color": "#0175C2",
+    "theme_color": "#0175C2",
+    "description": "Example of using Wasm with RFW",
+    "orientation": "portrait-primary",
+    "prefer_related_applications": false,
+    "icons": [
+        {
+            "src": "icons/Icon-192.png",
+            "sizes": "192x192",
+            "type": "image/png"
+        },
+        {
+            "src": "icons/Icon-512.png",
+            "sizes": "512x512",
+            "type": "image/png"
+        },
+        {
+            "src": "icons/Icon-maskable-192.png",
+            "sizes": "192x192",
+            "type": "image/png",
+            "purpose": "maskable"
+        },
+        {
+            "src": "icons/Icon-maskable-512.png",
+            "sizes": "512x512",
+            "type": "image/png",
+            "purpose": "maskable"
+        }
+    ]
+}
diff --git a/packages/rfw/example/wasm/windows/.gitignore b/packages/rfw/example/wasm/windows/.gitignore
new file mode 100644
index 0000000..d492d0d
--- /dev/null
+++ b/packages/rfw/example/wasm/windows/.gitignore
@@ -0,0 +1,17 @@
+flutter/ephemeral/
+
+# Visual Studio user-specific files.
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# Visual Studio build-related files.
+x64/
+x86/
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!*.[Cc]ache/
diff --git a/packages/rfw/example/wasm/windows/CMakeLists.txt b/packages/rfw/example/wasm/windows/CMakeLists.txt
new file mode 100644
index 0000000..697f916
--- /dev/null
+++ b/packages/rfw/example/wasm/windows/CMakeLists.txt
@@ -0,0 +1,95 @@
+cmake_minimum_required(VERSION 3.15)
+project(wasm LANGUAGES CXX)
+
+set(BINARY_NAME "wasm")
+
+cmake_policy(SET CMP0063 NEW)
+
+set(CMAKE_INSTALL_RPATH "$ORIGIN/lib")
+
+# Configure build options.
+get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
+if(IS_MULTICONFIG)
+  set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release"
+    CACHE STRING "" FORCE)
+else()
+  if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
+    set(CMAKE_BUILD_TYPE "Debug" CACHE
+      STRING "Flutter build mode" FORCE)
+    set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
+      "Debug" "Profile" "Release")
+  endif()
+endif()
+
+set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}")
+set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}")
+set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}")
+set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}")
+
+# Use Unicode for all projects.
+add_definitions(-DUNICODE -D_UNICODE)
+
+# Compilation settings that should be applied to most targets.
+function(APPLY_STANDARD_SETTINGS TARGET)
+  target_compile_features(${TARGET} PUBLIC cxx_std_17)
+  target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100")
+  target_compile_options(${TARGET} PRIVATE /EHsc)
+  target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0")
+  target_compile_definitions(${TARGET} PRIVATE "$<$<CONFIG:Debug>:_DEBUG>")
+endfunction()
+
+set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter")
+
+# Flutter library and tool build rules.
+add_subdirectory(${FLUTTER_MANAGED_DIR})
+
+# Application build
+add_subdirectory("runner")
+
+# Generated plugin build rules, which manage building the plugins and adding
+# them to the application.
+include(flutter/generated_plugins.cmake)
+
+
+# === Installation ===
+# Support files are copied into place next to the executable, so that it can
+# run in place. This is done instead of making a separate bundle (as on Linux)
+# so that building and running from within Visual Studio will work.
+set(BUILD_BUNDLE_DIR "$<TARGET_FILE_DIR:${BINARY_NAME}>")
+# Make the "install" step default, as it's required to run.
+set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1)
+if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
+  set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE)
+endif()
+
+set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data")
+set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}")
+
+install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}"
+  COMPONENT Runtime)
+
+install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
+  COMPONENT Runtime)
+
+install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
+  COMPONENT Runtime)
+
+if(PLUGIN_BUNDLED_LIBRARIES)
+  install(FILES "${PLUGIN_BUNDLED_LIBRARIES}"
+    DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
+    COMPONENT Runtime)
+endif()
+
+# Fully re-copy the assets directory on each build to avoid having stale files
+# from a previous install.
+set(FLUTTER_ASSET_DIR_NAME "flutter_assets")
+install(CODE "
+  file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\")
+  " COMPONENT Runtime)
+install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}"
+  DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime)
+
+# Install the AOT library on non-Debug builds only.
+install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
+  CONFIGURATIONS Profile;Release
+  COMPONENT Runtime)
diff --git a/packages/rfw/example/wasm/windows/flutter/CMakeLists.txt b/packages/rfw/example/wasm/windows/flutter/CMakeLists.txt
new file mode 100644
index 0000000..b02c548
--- /dev/null
+++ b/packages/rfw/example/wasm/windows/flutter/CMakeLists.txt
@@ -0,0 +1,103 @@
+cmake_minimum_required(VERSION 3.15)
+
+set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral")
+
+# Configuration provided via flutter tool.
+include(${EPHEMERAL_DIR}/generated_config.cmake)
+
+# TODO: Move the rest of this into files in ephemeral. See
+# https://github.com/flutter/flutter/issues/57146.
+set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper")
+
+# === Flutter Library ===
+set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll")
+
+# Published to parent scope for install step.
+set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE)
+set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE)
+set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE)
+set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE)
+
+list(APPEND FLUTTER_LIBRARY_HEADERS
+  "flutter_export.h"
+  "flutter_windows.h"
+  "flutter_messenger.h"
+  "flutter_plugin_registrar.h"
+  "flutter_texture_registrar.h"
+)
+list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/")
+add_library(flutter INTERFACE)
+target_include_directories(flutter INTERFACE
+  "${EPHEMERAL_DIR}"
+)
+target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib")
+add_dependencies(flutter flutter_assemble)
+
+# === Wrapper ===
+list(APPEND CPP_WRAPPER_SOURCES_CORE
+  "core_implementations.cc"
+  "standard_codec.cc"
+)
+list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/")
+list(APPEND CPP_WRAPPER_SOURCES_PLUGIN
+  "plugin_registrar.cc"
+)
+list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/")
+list(APPEND CPP_WRAPPER_SOURCES_APP
+  "flutter_engine.cc"
+  "flutter_view_controller.cc"
+)
+list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/")
+
+# Wrapper sources needed for a plugin.
+add_library(flutter_wrapper_plugin STATIC
+  ${CPP_WRAPPER_SOURCES_CORE}
+  ${CPP_WRAPPER_SOURCES_PLUGIN}
+)
+apply_standard_settings(flutter_wrapper_plugin)
+set_target_properties(flutter_wrapper_plugin PROPERTIES
+  POSITION_INDEPENDENT_CODE ON)
+set_target_properties(flutter_wrapper_plugin PROPERTIES
+  CXX_VISIBILITY_PRESET hidden)
+target_link_libraries(flutter_wrapper_plugin PUBLIC flutter)
+target_include_directories(flutter_wrapper_plugin PUBLIC
+  "${WRAPPER_ROOT}/include"
+)
+add_dependencies(flutter_wrapper_plugin flutter_assemble)
+
+# Wrapper sources needed for the runner.
+add_library(flutter_wrapper_app STATIC
+  ${CPP_WRAPPER_SOURCES_CORE}
+  ${CPP_WRAPPER_SOURCES_APP}
+)
+apply_standard_settings(flutter_wrapper_app)
+target_link_libraries(flutter_wrapper_app PUBLIC flutter)
+target_include_directories(flutter_wrapper_app PUBLIC
+  "${WRAPPER_ROOT}/include"
+)
+add_dependencies(flutter_wrapper_app flutter_assemble)
+
+# === Flutter tool backend ===
+# _phony_ is a non-existent file to force this command to run every time,
+# since currently there's no way to get a full input/output list from the
+# flutter tool.
+set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_")
+set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE)
+add_custom_command(
+  OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS}
+    ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN}
+    ${CPP_WRAPPER_SOURCES_APP}
+    ${PHONY_OUTPUT}
+  COMMAND ${CMAKE_COMMAND} -E env
+    ${FLUTTER_TOOL_ENVIRONMENT}
+    "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat"
+      windows-x64 $<CONFIG>
+  VERBATIM
+)
+add_custom_target(flutter_assemble DEPENDS
+  "${FLUTTER_LIBRARY}"
+  ${FLUTTER_LIBRARY_HEADERS}
+  ${CPP_WRAPPER_SOURCES_CORE}
+  ${CPP_WRAPPER_SOURCES_PLUGIN}
+  ${CPP_WRAPPER_SOURCES_APP}
+)
diff --git a/packages/rfw/example/wasm/windows/flutter/generated_plugins.cmake b/packages/rfw/example/wasm/windows/flutter/generated_plugins.cmake
new file mode 100644
index 0000000..4d10c25
--- /dev/null
+++ b/packages/rfw/example/wasm/windows/flutter/generated_plugins.cmake
@@ -0,0 +1,15 @@
+#
+# Generated file, do not edit.
+#
+
+list(APPEND FLUTTER_PLUGIN_LIST
+)
+
+set(PLUGIN_BUNDLED_LIBRARIES)
+
+foreach(plugin ${FLUTTER_PLUGIN_LIST})
+  add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin})
+  target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)
+  list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>)
+  list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
+endforeach(plugin)
diff --git a/packages/rfw/example/wasm/windows/runner/CMakeLists.txt b/packages/rfw/example/wasm/windows/runner/CMakeLists.txt
new file mode 100644
index 0000000..0b899a0
--- /dev/null
+++ b/packages/rfw/example/wasm/windows/runner/CMakeLists.txt
@@ -0,0 +1,17 @@
+cmake_minimum_required(VERSION 3.15)
+project(runner LANGUAGES CXX)
+
+add_executable(${BINARY_NAME} WIN32
+  "flutter_window.cpp"
+  "main.cpp"
+  "utils.cpp"
+  "win32_window.cpp"
+  "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc"
+  "Runner.rc"
+  "runner.exe.manifest"
+)
+apply_standard_settings(${BINARY_NAME})
+target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX")
+target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app)
+target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}")
+add_dependencies(${BINARY_NAME} flutter_assemble)
diff --git a/packages/rfw/example/wasm/windows/runner/Runner.rc b/packages/rfw/example/wasm/windows/runner/Runner.rc
new file mode 100644
index 0000000..7535ea4
--- /dev/null
+++ b/packages/rfw/example/wasm/windows/runner/Runner.rc
@@ -0,0 +1,121 @@
+// Microsoft Visual C++ generated resource script.
+//
+#pragma code_page(65001)
+#include "resource.h"
+
+#define APSTUDIO_READONLY_SYMBOLS
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 2 resource.
+//
+#include "winres.h"
+
+/////////////////////////////////////////////////////////////////////////////
+#undef APSTUDIO_READONLY_SYMBOLS
+
+/////////////////////////////////////////////////////////////////////////////
+// English (United States) resources
+
+#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
+LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
+
+#ifdef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// TEXTINCLUDE
+//
+
+1 TEXTINCLUDE
+BEGIN
+    "resource.h\0"
+END
+
+2 TEXTINCLUDE
+BEGIN
+    "#include ""winres.h""\r\n"
+    "\0"
+END
+
+3 TEXTINCLUDE
+BEGIN
+    "\r\n"
+    "\0"
+END
+
+#endif    // APSTUDIO_INVOKED
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Icon
+//
+
+// Icon with lowest ID value placed first to ensure application icon
+// remains consistent on all systems.
+IDI_APP_ICON            ICON                    "resources\\app_icon.ico"
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Version
+//
+
+#ifdef FLUTTER_BUILD_NUMBER
+#define VERSION_AS_NUMBER FLUTTER_BUILD_NUMBER
+#else
+#define VERSION_AS_NUMBER 1,0,0
+#endif
+
+#ifdef FLUTTER_BUILD_NAME
+#define VERSION_AS_STRING #FLUTTER_BUILD_NAME
+#else
+#define VERSION_AS_STRING "1.0.0"
+#endif
+
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION VERSION_AS_NUMBER
+ PRODUCTVERSION VERSION_AS_NUMBER
+ FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
+#ifdef _DEBUG
+ FILEFLAGS VS_FF_DEBUG
+#else
+ FILEFLAGS 0x0L
+#endif
+ FILEOS VOS__WINDOWS32
+ FILETYPE VFT_APP
+ FILESUBTYPE 0x0L
+BEGIN
+    BLOCK "StringFileInfo"
+    BEGIN
+        BLOCK "040904e4"
+        BEGIN
+            VALUE "CompanyName", "dev.flutter.rfw.examples" "\0"
+            VALUE "FileDescription", "Example of using Wasm with RFW" "\0"
+            VALUE "FileVersion", VERSION_AS_STRING "\0"
+            VALUE "InternalName", "wasm" "\0"
+            VALUE "LegalCopyright", "Copyright (C) 2021 dev.flutter.rfw.examples. All rights reserved." "\0"
+            VALUE "OriginalFilename", "wasm.exe" "\0"
+            VALUE "ProductName", "wasm" "\0"
+            VALUE "ProductVersion", VERSION_AS_STRING "\0"
+        END
+    END
+    BLOCK "VarFileInfo"
+    BEGIN
+        VALUE "Translation", 0x409, 1252
+    END
+END
+
+#endif    // English (United States) resources
+/////////////////////////////////////////////////////////////////////////////
+
+
+
+#ifndef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 3 resource.
+//
+
+
+/////////////////////////////////////////////////////////////////////////////
+#endif    // not APSTUDIO_INVOKED
diff --git a/packages/rfw/example/wasm/windows/runner/flutter_window.cpp b/packages/rfw/example/wasm/windows/runner/flutter_window.cpp
new file mode 100644
index 0000000..8254bd9
--- /dev/null
+++ b/packages/rfw/example/wasm/windows/runner/flutter_window.cpp
@@ -0,0 +1,65 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "flutter_window.h"
+
+#include <optional>
+
+#include "flutter/generated_plugin_registrant.h"
+
+FlutterWindow::FlutterWindow(const flutter::DartProject& project)
+    : project_(project) {}
+
+FlutterWindow::~FlutterWindow() {}
+
+bool FlutterWindow::OnCreate() {
+  if (!Win32Window::OnCreate()) {
+    return false;
+  }
+
+  RECT frame = GetClientArea();
+
+  // The size here must match the window dimensions to avoid unnecessary surface
+  // creation / destruction in the startup path.
+  flutter_controller_ = std::make_unique<flutter::FlutterViewController>(
+      frame.right - frame.left, frame.bottom - frame.top, project_);
+  // Ensure that basic setup of the controller was successful.
+  if (!flutter_controller_->engine() || !flutter_controller_->view()) {
+    return false;
+  }
+  RegisterPlugins(flutter_controller_->engine());
+  SetChildContent(flutter_controller_->view()->GetNativeWindow());
+  return true;
+}
+
+void FlutterWindow::OnDestroy() {
+  if (flutter_controller_) {
+    flutter_controller_ = nullptr;
+  }
+
+  Win32Window::OnDestroy();
+}
+
+LRESULT
+FlutterWindow::MessageHandler(HWND hwnd, UINT const message,
+                              WPARAM const wparam,
+                              LPARAM const lparam) noexcept {
+  // Give Flutter, including plugins, an opportunity to handle window messages.
+  if (flutter_controller_) {
+    std::optional<LRESULT> result =
+        flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam,
+                                                      lparam);
+    if (result) {
+      return *result;
+    }
+  }
+
+  switch (message) {
+    case WM_FONTCHANGE:
+      flutter_controller_->engine()->ReloadSystemFonts();
+      break;
+  }
+
+  return Win32Window::MessageHandler(hwnd, message, wparam, lparam);
+}
diff --git a/packages/rfw/example/wasm/windows/runner/flutter_window.h b/packages/rfw/example/wasm/windows/runner/flutter_window.h
new file mode 100644
index 0000000..f1fc669
--- /dev/null
+++ b/packages/rfw/example/wasm/windows/runner/flutter_window.h
@@ -0,0 +1,37 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef RUNNER_FLUTTER_WINDOW_H_
+#define RUNNER_FLUTTER_WINDOW_H_
+
+#include <flutter/dart_project.h>
+#include <flutter/flutter_view_controller.h>
+
+#include <memory>
+
+#include "win32_window.h"
+
+// A window that does nothing but host a Flutter view.
+class FlutterWindow : public Win32Window {
+ public:
+  // Creates a new FlutterWindow hosting a Flutter view running |project|.
+  explicit FlutterWindow(const flutter::DartProject& project);
+  virtual ~FlutterWindow();
+
+ protected:
+  // Win32Window:
+  bool OnCreate() override;
+  void OnDestroy() override;
+  LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam,
+                         LPARAM const lparam) noexcept override;
+
+ private:
+  // The project to run.
+  flutter::DartProject project_;
+
+  // The Flutter instance hosted by this window.
+  std::unique_ptr<flutter::FlutterViewController> flutter_controller_;
+};
+
+#endif  // RUNNER_FLUTTER_WINDOW_H_
diff --git a/packages/rfw/example/wasm/windows/runner/main.cpp b/packages/rfw/example/wasm/windows/runner/main.cpp
new file mode 100644
index 0000000..e18e5a4
--- /dev/null
+++ b/packages/rfw/example/wasm/windows/runner/main.cpp
@@ -0,0 +1,46 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <flutter/dart_project.h>
+#include <flutter/flutter_view_controller.h>
+#include <windows.h>
+
+#include "flutter_window.h"
+#include "utils.h"
+
+int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
+                      _In_ wchar_t *command_line, _In_ int show_command) {
+  // Attach to console when present (e.g., 'flutter run') or create a
+  // new console when running with a debugger.
+  if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) {
+    CreateAndAttachConsole();
+  }
+
+  // Initialize COM, so that it is available for use in the library and/or
+  // plugins.
+  ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
+
+  flutter::DartProject project(L"data");
+
+  std::vector<std::string> command_line_arguments = GetCommandLineArguments();
+
+  project.set_dart_entrypoint_arguments(std::move(command_line_arguments));
+
+  FlutterWindow window(project);
+  Win32Window::Point origin(10, 10);
+  Win32Window::Size size(1280, 720);
+  if (!window.CreateAndShow(L"wasm", origin, size)) {
+    return EXIT_FAILURE;
+  }
+  window.SetQuitOnClose(true);
+
+  ::MSG msg;
+  while (::GetMessage(&msg, nullptr, 0, 0)) {
+    ::TranslateMessage(&msg);
+    ::DispatchMessage(&msg);
+  }
+
+  ::CoUninitialize();
+  return EXIT_SUCCESS;
+}
diff --git a/packages/rfw/example/wasm/windows/runner/resource.h b/packages/rfw/example/wasm/windows/runner/resource.h
new file mode 100644
index 0000000..d5d958d
--- /dev/null
+++ b/packages/rfw/example/wasm/windows/runner/resource.h
@@ -0,0 +1,16 @@
+//{{NO_DEPENDENCIES}}
+// Microsoft Visual C++ generated include file.
+// Used by Runner.rc
+//
+#define IDI_APP_ICON 101
+
+// Next default values for new objects
+//
+#ifdef APSTUDIO_INVOKED
+#ifndef APSTUDIO_READONLY_SYMBOLS
+#define _APS_NEXT_RESOURCE_VALUE 102
+#define _APS_NEXT_COMMAND_VALUE 40001
+#define _APS_NEXT_CONTROL_VALUE 1001
+#define _APS_NEXT_SYMED_VALUE 101
+#endif
+#endif
diff --git a/packages/rfw/example/wasm/windows/runner/resources/app_icon.ico b/packages/rfw/example/wasm/windows/runner/resources/app_icon.ico
new file mode 100644
index 0000000..c04e20c
--- /dev/null
+++ b/packages/rfw/example/wasm/windows/runner/resources/app_icon.ico
Binary files differ
diff --git a/packages/rfw/example/wasm/windows/runner/runner.exe.manifest b/packages/rfw/example/wasm/windows/runner/runner.exe.manifest
new file mode 100644
index 0000000..c977c4a
--- /dev/null
+++ b/packages/rfw/example/wasm/windows/runner/runner.exe.manifest
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
+  <application xmlns="urn:schemas-microsoft-com:asm.v3">
+    <windowsSettings>
+      <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
+    </windowsSettings>
+  </application>
+  <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
+    <application>
+      <!-- Windows 10 -->
+      <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
+      <!-- Windows 8.1 -->
+      <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
+      <!-- Windows 8 -->
+      <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
+      <!-- Windows 7 -->
+      <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
+    </application>
+  </compatibility>
+</assembly>
diff --git a/packages/rfw/example/wasm/windows/runner/utils.cpp b/packages/rfw/example/wasm/windows/runner/utils.cpp
new file mode 100644
index 0000000..fb7e945
--- /dev/null
+++ b/packages/rfw/example/wasm/windows/runner/utils.cpp
@@ -0,0 +1,67 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "utils.h"
+
+#include <flutter_windows.h>
+#include <io.h>
+#include <stdio.h>
+#include <windows.h>
+
+#include <iostream>
+
+void CreateAndAttachConsole() {
+  if (::AllocConsole()) {
+    FILE* unused;
+    if (freopen_s(&unused, "CONOUT$", "w", stdout)) {
+      _dup2(_fileno(stdout), 1);
+    }
+    if (freopen_s(&unused, "CONOUT$", "w", stderr)) {
+      _dup2(_fileno(stdout), 2);
+    }
+    std::ios::sync_with_stdio();
+    FlutterDesktopResyncOutputStreams();
+  }
+}
+
+std::vector<std::string> GetCommandLineArguments() {
+  // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use.
+  int argc;
+  wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc);
+  if (argv == nullptr) {
+    return std::vector<std::string>();
+  }
+
+  std::vector<std::string> command_line_arguments;
+
+  // Skip the first argument as it's the binary name.
+  for (int i = 1; i < argc; i++) {
+    command_line_arguments.push_back(Utf8FromUtf16(argv[i]));
+  }
+
+  ::LocalFree(argv);
+
+  return command_line_arguments;
+}
+
+std::string Utf8FromUtf16(const wchar_t* utf16_string) {
+  if (utf16_string == nullptr) {
+    return std::string();
+  }
+  int target_length =
+      ::WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, -1,
+                            nullptr, 0, nullptr, nullptr);
+  if (target_length == 0) {
+    return std::string();
+  }
+  std::string utf8_string;
+  utf8_string.resize(target_length);
+  int converted_length = ::WideCharToMultiByte(
+      CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, -1, utf8_string.data(),
+      target_length, nullptr, nullptr);
+  if (converted_length == 0) {
+    return std::string();
+  }
+  return utf8_string;
+}
diff --git a/packages/rfw/example/wasm/windows/runner/utils.h b/packages/rfw/example/wasm/windows/runner/utils.h
new file mode 100644
index 0000000..bd81e1e
--- /dev/null
+++ b/packages/rfw/example/wasm/windows/runner/utils.h
@@ -0,0 +1,23 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef RUNNER_UTILS_H_
+#define RUNNER_UTILS_H_
+
+#include <string>
+#include <vector>
+
+// Creates a console for the process, and redirects stdout and stderr to
+// it for both the runner and the Flutter library.
+void CreateAndAttachConsole();
+
+// Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string
+// encoded in UTF-8. Returns an empty std::string on failure.
+std::string Utf8FromUtf16(const wchar_t* utf16_string);
+
+// Gets the command line arguments passed in as a std::vector<std::string>,
+// encoded in UTF-8. Returns an empty std::vector<std::string> on failure.
+std::vector<std::string> GetCommandLineArguments();
+
+#endif  // RUNNER_UTILS_H_
diff --git a/packages/rfw/example/wasm/windows/runner/win32_window.cpp b/packages/rfw/example/wasm/windows/runner/win32_window.cpp
new file mode 100644
index 0000000..85aa361
--- /dev/null
+++ b/packages/rfw/example/wasm/windows/runner/win32_window.cpp
@@ -0,0 +1,241 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "win32_window.h"
+
+#include <flutter_windows.h>
+
+#include "resource.h"
+
+namespace {
+
+constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW";
+
+// The number of Win32Window objects that currently exist.
+static int g_active_window_count = 0;
+
+using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd);
+
+// Scale helper to convert logical scaler values to physical using passed in
+// scale factor
+int Scale(int source, double scale_factor) {
+  return static_cast<int>(source * scale_factor);
+}
+
+// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module.
+// This API is only needed for PerMonitor V1 awareness mode.
+void EnableFullDpiSupportIfAvailable(HWND hwnd) {
+  HMODULE user32_module = LoadLibraryA("User32.dll");
+  if (!user32_module) {
+    return;
+  }
+  auto enable_non_client_dpi_scaling =
+      reinterpret_cast<EnableNonClientDpiScaling*>(
+          GetProcAddress(user32_module, "EnableNonClientDpiScaling"));
+  if (enable_non_client_dpi_scaling != nullptr) {
+    enable_non_client_dpi_scaling(hwnd);
+    FreeLibrary(user32_module);
+  }
+}
+
+}  // namespace
+
+// Manages the Win32Window's window class registration.
+class WindowClassRegistrar {
+ public:
+  ~WindowClassRegistrar() = default;
+
+  // Returns the singleton registar instance.
+  static WindowClassRegistrar* GetInstance() {
+    if (!instance_) {
+      instance_ = new WindowClassRegistrar();
+    }
+    return instance_;
+  }
+
+  // Returns the name of the window class, registering the class if it hasn't
+  // previously been registered.
+  const wchar_t* GetWindowClass();
+
+  // Unregisters the window class. Should only be called if there are no
+  // instances of the window.
+  void UnregisterWindowClass();
+
+ private:
+  WindowClassRegistrar() = default;
+
+  static WindowClassRegistrar* instance_;
+
+  bool class_registered_ = false;
+};
+
+WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr;
+
+const wchar_t* WindowClassRegistrar::GetWindowClass() {
+  if (!class_registered_) {
+    WNDCLASS window_class{};
+    window_class.hCursor = LoadCursor(nullptr, IDC_ARROW);
+    window_class.lpszClassName = kWindowClassName;
+    window_class.style = CS_HREDRAW | CS_VREDRAW;
+    window_class.cbClsExtra = 0;
+    window_class.cbWndExtra = 0;
+    window_class.hInstance = GetModuleHandle(nullptr);
+    window_class.hIcon =
+        LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON));
+    window_class.hbrBackground = 0;
+    window_class.lpszMenuName = nullptr;
+    window_class.lpfnWndProc = Win32Window::WndProc;
+    RegisterClass(&window_class);
+    class_registered_ = true;
+  }
+  return kWindowClassName;
+}
+
+void WindowClassRegistrar::UnregisterWindowClass() {
+  UnregisterClass(kWindowClassName, nullptr);
+  class_registered_ = false;
+}
+
+Win32Window::Win32Window() { ++g_active_window_count; }
+
+Win32Window::~Win32Window() {
+  --g_active_window_count;
+  Destroy();
+}
+
+bool Win32Window::CreateAndShow(const std::wstring& title, const Point& origin,
+                                const Size& size) {
+  Destroy();
+
+  const wchar_t* window_class =
+      WindowClassRegistrar::GetInstance()->GetWindowClass();
+
+  const POINT target_point = {static_cast<LONG>(origin.x),
+                              static_cast<LONG>(origin.y)};
+  HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST);
+  UINT dpi = FlutterDesktopGetDpiForMonitor(monitor);
+  double scale_factor = dpi / 96.0;
+
+  HWND window = CreateWindow(
+      window_class, title.c_str(), WS_OVERLAPPEDWINDOW | WS_VISIBLE,
+      Scale(origin.x, scale_factor), Scale(origin.y, scale_factor),
+      Scale(size.width, scale_factor), Scale(size.height, scale_factor),
+      nullptr, nullptr, GetModuleHandle(nullptr), this);
+
+  if (!window) {
+    return false;
+  }
+
+  return OnCreate();
+}
+
+// static
+LRESULT CALLBACK Win32Window::WndProc(HWND const window, UINT const message,
+                                      WPARAM const wparam,
+                                      LPARAM const lparam) noexcept {
+  if (message == WM_NCCREATE) {
+    auto window_struct = reinterpret_cast<CREATESTRUCT*>(lparam);
+    SetWindowLongPtr(window, GWLP_USERDATA,
+                     reinterpret_cast<LONG_PTR>(window_struct->lpCreateParams));
+
+    auto that = static_cast<Win32Window*>(window_struct->lpCreateParams);
+    EnableFullDpiSupportIfAvailable(window);
+    that->window_handle_ = window;
+  } else if (Win32Window* that = GetThisFromHandle(window)) {
+    return that->MessageHandler(window, message, wparam, lparam);
+  }
+
+  return DefWindowProc(window, message, wparam, lparam);
+}
+
+LRESULT
+Win32Window::MessageHandler(HWND hwnd, UINT const message, WPARAM const wparam,
+                            LPARAM const lparam) noexcept {
+  switch (message) {
+    case WM_DESTROY:
+      window_handle_ = nullptr;
+      Destroy();
+      if (quit_on_close_) {
+        PostQuitMessage(0);
+      }
+      return 0;
+
+    case WM_DPICHANGED: {
+      auto newRectSize = reinterpret_cast<RECT*>(lparam);
+      LONG newWidth = newRectSize->right - newRectSize->left;
+      LONG newHeight = newRectSize->bottom - newRectSize->top;
+
+      SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth,
+                   newHeight, SWP_NOZORDER | SWP_NOACTIVATE);
+
+      return 0;
+    }
+    case WM_SIZE: {
+      RECT rect = GetClientArea();
+      if (child_content_ != nullptr) {
+        // Size and position the child window.
+        MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left,
+                   rect.bottom - rect.top, TRUE);
+      }
+      return 0;
+    }
+
+    case WM_ACTIVATE:
+      if (child_content_ != nullptr) {
+        SetFocus(child_content_);
+      }
+      return 0;
+  }
+
+  return DefWindowProc(window_handle_, message, wparam, lparam);
+}
+
+void Win32Window::Destroy() {
+  OnDestroy();
+
+  if (window_handle_) {
+    DestroyWindow(window_handle_);
+    window_handle_ = nullptr;
+  }
+  if (g_active_window_count == 0) {
+    WindowClassRegistrar::GetInstance()->UnregisterWindowClass();
+  }
+}
+
+Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept {
+  return reinterpret_cast<Win32Window*>(
+      GetWindowLongPtr(window, GWLP_USERDATA));
+}
+
+void Win32Window::SetChildContent(HWND content) {
+  child_content_ = content;
+  SetParent(content, window_handle_);
+  RECT frame = GetClientArea();
+
+  MoveWindow(content, frame.left, frame.top, frame.right - frame.left,
+             frame.bottom - frame.top, true);
+
+  SetFocus(child_content_);
+}
+
+RECT Win32Window::GetClientArea() {
+  RECT frame;
+  GetClientRect(window_handle_, &frame);
+  return frame;
+}
+
+HWND Win32Window::GetHandle() { return window_handle_; }
+
+void Win32Window::SetQuitOnClose(bool quit_on_close) {
+  quit_on_close_ = quit_on_close;
+}
+
+bool Win32Window::OnCreate() {
+  // No-op; provided for subclasses.
+  return true;
+}
+
+void Win32Window::OnDestroy() {
+  // No-op; provided for subclasses.
+}
diff --git a/packages/rfw/example/wasm/windows/runner/win32_window.h b/packages/rfw/example/wasm/windows/runner/win32_window.h
new file mode 100644
index 0000000..d2a7300
--- /dev/null
+++ b/packages/rfw/example/wasm/windows/runner/win32_window.h
@@ -0,0 +1,99 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef RUNNER_WIN32_WINDOW_H_
+#define RUNNER_WIN32_WINDOW_H_
+
+#include <windows.h>
+
+#include <functional>
+#include <memory>
+#include <string>
+
+// A class abstraction for a high DPI-aware Win32 Window. Intended to be
+// inherited from by classes that wish to specialize with custom
+// rendering and input handling
+class Win32Window {
+ public:
+  struct Point {
+    unsigned int x;
+    unsigned int y;
+    Point(unsigned int x, unsigned int y) : x(x), y(y) {}
+  };
+
+  struct Size {
+    unsigned int width;
+    unsigned int height;
+    Size(unsigned int width, unsigned int height)
+        : width(width), height(height) {}
+  };
+
+  Win32Window();
+  virtual ~Win32Window();
+
+  // Creates and shows a win32 window with |title| and position and size using
+  // |origin| and |size|. New windows are created on the default monitor. Window
+  // sizes are specified to the OS in physical pixels, hence to ensure a
+  // consistent size to will treat the width height passed in to this function
+  // as logical pixels and scale to appropriate for the default monitor. Returns
+  // true if the window was created successfully.
+  bool CreateAndShow(const std::wstring& title, const Point& origin,
+                     const Size& size);
+
+  // Release OS resources associated with window.
+  void Destroy();
+
+  // Inserts |content| into the window tree.
+  void SetChildContent(HWND content);
+
+  // Returns the backing Window handle to enable clients to set icon and other
+  // window properties. Returns nullptr if the window has been destroyed.
+  HWND GetHandle();
+
+  // If true, closing this window will quit the application.
+  void SetQuitOnClose(bool quit_on_close);
+
+  // Return a RECT representing the bounds of the current client area.
+  RECT GetClientArea();
+
+ protected:
+  // Processes and route salient window messages for mouse handling,
+  // size change and DPI. Delegates handling of these to member overloads that
+  // inheriting classes can handle.
+  virtual LRESULT MessageHandler(HWND window, UINT const message,
+                                 WPARAM const wparam,
+                                 LPARAM const lparam) noexcept;
+
+  // Called when CreateAndShow is called, allowing subclass window-related
+  // setup. Subclasses should return false if setup fails.
+  virtual bool OnCreate();
+
+  // Called when Destroy is called.
+  virtual void OnDestroy();
+
+ private:
+  friend class WindowClassRegistrar;
+
+  // OS callback called by message pump. Handles the WM_NCCREATE message which
+  // is passed when the non-client area is being created and enables automatic
+  // non-client DPI scaling so that the non-client area automatically
+  // responsponds to changes in DPI. All other messages are handled by
+  // MessageHandler.
+  static LRESULT CALLBACK WndProc(HWND const window, UINT const message,
+                                  WPARAM const wparam,
+                                  LPARAM const lparam) noexcept;
+
+  // Retrieves a class instance pointer for |window|
+  static Win32Window* GetThisFromHandle(HWND const window) noexcept;
+
+  bool quit_on_close_ = false;
+
+  // window handle for top level window.
+  HWND window_handle_ = nullptr;
+
+  // window handle for hosted content.
+  HWND child_content_ = nullptr;
+};
+
+#endif  // RUNNER_WIN32_WINDOW_H_
diff --git a/packages/rfw/images/overview1.png b/packages/rfw/images/overview1.png
new file mode 100644
index 0000000..15fe045
--- /dev/null
+++ b/packages/rfw/images/overview1.png
Binary files differ
diff --git a/packages/rfw/images/overview2.png b/packages/rfw/images/overview2.png
new file mode 100644
index 0000000..33885cf
--- /dev/null
+++ b/packages/rfw/images/overview2.png
Binary files differ
diff --git a/packages/rfw/lib/dart/binary.dart b/packages/rfw/lib/dart/binary.dart
new file mode 100644
index 0000000..f2094db
--- /dev/null
+++ b/packages/rfw/lib/dart/binary.dart
@@ -0,0 +1,632 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This file is hand-formatted.
+
+// This file must not import `dart:ui`, directly or indirectly, as it is
+// intended to function even in pure Dart server or CLI environments.
+import 'dart:convert';
+import 'dart:typed_data';
+
+import 'model.dart';
+
+/// Encode data as a Remote Flutter Widgets binary data blob.
+///
+/// See also:
+///
+///  * [decodeDataBlob], which decodes this format.
+///  * [encodeLibraryBlob], which uses a superset of this format to encode
+///    Remote Flutter Widgets binary library blobs.
+Uint8List encodeDataBlob(Object value) {
+  final _BlobEncoder encoder = _BlobEncoder();
+  encoder.writeSignature(<int>[0xFE, 0x52, 0x57, 0x44]);
+  encoder.writeValue(value);
+  return encoder.bytes.toBytes();
+}
+
+/// Decode a Remote Flutter Widgets binary data blob.
+///
+/// This data is usually used in conjunction with [DynamicContent].
+///
+/// This method supports a subset of the format supported by
+/// [decodeLibraryBlob]; specifically, it reads a _value_ from that format
+/// (rather than a _library_), and disallows values other than maps, lists,
+/// ints, doubles, booleans, and strings. See [decodeLibraryBlob] for a
+/// description of the format.
+///
+/// The first four bytes of the file (in hex) are FE 52 57 44.
+///
+/// See also:
+///
+///  * [encodeDataBlob], which encodes this format.
+///  * [decodeLibraryBlob], which uses a superset of this format to decode
+///    Remote Flutter Widgets binary library blobs.
+///  * [parseDataFile], which parses the text variant of this format.
+Object decodeDataBlob(Uint8List bytes) {
+  final _BlobDecoder decoder = _BlobDecoder(bytes.buffer.asByteData(bytes.offsetInBytes, bytes.lengthInBytes));
+  decoder.expectSignature(<int>[0xFE, 0x52, 0x57, 0x44]);
+  final Object result = decoder.readValue();
+  if (!decoder.finished) {
+    throw const FormatException('Unexpected trailing bytes after value.');
+  }
+  return result;
+}
+
+/// Encode data as a Remote Flutter Widgets binary library blob.
+///
+/// See also:
+///
+///  * [decodeLibraryBlob], which decodes this format.
+///  * [encodeDataBlob], which uses a subset of this format to decode
+///    Remote Flutter Widgets binary data blobs.
+///  * [parseLibraryFile], which parses the text variant of this format.
+Uint8List encodeLibraryBlob(RemoteWidgetLibrary value) {
+  final _BlobEncoder encoder = _BlobEncoder();
+  encoder.writeSignature(<int>[0xFE, 0x52, 0x46, 0x57]);
+  encoder.writeLibrary(value);
+  return encoder.bytes.toBytes();
+}
+
+/// Decode a Remote Flutter Widgets binary library blob.
+///
+/// Remote widget libraries are usually used in conjunction with a [Runtime].
+///
+/// ## Format
+///
+/// This format is a depth-first serialization of the in-memory data structures,
+/// using a one-byte tag to identify types when necessary, and using 64 bit
+/// integers to encode lengths when necessary.
+///
+/// The first four bytes of the file (in hex) are FE 52 46 57.
+///
+/// Primitives in this format are as follows:
+///
+/// * Integers are encoded as little-endian two's complement 64 bit integers.
+///   For example, the number 513 (0x0000000000000201) is encoded as a 0x01
+///   byte, a 0x02 byte, and six 0x00 bytes, in that order.
+///
+/// * Doubles are encoded as little-endian IEEE binary64 numbers.
+///
+/// * Strings are encoded as an integer length followed by that many UTF-8
+///   encoded bytes.
+///
+///   For example, the string "Hello" would be encoded as:
+///
+///    05 00 00 00 00 00 00 00 48 65 6C 6C 6F
+///
+/// * Lists are encoded as an integer length, followed by that many values
+///   back to back. When lists are of specific types (e.g. lists of imports),
+///   each value in the list is encoded directly (untagged lists); when the list
+///   can have multiple types, each value is prefixed by a tag giving the type,
+///   followed by the value (tagged lists). For example, a list of integers with
+///   the values 1 and 2 in that order would be encoded as:
+///
+///    02 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 02 00 00 00 00 00 00 00.
+///
+///   A list of arbitrary values that happens to contain one string "Hello"
+///   would be encoded as follows; 0x04 is the tag for "String" (the full list
+///   of tags is described below):
+///
+///    01 00 00 00 00 00 00 00 04 05 00 00 00 00 00 00 00 48 65 6C 6C 6F
+///
+///   A list of length zero is eight zero bytes with no additional payload.
+///
+/// * Maps are encoded as an integer length, followed by key/value pairs. For
+///   maps where all the keys are strings (e.g. when encoding a [DynamicMap]),
+///   the keys are given without tags (an untagged map). For maps where the keys
+///   are of arbitrary values, the keys are prefixed by a tag byte (a tagged
+///   map; this is only used when encoding [Switch]es). The _values_ are always
+///   prefixed by a tag byte (all maps are over values of arbitrary types).
+///
+///   For example, the map `{ a: 15 }` (when the keys are known to always be
+///   strings, so they are untagged) is encoded as follows (0x02 is the tag for
+///   integers):
+///
+///    01 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 61 02 0F 00 00 00 00 00 00 00
+///
+/// Objects are encoded as follows:
+///
+/// * [RemoteWidgetLibrary] objects are encoded as an untagged list of
+///   imports and an untagged list of widget declarations.
+///
+/// * Imports are encoded as an untagged list of strings, each of which is
+///   one of the subparts of the imported library name. For example, `import
+///   a.b` is encoded as:
+///
+///    02 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 61 01 00 00 00 00 00 00 00 62
+///
+/// * Widget declarations are encoded as a string giving the declaration name,
+///   an untagged map for the initial state, and finally the value that
+///   represents the root of the widget declaration ([WidgetDeclaration.root],
+///   which is always either a [Switch] or a [ConstructorCall]).
+///
+///   When the widget's initial state is null, it is encoded as an empty map. By
+///   extension, this means no distinction is made between a "stateless" remote
+///   widget and a "stateful" remote widget whose initial state is empty. (This
+///   is reasonable since if the initial state is empty, no state can ever be
+///   changed, so the widget is in fact _de facto_ stateless.)
+///
+/// Values are encoded as a tag byte followed by their data, as follows:
+///
+/// * Booleans are encoded as just a tag, with the tag being 0x00 for false and
+///   0x01 for true.
+///
+/// * Integers have the tag 0x02, and are encoded as described above (two's
+///   complement, little-endian, 64 bit).
+///
+/// * Doubles have the tag 0x03, and are encoded as described above
+///   (little-endian binary64).
+///
+/// * Strings have the tag 0x04, and are encoded as described above (length
+///   followed by UTF-8 bytes).
+///
+/// * Lists ([DynamicList]) have the tag 0x05, are encoded as described above
+///   (length followed by tagged values). (Lists of untagged values are never
+///   found in a "value" context.)
+///
+/// * Maps ([DynamicMap]) have the tag 0x07, are encoded as described above
+///   (length followed by pairs of strings and tagged values). (Tagged maps,
+///   i.e. those with tagged keys, are never found in a "value" context.)
+///
+/// * Loops ([Loop]) have the tag 0x08. They are encoded as two tagged values,
+///   the [Loop.input] and the [Loop.output].
+///
+/// * Constructor calls ([ConstructorCall]) have the tag 0x09. They are encoded
+///   as a string for the [ConstructorCall.name] followed by an untagged map
+///   describing the [ConstructorCall.arguments].
+///
+/// * Argument, data, and state references ([ArgsReference], [DataReference],
+///   and [StateReference] respectively) have tags 0x0A, 0x0B, and 0x0D
+///   respectively, and are encoded as tagged lists of strings or integers
+///   giving the [Reference.parts] of the reference.
+///
+/// * Loop references ([LoopReference]) have the tag 0x0C, and are encoded as an
+///   integer giving the number of [Loop] objects between the reference and the
+///   loop being referenced (this is similar to a De Bruijn index), followed by
+///   a tagged list of strings or integers giving the [Reference.parts] of the
+///   reference.
+///
+/// * Switches ([Switch]) have the tag 0x0F. They are encoded as a tagged value
+///   describing the control value ([Switch.input]), followed by a tagged map
+///   for the various case values ([Switch.outputs]). The default case is
+///   represented by a value with tag 0x10 (and no data).
+///
+///   For example, this switch:
+///
+///   ```
+///   switch (args.a) {
+///    0: 'z',
+///    1: 'o',
+///    default: 'd',
+///   }
+///   ```
+///
+///   ...is encoded as follows (including the tag for the switch itself):
+///
+///    0F 0A 01 00 00 00 00 00  00 00 61 03 00 00 00 00
+///    00 00 00 02 00 00 00 00  00 00 00 00 04 01 00 00
+///    00 00 00 00 00 7A 02 01  00 00 00 00 00 00 00 04
+///    01 00 00 00 00 00 00 00  6F 10 04 01 00 00 00 00
+///    00 00 00 64
+///
+/// * Event handlers have the tag 0x0E, and are encoded as a string
+///   ([EventHandler.eventName]) and an untagged map
+///   ([EventHandler.eventArguments]).
+///
+/// * State-setting handlers have the tag 0x11, and are encoded as a tagged list
+///   of strings or integers giving the [Reference.parts] of the state reference
+///   ([SetStateHandler.stateReference]), followed by the tagged value to which
+///   to set that state entry ([SetStateHandler.value]).
+///
+/// See also:
+///
+///  * [encodeLibraryBlob], which encodes this format.
+///  * [decodeDataBlob], which uses a subset of this format to decode
+///    Remote Flutter Widgets binary data blobs.
+///  * [parseDataFile], which parses the text variant of this format.
+RemoteWidgetLibrary decodeLibraryBlob(Uint8List bytes) {
+  final _BlobDecoder decoder = _BlobDecoder(bytes.buffer.asByteData(bytes.offsetInBytes, bytes.lengthInBytes));
+  decoder.expectSignature(<int>[0xFE, 0x52, 0x46, 0x57]);
+  final RemoteWidgetLibrary result = decoder.readLibrary();
+  if (!decoder.finished) {
+    throw const FormatException('Unexpected trailing bytes after constructors.');
+  }
+  return result;
+}
+
+// endianess used by this format
+const Endian _blobEndian = Endian.little;
+
+// magic signatures
+const int _msFalse = 0x00;
+const int _msTrue = 0x01;
+const int _msInt64 = 0x02;
+const int _msBinary64 = 0x03;
+const int _msString = 0x04;
+const int _msList = 0x05;
+const int _msMap = 0x07;
+const int _msLoop = 0x08;
+const int _msWidget = 0x09;
+const int _msArgsReference = 0x0A;
+const int _msDataReference = 0x0B;
+const int _msLoopReference = 0x0C;
+const int _msStateReference = 0x0D;
+const int _msEvent = 0x0E;
+const int _msSwitch = 0x0F;
+const int _msDefault = 0x10;
+const int _msSetState = 0x11;
+
+/// API for decoding Remote Flutter Widgets binary blobs.
+///
+/// Binary data blobs can be decoded by using [readValue].
+///
+/// Binary library blobs can be decoded by using [readLibrary].
+///
+/// In either case, if [finished] returns false after parsing the root token,
+/// then there is unexpected further data in the file.
+class _BlobDecoder {
+  _BlobDecoder(this.bytes);
+
+  final ByteData bytes;
+
+  int _cursor = 0;
+
+  bool get finished => _cursor >= bytes.lengthInBytes;
+
+  void _advance(String context, int length) {
+    if (_cursor + length > bytes.lengthInBytes) {
+      throw FormatException('Could not read $context at offset $_cursor: unexpected end of file.');
+    }
+    _cursor += length;
+  }
+
+  int _readByte() {
+    final int byteOffset = _cursor;
+    _advance('byte', 1);
+    return bytes.getUint8(byteOffset);
+  }
+
+  int _readInt64() {
+    final int byteOffset = _cursor;
+    _advance('int64', 8);
+    return bytes.getInt64(byteOffset, _blobEndian);
+  }
+
+  double _readDouble() {
+    final int byteOffset = _cursor;
+    _advance('double', 8);
+    return bytes.getFloat64(byteOffset, _blobEndian);
+  }
+
+  String _readString() {
+    final int length = _readInt64();
+    final int byteOffset = _cursor;
+    _advance('string', length);
+    return utf8.decode(bytes.buffer.asUint8List(bytes.offsetInBytes + byteOffset, length));
+  }
+
+  List<Object> _readPartList() {
+    return List<Object>.generate(_readInt64(), (int index) {
+      final int type = _readByte();
+      switch (type) {
+        case _msString:
+          return _readString();
+        case _msInt64:
+          return _readInt64();
+        default:
+          throw FormatException('Invalid reference type 0x${type.toRadixString(16).toUpperCase().padLeft(2, "0")} while decoding blob.');
+      }
+    });
+  }
+
+  Map<String, Object?>? _readMap(Object Function() readNode, { bool nullIfEmpty = false }) {
+    final int count = _readInt64();
+    if (count == 0 && nullIfEmpty) {
+      return null;
+    }
+    return DynamicMap.fromEntries(
+      Iterable<MapEntry<String, Object>>.generate(
+        count,
+        (int index) => MapEntry<String, Object>(
+          _readString(),
+          readNode(),
+        ),
+      ),
+    );
+  }
+
+  Object? _readSwitchKey() {
+    final int type = _readByte();
+    if (type == _msDefault) {
+      return null;
+    }
+    return _parseArgument(type);
+  }
+
+  Switch _readSwitch() {
+    final Object value = _readArgument();
+    final int count = _readInt64();
+    final Map<Object?, Object> cases = Map<Object?, Object>.fromEntries(
+      Iterable<MapEntry<Object?, Object>>.generate(
+        count,
+        (int index) => MapEntry<Object?, Object>(
+          _readSwitchKey(),
+          _readArgument(),
+        ),
+      ),
+    );
+    return Switch(value, cases);
+  }
+
+  Object _parseValue(int type, Object Function() readNode) {
+    switch (type) {
+      case _msFalse:
+        return false;
+      case _msTrue:
+        return true;
+      case _msInt64:
+        return _readInt64();
+      case _msBinary64:
+        return _readDouble();
+      case _msString:
+        return _readString();
+      case _msList:
+        return DynamicList.generate(_readInt64(), (int index) => readNode());
+      case _msMap:
+        return _readMap(readNode)!;
+      default: throw FormatException('Unrecognized data type 0x${type.toRadixString(16).toUpperCase().padLeft(2, "0")} while decoding blob.');
+    }
+  }
+
+  Object readValue() {
+    final int type = _readByte();
+    return _parseValue(type, readValue);
+  }
+
+  Object _parseArgument(int type) {
+    switch (type) {
+      case _msLoop:
+        return Loop(_readArgument(), _readArgument());
+      case _msWidget:
+        return _readWidget();
+      case _msArgsReference:
+        return ArgsReference(_readPartList());
+      case _msDataReference:
+        return DataReference(_readPartList());
+      case _msLoopReference:
+        return LoopReference(_readInt64(), _readPartList());
+      case _msStateReference:
+        return StateReference(_readPartList());
+      case _msEvent:
+        return EventHandler(_readString(), _readMap(_readArgument)!);
+      case _msSwitch:
+        return _readSwitch();
+      case _msSetState:
+        return SetStateHandler(StateReference(_readPartList()), _readArgument());
+      default:
+        return _parseValue(type, _readArgument);
+    }
+  }
+
+  Object _readArgument() {
+    final int type = _readByte();
+    return _parseArgument(type);
+  }
+
+  ConstructorCall _readWidget() {
+    final String name = _readString();
+    return ConstructorCall(name, _readMap(_readArgument)!);
+  }
+
+  WidgetDeclaration _readDeclaration() {
+    final String name = _readString();
+    final DynamicMap? initialState = _readMap(readValue, nullIfEmpty: true);
+    final int type = _readByte();
+    final BlobNode root;
+    switch (type) {
+      case _msSwitch:
+        root = _readSwitch();
+        break;
+      case _msWidget:
+        root = _readWidget();
+        break;
+      default:
+        throw FormatException('Unrecognized data type 0x${type.toRadixString(16).toUpperCase().padLeft(2, "0")} while decoding widget declaration root.');
+    }
+    return WidgetDeclaration(name, initialState, root);
+  }
+
+  List<WidgetDeclaration> _readDeclarationList() {
+    return List<WidgetDeclaration>.generate(_readInt64(), (int index) => _readDeclaration());
+  }
+
+  Import _readImport() {
+    return Import(LibraryName(List<String>.generate(_readInt64(), (int index) => _readString())));
+  }
+
+  List<Import> _readImportList() {
+    return List<Import>.generate(_readInt64(), (int index) => _readImport());
+  }
+
+  RemoteWidgetLibrary readLibrary() {
+    return RemoteWidgetLibrary(_readImportList(), _readDeclarationList());
+  }
+
+  void expectSignature(List<int> signature) {
+    assert(signature.length == 4);
+    final List<int> bytes = <int>[];
+    bool match = true;
+    for (final int byte in signature) {
+      final int read = _readByte();
+      bytes.add(read);
+      if (read != byte) {
+        match = false;
+      }
+    }
+    if (!match) {
+      throw FormatException(
+        'File signature mismatch. '
+        'Expected ${signature.map<String>((int byte) => byte.toRadixString(16).toUpperCase().padLeft(2, "0")).join(" ")} '
+        'but found ${bytes.map<String>((int byte) => byte.toRadixString(16).toUpperCase().padLeft(2, "0")).join(" ")}.'
+      );
+    }
+  }
+}
+
+/// API for encoding Remote Flutter Widgets binary blobs.
+///
+/// Binary data blobs can be serialized using [writeValue].
+///
+/// Binary library blobs can be serialized using [writeLibrary].
+///
+/// The output is in [bytes], and can be cleared manually to reuse the [_BlobEncoder].
+class _BlobEncoder {
+  _BlobEncoder();
+
+  static final Uint8List _scratchOut = Uint8List(8);
+  static final ByteData _scratchIn = _scratchOut.buffer.asByteData(_scratchOut.offsetInBytes, _scratchOut.lengthInBytes);
+
+  final BytesBuilder bytes = BytesBuilder(); // copying builder -- we repeatedly add _scratchOut after changing it
+
+  void _writeInt64(int value) {
+    _scratchIn.setInt64(0, value, _blobEndian);
+    bytes.add(_scratchOut);
+  }
+
+  void _writeString(String value) {
+    final Uint8List buffer = utf8.encode(value) as Uint8List;
+    _writeInt64(buffer.length);
+    bytes.add(buffer);
+  }
+
+  void _writeMap(DynamicMap value, void Function(Object? value) recurse) {
+    _writeInt64(value.length);
+    value.forEach((String key, Object? value) {
+      _writeString(key);
+      recurse(value);
+    });
+  }
+
+  void _writePart(Object? value) {
+    if (value is int) {
+      bytes.addByte(_msInt64);
+      _writeInt64(value);
+    } else if (value is String) {
+      bytes.addByte(_msString);
+      _writeString(value);
+    } else {
+      throw StateError('Unexpected type ${value.runtimeType} while encoding blob.');
+    }
+  }
+
+  void _writeValue(Object? value, void Function(Object? value) recurse) {
+    if (value == false) {
+      bytes.addByte(_msFalse);
+    } else if (value == true) {
+      bytes.addByte(_msTrue);
+    } else if (value is double) {
+      bytes.addByte(_msBinary64);
+      _scratchIn.setFloat64(0, value, _blobEndian);
+      bytes.add(_scratchOut);
+    } else if (value is DynamicList) {
+      bytes.addByte(_msList);
+      _writeInt64(value.length);
+      value.forEach(recurse);
+    } else if (value is DynamicMap) {
+      bytes.addByte(_msMap);
+      _writeMap(value, recurse);
+    } else {
+      _writePart(value);
+    }
+  }
+
+  void writeValue(Object? value) {
+    _writeValue(value, writeValue);
+  }
+
+  void _writeArgument(Object? value) {
+    if (value is Loop) {
+      bytes.addByte(_msLoop);
+      _writeArgument(value.input);
+      _writeArgument(value.output);
+    } else if (value is ConstructorCall) {
+      bytes.addByte(_msWidget);
+      _writeString(value.name);
+      _writeMap(value.arguments, _writeArgument);
+    } else if (value is ArgsReference) {
+      bytes.addByte(_msArgsReference);
+      _writeInt64(value.parts.length);
+      value.parts.forEach(_writePart);
+    } else if (value is DataReference) {
+      bytes.addByte(_msDataReference);
+      _writeInt64(value.parts.length);
+      value.parts.forEach(_writePart);
+    } else if (value is LoopReference) {
+      bytes.addByte(_msLoopReference);
+      _writeInt64(value.loop);
+      _writeInt64(value.parts.length);
+      value.parts.forEach(_writePart);
+    } else if (value is StateReference) {
+      bytes.addByte(_msStateReference);
+      _writeInt64(value.parts.length);
+      value.parts.forEach(_writePart);
+    } else if (value is EventHandler) {
+      bytes.addByte(_msEvent);
+      _writeString(value.eventName);
+      _writeMap(value.eventArguments, _writeArgument);
+    } else if (value is Switch) {
+      bytes.addByte(_msSwitch);
+      _writeArgument(value.input);
+      _writeInt64(value.outputs.length);
+      value.outputs.forEach((Object? key, Object value) {
+        if (key == null) {
+          bytes.addByte(_msDefault);
+        } else {
+          _writeArgument(key);
+        }
+        _writeArgument(value);
+      });
+    } else if (value is SetStateHandler) {
+      bytes.addByte(_msSetState);
+      final StateReference reference = value.stateReference as StateReference;
+      _writeInt64(reference.parts.length);
+      reference.parts.forEach(_writePart);
+      _writeArgument(value.value);
+    } else {
+      assert(value is! BlobNode);
+      _writeValue(value, _writeArgument);
+    }
+  }
+
+  void _writeDeclarationList(List<WidgetDeclaration> value) {
+    _writeInt64(value.length);
+    for (final WidgetDeclaration declaration in value) {
+      _writeString(declaration.name);
+      if (declaration.initialState != null) {
+        _writeMap(declaration.initialState!, _writeArgument);
+      } else {
+        _writeInt64(0);
+      }
+      _writeArgument(declaration.root);
+    }
+  }
+
+  void _writeImportList(List<Import> value) {
+    _writeInt64(value.length);
+    for (final Import import in value) {
+      _writeInt64(import.name.parts.length);
+      import.name.parts.forEach(_writeString);
+    }
+  }
+
+  void writeLibrary(RemoteWidgetLibrary library) {
+    _writeImportList(library.imports);
+    _writeDeclarationList(library.widgets);
+  }
+
+  void writeSignature(List<int> signature) {
+    assert(signature.length == 4);
+    bytes.add(signature);
+  }
+}
diff --git a/packages/rfw/lib/dart/model.dart b/packages/rfw/lib/dart/model.dart
new file mode 100644
index 0000000..13d8382
--- /dev/null
+++ b/packages/rfw/lib/dart/model.dart
@@ -0,0 +1,649 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This file is hand-formatted.
+
+// This file must not import `dart:ui`, directly or indirectly, as it is
+// intended to function even in pure Dart server or CLI environments.
+import 'package:meta/meta.dart';
+
+/// A map whose keys are strings and whose values are [DynamicMap],
+/// [DynamicList], int, double, bool, string, and [BlobNode] objects.
+///
+/// Part of the data type for [DynamicContent] objects.
+typedef DynamicMap = Map<String, Object?>;
+
+/// A list whose values are [DynamicMap], [DynamicList], int, double, bool,
+/// string, and [BlobNode] objects.
+///
+/// Part of the data type for [DynamicContent] objects.
+typedef DynamicList = List<Object?>;
+
+/// Base class of nodes that appear in the output of [decodeDataBlob] and
+/// [decodeLibraryBlob].
+///
+/// In addition to this, the following types can be found in that output:
+///
+///  * [DynamicMap]
+///  * [DynamicList]
+///  * [int]
+///  * [double]
+///  * [bool]
+///  * [String]
+abstract class BlobNode {
+  /// Abstract const constructor. This constructor enables subclasses to provide
+  /// const constructors so that they can be used in const expressions.
+  const BlobNode();
+}
+
+bool _listEquals<T>(List<T>? a, List<T>? b) {
+  if (identical(a, b)) {
+    return true;
+  }
+  if (a == null || b == null || a.length != b.length) {
+    return false;
+  }
+  for (int index = 0; index < a.length; index += 1) {
+    if (a[index] != b[index]) {
+      return false;
+    }
+  }
+  return true;
+}
+
+/// The name of a widgets library in the RFW package.
+///
+/// Libraries are typically referred to with names like "core.widgets" or
+/// "com.example.shopping.cart". This class represents these names as lists of
+/// tokens, in those cases `['core', 'widgets']` and `['com', 'example',
+/// 'shopping', 'cart']`, for example.
+@immutable
+class LibraryName implements Comparable<LibraryName> {
+  /// Wrap the given list as a [LibraryName].
+  ///
+  /// The given list is not copied; it is an error to modify it after creating
+  /// the [LibraryName].
+  const LibraryName(this.parts);
+
+  /// The components of the structured library name.
+  final List<String> parts;
+
+  @override
+  bool operator ==(Object other) {
+    if (other.runtimeType != runtimeType) {
+      return false;
+    }
+    return other is LibraryName
+        && _listEquals<String>(parts, other.parts);
+  }
+
+  @override
+  int get hashCode => Object.hashAll(parts);
+
+  @override
+  String toString() => parts.join('.');
+
+  @override
+  int compareTo(LibraryName other) {
+    for (int index = 0; index < parts.length; index += 1) {
+      if (other.parts.length <= index) {
+        return 1;
+      }
+      final int result = parts[index].compareTo(other.parts[index]);
+      if (result != 0) {
+        return result;
+      }
+    }
+    assert(other.parts.length >= parts.length);
+    return parts.length - other.parts.length;
+  }
+}
+
+/// The name of a widget used by the RFW package, including its library name.
+///
+/// This can be used to identify both local widgets and remote widgets.
+@immutable
+class FullyQualifiedWidgetName implements Comparable<FullyQualifiedWidgetName> {
+  /// Wrap the given library name and widget name in a [FullyQualifiedWidgetName].
+  const FullyQualifiedWidgetName(this.library, this.widget);
+
+  /// The name of the library in which [widget] can be found.
+  final LibraryName library;
+
+  /// The name of the widget, which should be in the specified [library].
+  final String widget;
+
+  @override
+  bool operator ==(Object other) {
+    if (other.runtimeType != runtimeType) {
+      return false;
+    }
+    return other is FullyQualifiedWidgetName
+        && library == other.library && widget == other.widget;
+  }
+
+  @override
+  int get hashCode => Object.hash(library, widget);
+
+  @override
+  String toString() => '$library:$widget';
+
+  @override
+  int compareTo(FullyQualifiedWidgetName other) {
+    final int result = library.compareTo(other.library);
+    if (result != 0) {
+      return result;
+    }
+    return widget.compareTo(other.widget);
+  }
+}
+
+/// The type of the [missing] value.
+///
+/// This is used internally by the RFW package to avoid needing to use nullable
+/// types.
+class Missing extends BlobNode {
+  const Missing._();
+
+  @override
+  String toString() => '<missing>';
+}
+
+/// The value used by [DynamicContent] to represent missing data.
+///
+/// This is return from [DynamicContent.subscribe] when the specified key is not
+/// present.
+///
+/// Content in a [DynamicContent] should not contain [missing] values.
+const Missing missing = Missing._();
+
+/// Representation of the `...for` construct in Remote Flutter Widgets library
+/// blobs.
+class Loop extends BlobNode {
+  /// Creates a [Loop] with the given [input] and [output].
+  ///
+  /// The provided objects must not be mutated after being given to the
+  /// constructor (e.g. the ownership of any lists and maps passes to this
+  /// object).
+  const Loop(this.input, this.output);
+
+  /// The list on which to iterate.
+  ///
+  /// This is typically some sort of [Reference], but could be a [DynamicList].
+  ///
+  /// It is an error for this to be a value that does not resolve to a list.
+  final Object input;
+
+  /// The template to apply for each value on [input].
+  final Object output;
+
+  @override
+  String toString() => '...for loop in $input: $output';
+}
+
+/// Representation of the `switch` construct in Remote Flutter Widgets library
+/// blobs.
+class Switch extends BlobNode {
+  /// Creates a [Switch] with the given [input] and [outputs].
+  ///
+  /// The provided objects must not be mutated after being given to the
+  /// constructor. In particular, changing the [outputs] map after creating the
+  /// [Switch] is an error.
+  const Switch(this.input, this.outputs);
+
+  /// The value to switch on (after resolution).
+  final Object input;
+
+  /// The cases for this switch. Keys correspond to values to compare with
+  /// [input]. The null value is used as the default case.
+  ///
+  /// At runtime, if none of the keys match [input] and there is no null key,
+  /// the [Switch] as a whole is treated as if it was [missing]. If the [Switch]
+  /// is used where a [ConstructorCall] was expected, the result is an
+  /// [ErrorWidget].
+  final Map<Object?, Object> outputs;
+
+  @override
+  String toString() => 'switch $input $outputs';
+}
+
+/// Representation of references to widgets in Remote Flutter Widgets library
+/// blobs.
+class ConstructorCall extends BlobNode {
+  /// Creates a [ConstructorCall] for a widget of the given name in the current
+  /// library's scope, with the given [arguments].
+  ///
+  /// The [arguments] must not be mutated after the object is created.
+  const ConstructorCall(this.name, this.arguments);
+
+  /// The name of the widget to create.
+  ///
+  /// The name is looked up in the current library, or, failing that, in a
+  /// depth-first search of this library's dependencies.
+  final String name;
+
+  /// The arguments to pass to the constructor.
+  ///
+  /// Constructors in RFW only have named arguments. This differs from Dart
+  /// (where arguments can also be positional.)
+  final DynamicMap arguments;
+
+  @override
+  String toString() => '$name($arguments)';
+}
+
+/// Base class for various kinds of references in the RFW data structures.
+abstract class Reference extends BlobNode {
+  /// Abstract const constructor. This constructor enables subclasses to provide
+  /// const constructors so that they can be used in const expressions.
+  ///
+  /// The [parts] must not be mutated after the object is created.
+  const Reference(this.parts);
+
+  /// The components of the reference. Each entry must be either a String (to
+  /// index into a [DynamicMap]) an integer (to index into a [DynamicList]).
+  ///
+  /// It is an error for any of the parts to be of any other type.
+  final List<Object> parts;
+}
+
+/// Unbound reference to arguments.
+///
+/// This class is used to represent references of the form "args.foo.bar" after
+/// parsing, before the arguments are bound.
+class ArgsReference extends Reference {
+  /// Wraps the given [parts] as an [ArgsReference].
+  ///
+  /// The [parts] must not be mutated after the object is created.
+  const ArgsReference(List<Object> parts): super(parts);
+
+  /// Binds the arguments reference to a specific set of arguments.
+  ///
+  /// Returns a [BoundArgsReference] with the same [parts] and whose
+  /// [BoundArgsReference.arguments] is given by `arguments`.
+  BoundArgsReference bind(Object arguments) {
+    return BoundArgsReference(arguments, parts);
+  }
+
+  @override
+  String toString() => 'args.${parts.join(".")}';
+}
+
+/// Bound reference to arguments.
+///
+/// This class is used to represent references of the form "args.foo.bar" after
+/// a widget declaration has been bound to specific arguments via a constructor
+/// call. The [arguments] property is a reference to the
+/// [ConstructorCall.arguments] object (or, more typically, a clone of that
+/// object that itself has had references within it bound).
+///
+/// This class is an internal detail of the RFW [Runtime] and is generally not
+/// used directly.
+class BoundArgsReference extends Reference {
+  /// Wraps the given [parts] and [arguments] as an [ArgsReference].
+  ///
+  /// The parameters must not be mutated after the object is created.
+  ///
+  /// Generally this class is created using [ArgsReference.bind].
+  const BoundArgsReference(this.arguments, List<Object> parts): super(parts);
+
+  /// The object into which [parts] will be indexed.
+  ///
+  /// This could contain [Loop]s, which is why it cannot be indexed immediately
+  /// upon creation.
+  final Object arguments;
+
+  @override
+  String toString() => 'args($arguments).${parts.join(".")}';
+}
+
+/// Reference to the [DynamicContent] data that is passed into the widget (see
+/// [Runtime.build]'s `data` argument).
+class DataReference extends Reference {
+  /// Wraps the given [parts] as an [DataReference].
+  ///
+  /// The [parts] must not be mutated after the object is created.
+  const DataReference(List<Object> parts): super(parts);
+
+  /// Creates a new [DataRefererence] that indexes even deeper than this one.
+  ///
+  /// For example, suppose a widget's arguments consisted of a map with one key,
+  /// "a", whose value was a [DataRefererence] referencing "data.foo.bar". Now
+  /// suppose that the widget itself has an [ArgsReference] that references
+  /// "args.a.baz". The "args.a" part identifies the aforementioned
+  /// [DataReference], and so the resulting reference is actually to
+  /// "data.foo.bar.baz".
+  ///
+  /// In this example, the [DataReference] to "data.foo.bar" would have its
+  /// [constructReference] method invoked by the runtime, with `["baz"]` as the
+  /// `moreParts` argument, so that the resulting [DataReference]'s [parts] is a
+  /// combination of the original's (`["foo", "bar"]`) and the additional parts
+  /// provided to the method.
+  DataReference constructReference(List<Object> moreParts) {
+    return DataReference(parts + moreParts);
+  }
+
+  @override
+  String toString() => 'data.${parts.join(".")}';
+}
+
+/// Unbound reference to a [Loop].
+class LoopReference extends Reference {
+  /// Wraps the given [loop] and [parts] as a [LoopReference].
+  ///
+  /// The [parts] must not be mutated after the object is created.
+  const LoopReference(this.loop, List<Object> parts): super(parts);
+
+  /// The index to the referenced loop.
+  ///
+  /// Loop indices count up, so the nearest loop ancestor of the reference has
+  /// index zero, with indices counting up when going up the tree towards the
+  /// root.
+  final int loop; // this is basically a De Bruijn index
+
+  /// Creates a new [LoopRefererence] that indexes even deeper than this one.
+  ///
+  /// For example, suppose a widget's arguments consisted of a map with one key,
+  /// "a", whose value was a [LoopRefererence] referencing "loop0.foo.bar". Now
+  /// suppose that the widget itself has an [ArgsReference] that references
+  /// "args.a.baz". The "args.a" part identifies the aforementioned
+  /// [LoopReference], and so the resulting reference is actually to
+  /// "data.foo.bar.baz".
+  ///
+  /// In this example, the [LoopReference] to "loop0.foo.bar" would have its
+  /// [constructReference] method invoked by the runtime, with `["baz"]` as the
+  /// `moreParts` argument, so that the resulting [LoopReference]'s [parts] is a
+  /// combination of the original's (`["foo", "bar"]`) and the additional parts
+  /// provided to the method.
+  ///
+  /// The [loop] index is maintained in the new object.
+  LoopReference constructReference(List<Object> moreParts) {
+    return LoopReference(loop, parts + moreParts);
+  }
+
+  /// Binds the loop reference to a specific value.
+  ///
+  /// Returns a [BoundLoopReference] with the same [parts] and whose
+  /// [BoundLoopReference.value] is given by `value`. The [loop] index is
+  /// dropped in the process.
+  BoundLoopReference bind(Object value) {
+    return BoundLoopReference(value, parts);
+  }
+
+  @override
+  String toString() => 'loop$loop.${parts.join(".")}';
+}
+
+/// Bound reference to a [Loop].
+///
+/// This class is used to represent references of the form "loopvar.foo.bar"
+/// after the list containing the relevant loop has been dereferenced so that
+/// the loop variable refers to a specific value in the list. The [value] is
+/// that resolved value.
+///
+/// This class is an internal detail of the RFW [Runtime] and is generally not
+/// used directly.
+class BoundLoopReference extends Reference {
+  /// Wraps the given [value] and [parts] as a [BoundLoopReference].
+  ///
+  /// The [parts] must not be mutated after the object is created.
+  ///
+  /// Generally this class is created using [LoopReference.bind].
+  const BoundLoopReference(this.value, List<Object> parts): super(parts);
+
+  /// The object into which [parts] will index.
+  ///
+  /// This could contain further [Loop]s or unbound [LoopReference]s, which is
+  /// why it cannot be indexed immediately upon creation.
+  final Object value;
+
+  /// Creates a new [BoundLoopRefererence] that indexes even deeper than this
+  /// one.
+  ///
+  /// For example, suppose a widget's arguments consisted of a map with one key,
+  /// "a", whose value was a [BoundLoopRefererence] referencing "loop0.foo.bar".
+  /// Now suppose that the widget itself has an [ArgsReference] that references
+  /// "args.a.baz". The "args.a" part identifies the aforementioned
+  /// [BoundLoopReference], and so the resulting reference is actually to
+  /// "data.foo.bar.baz".
+  ///
+  /// In this example, the [BoundLoopReference] to "loop0.foo.bar" would have
+  /// its [constructReference] method invoked by the runtime, with `["baz"]` as
+  /// the `moreParts` argument, so that the resulting [BoundLoopReference]'s
+  /// [parts] is a combination of the original's (`["foo", "bar"]`) and the
+  /// additional parts provided to the method.
+  ///
+  /// The resolved [value] (which is what the [parts] will eventually index
+  /// into) is maintained in the new object.
+  BoundLoopReference constructReference(List<Object> moreParts) {
+    return BoundLoopReference(value, parts + moreParts);
+  }
+
+  @override
+  String toString() => 'loop($value).${parts.join(".")}';
+}
+
+/// Base class for [StateReference] and [BoundStateReference].
+///
+/// This is used to ensure [SetStateHandler]'s [SetStateHandler.stateReference]
+/// property can only hold a state reference.
+abstract class AnyStateReference extends Reference {
+  /// Abstract const constructor. This constructor enables subclasses to provide
+  /// const constructors so that they can be used in const expressions.
+  ///
+  /// The [parts] must not be mutated after the object is created.
+  const AnyStateReference(List<Object> parts): super(parts);
+}
+
+/// Unbound reference to remote widget's state.
+///
+/// This class is used to represent references of the form "state.foo.bar".
+class StateReference extends AnyStateReference {
+  /// Wraps the given [parts] as a [StateReference].
+  ///
+  /// The [parts] must not be mutated after the object is created.
+  const StateReference(List<Object> parts): super(parts);
+
+  /// Binds the state reference to a specific widget (identified by depth).
+  ///
+  /// Returns a [BoundStateReference] with the same [parts] and whose
+  /// [BoundLoopReference.depth] is given by `depth`.
+  BoundStateReference bind(int depth) {
+    return BoundStateReference(depth, parts);
+  }
+
+  @override
+  String toString() => 'state.${parts.join(".")}';
+}
+
+/// Bound reference to a remote widget's state.
+///
+/// This class is used to represent references of the form "state.foo.bar" after
+/// the widgets have been constructed, so that the right state can be
+/// identified.
+///
+/// This class is an internal detail of the RFW [Runtime] and is generally not
+/// used directly.
+class BoundStateReference extends AnyStateReference {
+  /// Wraps the given [depth] and [parts] as a [BoundStateReference].
+  ///
+  /// The [parts] must not be mutated after the object is created.
+  ///
+  /// Generally this class is created using [StateReference.bind].
+  const BoundStateReference(this.depth, List<Object> parts): super(parts);
+
+  /// The widget to whose state the state reference refers.
+  ///
+  /// This identifies the widget by depth starting at the widget that was
+  /// created by [Runtime.build] (or a [RemoteWidget], which uses that method).
+  ///
+  /// Since state references always go up the tree, this is an unambiguous way
+  /// to reference state, even though in practice in the entire tree multiple
+  /// widgets may be stateful at the same depth.
+  final int depth;
+
+  /// Creates a new [BoundStateRefererence] that indexes even deeper than this
+  /// one (deeper into the specified widget's state, not into a deeper widget!).
+  ///
+  /// For example, suppose a widget's arguments consisted of a map with one key,
+  /// "a", whose value was a [BoundStateRefererence] referencing "state.foo.bar".
+  /// Now suppose that the widget itself has an [ArgsReference] that references
+  /// "args.a.baz". The "args.a" part identifies the aforementioned
+  /// [BoundStateReference], and so the resulting reference is actually to
+  /// "state.foo.bar.baz".
+  ///
+  /// In this example, the [BoundStateReference] to "state.foo.bar" would have
+  /// its [constructReference] method invoked by the runtime, with `["baz"]` as
+  /// the `moreParts` argument, so that the resulting [BoundStateReference]'s
+  /// [parts] is a combination of the original's (`["foo", "bar"]`) and the
+  /// additional parts provided to the method.
+  ///
+  /// The [depth] is maintained in the new object.
+  BoundStateReference constructReference(List<Object> moreParts) {
+    return BoundStateReference(depth, parts + moreParts);
+  }
+
+  @override
+  String toString() => 'state^$depth.${parts.join(".")}';
+}
+
+/// Base class for [EventHandler] and [SetStateHandler].
+///
+/// This is used by the [Runtime] to quickly filter out objects that are not
+/// event handlers of any kind.
+abstract class AnyEventHandler extends BlobNode {
+  /// Abstract const constructor. This constructor enables subclasses to provide
+  /// const constructors so that they can be used in const expressions.
+  const AnyEventHandler();
+}
+
+/// Description of a callback in an RFW widget declaration.
+///
+/// This represents a signal to send to the application using the RFW package.
+/// Typically applications either handle such messages locally, or forward them
+/// to a server for further processing.
+class EventHandler extends AnyEventHandler {
+  /// Wraps the given event name and arguments in an [EventHandler] object.
+  ///
+  /// The [eventArguments] must not be mutated after the object is created.
+  const EventHandler(this.eventName, this.eventArguments);
+
+  /// A string to identify the event. This provides an unambiguous identifier
+  /// for the event, avoiding the need to establish a convention in the
+  /// [eventArguments].
+  final String eventName;
+
+  /// The payload to provide with the event.
+  final DynamicMap eventArguments;
+
+  @override
+  String toString() => 'event $eventName $eventArguments';
+}
+
+/// Description of a state setter in an RFW widget declaration.
+///
+/// This event handler is handled by the RFW [Runtime] itself by setting the
+/// state referenced by [stateReference] to the value represented by [value]
+/// when the event handler would be invoked.
+class SetStateHandler extends AnyEventHandler {
+  /// Wraps the given [stateReference] and [value] in a [SetStateHandler] object.
+  ///
+  /// The [value] must not be mutated after the object is created (e.g. in the
+  /// event that it is a [DynamicMap] or [DynamicList]).
+  const SetStateHandler(this.stateReference, this.value);
+
+  /// Identifies the member in the widget's state to mutate.
+  final AnyStateReference stateReference;
+
+  /// The value to which the specified state will be set.
+  final Object value;
+
+  @override
+  String toString() => 'set $stateReference = $value';
+}
+
+/// A library import.
+///
+/// Used to describe which libraries a remote widget libraries depends on. The
+/// identified libraries can be local or remote. Import loops are invalid.
+class Import extends BlobNode {
+  /// Wraps the given library [name] in an [Import] object.
+  const Import(this.name);
+
+  /// The name of the library to import.
+  final LibraryName name;
+
+  @override
+  String toString() => 'import $name;';
+}
+
+/// A description of a widget in a remote widget library.
+///
+/// The [root] must be either a [ConstructorCall] or a [Switch] that evaluates
+/// to a [ConstructorCall]. (In principle one can imagine that an
+/// [ArgsReference] that evaluates to a [ConstructorCall] would also be valid,
+/// but such a construct would be redundant and would not provide any additional
+/// expressivity, so it is disallowed.)
+///
+/// The tree rooted at [root] must not contain (directly or indirectly) a
+/// [ConstructorCall] that references the widget declared by this
+/// [WidgetDeclaration]: widget loops, even indirect loops or loops that would
+/// in principle be terminated by use of a [Switch], are not allowed.
+class WidgetDeclaration extends BlobNode {
+  /// Binds the given [name] to the definition given by [root].
+  ///
+  /// The [initialState] may be null. If it is not, this represents a stateful widget.
+  const WidgetDeclaration(this.name, this.initialState, this.root) : assert(root is ConstructorCall || root is Switch);
+
+  /// The name of the widget that this declaration represents.
+  ///
+  /// This is the left hand side of a widget declaration.
+  final String name;
+
+  /// If non-null, this is a stateful widget; the value is used to create the
+  /// initial copy of the state when the widget is created.
+  final DynamicMap? initialState;
+
+  /// The widget to return when this widget is used.
+  ///
+  /// This is usually a [ConstructorCall], but may be a [Switch] (so long as
+  /// that [Switch] resolves to a [ConstructorCall]. Other values (or a [Switch]
+  /// that does not resolve to a constructor call) will result in an
+  /// [ErrorWidget] being used.
+  final BlobNode root; // ConstructorCall or Switch
+
+  @override
+  String toString() => 'widget $name = $root;';
+}
+
+/// Base class for widget libraries.
+abstract class WidgetLibrary {
+  /// Abstract const constructor. This constructor enables subclasses to provide
+  /// const constructors so that they can be used in const expressions.
+  const WidgetLibrary();
+}
+
+/// The in-memory representation of the output of [parseTextLibraryFile] or
+/// [decodeLibraryBlob].
+class RemoteWidgetLibrary extends WidgetLibrary {
+  /// Wraps a set of [imports] and [widgets] (widget declarations) in a
+  /// [RemoteWidgetLibrary] object.
+  ///
+  /// The provided lists must not be mutated once the library is created.
+  const RemoteWidgetLibrary(this.imports, this.widgets);
+
+  /// The list of libraries that this library depends on.
+  ///
+  /// This must not be empty, since at least one local widget library must be in
+  /// scope in order for the remote widget library to be useful.
+  final List<Import> imports;
+
+  /// The list of widgets declared by this library.
+  ///
+  /// This can be empty.
+  final List<WidgetDeclaration> widgets;
+
+  @override
+  String toString() => const Iterable<Object>.empty().followedBy(imports).followedBy(widgets).join('\n');
+}
diff --git a/packages/rfw/lib/dart/text.dart b/packages/rfw/lib/dart/text.dart
new file mode 100644
index 0000000..da596b7
--- /dev/null
+++ b/packages/rfw/lib/dart/text.dart
@@ -0,0 +1,2454 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This file is hand-formatted.
+
+// This file must not import `dart:ui`, directly or indirectly, as it is
+// intended to function even in pure Dart server or CLI environments.
+
+import 'model.dart';
+
+/// Parse a Remote Flutter Widgets text data file.
+///
+/// This data is usually used in conjunction with [DynamicContent].
+///
+/// Parsing this format is about ten times slower than parsing the binary
+/// variant; see [decodeDataBlob]. As such it is strongly discouraged,
+/// especially in resource-constrained contexts like mobile applications.
+///
+/// ## Format
+///
+/// This format is inspired by JSON, but with the following changes:
+///
+///  * end-of-line comments are supported, using the "//" syntax from C (and
+///    Dart).
+///
+///  * map keys may be unquoted when they are alphanumeric.
+///
+///  * "null" is not a valid value except as a value in maps, where it is
+///    equivalent to omitting the corresponding key entirely. This is allowed
+///    primarily as a development aid to explicitly indicate missing keys. (The
+///    data model itself, and the binary format (see [decodeDataBlob]), do not
+///    contain this feature.)
+///
+///  * integers and doubles are distinguished explicitly, via the presence of
+///    the decimal point and/or exponent characters. Integers may also be
+///    expressed as hex literals. Numbers are explicitly 64 bit precision;
+///    specifically, signed 64 bit integers, or binary64 floating point numbers.
+///
+///  * files are always rooted at a map. (Different versions of JSON are
+///    ambiguous or contradictory about this.)
+///
+/// Here is the BNF:
+///
+/// ```bnf
+/// root ::= WS* map WS*
+/// ```
+///
+/// Every Remote Flutter Widget text data file must match the `root` production.
+///
+/// ```bnf
+/// map ::= "{" ( WS* entry WS* "," )* WS* entry? WS* "}"
+/// entry ::= ( identifier | string ) WS* ":" WS* ( value | "null" )
+/// ```
+///
+/// Maps are comma-separated lists of name-value pairs (a single trailing comma
+/// is permitted), surrounded by braces. The key must be either a quoted string
+/// or an unquoted identifier. The value must be either the keyword "null",
+/// which is equivalent to the entry being omitted, or a value as defined below.
+/// Duplicates (notwithstanding entries that use the keyword "null") are not
+/// permitted. In general the use of the "null" keyword is discouraged and is
+/// supported only to allow mechanically-generated data to consistently include
+/// every key even when one has no value.
+///
+/// ```bnf
+/// value ::= map | list | integer | double | string | "true" | "false"
+/// ```
+///
+/// The "true" and "false" keywords represented the boolean true and false
+/// values respectively.
+///
+/// ```bnf
+/// list ::= "[" ( WS* value WS* "," )* WS* value? WS* "]"
+/// ```
+///
+/// Following the pattern set by maps, lists are comma-separated lists of values
+/// (a single trailing comma is permitted), surrounded by brackets.
+///
+/// ```bnf
+/// identifier ::= letter ( letter | digit )*
+/// letter ::= <a character in the ranges A-Z, a-z, or the underscore>
+/// digit ::= <a character in the range 0-9>
+/// ```
+///
+/// Identifiers are alphanumeric sequences (with the underscore considered a
+/// letter) that do not start with a digit.
+///
+/// ```bnf
+/// string ::= DQ stringbodyDQ* DQ | SQ stringbodySQ* SQ
+/// DQ ::= <U+0022>
+/// stringbodyDQ ::= escape | characterDQ
+/// characterDQ ::= <U+0000..U+10FFFF except U+000A, U+0022, U+005C>
+/// SQ ::= <U+0027>
+/// stringbodySQ ::= escape | characterSQ
+/// characterSQ ::= <U+0000..U+10FFFF except U+000A, U+0027, U+005C>
+/// escape ::= <U+005C> ("b" | "f" | "n" | "r" | "t" | symbol | unicode)
+/// symbol ::= <U+0022, U+0027, U+005C, or U+002F>
+/// unicode ::= "u" hex hex hex hex
+/// hex ::= <a character in the ranges A-F, a-f, or 0-9>
+/// ```
+///
+/// Strings are double-quoted (U+0022, ") or single-quoted (U+0027, ') sequences
+/// of zero or more Unicode characters that do not include a newline, the quote
+/// character used, or the backslash character, mixed with zero or more escapes.
+///
+/// Escapes are a backslash character followed another character, as follows:
+///
+///  * `\b`: represents U+0008
+///  * `\f`: represents U+000C
+///  * `\n`: represents U+000A
+///  * `\r`: represents U+000D
+///  * `\t`: represents U+0009
+///  * `\uXXXX`: represents the UTF-16 codepoint with code XXXX.
+///
+/// Characters outside the basic multilingual plane must be represented either
+/// as literal characters, or as two escapes identifying UTF-16 surrogate pairs.
+/// (This is for compatibility with JSON.)
+///
+/// ```bnf
+/// integer ::= "-"? decimal | hexadecimal
+/// decimal ::= digit+
+/// hexadecimal ::= ("0x" | "0X") hex+
+/// ```
+///
+/// The "digit" (0-9) and "hex" (0-9, A-F, a-f) terminals are described earlier.
+///
+/// In the "decimal" form, the digits represent their value in base ten. A
+/// leading hyphen indicates the number is negative. In the "hexadecimal" form,
+/// the number is always positive, and is represented by the hex digits
+/// interpreted in base sixteen.
+///
+/// The numbers represented must be in the range -9,223,372,036,854,775,808 to
+/// 9,223,372,036,854,775,807.
+///
+/// ```
+/// double ::= "-"? digit+ ("." digit+)? (("e" | "E") "-"? digit+)?
+/// ```
+///
+/// Floating point numbers are represented by an optional negative sign
+/// indicating the number is negative, a significand with optional fractional
+/// component in the form of digits in base ten giving the integer component
+/// followed optionally by a decimal point and further base ten digits giving
+/// the fractional component, and an exponent which itself is represented by an
+/// optional negative sign indicating a negative exponent and a sequence of
+/// digits giving the base ten exponent itself.
+///
+/// The numbers represented must be values that can be expressed in the IEEE754
+/// binary64 format.
+///
+/// ```bnf
+/// WS ::= ( <U+0020> | <U+000A> | "//" comment* <U+000A or EOF> )
+/// comment ::= <U+0000..U+10FFFF except U+000A>
+/// ```
+///
+/// The `WS` token is used to represent where whitespace and comments are
+/// allowed.
+///
+/// See also:
+///
+///  * [parseLibraryFile], which uses a superset of this format to decode
+///    Remote Flutter Widgets text library files.
+///  * [decodeDataBlob], which decodes the binary variant of this format.
+DynamicMap parseDataFile(String file) {
+  final _Parser parser = _Parser(_tokenize(file));
+  return parser.readDataFile();
+}
+
+/// Parses a Remote Flutter Widgets text library file.
+///
+/// Remote widget libraries are usually used in conjunction with a [Runtime].
+///
+/// Parsing this format is about ten times slower than parsing the binary
+/// variant; see [decodeLibraryBlob]. As such it is strongly discouraged,
+/// especially in resource-constrained contexts like mobile applications.
+///
+/// ## Format
+///
+/// The format is a superset of the format defined by [parseDataFile].
+///
+/// Remote Flutter Widgets text library files consist of a list of imports
+/// followed by a list of widget declarations.
+///
+/// ### Imports
+///
+/// A remote widget library file is identified by a name which consists of
+/// several parts, which are by convention expressed separated by periods; for
+/// example, `core.widgets` or `net.example.x`.
+///
+/// A library's name is specified when the library is provided to the runtime
+/// using [Runtime.update].
+///
+/// A remote widget library depends on one or more other libraries that define
+/// the widgets that the primary library depends on. These dependencies can
+/// themselves be remote widget libraries, for example describing commonly-used
+/// widgets like branded buttons, or "local widget libraries" which are declared
+/// and hard-coded in the client itself and that provide a way to reference
+/// actual Flutter widgets (see [LocalWidgetLibrary]).
+///
+/// The Remote Flutter Widgets package ships with two local widget libraries,
+/// usually given the names `core.widgets` (see [createCoreWidgets]) and
+/// `core.material` (see [createMaterialWidgets]). An application can declare
+/// other local widget libraries for use by their remote widgets. These could
+/// correspond to UI controls, e.g. branded widgets used by other parts of the
+/// application, or to complete experiences, e.g. core parts of the application.
+/// For example, a blogging application might use Remote Flutter Widgets to
+/// represent the CRM parts of the experience, with the rich text editor being
+/// implemented on the client as a custom widget exposed to the remote libraries
+/// as a widget in a local widget library.
+///
+/// A library lists the other libraries that it depends on by name. When a
+/// widget is referenced, it is looked up by name first by examining the widget
+/// declarations in the file itself, then by examining the declarations of each
+/// dependency in turn, in a depth-first search.
+///
+/// It is an error for there to be a loop in the imports.
+///
+/// Imports have this form:
+///
+/// ```
+/// import library.name;
+/// ```
+///
+/// For example:
+///
+/// ```
+/// import core.widgets;
+/// ```
+///
+/// ### Widget declarations
+///
+/// The primary purpose of a remote widget library is to provide widget
+/// declarations. Each declaration defines a new widget. Widgets are defined in
+/// terms of other widgets, like stateless and stateful widgets in Flutter
+/// itself. As such, a widget declaration consists of a widget constructor call.
+///
+/// The widget declarations come after the imports.
+///
+/// To declare a widget named A in terms of a widget B, the following form is used:
+///
+/// ```
+/// widget A = B();
+/// ```
+///
+/// This declares a widget A, whose implementation is simply to use widget B.
+///
+/// If the widget A is to be stateful, a map is inserted before the equals sign:
+///
+/// ```
+/// widget A { } = B();
+/// ```
+///
+/// The map describes the default values of the state. For example, a button
+/// might have a "down" state, which is initially false:
+///
+/// ```
+/// widget Button { down: false } = Container();
+/// ```
+///
+/// _See the section on State below._
+///
+/// ### Widget constructor calls
+///
+/// A widget constructor call is an invocation of a remote or local widget
+/// declaration, along with its arguments. Arguments are a map of key-value
+/// pairs, where the values can be any of the types in the data model defined
+/// above plus any of the types defined below in this section, such as
+/// references to arguments, the data model, loops, state, switches, or
+/// event handlers.
+///
+/// In this example, several constructor calls are nested together:
+///
+/// ```
+/// widget Foo = Column(
+///   children: [
+///     Container(
+///       child: Text(text: "Hello"),
+///     ),
+///   ],
+/// );
+/// ```
+///
+/// The `Foo` widget is defined to create a `Column` widget. The `Column(...)`
+/// is a constructor call with one argument, named `children`, whose value is a
+/// list which itself contains a single constructor call, to `Container`. That
+/// constructor call also has only one argument, `child`, whose value, again, is
+/// a constructor call, in this case creating a `Text` widget.
+///
+/// ### References
+///
+/// Remote widget libraries typically contain _references_, e.g. to the
+/// arguments of a widget, or to the [DynamicContent] data, or to a stateful
+/// widget's state.
+///
+/// The various kinds of references all have the same basic pattern, a prefix
+/// followed by period-separated identifiers, strings, or integers. Identifiers
+/// and strings are used to index into maps, while integers are used to index
+/// into lists.
+///
+/// For example, "foo.2.fruit" would reference the key with the value "Kiwi" in
+/// the following structure:
+///
+/// ```c
+/// {
+///   foo: [
+///     { fruit: "Apple" },
+///     { fruit: "Banana" },
+///     { fruit: "Kiwi" }, // foo.2.fruit is the string "Kiwi" here
+///   ],
+///   bar: [ ],
+/// }
+/// ```
+///
+/// Peferences to a widget's arguments use "args" as the first component:
+///
+/// <code>args<i>.foo.bar</i></code>
+///
+/// References to the data model use use "data" as the first component, and
+/// references to state use "state" as the first component:
+///
+/// <code>data<i>.foo.bar</i></code>
+///
+/// <code>state<i>.foo.bar</i></code>
+///
+/// Finally, references to loop variables use the identifier specified in the
+/// loop as the first component:
+///
+/// <code><i>ident.foo.bar</i></code>
+///
+/// #### Argument references
+///
+/// Instead of passing literal values as arguments in widget constructor calls,
+/// a reference to one of the arguments of the remote widget being defined
+/// itself can be provided instead.
+///
+/// For example, suppose one instantiated a widget Foo as follows:
+///
+/// ```
+/// Foo(name: "Bobbins")
+/// ```
+///
+/// ...then in the definition of Foo, one might pass the value of this "name"
+/// argument to another widget, say a Text widget, as follows:
+///
+/// ```
+/// widget Foo = Text(text: args.name);
+/// ```
+///
+/// The arguments can have structure. For example, if the argument passed to Foo
+/// was:
+///
+/// ```
+/// Foo(show: { name: "Cracking the Cryptic", phrase: "Bobbins" })
+/// ```
+///
+/// ...then to specify the leaf node whose value is the string "Bobbins", one
+/// would specify an argument reference consisting of the values "show" and
+/// "phrase", as in `args.show.phrase`. For example:
+///
+/// ```
+/// widget Foo = Text(text: args.show.phrase);
+/// ```
+///
+/// #### Data model references
+///
+/// Instead of passing literal values as arguments in widget constructor calls,
+/// or references to one's own arguments, a reference to one of the nodes in the
+/// data model can be provided instead.
+///
+/// The data model is a tree of maps and lists with leaves formed of integers,
+/// doubles, bools, and strings (see [DynamicContent]). For example, if the data
+/// model looks like this:
+///
+/// ```
+/// { server: { cart: [ { name: "Apple"}, { name: "Banana"} ] }
+/// ```
+///
+/// ...then to specify the leaf node whose value is the string "Banana", one
+/// would specify a data model reference consisting of the values "server",
+/// "cart", 1, and "name", as in `data.server.cart.1.name`. For example:
+///
+/// ```
+/// Text(text: data.server.cart.1.name)
+/// ```
+///
+/// ### Loops
+///
+/// In a list, a loop can be employed to map another list into the host list,
+/// mapping values of the embedded list according to a provided template. Within
+/// the template, references to the value from the embedded list being expanded
+/// can be provided using a loop reference, which is similar to argument and
+/// data references.
+///
+/// A widget that shows all the values from a list in a [ListView] might look
+/// like this:
+///
+/// ```
+/// widget Items = ListView(
+///   children: [
+///     ...for item in args.list:
+///       Text(text: item),
+///   ],
+/// );
+/// ```
+///
+/// Such a widget would be used like this:
+///
+/// ```
+/// Items(list: [ "Hello", "World" ])
+/// ```
+///
+/// The syntax for a loop uses the following form:
+///
+/// <code>...for <i>ident</i> in <i>list</i>: <i>template</i></code>
+/// ```
+///
+/// ...where _ident_ is the identifier to bind to each value in the list, _list_
+/// is some value that evaluates to a list, and _template_ is a value that is to
+/// be evaluated for each item in _list_.
+///
+/// This loop syntax is only valid inside lists.
+///
+/// Loop references use the _ident_. In the example above, that is `item`. In
+/// more elaborate examples, it can include subreferences. For example:
+///
+/// ```
+/// widget Items = ListView(
+///   children: [
+///     Text(text: 'Products:'),
+///     ...for item in args.products:
+///       Text(text: product.name.displayName),
+///     Text(text: 'End of list.'),
+///   ],
+/// );
+/// ```
+///
+/// This might be used as follows:
+///
+/// ```
+/// Items(products: [
+///   { name: { abbreviation: "TI4", displayName: "Twilight Imperium IV" }, price: 120.0 },
+///   { name: { abbreviation: "POK", displayName: "Prophecy of Kings" }, price: 100.0 },
+/// ])
+/// ```
+///
+/// ### State
+///
+/// A widget declaration can say that it has an "initial state", the structure
+/// of which is the same as the data model structure (maps and lists of
+/// primitive types, the root is a map).
+///
+/// Here a button is described as having a "down" state whose first value is
+/// "false":
+///
+/// ```
+/// widget Button { down: false } = Container(
+///   // ...
+/// );
+/// ```
+///
+/// If a widget has state, then it can be referenced in the same way as the
+/// widget's arguments and the data model can be referenced, and it can be
+/// changed using event handlers as described below.
+///
+/// Here, the button's state is referenced (in a pretty nonsensical way;
+/// controlling whether its label wraps based on the value of the state):
+///
+/// ```
+/// widget Button { down: false } = Container(
+///   child: Text(text: 'Hello World', softWrap: state.down),
+/// );
+/// ```
+///
+/// State is usually used with Switches and state-setting handlers.
+///
+/// ### Switches
+///
+/// Anywhere in a widget declaration, a switch can be employed to change the
+/// evaluated value used at runtime. A switch has a value that is being used to
+/// control the switch, and then a series of cases with values to use if the
+/// control value matches the case value. A default can be provided.
+///
+/// The control value is usually a reference to arguments, data, state, or a
+/// loop variable.
+///
+/// The syntax for a switch uses the following form:
+///
+/// ```
+/// switch value {
+///   case1: template1,
+///   case2: template2,
+///   case3: template3,
+///   // ...
+///   default: templateD,
+/// }
+/// ```
+///
+/// ...where _value_ is the control value that will be compared to each case,
+/// the _caseX_ values are the values to which the control value is compared,
+/// _templateX_ are the templates to use, and _templateD_ is the default
+/// template to use if none of the cases can be met. Any number of cases can be
+/// specified; the template of the first one that exactly matches the given
+/// control value is the one that is used. The default entry is optional. If no
+/// value matches and there is no default, the switch evaluates to the "missing"
+/// value (null).
+///
+/// Extending the earlier button, this would move the margin around so that it
+/// appeared pressed when the "down" state was true (but note that we still
+/// don't have anything to toggle that state!):
+///
+/// ```
+/// widget Button { down: false } = Container(
+///   margin: switch state.down {
+///     false: [ 0.0, 0.0, 8.0, 8.0 ],
+///     true: [ 8.0, 8.0, 0.0, 0.0 ],
+///   },
+///   decoration: { type: "box", border: [ {} ] },
+///   child: args.child,
+/// );
+/// ```
+///
+/// ### Event handlers
+///
+/// There are two kinds of event handlers: those that signal an event for the
+/// host to handle (potentially by forwarding it to a server), and those that
+/// change the widget's state.
+///
+/// Signalling event handlers have a name and an arguments map:
+///
+/// ```
+/// event "..." { }
+/// ```
+///
+/// Tthe string is the name of the event, and the arguments map is the data to
+/// send with the event.
+///
+/// For example, the event handler in the following sequence sends the event
+/// called "hello" with a map containing just one key, "id", whose value is 1:
+///
+/// ```
+/// Button(
+///   onPressed: event "hello" { id: 1 },
+///   child: Text(text: "Greetings"),
+/// );
+/// ```
+///
+/// Event handlers that set state have a reference to a state, and a new value
+/// to assign to that state. Such handlers are only meaningful within widgets
+/// that have state, as described above. They have this form:
+///
+/// ```
+/// set state.foo.bar = value
+/// ```
+///
+/// The `state.foo.bar` part is a state reference (which must identify a part of
+/// the state that exists), and `value` is the new value to assign to that state.
+///
+/// This lets us finish the earlier button:
+///
+/// ```
+/// widget Button { down: false } = GestureDetector(
+///   onTapDown: set state.down = true,
+///   onTapUp: set state.down = false,
+///   onTapCancel: set state.down = false,
+///   onTap: args.onPressed,
+///   child: Container(
+///     margin: switch state.down {
+///       false: [ 0.0, 0.0, 8.0, 8.0 ],
+///       true: [ 8.0, 8.0, 0.0, 0.0 ],
+///     },
+///     decoration: { type: "box", border: [ {} ] },
+///     child: args.child,
+///   ),
+/// );
+/// ```
+///
+/// See also:
+///
+///  * [encodeLibraryBlob], which encodes the output of this method
+///    into the binary variant of this format.
+///  * [parseDataFile], which uses a subset of this format to decode
+///    Remote Flutter Widgets text data files.
+///  * [decodeLibraryBlob], which decodes the binary variant of this format.
+RemoteWidgetLibrary parseLibraryFile(String file) {
+  final _Parser parser = _Parser(_tokenize(file));
+  return parser.readLibraryFile();
+}
+
+const Set<String> _reservedWords = <String>{
+  'args',
+  'data',
+  'event',
+  'false',
+  'set',
+  'state',
+  'true',
+};
+
+abstract class _Token {
+  _Token(this.line, this.column);
+  final int line;
+  final int column;
+}
+
+class _SymbolToken extends _Token {
+  _SymbolToken(this.symbol, int line, int column): super(line, column);
+  final int symbol;
+
+  static const int dot = 0x2E;
+  static const int tripleDot = 0x2026;
+  static const int openParen = 0x28; // U+0028 LEFT PARENTHESIS character (()
+  static const int closeParen = 0x29; // U+0029 RIGHT PARENTHESIS character ())
+  static const int comma = 0x2C; // U+002C COMMA character (,)
+  static const int colon = 0x3A; // U+003A COLON character (:)
+  static const int semicolon = 0x3B; // U+003B SEMICOLON character (;)
+  static const int equals = 0x3D; // U+003D EQUALS SIGN character (=)
+  static const int openBracket = 0x5B; // U+005B LEFT SQUARE BRACKET character ([)
+  static const int closeBracket = 0x5D; // U+005D RIGHT SQUARE BRACKET character (])
+  static const int openBrace = 0x7B; // U+007B LEFT CURLY BRACKET character ({)
+  static const int closeBrace = 0x7D; // U+007D RIGHT CURLY BRACKET character (})
+
+  @override
+  String toString() => String.fromCharCode(symbol);
+}
+
+class _IntegerToken extends _Token {
+  _IntegerToken(this.value, int line, int column): super(line, column);
+  final int value;
+
+  @override
+  String toString() => '$value';
+}
+
+class _DoubleToken extends _Token {
+  _DoubleToken(this.value, int line, int column): super(line, column);
+  final double value;
+
+  @override
+  String toString() => '$value';
+}
+
+class _IdentifierToken extends _Token {
+  _IdentifierToken(this.value, int line, int column): super(line, column);
+  final String value;
+
+  @override
+  String toString() => value;
+}
+
+class _StringToken extends _Token {
+  _StringToken(this.value, int line, int column): super(line, column);
+  final String value;
+
+  @override
+  String toString() => '"$value"';
+}
+
+class _EofToken extends _Token {
+  _EofToken(int line, int column): super(line, column);
+
+  @override
+  String toString() => '<EOF>';
+}
+
+/// Indicates that there an error was detected while parsing a file.
+///
+/// This is used by [parseDataFile] and [parseLibraryFile] to indicate that the
+/// given file has a syntax or semantic error.
+///
+/// The [line] and [column] describe how far the parser had reached when the
+/// error was detected (this may not precisely indicate the actual location of
+/// the error).
+///
+/// The [message] property is a human-readable string describing the error.
+class ParserException implements Exception {
+  /// Create an instance of [ParserException].
+  ///
+  /// The arguments must not be null. See [message] for details on
+  /// the expected syntax of the error description.
+  const ParserException(this.message, this.line, this.column);
+
+  factory ParserException._fromToken(String message, _Token token) {
+    return ParserException(message, token.line, token.column);
+  }
+
+  factory ParserException._expected(String what, _Token token) {
+    return ParserException('Expected $what but found $token', token.line, token.column);
+  }
+
+  factory ParserException._unexpected(_Token token) {
+    return ParserException('Unexpected $token', token.line, token.column);
+  }
+
+  /// The error that was detected by the parser.
+  ///
+  /// This should be the start of a sentence which will make sense when " at
+  /// line ... column ..." is appended. So, for example, it should not end with
+  /// a period.
+  final String message;
+
+  /// The line number (using 1-based indexing) that the parser had reached when
+  /// the error was detected.
+  final int line;
+
+  /// The column number (using 1-based indexing) that the parser had reached
+  /// when the error was detected.
+  ///
+  /// This is measured in UTF-16 code units, even if the source file was UTF-8.
+  final int column;
+
+  @override
+  String toString() => '$message at line $line column $column.';
+}
+
+enum _TokenizerMode {
+  main,
+  minus,
+  zero,
+  minusInteger,
+  integer,
+  integerOnly,
+  numericDot,
+  fraction,
+  e,
+  negativeExponent,
+  exponent,
+  x,
+  hex,
+  dot1,
+  dot2,
+  identifier,
+  quote,
+  doubleQuote,
+  quoteEscape,
+  quoteEscapeUnicode1,
+  quoteEscapeUnicode2,
+  quoteEscapeUnicode3,
+  quoteEscapeUnicode4,
+  endQuote,
+  doubleQuoteEscape,
+  doubleQuoteEscapeUnicode1,
+  doubleQuoteEscapeUnicode2,
+  doubleQuoteEscapeUnicode3,
+  doubleQuoteEscapeUnicode4,
+  endDoubleQuote,
+  slash,
+  comment,
+}
+
+String _describeRune(int current) {
+  assert(current >= 0);
+  assert(current < 0x10FFFF);
+  if (current > 0x20) {
+    return 'U+${current.toRadixString(16).toUpperCase().padLeft(4, "0")} ("${String.fromCharCode(current)}")';
+  }
+  return 'U+${current.toRadixString(16).toUpperCase().padLeft(4, "0")}';
+}
+
+Iterable<_Token> _tokenize(String file) sync* {
+  final List<int> characters = file.runes.toList();
+  int index = 0;
+  int line = 1;
+  int column = 0;
+  final List<int> buffer = <int>[];
+  final List<int> buffer2 = <int>[];
+  _TokenizerMode mode = _TokenizerMode.main;
+  while (true) {
+    final int current;
+    if (index >= characters.length) {
+      current = -1;
+    } else {
+      current = characters[index];
+      if (current == 0x0A) {
+        line += 1;
+        column = 0;
+      } else {
+        column += 1;
+      }
+    }
+    switch (mode) {
+
+      case _TokenizerMode.main:
+        switch (current) {
+          case -1:
+            yield _EofToken(line, column);
+            return;
+          case 0x0A: // U+000A LINE FEED (LF)
+          case 0x20: // U+0020 SPACE character
+            break;
+          case 0x28: // U+0028 LEFT PARENTHESIS character (()
+          case 0x29: // U+0029 RIGHT PARENTHESIS character ())
+          case 0x2C: // U+002C COMMA character (,)
+          case 0x3A: // U+003A COLON character (:)
+          case 0x3B: // U+003B SEMICOLON character (;)
+          case 0x3D: // U+003D EQUALS SIGN character (=)
+          case 0x5B: // U+005B LEFT SQUARE BRACKET character ([)
+          case 0x5D: // U+005D RIGHT SQUARE BRACKET character (])
+          case 0x7B: // U+007B LEFT CURLY BRACKET character ({)
+          case 0x7D: // U+007D RIGHT CURLY BRACKET character (})
+            yield _SymbolToken(current, line, column);
+            break;
+          case 0x22: // U+0022 QUOTATION MARK character (")
+            assert(buffer.isEmpty);
+            mode = _TokenizerMode.doubleQuote;
+            break;
+          case 0x27: // U+0027 APOSTROPHE character (')
+            assert(buffer.isEmpty);
+            mode = _TokenizerMode.quote;
+            break;
+          case 0x2D: // U+002D HYPHEN-MINUS character (-)
+            assert(buffer.isEmpty);
+            mode = _TokenizerMode.minus;
+            buffer.add(current);
+            break;
+          case 0x2E: // U+002E FULL STOP character (.)
+            mode = _TokenizerMode.dot1;
+            break;
+          case 0x2F: // U+002F SOLIDUS character (/)
+            mode = _TokenizerMode.slash;
+            break;
+          case 0x30: // U+0030 DIGIT ZERO character (0)
+            assert(buffer.isEmpty);
+            mode = _TokenizerMode.zero;
+            buffer.add(current);
+            break;
+          case 0x31: // U+0031 DIGIT ONE character (1)
+          case 0x32: // U+0032 DIGIT TWO character (2)
+          case 0x33: // U+0033 DIGIT THREE character (3)
+          case 0x34: // U+0034 DIGIT FOUR character (4)
+          case 0x35: // U+0035 DIGIT FIVE character (5)
+          case 0x36: // U+0036 DIGIT SIX character (6)
+          case 0x37: // U+0037 DIGIT SEVEN character (7)
+          case 0x38: // U+0038 DIGIT EIGHT character (8)
+          case 0x39: // U+0039 DIGIT NINE character (9)
+            assert(buffer.isEmpty);
+            mode = _TokenizerMode.integer;
+            buffer.add(current);
+            break;
+          case 0x41: // U+0041 LATIN CAPITAL LETTER A character
+          case 0x42: // U+0042 LATIN CAPITAL LETTER B character
+          case 0x43: // U+0043 LATIN CAPITAL LETTER C character
+          case 0x44: // U+0044 LATIN CAPITAL LETTER D character
+          case 0x45: // U+0045 LATIN CAPITAL LETTER E character
+          case 0x46: // U+0046 LATIN CAPITAL LETTER F character
+          case 0x47: // U+0047 LATIN CAPITAL LETTER G character
+          case 0x48: // U+0048 LATIN CAPITAL LETTER H character
+          case 0x49: // U+0049 LATIN CAPITAL LETTER I character
+          case 0x4A: // U+004A LATIN CAPITAL LETTER J character
+          case 0x4B: // U+004B LATIN CAPITAL LETTER K character
+          case 0x4C: // U+004C LATIN CAPITAL LETTER L character
+          case 0x4D: // U+004D LATIN CAPITAL LETTER M character
+          case 0x4E: // U+004E LATIN CAPITAL LETTER N character
+          case 0x4F: // U+004F LATIN CAPITAL LETTER O character
+          case 0x50: // U+0050 LATIN CAPITAL LETTER P character
+          case 0x51: // U+0051 LATIN CAPITAL LETTER Q character
+          case 0x52: // U+0052 LATIN CAPITAL LETTER R character
+          case 0x53: // U+0053 LATIN CAPITAL LETTER S character
+          case 0x54: // U+0054 LATIN CAPITAL LETTER T character
+          case 0x55: // U+0055 LATIN CAPITAL LETTER U character
+          case 0x56: // U+0056 LATIN CAPITAL LETTER V character
+          case 0x57: // U+0057 LATIN CAPITAL LETTER W character
+          case 0x58: // U+0058 LATIN CAPITAL LETTER X character
+          case 0x59: // U+0059 LATIN CAPITAL LETTER Y character
+          case 0x5A: // U+005A LATIN CAPITAL LETTER Z character
+          case 0x61: // U+0061 LATIN SMALL LETTER A character
+          case 0x62: // U+0062 LATIN SMALL LETTER B character
+          case 0x63: // U+0063 LATIN SMALL LETTER C character
+          case 0x64: // U+0064 LATIN SMALL LETTER D character
+          case 0x65: // U+0065 LATIN SMALL LETTER E character
+          case 0x66: // U+0066 LATIN SMALL LETTER F character
+          case 0x67: // U+0067 LATIN SMALL LETTER G character
+          case 0x68: // U+0068 LATIN SMALL LETTER H character
+          case 0x69: // U+0069 LATIN SMALL LETTER I character
+          case 0x6A: // U+006A LATIN SMALL LETTER J character
+          case 0x6B: // U+006B LATIN SMALL LETTER K character
+          case 0x6C: // U+006C LATIN SMALL LETTER L character
+          case 0x6D: // U+006D LATIN SMALL LETTER M character
+          case 0x6E: // U+006E LATIN SMALL LETTER N character
+          case 0x6F: // U+006F LATIN SMALL LETTER O character
+          case 0x70: // U+0070 LATIN SMALL LETTER P character
+          case 0x71: // U+0071 LATIN SMALL LETTER Q character
+          case 0x72: // U+0072 LATIN SMALL LETTER R character
+          case 0x73: // U+0073 LATIN SMALL LETTER S character
+          case 0x74: // U+0074 LATIN SMALL LETTER T character
+          case 0x75: // U+0075 LATIN SMALL LETTER U character
+          case 0x76: // U+0076 LATIN SMALL LETTER V character
+          case 0x77: // U+0077 LATIN SMALL LETTER W character
+          case 0x78: // U+0078 LATIN SMALL LETTER X character
+          case 0x79: // U+0079 LATIN SMALL LETTER Y character
+          case 0x7A: // U+007A LATIN SMALL LETTER Z character
+          case 0x5F: // U+005F LOW LINE character (_)
+            assert(buffer.isEmpty);
+            mode = _TokenizerMode.identifier;
+            buffer.add(current);
+            break;
+          default:
+            throw ParserException('Unexpected character ${_describeRune(current)}', line, column);
+        }
+        break;
+
+      case _TokenizerMode.minus: // "-"
+        assert(buffer.length == 1 && buffer[0] == 0x2D);
+        switch (current) {
+          case -1:
+            throw ParserException('Unexpected end of file after minus sign', line, column);
+          case 0x30: // U+0030 DIGIT ZERO character (0)
+          case 0x31: // U+0031 DIGIT ONE character (1)
+          case 0x32: // U+0032 DIGIT TWO character (2)
+          case 0x33: // U+0033 DIGIT THREE character (3)
+          case 0x34: // U+0034 DIGIT FOUR character (4)
+          case 0x35: // U+0035 DIGIT FIVE character (5)
+          case 0x36: // U+0036 DIGIT SIX character (6)
+          case 0x37: // U+0037 DIGIT SEVEN character (7)
+          case 0x38: // U+0038 DIGIT EIGHT character (8)
+          case 0x39: // U+0039 DIGIT NINE character (9)
+            mode = _TokenizerMode.minusInteger;
+            buffer.add(current);
+            break;
+          default:
+            throw ParserException('Unexpected character ${_describeRune(current)} after minus sign (expected digit)', line, column);
+        }
+        break;
+
+      case _TokenizerMode.zero: // "0"
+        assert(buffer.length == 1 && buffer[0] == 0x30);
+        switch (current) {
+          case -1:
+            yield _IntegerToken(0, line, column);
+            yield _EofToken(line, column);
+            return;
+          case 0x0A: // U+000A LINE FEED (LF)
+          case 0x20: // U+0020 SPACE character
+            yield _IntegerToken(0, line, column);
+            buffer.clear();
+            mode = _TokenizerMode.main;
+            break;
+          case 0x28: // U+0028 LEFT PARENTHESIS character (()
+          case 0x29: // U+0029 RIGHT PARENTHESIS character ())
+          case 0x2C: // U+002C COMMA character (,)
+          case 0x3A: // U+003A COLON character (:)
+          case 0x3B: // U+003B SEMICOLON character (;)
+          case 0x3D: // U+003D EQUALS SIGN character (=)
+          case 0x5B: // U+005B LEFT SQUARE BRACKET character ([)
+          case 0x5D: // U+005D RIGHT SQUARE BRACKET character (])
+          case 0x7B: // U+007B LEFT CURLY BRACKET character ({)
+          case 0x7D: // U+007D RIGHT CURLY BRACKET character (})
+            yield _IntegerToken(0, line, column);
+            buffer.clear();
+            yield _SymbolToken(current, line, column);
+            mode = _TokenizerMode.main;
+            break;
+          case 0x2E: // U+002E FULL STOP character (.)
+            mode = _TokenizerMode.numericDot;
+            buffer.add(current);
+            break;
+          case 0x30: // U+0030 DIGIT ZERO character (0)
+          case 0x31: // U+0031 DIGIT ONE character (1)
+          case 0x32: // U+0032 DIGIT TWO character (2)
+          case 0x33: // U+0033 DIGIT THREE character (3)
+          case 0x34: // U+0034 DIGIT FOUR character (4)
+          case 0x35: // U+0035 DIGIT FIVE character (5)
+          case 0x36: // U+0036 DIGIT SIX character (6)
+          case 0x37: // U+0037 DIGIT SEVEN character (7)
+          case 0x38: // U+0038 DIGIT EIGHT character (8)
+          case 0x39: // U+0039 DIGIT NINE character (9)
+            mode = _TokenizerMode.integer;
+            buffer.add(current);
+            break;
+          case 0x45: // U+0045 LATIN CAPITAL LETTER E character
+          case 0x65: // U+0065 LATIN SMALL LETTER E character
+            mode = _TokenizerMode.e;
+            buffer.add(current);
+            break;
+          case 0x58: // U+0058 LATIN CAPITAL LETTER X character
+          case 0x78: // U+0078 LATIN SMALL LETTER X character
+            mode = _TokenizerMode.x;
+            buffer.clear();
+            break;
+          default:
+            throw ParserException('Unexpected character ${_describeRune(current)} after zero', line, column);
+        }
+        break;
+
+      case _TokenizerMode.minusInteger: // "-0"
+        switch (current) {
+          case -1:
+            yield _IntegerToken(int.parse(String.fromCharCodes(buffer), radix: 10), line, column);
+            yield _EofToken(line, column);
+            return;
+          case 0x0A: // U+000A LINE FEED (LF)
+          case 0x20: // U+0020 SPACE character
+            yield _IntegerToken(int.parse(String.fromCharCodes(buffer), radix: 10), line, column);
+            buffer.clear();
+            mode = _TokenizerMode.main;
+            break;
+          case 0x28: // U+0028 LEFT PARENTHESIS character (()
+          case 0x29: // U+0029 RIGHT PARENTHESIS character ())
+          case 0x2C: // U+002C COMMA character (,)
+          case 0x3A: // U+003A COLON character (:)
+          case 0x3B: // U+003B SEMICOLON character (;)
+          case 0x3D: // U+003D EQUALS SIGN character (=)
+          case 0x5B: // U+005B LEFT SQUARE BRACKET character ([)
+          case 0x5D: // U+005D RIGHT SQUARE BRACKET character (])
+          case 0x7B: // U+007B LEFT CURLY BRACKET character ({)
+          case 0x7D: // U+007D RIGHT CURLY BRACKET character (})
+            yield _IntegerToken(int.parse(String.fromCharCodes(buffer), radix: 10), line, column);
+            buffer.clear();
+            yield _SymbolToken(current, line, column);
+            mode = _TokenizerMode.main;
+            break;
+          case 0x2E: // U+002E FULL STOP character (.)
+            mode = _TokenizerMode.numericDot;
+            buffer.add(current);
+            break;
+          case 0x30: // U+0030 DIGIT ZERO character (0)
+          case 0x31: // U+0031 DIGIT ONE character (1)
+          case 0x32: // U+0032 DIGIT TWO character (2)
+          case 0x33: // U+0033 DIGIT THREE character (3)
+          case 0x34: // U+0034 DIGIT FOUR character (4)
+          case 0x35: // U+0035 DIGIT FIVE character (5)
+          case 0x36: // U+0036 DIGIT SIX character (6)
+          case 0x37: // U+0037 DIGIT SEVEN character (7)
+          case 0x38: // U+0038 DIGIT EIGHT character (8)
+          case 0x39: // U+0039 DIGIT NINE character (9)
+            mode = _TokenizerMode.integer;
+            buffer.add(current);
+            break;
+          case 0x45: // U+0045 LATIN CAPITAL LETTER E character
+          case 0x65: // U+0065 LATIN SMALL LETTER E character
+            mode = _TokenizerMode.e;
+            buffer.add(current);
+            break;
+          default:
+            throw ParserException('Unexpected character ${_describeRune(current)} after negative zero', line, column);
+        }
+        break;
+
+      case _TokenizerMode.integer: // "00", "1", "-00"
+        switch (current) {
+          case -1:
+            yield _IntegerToken(int.parse(String.fromCharCodes(buffer), radix: 10), line, column);
+            buffer.clear();
+            yield _EofToken(line, column);
+            return;
+          case 0x0A: // U+000A LINE FEED (LF)
+          case 0x20: // U+0020 SPACE character
+            yield _IntegerToken(int.parse(String.fromCharCodes(buffer), radix: 10), line, column);
+            buffer.clear();
+            mode = _TokenizerMode.main;
+            break;
+          case 0x28: // U+0028 LEFT PARENTHESIS character (()
+          case 0x29: // U+0029 RIGHT PARENTHESIS character ())
+          case 0x2C: // U+002C COMMA character (,)
+          case 0x3A: // U+003A COLON character (:)
+          case 0x3B: // U+003B SEMICOLON character (;)
+          case 0x3D: // U+003D EQUALS SIGN character (=)
+          case 0x5B: // U+005B LEFT SQUARE BRACKET character ([)
+          case 0x5D: // U+005D RIGHT SQUARE BRACKET character (])
+          case 0x7B: // U+007B LEFT CURLY BRACKET character ({)
+          case 0x7D: // U+007D RIGHT CURLY BRACKET character (})
+            yield _IntegerToken(int.parse(String.fromCharCodes(buffer), radix: 10), line, column);
+            buffer.clear();
+            yield _SymbolToken(current, line, column);
+            mode = _TokenizerMode.main;
+            break;
+          case 0x2E: // U+002E FULL STOP character (.)
+            mode = _TokenizerMode.numericDot;
+            buffer.add(current);
+            break;
+          case 0x30: // U+0030 DIGIT ZERO character (0)
+          case 0x31: // U+0031 DIGIT ONE character (1)
+          case 0x32: // U+0032 DIGIT TWO character (2)
+          case 0x33: // U+0033 DIGIT THREE character (3)
+          case 0x34: // U+0034 DIGIT FOUR character (4)
+          case 0x35: // U+0035 DIGIT FIVE character (5)
+          case 0x36: // U+0036 DIGIT SIX character (6)
+          case 0x37: // U+0037 DIGIT SEVEN character (7)
+          case 0x38: // U+0038 DIGIT EIGHT character (8)
+          case 0x39: // U+0039 DIGIT NINE character (9)
+            buffer.add(current);
+            break;
+          case 0x45: // U+0045 LATIN CAPITAL LETTER E character
+          case 0x65: // U+0065 LATIN SMALL LETTER E character
+            mode = _TokenizerMode.e;
+            buffer.add(current);
+            break;
+          default:
+            throw ParserException('Unexpected character ${_describeRune(current)}', line, column);
+        }
+        break;
+
+      case _TokenizerMode.integerOnly:
+        switch (current) {
+          case -1:
+            yield _IntegerToken(int.parse(String.fromCharCodes(buffer), radix: 10), line, column);
+            buffer.clear();
+            yield _EofToken(line, column);
+            return;
+          case 0x0A: // U+000A LINE FEED (LF)
+          case 0x20: // U+0020 SPACE character
+            yield _IntegerToken(int.parse(String.fromCharCodes(buffer), radix: 10), line, column);
+            buffer.clear();
+            mode = _TokenizerMode.main;
+            break;
+          case 0x28: // U+0028 LEFT PARENTHESIS character (()
+          case 0x29: // U+0029 RIGHT PARENTHESIS character ())
+          case 0x2C: // U+002C COMMA character (,)
+          case 0x3A: // U+003A COLON character (:)
+          case 0x3B: // U+003B SEMICOLON character (;)
+          case 0x3D: // U+003D EQUALS SIGN character (=)
+          case 0x5B: // U+005B LEFT SQUARE BRACKET character ([)
+          case 0x5D: // U+005D RIGHT SQUARE BRACKET character (])
+          case 0x7B: // U+007B LEFT CURLY BRACKET character ({)
+          case 0x7D: // U+007D RIGHT CURLY BRACKET character (})
+            yield _IntegerToken(int.parse(String.fromCharCodes(buffer), radix: 10), line, column);
+            buffer.clear();
+            yield _SymbolToken(current, line, column);
+            mode = _TokenizerMode.main;
+            break;
+          case 0x2E: // U+002E FULL STOP character (.)
+            yield _IntegerToken(int.parse(String.fromCharCodes(buffer), radix: 10), line, column);
+            buffer.clear();
+            mode = _TokenizerMode.dot1;
+            break;
+          case 0x30: // U+0030 DIGIT ZERO character (0)
+          case 0x31: // U+0031 DIGIT ONE character (1)
+          case 0x32: // U+0032 DIGIT TWO character (2)
+          case 0x33: // U+0033 DIGIT THREE character (3)
+          case 0x34: // U+0034 DIGIT FOUR character (4)
+          case 0x35: // U+0035 DIGIT FIVE character (5)
+          case 0x36: // U+0036 DIGIT SIX character (6)
+          case 0x37: // U+0037 DIGIT SEVEN character (7)
+          case 0x38: // U+0038 DIGIT EIGHT character (8)
+          case 0x39: // U+0039 DIGIT NINE character (9)
+            buffer.add(current);
+            break;
+          default:
+            throw ParserException('Unexpected character ${_describeRune(current)} in integer', line, column);
+        }
+        break;
+
+      case _TokenizerMode.numericDot: // "0.", "-0.", "00.", "1.", "-00."
+        switch (current) {
+          case -1:
+            throw ParserException('Unexpected end of file after decimal point', line, column);
+          case 0x30: // U+0030 DIGIT ZERO character (0)
+          case 0x31: // U+0031 DIGIT ONE character (1)
+          case 0x32: // U+0032 DIGIT TWO character (2)
+          case 0x33: // U+0033 DIGIT THREE character (3)
+          case 0x34: // U+0034 DIGIT FOUR character (4)
+          case 0x35: // U+0035 DIGIT FIVE character (5)
+          case 0x36: // U+0036 DIGIT SIX character (6)
+          case 0x37: // U+0037 DIGIT SEVEN character (7)
+          case 0x38: // U+0038 DIGIT EIGHT character (8)
+          case 0x39: // U+0039 DIGIT NINE character (9)
+            mode = _TokenizerMode.fraction;
+            buffer.add(current);
+            break;
+          default:
+            throw ParserException('Unexpected character ${_describeRune(current)} in fraction component', line, column);
+        }
+        break;
+
+      case _TokenizerMode.fraction: // "0.0", "-0.0", "00.0", "1.0", "-00.0"
+        switch (current) {
+          case -1:
+            yield _DoubleToken(double.parse(String.fromCharCodes(buffer)), line, column);
+            yield _EofToken(line, column);
+            return;
+          case 0x0A: // U+000A LINE FEED (LF)
+          case 0x20: // U+0020 SPACE character
+            yield _DoubleToken(double.parse(String.fromCharCodes(buffer)), line, column);
+            buffer.clear();
+            mode = _TokenizerMode.main;
+            break;
+          case 0x28: // U+0028 LEFT PARENTHESIS character (()
+          case 0x29: // U+0029 RIGHT PARENTHESIS character ())
+          case 0x2C: // U+002C COMMA character (,)
+          case 0x3A: // U+003A COLON character (:)
+          case 0x3B: // U+003B SEMICOLON character (;)
+          case 0x3D: // U+003D EQUALS SIGN character (=)
+          case 0x5B: // U+005B LEFT SQUARE BRACKET character ([)
+          case 0x5D: // U+005D RIGHT SQUARE BRACKET character (])
+          case 0x7B: // U+007B LEFT CURLY BRACKET character ({)
+          case 0x7D: // U+007D RIGHT CURLY BRACKET character (})
+            yield _DoubleToken(double.parse(String.fromCharCodes(buffer)), line, column);
+            buffer.clear();
+            yield _SymbolToken(current, line, column);
+            mode = _TokenizerMode.main;
+            break;
+          case 0x30: // U+0030 DIGIT ZERO character (0)
+          case 0x31: // U+0031 DIGIT ONE character (1)
+          case 0x32: // U+0032 DIGIT TWO character (2)
+          case 0x33: // U+0033 DIGIT THREE character (3)
+          case 0x34: // U+0034 DIGIT FOUR character (4)
+          case 0x35: // U+0035 DIGIT FIVE character (5)
+          case 0x36: // U+0036 DIGIT SIX character (6)
+          case 0x37: // U+0037 DIGIT SEVEN character (7)
+          case 0x38: // U+0038 DIGIT EIGHT character (8)
+          case 0x39: // U+0039 DIGIT NINE character (9)
+            buffer.add(current);
+            break;
+          case 0x45: // U+0045 LATIN CAPITAL LETTER E character
+          case 0x65: // U+0065 LATIN SMALL LETTER E character
+            mode = _TokenizerMode.e;
+            buffer.add(current);
+            break;
+          default:
+            throw ParserException('Unexpected character ${_describeRune(current)} in fraction component', line, column);
+        }
+        break;
+
+      case _TokenizerMode.e: // "0e", "-0e", "00e", "1e", "-00e", "0.0e", "-0.0e", "00.0e", "1.0e", "-00.0e"
+        switch (current) {
+          case -1:
+            throw ParserException('Unexpected end of file after exponent separator', line, column);
+          case 0x2D: // U+002D HYPHEN-MINUS character (-)
+            mode = _TokenizerMode.negativeExponent;
+            buffer.add(current);
+            break;
+          case 0x30: // U+0030 DIGIT ZERO character (0)
+          case 0x31: // U+0031 DIGIT ONE character (1)
+          case 0x32: // U+0032 DIGIT TWO character (2)
+          case 0x33: // U+0033 DIGIT THREE character (3)
+          case 0x34: // U+0034 DIGIT FOUR character (4)
+          case 0x35: // U+0035 DIGIT FIVE character (5)
+          case 0x36: // U+0036 DIGIT SIX character (6)
+          case 0x37: // U+0037 DIGIT SEVEN character (7)
+          case 0x38: // U+0038 DIGIT EIGHT character (8)
+          case 0x39: // U+0039 DIGIT NINE character (9)
+            mode = _TokenizerMode.exponent;
+            buffer.add(current);
+            break;
+          default:
+            throw ParserException('Unexpected character ${_describeRune(current)} after exponent separator', line, column);
+        }
+        break;
+
+      case _TokenizerMode.negativeExponent: // "0e-", "-0e-", "00e-", "1e-", "-00e-", "0.0e-", "-0.0e-", "00.0e-", "1.0e-", "-00.0e-"
+        switch (current) {
+          case -1:
+            throw ParserException('Unexpected end of file after exponent separator and minus sign', line, column);
+          case 0x30: // U+0030 DIGIT ZERO character (0)
+          case 0x31: // U+0031 DIGIT ONE character (1)
+          case 0x32: // U+0032 DIGIT TWO character (2)
+          case 0x33: // U+0033 DIGIT THREE character (3)
+          case 0x34: // U+0034 DIGIT FOUR character (4)
+          case 0x35: // U+0035 DIGIT FIVE character (5)
+          case 0x36: // U+0036 DIGIT SIX character (6)
+          case 0x37: // U+0037 DIGIT SEVEN character (7)
+          case 0x38: // U+0038 DIGIT EIGHT character (8)
+          case 0x39: // U+0039 DIGIT NINE character (9)
+            mode = _TokenizerMode.exponent;
+            buffer.add(current);
+            break;
+          default:
+            throw ParserException('Unexpected character ${_describeRune(current)} in exponent', line, column);
+        }
+        break;
+
+      case _TokenizerMode.exponent: // "0e0", "-0e0", "00e0", "1e0", "-00e0", "0.0e0", "-0.0e0", "00.0e0", "1.0e0", "-00.0e0", "0e-0", "-0e-0", "00e-0", "1e-0", "-00e-0", "0.0e-0", "-0.0e-0", "00.0e-0", "1.0e-0", "-00.0e-0"
+        switch (current) {
+          case -1:
+            yield _DoubleToken(double.parse(String.fromCharCodes(buffer)), line, column);
+            yield _EofToken(line, column);
+            return;
+          case 0x0A: // U+000A LINE FEED (LF)
+          case 0x20: // U+0020 SPACE character
+            yield _DoubleToken(double.parse(String.fromCharCodes(buffer)), line, column);
+            buffer.clear();
+            mode = _TokenizerMode.main;
+            break;
+          case 0x28: // U+0028 LEFT PARENTHESIS character (()
+          case 0x29: // U+0029 RIGHT PARENTHESIS character ())
+          case 0x2C: // U+002C COMMA character (,)
+          case 0x3A: // U+003A COLON character (:)
+          case 0x3B: // U+003B SEMICOLON character (;)
+          case 0x3D: // U+003D EQUALS SIGN character (=)
+          case 0x5B: // U+005B LEFT SQUARE BRACKET character ([)
+          case 0x5D: // U+005D RIGHT SQUARE BRACKET character (])
+          case 0x7B: // U+007B LEFT CURLY BRACKET character ({)
+          case 0x7D: // U+007D RIGHT CURLY BRACKET character (})
+            yield _DoubleToken(double.parse(String.fromCharCodes(buffer)), line, column);
+            buffer.clear();
+            yield _SymbolToken(current, line, column);
+            mode = _TokenizerMode.main;
+            break;
+          case 0x30: // U+0030 DIGIT ZERO character (0)
+          case 0x31: // U+0031 DIGIT ONE character (1)
+          case 0x32: // U+0032 DIGIT TWO character (2)
+          case 0x33: // U+0033 DIGIT THREE character (3)
+          case 0x34: // U+0034 DIGIT FOUR character (4)
+          case 0x35: // U+0035 DIGIT FIVE character (5)
+          case 0x36: // U+0036 DIGIT SIX character (6)
+          case 0x37: // U+0037 DIGIT SEVEN character (7)
+          case 0x38: // U+0038 DIGIT EIGHT character (8)
+          case 0x39: // U+0039 DIGIT NINE character (9)
+            buffer.add(current);
+            break;
+          default:
+            throw ParserException('Unexpected character ${_describeRune(current)} in exponent', line, column);
+        }
+        break;
+
+      case _TokenizerMode.x: // "0x", "0X"
+        switch (current) {
+          case -1:
+            throw ParserException('Unexpected end of file after 0x prefix', line, column);
+          case 0x30: // U+0030 DIGIT ZERO character (0)
+          case 0x31: // U+0031 DIGIT ONE character (1)
+          case 0x32: // U+0032 DIGIT TWO character (2)
+          case 0x33: // U+0033 DIGIT THREE character (3)
+          case 0x34: // U+0034 DIGIT FOUR character (4)
+          case 0x35: // U+0035 DIGIT FIVE character (5)
+          case 0x36: // U+0036 DIGIT SIX character (6)
+          case 0x37: // U+0037 DIGIT SEVEN character (7)
+          case 0x38: // U+0038 DIGIT EIGHT character (8)
+          case 0x39: // U+0039 DIGIT NINE character (9)
+          case 0x41: // U+0041 LATIN CAPITAL LETTER A character
+          case 0x42: // U+0042 LATIN CAPITAL LETTER B character
+          case 0x43: // U+0043 LATIN CAPITAL LETTER C character
+          case 0x44: // U+0044 LATIN CAPITAL LETTER D character
+          case 0x45: // U+0045 LATIN CAPITAL LETTER E character
+          case 0x46: // U+0046 LATIN CAPITAL LETTER F character
+          case 0x61: // U+0061 LATIN SMALL LETTER A character
+          case 0x62: // U+0062 LATIN SMALL LETTER B character
+          case 0x63: // U+0063 LATIN SMALL LETTER C character
+          case 0x64: // U+0064 LATIN SMALL LETTER D character
+          case 0x65: // U+0065 LATIN SMALL LETTER E character
+          case 0x66: // U+0066 LATIN SMALL LETTER F character
+            mode = _TokenizerMode.hex;
+            buffer.add(current);
+            break;
+          default:
+            throw ParserException('Unexpected character ${_describeRune(current)} after 0x prefix', line, column);
+        }
+        break;
+
+      case _TokenizerMode.hex:
+        switch (current) {
+          case -1:
+            yield _IntegerToken(int.parse(String.fromCharCodes(buffer), radix: 16), line, column);
+            yield _EofToken(line, column);
+            return;
+          case 0x0A: // U+000A LINE FEED (LF)
+          case 0x20: // U+0020 SPACE character
+            yield _IntegerToken(int.parse(String.fromCharCodes(buffer), radix: 16), line, column);
+            buffer.clear();
+            mode = _TokenizerMode.main;
+            break;
+          case 0x28: // U+0028 LEFT PARENTHESIS character (()
+          case 0x29: // U+0029 RIGHT PARENTHESIS character ())
+          case 0x2C: // U+002C COMMA character (,)
+          case 0x3A: // U+003A COLON character (:)
+          case 0x3B: // U+003B SEMICOLON character (;)
+          case 0x3D: // U+003D EQUALS SIGN character (=)
+          case 0x5B: // U+005B LEFT SQUARE BRACKET character ([)
+          case 0x5D: // U+005D RIGHT SQUARE BRACKET character (])
+          case 0x7B: // U+007B LEFT CURLY BRACKET character ({)
+          case 0x7D: // U+007D RIGHT CURLY BRACKET character (})
+            yield _IntegerToken(int.parse(String.fromCharCodes(buffer), radix: 16), line, column);
+            buffer.clear();
+            yield _SymbolToken(current, line, column);
+            mode = _TokenizerMode.main;
+            break;
+          case 0x30: // U+0030 DIGIT ZERO character (0)
+          case 0x31: // U+0031 DIGIT ONE character (1)
+          case 0x32: // U+0032 DIGIT TWO character (2)
+          case 0x33: // U+0033 DIGIT THREE character (3)
+          case 0x34: // U+0034 DIGIT FOUR character (4)
+          case 0x35: // U+0035 DIGIT FIVE character (5)
+          case 0x36: // U+0036 DIGIT SIX character (6)
+          case 0x37: // U+0037 DIGIT SEVEN character (7)
+          case 0x38: // U+0038 DIGIT EIGHT character (8)
+          case 0x39: // U+0039 DIGIT NINE character (9)
+          case 0x41: // U+0041 LATIN CAPITAL LETTER A character
+          case 0x42: // U+0042 LATIN CAPITAL LETTER B character
+          case 0x43: // U+0043 LATIN CAPITAL LETTER C character
+          case 0x44: // U+0044 LATIN CAPITAL LETTER D character
+          case 0x45: // U+0045 LATIN CAPITAL LETTER E character
+          case 0x46: // U+0046 LATIN CAPITAL LETTER F character
+          case 0x61: // U+0061 LATIN SMALL LETTER A character
+          case 0x62: // U+0062 LATIN SMALL LETTER B character
+          case 0x63: // U+0063 LATIN SMALL LETTER C character
+          case 0x64: // U+0064 LATIN SMALL LETTER D character
+          case 0x65: // U+0065 LATIN SMALL LETTER E character
+          case 0x66: // U+0066 LATIN SMALL LETTER F character
+            buffer.add(current);
+            break;
+          default:
+            throw ParserException('Unexpected character ${_describeRune(current)} in hex literal', line, column);
+        }
+        break;
+
+      case _TokenizerMode.dot1: // "."
+        switch (current) {
+          case -1:
+            yield _SymbolToken(0x2E, line, column);
+            yield _EofToken(line, column);
+            return;
+          case 0x0A: // U+000A LINE FEED (LF)
+          case 0x20: // U+0020 SPACE character
+            yield _SymbolToken(0x2E, line, column);
+            mode = _TokenizerMode.main;
+            break;
+          case 0x22: // U+0022 QUOTATION MARK character (")
+            yield _SymbolToken(0x2E, line, column);
+            assert(buffer.isEmpty);
+            mode = _TokenizerMode.doubleQuote;
+            break;
+          case 0x27: // U+0027 APOSTROPHE character (')
+            yield _SymbolToken(0x2E, line, column);
+            assert(buffer.isEmpty);
+            mode = _TokenizerMode.quote;
+            break;
+          case 0x28: // U+0028 LEFT PARENTHESIS character (()
+          case 0x29: // U+0029 RIGHT PARENTHESIS character ())
+          case 0x2C: // U+002C COMMA character (,)
+          case 0x3A: // U+003A COLON character (:)
+          case 0x3B: // U+003B SEMICOLON character (;)
+          case 0x3D: // U+003D EQUALS SIGN character (=)
+          case 0x5B: // U+005B LEFT SQUARE BRACKET character ([)
+          case 0x5D: // U+005D RIGHT SQUARE BRACKET character (])
+          case 0x7B: // U+007B LEFT CURLY BRACKET character ({)
+          case 0x7D: // U+007D RIGHT CURLY BRACKET character (})
+            yield _SymbolToken(0x2E, line, column);
+            yield _SymbolToken(current, line, column);
+            break;
+          case 0x2E: // U+002E FULL STOP character (.)
+            mode = _TokenizerMode.dot2;
+            break;
+          case 0x30: // U+0030 DIGIT ZERO character (0)
+          case 0x31: // U+0031 DIGIT ONE character (1)
+          case 0x32: // U+0032 DIGIT TWO character (2)
+          case 0x33: // U+0033 DIGIT THREE character (3)
+          case 0x34: // U+0034 DIGIT FOUR character (4)
+          case 0x35: // U+0035 DIGIT FIVE character (5)
+          case 0x36: // U+0036 DIGIT SIX character (6)
+          case 0x37: // U+0037 DIGIT SEVEN character (7)
+          case 0x38: // U+0038 DIGIT EIGHT character (8)
+          case 0x39: // U+0039 DIGIT NINE character (9)
+            yield _SymbolToken(0x2E, line, column);
+            assert(buffer.isEmpty);
+            mode = _TokenizerMode.integerOnly;
+            buffer.add(current);
+            break;
+          case 0x41: // U+0041 LATIN CAPITAL LETTER A character
+          case 0x42: // U+0042 LATIN CAPITAL LETTER B character
+          case 0x43: // U+0043 LATIN CAPITAL LETTER C character
+          case 0x44: // U+0044 LATIN CAPITAL LETTER D character
+          case 0x45: // U+0045 LATIN CAPITAL LETTER E character
+          case 0x46: // U+0046 LATIN CAPITAL LETTER F character
+          case 0x47: // U+0047 LATIN CAPITAL LETTER G character
+          case 0x48: // U+0048 LATIN CAPITAL LETTER H character
+          case 0x49: // U+0049 LATIN CAPITAL LETTER I character
+          case 0x4A: // U+004A LATIN CAPITAL LETTER J character
+          case 0x4B: // U+004B LATIN CAPITAL LETTER K character
+          case 0x4C: // U+004C LATIN CAPITAL LETTER L character
+          case 0x4D: // U+004D LATIN CAPITAL LETTER M character
+          case 0x4E: // U+004E LATIN CAPITAL LETTER N character
+          case 0x4F: // U+004F LATIN CAPITAL LETTER O character
+          case 0x50: // U+0050 LATIN CAPITAL LETTER P character
+          case 0x51: // U+0051 LATIN CAPITAL LETTER Q character
+          case 0x52: // U+0052 LATIN CAPITAL LETTER R character
+          case 0x53: // U+0053 LATIN CAPITAL LETTER S character
+          case 0x54: // U+0054 LATIN CAPITAL LETTER T character
+          case 0x55: // U+0055 LATIN CAPITAL LETTER U character
+          case 0x56: // U+0056 LATIN CAPITAL LETTER V character
+          case 0x57: // U+0057 LATIN CAPITAL LETTER W character
+          case 0x58: // U+0058 LATIN CAPITAL LETTER X character
+          case 0x59: // U+0059 LATIN CAPITAL LETTER Y character
+          case 0x5A: // U+005A LATIN CAPITAL LETTER Z character
+          case 0x61: // U+0061 LATIN SMALL LETTER A character
+          case 0x62: // U+0062 LATIN SMALL LETTER B character
+          case 0x63: // U+0063 LATIN SMALL LETTER C character
+          case 0x64: // U+0064 LATIN SMALL LETTER D character
+          case 0x65: // U+0065 LATIN SMALL LETTER E character
+          case 0x66: // U+0066 LATIN SMALL LETTER F character
+          case 0x67: // U+0067 LATIN SMALL LETTER G character
+          case 0x68: // U+0068 LATIN SMALL LETTER H character
+          case 0x69: // U+0069 LATIN SMALL LETTER I character
+          case 0x6A: // U+006A LATIN SMALL LETTER J character
+          case 0x6B: // U+006B LATIN SMALL LETTER K character
+          case 0x6C: // U+006C LATIN SMALL LETTER L character
+          case 0x6D: // U+006D LATIN SMALL LETTER M character
+          case 0x6E: // U+006E LATIN SMALL LETTER N character
+          case 0x6F: // U+006F LATIN SMALL LETTER O character
+          case 0x70: // U+0070 LATIN SMALL LETTER P character
+          case 0x71: // U+0071 LATIN SMALL LETTER Q character
+          case 0x72: // U+0072 LATIN SMALL LETTER R character
+          case 0x73: // U+0073 LATIN SMALL LETTER S character
+          case 0x74: // U+0074 LATIN SMALL LETTER T character
+          case 0x75: // U+0075 LATIN SMALL LETTER U character
+          case 0x76: // U+0076 LATIN SMALL LETTER V character
+          case 0x77: // U+0077 LATIN SMALL LETTER W character
+          case 0x78: // U+0078 LATIN SMALL LETTER X character
+          case 0x79: // U+0079 LATIN SMALL LETTER Y character
+          case 0x7A: // U+007A LATIN SMALL LETTER Z character
+          case 0x5F: // U+005F LOW LINE character (_)
+            yield _SymbolToken(0x2E, line, column);
+            assert(buffer.isEmpty);
+            mode = _TokenizerMode.identifier;
+            buffer.add(current);
+            break;
+          default:
+            throw ParserException('Unexpected character ${_describeRune(current)} after period', line, column);
+        }
+        break;
+
+      case _TokenizerMode.dot2: // ".."
+        switch (current) {
+          case -1:
+            throw ParserException('Unexpected end of file inside "..." symbol', line, column);
+          case 0x2E: // U+002E FULL STOP character (.)
+            yield _SymbolToken(_SymbolToken.tripleDot, line, column);
+            mode = _TokenizerMode.main;
+            break;
+          default:
+            throw ParserException('Unexpected character ${_describeRune(current)} inside "..." symbol', line, column);
+        }
+        break;
+
+      case _TokenizerMode.identifier:
+        switch (current) {
+          case -1:
+            yield _IdentifierToken(String.fromCharCodes(buffer), line, column);
+            yield _EofToken(line, column);
+            return;
+          case 0x0A: // U+000A LINE FEED (LF)
+          case 0x20: // U+0020 SPACE character
+            yield _IdentifierToken(String.fromCharCodes(buffer), line, column);
+            buffer.clear();
+            mode = _TokenizerMode.main;
+            break;
+          case 0x28: // U+0028 LEFT PARENTHESIS character (()
+          case 0x29: // U+0029 RIGHT PARENTHESIS character ())
+          case 0x2C: // U+002C COMMA character (,)
+          case 0x3A: // U+003A COLON character (:)
+          case 0x3B: // U+003B SEMICOLON character (;)
+          case 0x3D: // U+003D EQUALS SIGN character (=)
+          case 0x5B: // U+005B LEFT SQUARE BRACKET character ([)
+          case 0x5D: // U+005D RIGHT SQUARE BRACKET character (])
+          case 0x7B: // U+007B LEFT CURLY BRACKET character ({)
+          case 0x7D: // U+007D RIGHT CURLY BRACKET character (})
+            yield _IdentifierToken(String.fromCharCodes(buffer), line, column);
+            buffer.clear();
+            yield _SymbolToken(current, line, column);
+            mode = _TokenizerMode.main;
+            break;
+          case 0x2E: // U+002E FULL STOP character (.)
+            yield _IdentifierToken(String.fromCharCodes(buffer), line, column);
+            buffer.clear();
+            mode = _TokenizerMode.dot1;
+            break;
+          case 0x30: // U+0030 DIGIT ZERO character (0)
+          case 0x31: // U+0031 DIGIT ONE character (1)
+          case 0x32: // U+0032 DIGIT TWO character (2)
+          case 0x33: // U+0033 DIGIT THREE character (3)
+          case 0x34: // U+0034 DIGIT FOUR character (4)
+          case 0x35: // U+0035 DIGIT FIVE character (5)
+          case 0x36: // U+0036 DIGIT SIX character (6)
+          case 0x37: // U+0037 DIGIT SEVEN character (7)
+          case 0x38: // U+0038 DIGIT EIGHT character (8)
+          case 0x39: // U+0039 DIGIT NINE character (9)
+          case 0x41: // U+0041 LATIN CAPITAL LETTER A character
+          case 0x42: // U+0042 LATIN CAPITAL LETTER B character
+          case 0x43: // U+0043 LATIN CAPITAL LETTER C character
+          case 0x44: // U+0044 LATIN CAPITAL LETTER D character
+          case 0x45: // U+0045 LATIN CAPITAL LETTER E character
+          case 0x46: // U+0046 LATIN CAPITAL LETTER F character
+          case 0x47: // U+0047 LATIN CAPITAL LETTER G character
+          case 0x48: // U+0048 LATIN CAPITAL LETTER H character
+          case 0x49: // U+0049 LATIN CAPITAL LETTER I character
+          case 0x4A: // U+004A LATIN CAPITAL LETTER J character
+          case 0x4B: // U+004B LATIN CAPITAL LETTER K character
+          case 0x4C: // U+004C LATIN CAPITAL LETTER L character
+          case 0x4D: // U+004D LATIN CAPITAL LETTER M character
+          case 0x4E: // U+004E LATIN CAPITAL LETTER N character
+          case 0x4F: // U+004F LATIN CAPITAL LETTER O character
+          case 0x50: // U+0050 LATIN CAPITAL LETTER P character
+          case 0x51: // U+0051 LATIN CAPITAL LETTER Q character
+          case 0x52: // U+0052 LATIN CAPITAL LETTER R character
+          case 0x53: // U+0053 LATIN CAPITAL LETTER S character
+          case 0x54: // U+0054 LATIN CAPITAL LETTER T character
+          case 0x55: // U+0055 LATIN CAPITAL LETTER U character
+          case 0x56: // U+0056 LATIN CAPITAL LETTER V character
+          case 0x57: // U+0057 LATIN CAPITAL LETTER W character
+          case 0x58: // U+0058 LATIN CAPITAL LETTER X character
+          case 0x59: // U+0059 LATIN CAPITAL LETTER Y character
+          case 0x5A: // U+005A LATIN CAPITAL LETTER Z character
+          case 0x61: // U+0061 LATIN SMALL LETTER A character
+          case 0x62: // U+0062 LATIN SMALL LETTER B character
+          case 0x63: // U+0063 LATIN SMALL LETTER C character
+          case 0x64: // U+0064 LATIN SMALL LETTER D character
+          case 0x65: // U+0065 LATIN SMALL LETTER E character
+          case 0x66: // U+0066 LATIN SMALL LETTER F character
+          case 0x67: // U+0067 LATIN SMALL LETTER G character
+          case 0x68: // U+0068 LATIN SMALL LETTER H character
+          case 0x69: // U+0069 LATIN SMALL LETTER I character
+          case 0x6A: // U+006A LATIN SMALL LETTER J character
+          case 0x6B: // U+006B LATIN SMALL LETTER K character
+          case 0x6C: // U+006C LATIN SMALL LETTER L character
+          case 0x6D: // U+006D LATIN SMALL LETTER M character
+          case 0x6E: // U+006E LATIN SMALL LETTER N character
+          case 0x6F: // U+006F LATIN SMALL LETTER O character
+          case 0x70: // U+0070 LATIN SMALL LETTER P character
+          case 0x71: // U+0071 LATIN SMALL LETTER Q character
+          case 0x72: // U+0072 LATIN SMALL LETTER R character
+          case 0x73: // U+0073 LATIN SMALL LETTER S character
+          case 0x74: // U+0074 LATIN SMALL LETTER T character
+          case 0x75: // U+0075 LATIN SMALL LETTER U character
+          case 0x76: // U+0076 LATIN SMALL LETTER V character
+          case 0x77: // U+0077 LATIN SMALL LETTER W character
+          case 0x78: // U+0078 LATIN SMALL LETTER X character
+          case 0x79: // U+0079 LATIN SMALL LETTER Y character
+          case 0x7A: // U+007A LATIN SMALL LETTER Z character
+          case 0x5F: // U+005F LOW LINE character (_)
+            buffer.add(current);
+            break;
+          default:
+            throw ParserException('Unexpected character ${_describeRune(current)} inside identifier', line, column);
+        }
+        break;
+
+      case _TokenizerMode.quote:
+        switch (current) {
+          case -1:
+            throw ParserException('Unexpected end of file inside string', line, column);
+          case 0x0A: // U+000A LINE FEED (LF)
+            throw ParserException('Unexpected end of line inside string', line, column);
+          case 0x27: // U+0027 APOSTROPHE character (')
+            yield _StringToken(String.fromCharCodes(buffer), line, column);
+            buffer.clear();
+            mode = _TokenizerMode.endQuote;
+            break;
+          case 0x5C: // U+005C REVERSE SOLIDUS character (\)
+            mode = _TokenizerMode.quoteEscape;
+            break;
+          default:
+            buffer.add(current);
+        }
+        break;
+
+      case _TokenizerMode.quoteEscape:
+        switch (current) {
+          case -1:
+            throw ParserException('Unexpected end of file inside string', line, column);
+          case 0x22: // U+0022 QUOTATION MARK character (")
+          case 0x27: // U+0027 APOSTROPHE character (')
+          case 0x5C: // U+005C REVERSE SOLIDUS character (\)
+          case 0x2F: // U+002F SOLIDUS character (/)
+            buffer.add(current);
+            mode = _TokenizerMode.quote;
+            break;
+          case 0x62: // U+0062 LATIN SMALL LETTER B character
+            buffer.add(0x08);
+            mode = _TokenizerMode.quote;
+            break;
+          case 0x66: // U+0066 LATIN SMALL LETTER F character
+            buffer.add(0x0C);
+            mode = _TokenizerMode.quote;
+            break;
+          case 0x6E: // U+006E LATIN SMALL LETTER N character
+            buffer.add(0x0A);
+            mode = _TokenizerMode.quote;
+            break;
+          case 0x72: // U+0072 LATIN SMALL LETTER R character
+            buffer.add(0x0D);
+            mode = _TokenizerMode.quote;
+            break;
+          case 0x74: // U+0074 LATIN SMALL LETTER T character
+            buffer.add(0x09);
+            mode = _TokenizerMode.quote;
+            break;
+          case 0x75: // U+0075 LATIN SMALL LETTER U character
+            assert(buffer2.isEmpty);
+            mode = _TokenizerMode.quoteEscapeUnicode1;
+            break;
+          default:
+            throw ParserException('Unexpected character ${_describeRune(current)} after backslash in string', line, column);
+        }
+        break;
+
+      case _TokenizerMode.quoteEscapeUnicode1:
+        switch (current) {
+          case -1:
+            throw ParserException('Unexpected end of file inside Unicode escape', line, column);
+          case 0x30: // U+0030 DIGIT ZERO character (0)
+          case 0x31: // U+0031 DIGIT ONE character (1)
+          case 0x32: // U+0032 DIGIT TWO character (2)
+          case 0x33: // U+0033 DIGIT THREE character (3)
+          case 0x34: // U+0034 DIGIT FOUR character (4)
+          case 0x35: // U+0035 DIGIT FIVE character (5)
+          case 0x36: // U+0036 DIGIT SIX character (6)
+          case 0x37: // U+0037 DIGIT SEVEN character (7)
+          case 0x38: // U+0038 DIGIT EIGHT character (8)
+          case 0x39: // U+0039 DIGIT NINE character (9)
+          case 0x41: // U+0041 LATIN CAPITAL LETTER A character
+          case 0x42: // U+0042 LATIN CAPITAL LETTER B character
+          case 0x43: // U+0043 LATIN CAPITAL LETTER C character
+          case 0x44: // U+0044 LATIN CAPITAL LETTER D character
+          case 0x45: // U+0045 LATIN CAPITAL LETTER E character
+          case 0x46: // U+0046 LATIN CAPITAL LETTER F character
+          case 0x61: // U+0061 LATIN SMALL LETTER A character
+          case 0x62: // U+0062 LATIN SMALL LETTER B character
+          case 0x63: // U+0063 LATIN SMALL LETTER C character
+          case 0x64: // U+0064 LATIN SMALL LETTER D character
+          case 0x65: // U+0065 LATIN SMALL LETTER E character
+          case 0x66: // U+0066 LATIN SMALL LETTER F character
+            buffer2.add(current);
+            mode = _TokenizerMode.quoteEscapeUnicode2;
+            break;
+          default:
+            throw ParserException('Unexpected character ${_describeRune(current)} in Unicode escape', line, column);
+        }
+        break;
+
+      case _TokenizerMode.quoteEscapeUnicode2:
+        switch (current) {
+          case -1:
+            throw ParserException('Unexpected end of file inside Unicode escape', line, column);
+          case 0x30: // U+0030 DIGIT ZERO character (0)
+          case 0x31: // U+0031 DIGIT ONE character (1)
+          case 0x32: // U+0032 DIGIT TWO character (2)
+          case 0x33: // U+0033 DIGIT THREE character (3)
+          case 0x34: // U+0034 DIGIT FOUR character (4)
+          case 0x35: // U+0035 DIGIT FIVE character (5)
+          case 0x36: // U+0036 DIGIT SIX character (6)
+          case 0x37: // U+0037 DIGIT SEVEN character (7)
+          case 0x38: // U+0038 DIGIT EIGHT character (8)
+          case 0x39: // U+0039 DIGIT NINE character (9)
+          case 0x41: // U+0041 LATIN CAPITAL LETTER A character
+          case 0x42: // U+0042 LATIN CAPITAL LETTER B character
+          case 0x43: // U+0043 LATIN CAPITAL LETTER C character
+          case 0x44: // U+0044 LATIN CAPITAL LETTER D character
+          case 0x45: // U+0045 LATIN CAPITAL LETTER E character
+          case 0x46: // U+0046 LATIN CAPITAL LETTER F character
+          case 0x61: // U+0061 LATIN SMALL LETTER A character
+          case 0x62: // U+0062 LATIN SMALL LETTER B character
+          case 0x63: // U+0063 LATIN SMALL LETTER C character
+          case 0x64: // U+0064 LATIN SMALL LETTER D character
+          case 0x65: // U+0065 LATIN SMALL LETTER E character
+          case 0x66: // U+0066 LATIN SMALL LETTER F character
+            buffer2.add(current);
+            mode = _TokenizerMode.quoteEscapeUnicode3;
+            break;
+          default:
+            throw ParserException('Unexpected character ${_describeRune(current)} in Unicode escape', line, column);
+        }
+        break;
+
+      case _TokenizerMode.quoteEscapeUnicode3:
+        switch (current) {
+          case -1:
+            throw ParserException('Unexpected end of file inside Unicode escape', line, column);
+          case 0x30: // U+0030 DIGIT ZERO character (0)
+          case 0x31: // U+0031 DIGIT ONE character (1)
+          case 0x32: // U+0032 DIGIT TWO character (2)
+          case 0x33: // U+0033 DIGIT THREE character (3)
+          case 0x34: // U+0034 DIGIT FOUR character (4)
+          case 0x35: // U+0035 DIGIT FIVE character (5)
+          case 0x36: // U+0036 DIGIT SIX character (6)
+          case 0x37: // U+0037 DIGIT SEVEN character (7)
+          case 0x38: // U+0038 DIGIT EIGHT character (8)
+          case 0x39: // U+0039 DIGIT NINE character (9)
+          case 0x41: // U+0041 LATIN CAPITAL LETTER A character
+          case 0x42: // U+0042 LATIN CAPITAL LETTER B character
+          case 0x43: // U+0043 LATIN CAPITAL LETTER C character
+          case 0x44: // U+0044 LATIN CAPITAL LETTER D character
+          case 0x45: // U+0045 LATIN CAPITAL LETTER E character
+          case 0x46: // U+0046 LATIN CAPITAL LETTER F character
+          case 0x61: // U+0061 LATIN SMALL LETTER A character
+          case 0x62: // U+0062 LATIN SMALL LETTER B character
+          case 0x63: // U+0063 LATIN SMALL LETTER C character
+          case 0x64: // U+0064 LATIN SMALL LETTER D character
+          case 0x65: // U+0065 LATIN SMALL LETTER E character
+          case 0x66: // U+0066 LATIN SMALL LETTER F character
+            buffer2.add(current);
+            mode = _TokenizerMode.quoteEscapeUnicode4;
+            break;
+          default:
+            throw ParserException('Unexpected character ${_describeRune(current)} in Unicode escape', line, column);
+        }
+        break;
+
+      case _TokenizerMode.quoteEscapeUnicode4:
+        switch (current) {
+          case -1:
+            throw ParserException('Unexpected end of file inside Unicode escape', line, column);
+          case 0x30: // U+0030 DIGIT ZERO character (0)
+          case 0x31: // U+0031 DIGIT ONE character (1)
+          case 0x32: // U+0032 DIGIT TWO character (2)
+          case 0x33: // U+0033 DIGIT THREE character (3)
+          case 0x34: // U+0034 DIGIT FOUR character (4)
+          case 0x35: // U+0035 DIGIT FIVE character (5)
+          case 0x36: // U+0036 DIGIT SIX character (6)
+          case 0x37: // U+0037 DIGIT SEVEN character (7)
+          case 0x38: // U+0038 DIGIT EIGHT character (8)
+          case 0x39: // U+0039 DIGIT NINE character (9)
+          case 0x41: // U+0041 LATIN CAPITAL LETTER A character
+          case 0x42: // U+0042 LATIN CAPITAL LETTER B character
+          case 0x43: // U+0043 LATIN CAPITAL LETTER C character
+          case 0x44: // U+0044 LATIN CAPITAL LETTER D character
+          case 0x45: // U+0045 LATIN CAPITAL LETTER E character
+          case 0x46: // U+0046 LATIN CAPITAL LETTER F character
+          case 0x61: // U+0061 LATIN SMALL LETTER A character
+          case 0x62: // U+0062 LATIN SMALL LETTER B character
+          case 0x63: // U+0063 LATIN SMALL LETTER C character
+          case 0x64: // U+0064 LATIN SMALL LETTER D character
+          case 0x65: // U+0065 LATIN SMALL LETTER E character
+          case 0x66: // U+0066 LATIN SMALL LETTER F character
+            buffer2.add(current);
+            buffer.add(int.parse(String.fromCharCodes(buffer2), radix: 16));
+            buffer2.clear();
+            mode = _TokenizerMode.quote;
+            break;
+          default:
+            throw ParserException('Unexpected character ${_describeRune(current)} in Unicode escape', line, column);
+        }
+        break;
+
+      case _TokenizerMode.endQuote:
+        switch (current) {
+          case -1:
+            yield _EofToken(line, column);
+            return;
+          case 0x0A: // U+000A LINE FEED (LF)
+          case 0x20: // U+0020 SPACE character
+            mode = _TokenizerMode.main;
+            break;
+          case 0x28: // U+0028 LEFT PARENTHESIS character (()
+          case 0x29: // U+0029 RIGHT PARENTHESIS character ())
+          case 0x2C: // U+002C COMMA character (,)
+          case 0x3A: // U+003A COLON character (:)
+          case 0x3B: // U+003B SEMICOLON character (;)
+          case 0x3D: // U+003D EQUALS SIGN character (=)
+          case 0x5B: // U+005B LEFT SQUARE BRACKET character ([)
+          case 0x5D: // U+005D RIGHT SQUARE BRACKET character (])
+          case 0x7B: // U+007B LEFT CURLY BRACKET character ({)
+          case 0x7D: // U+007D RIGHT CURLY BRACKET character (})
+            yield _SymbolToken(current, line, column);
+            mode = _TokenizerMode.main;
+            break;
+          case 0x2E: // U+002E FULL STOP character (.)
+            mode = _TokenizerMode.dot1;
+            break;
+          default:
+            throw ParserException('Unexpected character ${_describeRune(current)} after end quote', line, column);
+        }
+        break;
+
+      case _TokenizerMode.doubleQuote:
+        switch (current) {
+          case -1:
+            throw ParserException('Unexpected end of file inside string', line, column);
+          case 0x0A: // U+000A LINE FEED (LF)
+            throw ParserException('Unexpected end of line inside string', line, column);
+          case 0x22: // U+0022 QUOTATION MARK character (")
+            yield _StringToken(String.fromCharCodes(buffer), line, column);
+            buffer.clear();
+            mode = _TokenizerMode.endQuote;
+            break;
+          case 0x5C: // U+005C REVERSE SOLIDUS character (\)
+            mode = _TokenizerMode.doubleQuoteEscape;
+            break;
+          default:
+            buffer.add(current);
+        }
+        break;
+
+      case _TokenizerMode.doubleQuoteEscape:
+        switch (current) {
+          case -1:
+            throw ParserException('Unexpected end of file inside string', line, column);
+          case 0x22: // U+0022 QUOTATION MARK character (")
+          case 0x27: // U+0027 APOSTROPHE character (')
+          case 0x5C: // U+005C REVERSE SOLIDUS character (\)
+          case 0x2F: // U+002F SOLIDUS character (/)
+            buffer.add(current);
+            mode = _TokenizerMode.doubleQuote;
+            break;
+          case 0x62: // U+0062 LATIN SMALL LETTER B character
+            buffer.add(0x08);
+            mode = _TokenizerMode.doubleQuote;
+            break;
+          case 0x66: // U+0066 LATIN SMALL LETTER F character
+            buffer.add(0x0C);
+            mode = _TokenizerMode.doubleQuote;
+            break;
+          case 0x6E: // U+006E LATIN SMALL LETTER N character
+            buffer.add(0x0A);
+            mode = _TokenizerMode.doubleQuote;
+            break;
+          case 0x72: // U+0072 LATIN SMALL LETTER R character
+            buffer.add(0x0D);
+            mode = _TokenizerMode.doubleQuote;
+            break;
+          case 0x74: // U+0074 LATIN SMALL LETTER T character
+            buffer.add(0x09);
+            mode = _TokenizerMode.doubleQuote;
+            break;
+          case 0x75: // U+0075 LATIN SMALL LETTER U character
+            assert(buffer2.isEmpty);
+            mode = _TokenizerMode.doubleQuoteEscapeUnicode1;
+            break;
+          default:
+            throw ParserException('Unexpected character ${_describeRune(current)} after backslash in string', line, column);
+        }
+        break;
+
+      case _TokenizerMode.doubleQuoteEscapeUnicode1:
+        switch (current) {
+          case -1:
+            throw ParserException('Unexpected end of file inside Unicode escape', line, column);
+          case 0x30: // U+0030 DIGIT ZERO character (0)
+          case 0x31: // U+0031 DIGIT ONE character (1)
+          case 0x32: // U+0032 DIGIT TWO character (2)
+          case 0x33: // U+0033 DIGIT THREE character (3)
+          case 0x34: // U+0034 DIGIT FOUR character (4)
+          case 0x35: // U+0035 DIGIT FIVE character (5)
+          case 0x36: // U+0036 DIGIT SIX character (6)
+          case 0x37: // U+0037 DIGIT SEVEN character (7)
+          case 0x38: // U+0038 DIGIT EIGHT character (8)
+          case 0x39: // U+0039 DIGIT NINE character (9)
+          case 0x41: // U+0041 LATIN CAPITAL LETTER A character
+          case 0x42: // U+0042 LATIN CAPITAL LETTER B character
+          case 0x43: // U+0043 LATIN CAPITAL LETTER C character
+          case 0x44: // U+0044 LATIN CAPITAL LETTER D character
+          case 0x45: // U+0045 LATIN CAPITAL LETTER E character
+          case 0x46: // U+0046 LATIN CAPITAL LETTER F character
+          case 0x61: // U+0061 LATIN SMALL LETTER A character
+          case 0x62: // U+0062 LATIN SMALL LETTER B character
+          case 0x63: // U+0063 LATIN SMALL LETTER C character
+          case 0x64: // U+0064 LATIN SMALL LETTER D character
+          case 0x65: // U+0065 LATIN SMALL LETTER E character
+          case 0x66: // U+0066 LATIN SMALL LETTER F character
+            buffer2.add(current);
+            mode = _TokenizerMode.doubleQuoteEscapeUnicode2;
+            break;
+          default:
+            throw ParserException('Unexpected character ${_describeRune(current)} in Unicode escape', line, column);
+        }
+        break;
+
+      case _TokenizerMode.doubleQuoteEscapeUnicode2:
+        switch (current) {
+          case -1:
+            throw ParserException('Unexpected end of file inside Unicode escape', line, column);
+          case 0x30: // U+0030 DIGIT ZERO character (0)
+          case 0x31: // U+0031 DIGIT ONE character (1)
+          case 0x32: // U+0032 DIGIT TWO character (2)
+          case 0x33: // U+0033 DIGIT THREE character (3)
+          case 0x34: // U+0034 DIGIT FOUR character (4)
+          case 0x35: // U+0035 DIGIT FIVE character (5)
+          case 0x36: // U+0036 DIGIT SIX character (6)
+          case 0x37: // U+0037 DIGIT SEVEN character (7)
+          case 0x38: // U+0038 DIGIT EIGHT character (8)
+          case 0x39: // U+0039 DIGIT NINE character (9)
+          case 0x41: // U+0041 LATIN CAPITAL LETTER A character
+          case 0x42: // U+0042 LATIN CAPITAL LETTER B character
+          case 0x43: // U+0043 LATIN CAPITAL LETTER C character
+          case 0x44: // U+0044 LATIN CAPITAL LETTER D character
+          case 0x45: // U+0045 LATIN CAPITAL LETTER E character
+          case 0x46: // U+0046 LATIN CAPITAL LETTER F character
+          case 0x61: // U+0061 LATIN SMALL LETTER A character
+          case 0x62: // U+0062 LATIN SMALL LETTER B character
+          case 0x63: // U+0063 LATIN SMALL LETTER C character
+          case 0x64: // U+0064 LATIN SMALL LETTER D character
+          case 0x65: // U+0065 LATIN SMALL LETTER E character
+          case 0x66: // U+0066 LATIN SMALL LETTER F character
+            buffer2.add(current);
+            mode = _TokenizerMode.doubleQuoteEscapeUnicode3;
+            break;
+          default:
+            throw ParserException('Unexpected character ${_describeRune(current)} in Unicode escape', line, column);
+        }
+        break;
+
+      case _TokenizerMode.doubleQuoteEscapeUnicode3:
+        switch (current) {
+          case -1:
+            throw ParserException('Unexpected end of file inside Unicode escape', line, column);
+          case 0x30: // U+0030 DIGIT ZERO character (0)
+          case 0x31: // U+0031 DIGIT ONE character (1)
+          case 0x32: // U+0032 DIGIT TWO character (2)
+          case 0x33: // U+0033 DIGIT THREE character (3)
+          case 0x34: // U+0034 DIGIT FOUR character (4)
+          case 0x35: // U+0035 DIGIT FIVE character (5)
+          case 0x36: // U+0036 DIGIT SIX character (6)
+          case 0x37: // U+0037 DIGIT SEVEN character (7)
+          case 0x38: // U+0038 DIGIT EIGHT character (8)
+          case 0x39: // U+0039 DIGIT NINE character (9)
+          case 0x41: // U+0041 LATIN CAPITAL LETTER A character
+          case 0x42: // U+0042 LATIN CAPITAL LETTER B character
+          case 0x43: // U+0043 LATIN CAPITAL LETTER C character
+          case 0x44: // U+0044 LATIN CAPITAL LETTER D character
+          case 0x45: // U+0045 LATIN CAPITAL LETTER E character
+          case 0x46: // U+0046 LATIN CAPITAL LETTER F character
+          case 0x61: // U+0061 LATIN SMALL LETTER A character
+          case 0x62: // U+0062 LATIN SMALL LETTER B character
+          case 0x63: // U+0063 LATIN SMALL LETTER C character
+          case 0x64: // U+0064 LATIN SMALL LETTER D character
+          case 0x65: // U+0065 LATIN SMALL LETTER E character
+          case 0x66: // U+0066 LATIN SMALL LETTER F character
+            buffer2.add(current);
+            mode = _TokenizerMode.doubleQuoteEscapeUnicode4;
+            break;
+          default:
+            throw ParserException('Unexpected character ${_describeRune(current)} in Unicode escape', line, column);
+        }
+        break;
+
+      case _TokenizerMode.doubleQuoteEscapeUnicode4:
+        switch (current) {
+          case -1:
+            throw ParserException('Unexpected end of file inside Unicode escape', line, column);
+          case 0x30: // U+0030 DIGIT ZERO character (0)
+          case 0x31: // U+0031 DIGIT ONE character (1)
+          case 0x32: // U+0032 DIGIT TWO character (2)
+          case 0x33: // U+0033 DIGIT THREE character (3)
+          case 0x34: // U+0034 DIGIT FOUR character (4)
+          case 0x35: // U+0035 DIGIT FIVE character (5)
+          case 0x36: // U+0036 DIGIT SIX character (6)
+          case 0x37: // U+0037 DIGIT SEVEN character (7)
+          case 0x38: // U+0038 DIGIT EIGHT character (8)
+          case 0x39: // U+0039 DIGIT NINE character (9)
+          case 0x41: // U+0041 LATIN CAPITAL LETTER A character
+          case 0x42: // U+0042 LATIN CAPITAL LETTER B character
+          case 0x43: // U+0043 LATIN CAPITAL LETTER C character
+          case 0x44: // U+0044 LATIN CAPITAL LETTER D character
+          case 0x45: // U+0045 LATIN CAPITAL LETTER E character
+          case 0x46: // U+0046 LATIN CAPITAL LETTER F character
+          case 0x61: // U+0061 LATIN SMALL LETTER A character
+          case 0x62: // U+0062 LATIN SMALL LETTER B character
+          case 0x63: // U+0063 LATIN SMALL LETTER C character
+          case 0x64: // U+0064 LATIN SMALL LETTER D character
+          case 0x65: // U+0065 LATIN SMALL LETTER E character
+          case 0x66: // U+0066 LATIN SMALL LETTER F character
+            buffer2.add(current);
+            buffer.add(int.parse(String.fromCharCodes(buffer2), radix: 16));
+            buffer2.clear();
+            mode = _TokenizerMode.doubleQuote;
+            break;
+          default:
+            throw ParserException('Unexpected character ${_describeRune(current)} in Unicode escape', line, column);
+        }
+        break;
+
+      case _TokenizerMode.endDoubleQuote:
+        switch (current) {
+          case -1:
+            yield _EofToken(line, column);
+            return;
+          case 0x0A: // U+000A LINE FEED (LF)
+          case 0x20: // U+0020 SPACE character
+            mode = _TokenizerMode.main;
+            break;
+          case 0x28: // U+0028 LEFT PARENTHESIS character (()
+          case 0x29: // U+0029 RIGHT PARENTHESIS character ())
+          case 0x2C: // U+002C COMMA character (,)
+          case 0x3A: // U+003A COLON character (:)
+          case 0x3B: // U+003B SEMICOLON character (;)
+          case 0x3D: // U+003D EQUALS SIGN character (=)
+          case 0x5B: // U+005B LEFT SQUARE BRACKET character ([)
+          case 0x5D: // U+005D RIGHT SQUARE BRACKET character (])
+          case 0x7B: // U+007B LEFT CURLY BRACKET character ({)
+          case 0x7D: // U+007D RIGHT CURLY BRACKET character (})
+            yield _SymbolToken(current, line, column);
+            mode = _TokenizerMode.main;
+            break;
+          case 0x2E: // U+002E FULL STOP character (.)
+            mode = _TokenizerMode.dot1;
+            break;
+          default:
+            throw ParserException('Unexpected character ${_describeRune(current)} after end doublequote', line, column);
+        }
+        break;
+
+      case _TokenizerMode.slash:
+        switch (current) {
+          case -1:
+            throw ParserException('Unexpected end of file inside comment delimiter', line, column);
+          case 0x2F: // U+002F SOLIDUS character (/)
+            mode = _TokenizerMode.comment;
+            break;
+          default:
+            throw ParserException('Unexpected character ${_describeRune(current)} inside comment delimiter', line, column);
+        }
+        break;
+
+      case _TokenizerMode.comment:
+        switch (current) {
+          case -1:
+            yield _EofToken(line, column);
+            return;
+          case 0x0A: // U+000A LINE FEED (LF)
+            mode = _TokenizerMode.main;
+            break;
+          default:
+            // ignored, comment
+            break;
+        }
+        break;
+    }
+    index += 1;
+  }
+}
+
+/// API for parsing Remote Flutter Widgets text files.
+///
+/// Text data files can be parsed by using [readDataFile]. (Unlike the binary
+/// variant of this format, the root of a text data file is always a map.)
+///
+/// Test library files can be parsed by using [readLibraryFile].
+class _Parser {
+  _Parser(Iterable<_Token> source) : _source = source.iterator..moveNext();
+
+  final Iterator<_Token> _source;
+
+  void _advance() {
+    assert(_source.current is! _EofToken);
+    final bool advanced = _source.moveNext();
+    assert(advanced == true); // see https://github.com/dart-lang/sdk/issues/47017
+  }
+
+  bool _foundIdentifier(String identifier) {
+    return (_source.current is _IdentifierToken)
+        && ((_source.current as _IdentifierToken).value == identifier);
+  }
+
+  void _expectIdentifier(String value) {
+    if (_source.current is! _IdentifierToken) {
+      throw ParserException._expected('identifier', _source.current);
+    }
+    if ((_source.current as _IdentifierToken).value != value) {
+      throw ParserException._expected(value, _source.current);
+    }
+    _advance();
+  }
+
+  String _readIdentifier() {
+    if (_source.current is! _IdentifierToken) {
+      throw ParserException._expected('identifier', _source.current);
+    }
+    final String result = (_source.current as _IdentifierToken).value;
+    _advance();
+    return result;
+  }
+
+  String _readString() {
+    if (_source.current is! _StringToken) {
+      throw ParserException._expected('string', _source.current);
+    }
+    final String result = (_source.current as _StringToken).value;
+    _advance();
+    return result;
+  }
+
+  bool _foundSymbol(int symbol) {
+    return (_source.current is _SymbolToken)
+        && ((_source.current as _SymbolToken).symbol == symbol);
+  }
+
+  bool _maybeReadSymbol(int symbol) {
+    if (_foundSymbol(symbol)) {
+      _advance();
+      return true;
+    }
+    return false;
+  }
+
+  void _expectSymbol(int symbol) {
+    if (_source.current is! _SymbolToken) {
+      throw ParserException._expected('symbol "${String.fromCharCode(symbol)}"', _source.current);
+    }
+    if ((_source.current as _SymbolToken).symbol != symbol) {
+      throw ParserException._expected('symbol "${String.fromCharCode(symbol)}"', _source.current);
+    }
+    _advance();
+  }
+
+  String _readKey() {
+    if (_source.current is _IdentifierToken) {
+      return _readIdentifier();
+    }
+    return _readString();
+  }
+
+  DynamicMap _readMap({ required bool extended }) {
+    _expectSymbol(_SymbolToken.openBrace);
+    final DynamicMap results = _readMapBody(extended: extended);
+    _expectSymbol(_SymbolToken.closeBrace);
+    return results;
+  }
+
+  DynamicMap _readMapBody({ required bool extended }) {
+    final DynamicMap results = DynamicMap(); // ignore: prefer_collection_literals
+    while (_source.current is! _SymbolToken) {
+      final String key = _readKey();
+      if (results.containsKey(key)) {
+        throw ParserException._fromToken('Duplicate key "$key" in map', _source.current);
+      }
+      _expectSymbol(_SymbolToken.colon);
+      final Object value = _readValue(extended: extended, nullOk: true);
+      if (value != missing) {
+        results[key] = value;
+      }
+      if (_foundSymbol(_SymbolToken.comma)) {
+        _advance();
+      } else {
+        break;
+      }
+    }
+    return results;
+  }
+
+  final List<String> _loopIdentifiers = <String>[];
+
+  DynamicList _readList({ required bool extended }) {
+    final DynamicList results = DynamicList.empty(growable: true);
+    _expectSymbol(_SymbolToken.openBracket);
+    while (!_foundSymbol(_SymbolToken.closeBracket)) {
+      if (extended && _foundSymbol(_SymbolToken.tripleDot)) {
+        _advance();
+        _expectIdentifier('for');
+        final _Token loopIdentifierToken = _source.current;
+        final String loopIdentifier = _readIdentifier();
+        if (_reservedWords.contains(loopIdentifier)) {
+          throw ParserException._fromToken('$loopIdentifier is a reserved word', loopIdentifierToken);
+        }
+        _expectIdentifier('in');
+        final Object collection = _readValue(extended: true);
+        _expectSymbol(_SymbolToken.colon);
+        _loopIdentifiers.add(loopIdentifier);
+        final Object template = _readValue(extended: extended);
+        assert(_loopIdentifiers.last == loopIdentifier);
+        _loopIdentifiers.removeLast();
+        results.add(Loop(collection, template));
+      } else {
+        final Object value = _readValue(extended: extended);
+        results.add(value);
+      }
+      if (_foundSymbol(_SymbolToken.comma)) {
+        _advance();
+      } else if (!_foundSymbol(_SymbolToken.closeBracket)) {
+        throw ParserException._expected('comma', _source.current);
+      }
+    }
+    _expectSymbol(_SymbolToken.closeBracket);
+    return results;
+  }
+
+  Switch _readSwitch() {
+    final Object value = _readValue(extended: true);
+    final Map<Object?, Object> cases = <Object?, Object>{};
+    _expectSymbol(_SymbolToken.openBrace);
+    while (_source.current is! _SymbolToken) {
+      final Object? key;
+      if (_foundIdentifier('default')) {
+        if (cases.containsKey(null)) {
+          throw ParserException._fromToken('Switch has multiple default cases', _source.current);
+        }
+        key = null;
+        _advance();
+      } else {
+        key = _readValue(extended: true);
+        if (cases.containsKey(key)) {
+          throw ParserException._fromToken('Switch has duplicate cases for key $key', _source.current);
+        }
+      }
+      _expectSymbol(_SymbolToken.colon);
+      final Object value = _readValue(extended: true);
+      cases[key] = value;
+      if (_foundSymbol(_SymbolToken.comma)) {
+        _advance();
+      } else {
+        break;
+      }
+    }
+    _expectSymbol(_SymbolToken.closeBrace);
+    return Switch(value, cases);
+  }
+
+  List<Object> _readParts({ bool optional = false }) {
+    if (optional && !_foundSymbol(_SymbolToken.dot)) {
+      return const <Object>[];
+    }
+    final List<Object> results = <Object>[];
+    do {
+      _expectSymbol(_SymbolToken.dot);
+      if (_source.current is _IntegerToken) {
+        results.add((_source.current as _IntegerToken).value);
+      } else if (_source.current is _StringToken) {
+        results.add((_source.current as _StringToken).value);
+      } else if (_source.current is _IdentifierToken) {
+        results.add((_source.current as _IdentifierToken).value);
+      } else {
+        throw ParserException._unexpected(_source.current);
+      }
+      _advance();
+    } while (_foundSymbol(_SymbolToken.dot));
+    return results;
+  }
+
+  Object _readValue({ required bool extended, bool nullOk = false }) {
+    if (_source.current is _SymbolToken) {
+      switch ((_source.current as _SymbolToken).symbol) {
+        case _SymbolToken.openBracket:
+          return _readList(extended: extended);
+        case _SymbolToken.openBrace:
+          return _readMap(extended: extended);
+      }
+    } else if (_source.current is _IntegerToken) {
+      final Object result = (_source.current as _IntegerToken).value;
+      _advance();
+      return result;
+    } else if (_source.current is _DoubleToken) {
+      final Object result = (_source.current as _DoubleToken).value;
+      _advance();
+      return result;
+    } else if (_source.current is _StringToken) {
+      final Object result = (_source.current as _StringToken).value;
+      _advance();
+      return result;
+    } else if (_source.current is _IdentifierToken) {
+      final String identifier = (_source.current as _IdentifierToken).value;
+      if (identifier == 'true') {
+        _advance();
+        return true;
+      }
+      if (identifier == 'false') {
+        _advance();
+        return false;
+      }
+      if (identifier == 'null' && nullOk) {
+        _advance();
+        return missing;
+      }
+      if (!extended) {
+        throw ParserException._unexpected(_source.current);
+      }
+      if (identifier == 'event') {
+        _advance();
+        return EventHandler(_readString(), _readMap(extended: true));
+      }
+      if (identifier == 'args') {
+        _advance();
+        return ArgsReference(_readParts());
+      }
+      if (identifier == 'data') {
+        _advance();
+        return DataReference(_readParts());
+      }
+      if (identifier == 'state') {
+        _advance();
+        return StateReference(_readParts());
+      }
+      if (identifier == 'switch') {
+        _advance();
+        return _readSwitch();
+      }
+      if (identifier == 'set') {
+        _advance();
+        _expectIdentifier('state');
+        final StateReference stateReference = StateReference(_readParts());
+        _expectSymbol(_SymbolToken.equals);
+        final Object value = _readValue(extended: true);
+        return SetStateHandler(stateReference, value);
+      }
+      final int index = _loopIdentifiers.lastIndexOf(identifier) + 1;
+      if (index > 0) {
+        _advance();
+        return LoopReference(_loopIdentifiers.length - index, _readParts(optional: true));
+      }
+      return _readConstructorCall();
+    }
+    throw ParserException._unexpected(_source.current);
+  }
+
+  ConstructorCall _readConstructorCall() {
+    final String name = _readIdentifier();
+    _expectSymbol(_SymbolToken.openParen);
+    final DynamicMap arguments = _readMapBody(extended: true);
+    _expectSymbol(_SymbolToken.closeParen);
+    return ConstructorCall(name, arguments);
+  }
+
+  WidgetDeclaration _readWidgetDeclaration() {
+    _expectIdentifier('widget');
+    final String name = _readIdentifier();
+    DynamicMap? initialState;
+    if (_foundSymbol(_SymbolToken.openBrace)) {
+      initialState = _readMap(extended: false);
+    }
+    _expectSymbol(_SymbolToken.equals);
+    final BlobNode root;
+    if (_foundIdentifier('switch')) {
+      _advance();
+      root = _readSwitch();
+    } else {
+      root = _readConstructorCall();
+    }
+    _expectSymbol(_SymbolToken.semicolon);
+    return WidgetDeclaration(name, initialState, root);
+  }
+
+  Iterable<WidgetDeclaration> _readWidgetDeclarations() sync* {
+    while (_foundIdentifier('widget')) {
+      yield _readWidgetDeclaration();
+    }
+  }
+
+  Import _readImport() {
+    _expectIdentifier('import');
+    final List<String> parts = <String>[];
+    do {
+      parts.add(_readKey());
+    } while (_maybeReadSymbol(_SymbolToken.dot));
+    _expectSymbol(_SymbolToken.semicolon);
+    return Import(LibraryName(parts));
+  }
+
+  Iterable<Import> _readImports() sync* {
+    while (_foundIdentifier('import')) {
+      yield _readImport();
+    }
+  }
+
+  DynamicMap readDataFile() {
+    final DynamicMap result = _readMap(extended: false);
+    _expectEof('end of file');
+    return result;
+  }
+
+  RemoteWidgetLibrary readLibraryFile() {
+    final RemoteWidgetLibrary result = RemoteWidgetLibrary(
+      _readImports().toList(),
+      _readWidgetDeclarations().toList(),
+    );
+    if (result.widgets.isEmpty) {
+      _expectEof('keywords "import" or "widget", or end of file');
+    } else {
+      _expectEof('keyword "widget" or end of file');
+    }
+    return result;
+  }
+
+  void _expectEof(String expectation) {
+    if (_source.current is! _EofToken) {
+      throw ParserException._expected(expectation, _source.current);
+    }
+    final bool more = _source.moveNext();
+    assert(more == false); // see https://github.com/dart-lang/sdk/issues/47017
+  }
+}
diff --git a/packages/rfw/lib/flutter/argument_decoders.dart b/packages/rfw/lib/flutter/argument_decoders.dart
new file mode 100644
index 0000000..8554413
--- /dev/null
+++ b/packages/rfw/lib/flutter/argument_decoders.dart
@@ -0,0 +1,1422 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// There's a lot of <Object>[] lists in this file so to avoid making this
+// file even less readable we relax our usual stance on verbose typing.
+// ignore_for_file: always_specify_types
+
+// This file is hand-formatted.
+
+import 'dart:math' as math show pi;
+import 'dart:ui' show FontFeature; // TODO(ianh): https://github.com/flutter/flutter/issues/87235
+
+import 'package:flutter/material.dart';
+
+import 'runtime.dart';
+
+/// Default duration and curve for animations in remote flutter widgets.
+///
+/// This inherited widget allows a duration and a curve (defaulting to 200ms and
+/// [Curves.fastOutSlowIn]) to be set as the default to use when local widgets
+/// use the [ArgumentsDecoder.curve] and [ArgumentsDecoder.duration] methods and
+/// find that the [DataSource] has no explicit curve or duration.
+class AnimationDefaults extends InheritedWidget {
+  /// Configures an [AnimanionDefaults] widget.
+  ///
+  /// The [duration] and [curve] are optional, and default to 200ms and
+  /// [Curves.fastOutSlowIn] respectively.
+  const AnimationDefaults({
+    Key? key,
+    this.duration,
+    this.curve,
+    required Widget child,
+  }) : super(key: key, child: child);
+
+  /// The default duration that [ArgumentsDecoder.duration] should use.
+  ///
+  /// Defaults to 200ms when this is null.
+  final Duration? duration;
+
+  /// The default curve that [ArgumentsDecoder.curve] should use.
+  ///
+  /// Defaults to [Curves.fastOutSlowIn] when this is null.
+  final Curve? curve;
+
+  /// Return the ambient [AnimationDefaults.duration], or 200ms if there is no
+  /// ambient [AnimationDefaults] or if the nearest [AnimationDefaults] has a
+  /// null [duration].
+  static Duration durationOf(BuildContext context) {
+    return context.dependOnInheritedWidgetOfExactType<AnimationDefaults>()?.duration ?? const Duration(milliseconds: 200);
+  }
+
+  /// Return the ambient [AnimationDefaults.curve], or [Curves.fastOutSlowIn] if
+  /// there is no ambient [AnimationDefaults] or if the nearest
+  /// [AnimationDefaults] has a null [curve].
+  static Curve curveOf(BuildContext context) {
+    return context.dependOnInheritedWidgetOfExactType<AnimationDefaults>()?.curve ?? Curves.fastOutSlowIn;
+  }
+
+  @override
+  bool updateShouldNotify(AnimationDefaults oldWidget) => duration != oldWidget.duration || curve != oldWidget.curve;
+}
+
+/// Signature for methods that decode structured values from a [DataSource],
+/// such as the static methods of [ArgumentDecoders].
+///
+/// Used to make some of the methods of that class extensible.
+typedef ArgumentDecoder<T> = T Function(DataSource source, List<Object> key);
+
+/// A set of methods for decoding structured values from a [DataSource].
+///
+/// Specifically, these methods decode types that are used by local widgets
+/// (q.v. [createCoreWidgets]).
+///
+/// These methods take a [DataSource] and a `key`. The `key` is a path to the
+/// part of the [DataSource] that the value should be read from. This may
+/// identify a map, a list, or a leaf value, depending on the needs of the
+/// method.
+class ArgumentDecoders {
+  const ArgumentDecoders._();
+
+  /// This is a workaround for https://github.com/dart-lang/sdk/issues/47021
+  static const ArgumentDecoders __ = ArgumentDecoders._(); // ignore: unused_field
+
+  // (in alphabetical order)
+
+  /// Decodes an [AlignmentDirectional] or [Alignment] object out of the
+  /// specified map.
+  ///
+  /// If the map has `start` and `y` keys, then it is interpreted as an
+  /// [AlignmentDirectional] with those values. Otherwise if it has `x` and `y`
+  /// it's an [Alignment] with those values. Otherwise it returns null.
+  static AlignmentGeometry? alignment(DataSource source, List<Object> key) {
+    if (!source.isMap(key)) {
+      return null;
+    }
+    final double? x = source.v<double>([...key, 'x']);
+    final double? start = source.v<double>([...key, 'start']);
+    final double? y = source.v<double>([...key, 'y']);
+    if (x == null && start == null) {
+      return null;
+    }
+    if (y == null) {
+      return null;
+    }
+    if (start != null) {
+      return AlignmentDirectional(start, y);
+    }
+    x!;
+    return Alignment(x, y);
+  }
+
+  /// Decodes the specified map into a [BoxConstraints].
+  ///
+  /// The keys used are `minWidth`, `maxWidth`, `minHeight`, and `maxHeight`.
+  /// Omitted keys are defaulted to 0.0 for minimums and infinity for maximums.
+  static BoxConstraints? boxConstraints(DataSource source, List<Object> key) {
+    if (!source.isMap(key)) {
+      return null;
+    }
+    return BoxConstraints(
+      minWidth: source.v<double>([...key, 'minWidth']) ?? 0.0,
+      maxWidth: source.v<double>([...key, 'maxWidth']) ?? double.infinity,
+      minHeight: source.v<double>([...key, 'minHeight']) ?? 0.0,
+      maxHeight: source.v<double>([...key, 'maxHeight']) ?? double.infinity,
+    );
+  }
+
+  /// Returns a [BorderDirectional] from the specified list.
+  ///
+  /// The list is a list of values as interpreted by [borderSide]. An empty or
+  /// missing list results in a null return value. The list should have one
+  /// through four items. Extra items are ignored.
+  ///
+  /// The values are interpreted as follows:
+  ///
+  ///  * start: first value.
+  ///  * top: second value, defaulting to same as start.
+  ///  * end: third value, defaulting to same as start.
+  ///  * bottom: fourth value, defaulting to same as top.
+  static BoxBorder? border(DataSource source, List<Object> key) {
+    final BorderSide? a = borderSide(source, [...key, 0]);
+    if (a == null) {
+      return null;
+    }
+    final BorderSide? b = borderSide(source, [...key, 1]);
+    final BorderSide? c = borderSide(source, [...key, 2]);
+    final BorderSide? d = borderSide(source, [...key, 3]);
+    return BorderDirectional(
+      start: a,
+      top: b ?? a,
+      end: c ?? a,
+      bottom: d ?? b ?? a,
+    );
+  }
+
+  /// Returns a [BorderRadiusDirectional] from the specified list.
+  ///
+  /// The list is a list of values as interpreted by [radius]. An empty or
+  /// missing list results in a null return value. The list should have one
+  /// through four items. Extra items are ignored.
+  ///
+  /// The values are interpreted as follows:
+  ///
+  ///  * topStart: first value.
+  ///  * topEnd: second value, defaulting to same as topStart.
+  ///  * bottomStart: third value, defaulting to same as topStart.
+  ///  * bottomEnd: fourth value, defaulting to same as topEnd.
+  static BorderRadiusGeometry? borderRadius(DataSource source, List<Object> key) {
+    final Radius? a = radius(source, [...key, 0]);
+    if (a == null) {
+      return null;
+    }
+    final Radius? b = radius(source, [...key, 1]);
+    final Radius? c = radius(source, [...key, 2]);
+    final Radius? d = radius(source, [...key, 3]);
+    return BorderRadiusDirectional.only(
+      topStart: a,
+      topEnd: b ?? a,
+      bottomStart: c ?? a,
+      bottomEnd: d ?? b ?? a,
+    );
+  }
+
+  /// Returns a [BorderSide] from the specified map.
+  ///
+  /// If the map is absent, returns null.
+  ///
+  /// Otherwise (even if it has no keys), the [BorderSide] is created from the
+  /// keys `color` (see [color], defaults to black), `width` (a double, defaults
+  /// to 1.0), and `style` (see [enumValue] for [BorderStyle], defaults to
+  /// [BorderStyle.solid]).
+  static BorderSide? borderSide(DataSource source, List<Object> key) {
+    if (!source.isMap(key)) {
+      return null;
+    }
+    return BorderSide(
+      color: color(source, [...key, 'color']) ?? const Color(0xFF000000),
+      width: source.v<double>([...key, 'width']) ?? 1.0,
+      style: enumValue<BorderStyle>(BorderStyle.values, source, [...key, 'style']) ?? BorderStyle.solid,
+    );
+  }
+
+  /// Returns a [BoxShadow] from the specified map.
+  ///
+  /// If the map is absent, returns null.
+  ///
+  /// Otherwise (even if it has no keys), the [BoxShadow] is created from the
+  /// keys `color` (see [color], defaults to black), `offset` (see [offset],
+  /// defaults to [Offset.zero]), `blurRadius` (double, defaults to zero), and
+  /// `spreadRadius` (double, defaults to zero).
+  static BoxShadow boxShadow(DataSource source, List<Object> key) {
+    if (!source.isMap(key)) {
+      return const BoxShadow();
+    }
+    return BoxShadow(
+      color: color(source, [...key, 'color']) ?? const Color(0xFF000000),
+      offset: offset(source, [...key, 'offset']) ?? Offset.zero,
+      blurRadius: source.v<double>([...key, 'blurRadius']) ?? 0.0,
+      spreadRadius: source.v<double>([...key, 'spreadRadius']) ?? 0.0,
+    );
+  }
+
+  /// Returns a [Color] from the specified integer.
+  ///
+  /// Returns null if it's not an integer; otherwise, passes it to the [new
+  /// Color] constructor.
+  static Color? color(DataSource source, List<Object> key) {
+    final int? value = source.v<int>(key);
+    if (value == null) {
+      return null;
+    }
+    return Color(value);
+  }
+
+  /// Returns a [ColorFilter] from the specified map.
+  ///
+  /// The `type` key specifies the kind of filter.
+  ///
+  /// A type of `linearToSrgbGamma` creates a [ColorFilter.linearToSrgbGamma].
+  ///
+  /// A type of `matrix` creates a [ColorFilter.matrix], parsing the `matrix`
+  /// key as per [colorMatrix]). If there is no `matrix` key, returns null.
+  ///
+  /// A type of `mode` creates a [ColorFilter.mode], using the `color` key
+  /// (see[color], defaults to black) and the `blendMode` key (see [enumValue] for
+  /// [BlendMdoe], defaults to [BlendMode.srcOver])
+  ///
+  /// A type of `srgbToLinearGamma` creates a [ColorFilter.srgbToLinearGamma].
+  ///
+  /// If the type is none of these, but is not null, then the type is looked up
+  /// in [colorFilterDecoders], and if an entry is found, this method defers to
+  /// that callback.
+  ///
+  /// Otherwise, returns null.
+  static ColorFilter? colorFilter(DataSource source, List<Object> key) {
+    final String? type = source.v<String>([...key, 'type']);
+    switch (type) {
+      case null:
+        return null;
+      case 'linearToSrgbGamma':
+        return const ColorFilter.linearToSrgbGamma();
+      case 'matrix':
+        final List<double>? matrix = colorMatrix(source, [...key, 'matrix']);
+        if (matrix == null) {
+          return null;
+        }
+        return ColorFilter.matrix(matrix);
+      case 'mode':
+        return ColorFilter.mode(
+          color(source, [...key, 'color']) ?? const Color(0xFF000000),
+          enumValue<BlendMode>(BlendMode.values, source, [...key, 'blendMode']) ?? BlendMode.srcOver,
+        );
+      case 'srgbToLinearGamma':
+        return const ColorFilter.srgbToLinearGamma();
+      default:
+        final ArgumentDecoder<ColorFilter?>? decoder = colorFilterDecoders[type];
+        if (decoder == null) {
+          return null;
+        }
+        return decoder(source, key);
+    }
+  }
+
+  /// Extension mechanism for [colorFilter].
+  static final Map<String, ArgumentDecoder<ColorFilter?>> colorFilterDecoders = <String, ArgumentDecoder<ColorFilter?>>{};
+
+  /// Returns a list of 20 doubles from the specified list.
+  ///
+  /// If the specified key does not identify a list, returns null instead.
+  ///
+  /// If the list has fewer than 20 entries or if any of the entries are not
+  /// doubles, any entries that could not be obtained are replaced by zero.
+  ///
+  /// Used by [colorFilter] in the `matrix` mode.
+  static List<double>? colorMatrix(DataSource source, List<Object> key) {
+    if (!source.isList(key)) {
+      return null;
+    }
+    return <double>[
+      source.v<double>([...key, 0]) ?? 0.0,
+      source.v<double>([...key, 1]) ?? 0.0,
+      source.v<double>([...key, 2]) ?? 0.0,
+      source.v<double>([...key, 3]) ?? 0.0,
+      source.v<double>([...key, 4]) ?? 0.0,
+      source.v<double>([...key, 5]) ?? 0.0,
+      source.v<double>([...key, 6]) ?? 0.0,
+      source.v<double>([...key, 7]) ?? 0.0,
+      source.v<double>([...key, 8]) ?? 0.0,
+      source.v<double>([...key, 9]) ?? 0.0,
+      source.v<double>([...key, 10]) ?? 0.0,
+      source.v<double>([...key, 11]) ?? 0.0,
+      source.v<double>([...key, 12]) ?? 0.0,
+      source.v<double>([...key, 13]) ?? 0.0,
+      source.v<double>([...key, 14]) ?? 0.0,
+      source.v<double>([...key, 15]) ?? 0.0,
+      source.v<double>([...key, 16]) ?? 0.0,
+      source.v<double>([...key, 17]) ?? 0.0,
+      source.v<double>([...key, 18]) ?? 0.0,
+      source.v<double>([...key, 19]) ?? 0.0,
+    ];
+  }
+
+  /// Returns a [Color] from the specified integer.
+  ///
+  /// Returns black if it's not an integer; otherwise, passes it to the [new
+  /// Color] constructor.
+  ///
+  /// This is useful in situations where null is not acceptable, for example,
+  /// when providing a decoder to [list]. Otherwise, prefer using [DataSource.v]
+  /// directly.
+  static Color colorOrBlack(DataSource source, List<Object> key) {
+    return color(source, key) ?? const Color(0xFF000000);
+  }
+
+  /// Returns a [Curve] from the specified string.
+  ///
+  /// The given key should specify a string. If that string matches one of the
+  /// names of static curves defined in the [Curves] class, then that curve is
+  /// returned. Otherwise, if the string was not null, and is present as a key
+  /// in the [curveDecoders] map, then the matching decoder from that map is
+  /// invoked. Otherwise, the default obtained from [AnimationDefaults.curveOf]
+  /// is used (which is why a [BuildContext] is required).
+  static Curve curve(DataSource source, List<Object> key, BuildContext context) {
+    final String? type = source.v<String>(key);
+    switch (type) {
+      case 'linear':
+        return Curves.linear;
+      case 'decelerate':
+        return Curves.decelerate;
+      case 'fastLinearToSlowEaseIn':
+        return Curves.fastLinearToSlowEaseIn;
+      case 'ease':
+        return Curves.ease;
+      case 'easeIn':
+        return Curves.easeIn;
+      case 'easeInToLinear':
+        return Curves.easeInToLinear;
+      case 'easeInSine':
+        return Curves.easeInSine;
+      case 'easeInQuad':
+        return Curves.easeInQuad;
+      case 'easeInCubic':
+        return Curves.easeInCubic;
+      case 'easeInQuart':
+        return Curves.easeInQuart;
+      case 'easeInQuint':
+        return Curves.easeInQuint;
+      case 'easeInExpo':
+        return Curves.easeInExpo;
+      case 'easeInCirc':
+        return Curves.easeInCirc;
+      case 'easeInBack':
+        return Curves.easeInBack;
+      case 'easeOut':
+        return Curves.easeOut;
+      case 'linearToEaseOut':
+        return Curves.linearToEaseOut;
+      case 'easeOutSine':
+        return Curves.easeOutSine;
+      case 'easeOutQuad':
+        return Curves.easeOutQuad;
+      case 'easeOutCubic':
+        return Curves.easeOutCubic;
+      case 'easeOutQuart':
+        return Curves.easeOutQuart;
+      case 'easeOutQuint':
+        return Curves.easeOutQuint;
+      case 'easeOutExpo':
+        return Curves.easeOutExpo;
+      case 'easeOutCirc':
+        return Curves.easeOutCirc;
+      case 'easeOutBack':
+        return Curves.easeOutBack;
+      case 'easeInOut':
+        return Curves.easeInOut;
+      case 'easeInOutSine':
+        return Curves.easeInOutSine;
+      case 'easeInOutQuad':
+        return Curves.easeInOutQuad;
+      case 'easeInOutCubic':
+        return Curves.easeInOutCubic;
+      case 'easeInOutCubicEmphasized':
+        return Curves.easeInOutCubicEmphasized;
+      case 'easeInOutQuart':
+        return Curves.easeInOutQuart;
+      case 'easeInOutQuint':
+        return Curves.easeInOutQuint;
+      case 'easeInOutExpo':
+        return Curves.easeInOutExpo;
+      case 'easeInOutCirc':
+        return Curves.easeInOutCirc;
+      case 'easeInOutBack':
+        return Curves.easeInOutBack;
+      case 'fastOutSlowIn':
+        return Curves.fastOutSlowIn;
+      case 'slowMiddle':
+        return Curves.slowMiddle;
+      case 'bounceIn':
+        return Curves.bounceIn;
+      case 'bounceOut':
+        return Curves.bounceOut;
+      case 'bounceInOut':
+        return Curves.bounceInOut;
+      case 'elasticIn':
+        return Curves.elasticIn;
+      case 'elasticOut':
+        return Curves.elasticOut;
+      case 'elasticInOut':
+        return Curves.elasticInOut;
+      default:
+        if (type != null) {
+          final ArgumentDecoder<Curve>? decoder = curveDecoders[type];
+          if (decoder != null) {
+            return decoder(source, key);
+          }
+        }
+        return AnimationDefaults.curveOf(context);
+    }
+  }
+
+  /// Extension mechanism for [curve].
+  ///
+  /// The decoders must not return null.
+  ///
+  /// The given key will specify a string, which is known to not match any of
+  /// the values in [Curves].
+  static final Map<String, ArgumentDecoder<Curve>> curveDecoders = <String, ArgumentDecoder<Curve>>{};
+
+  /// Returns a [Decoration] from the specified map.
+  ///
+  /// The `type` key specifies the kind of decoration.
+  ///
+  /// A type of `box` creates a [BoxDecoration] using the keys `color`
+  /// ([color]), `image` ([decorationImage]), `border` ([border]),
+  /// `borderRadius` ([borderRadius]), `boxShadow` (a [list] of [boxShadow]),
+  /// `gradient` ([gradient]), `backgroundBlendMode` (an [enumValue] of [BlendMode]),
+  /// and `shape` (an [enumValue] of [BoxShape]), these keys each corresponding to
+  /// the properties of [BoxDecoration] with the same name.
+  ///
+  /// A type of `flutterLogo` creates a [FlutterLogoDecoration] using the keys
+  /// `color` ([color], corresponds to [FlutterLogoDecoration.textColor]),
+  /// `style` ([enumValue] of [FlutterLogoStyle], defaults to
+  /// [FlutterLogoStyle.markOnly]), and `margin` ([edgeInsets], always with a
+  /// left-to-right direction), the latter two keys corresponding to
+  /// the properties of [FlutterLogoDecoration] with the same name.
+  ///
+  /// A type of `shape` creates a [ShapeDecoration] using the keys `color`
+  /// ([color]), `image` ([decorationImage]), `gradient` ([gradient]), `shadows`
+  /// (a [list] of [boxShadow]), and `shape` ([shapeBorder]), these keys each
+  /// corresponding to the properties of [ShapeDecoration] with the same name.
+  ///
+  /// If the type is none of these, but is not null, then the type is looked up
+  /// in [decorationDecoders], and if an entry is found, this method defers to
+  /// that callback.
+  ///
+  /// Otherwise, returns null.
+  static Decoration? decoration(DataSource source, List<Object> key) {
+    final String? type = source.v<String>([...key, 'type']);
+    switch (type) {
+      case null:
+        return null;
+      case 'box':
+        return BoxDecoration(
+          color: color(source, [...key, 'color']),
+          image: decorationImage(source, [...key, 'image']),
+          border: border(source, [...key, 'border']),
+          borderRadius: borderRadius(source, [...key, 'borderRadius']),
+          boxShadow: list<BoxShadow>(source, [...key, 'boxShadow'], boxShadow),
+          gradient: gradient(source, [...key, 'gradient']),
+          backgroundBlendMode: enumValue<BlendMode>(BlendMode.values, source, [...key, 'backgroundBlendMode']),
+          shape: enumValue<BoxShape>(BoxShape.values, source, [...key, 'shape']) ?? BoxShape.rectangle,
+        );
+      case 'flutterLogo':
+        return FlutterLogoDecoration(
+          textColor: color(source, [...key, 'color']) ?? const Color(0xFF757575),
+          style: enumValue<FlutterLogoStyle>(FlutterLogoStyle.values, source, [...key, 'style']) ?? FlutterLogoStyle.markOnly,
+          margin: (edgeInsets(source, [...key, 'margin']) ?? EdgeInsets.zero).resolve(TextDirection.ltr),
+        );
+      case 'shape':
+        return ShapeDecoration(
+          color: color(source, [...key, 'color']),
+          image: decorationImage(source, [...key, 'image']),
+          gradient: gradient(source, [...key, 'gradient']),
+          shadows: list<BoxShadow>(source, [...key, 'shadows'], boxShadow),
+          shape: shapeBorder(source, [...key, 'shape']) ?? const Border(),
+        );
+      default:
+        final ArgumentDecoder<Decoration?>? decoder = decorationDecoders[type];
+        if (decoder == null) {
+          return null;
+        }
+        return decoder(source, key);
+    }
+  }
+
+  /// Extension mechanism for [decoration].
+  static final Map<String, ArgumentDecoder<Decoration?>> decorationDecoders = <String, ArgumentDecoder<Decoration?>>{};
+
+  /// Returns a [DecorationImage] from the specified map.
+  ///
+  /// The [DecorationImage.image] is determined by interpreting the same key as
+  /// per [imageProvider]. If that method returns null, then this returns null
+  /// also. Otherwise, the return value is used as the provider and additional
+  /// keys map to the identically-named properties of [DecorationImage]:
+  /// `onError` (must be an event handler; the payload map is augmented by an
+  /// `exception` key that contains the text serialization of the exception and
+  /// a `stackTrace` key that contains the stack trace, also as a string),
+  /// `colorFilter` ([colorFilter]), `fit` ([enumValue] of [BoxFit]), `alignment`
+  /// ([alignment], defaults to [Alignment.center]), `centerSlice` ([rect]),
+  /// `repeat` ([enumValue] of [ImageRepeat], defaults to [ImageRepeat.noRepeat]),
+  /// `matchTextDirection` (boolean, defaults to false).
+  static DecorationImage? decorationImage(DataSource source, List<Object> key) {
+    final ImageProvider? provider = imageProvider(source, key);
+    if (provider == null) {
+      return null;
+    }
+    return DecorationImage(
+      image: provider,
+      onError: (Object exception, StackTrace? stackTrace) {
+        final VoidCallback? handler = source.voidHandler([...key, 'onError'], { 'exception': exception.toString(), 'stackTrack': stackTrace.toString() });
+        if (handler != null) {
+          handler();
+        }
+      },
+      colorFilter: colorFilter(source, [...key, 'colorFilter']),
+      fit: enumValue<BoxFit>(BoxFit.values, source, [...key, 'fit']),
+      alignment: alignment(source, [...key, 'alignment']) ?? Alignment.center,
+      centerSlice: rect(source, [...key, 'centerSlice']),
+      repeat: enumValue<ImageRepeat>(ImageRepeat.values, source, [...key, 'repeat']) ?? ImageRepeat.noRepeat,
+      matchTextDirection: source.v<bool>([...key, 'matchTextDirection']) ?? false,
+    );
+  }
+
+  /// Returns a double from the specified double.
+  ///
+  /// Returns 0.0 if it's not a double.
+  ///
+  /// This is useful in situations where null is not acceptable, for example,
+  /// when providing a decoder to [list]. Otherwise, prefer using [DataSource.v]
+  /// directly.
+  static double doubleOrZero(DataSource source, List<Object> key) {
+    return source.v<double>(key) ?? 0.0;
+  }
+
+  /// Returns a [Duration] from the specified integer.
+  ///
+  /// If it's not an integer, the default obtained from
+  /// [AnimationDefaults.durationOf] is used (which is why a [BuildContext] is
+  /// required).
+  static Duration duration(DataSource source, List<Object> key, BuildContext context) {
+    final int? value = source.v<int>(key);
+    if (value == null) {
+      return AnimationDefaults.durationOf(context);
+    }
+    return Duration(milliseconds: value);
+  }
+
+  /// Returns an [EdgeInsetsDirectional] from the specified list.
+  ///
+  /// The list is a list of doubles. An empty or missing list results in a null
+  /// return value. The list should have one through four items. Extra items are
+  /// ignored.
+  ///
+  /// The values are interpreted as follows:
+  ///
+  ///  * start: first value.
+  ///  * top: second value, defaulting to same as start.
+  ///  * end: third value, defaulting to same as start.
+  ///  * bottom: fourth value, defaulting to same as top.
+  static EdgeInsetsGeometry? edgeInsets(DataSource source, List<Object> key) {
+    final double? a = source.v<double>([...key, 0]);
+    if (a == null) {
+      return null;
+    }
+    final double? b = source.v<double>([...key, 1]);
+    final double? c = source.v<double>([...key, 2]);
+    final double? d = source.v<double>([...key, 3]);
+    return EdgeInsetsDirectional.fromSTEB(
+      a,
+      b ?? a,
+      c ?? a,
+      d ?? b ?? a,
+    );
+  }
+
+  /// Returns one of the values of the specified enum `T`, from the specified string.
+  ///
+  /// The string must match the name of the enum value, excluding the enum type
+  /// name (the part of its [toString] after the dot).
+  ///
+  /// The first argument must be the `values` list for that enum; this is the
+  /// list of values that is searched.
+  ///
+  /// For example, `enumValue<TileMode>(TileMode.values, source, ['tileMode']) ??
+  /// TileMode.clamp` reads the `tileMode` key of `source`, and looks for the
+  /// first match in [TileMode.values], defaulting to [TileMode.clamp] if
+  /// nothing matches; thus, the string `mirror` would return [TileMode.mirror].
+  static T? enumValue<T>(List<T> values, DataSource source, List<Object> key) {
+    final String? value = source.v<String>(key);
+    if (value == null) {
+      return null;
+    }
+    for (int index = 0; index < values.length; index += 1) {
+      if (value == values[index].toString().split('.').last) {
+        return values[index];
+      }
+    }
+    return null;
+  }
+
+  /// Returns a [FontFeature] from the specified map.
+  ///
+  /// The `feature` key is used as the font feature name (defaulting to the
+  /// probably-useless private value "NONE"), and the `value` key is used as the
+  /// value (defaulting to 1, which typically means "enabled").
+  ///
+  /// As this never returns null, it is possible to use it with [list].
+  static FontFeature fontFeature(DataSource source, List<Object> key) {
+    return FontFeature(source.v<String>([...key, 'feature']) ?? 'NONE', source.v<int>([...key, 'value']) ?? 1);
+  }
+
+  /// Returns a [Gradient] from the specified map.
+  ///
+  /// The `type` key specifies the kind of gradient.
+  ///
+  /// A type of `linear` creates a [LinearGradient] using the keys `begin`
+  /// ([alignment], defaults to [Alignment.centerLeft]), `end` ([alignment],
+  /// defaults to [Alignment.centerRight]), `colors` ([list] of [colorOrBlack],
+  /// defaults to a two-element list with black and white), `stops` ([list] of
+  /// [doubleOrZero]), and `tileMode` ([enumValue] of [TileMode], defaults to
+  /// [TileMode.clamp]), these keys each corresponding to the properties of
+  /// [BoxDecoration] with the same name.
+  ///
+  /// A type of `radial` creates a [RadialGradient] using the keys `center`
+  /// ([alignment], defaults to [Alignment.center]), `radius' (double, defaults
+  /// to 0.5), `colors` ([list] of [colorOrBlack], defaults to a two-element
+  /// list with black and white), `stops` ([list] of [doubleOrZero]), `tileMode`
+  /// ([enumValue] of [TileMode], defaults to [TileMode.clamp]), `focal`
+  /// (([alignment]), and `focalRadius` (double, defaults to zero), these keys
+  /// each corresponding to the properties of [BoxDecoration] with the same
+  /// name.
+  ///
+  /// A type of `linear` creates a [LinearGradient] using the keys `center`
+  /// ([alignment], defaults to [Alignment.center]), `startAngle` (double,
+  /// defaults to 0.0), `endAngle` (double, defaults to 2π), `colors` ([list] of
+  /// [colorOrBlack], defaults to a two-element list with black and white),
+  /// `stops` ([list] of [doubleOrZero]), and `tileMode` ([enumValue] of [TileMode],
+  /// defaults to [TileMode.clamp]), these keys each corresponding to the
+  /// properties of [BoxDecoration] with the same name.
+  ///
+  /// The `transform` property of these gradient classes is not supported.
+  // TODO(ianh): https://github.com/flutter/flutter/issues/87208
+  ///
+  /// If the type is none of these, but is not null, then the type is looked up
+  /// in [gradientDecoders], and if an entry is found, this method defers to
+  /// that callback.
+  ///
+  /// Otherwise, returns null.
+  static Gradient? gradient(DataSource source, List<Object> key) {
+    final String? type = source.v<String>([...key, 'type']);
+    switch (type) {
+      case null:
+        return null;
+      case 'linear':
+        return LinearGradient(
+          begin: alignment(source, [...key, 'begin']) ?? Alignment.centerLeft,
+          end: alignment(source, [...key, 'end']) ?? Alignment.centerRight,
+          colors: list<Color>(source, [...key, 'colors'], colorOrBlack) ?? const <Color>[Color(0xFF000000), Color(0xFFFFFFFF)],
+          stops: list<double>(source, [...key, 'stops'], doubleOrZero),
+          tileMode: enumValue<TileMode>(TileMode.values, source, [...key, 'tileMode']) ?? TileMode.clamp,
+          // transform: GradientTransformMatrix(matrix(source, [...key, 'transform'])), // blocked by https://github.com/flutter/flutter/issues/87208
+        );
+      case 'radial':
+        return RadialGradient(
+          center: alignment(source, [...key, 'center']) ?? Alignment.center,
+          radius: source.v<double>([...key, 'radius']) ?? 0.5,
+          colors: list<Color>(source, [...key, 'colors'], colorOrBlack) ?? const <Color>[Color(0xFF000000), Color(0xFFFFFFFF)],
+          stops: list<double>(source, [...key, 'stops'], doubleOrZero),
+          tileMode: enumValue<TileMode>(TileMode.values, source, [...key, 'tileMode']) ?? TileMode.clamp,
+          focal: alignment(source, [...key, 'focal']),
+          focalRadius: source.v<double>([...key, 'focalRadius']) ?? 0.0,
+          // transform: GradientTransformMatrix(matrix(source, [...key, 'transform'])), // blocked by https://github.com/flutter/flutter/issues/87208
+        );
+      case 'sweep':
+        return SweepGradient(
+          center: alignment(source, [...key, 'center']) ?? Alignment.center,
+          startAngle: source.v<double>([...key, 'startAngle']) ?? 0.0,
+          endAngle: source.v<double>([...key, 'endAngle']) ?? math.pi * 2,
+          colors: list<Color>(source, [...key, 'colors'], colorOrBlack) ?? const <Color>[Color(0xFF000000), Color(0xFFFFFFFF)],
+          stops: list<double>(source, [...key, 'stops'], doubleOrZero),
+          tileMode: enumValue<TileMode>(TileMode.values, source, [...key, 'tileMode']) ?? TileMode.clamp,
+          // transform: GradientTransformMatrix(matrix(source, [...key, 'transform'])), // blocked by https://github.com/flutter/flutter/issues/87208
+        );
+      default:
+        final ArgumentDecoder<Gradient?>? decoder = gradientDecoders[type];
+        if (decoder == null) {
+          return null;
+        }
+        return decoder(source, key);
+    }
+  }
+
+  /// Extension mechanism for [gradient].
+  static final Map<String, ArgumentDecoder<Gradient?>> gradientDecoders = <String, ArgumentDecoder<Gradient?>>{};
+
+  /// Returns a [SliverGridDelegate] from the specified map.
+  ///
+  /// The `type` key specifies the kind of grid delegate.
+  ///
+  /// A type of `fixedCrossAxisCount` creates a
+  /// [SliverGridDelegateWithFixedCrossAxisCount] using the keys
+  /// `crossAxisCount`, `mainAxisSpacing`, `crossAxisSpacing`,
+  /// `childAspectRatio`, and `mainAxisExtent`.
+  ///
+  /// A type of `maxCrossAxisExtent` creates a
+  /// [SliverGridDelegateWithMaxCrossAxisExtent] using the keys
+  /// maxCrossAxisExtent:`, `mainAxisSpacing`, `crossAxisSpacing`,
+  /// `childAspectRatio`, and `mainAxisExtent`.
+  ///
+  /// The types (int or double) and defaults for these keys match the
+  /// identically named arguments to the default constructors of those classes.
+  ///
+  /// If the type is none of these, but is not null, then the type is looked up
+  /// in [gridDelegateDecoders], and if an entry is found, this method defers to
+  /// that callback.
+  ///
+  /// Otherwise, returns null.
+  static SliverGridDelegate? gridDelegate(DataSource source, List<Object> key) {
+    final String? type = source.v<String>([...key, 'type']);
+    switch (type) {
+      case null:
+        return null;
+      case 'fixedCrossAxisCount':
+        return SliverGridDelegateWithFixedCrossAxisCount(
+          crossAxisCount: source.v<int>([...key, 'crossAxisCount']) ?? 2,
+          mainAxisSpacing: source.v<double>([...key, 'mainAxisSpacing']) ?? 0.0,
+          crossAxisSpacing: source.v<double>([...key, 'crossAxisSpacing']) ?? 0.0,
+          childAspectRatio: source.v<double>([...key, 'childAspectRatio']) ?? 1.0,
+          mainAxisExtent: source.v<double>([...key, 'mainAxisExtent']),
+        );
+      case 'maxCrossAxisExtent':
+        return SliverGridDelegateWithMaxCrossAxisExtent(
+          maxCrossAxisExtent: source.v<double>([...key, 'maxCrossAxisExtent']) ?? 100.0,
+          mainAxisSpacing: source.v<double>([...key, 'mainAxisSpacing']) ?? 0.0,
+          crossAxisSpacing: source.v<double>([...key, 'crossAxisSpacing']) ?? 0.0,
+          childAspectRatio: source.v<double>([...key, 'childAspectRatio']) ?? 1.0,
+          mainAxisExtent: source.v<double>([...key, 'mainAxisExtent']),
+        );
+      default:
+        final ArgumentDecoder<SliverGridDelegate?>? decoder = gridDelegateDecoders[type];
+        if (decoder == null) {
+          return null;
+        }
+        return decoder(source, key);
+    }
+  }
+
+  /// Extension mechanism for [gridDelegate].
+  static final Map<String, ArgumentDecoder<SliverGridDelegate?>> gridDelegateDecoders = <String, ArgumentDecoder<SliverGridDelegate?>>{};
+
+  /// Returns an [IconData] from the specified map.
+  ///
+  /// If the map does not have an `icon` key that is an integer, returns null.
+  ///
+  /// Otherwise, returns an [IconData] with the [IconData.codePoint] set to the
+  /// integer from the `icon` key, the [IconData.fontFamily] set to the string
+  /// from the `fontFamily` key, and the [IconData.matchTextDirection] set to
+  /// the boolean from the `matchTextDirection` key (defaulting to false).
+  ///
+  /// For Material Design icons (those from the [Icons] class), the code point
+  /// can be obtained from the documentation for the icon, and the font family
+  /// is `MaterialIcons`. For example, [Icons.chalet] would correspond to
+  /// `{ icon: 0xe14f, fontFamily: 'MaterialIcons' }`.
+  ///
+  /// When building the release build of an application that uses the RFW
+  /// package, because this method creates non-const [IconData] objects
+  /// dynamically, the `--no-tree-shake-icons` option must be used.
+  static IconData? iconData(DataSource source, List<Object> key) {
+    final int? icon = source.v<int>([...key, 'icon']);
+    if (icon == null) {
+      return null;
+    }
+    return IconData(
+      icon,
+      fontFamily: source.v<String>([...key, 'fontFamily']),
+      matchTextDirection: source.v<bool>([...key, 'matchTextDirection']) ?? false,
+    );
+  }
+
+  /// Returns an [IconThemeData] from the specified map.
+  ///
+  /// If the map is absent, returns null.
+  ///
+  /// Otherwise (even if it has no keys), the [IconThemeData] is created from
+  /// the following keys: 'color` ([color]), `opacity` (double), `size`
+  /// (double).
+  static IconThemeData? iconThemeData(DataSource source, List<Object> key) {
+    if (!source.isMap(key)) {
+      return null;
+    }
+    return IconThemeData(
+      color: color(source, [...key, 'color']),
+      opacity: source.v<double>([...key, 'opacity']),
+      size: source.v<double>([...key, 'size']),
+    );
+  }
+
+  /// Returns an [ImageProvider] from the specifed map.
+  ///
+  /// The `source` key of the specified map is controlling. It must be a string.
+  /// If its value is one of the keys in [imageProviderDecoders], then the
+  /// relevant decoder is invoked and its return value is used (even if it is
+  /// null).
+  ///
+  /// Otherwise, if the `source` key gives an absolute URL (one with a scheme),
+  /// then a [NetworkImage] with that URL is returned. Its scale is given by the
+  /// `scale` key (double, defaults to 1.0).
+  ///
+  /// Otherwise, if the `source` key gives a relative URL (i.e. it can be parsed
+  /// as a URL and has no scheme), an [AssetImage] with the name given by the
+  /// `source` key is returned.
+  ///
+  /// Otherwise, if there is no `source` key in the map, or if that cannot be
+  /// parsed as a URL (absolute or relative), null is returned.
+  static ImageProvider? imageProvider(DataSource source, List<Object> key) {
+    final String? image = source.v<String>([...key, 'source']);
+    if (image == null) {
+      return null;
+    }
+    if (imageProviderDecoders.containsKey(image)) {
+      return imageProviderDecoders[image]!(source, key);
+    }
+    final Uri? imageUrl = Uri.tryParse(image);
+    if (imageUrl == null) {
+      return null;
+    }
+    if (!imageUrl.hasScheme) {
+      return AssetImage(image);
+    }
+    return NetworkImage(image, scale: source.v<double>([...key, 'scale']) ?? 1.0);
+  }
+
+  /// Extension mechanism for [imageProvider].
+  static final Map<String, ArgumentDecoder<ImageProvider?>> imageProviderDecoders = <String, ArgumentDecoder<ImageProvider?>>{};
+
+  /// Returns a [List] of `T` values from the specified list, using the given
+  /// `decoder` to parse each value.
+  ///
+  /// If the list is absent _or empty_, returns null (not an empty list).
+  ///
+  /// Otherwise, returns a list with as many items as the specified list, with
+  /// each entry in the list decoded using `decoder`.
+  ///
+  /// If `T` is non-nullable, the decoder must also be non-nullable.
+  static List<T>? list<T>(DataSource source, List<Object> key, ArgumentDecoder<T> decoder) {
+    final int count = source.length(key);
+    if (count == 0) {
+      return null;
+    }
+    return List<T>.generate(count, (int index) {
+      return decoder(source, [...key, index]);
+    });
+  }
+
+  /// Returns a [Locale] from the specified string.
+  ///
+  /// The string is split on hyphens ("-").
+  ///
+  /// If the string is null, returns null.
+  ///
+  /// If there is no hyphen in the list, uses the one-argument form of [new
+  /// Locale], passing the whole string.
+  ///
+  /// If there is one hyphen in the list, uses the two-argument form of [new
+  /// Locale], passing the parts before and after the hyphen respectively.
+  ///
+  /// If there are two or more hyphens, uses the [new Locale.fromSubtags]
+  /// constructor.
+  static Locale? locale(DataSource source, List<Object> key) {
+    final String? value = source.v<String>(key);
+    if (value == null) {
+      return null;
+    }
+    final List<String> subtags = value.split('-');
+    if (subtags.isEmpty) {
+      return null;
+    }
+    if (subtags.length == 1) {
+      return Locale(value);
+    }
+    if (subtags.length == 2) {
+      return Locale(subtags[0], subtags[1]);
+    }
+    // TODO(ianh): verify this is correct (I tried looking up the Unicode spec but it was... confusing)
+    return Locale.fromSubtags(languageCode: subtags[0], scriptCode: subtags[1], countryCode: subtags[2]);
+  }
+
+  /// Returns a list of 16 doubles from the specified list.
+  ///
+  /// If the list is missing or has fewer than 16 entries, returns null.
+  ///
+  /// Otherwise, returns a list of 16 entries, corresponding to the first 16
+  /// entries of the specified list, with any non-double values replaced by 0.0.
+  static Matrix4? matrix(DataSource source, List<Object> key) {
+    final double? arg15 = source.v<double>([...key, 15]);
+    if (arg15 == null) {
+      return null;
+    }
+    return Matrix4(
+      source.v<double>([...key, 0]) ?? 0.0,
+      source.v<double>([...key, 1]) ?? 0.0,
+      source.v<double>([...key, 2]) ?? 0.0,
+      source.v<double>([...key, 3]) ?? 0.0,
+      source.v<double>([...key, 4]) ?? 0.0,
+      source.v<double>([...key, 5]) ?? 0.0,
+      source.v<double>([...key, 6]) ?? 0.0,
+      source.v<double>([...key, 7]) ?? 0.0,
+      source.v<double>([...key, 8]) ?? 0.0,
+      source.v<double>([...key, 9]) ?? 0.0,
+      source.v<double>([...key, 10]) ?? 0.0,
+      source.v<double>([...key, 11]) ?? 0.0,
+      source.v<double>([...key, 12]) ?? 0.0,
+      source.v<double>([...key, 13]) ?? 0.0,
+      source.v<double>([...key, 14]) ?? 0.0,
+      arg15,
+    );
+  }
+
+  /// Returns a [MaskFilter] from the specified map.
+  ///
+  /// The `type` key specifies the kind of mask filter.
+  ///
+  /// A type of `blur` creates a [MaskFilter.blur]. The `style` key ([enumValue] of
+  /// [BlurStyle], defaults to [BlurStyle.normal]) is used as the blur style,
+  /// and the `sigma` key (double, defaults to 1.0) is used as the blur sigma.
+  ///
+  /// If the type is none of these, but is not null, then the type is looked up
+  /// in [maskFilterDecoders], and if an entry is found, this method defers to
+  /// that callback.
+  ///
+  /// Otherwise, returns null.
+  static MaskFilter? maskFilter(DataSource source, List<Object> key) {
+    final String? type = source.v<String>([...key, 'type']);
+    switch (type) {
+      case null:
+        return null;
+      case 'blur':
+        return MaskFilter.blur(
+          enumValue<BlurStyle>(BlurStyle.values, source, [...key, 'style']) ?? BlurStyle.normal,
+          source.v<double>([...key, 'sigma']) ?? 1.0,
+        );
+      default:
+        final ArgumentDecoder<MaskFilter?>? decoder = maskFilterDecoders[type];
+        if (decoder == null) {
+          return null;
+        }
+        return decoder(source, key);
+    }
+  }
+
+  /// Extension mechanism for [maskFilter].
+  static final Map<String, ArgumentDecoder<MaskFilter?>> maskFilterDecoders = <String, ArgumentDecoder<MaskFilter?>>{};
+
+  /// Returns an [Offset] from the specified map.
+  ///
+  /// The map must have an `x` key and a `y` key, doubles.
+  static Offset? offset(DataSource source, List<Object> key) {
+    final double? x = source.v<double>([...key, 'x']);
+    if (x == null) {
+      return null;
+    }
+    final double? y = source.v<double>([...key, 'y']);
+    if (y == null) {
+      return null;
+    }
+    return Offset(x, y);
+  }
+
+  /// Returns a [Paint] from the specified map.
+  ///
+  /// If the map is absent, returns null.
+  ///
+  /// Otherwise (even if it has no keys), a new [Paint] is created and its
+  /// properties are set according to the identically-named properties of the
+  /// map, as follows:
+  ///
+  ///  * `blendMode`: [enumValue] of [BlendMode].
+  ///  * `color`: [color].
+  ///  * `colorFilter`: [colorFilter].
+  ///  * `filterQuality`: [enumValue] of [FilterQuality].
+  //  * `imageFilter`: [imageFilter].
+  //  * `invertColors`: boolean.
+  ///  * `isAntiAlias`: boolean.
+  ///  * `maskFilter`: [maskFilter].
+  ///  * `shader`: [shader].
+  //  * `strokeCap`: [enumValue] of [StrokeCap].
+  //  * `strokeJoin`: [enumValue] of [StrokeJoin].
+  //  * `strokeMiterLimit`: double.
+  //  * `strokeWidth`: double.
+  //  * `style`: [enumValue] of [PaintingStyle].
+  ///
+  /// (Some values are not supported, because there is no way for them to be
+  /// used currently in RFW contexts.)
+  static Paint? paint(DataSource source, List<Object> key) {
+    if (!source.isMap(key)) {
+      return null;
+    }
+    final Paint result = Paint();
+    final BlendMode? paintBlendMode = enumValue<BlendMode>(BlendMode.values, source, [...key, 'blendMode']);
+    if (paintBlendMode != null) {
+      result.blendMode = paintBlendMode;
+    }
+    final Color? paintColor = color(source, [...key, 'color']);
+    if (paintColor != null) {
+      result.color = paintColor;
+    }
+    final ColorFilter? paintColorFilter = colorFilter(source, [...key, 'colorFilter']);
+    if (paintColorFilter != null) {
+      result.colorFilter = paintColorFilter;
+    }
+    final FilterQuality? paintFilterQuality = enumValue<FilterQuality>(FilterQuality.values, source, [...key, 'filterQuality']);
+    if (paintFilterQuality != null) {
+      result.filterQuality = paintFilterQuality;
+    }
+    // final ImageFilter? paintImageFilter = imageFilter(source, [...key, 'imageFilter']);
+    // if (paintImageFilter != null) {
+    //   result.imageFilter = paintImageFilter;
+    // }
+    // final bool? paintInvertColors = source.v<bool>([...key, 'invertColors']);
+    // if (paintInvertColors != null) {
+    //   result.invertColors = paintInvertColors;
+    // }
+    final bool? paintIsAntiAlias = source.v<bool>([...key, 'isAntiAlias']);
+    if (paintIsAntiAlias != null) {
+      result.isAntiAlias = paintIsAntiAlias;
+    }
+    final MaskFilter? paintMaskFilter = maskFilter(source, [...key, 'maskFilter']);
+    if (paintMaskFilter != null) {
+      result.maskFilter = paintMaskFilter;
+    }
+    final Shader? paintShader = shader(source, [...key, 'shader']);
+    if (paintShader != null) {
+      result.shader = paintShader;
+    }
+    // final StrokeCap? paintStrokeCap = enumValue<StrokeCap>(StrokeCap.values, source, [...key, 'strokeCap']),
+    // if (paintStrokeCap != null) {
+    //   result.strokeCap = paintStrokeCap;
+    // }
+    // final StrokeJoin? paintStrokeJoin = enumValue<StrokeJoin>(StrokeJoin.values, source, [...key, 'strokeJoin']),
+    // if (paintStrokeJoin != null) {
+    //   result.strokeJoin = paintStrokeJoin;
+    // }
+    // final double paintStrokeMiterLimit? = source.v<double>([...key, 'strokeMiterLimit']),
+    // if (paintStrokeMiterLimit != null) {
+    //   result.strokeMiterLimit = paintStrokeMiterLimit;
+    // }
+    // final double paintStrokeWidth? = source.v<double>([...key, 'strokeWidth']),
+    // if (paintStrokeWidth != null) {
+    //   result.strokeWidth = paintStrokeWidth;
+    // }
+    // final PaintingStyle? paintStyle = enumValue<PaintingStyle>(PaintingStyle.values, source, [...key, 'style']),
+    // if (paintStyle != null) {
+    //   result.style = paintStyle;
+    // }
+    return result;
+  }
+
+  /// Returns a [Radius] from the specified map.
+  ///
+  /// The map must have an `x` value corresponding to [Radius.x], and may have a
+  /// `y` value corresponding to [Radius.y].
+  ///
+  /// If the map only has an `x` key, the `y` value is assumed to be the same
+  /// (as in [Radius.circular]).
+  ///
+  /// If the `x` key is absent, the returned value is null.
+  static Radius? radius(DataSource source, List<Object> key) {
+    final double? x = source.v<double>([...key, 'x']);
+    if (x == null) {
+      return null;
+    }
+    final double y = source.v<double>([...key, 'y']) ?? x;
+    return Radius.elliptical(x, y);
+  }
+
+  /// Returns a [Rect] from the specified map.
+  ///
+  /// If the map is absent, returns null.
+  ///
+  /// Otherwise, returns a [Rect.fromLTWH] whose x, y, width, and height
+  /// components are determined from the `x`, `y`, `w`, and `h` properties of
+  /// the map, with missing (or non-double) values replaced by zeros.
+  static Rect? rect(DataSource source, List<Object> key) {
+    if (!source.isMap(key)) {
+      return null;
+    }
+    final double x = source.v<double>([...key, 'x']) ?? 0.0;
+    final double y = source.v<double>([...key, 'y']) ?? 0.0;
+    final double w = source.v<double>([...key, 'w']) ?? 0.0;
+    final double h = source.v<double>([...key, 'h']) ?? 0.0;
+    return Rect.fromLTWH(x, y, w, h);
+  }
+
+  /// Returns a [ShapeBorder] from the specified map or list.
+  ///
+  /// If the key identifies a list, then each entry in the list is decoded by
+  /// recursively invoking [shapeBorder], and the result is the combination of
+  /// those [ShapeBorder] values as obtained using the [ShapeBorder.+] operator.
+  ///
+  /// Otherwise, if the key identifies a map with a `type` value, the map is
+  /// interpreted according to the `type` as follows:
+  ///
+  ///  * `box`: the map's `sides` key is interpreted as a list by [border] and
+  ///     the resulting [BoxBorder] (actually, [BorderDirectional]) is returned.
+  ///
+  ///  * `beveled`: a [BeveledRectangleBorder] is returned; the `side` key is
+  ///    interpreted by [borderSide] to set the [BeveledRectangleBorder.side]
+  ///    (default of [BorderSide.none)), and the `borderRadius` key is
+  ///    interpreted by [borderRadius] to set the
+  ///    [BeveledRectangleBorder.borderRadius] (default [BorderRadius.zero]).
+  ///
+  ///  * `circle`: a [CircleBorder] is returned; the `side` key is interpreted
+  ///    by [borderSide] to set the [BeveledRectangleBorder.side] (default of
+  ///    [BorderSide.none)).
+  ///
+  ///  * `continuous`: a [ContinuousRectangleBorder] is returned; the `side` key
+  ///    is interpreted by [borderSide] to set the [BeveledRectangleBorder.side]
+  ///    (default of [BorderSide.none)), and the `borderRadius` key is
+  ///    interpreted by [borderRadius] to set the
+  ///    [BeveledRectangleBorder.borderRadius] (default [BorderRadius.zero]).
+  ///
+  ///  * `rounded`: a [RoundedRectangleBorder] is returned; the `side` key is
+  ///    interpreted by [borderSide] to set the [BeveledRectangleBorder.side]
+  ///    (default of [BorderSide.none)), and the `borderRadius` key is
+  ///    interpreted by [borderRadius] to set the
+  ///    [BeveledRectangleBorder.borderRadius] (default [BorderRadius.zero]).
+  ///
+  ///  * `stadium`: a [StadiumBorder] is returned; the `side` key is interpreted
+  ///    by [borderSide] to set the [BeveledRectangleBorder.side] (default of
+  ///    [BorderSide.none)).
+  ///
+  /// If the type is none of these, then the type is looked up in
+  /// [shapeBorderDecoders], and if an entry is found, this method defers to
+  /// that callback.
+  ///
+  /// Otherwise, if type is null or is not found in [shapeBorderDecoders], returns null.
+  static ShapeBorder? shapeBorder(DataSource source, List<Object> key) {
+    final List<ShapeBorder?>? shapes = list<ShapeBorder?>(source, key, shapeBorder);
+    if (shapes != null) {
+      return shapes.where((ShapeBorder? a) => a != null).reduce((ShapeBorder? a, ShapeBorder? b) => a! + b!);
+    }
+    final String? type = source.v<String>([...key, 'type']);
+    switch (type) {
+      case null:
+        return null;
+      case 'box':
+        return border(source, [...key, 'sides']) ?? const Border();
+      case 'beveled':
+        return BeveledRectangleBorder(
+          side: borderSide(source, [...key, 'side']) ?? BorderSide.none,
+          borderRadius: borderRadius(source, [...key, 'borderRadius']) ?? BorderRadius.zero,
+        );
+      case 'circle':
+        return CircleBorder(
+          side: borderSide(source, [...key, 'side']) ?? BorderSide.none,
+        );
+      case 'continuous':
+        return ContinuousRectangleBorder(
+          side: borderSide(source, [...key, 'side']) ?? BorderSide.none,
+          borderRadius: borderRadius(source, [...key, 'borderRadius']) ?? BorderRadius.zero,
+        );
+      case 'rounded':
+        return RoundedRectangleBorder(
+          side: borderSide(source, [...key, 'side']) ?? BorderSide.none,
+          borderRadius: borderRadius(source, [...key, 'borderRadius']) ?? BorderRadius.zero,
+        );
+      case 'stadium':
+        return StadiumBorder(
+          side: borderSide(source, [...key, 'side']) ?? BorderSide.none,
+        );
+      default:
+        final ArgumentDecoder<ShapeBorder>? decoder = shapeBorderDecoders[type];
+        if (decoder == null) {
+          return null;
+        }
+        return decoder(source, key);
+    }
+  }
+
+  /// Extension mechanism for [shapeBorder].
+  static final Map<String, ArgumentDecoder<ShapeBorder>> shapeBorderDecoders = <String, ArgumentDecoder<ShapeBorder>>{};
+
+  /// Returns a [Shader] based on the specified map.
+  ///
+  /// The `type` key specifies the kind of shader.
+  ///
+  /// A type of `linear`, `radial`, or `sweep` is interpreted as described by
+  /// [gradient]; then, the gradient is compiled to a shader by applying the
+  /// `rect` (interpreted by [rect]) and `textDirection` (interpreted as an
+  /// [enumValue] of [TextDirection]) using the [Gradient.createShader] method.
+  ///
+  /// If the type is none of these, but is not null, then the type is looked up
+  /// in [shaderDecoders], and if an entry is found, this method defers to
+  /// that callback.
+  ///
+  /// Otherwise, returns null.
+  static Shader? shader(DataSource source, List<Object> key) {
+    final String? type = source.v<String>([...key, 'type']);
+    switch (type) {
+      case null:
+        return null;
+      case 'linear':
+      case 'radial':
+      case 'sweep':
+        return gradient(source, key)!.createShader(
+          rect(source, [...key, 'rect']) ?? Rect.zero,
+          textDirection: enumValue<TextDirection>(TextDirection.values, source, ['textDirection']) ?? TextDirection.ltr,
+        );
+      default:
+        final ArgumentDecoder<Shader?>? decoder = shaderDecoders[type];
+        if (decoder == null) {
+          return null;
+        }
+        return decoder(source, key);
+    }
+  }
+
+  /// Extension mechanism for [shader].
+  static final Map<String, ArgumentDecoder<Shader?>> shaderDecoders = <String, ArgumentDecoder<Shader?>>{};
+
+  /// Returns a string from the specified string.
+  ///
+  /// Returns the empty string if it's not a string.
+  ///
+  /// This is useful in situations where null is not acceptable, for example,
+  /// when providing a decoder to [list]. Otherwise, prefer using [DataSource.v]
+  /// directly.
+  static String string(DataSource source, List<Object> key) {
+    return source.v<String>(key) ?? '';
+  }
+
+  /// Returns a [StrutStyle] from the specified map.
+  ///
+  /// If the map is absent, returns null.
+  ///
+  /// Otherwise (even if it has no keys), the [StrutStyle] is created from the
+  /// following keys: 'fontFamily` (string), `fontFamilyFallback` ([list] of
+  /// [string]), `fontSize` (double), `height` (double), `leadingDistribution`
+  /// ([enumValue] of [TextLeadingDistribution]), `leading` (double),
+  /// `fontWeight` ([enumValue] of [FontWeight]), `fontStyle` ([enumValue] of
+  /// [FontStyle]), `forceStrutHeight` (boolean).
+  static StrutStyle? strutStyle(DataSource source, List<Object> key) {
+    if (!source.isMap(key)) {
+      return null;
+    }
+    return StrutStyle(
+      fontFamily: source.v<String>([...key, 'fontFamily']),
+      fontFamilyFallback: list<String>(source, [...key, 'fontFamilyFallback'], string),
+      fontSize: source.v<double>([...key, 'fontSize']),
+      height: source.v<double>([...key, 'height']),
+      leadingDistribution: enumValue<TextLeadingDistribution>(TextLeadingDistribution.values, source, [...key, 'leadingDistribution']),
+      leading: source.v<double>([...key, 'leading']),
+      fontWeight: enumValue<FontWeight>(FontWeight.values, source, [...key, 'fontWeight']),
+      fontStyle: enumValue<FontStyle>(FontStyle.values, source, [...key, 'fontStyle']),
+      forceStrutHeight: source.v<bool>([...key, 'forceStrutHeight']),
+    );
+  }
+
+  /// Returns a [TextHeightBehavior] from the specified map.
+  ///
+  /// If the map is absent, returns null.
+  ///
+  /// Otherwise (even if it has no keys), the [TextHeightBehavior] is created
+  /// from the following keys: 'applyHeightToFirstAscent` (boolean; defaults to
+  /// true), `applyHeightToLastDescent` (boolean, defaults to true), and
+  /// `leadingDistribution` ([enumValue] of [TextLeadingDistribution], deafults
+  /// to [TextLeadingDistribution.proportional]).
+  static TextHeightBehavior? textHeightBehavior(DataSource source, List<Object> key) {
+    if (!source.isMap(key)) {
+      return null;
+    }
+    return TextHeightBehavior(
+      applyHeightToFirstAscent: source.v<bool>([...key, 'applyHeightToFirstAscent']) ?? true,
+      applyHeightToLastDescent: source.v<bool>([...key, 'applyHeightToLastDescent']) ?? true,
+      leadingDistribution: enumValue<TextLeadingDistribution>(TextLeadingDistribution.values, source, [...key, 'leadingDistribution']) ?? TextLeadingDistribution.proportional,
+    );
+  }
+
+  /// Returns a [TextDecoration] from the specified list or string.
+  ///
+  /// If the key identifies a list, then each entry in the list is decoded by
+  /// recursively invoking [textDecoration], and the result is the combination
+  /// of those [TextDecoration] values as obtained using
+  /// [TextDecoration.combine].
+  ///
+  /// Otherwise, if the key identifies a string, then the value `lineThrough` is
+  /// mapped to [TextDecoration.lineThrough], `overline` to
+  /// [TextDecoration.overline], and `underline` to [TextDecoration.underline].
+  /// Other values (and the abscence of a value) are interpreted as
+  /// [TextDecoration.none].
+  static TextDecoration textDecoration(DataSource source, List<Object> key) {
+    final List<TextDecoration>? decorations = list<TextDecoration>(source, key, textDecoration);
+    if (decorations != null) {
+      return TextDecoration.combine(decorations);
+    }
+    switch (source.v<String>([...key])) {
+      case 'lineThrough':
+        return TextDecoration.lineThrough;
+      case 'overline':
+        return TextDecoration.overline;
+      case 'underline':
+        return TextDecoration.underline;
+      default:
+        return TextDecoration.none;
+    }
+  }
+
+  /// Returns a [TextStyle] from the specified map.
+  ///
+  /// If the map is absent, returns null.
+  ///
+  /// Otherwise (even if it has no keys), the [TextStyle] is created from the
+  /// following keys: `color` ([color]), `backgroundColor` ([color]), `fontSize`
+  /// (double), `fontWeight` ([enumValue] of [FontWeight]), `fontStyle`
+  /// ([enumValue] of [FontStyle]), `letterSpacing` (double), `wordSpacing`
+  /// (double), `textBaseline` ([enumValue] of [TextBaseline]), `height`
+  /// (double), `leadingDistribution` ([enumValue] of
+  /// [TextLeadingDistribution]), `locale` ([locale]), `foreground` ([paint]),
+  /// `background` ([paint]), `shadows` ([list] of [boxShadow]s), `fontFeatures`
+  /// ([list] of [fontFeature]s), `decoration` ([textDecoration]),
+  /// `decorationColor` ([color]), `decorationStyle` ([enumValue] of
+  /// [TextDecorationStyle]), `decorationThickness` (double), 'fontFamily`
+  /// (string), `fontFamilyFallback` ([list] of [string]), and `overflow`
+  /// ([enumValue] of [TextOverflow]).
+  static TextStyle? textStyle(DataSource source, List<Object> key) {
+    if (!source.isMap(key)) {
+      return null;
+    }
+    return TextStyle(
+      color: color(source, [...key, 'color']),
+      backgroundColor: color(source, [...key, 'backgroundColor']),
+      fontSize: source.v<double>([...key, 'fontSize']),
+      fontWeight: enumValue<FontWeight>(FontWeight.values, source, [...key, 'fontWeight']),
+      fontStyle: enumValue<FontStyle>(FontStyle.values, source, [...key, 'fontStyle']),
+      letterSpacing: source.v<double>([...key, 'letterSpacing']),
+      wordSpacing: source.v<double>([...key, 'wordSpacing']),
+      textBaseline: enumValue<TextBaseline>(TextBaseline.values, source, ['textBaseline']),
+      height: source.v<double>([...key, 'height']),
+      leadingDistribution: enumValue<TextLeadingDistribution>(TextLeadingDistribution.values, source, [...key, 'leadingDistribution']),
+      locale: locale(source, [...key, 'locale']),
+      foreground: paint(source, [...key, 'foreground']),
+      background: paint(source, [...key, 'background']),
+      shadows: list<BoxShadow>(source, [...key, 'shadows'], boxShadow),
+      fontFeatures: list<FontFeature>(source, [...key, 'fontFeatures'], fontFeature),
+      decoration: textDecoration(source, [...key, 'decoration']),
+      decorationColor: color(source, [...key, 'decorationColor']),
+      decorationStyle: enumValue<TextDecorationStyle>(TextDecorationStyle.values, source, [...key, 'decorationStyle']),
+      decorationThickness: source.v<double>([...key, 'decorationThickness']),
+      fontFamily: source.v<String>([...key, 'fontFamily']),
+      fontFamilyFallback: list<String>(source, [...key, 'fontFamilyFallback'], string),
+      overflow: enumValue<TextOverflow>(TextOverflow.values, source, ['overflow']),
+    );
+  }
+
+  /// Returns a [VisualDensity] from the specified string or map.
+  ///
+  /// If the specified value is a string, then it is interpreted as follows:
+  ///
+  ///  * `adaptivePlatformDensity`: returns
+  ///    [VisualDensity.adaptivePlatformDensity] (which varies by platform).
+  ///  * `comfortable`: returns [VisualDensity.comfortable].
+  ///  * `compact`: returns [VisualDensity.compact].
+  ///  * `standard`: returns [VisualDensity.standard].
+  ///
+  /// Otherwise, if the specified value is a map, then the keys `horizontal` and
+  /// `vertical` (doubles) are used to create a custom [VisualDensity]. The
+  /// specified values must be in the range given by
+  /// [VisualDensity.minimumDensity] to [VisualDensity.maximumDensity]. Missing
+  /// values are interpreted as zero.
+  static VisualDensity? visualDensity(DataSource source, List<Object> key) {
+    final String? type = source.v<String>(key);
+    switch (type) {
+      case 'adaptivePlatformDensity':
+        return VisualDensity.adaptivePlatformDensity;
+      case 'comfortable':
+        return VisualDensity.comfortable;
+      case 'compact':
+        return VisualDensity.compact;
+      case 'standard':
+        return VisualDensity.standard;
+      default:
+        if (!source.isMap(key)) {
+          return null;
+        }
+        return VisualDensity(
+          horizontal: source.v<double>([...key, 'horizontal']) ?? 0.0,
+          vertical: source.v<double>([...key, 'vertical']) ?? 0.0,
+        );
+    }
+  }
+}
diff --git a/packages/rfw/lib/flutter/content.dart b/packages/rfw/lib/flutter/content.dart
new file mode 100644
index 0000000..e043ba6
--- /dev/null
+++ b/packages/rfw/lib/flutter/content.dart
@@ -0,0 +1,350 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This file is hand-formatted.
+
+import 'dart:async';
+import 'dart:convert';
+
+import '../dart/model.dart';
+
+/// Signature for the callback passed to [DynamicContent.subscribe].
+typedef SubscriptionCallback = void Function(Object value);
+
+/// Returns a copy of a data structure if it consists of only [DynamicMap]s,
+/// [DynamicList]s, [int]s, [double]s, [bool]s, and [String]s.
+///
+/// This is relatively expensive as the entire data structure must be walked and
+/// new objects created.
+Object? deepClone(Object? template) {
+  if (template == null) {
+    return null;
+  } else if (template is DynamicMap) {
+    return template.map((String key, Object? value) => MapEntry<String, Object?>(key, deepClone(value)));
+  } else if (template is DynamicList) {
+    return template.map((Object? value) => deepClone(value)).toList();
+  } else {
+    assert(template is int || template is double || template is bool || template is String, 'unexpected state object type: ${template.runtimeType} ($template)');
+    return template;
+  }
+}
+
+/// Configuration data from the remote widgets.
+///
+/// Typically this represents the data model, and is updated frequently (or at
+/// least, more frequently than the remote widget definitions) by the server
+/// (or, indeed, by local code, in response to events or other activity).
+///
+/// ## Structure
+///
+/// A [DynamicContent] object represents a tree. A consumer (the remote widgets)
+/// can subscribe to a node to obtain its value.
+///
+/// The root of a [DynamicContent] tree is a map of string-value pairs. The
+/// values are:
+///
+///  * Other maps of string-value pairs ([DynamicMap]).
+///  * Lists of values ([DynamicList]).
+///  * Booleans ([bool]).
+///  * Integers ([int]).
+///  * Doubles ([double]).
+///  * Strings ([String]).
+///
+/// The keys in the root map are independently updated. Typically each
+/// represents a different category of data from the server that the server
+/// updates independently, e.g. theming information and the app state might be
+/// provided separately.
+///
+/// ## Updates
+///
+/// Data is updated using [update] and [updateAll]. The objects passed to those
+/// methods are of the types described above.
+///
+/// Objects for [update] can be obtained in several ways:
+///
+/// 1. Dart maps, lists, and literals of the types given above ("raw data") can
+///    be created directly in code. This is useful for configuring remote
+///    widgets with local client information such as the current time, GPS
+///    coordinates, system settings like dark mode, window dimensions, etc,
+///    where the data was never encoded in the first place.
+///
+/// 2. A Remote Flutter Widgets binary data blob can be parsed using
+///    [decodeDataBlob]. This is the preferred method for decoding data obtained
+///    from the network. See [encodeDataBlob] for a function that generates data
+///    in this format.
+///
+/// 3. A Remote Flutter Widgets text data file can be parsed using
+///    [parseTextDataFile]. Decoding this text format is about ten times slower
+///    than decoding the binary format and about five times slower than decoding
+///    JSON, so it is discouraged in production applications. This text
+///    representation of the Remote Flutter Widgets binary data blob format is
+///    similar to JSON. This form is typically not used in applications; it is
+///    more common for this format to be used on the server side, parsed and
+///    then encoded in binary form for transmission to the client.
+///
+/// 4. Data in JSON form can be decoded using [JsonCodec.decode] (typically
+///    using the [json] object); the JSON decoder creates the same types of data
+///    structures as expected by [update]. This is not generally recommended but
+///    may be appropriate if the data was obtained from a third-party source in
+///    JSON form and could not be preprocessed by a server to convert the data
+///    to the binary form described above. Numbers in JSON are interpreted as
+///    doubles if they contain a decimal point or an `e` in their source
+///    representation, and as integers otherwise. This can cause issues as the
+///    [DynamicContent] and [DataSource] are strongly typed and distinguish
+///    [int] and [double]. Explicit nulls in the JSON are an error (the data
+///    format supported by [DynamicContent] does not support nulls). Decoding
+///    JSON is about 1.5x slower than the binary format.
+///
+/// Subscribers are notified immediately after an update if their data changed.
+///
+/// ## References
+///
+/// To subscribe to a node, the [subscribe] method is used. The method returns
+/// the current value. When the value later changes, the provided callback is
+/// invoked with the new value.
+///
+/// The [unsubscribe] method must be called when the client no longer needs
+/// updates (e.g. when the widget goes away).
+///
+/// To identify a node, a list of keys is used, giving the path from the root to
+/// the node. Each key is either a string (to index into maps) or an integer (to
+/// index into lists). If no node is identified, the [missing] value is
+/// returned. Similarly, if a node goes away, subscribers are given the value
+/// [missing] as the new value. It is not an error to subscribe to missing data.
+/// It _is_ an error to add [missing] values to the data model, however.
+///
+/// The [LocalWidgetBuilder]s passed to a [LocalWidgetLibrary] use a
+/// [DataSource] as their interface into the [DynamicContent]. To ensure the
+/// integrity of the update mechanism, that interface only allows access to
+/// leaves, not intermediate nodes (maps and lists).
+///
+/// It is an error to subscribe to the same key multiple times with the same
+/// callback.
+class DynamicContent {
+  /// Create a fresh [DynamicContent] object.
+  ///
+  /// The `initialData` argument, if provided, is used to update all the keys
+  /// in the [DynamicContent], as if [updateAll] had been called.
+  DynamicContent([ DynamicMap? initialData ]) {
+    if (initialData != null) {
+      updateAll(initialData);
+    }
+  }
+
+  final _DynamicNode _root = _DynamicNode.root();
+
+  /// Update all the keys in the [DynamicContent].
+  ///
+  /// Each key in the provided map is added to [DynamicContent], replacing any
+  /// data that was there previously, as if [update] had been called for each
+  /// key.
+  ///
+  /// Existing keys that are not present in the given map are left unmodified.
+  void updateAll(DynamicMap initialData) {
+    for (final String key in initialData.keys) {
+      final Object value = initialData[key] ?? missing;
+      update(key, value);
+    }
+  }
+
+  /// Updates the content with the specified data.
+  ///
+  /// The given `rootKey` is updated with the data `value`.
+  ///
+  /// The `value` must consist exclusively of [DynamicMap], [DynamicList], [int],
+  /// [double], [bool], and [String] objects.
+  void update(String rootKey, Object value) {
+    _root.updateKey(rootKey, deepClone(value)!);
+    _scheduleCleanup();
+  }
+
+  /// Obtain the value at location `key`, and subscribe `callback` to that key
+  /// so that future [update]s will invoke the callback with the new value.
+  ///
+  /// The value is always non-null; if the value is missing, the [missing]
+  /// object is used instead.
+  ///
+  /// Use [unsubscribe] when the subscription is no longer needed.
+  Object subscribe(List<Object> key, SubscriptionCallback callback) {
+    return _root.subscribe(key, 0, callback);
+  }
+
+  /// Removes a subscription created by [subscribe].
+  void unsubscribe(List<Object> key, SubscriptionCallback callback) {
+    _root.unsubscribe(key, 0, callback);
+  }
+
+  bool _cleanupPending = false;
+
+  void _scheduleCleanup() {
+    if (!_cleanupPending) {
+      _cleanupPending = true;
+      scheduleMicrotask(() {
+        _cleanupPending = false;
+        _DynamicNode.cleanup();
+      });
+    }
+  }
+
+  @override
+  String toString() => '$runtimeType($_root)';
+}
+
+// Node in the [DynamicContent] tree. This should contain no [BlobNode]s.
+class _DynamicNode {
+  _DynamicNode(this._key, this._parent, this._value) : assert(_value == missing || _hasValidType(_value));
+
+  _DynamicNode.root() : _key = missing, _parent = null, _value = DynamicMap(); // ignore: prefer_collection_literals
+
+  final Object _key;
+  final _DynamicNode? _parent;
+  Object _value;
+
+  final Set<SubscriptionCallback> _callbacks = <SubscriptionCallback>{};
+  final Map<Object, _DynamicNode> _children = <Object, _DynamicNode>{};
+
+  bool get isObsolete => _callbacks.isEmpty && _children.isEmpty;
+
+  static final Set<_DynamicNode> _obsoleteNodes = <_DynamicNode>{};
+
+  /// Allow garbage collection to collect unused nodes.
+  ///
+  /// When a node has no subscribers, it is no longer needed (it can be
+  /// recreated if necessary from the raw data). In that situation, the node
+  /// adds itself to a list of "obsolete nodes", but the parent still references
+  /// it and therefore garbage collection would not notice that it is no longer
+  /// used.
+  ///
+  /// This method solves this problem by disconnecting obsolete nodes from the
+  /// tree.
+  static void cleanup() {
+    while (_obsoleteNodes.isNotEmpty) {
+      final _DynamicNode node = _obsoleteNodes.first;
+      _obsoleteNodes.remove(node);
+      if (node.isObsolete) {
+        node._parent?._forget(node._key, node);
+      }
+    }
+  }
+
+  void _forget(Object childKey, _DynamicNode child) {
+    assert(_children[childKey] == child);
+    _children.remove(childKey);
+    if (isObsolete) {
+      _obsoleteNodes.add(this);
+    }
+  }
+
+  static bool _hasValidType(Object? value) {
+    if (value is DynamicMap) {
+      return value.values.every(_hasValidType);
+    }
+    if (value is DynamicList) {
+      return value.every(_hasValidType);
+    }
+    return value is int
+        || value is double
+        || value is bool
+        || value is String;
+  }
+
+  _DynamicNode _prepare(Object childKey) {
+    assert(childKey is String || childKey is int);
+    if (!_children.containsKey(childKey)) {
+      Object value;
+      if (_value is DynamicMap) {
+        if (childKey is String && (_value as DynamicMap).containsKey(childKey)) {
+          value = (_value as DynamicMap)[childKey]!;
+        } else {
+          value = missing;
+        }
+      } else if (_value is DynamicList) {
+        if (childKey is int && childKey >= 0 && childKey < (_value as DynamicList).length) {
+          value = (_value as DynamicList)[childKey]!;
+        } else {
+          value = missing;
+        }
+      } else {
+        value = _value;
+      }
+      _children[childKey] = _DynamicNode(childKey, this, value);
+    }
+    return _children[childKey]!;
+  }
+
+  Object subscribe(List<Object> key, int index, SubscriptionCallback callback) {
+    _obsoleteNodes.remove(this);
+    if (index == key.length) {
+      assert(!_callbacks.contains(callback));
+      _callbacks.add(callback);
+      return _value;
+    }
+    final _DynamicNode child = _prepare(key[index]);
+    return child.subscribe(key, index + 1, callback);
+  }
+
+  void unsubscribe(List<Object> key, int index, SubscriptionCallback callback) {
+    if (index == key.length) {
+      assert(_callbacks.contains(callback));
+      _callbacks.remove(callback);
+      if (_callbacks.isEmpty) {
+        _obsoleteNodes.add(this);
+      }
+    } else {
+      assert(_children.containsKey(key[index]));
+      _children[key[index]]!.unsubscribe(key, index + 1, callback);
+    }
+  }
+
+  void update(Object value) {
+    assert(value == missing || _hasValidType(value), 'cannot update $this using $value');
+    if (value == _value) {
+      return;
+    }
+    _value = value;
+    if (value is DynamicMap) {
+      for (final Object childKey in _children.keys) {
+        if (childKey is String && value.containsKey(childKey)) {
+          _children[childKey]!.update(value[childKey]!);
+        } else {
+          _children[childKey]!.update(missing);
+        }
+      }
+    } else if (value is DynamicList) {
+      for (final Object childKey in _children.keys) {
+        if (childKey is int && childKey >= 0 && childKey < value.length) {
+          _children[childKey]!.update(value[childKey]!);
+        } else {
+          _children[childKey]!.update(missing);
+        }
+      }
+    } else {
+      for (final _DynamicNode child in _children.values) {
+        child.update(missing);
+      }
+    }
+    _sendUpdates(value);
+  }
+
+  void _sendUpdates(Object value) {
+    for (final SubscriptionCallback callback in _callbacks) {
+      callback(value);
+    }
+  }
+
+  void updateKey(String rootKey, Object value) {
+    assert(_value is DynamicMap);
+    assert(_hasValidType(value));
+    if ((_value as DynamicMap)[rootKey] == value) {
+      return;
+    }
+    (_value as DynamicMap)[rootKey] = value;
+    if (_children.containsKey(rootKey)) {
+      _children[rootKey]!.update(value);
+    }
+  }
+
+  @override
+  String toString() => '$_value';
+}
diff --git a/packages/rfw/lib/flutter/core_widgets.dart b/packages/rfw/lib/flutter/core_widgets.dart
new file mode 100644
index 0000000..4863323
--- /dev/null
+++ b/packages/rfw/lib/flutter/core_widgets.dart
@@ -0,0 +1,659 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// There's a lot of <Object>[] lists in this file so to avoid making this
+// file even less readable we relax our usual stance on verbose typing.
+// ignore_for_file: always_specify_types
+
+// This file is hand-formatted.
+
+import 'dart:ui' show FontFeature;
+
+import 'package:flutter/gestures.dart' show DragStartBehavior;
+import 'package:flutter/material.dart';
+import 'package:flutter/widgets.dart';
+
+import 'argument_decoders.dart';
+import 'runtime.dart';
+
+/// A widget library for Remote Flutter Widgets that defines widgets that are
+/// implemented on the client in terms of Flutter widgets from the `widgets`
+/// Dart library.
+///
+/// The following widgets are implemented:
+///
+///  * [Align]
+///  * [AspectRatio]
+///  * [Center]
+///  * [ColoredBox]
+///  * [Column]
+///  * [Container] (actually uses [AnimatedContainer])
+///  * [DefaultTextStyle]
+///  * [Directionality]
+///  * [Expanded]
+///  * [FittedBox]
+///  * [GestureDetector]
+///  * [GridView] (actually uses [GridView.builder])
+///  * [Icon]
+///  * [IconTheme]
+///  * [IntrinsicHeight]
+///  * [IntrinsicWidth]
+///  * [Image] (see below)
+///  * [ListBody]
+///  * [ListView] (actually uses [ListView.builder])
+///  * [Opacity] (actually uses [AnimatedOpacity])
+///  * [Padding] (actually uses [AnimatedPadding])
+///  * [Placeholder]
+///  * [Positioned] (actually uses [AnimatedPositionedDirectional])
+///  * [Rotation] (actually uses [AnimatedRotation])
+///  * [Row]
+///  * [SafeArea]
+///  * [Scale] (actually uses [AnimatedScale])
+///  * [SingleChildScrollView]
+///  * [SizedBox]
+///  * `SizedBoxExpand` (actually [SizedBox.expand])
+///  * `SizedBoxShrink` (actually [SizedBox.shrink])
+///  * [Spacer]
+///  * [Stack]
+///  * [Text]
+///  * [Wrap]
+///
+/// For each, every parameter is implemented using the same name. Parameters
+/// that take structured types are represented using maps, with each named
+/// parameter of that type's default constructor represented by a key, with the
+/// following notable caveats and exceptions:
+///
+///  * Enums are represented as strings with the unqualified name of the value.
+///    For example, [MainAxisAlignment.start] is represented as the string
+///    `"start"`.
+///
+///  * Types that have multiple subclasses (or multiple very unrelated
+///    constructors, like [ColorFilter]) are represented as maps where the `type`
+///    key specifies the type. Typically these have an extension mechanism.
+///
+///  * Matrices are represented as **column-major** flattened arrays. [Matrix4]
+///    values must have exactly 16 doubles in the array.
+///
+///  * [AlignmentGeometry] values can be represented either as `{x: ..., y:
+///    ...}` for a non-directional variant or `{start: ..., y: ...}` for a
+///    directional variant.
+///
+///  * [BoxBorder] instances are defined as arrays of [BorderSide] maps. If the
+///    array has length 1, then that value is used for all four sides. Two
+///    values become the horizontal and vertical sides respectively. Three
+///    values become the start, top-and-bottom, and end respectively. Four
+///    values become the start, top, end, and bottom respectively.
+///
+///  * [BorderRadiusGeometry] values work similarly to [BoxBorder], as an array
+///    of [Radius] values. If the array has one value, it's used for all corners.
+///    With two values, the first becomes the `topStart` and `bottomStart`
+///    corners and the second the `topEnd` and `bottomEnd`. With three, the
+///    values are used for `topStart`, `topEnd`-and-`bottomEnd`, and
+///    `bottomStart` respectively. Four values map to the `topStart`, `topEnd`,
+///    `bottomStart`, and `bottomEnd` respectively.
+///
+///  * [Color] values are represented as integers. The hex literal values are
+///    most convenient for this, the alpha, red, green, and blue channels map to
+///    the 32 bit hex integer as 0xAARRGGBB.
+///
+///  * [ColorFilter] is represented as a map with a `type` key that matches the
+///    constructor name (e.g. `linearToSrgbGamma`). The `matrix` version uses
+///    the `matrix` key for the matrix, expecting a 20-value array. The `mode`
+///    version expects a `color` key for the color (defaults to black) and a
+///    `blendMode` key for the blend mode (defaults to [BlendMode.srcOver]).
+///    Other types are looked up in [ArgumentDecoders.colorFilterDecoders].
+///
+///  * [Curve] values are represented as a string giving the kind of curve from
+///    the predefined [Curves], e.g. `easeInOutCubicEmphasized`. More types may
+///    be added using [ArgumentDecoders.curveDecoders].
+///
+///  * The types supported for [Decoration] are `box` for [BoxDecoration],
+///    `flutterLogo` for [FlutterLogoDecoration], and `shape` for
+///    [ShapeDecoration]. More types can be added with [decorationDecoders].
+///
+///  * [DecorationImage] expects a `source` key that gives either an absolute
+///    URL (to use a [NetworkImage]) or the name of an asset in the client
+///    binary (to use [AssetImage]). In the case of a URL, the `scale` key gives
+///    the scale to pass to the [NetworkImage] constructor.
+///    [DecorationImage.onError] is supported as an event handler with arguments
+///    giving the stringified exception and stack trace. Values can be added to
+///    [ArgumentDecoders.imageProviderDecoders] to override the behavior described here.
+///
+///  * [Duration] is represented by an integer giving milliseconds.
+///
+///  * [EdgeInsetsGeometry] values work like [BoxBorder], with each value in the
+///    array being a double rather than a map.
+///
+///  * [FontFeature] values are a map with a `feature` key and a `value` key.
+///    The `value` defaults to 1. (Technically the `feature` defaults to `NONE`,
+///    too, but that's hardly useful.)
+///
+///  * The [dart:ui.Gradient] and [painting.Gradient] types are both represented
+///    as a map with a type that is either `linear` (for [LinearGradient]),
+///    `radial` (for [RadialGradient]), or `sweep` (for [SweepGradient]), using
+///    the conventions from the [painting.Gradient] version. The `transform`
+///    property on these objects is not currently supported. New gradient types
+///    can be implemented using [ArgumentDecoders.gradientDecoders].
+///
+///  * The [GridDelegate] type is represented as a map with a `type` key that is
+///    either `fixedCrossAxisCount` for
+///    [SliverGridDelegateWithFixedCrossAxisCount] or `maxCrossAxisExtent` for
+///    [SliverGridDelegateWithMaxCrossAxisExtent]. New delegate types can be
+///    supported using [ArgumentDecoders.gridDelegateDecoders].
+///
+///  * [IconData] is represented as a map with an `icon` key giving the
+///    [IconData.codePoint] (and corresponding keys for the other parameters of
+///    the [IconData] constructor). To determine the values to use for icons in
+///    the MaterialIcons font, see how the icons are defined in [Icons]. For
+///    example, [Icons.flutter_dash] is `IconData(0xe2a0, fontFamily:
+///    'MaterialIcons')` so it would be represented here as `{ icon: 0xE2A0,
+///    fontFamily: "MaterialIcons" }`. (The client must have the font as a
+///    defined asset.)
+///
+///  * [Locale] values are defined as a string in the form `languageCode`,
+///    `languageCode-countryCode`, or
+///    `languageCode-scriptCode-countryCode-ignoredSubtags`. The string is split
+///    on hyphens.
+///
+///  * [MaskFilter] is represented as a map with a `type` key that must be
+///    `blur`; only [MaskFilter.blur] is supported. (The other keys must be
+///    `style`, the [BlurStyle], and `sigma`.)
+///
+///  * [Offset]s are a map with an `x` key and a `y` key.
+///
+///  * [Paint] objects are represented as maps; each property of [Paint] is a
+///    key as if there was a constructor that could set all of [Paint]'s
+///    properties with named parameters. In principle all properties are
+///    supported, though since [Paint] is only used as part of
+///    [painting.TextStyle.background] and [painting.TextStyle.foreground], in
+///    practice some of the properties are ignored since they would be no-ops
+///    (e.g. `invertColors`).
+///
+///  * [Radius] is represented as a map with an `x` value and optionally a `y`
+///    value; if the `y` value is absent, the `x` value is used for both.
+///
+///  * [Rect] values are represented as an array with four doubles, giving the
+///    x, y, width, and height respectively.
+///
+///  * [ShapeBorder] values are represented as either maps with a `type` _or_ as
+///    an array of [ShapeBorder] values. In the array case, the values are
+///    reduced together using [ShapeBorder.+]. When represented as maps, the
+///    type must be one of `box` ([BoxBorder]), `beveled`
+///    ([BeveledRectangleBorder]), `circle` ([CircleBorder]), `continuous`
+///    ([ContinuousRectangleBorder]), `rounded` ([RoundedRectangleBorder]), or
+///    `stadium` ([StadiumBorder]). In the case of `box`, there must be a
+///    `sides` key whose value is an array that is interpreted as per
+///    [BoxBorder] above. Support for new types can be added using the
+///    [ArgumentDecoders.shapeBorderDecoders] map.
+///
+///  * [Shader] values are a map with a `type` that is either `linear`,
+///    `radial`, or `sweep`; in each case, the data is interpreted as per the
+///    [Gradient] case above, except that the gradient is specifically applied
+///    to a [Rect] given by the `rect` key and a [TextDirection] given by the
+///    `textDirection` key. New shader types can be added using
+///    [ArgumentDecoders.shaderDecoders].
+///
+///  * [TextDecoration] is represented either as an array of [TextDecoration]
+///    values (combined via [TextDecoration.combine]) or a string which matches
+///    the name of one of the [TextDecoration] constants (e.g. `underline`).
+///
+///  * [VisualDensity] is either represented as a string which matches one of the
+///    predefined values (`adaptivePlatformDensity`, `comfortable`, etc), or as
+///    a map with keys `horizontal` and `vertical` to define a custom density.
+///
+/// Some of the widgets have special considerations:
+///
+///  * [Image] does not support the builder callbacks or the [Image.opacity]
+///    parameter (because builders are code and code can't be represented in RFW
+///    arguments). The map should have a `source` key that is interpreted as
+///    described above for [DecorationImage]. If the `source` is omitted, an
+///    [AssetImage] with the name `error.png` is used instead (which will likely
+///    fail unless such an asset is declared in the client).
+///
+///  * Parameters of type [ScrollController] and [ScrollPhysics] are not
+///    supported, because they can't really be exposed to declarative code (they
+///    expect to be configured using code that implements delegates or that
+///    interacts with controllers).
+///
+///  * The [Text] widget's first argument, the string, is represented using the
+///    key `text`, which must be either a string or an array of strings to be
+///    concatenated.
+///
+/// One additional widget is defined, [AnimationDefaults]. It has a `duration`
+/// argument and `curve` argument. It sets the default animation duration and
+/// curve for widgets in the library that use the animated variants. If absent,
+/// a default of 200ms and [Curves.fastOutSlowIn] is used.
+LocalWidgetLibrary createCoreWidgets() => LocalWidgetLibrary(_coreWidgetsDefinitions);
+
+// In these widgets we make an effort to expose every single argument available.
+Map<String, LocalWidgetBuilder> get _coreWidgetsDefinitions => <String, LocalWidgetBuilder>{
+
+  // Keep these in alphabetical order.
+  
+  'AnimationDefaults': (BuildContext context, DataSource source) {
+    return AnimationDefaults(
+      duration: ArgumentDecoders.duration(source, ['duration'], context),
+      curve: ArgumentDecoders.curve(source, ['curve'], context),
+      child: source.child(['child']),
+    );
+  },
+
+  'Align': (BuildContext context, DataSource source) {
+    return AnimatedAlign(
+      duration: ArgumentDecoders.duration(source, ['duration'], context),
+      curve: ArgumentDecoders.curve(source, ['curve'], context),
+      alignment: ArgumentDecoders.alignment(source, ['alignment']) ?? Alignment.center,
+      widthFactor: source.v<double>(['widthFactor']),
+      heightFactor: source.v<double>(['heightFactor']),
+      child: source.optionalChild(['child']),
+      onEnd: source.voidHandler(['onEnd']),
+    );
+  },
+
+  'AspectRatio': (BuildContext context, DataSource source) {
+    return AspectRatio(
+      aspectRatio: source.v<double>(['aspectRatio']) ?? 1.0,
+      child: source.optionalChild(['child']),
+    );
+  },
+
+  'Center': (BuildContext context, DataSource source) {
+    return Center(
+      widthFactor: source.v<double>(['widthFactor']),
+      heightFactor: source.v<double>(['heightFactor']),
+      child: source.optionalChild(['child']),
+    );
+  },
+
+  'ColoredBox': (BuildContext context, DataSource source) {
+    return ColoredBox(
+      color: ArgumentDecoders.color(source, ['color']) ?? const Color(0xFF000000),
+      child: source.optionalChild(['child']),
+    );
+  },
+
+  'Column': (BuildContext context, DataSource source) {
+    return Column(
+      mainAxisAlignment: ArgumentDecoders.enumValue<MainAxisAlignment>(MainAxisAlignment.values, source, ['mainAxisAlignment']) ?? MainAxisAlignment.start,
+      mainAxisSize: ArgumentDecoders.enumValue<MainAxisSize>(MainAxisSize.values, source, ['mainAxisSize']) ?? MainAxisSize.max,
+      crossAxisAlignment: ArgumentDecoders.enumValue<CrossAxisAlignment>(CrossAxisAlignment.values, source, ['crossAxisAlignment']) ?? CrossAxisAlignment.center,
+      textDirection: ArgumentDecoders.enumValue<TextDirection>(TextDirection.values, source, ['textDirection']),
+      verticalDirection: ArgumentDecoders.enumValue<VerticalDirection>(VerticalDirection.values, source, ['verticalDirection']) ?? VerticalDirection.down,
+      textBaseline: ArgumentDecoders.enumValue<TextBaseline>(TextBaseline.values, source, ['textBaseline']),
+      children: source.childList(['children']),
+    );
+  },
+
+  'Container': (BuildContext context, DataSource source) {
+    return AnimatedContainer(
+      duration: ArgumentDecoders.duration(source, ['duration'], context),
+      curve: ArgumentDecoders.curve(source, ['curve'], context),
+      alignment: ArgumentDecoders.alignment(source, ['alignment']),
+      padding: ArgumentDecoders.edgeInsets(source, ['padding']),
+      color: ArgumentDecoders.color(source, ['color']),
+      decoration: ArgumentDecoders.decoration(source, ['decoration']),
+      foregroundDecoration: ArgumentDecoders.decoration(source, ['foregroundDecoration']),
+      width: source.v<double>(['width']),
+      height: source.v<double>(['height']),
+      constraints: ArgumentDecoders.boxConstraints(source, ['constraints']),
+      margin: ArgumentDecoders.edgeInsets(source, ['margin']),
+      transform: ArgumentDecoders.matrix(source, ['transform']),
+      transformAlignment: ArgumentDecoders.alignment(source, ['transformAlignment']),
+      child: source.optionalChild(['child']),
+      clipBehavior: ArgumentDecoders.enumValue<Clip>(Clip.values, source, ['clipBehavior']) ?? Clip.none,
+      onEnd: source.voidHandler(['onEnd']),
+    );
+  },
+
+  'DefaultTextStyle': (BuildContext context, DataSource source) {
+    return AnimatedDefaultTextStyle(
+      duration: ArgumentDecoders.duration(source, ['duration'], context),
+      curve: ArgumentDecoders.curve(source, ['curve'], context),
+      style: ArgumentDecoders.textStyle(source, ['style']) ?? const TextStyle(),
+      textAlign: ArgumentDecoders.enumValue<TextAlign>(TextAlign.values, source, ['textAlign']),
+      softWrap: source.v<bool>(['softWrap']) ?? true,
+      overflow: ArgumentDecoders.enumValue<TextOverflow>(TextOverflow.values, source, ['overflow']) ?? TextOverflow.clip,
+      maxLines: source.v<int>(['maxLines']),
+      textWidthBasis: ArgumentDecoders.enumValue<TextWidthBasis>(TextWidthBasis.values, source, ['textWidthBasis']) ?? TextWidthBasis.parent,
+      textHeightBehavior: ArgumentDecoders.textHeightBehavior(source, ['textHeightBehavior']),
+      child: source.child(['child']),
+      onEnd: source.voidHandler(['onEnd']),
+    );
+  },
+
+  'Directionality': (BuildContext context, DataSource source) {
+    return Directionality(
+      textDirection: ArgumentDecoders.enumValue<TextDirection>(TextDirection.values, source, ['textDirection']) ?? TextDirection.ltr,
+      child: source.child(['child']),
+    );
+  },
+
+  'Expanded': (BuildContext context, DataSource source) {
+    return Expanded(
+      flex: source.v<int>(['flex']) ?? 1,
+      child: source.child(['child']),
+    );
+  },
+
+  'FittedBox': (BuildContext context, DataSource source) {
+    return FittedBox(
+      fit: ArgumentDecoders.enumValue<BoxFit>(BoxFit.values, source, ['fit']) ?? BoxFit.contain,
+      alignment: ArgumentDecoders.alignment(source, ['alignment']) ?? Alignment.center,
+      clipBehavior: ArgumentDecoders.enumValue<Clip>(Clip.values, source, ['clipBehavior']) ?? Clip.none,
+      child: source.optionalChild(['child']),
+    );
+  },
+
+  'GestureDetector': (BuildContext context, DataSource source) {
+    return GestureDetector(
+      onTap: source.voidHandler(['onTap']),
+      onTapDown: source.handler(['onTapDown'], (VoidCallback trigger) => (TapDownDetails details) => trigger()),
+      onTapUp: source.handler(['onTapUp'], (VoidCallback trigger) => (TapUpDetails details) => trigger()),
+      onTapCancel: source.voidHandler(['onTapCancel']),
+      onDoubleTap: source.voidHandler(['onDoubleTap']),
+      onLongPress: source.voidHandler(['onLongPress']),
+      behavior: ArgumentDecoders.enumValue<HitTestBehavior>(HitTestBehavior.values, source, ['behavior']),
+      child: source.optionalChild(['child']),
+    );
+  },
+
+  'GridView': (BuildContext context, DataSource source) {
+    return GridView.builder(
+      scrollDirection: ArgumentDecoders.enumValue<Axis>(Axis.values, source, ['scrollDirection']) ?? Axis.vertical,
+      reverse: source.v<bool>(['reverse']) ?? false,
+      // controller,
+      primary: source.v<bool>(['primary']),
+      // physics,
+      shrinkWrap: source.v<bool>(['shrinkWrap']) ?? false,
+      padding: ArgumentDecoders.edgeInsets(source, ['padding']),
+      gridDelegate: ArgumentDecoders.gridDelegate(source, ['gridDelegate']) ?? const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2),
+      itemBuilder: (BuildContext context, int index) => source.child(['children', index]),
+      itemCount: source.length(['children']),
+      addAutomaticKeepAlives: source.v<bool>(['addAutomaticKeepAlives']) ?? true,
+      addRepaintBoundaries: source.v<bool>(['addRepaintBoundaries']) ?? true,
+      addSemanticIndexes: source.v<bool>(['addSemanticIndexes']) ?? true,
+      cacheExtent: source.v<double>(['cacheExtent']),
+      semanticChildCount: source.v<int>(['semanticChildCount']),
+      dragStartBehavior: ArgumentDecoders.enumValue<DragStartBehavior>(DragStartBehavior.values, source, ['dragStartBehavior']) ?? DragStartBehavior.start,
+      keyboardDismissBehavior: ArgumentDecoders.enumValue<ScrollViewKeyboardDismissBehavior>(ScrollViewKeyboardDismissBehavior.values, source, ['keyboardDismissBehavior']) ?? ScrollViewKeyboardDismissBehavior.manual,
+      restorationId: source.v<String>(['restorationId']),
+      clipBehavior: ArgumentDecoders.enumValue<Clip>(Clip.values, source, ['clipBehavior']) ?? Clip.hardEdge,
+    );
+  },
+
+  'Icon': (BuildContext context, DataSource source) {
+    return Icon(
+      ArgumentDecoders.iconData(source, []) ?? Icons.flutter_dash,
+      size: source.v<double>(['size']),
+      color: ArgumentDecoders.color(source, ['color']),
+      semanticLabel: source.v<String>(['semanticLabel']),
+      textDirection: ArgumentDecoders.enumValue<TextDirection>(TextDirection.values, source, ['textDirection']),
+    );
+  },
+
+  'IconTheme': (BuildContext context, DataSource source) {
+    return IconTheme(
+      data: ArgumentDecoders.iconThemeData(source, []) ?? const IconThemeData(),
+      child: source.child(['child']),
+    );
+  },
+
+  'IntrinsicHeight': (BuildContext context, DataSource source) {
+    return IntrinsicHeight(
+      child: source.optionalChild(['child']),
+    );
+  },
+
+  'IntrinsicWidth': (BuildContext context, DataSource source) {
+    return IntrinsicWidth(
+      stepWidth: source.v<double>(['width']),
+      stepHeight: source.v<double>(['height']),
+      child: source.optionalChild(['child']),
+    );
+  },
+
+  'Image': (BuildContext context, DataSource source) {
+    return Image(
+      image: ArgumentDecoders.imageProvider(source, []) ?? const AssetImage('error.png'),
+      // ImageFrameBuilder? frameBuilder,
+      // ImageLoadingBuilder? loadingBuilder,
+      // ImageErrorWidgetBuilder? errorBuilder,
+      semanticLabel: source.v<String>(['semanticLabel']),
+      excludeFromSemantics: source.v<bool>(['excludeFromSemantics']) ?? false,
+      width: source.v<double>(['width']),
+      height: source.v<double>(['height']),
+      color: ArgumentDecoders.color(source, ['color']),
+      // Animation<double>? opacity,
+      colorBlendMode: ArgumentDecoders.enumValue<BlendMode>(BlendMode.values, source, ['blendMode']),
+      fit: ArgumentDecoders.enumValue<BoxFit>(BoxFit.values, source, ['fit']),
+      alignment: ArgumentDecoders.alignment(source, ['alignment']) ?? Alignment.center,
+      repeat: ArgumentDecoders.enumValue<ImageRepeat>(ImageRepeat.values, source, ['repeat']) ?? ImageRepeat.noRepeat,
+      centerSlice: ArgumentDecoders.rect(source, ['centerSlice']),
+      matchTextDirection: source.v<bool>(['matchTextDirection']) ?? false,
+      gaplessPlayback: source.v<bool>(['gaplessPlayback']) ?? false,
+      isAntiAlias: source.v<bool>(['isAntiAlias']) ?? false,
+      filterQuality: ArgumentDecoders.enumValue<FilterQuality>(FilterQuality.values, source, ['filterQuality']) ?? FilterQuality.low,
+    );
+  },
+
+  'ListBody': (BuildContext context, DataSource source) {
+    return ListBody(
+      mainAxis: ArgumentDecoders.enumValue<Axis>(Axis.values, source, ['mainAxis']) ?? Axis.vertical,
+      reverse: source.v<bool>(['reverse']) ?? false,
+      children: source.childList(['children']),
+    );
+  },
+
+  'ListView': (BuildContext context, DataSource source) {
+    return ListView.builder(
+      scrollDirection: ArgumentDecoders.enumValue<Axis>(Axis.values, source, ['scrollDirection']) ?? Axis.vertical,
+      reverse: source.v<bool>(['reverse']) ?? false,
+      // ScrollController? controller,
+      primary: source.v<bool>(['primary']),
+      // ScrollPhysics? physics,
+      shrinkWrap: source.v<bool>(['shrinkWrap']) ?? false,
+      padding: ArgumentDecoders.edgeInsets(source, ['padding']),
+      itemExtent: source.v<double>(['itemExtent']),
+      prototypeItem: source.optionalChild(['prototypeItem']),
+      itemCount: source.length(['children']),
+      itemBuilder: (BuildContext context, int index) => source.child(['children', index]),
+      clipBehavior: ArgumentDecoders.enumValue<Clip>(Clip.values, source, ['clipBehavior']) ?? Clip.hardEdge,
+      addAutomaticKeepAlives: source.v<bool>(['addAutomaticKeepAlives']) ?? true,
+      addRepaintBoundaries: source.v<bool>(['addRepaintBoundaries']) ?? true,
+      addSemanticIndexes: source.v<bool>(['addSemanticIndexes']) ?? true,
+      cacheExtent: source.v<double>(['cacheExtent']),
+      semanticChildCount: source.v<int>(['semanticChildCount']),
+      dragStartBehavior: ArgumentDecoders.enumValue<DragStartBehavior>(DragStartBehavior.values, source, ['dragStartBehavior']) ?? DragStartBehavior.start,
+      keyboardDismissBehavior: ArgumentDecoders.enumValue<ScrollViewKeyboardDismissBehavior>(ScrollViewKeyboardDismissBehavior.values, source, ['keyboardDismissBehavior']) ?? ScrollViewKeyboardDismissBehavior.manual,
+      restorationId: source.v<String>(['restorationId']),
+    );
+  },
+
+  'Opacity': (BuildContext context, DataSource source) {
+    return AnimatedOpacity(
+      duration: ArgumentDecoders.duration(source, ['duration'], context),
+      curve: ArgumentDecoders.curve(source, ['curve'], context),
+      opacity: source.v<double>(['opacity']) ?? 0.0,
+      onEnd: source.voidHandler(['onEnd']),
+      alwaysIncludeSemantics: source.v<bool>(['alwaysIncludeSemantics']) ?? true,
+    );
+  },
+
+  'Padding': (BuildContext context, DataSource source) {
+    return AnimatedPadding(
+      duration: ArgumentDecoders.duration(source, ['duration'], context),
+      curve: ArgumentDecoders.curve(source, ['curve'], context),
+      padding: ArgumentDecoders.edgeInsets(source, ['padding']) ?? EdgeInsets.zero,
+      child: source.optionalChild(['child']),
+      onEnd: source.voidHandler(['onEnd']),
+    );
+  },
+
+  'Placeholder': (BuildContext context, DataSource source) {
+    return Placeholder(
+      color: ArgumentDecoders.color(source, ['color']) ?? const Color(0xFF455A64),
+      strokeWidth: source.v<double>(['strokeWidth']) ?? 2.0,
+      fallbackWidth: source.v<double>(['placeholderWidth']) ?? 400.0,
+      fallbackHeight: source.v<double>(['placeholderHeight']) ?? 400.0,
+    );
+  },
+
+  'Positioned': (BuildContext context, DataSource source) {
+    return AnimatedPositionedDirectional(
+      duration: ArgumentDecoders.duration(source, ['duration'], context),
+      curve: ArgumentDecoders.curve(source, ['curve'], context),
+      start: source.v<double>(['start']),
+      top: source.v<double>(['top']),
+      end: source.v<double>(['end']),
+      bottom: source.v<double>(['bottom']),
+      width: source.v<double>(['width']),
+      height: source.v<double>(['height']),
+      child: source.child(['child']),
+      onEnd: source.voidHandler(['onEnd']),
+    );
+  },
+
+  'Rotation': (BuildContext context, DataSource source) {
+    return AnimatedRotation(
+      duration: ArgumentDecoders.duration(source, ['duration'], context),
+      curve: ArgumentDecoders.curve(source, ['curve'], context),
+      turns: source.v<double>(['turns']) ?? 0.0,
+      alignment: (ArgumentDecoders.alignment(source, ['alignment']) ?? Alignment.center).resolve(Directionality.of(context)),
+      filterQuality: ArgumentDecoders.enumValue<FilterQuality>(FilterQuality.values, source, ['filterQuality']),
+      child: source.optionalChild(['child']),
+      onEnd: source.voidHandler(['onEnd']),
+    );
+  },
+
+  'Row': (BuildContext context, DataSource source) {
+    return Row(
+      mainAxisAlignment: ArgumentDecoders.enumValue<MainAxisAlignment>(MainAxisAlignment.values, source, ['mainAxisAlignment']) ?? MainAxisAlignment.start,
+      mainAxisSize: ArgumentDecoders.enumValue<MainAxisSize>(MainAxisSize.values, source, ['mainAxisSize']) ?? MainAxisSize.max,
+      crossAxisAlignment: ArgumentDecoders.enumValue<CrossAxisAlignment>(CrossAxisAlignment.values, source, ['crossAxisAlignment']) ?? CrossAxisAlignment.center,
+      textDirection: ArgumentDecoders.enumValue<TextDirection>(TextDirection.values, source, ['textDirection']),
+      verticalDirection: ArgumentDecoders.enumValue<VerticalDirection>(VerticalDirection.values, source, ['verticalDirection']) ?? VerticalDirection.down,
+      textBaseline: ArgumentDecoders.enumValue<TextBaseline>(TextBaseline.values, source, ['textBaseline']),
+      children: source.childList(['children']),
+    );
+  },
+
+  'SafeArea': (BuildContext context, DataSource source) {
+    return SafeArea(
+      left: source.v<bool>(['left']) ?? true,
+      top: source.v<bool>(['top']) ?? true,
+      right: source.v<bool>(['right']) ?? true,
+      bottom: source.v<bool>(['bottom']) ?? true,
+      minimum: (ArgumentDecoders.edgeInsets(source, ['minimum']) ?? EdgeInsets.zero).resolve(Directionality.of(context)),
+      maintainBottomViewPadding: source.v<bool>(['maintainBottomViewPadding']) ?? false,
+      child: source.child(['child']),
+    );
+  },
+
+  'Scale': (BuildContext context, DataSource source) {
+    return AnimatedScale(
+      duration: ArgumentDecoders.duration(source, ['duration'], context),
+      curve: ArgumentDecoders.curve(source, ['curve'], context),
+      scale: source.v<double>(['scale']) ?? 1.0,
+      alignment: (ArgumentDecoders.alignment(source, ['alignment']) ?? Alignment.center).resolve(Directionality.of(context)),
+      filterQuality: ArgumentDecoders.enumValue<FilterQuality>(FilterQuality.values, source, ['filterQuality']),
+      child: source.optionalChild(['child']),
+      onEnd: source.voidHandler(['onEnd']),
+    );
+  },
+
+  'SingleChildScrollView': (BuildContext context, DataSource source) {
+    return SingleChildScrollView(
+      scrollDirection: ArgumentDecoders.enumValue<Axis>(Axis.values, source, ['scrollDirection']) ?? Axis.vertical,
+      reverse: source.v<bool>(['reverse']) ?? false,
+      padding: ArgumentDecoders.edgeInsets(source, ['padding']),
+      primary: source.v<bool>(['primary']) ?? true,
+      // ScrollPhysics? physics,
+      // ScrollController? controller,
+      child: source.optionalChild(['child']),
+      dragStartBehavior: ArgumentDecoders.enumValue<DragStartBehavior>(DragStartBehavior.values, source, ['dragStartBehavior']) ?? DragStartBehavior.start,
+      clipBehavior: ArgumentDecoders.enumValue<Clip>(Clip.values, source, ['clipBehavior']) ?? Clip.hardEdge,
+      restorationId: source.v<String>(['restorationId']),
+      keyboardDismissBehavior: ArgumentDecoders.enumValue<ScrollViewKeyboardDismissBehavior>(ScrollViewKeyboardDismissBehavior.values, source, ['keyboardDismissBehavior']) ?? ScrollViewKeyboardDismissBehavior.manual,
+    );
+  },
+
+  'SizedBox': (BuildContext context, DataSource source) {
+    return SizedBox(
+      width: source.v<double>(['width']),
+      height: source.v<double>(['height']),
+      child: source.optionalChild(['child']),
+    );
+  },
+
+  'SizedBoxExpand': (BuildContext context, DataSource source) {
+    return SizedBox.expand(
+      child: source.optionalChild(['child']),
+    );
+  },
+
+  'SizedBoxShrink': (BuildContext context, DataSource source) {
+    return SizedBox.shrink(
+      child: source.optionalChild(['child']),
+    );
+  },
+
+  'Spacer': (BuildContext context, DataSource source) {
+    return Spacer(
+      flex: source.v<int>(['flex']) ?? 1,
+    );
+  },
+
+  'Stack': (BuildContext context, DataSource source) {
+    return Stack(
+      alignment: ArgumentDecoders.alignment(source, ['alignment']) ?? AlignmentDirectional.topStart,
+      textDirection: ArgumentDecoders.enumValue<TextDirection>(TextDirection.values, source, ['textDirection']),
+      fit: ArgumentDecoders.enumValue<StackFit>(StackFit.values, source, ['fit']) ?? StackFit.loose,
+      clipBehavior: ArgumentDecoders.enumValue<Clip>(Clip.values, source, ['clipBehavior']) ?? Clip.hardEdge,
+      children: source.childList(['children']),
+    );
+  },
+
+  'Text': (BuildContext context, DataSource source) {
+    String? text = source.v<String>(['text']);
+    if (text == null) {
+      final StringBuffer builder = StringBuffer();
+      final int count = source.length(['text']);
+      for (int index = 0; index < count; index += 1) {
+        builder.write(source.v<String>(['text', index]) ?? '');
+      }
+      text = builder.toString();
+    }
+    return Text(
+      text,
+      style: ArgumentDecoders.textStyle(source, ['style']),
+      strutStyle: ArgumentDecoders.strutStyle(source, ['strutStyle']),
+      textAlign: ArgumentDecoders.enumValue<TextAlign>(TextAlign.values, source, ['textAlign']),
+      textDirection: ArgumentDecoders.enumValue<TextDirection>(TextDirection.values, source, ['textDirection']),
+      locale: ArgumentDecoders.locale(source, ['locale']),
+      softWrap: source.v<bool>(['softWrap']),
+      overflow: ArgumentDecoders.enumValue<TextOverflow>(TextOverflow.values, source, ['overflow']),
+      textScaleFactor: source.v<double>(['textScaleFactor']),
+      maxLines: source.v<int>(['maxLines']),
+      semanticsLabel: source.v<String>(['semanticsLabel']),
+      textWidthBasis: ArgumentDecoders.enumValue<TextWidthBasis>(TextWidthBasis.values, source, ['textWidthBasis']),
+      textHeightBehavior: ArgumentDecoders.textHeightBehavior(source, ['textHeightBehavior']),
+    );
+  },
+
+  'Wrap': (BuildContext context, DataSource source) {
+    return Wrap(
+      direction: ArgumentDecoders.enumValue<Axis>(Axis.values, source, ['direction']) ?? Axis.horizontal,
+      alignment: ArgumentDecoders.enumValue<WrapAlignment>(WrapAlignment.values, source, ['alignment']) ?? WrapAlignment.start,
+      spacing: source.v<double>(['spacing']) ?? 0.0,
+      runAlignment: ArgumentDecoders.enumValue<WrapAlignment>(WrapAlignment.values, source, ['runAlignment']) ?? WrapAlignment.start,
+      runSpacing: source.v<double>(['runSpacing']) ?? 0.0,
+      crossAxisAlignment: ArgumentDecoders.enumValue<WrapCrossAlignment>(WrapCrossAlignment.values, source, ['crossAxisAlignment']) ?? WrapCrossAlignment.start,
+      textDirection: ArgumentDecoders.enumValue<TextDirection>(TextDirection.values, source, ['textDirection']),
+      verticalDirection: ArgumentDecoders.enumValue<VerticalDirection>(VerticalDirection.values, source, ['verticalDirection']) ?? VerticalDirection.down,
+      clipBehavior: ArgumentDecoders.enumValue<Clip>(Clip.values, source, ['clipBehavior']) ?? Clip.none,
+      children: source.childList(['children']),
+    );
+  },
+
+};
diff --git a/packages/rfw/lib/flutter/material_widgets.dart b/packages/rfw/lib/flutter/material_widgets.dart
new file mode 100644
index 0000000..c10d9f3
--- /dev/null
+++ b/packages/rfw/lib/flutter/material_widgets.dart
@@ -0,0 +1,350 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// There's a lot of <Object>[] lists in this file so to avoid making this
+// file even less readable we relax our usual stance on verbose typing.
+// ignore_for_file: always_specify_types
+
+// This file is hand-formatted.
+
+import 'package:flutter/gestures.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter/widgets.dart';
+
+import 'argument_decoders.dart';
+import 'runtime.dart';
+
+/// A widget library for Remote Flutter Widgets that defines widgets that are
+/// implemented on the client in terms of Flutter widgets from the `material`
+/// Dart library.
+///
+/// The following widgets are implemented:
+///
+///  * [AboutListTile]
+///  * [AppBar]
+///  * [ButtonBar]
+///  * [Card]
+///  * [CircularProgressIndicator]
+///  * [Divider]
+///  * [DrawerHeader]
+///  * [ElevatedButton]
+///  * [FloatingActionButton]
+///  * [InkWell]
+///  * [LinearProgressIndicator]
+///  * [ListTile]
+///  * [OutlinedButton]
+///  * [Scaffold]
+///  * [TextButton]
+///  * [VerticalDivider]
+///
+/// For each, every parameter is implemented using the same name. Parameters
+/// that take structured types are represented using maps, with each named
+/// parameter of that type's default constructor represented by a key. The
+/// conventions edscribed for [createCoreWidgets] are reused here.
+///
+/// In addition, the following conventions are introduced:
+///
+///  * Hero tags are always strings.
+///
+///  * [VisualDensity] is represented in the manner described in the documentation
+///    of the [ArgumentDecoders.visualDensity] method.
+///
+/// Some features are not supported:
+///
+///  * [AppBar]s do not support [AppBar.bottom], [AppBar.flexibleSpace], and
+///    related properties. Also, [AppBar.systemOverlayStyle] is not suported.
+///
+///  * Theming in general is not currently supported.
+///
+///  * Properties whose values are [Animation]s or based on
+///    [MaterialStateProperty] are not supported.
+///
+///  * Features related to focus or configuring mouse support are not
+///    implemented.
+///
+///  * Callbacks such as [Scafford.onDrawerChanged] are not exposed.
+///
+///  * The [Scaffold]'s floating action button position and animation features
+///    are not supported.
+///
+/// In general, the trend will all of these unsupported features is that this
+/// library doesn't support features that can't be trivially expressed using the
+/// JSON-like structures of RFW. For example, [MaterialStateProperty] is
+/// designed to be used with code to select the values, which doesn't work well
+/// in the RFW structure.
+LocalWidgetLibrary createMaterialWidgets() => LocalWidgetLibrary(_materialWidgetsDefinitions);
+
+Map<String, LocalWidgetBuilder> get _materialWidgetsDefinitions => <String, LocalWidgetBuilder>{
+
+  // Keep these in alphabetical order.
+
+  'AboutListTile': (BuildContext context, DataSource source) {
+    return AboutListTile(
+      icon: source.optionalChild(['icon']),
+      child: source.optionalChild(['child']),
+      applicationName: source.v<String>(['applicationName']),
+      applicationVersion: source.v<String>(['applicationVersion']),
+      applicationIcon: source.optionalChild(['applicationIcon']),
+      applicationLegalese: source.v<String>(['applicationLegalese']),
+      aboutBoxChildren: source.childList(['aboutBoxChildren']),
+      dense: source.v<bool>(['dense']),
+    );
+  },
+
+  'AppBar': (BuildContext context, DataSource source) {
+    // not implemented: bottom (and bottomOpacity), flexibleSpace; systemOverlayStyle
+    return AppBar(
+      leading: source.optionalChild(['leading']),
+      automaticallyImplyLeading: source.v<bool>(['automaticallyImplyLeading']) ?? true,
+      title: source.optionalChild(['title']),
+      actions: source.childList(['actions']),
+      elevation: source.v<double>(['elevation']),
+      shadowColor: ArgumentDecoders.color(source, ['shadowColor']),
+      shape: ArgumentDecoders.shapeBorder(source, ['shape']),
+      backgroundColor: ArgumentDecoders.color(source, ['backgroundColor']),
+      foregroundColor: ArgumentDecoders.color(source, ['foregroundColor']),
+      iconTheme: ArgumentDecoders.iconThemeData(source, ['iconTheme']),
+      actionsIconTheme: ArgumentDecoders.iconThemeData(source, ['actionsIconTheme']),
+      primary: source.v<bool>(['primary']) ?? true,
+      centerTitle: source.v<bool>(['centerTitle']),
+      excludeHeaderSemantics: source.v<bool>(['excludeHeaderSemantics']) ?? false,
+      titleSpacing: source.v<double>(['titleSpacing']),
+      toolbarOpacity: source.v<double>(['toolbarOpacity']) ?? 1.0,
+      toolbarHeight: source.v<double>(['toolbarHeight']),
+      leadingWidth: source.v<double>(['leadingWidth']),
+      toolbarTextStyle: ArgumentDecoders.textStyle(source, ['toolbarTextStyle']),
+      titleTextStyle: ArgumentDecoders.textStyle(source, ['titleTextStyle']),
+    );
+  },
+
+  'ButtonBar': (BuildContext context, DataSource source) {
+    // not implemented: buttonTextTheme
+    return ButtonBar(
+      alignment: ArgumentDecoders.enumValue<MainAxisAlignment>(MainAxisAlignment.values, source, ['alignment']) ?? MainAxisAlignment.start,
+      mainAxisSize: ArgumentDecoders.enumValue<MainAxisSize>(MainAxisSize.values, source, ['mainAxisSize']) ?? MainAxisSize.max,
+      buttonMinWidth: source.v<double>(['buttonMinWidth']),
+      buttonHeight: source.v<double>(['buttonHeight']),
+      buttonPadding: ArgumentDecoders.edgeInsets(source, ['buttonPadding']),
+      buttonAlignedDropdown: source.v<bool>(['buttonAlignedDropdown']) ?? false,
+      layoutBehavior: ArgumentDecoders.enumValue<ButtonBarLayoutBehavior>(ButtonBarLayoutBehavior.values, source, ['layoutBehavior']),
+      overflowDirection: ArgumentDecoders.enumValue<VerticalDirection>(VerticalDirection.values, source, ['overflowDirection']),
+      overflowButtonSpacing: source.v<double>(['overflowButtonSpacing']),
+      children: source.childList(['children']),
+    );
+  },
+
+  'Card': (BuildContext context, DataSource source) {
+    return Card(
+      color: ArgumentDecoders.color(source, ['color']),
+      shadowColor: ArgumentDecoders.color(source, ['shadowColor']),
+      elevation: source.v<double>(['elevation']),
+      shape: ArgumentDecoders.shapeBorder(source, ['shape']),
+      borderOnForeground: source.v<bool>(['borderOnForeground']) ?? true,
+      margin: ArgumentDecoders.edgeInsets(source, ['margin']),
+      clipBehavior: ArgumentDecoders.enumValue<Clip>(Clip.values, source, ['clipBehavior']) ?? Clip.none,
+      child: source.optionalChild(['child']),
+      semanticContainer: source.v<bool>(['semanticContainer']) ?? true,
+    );
+  },
+
+  'CircularProgressIndicator': (BuildContext context, DataSource source) {
+    // not implemented: valueColor
+    return CircularProgressIndicator(
+      value: source.v<double>(['value']),
+      color: ArgumentDecoders.color(source, ['color']),
+      backgroundColor: ArgumentDecoders.color(source, ['backgroundColor']),
+      strokeWidth: source.v<double>(['strokeWidth']) ?? 4.0,
+      semanticsLabel: source.v<String>(['semanticsLabel']),
+      semanticsValue: source.v<String>(['semanticsValue']),
+    );
+  },
+
+  'Divider': (BuildContext context, DataSource source) {
+    return Divider(
+      height: source.v<double>(['height']),
+      thickness: source.v<double>(['thickness']),
+      indent: source.v<double>(['indent']),
+      endIndent: source.v<double>(['endIndent']),
+      color: ArgumentDecoders.color(source, ['color']),
+    );
+  },
+
+  'Drawer': (BuildContext context, DataSource source) {
+    return Drawer(
+      elevation: source.v<double>(['elevation']) ?? 16.0,
+      semanticLabel: source.v<String>(['semanticLabel']),
+      child: source.optionalChild(['child']),
+    );
+  },
+
+  'DrawerHeader': (BuildContext context, DataSource source) {
+    return DrawerHeader(
+      duration: ArgumentDecoders.duration(source, ['duration'], context),
+      curve: ArgumentDecoders.curve(source, ['curve'], context),
+      decoration: ArgumentDecoders.decoration(source, ['decoration']),
+      margin: ArgumentDecoders.edgeInsets(source, ['margin']) ?? const EdgeInsets.only(bottom: 8.0),
+      padding: ArgumentDecoders.edgeInsets(source, ['padding']) ?? const EdgeInsets.fromLTRB(16.0, 16.0, 16.0, 8.0),
+      child: source.optionalChild(['child']),
+    );
+  },
+
+  'ElevatedButton': (BuildContext context, DataSource source) {
+    // not implemented: buttonStyle, focusNode
+    return ElevatedButton(
+      onPressed: source.voidHandler(['onPressed']),
+      onLongPress: source.voidHandler(['onLongPress']),
+      autofocus: source.v<bool>(['autofocus']) ?? false,
+      clipBehavior: ArgumentDecoders.enumValue<Clip>(Clip.values, source, ['clipBehavior']) ?? Clip.none,
+      child: source.child(['child']),
+    );
+  },
+
+  'FloatingActionButton': (BuildContext context, DataSource source) {
+    // not implemented: mouseCursor, focusNode
+    return FloatingActionButton(
+      child: source.child(['child']),
+      tooltip: source.v<String>(['tooltip']),
+      foregroundColor: ArgumentDecoders.color(source, ['foregroundColor']),
+      backgroundColor: ArgumentDecoders.color(source, ['backgroundColor']),
+      focusColor: ArgumentDecoders.color(source, ['focusColor']),
+      hoverColor: ArgumentDecoders.color(source, ['hoverColor']),
+      splashColor: ArgumentDecoders.color(source, ['splashColor']),
+      heroTag: source.v<String>(['heroTag']),
+      elevation: source.v<double>(['elevation']),
+      focusElevation: source.v<double>(['focusElevation']),
+      hoverElevation: source.v<double>(['hoverElevation']),
+      highlightElevation: source.v<double>(['highlightElevation']),
+      disabledElevation: source.v<double>(['disabledElevation']),
+      onPressed: source.voidHandler(['onPressed']),
+      mini: source.v<bool>(['mini']) ?? false,
+      shape: ArgumentDecoders.shapeBorder(source, ['shape']),
+      clipBehavior: ArgumentDecoders.enumValue<Clip>(Clip.values, source, ['clipBehavior']) ?? Clip.none,
+      autofocus: source.v<bool>(['autofocus']) ?? false,
+      materialTapTargetSize: ArgumentDecoders.enumValue<MaterialTapTargetSize>(MaterialTapTargetSize.values, source, ['materialTapTargetSize']),
+      isExtended: source.v<bool>(['isExtended']) ?? false,
+      enableFeedback: source.v<bool>(['enableFeedback']),
+    );
+  },
+
+  'InkWell': (BuildContext context, DataSource source) {
+    // not implemented: onHighlightChanged, onHover; mouseCursor; focusColor, hoverColor, highlightColor, overlayColor, splashColor; splashFactory; focusNode, onFocusChange
+    return InkWell(
+      onTap: source.voidHandler(['onTap']),
+      onDoubleTap: source.voidHandler(['onDoubleTap']),
+      onLongPress: source.voidHandler(['onLongPress']),
+      onTapDown: source.handler(['onTapDown'], (VoidCallback trigger) => (TapDownDetails details) => trigger()),
+      onTapCancel: source.voidHandler(['onTapCancel']),
+      radius: source.v<double>(['radius']),
+      borderRadius: ArgumentDecoders.borderRadius(source, ['borderRadius'])?.resolve(Directionality.of(context)),
+      customBorder: ArgumentDecoders.shapeBorder(source, ['customBorder']),
+      enableFeedback: source.v<bool>(['enableFeedback']) ?? true,
+      excludeFromSemantics: source.v<bool>(['excludeFromSemantics']) ?? false,
+      autofocus: source.v<bool>(['autofocus']) ?? false,
+      child: source.optionalChild(['child']),
+    );
+  },
+
+  'LinearProgressIndicator': (BuildContext context, DataSource source) {
+    // not implemented: valueColor
+    return LinearProgressIndicator(
+      value: source.v<double>(['value']),
+      color: ArgumentDecoders.color(source, ['color']),
+      backgroundColor: ArgumentDecoders.color(source, ['backgroundColor']),
+      minHeight: source.v<double>(['minHeight']),
+      semanticsLabel: source.v<String>(['semanticsLabel']),
+      semanticsValue: source.v<String>(['semanticsValue']),
+    );
+  },
+
+  'ListTile': (BuildContext context, DataSource source) {
+    // not implemented: mouseCursor, focusNode
+    return ListTile(
+      leading: source.optionalChild(['leading']),
+      title: source.optionalChild(['title']),
+      subtitle: source.optionalChild(['subtitle']),
+      trailing: source.optionalChild(['trailing']),
+      isThreeLine: source.v<bool>(['isThreeLine']) ?? false,
+      dense: source.v<bool>(['dense']),
+      visualDensity: ArgumentDecoders.visualDensity(source, ['visualDensity']),
+      shape: ArgumentDecoders.shapeBorder(source, ['shape']),
+      contentPadding: ArgumentDecoders.edgeInsets(source, ['contentPadding']),
+      enabled: source.v<bool>(['enabled']) ?? true,
+      onTap: source.voidHandler(['onTap']),
+      onLongPress: source.voidHandler(['onLongPress']),
+      selected: source.v<bool>(['selected']) ?? false,
+      focusColor: ArgumentDecoders.color(source, ['focusColor']),
+      hoverColor: ArgumentDecoders.color(source, ['hoverColor']),
+      autofocus: source.v<bool>(['autofocus']) ?? false,
+      tileColor: ArgumentDecoders.color(source, ['tileColor']),
+      selectedTileColor: ArgumentDecoders.color(source, ['selectedTileColor']),
+      enableFeedback: source.v<bool>(['enableFeedback']),
+      horizontalTitleGap: source.v<double>(['horizontalTitleGap']),
+      minVerticalPadding: source.v<double>(['minVerticalPadding']),
+      minLeadingWidth: source.v<double>(['minLeadingWidth']),
+    );
+  },
+
+  'OutlinedButton': (BuildContext context, DataSource source) {
+    // not implemented: buttonStyle, focusNode
+    return OutlinedButton(
+      onPressed: source.voidHandler(['onPressed']),
+      onLongPress: source.voidHandler(['onLongPress']),
+      autofocus: source.v<bool>(['autofocus']) ?? false,
+      clipBehavior: ArgumentDecoders.enumValue<Clip>(Clip.values, source, ['clipBehavior']) ?? Clip.none,
+      child: source.child(['child']),
+    );
+  },
+
+  'Scaffold': (BuildContext context, DataSource source) {
+    // not implemented: floatingActionButtonLocation, floatingActionButtonAnimator; onDrawerChanged, onEndDrawerChanged
+    final Widget? appBarWidget = source.optionalChild(['appBar']);
+    final List<Widget> persistentFooterButtons = source.childList(['persistentFooterButtons']);
+    return Scaffold(
+      appBar: appBarWidget == null ? null : PreferredSize(
+        preferredSize: Size.fromHeight(source.v<double>(['bottomHeight']) ?? 56.0),
+        child: appBarWidget,
+      ),
+      body: source.optionalChild(['body']),
+      floatingActionButton: source.optionalChild(['floatingActionButton']),
+      persistentFooterButtons: persistentFooterButtons.isEmpty ? null : persistentFooterButtons,
+      drawer: source.optionalChild(['drawer']),
+      endDrawer: source.optionalChild(['endDrawer']),
+      bottomNavigationBar: source.optionalChild(['bottomNavigationBar']),
+      bottomSheet: source.optionalChild(['bottomSheet']),
+      backgroundColor: ArgumentDecoders.color(source, ['backgroundColor']),
+      resizeToAvoidBottomInset: source.v<bool>(['resizeToAvoidBottomInset']),
+      primary: source.v<bool>(['primary']) ?? true,
+      drawerDragStartBehavior: ArgumentDecoders.enumValue<DragStartBehavior>(DragStartBehavior.values, source, ['drawerDragStartBehavior']) ?? DragStartBehavior.start,
+      extendBody: source.v<bool>(['extendBody']) ?? false,
+      extendBodyBehindAppBar: source.v<bool>(['extendBodyBehindAppBar']) ?? false,
+      drawerScrimColor: ArgumentDecoders.color(source, ['drawerScrimColor']),
+      drawerEdgeDragWidth: source.v<double>(['drawerEdgeDragWidth']),
+      drawerEnableOpenDragGesture: source.v<bool>(['drawerEnableOpenDragGesture']) ?? true,
+      endDrawerEnableOpenDragGesture: source.v<bool>(['endDrawerEnableOpenDragGesture']) ?? true,
+      restorationId: source.v<String>(['restorationId']),
+    );
+  },
+
+  'TextButton': (BuildContext context, DataSource source) {
+    // not implemented: buttonStyle, focusNode
+    return TextButton(
+      onPressed: source.voidHandler(['onPressed']),
+      onLongPress: source.voidHandler(['onLongPress']),
+      autofocus: source.v<bool>(['autofocus']) ?? false,
+      clipBehavior: ArgumentDecoders.enumValue<Clip>(Clip.values, source, ['clipBehavior']) ?? Clip.none,
+      child: source.child(['child']),
+    );
+  },
+
+  'VerticalDivider': (BuildContext context, DataSource source) {
+    return VerticalDivider(
+      width: source.v<double>(['width']),
+      thickness: source.v<double>(['thickness']),
+      indent: source.v<double>(['indent']),
+      endIndent: source.v<double>(['endIndent']),
+      color: ArgumentDecoders.color(source, ['color']),
+    );
+  },
+
+};
diff --git a/packages/rfw/lib/flutter/remote_widget.dart b/packages/rfw/lib/flutter/remote_widget.dart
new file mode 100644
index 0000000..34d05fa
--- /dev/null
+++ b/packages/rfw/lib/flutter/remote_widget.dart
@@ -0,0 +1,99 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This file is hand-formatted.
+
+import 'package:flutter/widgets.dart';
+
+import '../dart/model.dart';
+import 'content.dart';
+import 'runtime.dart';
+
+export '../dart/model.dart' show DynamicMap, LibraryName;
+
+/// Injection point for a remote widget.
+///
+/// This widget combines an RFW [Runtime] and [DynamicData], inserting a
+/// specified [widget] into the tree.
+class RemoteWidget extends StatefulWidget {
+  /// Inserts the specified [widget] into the tree.
+  ///
+  /// The [onEvent] argument is optional. When omitted, events are discarded.
+  const RemoteWidget({ Key? key, required this.runtime, required this.widget, required this.data, this.onEvent }) : super(key: key);
+
+  /// The [Runtime] to use to render the widget specified by [library] and [name].
+  ///
+  /// This should update rarely (doing so is relatively expensive), but it is
+  /// fine to update it. For example, a client could update this on the fly when
+  /// the server deploys a new version of the widget library.
+  ///
+  /// Frequent updates (e.g. animations) should be done by updating [data] instead.
+  final Runtime runtime;
+
+  /// The name of the widget to display, and the library from which to obtain
+  /// in.
+  ///
+  /// The widget must be either declared in the specified library or one of its
+  /// dependencies.
+  ///
+  /// The data to show in the widget is specified using [data].
+  final FullyQualifiedWidgetName widget;
+
+  /// The data to which the widget specified by [name] will be bound.
+  ///
+  /// This includes data that comes from the application, e.g. a description of
+  /// the user's device, the current time, or an animation controller's value,
+  /// and data that comes from the server, e.g. the contents of the user's
+  /// shopping cart.
+  ///
+  /// This can be updated frequently (once per frame) using
+  /// [DynamicContent.update].
+  final DynamicContent data;
+
+  /// Called when there's an event triggered by a remote widget.
+  ///
+  /// If this is null, events are discarded.
+  final RemoteEventHandler? onEvent;
+
+  @override
+  _RemoteWidgetState createState() => _RemoteWidgetState();
+}
+
+class _RemoteWidgetState extends State<RemoteWidget> {
+  @override
+  void initState() {
+    super.initState();
+    widget.runtime.addListener(_runtimeChanged);
+  }
+
+  @override
+  void didUpdateWidget(RemoteWidget oldWidget) {
+    super.didUpdateWidget(oldWidget);
+    if (oldWidget.runtime != widget.runtime) {
+      oldWidget.runtime.removeListener(_runtimeChanged);
+      widget.runtime.addListener(_runtimeChanged);
+    }
+  }
+
+  @override
+  void dispose() {
+    widget.runtime.removeListener(_runtimeChanged);
+    super.dispose();
+  }
+
+  void _runtimeChanged() {
+    setState(() { /* widget probably changed */ });
+  }
+
+  void _eventHandler(String eventName, DynamicMap eventArguments) {
+    if (widget.onEvent != null) {
+      widget.onEvent!(eventName, eventArguments);
+    }
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return widget.runtime.build(context, widget.widget, widget.data, _eventHandler);
+  }
+}
diff --git a/packages/rfw/lib/flutter/runtime.dart b/packages/rfw/lib/flutter/runtime.dart
new file mode 100644
index 0000000..a0e594d
--- /dev/null
+++ b/packages/rfw/lib/flutter/runtime.dart
@@ -0,0 +1,1104 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This file is hand-formatted.
+
+import 'package:flutter/foundation.dart';
+import 'package:flutter/widgets.dart';
+
+import '../dart/model.dart';
+import 'content.dart';
+
+/// Signature of builders for local widgets.
+///
+/// The [LocalWidgetLibrary] class wraps a map of widget names to
+/// [LocalWidgetBuilder] callbacks.
+typedef LocalWidgetBuilder = Widget Function(BuildContext context, DataSource source);
+
+/// Signature of the callback passed to a [RemoteWidget].
+///
+/// This is used by [RemoteWidget] and [Runtime.build] as the callback for
+/// events triggered by remote widgets.
+typedef RemoteEventHandler = void Function(String eventName, DynamicMap eventArguments);
+
+/// Signature of the callback passed to [DataSource.handler].
+///
+/// The callback should return a function of type `T`. That function should call
+/// `trigger`.
+///
+/// See [DataSource.handler] for details.
+typedef HandlerGenerator<T extends Function> = T Function(HandlerTrigger trigger);
+
+/// Signature of the callback passed to a [HandlerGenerator].
+///
+/// See [DataSource.handler] for details.
+typedef HandlerTrigger = void Function([DynamicMap? extraArguments]);
+
+/// Used to indicate that there is an error with one of the libraries loaded
+/// into the Remote Flutter Widgets [Runtime].
+///
+/// For example, a reference to a state variable did not match any actual state
+/// values, or a library import loop.
+class RemoteFlutterWidgetsException implements Exception {
+  /// Creates a [RemoteFlutterWidgetsException].
+  ///
+  /// The message should be a complete sentence, starting with a capital letter
+  /// and ending with a period.
+  const RemoteFlutterWidgetsException(this.message);
+
+  /// A description of the problem that was detected.
+  ///
+  /// This will end with a period.
+  final String message;
+
+  @override
+  String toString() => message;
+}
+
+/// Interface for [LocalWidgetBuilder] to obtain data from arguments.
+///
+/// The interface exposes the [v] method, the argument to which is a list of
+/// keys forming a path to a node in the arguments expected by the widget. If
+/// the method's type argument does not match the value obtained, null is
+/// returned instead.
+///
+/// In addition, to fetch widgets specifically, the [child] and [childList]
+/// methods must be used, and to fetch event handlers, the [handler] method must
+/// be used.
+///
+/// The [isList] and [isMap] methods can be used to avoid inspecting keys that
+/// may not be present (e.g. before reading 15 keys in a map that isn't even
+/// present, consider checking if the map is present using [isMap] and
+/// short-circuiting the key lookups if it is not).
+///
+/// To iterate over a list, the [length] method can be used to find the number
+/// of items in the list.
+abstract class DataSource {
+  /// Return the int, double, bool, or String value at the given path of the
+  /// arguments to the widget.
+  ///
+  /// If `T` is not [Object] and `T` does not match the type of the value
+  /// obtained, then the method returns null.
+  T? v<T>(List<Object> argsKey);
+
+  /// Return true if the given key identifies a list, otherwise false.
+  bool isList(List<Object> argsKey);
+
+  /// Return the length of the list at the given path of the arguments to the
+  /// widget.
+  ///
+  /// If the given path does not identify a list, returns zero.
+  int length(List<Object> argsKey);
+
+  /// Return true if the given key identifies a map, otherwise false.
+  bool isMap(List<Object> argsKey);
+
+  /// Build the child at the given key.
+  ///
+  /// If the node specified is not a widget, returns an [ErrorWidget].
+  ///
+  /// See also:
+  ///
+  ///  * [optionalChild], which returns null if the widget is missing.
+  Widget child(List<Object> argsKey);
+
+  /// Build the child at the given key.
+  ///
+  /// If the node specified is not a widget, returns null.
+  ///
+  /// See also:
+  ///
+  ///  * [child], which returns an [ErrorWidget] instead of null if the widget
+  ///    is missing.
+  Widget? optionalChild(List<Object> argsKey);
+
+  /// Builds the children at the given key.
+  ///
+  /// If the node is missing, returns an empty list.
+  ///
+  /// If the node specified is not a list of widgets, returns a list with the
+  /// non-widget nodes replaced by [ErrorWidget].
+  List<Widget> childList(List<Object> argsKey);
+
+  /// Gets a [VoidCallback] event handler at the given key.
+  ///
+  /// If the node specified is an [AnyEventHandler] or a [DynamicList] of
+  /// [AnyEventHandler]s, returns a callback that invokes the specified event
+  /// handler(s), merging the given `extraArguments` into the arguments
+  /// specified in each event handler. In the event of a key conflict (where
+  /// both the arguments specified in the remote widget declaration and the
+  /// argument provided to this method have the same name), the arguments
+  /// specified here take precedence.
+  VoidCallback? voidHandler(List<Object> argsKey, [ DynamicMap? extraArguments ]);
+
+  /// Gets an event handler at the given key.
+  ///
+  /// The event handler can be of any Function type, as specified by the type
+  /// argument `T`.
+  ///
+  /// When this method is called, the second argument, `generator`, is invoked.
+  /// The `generator` callback must return a function, which we will call
+  /// _entrypoint_, that matches the signature of `T`. The `generator` callback
+  /// receives an argument, which we will call `trigger`. The _entrypoint_
+  /// function must call `trigger`, optionally passing it any extra arguments
+  /// that should be merged into the arguments specified in each event handler.
+  ///
+  /// This is admittedly a little confusing. At its core, the problem is that
+  /// this method cannot itself automatically create a function (_entrypoint_)
+  /// of the right type (`T`), and therefore a callback (`generator`) that knows
+  /// how to wrap a function body (`trigger`) in the right signature (`T`) is
+  /// needed to actually build that function (_entrypoint_).
+  T? handler<T extends Function>(List<Object> argsKey, HandlerGenerator<T> generator);
+}
+
+/// Widgets defined by the client application. All remote widgets eventually
+/// bottom out in these widgets.
+class LocalWidgetLibrary extends WidgetLibrary {
+  /// Create a [LocalWidgetLibrary].
+  ///
+  /// The given map must not change once the object is created.
+  LocalWidgetLibrary(this._widgets);
+
+  final Map<String, LocalWidgetBuilder> _widgets;
+
+  /// Returns the builder for the widget of the given name, if any.
+  @protected
+  LocalWidgetBuilder? findConstructor(String name) {
+    return _widgets[name];
+  }
+}
+
+class _ResolvedConstructor {
+  const _ResolvedConstructor(this.fullName, this.constructor);
+  final FullyQualifiedWidgetName fullName;
+  final Object constructor;
+}
+
+/// The logic that builds and maintains Remote Flutter Widgets.
+///
+/// To declare the libraries of widgets, the [update] method is used.
+///
+/// At least one [LocalWidgetLibrary] instance must be declared
+/// so that [RemoteWidgetLibrary] instances can resolve to real widgets.
+///
+/// The [build] method returns a [Widget] generated from one of the libraries of
+/// widgets added in this manner. Generally, it is simpler to use the
+/// [RemoteWidget] widget (which calls [build]).
+class Runtime extends ChangeNotifier {
+  /// Create a [Runtime] object.
+  ///
+  /// This object should be [dispose]d when it is no longer needed.
+  Runtime();
+
+  final Map<LibraryName, WidgetLibrary> _libraries = <LibraryName, WidgetLibrary>{};
+
+  /// Replace the definitions of the specified library (`name`).
+  ///
+  /// References to widgets that are not defined in the available libraries will
+  /// default to using the [ErrorWidget] widget.
+  ///
+  /// [LocalWidgetLibrary] and [RemoteWidgetLibrary] instances are added using
+  /// this method.
+  ///
+  /// [RemoteWidgetLibrary] instances are typically first obtained using
+  /// [decodeLibraryBlob].
+  ///
+  /// To remove a library, the libraries must be cleared using [clearLibraries]
+  /// and then all the libraries must be readded.
+  void update(LibraryName name, WidgetLibrary library) {
+    _libraries[name] = library;
+    _clearCache();
+  }
+
+  /// Remove all the libraries and start afresh.
+  ///
+  /// Calling this notifies the listeners, which typically causes them to
+  /// rebuild their widgets in the next frame (for example, that is how
+  /// [RemoteWidget] is implemented). If no libraries are readded after calling
+  /// [clearLibraries], and there are any listeners, they will fail to rebuild
+  /// any widgets that they were configured to create. For this reason, this
+  /// call should usually be immediately followed by calls to [update].
+  void clearLibraries() {
+    _libraries.clear();
+    _clearCache();
+  }
+
+  final Map<FullyQualifiedWidgetName, _ResolvedConstructor?> _cachedConstructors = <FullyQualifiedWidgetName, _ResolvedConstructor?>{};
+  final Map<FullyQualifiedWidgetName, _CurriedWidget> _widgets = <FullyQualifiedWidgetName, _CurriedWidget>{};
+
+  void _clearCache() {
+    _cachedConstructors.clear();
+    _widgets.clear();
+    notifyListeners();
+  }
+
+  /// Build the root widget of a Remote Widget subtree.
+  ///
+  /// The widget is identified by a [FullyQualifiedWidgetName], which identifies
+  /// a library and a widget name. The widget does not strictly have to be in
+  /// that library, so long as it is in that library's dependencies.
+  ///
+  /// The data for the widget is given by the `data` argument. That object can
+  /// be updated independently, the widget will rebuild appropriately as it
+  /// changes.
+  ///
+  /// The `remoteEventTarget` argument is the callback to invoke whenever a
+  /// remote widget event handler is triggered.
+  Widget build(BuildContext context, FullyQualifiedWidgetName widget, DynamicContent data, RemoteEventHandler remoteEventTarget) {
+    final _CurriedWidget boundWidget;
+    if (_widgets.containsKey(widget)) {
+      boundWidget = _widgets[widget]!;
+    } else {
+      _checkForImportLoops(widget.library);
+      boundWidget = _applyConstructorAndBindArguments(widget, const <String, Object?>{}, -1, <FullyQualifiedWidgetName>{});
+      _widgets[widget] = boundWidget;
+    }
+    return boundWidget.build(context, data, remoteEventTarget, const <_WidgetState>[]);
+  }
+
+  void _checkForImportLoops(LibraryName name, [ Set<LibraryName>? visited ]) {
+    final WidgetLibrary? library = _libraries[name];
+    if (library is RemoteWidgetLibrary) {
+      visited ??= <LibraryName>{};
+      visited.add(name);
+      for (final Import import in library.imports) {
+        final LibraryName dependency = import.name;
+        if (visited.contains(dependency)) {
+          final List<LibraryName> path = <LibraryName>[dependency];
+          for (final LibraryName name in visited.toList().reversed) {
+            if (name == dependency) {
+              break;
+            }
+            path.add(name);
+          }
+          if (path.length == 1) {
+            assert(path.single == dependency);
+            throw RemoteFlutterWidgetsException('Library $dependency depends on itself.');
+          } else {
+            throw RemoteFlutterWidgetsException('Library $dependency indirectly depends on itself via ${path.reversed.join(" which depends on ")}.');
+          }
+        }
+        _checkForImportLoops(dependency, visited.toSet());
+      }
+    }
+  }
+
+  _ResolvedConstructor? _findConstructor(FullyQualifiedWidgetName fullName) {
+    if (_cachedConstructors.containsKey(fullName)) {
+      return _cachedConstructors[fullName];
+    }
+    final WidgetLibrary? library = _libraries[fullName.library];
+    if (library is RemoteWidgetLibrary) {
+      for (final WidgetDeclaration constructor in library.widgets) {
+        if (constructor.name == fullName.widget) {
+          return _cachedConstructors[fullName] = _ResolvedConstructor(fullName, constructor);
+        }
+      }
+      for (final Import import in library.imports) {
+        final LibraryName dependency = import.name;
+        final _ResolvedConstructor? result = _findConstructor(FullyQualifiedWidgetName(dependency, fullName.widget));
+        if (result != null) {
+          return _cachedConstructors[fullName] = result;
+        }
+      }
+    } else if (library is LocalWidgetLibrary) {
+      final LocalWidgetBuilder? constructor = library.findConstructor(fullName.widget);
+      if (constructor != null) {
+        return _cachedConstructors[fullName] = _ResolvedConstructor(fullName, constructor);
+      }
+    } else {
+      assert(library is Null); // ignore: prefer_void_to_null, type_check_with_null, https://github.com/dart-lang/sdk/issues/47017#issuecomment-907562014
+    }
+    _cachedConstructors[fullName] = null;
+    return null;
+  }
+
+  Iterable<LibraryName> _findMissingLibraries(LibraryName library) sync* {
+    final WidgetLibrary? root = _libraries[library];
+    if (root == null) {
+      yield library;
+      return;
+    }
+    if (root is LocalWidgetLibrary) {
+      return;
+    }
+    root as RemoteWidgetLibrary;
+    for (final Import import in root.imports) {
+      yield* _findMissingLibraries(import.name);
+    }
+  }
+
+  /// Resolves argument references ([ArgsReference] objects) in the given
+  /// `node`, and applies [ConstructorCall]s so that all remaining widgets are
+  /// local widgets.
+  _CurriedWidget _applyConstructorAndBindArguments(FullyQualifiedWidgetName fullName, DynamicMap arguments, int stateDepth, Set<FullyQualifiedWidgetName> usedWidgets) {
+    final _ResolvedConstructor? widget = _findConstructor(fullName);
+    if (widget != null) {
+      if (widget.constructor is WidgetDeclaration) {
+        if (usedWidgets.contains(widget.fullName)) {
+          return _CurriedLocalWidget.error(fullName, 'Widget loop: Tried to call ${widget.fullName} constructor reentrantly.');
+        }
+        usedWidgets = usedWidgets.toSet()..add(widget.fullName);
+        final WidgetDeclaration constructor = widget.constructor as WidgetDeclaration;
+        int newDepth;
+        if (constructor.initialState != null) {
+          newDepth = stateDepth + 1;
+        } else {
+          newDepth = stateDepth;
+        }
+        Object result = _bindArguments(widget.fullName, constructor.root, arguments, newDepth, usedWidgets);
+        if (result is Switch) {
+          result = _CurriedSwitch(widget.fullName, result, arguments, constructor.initialState);
+        } else {
+          result as _CurriedWidget;
+          if (constructor.initialState != null) {
+            result = _CurriedRemoteWidget(widget.fullName, result, arguments, constructor.initialState);
+          }
+        }
+        return result as _CurriedWidget;
+      }
+      assert(widget.constructor is LocalWidgetBuilder);
+      return _CurriedLocalWidget(widget.fullName, widget.constructor as LocalWidgetBuilder, arguments);
+    }
+    final Set<LibraryName> missingLibraries = _findMissingLibraries(fullName.library).toSet();
+    if (missingLibraries.isNotEmpty) {
+      return _CurriedLocalWidget.error(
+        fullName,
+        'Could not find remote widget named ${fullName.widget} in ${fullName.library}, '
+        'possibly because some dependencies were missing: ${missingLibraries.join(", ")}',
+      );
+    }
+    return _CurriedLocalWidget.error(fullName, 'Could not find remote widget named ${fullName.widget} in ${fullName.library}.');
+  }
+
+  Object _bindArguments(FullyQualifiedWidgetName context, Object node, Object arguments, int stateDepth, Set<FullyQualifiedWidgetName> usedWidgets) {
+    if (node is ConstructorCall) {
+      final DynamicMap subArguments = _bindArguments(context, node.arguments, arguments, stateDepth, usedWidgets) as DynamicMap;
+      return _applyConstructorAndBindArguments(FullyQualifiedWidgetName(context.library, node.name), subArguments, stateDepth, usedWidgets);
+    }
+    if (node is DynamicMap) {
+      return node.map<String, Object?>(
+        (String name, Object? value) => MapEntry<String, Object?>(name, _bindArguments(context, value!, arguments, stateDepth, usedWidgets)),
+      );
+    }
+    if (node is DynamicList) {
+      return node.map<Object>(
+        (Object? value) => _bindArguments(context, value!, arguments, stateDepth, usedWidgets),
+      ).toList();
+    }
+    if (node is Loop) {
+      final Object input = _bindArguments(context, node.input, arguments, stateDepth, usedWidgets);
+      final Object output = _bindArguments(context, node.output, arguments, stateDepth, usedWidgets);
+      return Loop(input, output);
+    }
+    if (node is Switch) {
+      return Switch(
+        _bindArguments(context, node.input, arguments, stateDepth, usedWidgets),
+        node.outputs.map<Object?, Object>(
+          (Object? key, Object value) {
+            return MapEntry<Object?, Object>(
+              key == null ? key : _bindArguments(context, key, arguments, stateDepth, usedWidgets),
+              _bindArguments(context, value, arguments, stateDepth, usedWidgets),
+            );
+          },
+        ),
+      );
+    }
+    if (node is ArgsReference) {
+      return node.bind(arguments);
+    }
+    if (node is StateReference) {
+      return node.bind(stateDepth);
+    }
+    if (node is EventHandler) {
+      return EventHandler(node.eventName, _bindArguments(context, node.eventArguments, arguments, stateDepth, usedWidgets) as DynamicMap);
+    }
+    if (node is SetStateHandler) {
+      assert(node.stateReference is StateReference);
+      final BoundStateReference stateReference = (node.stateReference as StateReference).bind(stateDepth);
+      return SetStateHandler(stateReference, _bindArguments(context, node.value, arguments, stateDepth, usedWidgets));
+    }
+    assert(node is! WidgetDeclaration);
+    return node;
+  }
+}
+
+// Internal structure to represent the result of indexing into a list.
+//
+// There are two ways this can go: either we index in and find a result, in
+// which case [result] is that value and the other fields are null, or we fail
+// to index into the list and we obtain the length as a side-effect, in which
+// case [result] is null, [rawList] is the raw list (might contain [Loop] objects),
+// and [length] is the effective length after expanding all the internal loops.
+class _ResolvedDynamicList {
+  const _ResolvedDynamicList(this.rawList, this.result, this.length);
+  final DynamicList? rawList;
+  final Object? result; // null means out of range
+  final int? length; // might be null if result is not null
+}
+
+typedef _DataResolverCallback = Object Function(List<Object> dataKey);
+typedef _StateResolverCallback = Object Function(List<Object> stateKey, int depth);
+
+abstract class _CurriedWidget extends BlobNode {
+  const _CurriedWidget(this.fullName, this.arguments, this.initialState);
+
+  final FullyQualifiedWidgetName fullName;
+  final DynamicMap arguments;
+  final DynamicMap? initialState;
+
+  static Object _bindLoopVariable(Object node, Object argument, int depth) {
+    if (node is DynamicMap) {
+      return node.map<String, Object?>(
+        (String name, Object? value) => MapEntry<String, Object?>(name, _bindLoopVariable(value!, argument, depth)),
+      );
+    }
+    if (node is DynamicList) {
+      return node.map<Object>(
+        (Object? value) => _bindLoopVariable(value!, argument, depth),
+      ).toList();
+    }
+    if (node is Loop) {
+      return Loop(_bindLoopVariable(node.input, argument, depth), _bindLoopVariable(node.output, argument, depth + 1));
+    }
+    if (node is Switch) {
+      return Switch(
+        _bindLoopVariable(node.input, argument, depth),
+        node.outputs.map<Object?, Object>(
+          (Object? key, Object value) => MapEntry<Object?, Object>(
+            key == null ? null : _bindLoopVariable(key, argument, depth),
+            _bindLoopVariable(value, argument, depth),
+          ),
+        )
+      );
+    }
+    if (node is _CurriedLocalWidget) {
+      return _CurriedLocalWidget(
+        node.fullName,
+        node.child,
+        _bindLoopVariable(node.arguments, argument, depth) as DynamicMap,
+      );
+    }
+    if (node is _CurriedRemoteWidget) {
+      return _CurriedRemoteWidget(
+        node.fullName,
+        _bindLoopVariable(node.child, argument, depth) as _CurriedWidget,
+        _bindLoopVariable(node.arguments, argument, depth) as DynamicMap,
+        node.initialState,
+      );
+    }
+    if (node is _CurriedSwitch) {
+      return _CurriedSwitch(
+        node.fullName,
+        _bindLoopVariable(node.root, argument, depth) as Switch,
+        _bindLoopVariable(node.arguments, argument, depth) as DynamicMap,
+        node.initialState,
+      );
+    }
+    if (node is LoopReference) {
+      if (node.loop == depth) {
+        return node.bind(argument);
+      }
+      return node;
+    }
+    if (node is BoundArgsReference) {
+      return BoundArgsReference(_bindLoopVariable(node.arguments, argument, depth), node.parts);
+    }
+    if (node is EventHandler) {
+      return EventHandler(node.eventName, _bindLoopVariable(node.eventArguments, argument, depth) as DynamicMap);
+    }
+    if (node is SetStateHandler) {
+      return SetStateHandler(node.stateReference, _bindLoopVariable(node.value, argument, depth));
+    }
+    return node;
+  }
+
+  /// Look up the _index_th entry in `list`, expanding any loops in `list`.
+  ///
+  /// If `index` is -1, this evaluates the entire list to ensure the length is available.
+  //
+  // TODO(ianh): This really should have some sort of caching. Right now, evaluating a whole list
+  // ends up being around O(N^2) since we have to walk the list from the start for every entry.
+  static _ResolvedDynamicList _listLookup(DynamicList list, int targetEffectiveIndex, _StateResolverCallback stateResolver, _DataResolverCallback dataResolver) {
+    if (list.any((Object? entry) => entry is Loop)) {
+      int currentIndex = 0; // where we are in `list` (some entries of which might represent multiple values, because they are themselves loops)
+      int effectiveIndex = 0; // where we are in the fully expanded list (the coordinate space in which we're aiming for `targetEffectiveIndex`)
+      while ((effectiveIndex <= targetEffectiveIndex || targetEffectiveIndex < 0) && currentIndex < list.length) {
+        final Object node = list[currentIndex]!;
+        if (node is Loop) {
+          Object inputList = node.input;
+          while (inputList is! DynamicList) {
+            if (inputList is BoundArgsReference) {
+              inputList = _resolveFrom(inputList.arguments, inputList.parts, stateResolver, dataResolver);
+            } else if (inputList is DataReference) {
+              inputList = dataResolver(inputList.parts);
+            } else if (inputList is BoundStateReference) {
+              inputList = stateResolver(inputList.parts, inputList.depth);
+            } else if (inputList is BoundLoopReference) {
+              inputList = _resolveFrom(inputList.value, inputList.parts, stateResolver, dataResolver);
+            } else if (inputList is Switch) {
+              inputList = _resolveFrom(inputList, const <Object>[], stateResolver, dataResolver);
+            } else {
+              // e.g. it's a map or something else that isn't indexable
+              inputList = DynamicList.empty();
+            }
+            assert(inputList is! _ResolvedDynamicList);
+          }
+          final _ResolvedDynamicList entry = _listLookup(inputList, targetEffectiveIndex >= 0 ? targetEffectiveIndex - effectiveIndex : -1, stateResolver, dataResolver);
+          if (entry.result != null) {
+            final Object boundResult = _bindLoopVariable(node.output, entry.result!, 0);
+            return _ResolvedDynamicList(null, boundResult, null);
+          }
+          effectiveIndex += entry.length!;
+        } else { // list[currentIndex] is not a Loop
+          if (effectiveIndex == targetEffectiveIndex) {
+            return _ResolvedDynamicList(null, list[currentIndex], null);
+          }
+          effectiveIndex += 1;
+        }
+        currentIndex += 1;
+      }
+      return _ResolvedDynamicList(list, null, effectiveIndex);
+    } else {
+      if (targetEffectiveIndex < 0 || targetEffectiveIndex >= list.length) {
+        return _ResolvedDynamicList(list, null, list.length);
+      }
+      return _ResolvedDynamicList(list, list[targetEffectiveIndex]!, list.length);
+    }
+  }
+
+  static Object _resolveFrom(Object root, List<Object> parts, _StateResolverCallback stateResolver, _DataResolverCallback dataResolver) {
+    int index = 0;
+    Object current = root;
+    while (true) {
+      if (current is DataReference) {
+        if (index < parts.length) {
+          current = current.constructReference(parts.sublist(index));
+          index = parts.length;
+        }
+        current = dataResolver(current.parts);
+        continue;
+      } else if (current is BoundArgsReference) {
+        List<Object> nextParts = current.parts;
+        if (index < parts.length) {
+          nextParts += parts.sublist(index);
+        }
+        parts = nextParts;
+        current = current.arguments;
+        index = 0;
+        continue;
+      } else if (current is BoundStateReference) {
+        if (index < parts.length) {
+          current = current.constructReference(parts.sublist(index));
+          index = parts.length;
+        }
+        current = stateResolver(current.parts, current.depth);
+        continue;
+      } else if (current is BoundLoopReference) {
+        List<Object> nextParts = current.parts;
+        if (index < parts.length) {
+          nextParts += parts.sublist(index);
+        }
+        parts = nextParts;
+        current = current.value;
+        index = 0;
+        continue;
+      } else if (current is Switch) {
+        final Object key = _resolveFrom(current.input, const <Object>[], stateResolver, dataResolver);
+        Object? value = current.outputs[key];
+        if (value == null) {
+          value = current.outputs[null];
+          if (value == null) {
+            return missing;
+          }
+        }
+        current = value;
+        continue;
+      } else if (index >= parts.length) {
+        if (current is EventHandler) {
+          current = EventHandler(current.eventName, _fix(current.eventArguments, stateResolver, dataResolver) as DynamicMap);
+        } else if (current is SetStateHandler) {
+          current = SetStateHandler(current.stateReference, _fix(current.value, stateResolver, dataResolver));
+        }
+        break;
+      } else if (current is DynamicMap) {
+        if (parts[index] is! String) {
+          return missing;
+        }
+        if (!current.containsKey(parts[index])) {
+          return missing;
+        }
+        current = current[parts[index]]!;
+      } else if (current is DynamicList) {
+        if (parts[index] is! int) {
+          return missing;
+        }
+        current = _listLookup(current, parts[index] as int, stateResolver, dataResolver).result ?? missing;
+      } else {
+        assert(current is! ArgsReference);
+        assert(current is! StateReference);
+        assert(current is! LoopReference);
+        return missing;
+      }
+      index += 1;
+    }
+    assert(current is! Reference, 'Unexpected unbound reference (of type ${current.runtimeType}): $current');
+    assert(current is! Switch);
+    assert(current is! Loop);
+    return current;
+  }
+
+  static Object _fix(Object root, _StateResolverCallback stateResolver, _DataResolverCallback dataResolver) {
+    if (root is DynamicMap) {
+      return root.map((String key, Object? value) => MapEntry<String, Object?>(key, _fix(root[key]!, stateResolver, dataResolver)));
+    } else if (root is DynamicList) {
+      if (root.any((Object? entry) => entry is Loop)) {
+        final int length = _listLookup(root, -1, stateResolver, dataResolver).length!;
+        return DynamicList.generate(length, (int index) => _fix(_listLookup(root, index, stateResolver, dataResolver).result!, stateResolver, dataResolver));
+      } else {
+        return DynamicList.generate(root.length, (int index) => _fix(root[index]!, stateResolver, dataResolver));
+      }
+    } else if (root is BlobNode) {
+      return _resolveFrom(root, const <Object>[], stateResolver, dataResolver);
+    } else {
+      return root;
+    }
+  }
+
+  Object resolve(List<Object> parts, _StateResolverCallback stateResolver, _DataResolverCallback dataResolver, { required bool expandLists }) {
+    Object result = _resolveFrom(arguments, parts, stateResolver, dataResolver);
+    if (result is DynamicList && expandLists) {
+      result = _listLookup(result, -1, stateResolver, dataResolver);
+    }
+    assert(result is! Reference);
+    assert(result is! Switch);
+    assert(result is! Loop);
+    return result;
+  }
+
+  Widget build(BuildContext context, DynamicContent data, RemoteEventHandler remoteEventTarget, List<_WidgetState> states) {
+    return _Widget(curriedWidget: this, data: data, remoteEventTarget: remoteEventTarget, states: states);
+  }
+
+  Widget buildChild(BuildContext context, DataSource source, DynamicContent data, RemoteEventHandler remoteEventTarget, List<_WidgetState> states, _StateResolverCallback stateResolver, _DataResolverCallback dataResolver);
+
+  @override
+  String toString() => '$fullName ${initialState ?? "{}"} $arguments';
+}
+
+class _CurriedLocalWidget extends _CurriedWidget {
+  const _CurriedLocalWidget(FullyQualifiedWidgetName fullName, this.child, DynamicMap arguments) : super(fullName, arguments, null);
+
+  factory _CurriedLocalWidget.error(FullyQualifiedWidgetName fullName, String message) {
+    return _CurriedLocalWidget(fullName, (BuildContext context, DataSource data) => _buildErrorWidget(message), const <String, Object?>{});
+  }
+
+  final LocalWidgetBuilder child;
+
+  @override
+  Widget buildChild(BuildContext context, DataSource source, DynamicContent data, RemoteEventHandler remoteEventTarget, List<_WidgetState> states,  _StateResolverCallback stateResolver, _DataResolverCallback dataResolver) {
+    return child(context, source);
+  }
+}
+
+class _CurriedRemoteWidget extends _CurriedWidget {
+  const _CurriedRemoteWidget(FullyQualifiedWidgetName fullName, this.child, DynamicMap arguments, DynamicMap? initialState) : super(fullName, arguments, initialState);
+
+  final _CurriedWidget child;
+
+  @override
+  Widget buildChild(BuildContext context, DataSource source, DynamicContent data, RemoteEventHandler remoteEventTarget, List<_WidgetState> states,  _StateResolverCallback stateResolver, _DataResolverCallback dataResolver) {
+    return child.build(context, data, remoteEventTarget, states);
+  }
+
+  @override
+  String toString() => '${super.toString()} = $child';
+}
+
+class _CurriedSwitch extends _CurriedWidget {
+  const _CurriedSwitch(FullyQualifiedWidgetName fullName, this.root, DynamicMap arguments, DynamicMap? initialState) : super(fullName, arguments, initialState);
+
+  final Switch root;
+
+  @override
+  Widget buildChild(BuildContext context, DataSource source, DynamicContent data, RemoteEventHandler remoteEventTarget, List<_WidgetState> states,  _StateResolverCallback stateResolver, _DataResolverCallback dataResolver) {
+    Object result = _CurriedWidget._resolveFrom(root, const <Object>[], stateResolver, dataResolver);
+    if (result is _CurriedWidget) {
+      result = result.build(context, data, remoteEventTarget, states);
+      return result as Widget;
+    }
+    return _buildErrorWidget('Switch in $fullName did not resolve to a widget (got $result).');
+  }
+
+  @override
+  String toString() => '${super.toString()} = $root';
+}
+
+class _Widget extends StatefulWidget {
+  const _Widget({ Key? key, required this.curriedWidget, required this.data, required this.remoteEventTarget, required this.states }) : super(key: key);
+
+  final _CurriedWidget curriedWidget;
+
+  final DynamicContent data;
+
+  final RemoteEventHandler remoteEventTarget;
+
+  final List<_WidgetState> states;
+
+  @override
+  State<_Widget> createState() => _WidgetState();
+}
+
+class _WidgetState extends State<_Widget> implements DataSource {
+  DynamicContent? _state;
+  DynamicMap? _stateStore;
+  late List<_WidgetState> _states;
+
+  @override
+  void initState() {
+    super.initState();
+    _updateState();
+  }
+
+  @override
+  void didUpdateWidget(_Widget oldWidget) {
+    super.didUpdateWidget(oldWidget);
+    if (oldWidget.curriedWidget != widget.curriedWidget) {
+      _updateState();
+    }
+    if (oldWidget.data != widget.data || oldWidget.curriedWidget != widget.curriedWidget || oldWidget.states != widget.states) {
+      _unsubscribe();
+    }
+  }
+
+  @override
+  void dispose() {
+    _unsubscribe();
+    super.dispose();
+  }
+
+  void _updateState() {
+    _stateStore = deepClone(widget.curriedWidget.initialState) as DynamicMap?;
+    if (_stateStore != null) {
+      _state ??= DynamicContent();
+      _state!.updateAll(_stateStore!);
+    } else {
+      _state = null;
+    }
+    _states = widget.states;
+    if (_state != null) {
+      _states = _states.toList()..add(this);
+    }
+  }
+
+  void _handleSetState(int depth, List<Object> parts, Object value) {
+    _states[depth].applySetState(parts, value);
+  }
+
+  void applySetState(List<Object> parts, Object value) {
+    assert(parts.isNotEmpty);
+    assert(_stateStore != null);
+    int index = 0;
+    Object current = _stateStore!;
+    while (index < parts.length) {
+      final Object subindex = parts[index];
+      if (current is DynamicMap) {
+        if (subindex is! String) {
+          throw RemoteFlutterWidgetsException('${parts.join(".")} does not identify existing state.');
+        }
+        if (!current.containsKey(subindex)) {
+          throw RemoteFlutterWidgetsException('${parts.join(".")} does not identify existing state.');
+        }
+        if (index == parts.length - 1) {
+          current[subindex] = value;
+        } else {
+          current = current[parts[index]]!;
+        }
+      } else if (current is DynamicList) {
+        if (subindex is! int) {
+          throw RemoteFlutterWidgetsException('${parts.join(".")} does not identify existing state.');
+        }
+        if (subindex < 0 || subindex >= current.length) {
+          throw RemoteFlutterWidgetsException('${parts.join(".")} does not identify existing state.');
+        }
+        if (index == parts.length - 1) {
+          current[subindex] = value;
+        } else {
+          current = current[subindex]!;
+        }
+      } else {
+        throw RemoteFlutterWidgetsException('${parts.join(".")} does not identify existing state.');
+      }
+      index += 1;
+    }
+    _state!.updateAll(_stateStore!);
+  }
+
+  // List of subscriptions into [widget.data].
+  //
+  // Keys are into the [DynamicContent] object.
+  final Map<_Key, _Subscription> _subscriptions = <_Key, _Subscription>{};
+
+  void _unsubscribe() {
+    for (final _Subscription value in _subscriptions.values) {
+      value.dispose();
+    }
+    _subscriptions.clear();
+    _argsCache.clear();
+  }
+
+  @override
+  T? v<T>(List<Object> argsKey) {
+    assert(T == Object || T == int || T == double || T == bool || T == String);
+    final Object value = _fetch(argsKey, expandLists: false);
+    return value is T ? value as T : null;
+  }
+
+  @override
+  bool isList(List<Object> argsKey) {
+    final Object value = _fetch(argsKey, expandLists: false);
+    return value is _ResolvedDynamicList
+        || value is DynamicList;
+  }
+
+  @override
+  int length(List<Object> argsKey) {
+    final Object value = _fetch(argsKey, expandLists: true);
+    if (value is _ResolvedDynamicList) {
+      if (value.rawList != null) {
+        assert(value.length != null);
+        return value.length!;
+      }
+    }
+    assert(value is! DynamicList);
+    return 0;
+  }
+
+  @override
+  bool isMap(List<Object> argsKey) {
+    final Object value = _fetch(argsKey, expandLists: false);
+    return value is DynamicMap;
+  }
+
+  @override
+  Widget child(List<Object> argsKey) {
+    final Object value = _fetch(argsKey, expandLists: false);
+    if (value is _CurriedWidget) {
+      return value.build(context, widget.data, widget.remoteEventTarget, widget.states);
+    }
+    return _buildErrorWidget('Not a widget at $argsKey (got $value) for ${widget.curriedWidget.fullName}.');
+  }
+
+  @override
+  Widget? optionalChild(List<Object> argsKey) {
+    final Object value = _fetch(argsKey, expandLists: false);
+    if (value is _CurriedWidget) {
+      return value.build(context, widget.data, widget.remoteEventTarget, widget.states);
+    }
+    return null;
+  }
+
+  @override
+  List<Widget> childList(List<Object> argsKey) {
+    final Object value = _fetch(argsKey, expandLists: true);
+    if (value is _ResolvedDynamicList) {
+      assert(value.length != null);
+      final DynamicList fullList = _fetchList(argsKey, value.length!);
+      return fullList.map<Widget>((Object? node) {
+        if (node is _CurriedWidget) {
+          return node.build(context, widget.data, widget.remoteEventTarget, _states);
+        }
+        return _buildErrorWidget('Not a widget at $argsKey (got $node) for ${widget.curriedWidget.fullName}.');
+      }).toList();
+    }
+    if (value == missing) {
+      return const <Widget>[];
+    }
+    return <Widget>[
+      _buildErrorWidget('Not a widget list at $argsKey (got $value) for ${widget.curriedWidget.fullName}.'),
+    ];
+  }
+
+  @override
+  VoidCallback? voidHandler(List<Object> argsKey, [ DynamicMap? extraArguments ]) {
+    return handler<VoidCallback>(argsKey, (HandlerTrigger callback) => () => callback(extraArguments));
+  }
+
+  @override
+  T? handler<T extends Function>(List<Object> argsKey, HandlerGenerator<T> generator) {
+    Object value = _fetch(argsKey, expandLists: true);
+    if (value is AnyEventHandler) {
+      value = <Object>[ value ];
+    } else if (value is _ResolvedDynamicList) {
+      value = _fetchList(argsKey, value.length!);
+    }
+    if (value is DynamicList) {
+      final List<AnyEventHandler> handlers = value.whereType<AnyEventHandler>().toList();
+      if (handlers.isNotEmpty) {
+        return generator(([DynamicMap? extraArguments]) {
+          for (final AnyEventHandler entry in handlers) {
+            if (entry is EventHandler) {
+              DynamicMap arguments = entry.eventArguments;
+              if (extraArguments != null) {
+                arguments = DynamicMap.fromEntries(arguments.entries.followedBy(extraArguments.entries));
+              }
+              widget.remoteEventTarget(entry.eventName, arguments);
+            } else if (entry is SetStateHandler) {
+              assert(entry.stateReference is BoundStateReference);
+              _handleSetState((entry.stateReference as BoundStateReference).depth, entry.stateReference.parts, entry.value);
+            }
+          }
+        });
+      }
+    }
+    return null;
+  }
+
+  // null values means the data is not in the cache
+  final Map<_Key, Object?> _argsCache = <_Key, Object?>{};
+
+  bool _debugFetching = false;
+  final List<_Subscription> _dependencies = <_Subscription>[];
+
+  Object _fetch(List<Object> argsKey, { required bool expandLists }) {
+    final _Key key = _Key(_kArgsSection, argsKey);
+    final Object? value = _argsCache[key];
+    if (value != null && (value is! DynamicList || !expandLists)) {
+      return value;
+    }
+    assert(!_debugFetching);
+    try {
+      _debugFetching = true;
+      final Object result = widget.curriedWidget.resolve(argsKey, _stateResolver, _dataResolver, expandLists: expandLists);
+      for (final _Subscription subscription in _dependencies) {
+        subscription.addClient(key);
+      }
+      _argsCache[key] = result;
+      return result;
+    } finally {
+      _dependencies.clear();
+      _debugFetching = false;
+    }
+  }
+
+  DynamicList _fetchList(List<Object> argsKey, int length) {
+    return DynamicList.generate(length, (int index) {
+      return _fetch(<Object>[...argsKey, index], expandLists: false);
+    });
+  }
+
+  Object _dataResolver(List<Object> rawDataKey) {
+    final _Key dataKey = _Key(_kDataSection, rawDataKey);
+    final _Subscription subscription;
+    if (!_subscriptions.containsKey(dataKey)) {
+      subscription = _Subscription(widget.data, this, rawDataKey);
+      _subscriptions[dataKey] = subscription;
+    } else {
+      subscription = _subscriptions[dataKey]!;
+    }
+    _dependencies.add(subscription);
+    return subscription.value;
+  }
+
+  Object _stateResolver(List<Object> rawStateKey, int depth) {
+    final _Key stateKey = _Key(depth, rawStateKey);
+    final _Subscription subscription;
+    if (!_subscriptions.containsKey(stateKey)) {
+      if (depth >= _states.length) {
+        throw const RemoteFlutterWidgetsException('Reference to state value did not correspond to any stateful remote widget.');
+      }
+      final DynamicContent? state = _states[depth]._state;
+      if (state == null) {
+        return missing;
+      }
+      subscription = _Subscription(state, this, rawStateKey);
+      _subscriptions[stateKey] = subscription;
+    } else {
+      subscription = _subscriptions[stateKey]!;
+    }
+    _dependencies.add(subscription);
+    return subscription.value;
+  }
+
+  void updateData(Set<_Key> affectedArgs) {
+    setState(() {
+      for (final _Key key in affectedArgs) {
+        _argsCache[key] = null;
+      }
+    });
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    // TODO(ianh): what if this creates some _dependencies?
+    return widget.curriedWidget.buildChild(context, this, widget.data, widget.remoteEventTarget, _states, _stateResolver, _dataResolver);
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(StringProperty('name', '${widget.curriedWidget.fullName}'));
+  }
+}
+
+const int _kDataSection = -1;
+const int _kArgsSection = -2;
+
+@immutable
+class _Key {
+  _Key(this.section, this.parts) : assert(_isValidKey(parts), '$parts is not a valid key');
+
+  static bool _isValidKey(List<Object> parts) {
+    return parts.every((Object segment) => segment is int || segment is String);
+  }
+
+  final int section;
+  final List<Object> parts;
+
+  @override
+  bool operator ==(Object other) {
+    return other is _Key // _Key has no subclasses, don't need to check runtimeType
+        && section == other.section
+        && listEquals(parts, other.parts);
+  }
+
+  @override
+  int get hashCode => hashValues(section, hashList(parts));
+}
+
+class _Subscription {
+  _Subscription(this._data, this._state, this._dataKey) {
+    _update(_data.subscribe(_dataKey, _update));
+  }
+
+  final DynamicContent _data;
+  final _WidgetState _state;
+  final List<Object> _dataKey;
+  final Set<_Key> _clients = <_Key>{};
+
+  Object get value => _value;
+  late Object _value;
+
+  void _update(Object value) {
+    _state.updateData(_clients);
+    _value = value;
+  }
+
+  void addClient(_Key key) {
+    _clients.add(key);
+  }
+
+  void dispose() {
+    _data.unsubscribe(_dataKey, _update);
+  }
+}
+
+ErrorWidget _buildErrorWidget(String message) {
+  FlutterError.reportError(FlutterErrorDetails(
+    exception: message,
+    stack: StackTrace.current,
+    library: 'Remote Flutter Widgets',
+  ));
+  return ErrorWidget(message);
+}
diff --git a/packages/rfw/lib/formats.dart b/packages/rfw/lib/formats.dart
new file mode 100644
index 0000000..ae327e3
--- /dev/null
+++ b/packages/rfw/lib/formats.dart
@@ -0,0 +1,32 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/// # Remote Flutter Widgets - formats only import
+///
+/// This is a subset of the [rfw] library that only exposes features that
+/// do not depend on Flutter.
+///
+/// Specifically, the following APIs are exposed by this library:
+///
+///  * [parseLibraryFile] and [parseDataFile], for parsing Remote Flutter
+///    Widgets text library and data files respectively. (These are not exposed
+///    by the [rfw] library since they are not intended for use in client-side
+///    code.)
+///
+///  * [encodeLibraryBlob] and [encodeDataBlob], for encoding the output of the
+///    previous methods into binary form.
+///
+///  * [decodeLibraryBlob] and [decodeDataBlob], which decode those binary
+///    forms.
+///
+///  * The [DynamicMap], [DynamicList], and [BlobNode] types (and subclasses),
+///    which are used to represent the data model and remote widget libraries in
+///    memory.
+///
+/// For client-side code, import `package:rfw/rfw.dart` instead.
+library formats;
+
+export 'dart/binary.dart';
+export 'dart/model.dart';
+export 'dart/text.dart';
diff --git a/packages/rfw/lib/rfw.dart b/packages/rfw/lib/rfw.dart
new file mode 100644
index 0000000..23cfab7
--- /dev/null
+++ b/packages/rfw/lib/rfw.dart
@@ -0,0 +1,58 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+/// # Remote Flutter Widgets
+///
+/// Renders a widget tree described in a file, which you can change at runtime.
+///
+/// There are many ways to use a package such as this one. In general, the
+/// approach looks something like this:
+///
+/// ![The Remote Flutter Widgets comes from the server over the network and into the Runtime. The Runtime also receives the Data model, which is populated both from Client data and from Server data obtained over the network. The Runtime creates Flutter Widgets, which send state updates back to the Runtime, and send user input to the Client logic, which either directly changes the Client data, or sends messages over the network to the Server logic, which then updates the Server data.](https://raw.githubusercontent.com/flutter/packages/master/packages/rfw/images/overview1.png)
+///
+/// The network aspects of this design are out of scope for this package. Remote
+/// widget libraries and data should be cached locally, to avoid network issues
+/// causing interface failures.
+///
+/// In the extreme, this package can be combined with a local scripting runtime
+/// (e.g. [https://pub.dev/packages/hetu_script](hetu_script)) to run
+/// remotely-fetched logic locally:
+///
+/// ![The Remote Flutter Widgets once again come from the server and follow the same path via the network to the Runtime. The Runtime combines this with the Data model to generate the Flutter Widgets, which send state updates directly back to the runtime and user input to the Hard-coded client logic. That logic updates the Client data which updates the Data model, but also sends messages to the Scripting engine which is also on the Client. The Scripting engine is configured from Scripts obtained over the network, and generates Script data that also populates the Data model.](https://raw.githubusercontent.com/flutter/packages/master/packages/rfw/images/overview2.png)
+///
+///
+/// ## Using the [RemoteWidget] widget
+///
+/// To render a remote widget, the [RemoteWidget] widget can be used. It takes a
+/// [DynamicContent] instance and a [Runtime] instance, which must be configured
+/// ahead of time.
+///
+/// ## Best practices for handling remote data
+///
+/// The most efficient way to parse remote data is [decodeDataBlob] and the most
+/// efficient way to parse remote widget libraries is [decodeLibraryBlob].
+///
+/// The methods for parsing the text format are not exported by
+/// `package:rfw/rfw.dart` to discourage their use in client-side code.
+///
+/// ## Server-side dart
+///
+/// This package can be used in non-Flutter environments by importing
+/// `package:rfw/formats.dart` rather than `package:rfw/rfw.dart`. In the
+/// `formats` mode, the [Runtime] and [DynamicContent] objects, as well as the
+/// [RemoteWidget] widget, are not available, but the [parseDataFile] and
+/// [parseLibraryFile] methods are. They can be used in conjunction with
+/// [encodeDataBlob] and [encodeLibraryBlob] (respectively) to generate the
+/// binary files used by client-side code.
+library rfw;
+
+export 'dart/binary.dart';
+export 'dart/model.dart';
+export 'dart/text.dart' hide parseDataFile, parseLibraryFile;
+export 'flutter/argument_decoders.dart';
+export 'flutter/content.dart';
+export 'flutter/core_widgets.dart';
+export 'flutter/material_widgets.dart';
+export 'flutter/remote_widget.dart';
+export 'flutter/runtime.dart';
diff --git a/packages/rfw/pubspec.yaml b/packages/rfw/pubspec.yaml
new file mode 100644
index 0000000..37dfdbf
--- /dev/null
+++ b/packages/rfw/pubspec.yaml
@@ -0,0 +1,18 @@
+name: rfw
+description: Remote Flutter widgets
+repository: https://github.com/flutter/packages/tree/master/packages/rfw
+issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+rfw%22
+version: 1.0.0
+
+environment:
+  sdk: ">=2.13.0 <3.0.0"
+  flutter: ">=1.17.0"
+
+dependencies:
+  flutter:
+    sdk: flutter
+  meta: ^1.7.0
+
+dev_dependencies:
+  flutter_test:
+    sdk: flutter
diff --git a/packages/rfw/run_tests.sh b/packages/rfw/run_tests.sh
new file mode 100755
index 0000000..abf2398
--- /dev/null
+++ b/packages/rfw/run_tests.sh
@@ -0,0 +1,53 @@
+#!/bin/bash
+# Copyright 2013 The Flutter Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+# Please update these targets when you update this package.
+# Please ensure that test coverage continues to be 100%.
+
+TARGET_LINES=2125
+TARGET_PERCENT=100
+LAST_UPDATE="2021-08-30"
+
+# ----------------------------------------------------------------------
+
+# This script is mentioned in the README.md file.
+
+set -e
+
+if [ "$CHANNEL" == "stable" ]; then
+    # For now these are disabled because this package has never been supported
+    # on the stable channel and requires newer language features that have not
+    # yet shipped to a stable build. -Hixie, 2021-08-30
+    echo "Skipping tests on stable channel."
+    exit 0
+fi
+
+rm -rf coverage
+# We run with --update-goldens because the goal here is not to verify the tests
+# pass but to verify the coverage, and the goldens are not always going to pass
+# when run on different platforms (e.g. on Cirrus we run this on a mac but the
+# goldens expect a linux box).
+flutter test --coverage --update-goldens
+ACTUAL=`lcov -l coverage/lcov.info | tail -1 | cut -d '|' -f 2 | cut -d '%' -f 2`
+# We only check the TARGET_LINES matches, not the TARGET_PERCENT,
+# because we expect the percentage to drop over time as Dart fixes
+# various bugs in how it determines what lines are coverable.
+if [ $ACTUAL -lt $TARGET_LINES ]; then
+    echo
+    echo "                      ╭──────────────────────────────╮"
+    echo "                      │ COVERAGE REGRESSION DETECTED │"
+    echo "                      ╰──────────────────────────────╯"
+    echo
+    lcov --quiet --list coverage/lcov.info
+    echo
+    echo "Coverage has reduced to only" $ACTUAL "lines. This is lower than it was"
+    echo "as of $LAST_UPDATE, when coverage was $TARGET_PERCENT%, covering $TARGET_LINES lines."
+    echo "Please add sufficient tests to get coverage back to 100%, and update"
+    echo "run_tests.sh to have the appropriate targets."
+    echo
+    echo "When in doubt, ask @Hixie for advice. Thanks!"
+    exit 1
+fi
+rm -rf coverage
diff --git a/packages/rfw/test/argument_decoders_test.dart b/packages/rfw/test/argument_decoders_test.dart
new file mode 100644
index 0000000..dfe0abb
--- /dev/null
+++ b/packages/rfw/test/argument_decoders_test.dart
@@ -0,0 +1,498 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This file is hand-formatted.
+
+import 'package:flutter/material.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:rfw/formats.dart' show parseLibraryFile;
+import 'package:rfw/rfw.dart';
+
+void main() {
+  testWidgets('String example', (WidgetTester tester) async {
+    Duration? duration;
+    Curve? curve;
+    int buildCount = 0;
+    final Widget builder = Builder(
+      builder: (BuildContext context) {
+        buildCount += 1;
+        duration = AnimationDefaults.durationOf(context);
+        curve = AnimationDefaults.curveOf(context);
+        return const SizedBox.shrink();
+      },
+    );
+    await tester.pumpWidget(
+      AnimationDefaults(
+        duration: const Duration(milliseconds: 500),
+        curve: Curves.easeIn,
+        child: builder,
+      ),
+    );
+    expect(duration, const Duration(milliseconds: 500));
+    expect(curve, Curves.easeIn);
+    expect(buildCount, 1);
+    await tester.pumpWidget(
+      AnimationDefaults(
+        duration: const Duration(milliseconds: 500),
+        curve: Curves.easeIn,
+        child: builder,
+      ),
+    );
+    expect(buildCount, 1);
+    await tester.pumpWidget(
+      AnimationDefaults(
+        duration: const Duration(milliseconds: 501),
+        curve: Curves.easeIn,
+        child: builder,
+      ),
+    );
+    expect(buildCount, 2);
+  });
+
+  testWidgets('spot checks', (WidgetTester tester) async {
+    Duration? duration;
+    Curve? curve;
+    int buildCount = 0;
+    final Runtime runtime = Runtime()
+      ..update(const LibraryName(<String>['core']), createCoreWidgets())
+      ..update(const LibraryName(<String>['builder']), LocalWidgetLibrary(<String, LocalWidgetBuilder>{
+        'Test': (BuildContext context, DataSource source) {
+          buildCount += 1;
+          duration = AnimationDefaults.durationOf(context);
+          curve = AnimationDefaults.curveOf(context);
+          return const SizedBox.shrink();
+        },
+      }))
+      ..update(const LibraryName(<String>['test']), parseLibraryFile('import core; widget root = SizedBox();'));
+    final DynamicContent data = DynamicContent();
+    final List<String> eventLog = <String>[];
+    await tester.pumpWidget(
+      RemoteWidget(
+        runtime: runtime,
+        data: data,
+        widget: const FullyQualifiedWidgetName(LibraryName(<String>['test']), 'root'),
+        onEvent: (String eventName, DynamicMap eventArguments) {
+          eventLog.add(eventName);
+          expect(eventArguments, const <String, Object?>{ 'argument': true });
+        },
+      ),
+    );
+    expect(find.byType(SizedBox), findsOneWidget);
+
+    runtime.update(const LibraryName(<String>['test']), parseLibraryFile('''
+      import core;
+      widget root = Align(alignment: { x: 0.25, y: 0.75 });
+    '''));
+    await tester.pump();
+    expect(tester.widget<Align>(find.byType(Align)).alignment, const Alignment(0.25, 0.75));
+
+    runtime.update(const LibraryName(<String>['test']), parseLibraryFile('''
+      import core;
+      widget root = Align(alignment: { start: 0.25, y: 0.75 });
+    '''));
+    await tester.pump();
+    expect(tester.widget<Align>(find.byType(Align)).alignment, const Alignment(0.25, 0.75));
+
+    runtime.update(const LibraryName(<String>['test']), parseLibraryFile('''
+      import core;
+      import builder;
+      widget root = AnimationDefaults(curve: "easeOut", duration: 5000, child: Test());
+    '''));
+    await tester.pump();
+    expect(buildCount, 1);
+    expect(duration, const Duration(seconds: 5));
+    expect(curve, Curves.easeOut);
+
+    ArgumentDecoders.curveDecoders['saw3'] = (DataSource source, List<Object> key) => const SawTooth(3);
+    runtime.update(const LibraryName(<String>['test']), parseLibraryFile('''
+      import core;
+      import builder;
+      widget root = AnimationDefaults(curve: "saw3", child: Test());
+    '''));
+    await tester.pump();
+    expect(curve, isA<SawTooth>());
+
+    runtime.update(const LibraryName(<String>['test']), parseLibraryFile('''
+      import core;
+      widget root = AspectRatio(aspectRatio: 0.5);
+    '''));
+    await tester.pump();
+    expect(tester.widget<AspectRatio>(find.byType(AspectRatio)).aspectRatio, 0.5);
+
+    runtime.update(const LibraryName(<String>['test']), parseLibraryFile('''
+      import core;
+      widget root = Center(widthFactor: 0.25);
+    '''));
+    await tester.pump();
+    expect(tester.widget<Center>(find.byType(Center)).widthFactor, 0.25);
+    expect(tester.widget<Center>(find.byType(Center)).heightFactor, null);
+
+    runtime.update(const LibraryName(<String>['test']), parseLibraryFile('''
+      import core;
+      widget root = ColoredBox(color: 0xFF112233);
+    '''));
+    await tester.pump();
+    expect(tester.widget<ColoredBox>(find.byType(ColoredBox)).color, const Color(0xFF112233));
+
+    runtime.update(const LibraryName(<String>['test']), parseLibraryFile('''
+      import core;
+      widget root = Column(
+        mainAxisAlignment: "center",
+        children: [ ColoredBox(color: 1), ColoredBox(color: 2) ],
+      );
+    '''));
+    await tester.pump();
+    expect(tester.widget<Column>(find.byType(Column)).mainAxisAlignment, MainAxisAlignment.center);
+    expect(tester.widget<Column>(find.byType(Column)).crossAxisAlignment, CrossAxisAlignment.center);
+    expect(tester.widget<Column>(find.byType(Column)).verticalDirection, VerticalDirection.down);
+    expect(tester.widget<Column>(find.byType(Column)).children, hasLength(2));
+    expect(tester.widgetList<ColoredBox>(find.byType(ColoredBox)).toList()[0].color, const Color(0x00000001));
+    expect(tester.widgetList<ColoredBox>(find.byType(ColoredBox)).toList()[1].color, const Color(0x00000002));
+
+    runtime.update(const LibraryName(<String>['test']), parseLibraryFile('''
+      import core;
+      widget root = ColoredBox(color: 0xFF112233);
+    '''));
+    await tester.pump();
+    expect(tester.widget<ColoredBox>(find.byType(ColoredBox)).color, const Color(0xFF112233));
+
+    runtime.update(const LibraryName(<String>['test']), parseLibraryFile('''
+      import core;
+      widget root = DefaultTextStyle(
+        textHeightBehavior: { applyHeightToLastDescent: false },
+        child: SizedBoxShrink(),
+      );
+    '''));
+    await tester.pump();
+    expect(
+      tester.widget<DefaultTextStyle>(find.byType(DefaultTextStyle)).textHeightBehavior,
+      const TextHeightBehavior(applyHeightToLastDescent: false),
+    );
+
+    runtime.update(const LibraryName(<String>['test']), parseLibraryFile('''
+      import core;
+      widget root = Directionality(
+        textDirection: "ltr",
+        child: SizedBoxShrink(),
+      );
+    '''));
+    await tester.pump();
+    expect(tester.widget<Directionality>(find.byType(Directionality)).textDirection, TextDirection.ltr);
+
+    runtime.update(const LibraryName(<String>['test']), parseLibraryFile('''
+      import core;
+      widget root = FittedBox(
+        fit: "cover",
+      );
+    '''));
+    await tester.pump();
+    expect(tester.widget<FittedBox>(find.byType(FittedBox)).fit, BoxFit.cover);
+
+    runtime.update(const LibraryName(<String>['test']), parseLibraryFile('''
+      import core;
+      widget root = GestureDetector(
+        onTap: event 'tap' { argument: true },
+        child: ColoredBox(),
+      );
+    '''));
+    await tester.pump();
+    await tester.tap(find.byType(ColoredBox));
+    expect(eventLog, <String>['tap']);
+    eventLog.clear();
+
+    runtime.update(const LibraryName(<String>['test']), parseLibraryFile('''
+      import core;
+      widget root = Directionality(
+        textDirection: "ltr",
+        child: Icon(
+          icon: 0x0001,
+          fontFamily: 'FONT',
+        ),
+      );
+    '''));
+    await tester.pump();
+    expect(tester.widget<Icon>(find.byType(Icon)).icon!.codePoint, 1);
+    expect(tester.widget<Icon>(find.byType(Icon)).icon!.fontFamily, 'FONT');
+
+    runtime.update(const LibraryName(<String>['test']), parseLibraryFile('''
+      import core;
+      widget root = IconTheme(
+        color: 0x12345678,
+        child: SizedBoxShrink(),
+      );
+    '''));
+    await tester.pump();
+    expect(tester.widget<IconTheme>(find.byType(IconTheme)).data.color, const Color(0x12345678));
+  });
+
+  testWidgets('golden checks', (WidgetTester tester) async {
+    final Runtime runtime = Runtime()
+      ..update(const LibraryName(<String>['core']), createCoreWidgets())
+      ..update(const LibraryName(<String>['test']), parseLibraryFile('import core; widget root = SizedBox();'));
+    final DynamicContent data = DynamicContent();
+    await tester.pumpWidget(
+      Directionality(
+        textDirection: TextDirection.rtl,
+        child: RemoteWidget(
+          runtime: runtime,
+          data: data,
+          widget: const FullyQualifiedWidgetName(LibraryName(<String>['test']), 'root'),
+        ),
+      ),
+    );
+    expect(find.byType(RemoteWidget), findsOneWidget);
+
+    ArgumentDecoders.decorationDecoders['tab'] = (DataSource source, List<Object> key) {
+      return UnderlineTabIndicator(
+        borderSide: ArgumentDecoders.borderSide(source, <Object>[...key, 'side']) ?? const BorderSide(width: 2.0, color: Color(0xFFFFFFFF)),
+        insets: ArgumentDecoders.edgeInsets(source, <Object>['insets']) ?? EdgeInsets.zero,
+      );
+    };
+
+    runtime.update(const LibraryName(<String>['test']), parseLibraryFile('''
+      import core;
+      widget root = Container(
+        margin: [20.0, 10.0, 30.0, 5.0],
+        padding: [10.0],
+        decoration: {
+          type: 'box',
+          borderRadius: [ { x: 120.0 }, { x: 130.0, y: 40.0 } ],
+          image: {
+            // this image doesn't exist so nothing much happens here
+            // we check the results of this parse in a separate expect
+            source: 'asset',
+            color: 0xFF00BBCC,
+            centerSlice: { x: 5.0, y: 8.0, w: 100.0, h: 70.0 },
+            colorFilter: {
+              type: 'matrix', matrix: [
+                1.0, 1.0, 1.0, 1.0, 1.0,
+                1.0, 1.0, 1.0, 1.0, 1.0,
+                1.0, 1.0, 1.0, 1.0, 1.0,
+                1.0, 1.0, 1.0, 1.0, 1.0,
+              ],
+            },
+          },
+          gradient: {
+            type: 'sweep',
+          },
+        },
+        foregroundDecoration: {
+          type: 'box',
+          border: [ { width: 10.0, color: 0xFFFFFF00 }, { width: 3.0, color: 0xFF00FFFF } ],
+          boxShadow: [ { offset: { x: 25.0, y: 25.0 }, color: 0x5F000000, } ],
+          image: {
+            // this image also doesn't exist
+            // we check the results of this parse in a separate expect
+            source: 'x-invalid://',
+            colorFilter: {
+              type: 'mode',
+              color: 0xFF8811FF,
+              blendMode: "xor",
+            },
+          },
+          gradient: {
+            type: 'linear',
+            colors: [ 0x1F009900, 0x1F33CC33, 0x7F777700 ],
+            stops: [ 0.0, 0.75, 1.0 ],
+          },
+        },
+        alignment: { x: 0.0, y: -0.5, },
+        transform: [
+          0.9, 0.2, 0.1, 0.0,
+          -0.1, 1.1, 0.0, 0.0,
+          0.0, 0.0, 1.0, 0.0,
+          50.0, -20.0, 0.0, 1.0,
+        ],
+        child: Container(
+          constraints: { maxWidth: 400.0, maxHeight: 350.0 },
+          margin: [5.0, 25.0, 10.0, 20.0],
+          decoration: {
+            type: 'box',
+            color: 0xFF9911CC,
+            gradient: { type: 'custom' },
+          },
+          foregroundDecoration: {
+            type: 'flutterLogo',
+            margin: [ 100.0 ],
+          },
+          child: Container(
+            margin: [5.0],
+            decoration: {
+              type: 'tab',
+              side: { width: 20.0, color: 0xFFFFFFFF },
+            },
+            foregroundDecoration: {
+              type: 'shape',
+              shape: [
+                { type: 'box', border: { width: 10.0, color: 0xFF0000FF } },
+                { type: 'beveled', borderRadius: [ { x: 60.0 } ], side: { width: 10.0, color: 0xFF0033FF } },
+                { type: 'circle', side: { width: 10.0, color: 0xFF0066FF } },
+                { type: 'continuous', borderRadius: [ { x: 60.0 }, { x: 80.0 }, { x: 0.0 }, { x: 20.0, y: 50.0 } ], side: { width: 10.0, color: 0xFFEEFF33 } },
+                { type: 'rounded', borderRadius: [ { x: 20.0 } ], side: { width: 10.0, color: 0xFF00CCFF } },
+                { type: 'stadium', side: { width: 10.0, color: 0xFF00FFFF } },
+                { type: 'custom', side: { width: 100.0, color: 0xFFFF0000 } }, // should not render
+              ],
+              gradient: {
+                type: 'radial',
+              },
+            },
+          ),
+        ),
+      );
+    '''));
+    await tester.pump();
+    await expectLater(
+      find.byType(RemoteWidget),
+      matchesGoldenFile('goldens/argument_decoders_test.containers.png'),
+    );
+    expect(find.byType(DecoratedBox), findsNWidgets(6));
+    expect(
+      (tester.widgetList<DecoratedBox>(find.byType(DecoratedBox)).toList()[1].decoration as BoxDecoration).image.toString(),
+      'DecorationImage(AssetImage(bundle: null, name: "asset"), ' // this just seemed like the easiest way to check all this...
+      'ColorFilter.matrix([1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0]), '
+      'Alignment.center, centerSlice: Rect.fromLTRB(5.0, 8.0, 105.0, 78.0), scale 1.0, opacity 1.0, FilterQuality.low)',
+    );
+    expect(
+      (tester.widgetList<DecoratedBox>(find.byType(DecoratedBox)).toList()[0].decoration as BoxDecoration).image.toString(),
+      'DecorationImage(NetworkImage("x-invalid://", scale: 1.0), '
+      'ColorFilter.mode(Color(0xff8811ff), BlendMode.xor), Alignment.center, scale 1.0, '
+      'opacity 1.0, FilterQuality.low)',
+    );
+
+    runtime.update(const LibraryName(<String>['test']), parseLibraryFile('''
+      import core;
+      widget root = Column(
+        children: [
+          Text(
+            text: [
+              'Hello World Hello World Hello World Hello World Hello World Hello World Hello World',
+              'Hello World Hello World Hello World Hello World Hello World Hello World Hello World',
+            ],
+            locale: "en-US",
+            style: {
+              fontFamilyFallback: [ "a", "b" ],
+              fontSize: 30.0,
+            },
+            strutStyle: {
+              fontSize: 50.0,
+            },
+          ),
+          Expanded(
+            flex: 2,
+            child: Text(
+              text: 'Aaaa Aaaaaaa Aaaaa',
+              locale: "en",
+              style: {
+                decoration: [ "underline", "overline" ],
+                decorationColor: 0xFF00FF00,
+                fontFeatures: [ { feature: 'sups' } ],
+                foreground: {
+                  blendMode: 'color',
+                  color: 0xFFEEDDCC,
+                  colorFilter: { type: 'srgbToLinearGamma' },
+                  filterQuality: "high",
+                  isAntiAlias: true,
+                  maskFilter: { type: 'blur' },
+                  shader: { type: 'linear', rect: { x: 0.0, y: 0.0, w: 300.0, h: 200.0, } }
+                },
+                background: {
+                  colorFilter: { type: 'custom' },
+                  maskFilter: { type: 'custom' },
+                  shader: { type: 'custom' },
+                },
+              },
+            ),
+          ),
+          Expanded(
+            flex: 1,
+            child: Text(
+              text: 'B',
+              locale: "en-latin-GB",
+            ),
+          ),
+        ],
+      );
+    '''));
+    await tester.pump();
+    expect(tester.firstWidget<Text>(find.byType(Text)).style!.fontFamilyFallback, <String>[ 'a', 'b' ]);
+    expect(tester.widgetList<Text>(find.byType(Text)).map<Locale>((Text widget) => widget.locale!), const <Locale>[Locale('en', 'US'), Locale('en'), Locale.fromSubtags(languageCode: 'en', scriptCode: 'latin', countryCode: 'GB')]);
+    await expectLater(
+      find.byType(RemoteWidget),
+      matchesGoldenFile('goldens/argument_decoders_test.text.png'),
+    );
+
+    runtime.update(const LibraryName(<String>['test']), parseLibraryFile('''
+      import core;
+      widget root = GridView(
+        gridDelegate: { type: 'fixedCrossAxisCount', crossAxisCount: 3 },
+        children: [
+          ColoredBox(color: 0xFF118844),
+          ColoredBox(color: 0xFFEE8844),
+          ColoredBox(color: 0xFF882244),
+          ColoredBox(color: 0xFF449999),
+          ColoredBox(color: 0xFF330088),
+          ColoredBox(color: 0xFF8822CC),
+          ColoredBox(color: 0xFF330000),
+          ColoredBox(color: 0xFF992288),
+        ],
+      );
+    '''));
+    await tester.pump();
+    await expectLater(
+      find.byType(RemoteWidget),
+      matchesGoldenFile('goldens/argument_decoders_test.gridview.fixed.png'),
+    );
+
+    runtime.update(const LibraryName(<String>['test']), parseLibraryFile('''
+      import core;
+      widget root = GridView(
+        gridDelegate: { type: 'maxCrossAxisExtent', maxCrossAxisExtent: 50.0 },
+        children: [
+          ColoredBox(color: 0xFF118844),
+          ColoredBox(color: 0xFFEE8844),
+          ColoredBox(color: 0xFF882244),
+          ColoredBox(color: 0xFF449999),
+          ColoredBox(color: 0xFF330088),
+          ColoredBox(color: 0xFF8822CC),
+          ColoredBox(color: 0xFF330000),
+          ColoredBox(color: 0xFF992288),
+        ],
+      );
+    '''));
+    await tester.pump();
+    await expectLater(
+      find.byType(RemoteWidget),
+      matchesGoldenFile('goldens/argument_decoders_test.gridview.max.png'),
+    );
+
+    int sawGridDelegateDecoder = 0;
+    ArgumentDecoders.gridDelegateDecoders['custom'] = (DataSource source, List<Object> key) {
+      sawGridDelegateDecoder += 1;
+      return null;
+    };
+    runtime.update(const LibraryName(<String>['test']), parseLibraryFile('''
+      import core;
+      widget root = GridView(
+        gridDelegate: { type: 'custom' },
+        children: [
+          ColoredBox(color: 0xFF118844),
+          ColoredBox(color: 0xFFEE8844),
+          ColoredBox(color: 0xFF882244),
+          ColoredBox(color: 0xFF449999),
+          ColoredBox(color: 0xFF330088),
+          ColoredBox(color: 0xFF8822CC),
+          ColoredBox(color: 0xFF330000),
+          ColoredBox(color: 0xFF992288),
+        ],
+      );
+    '''));
+    expect(sawGridDelegateDecoder, 0);
+    await tester.pump();
+    expect(sawGridDelegateDecoder, 1);
+    await expectLater(
+      find.byType(RemoteWidget),
+      matchesGoldenFile('goldens/argument_decoders_test.gridview.custom.png'),
+    );
+  });
+}
diff --git a/packages/rfw/test/binary_test.dart b/packages/rfw/test/binary_test.dart
new file mode 100644
index 0000000..de8921c
--- /dev/null
+++ b/packages/rfw/test/binary_test.dart
@@ -0,0 +1,459 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This file is hand-formatted.
+
+import 'dart:typed_data';
+
+import 'package:flutter_test/flutter_test.dart';
+import 'package:rfw/formats.dart';
+
+void main() {
+  testWidgets('String example', (WidgetTester tester) async {
+    final Uint8List bytes = encodeDataBlob('Hello');
+    expect(bytes, <int>[ 0xFE, 0x52, 0x57, 0x44, 0x04, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x65, 0x6C, 0x6C, 0x6F ]);
+    final Object value = decodeDataBlob(bytes);
+    expect(value, isA<String>());
+    expect(value, 'Hello');
+  });
+
+  testWidgets('Map example', (WidgetTester tester) async {
+    final Uint8List bytes = encodeDataBlob(const <String, Object?>{ 'a': 15 });
+    expect(bytes, <int>[
+      0xFE, 0x52, 0x57, 0x44, 0x07, 0x01, 0x00, 0x00,  0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,
+      0x00, 0x00, 0x00, 0x00, 0x00, 0x61, 0x02, 0x0F,  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    ]);
+    final Object value = decodeDataBlob(bytes);
+    expect(value, isA<DynamicMap>());
+    expect(value, const <String, Object?>{ 'a': 15 });
+  });
+
+  testWidgets('Signature check in decoders', (WidgetTester tester) async {
+    try {
+      decodeDataBlob(Uint8List.fromList(<int>[ 0xFE, 0x52, 0x46, 0x57, 0x04, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x65, 0x6C, 0x6C, 0x6F ]));
+      fail('did not throw exception');
+    } on FormatException catch (e) {
+      expect('$e', contains('File signature mismatch. Expected FE 52 57 44 but found FE 52 46 57.'));
+    }
+    try {
+      decodeLibraryBlob(Uint8List.fromList(<int>[ 0xFE, 0x52, 0x57, 0x44, 0x04, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x65, 0x6C, 0x6C, 0x6F ]));
+      fail('did not throw exception');
+    } on FormatException catch (e) {
+      expect('$e', contains('File signature mismatch. Expected FE 52 46 57 but found FE 52 57 44.'));
+    }
+  });
+
+  testWidgets('Trailing byte check', (WidgetTester tester) async {
+    try {
+      decodeDataBlob(Uint8List.fromList(<int>[ 0xFE, 0x52, 0x57, 0x44, 0x00, 0x00 ]));
+      fail('did not throw exception');
+    } on FormatException catch (e) {
+      expect('$e', contains('Unexpected trailing bytes after value.'));
+    }
+    try {
+      decodeLibraryBlob(Uint8List.fromList(<int>[ 0xFE, 0x52, 0x46, 0x57, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 ]));
+      fail('did not throw exception');
+    } on FormatException catch (e) {
+      expect('$e', contains('Unexpected trailing bytes after constructors.'));
+    }
+  });
+
+  testWidgets('Incomplete files in signatures', (WidgetTester tester) async {
+    try {
+      decodeDataBlob(Uint8List.fromList(<int>[ 0xFE, 0x52, 0x57 ]));
+      fail('did not throw exception');
+    } on FormatException catch (e) {
+      expect('$e', contains('Could not read byte at offset 3: unexpected end of file.'));
+    }
+    try {
+      decodeLibraryBlob(Uint8List.fromList(<int>[ 0xFE, 0x52, 0x46 ]));
+      fail('did not throw exception');
+    } on FormatException catch (e) {
+      expect('$e', contains('Could not read byte at offset 3: unexpected end of file.'));
+    }
+  });
+
+  testWidgets('Incomplete files after signatures', (WidgetTester tester) async {
+    try {
+      decodeDataBlob(Uint8List.fromList(<int>[ 0xFE, 0x52, 0x57, 0x44 ]));
+      fail('did not throw exception');
+    } on FormatException catch (e) {
+      expect('$e', contains('Could not read byte at offset 4: unexpected end of file.'));
+    }
+    try {
+      decodeLibraryBlob(Uint8List.fromList(<int>[ 0xFE, 0x52, 0x46, 0x57 ]));
+      fail('did not throw exception');
+    } on FormatException catch (e) {
+      expect('$e', contains('Could not read int64 at offset 4: unexpected end of file.'));
+    }
+  });
+
+  testWidgets('Invalid value tag', (WidgetTester tester) async {
+    try {
+      decodeDataBlob(Uint8List.fromList(<int>[ 0xFE, 0x52, 0x57, 0x44, 0xCC ]));
+      fail('did not throw exception');
+    } on FormatException catch (e) {
+      expect('$e', contains('Unrecognized data type 0xCC while decoding blob.'));
+    }
+  });
+
+  testWidgets('Library encoder smoke test', (WidgetTester tester) async {
+    final Uint8List bytes = encodeLibraryBlob(const RemoteWidgetLibrary(<Import>[], <WidgetDeclaration>[]));
+    expect(bytes, <int>[ 0xFE, 0x52, 0x46, 0x57, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 ]);
+    final RemoteWidgetLibrary value = decodeLibraryBlob(bytes);
+    expect(value.imports, isEmpty);
+    expect(value.widgets, isEmpty);
+  });
+
+  testWidgets('Library encoder: imports', (WidgetTester tester) async {
+    final Uint8List bytes = encodeLibraryBlob(const RemoteWidgetLibrary(<Import>[Import(LibraryName(<String>['a']))], <WidgetDeclaration>[]));
+    expect(bytes, <int>[
+      0xFE, 0x52, 0x46, 0x57, 0x01, 0x00, 0x00, 0x00,  0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+      0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,  0x00, 0x00, 0x00, 0x00, 0x61, 0x00, 0x00, 0x00,
+      0x00, 0x00, 0x00, 0x00, 0x00,
+    ]);
+    final RemoteWidgetLibrary value = decodeLibraryBlob(bytes);
+    expect(value.imports, hasLength(1));
+    expect(value.imports.single.name, const LibraryName(<String>['a']));
+    expect(value.widgets, isEmpty);
+  });
+
+  testWidgets('Doubles', (WidgetTester tester) async {
+    final Uint8List bytes = encodeDataBlob(0.25);
+    expect(bytes, <int>[ 0xFE, 0x52, 0x57, 0x44, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xD0, 0x3F ]);
+    final Object value = decodeDataBlob(bytes);
+    expect(value, isA<double>());
+    expect(value, 0.25);
+  });
+
+  testWidgets('Library decoder: invalid widget declaration root', (WidgetTester tester) async {
+    final Uint8List bytes = Uint8List.fromList(<int>[
+      0xfe, 0x52, 0x46, 0x57, 0x00, 0x00, 0x00, 0x00,  0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+      0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,  0x00, 0x00, 0x00, 0x00, 0x61, 0x00, 0x00, 0x00,
+      0x00, 0x00, 0x00, 0x00, 0x00, 0xEF,
+    ]);
+    try {
+      decodeLibraryBlob(bytes);
+    } on FormatException catch (e) {
+      expect('$e', contains('Unrecognized data type 0xEF while decoding widget declaration root.'));
+    }
+  });
+
+  testWidgets('Library encoder: args references', (WidgetTester tester) async {
+    final Uint8List bytes = encodeLibraryBlob(const RemoteWidgetLibrary(<Import>[], <WidgetDeclaration>[
+      WidgetDeclaration('a', null, ConstructorCall('b', <String, Object?>{ 'c': <Object?>[ ArgsReference(<Object>['d', 5]) ] })),
+    ]));
+    expect(bytes, <int>[
+      0xfe, 0x52, 0x46, 0x57, 0x00, 0x00, 0x00, 0x00,  0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+      0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,  0x00, 0x00, 0x00, 0x00, 0x61, 0x00, 0x00, 0x00,
+      0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x01, 0x00,  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x62, 0x01,
+      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63,
+      0x05, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  0x00, 0x0a, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00,
+      0x00, 0x00, 0x04, 0x01, 0x00, 0x00, 0x00, 0x00,  0x00, 0x00, 0x00, 0x64, 0x02, 0x05, 0x00, 0x00,
+      0x00, 0x00, 0x00, 0x00, 0x00,
+    ]);
+    final RemoteWidgetLibrary value = decodeLibraryBlob(bytes);
+    expect(value.imports, isEmpty);
+    expect(value.widgets, hasLength(1));
+    expect(value.widgets.first.name, 'a');
+    expect(value.widgets.first.initialState, isNull);
+    expect(value.widgets.first.root, isA<ConstructorCall>());
+    expect((value.widgets.first.root as ConstructorCall).name, 'b');
+    expect((value.widgets.first.root as ConstructorCall).arguments, hasLength(1));
+    expect((value.widgets.first.root as ConstructorCall).arguments.keys, <Object?>['c']);
+    expect((value.widgets.first.root as ConstructorCall).arguments['c'], hasLength(1));
+    expect(((value.widgets.first.root as ConstructorCall).arguments['c']! as DynamicList)[0], isA<ArgsReference>());
+    expect((((value.widgets.first.root as ConstructorCall).arguments['c']! as DynamicList)[0]! as ArgsReference).parts, hasLength(2));
+    expect((((value.widgets.first.root as ConstructorCall).arguments['c']! as DynamicList)[0]! as ArgsReference).parts[0], 'd');
+    expect((((value.widgets.first.root as ConstructorCall).arguments['c']! as DynamicList)[0]! as ArgsReference).parts[1], 5);
+  });
+
+  testWidgets('Library encoder: invalid args references', (WidgetTester tester) async {
+    try {
+      encodeLibraryBlob(const RemoteWidgetLibrary(<Import>[], <WidgetDeclaration>[
+        WidgetDeclaration('a', null, ConstructorCall('b', <String, Object?>{ 'c': <Object?>[ ArgsReference(<Object>[false]) ] })),
+      ]));
+    } on StateError catch (e) {
+      expect('$e', contains('Unexpected type bool while encoding blob.'));
+    }
+  });
+
+  testWidgets('Library decoder: invalid args references', (WidgetTester tester) async {
+    final Uint8List bytes = Uint8List.fromList(<int>[
+      0xfe, 0x52, 0x46, 0x57, 0x00, 0x00, 0x00, 0x00,  0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+      0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,  0x00, 0x00, 0x00, 0x00, 0x61, 0x00, 0x00, 0x00,
+      0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x01, 0x00,  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x62, 0x01,
+      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63,
+      0x05, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  0x00, 0x0a, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
+      0x00, 0x00, 0xAC,
+    ]);
+    try {
+      decodeLibraryBlob(bytes);
+    } on FormatException catch (e) {
+      expect('$e', contains('Invalid reference type 0xAC while decoding blob.'));
+    }
+  });
+
+  testWidgets('Library encoder: switches', (WidgetTester tester) async {
+    final Uint8List bytes = encodeLibraryBlob(const RemoteWidgetLibrary(<Import>[], <WidgetDeclaration>[
+      WidgetDeclaration('a', null, Switch('b', <Object?, Object>{ null: 'c' })),
+    ]));
+    expect(bytes, <int>[
+      0xfe, 0x52, 0x46, 0x57, 0x00, 0x00, 0x00, 0x00,  0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+      0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,  0x00, 0x00, 0x00, 0x00, 0x61, 0x00, 0x00, 0x00,
+      0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x04, 0x01,  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x62,
+      0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  0x10, 0x04, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
+      0x00, 0x00, 0x63,
+    ]);
+    final RemoteWidgetLibrary value = decodeLibraryBlob(bytes);
+    expect(value.imports, isEmpty);
+    expect(value.widgets, hasLength(1));
+    expect(value.widgets.first.name, 'a');
+    expect(value.widgets.first.initialState, isNull);
+    expect(value.widgets.first.root, isA<Switch>());
+    expect((value.widgets.first.root as Switch).input, 'b');
+    expect((value.widgets.first.root as Switch).outputs, hasLength(1));
+    expect((value.widgets.first.root as Switch).outputs.keys, <Object?>[null]);
+    expect((value.widgets.first.root as Switch).outputs[null], 'c');
+  });
+
+  testWidgets('Library encoder: switches', (WidgetTester tester) async {
+    final Uint8List bytes = encodeLibraryBlob(const RemoteWidgetLibrary(<Import>[], <WidgetDeclaration>[
+      WidgetDeclaration('a', null, Switch('b', <Object?, Object>{ 'c': 'd' })),
+    ]));
+    expect(bytes, <int>[
+      0xfe, 0x52, 0x46, 0x57, 0x00, 0x00, 0x00, 0x00,  0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+      0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,  0x00, 0x00, 0x00, 0x00, 0x61, 0x00, 0x00, 0x00,
+      0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x04, 0x01,  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x62,
+      0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  0x04, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+      0x00, 0x63, 0x04, 0x01, 0x00, 0x00, 0x00, 0x00,  0x00, 0x00, 0x00, 0x64,
+    ]);
+    final RemoteWidgetLibrary value = decodeLibraryBlob(bytes);
+    expect(value.imports, isEmpty);
+    expect(value.widgets, hasLength(1));
+    expect(value.widgets.first.name, 'a');
+    expect(value.widgets.first.initialState, isNull);
+    expect(value.widgets.first.root, isA<Switch>());
+    expect((value.widgets.first.root as Switch).input, 'b');
+    expect((value.widgets.first.root as Switch).outputs, hasLength(1));
+    expect((value.widgets.first.root as Switch).outputs.keys, <Object?>['c']);
+    expect((value.widgets.first.root as Switch).outputs['c'], 'd');
+  });
+
+  testWidgets('Bools', (WidgetTester tester) async {
+    final Uint8List bytes = encodeDataBlob(const <Object?>[ false, true ]);
+    expect(bytes, <int>[
+      0xFE, 0x52, 0x57, 0x44, 0x05, 0x02, 0x00, 0x00,  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+    ]);
+    final Object value = decodeDataBlob(bytes);
+    expect(value, isA<DynamicList>());
+    expect(value, const <Object?>{ false, true });
+  });
+
+  testWidgets('Library encoder: loops', (WidgetTester tester) async {
+    final Uint8List bytes = encodeLibraryBlob(const RemoteWidgetLibrary(<Import>[], <WidgetDeclaration>[
+      WidgetDeclaration('a', null, ConstructorCall('b', <String, Object?>{ 'c': <Object?>[
+        Loop(<Object?>[], ConstructorCall('d', <String, Object?>{ 'e': LoopReference(0, <Object>[]) })),
+      ] })),
+    ]));
+    expect(bytes, <int>[
+      0xfe, 0x52, 0x46, 0x57, 0x00, 0x00, 0x00, 0x00,  0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+      0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,  0x00, 0x00, 0x00, 0x00, 0x61, 0x00, 0x00, 0x00,
+      0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x01, 0x00,  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x62, 0x01,
+      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63,
+      0x05, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  0x00, 0x08, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00,
+      0x00, 0x00, 0x00, 0x09, 0x01, 0x00, 0x00, 0x00,  0x00, 0x00, 0x00, 0x00, 0x64, 0x01, 0x00, 0x00,
+      0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,  0x00, 0x00, 0x00, 0x00, 0x00, 0x65, 0x0c, 0x00,
+      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+    ]);
+    final RemoteWidgetLibrary value = decodeLibraryBlob(bytes);
+    expect(value.imports, isEmpty);
+    expect(value.widgets, hasLength(1));
+    expect(value.widgets.first.name, 'a');
+    expect(value.widgets.first.initialState, isNull);
+    expect(value.widgets.first.root, isA<ConstructorCall>());
+    expect((value.widgets.first.root as ConstructorCall).name, 'b');
+    expect((value.widgets.first.root as ConstructorCall).arguments, hasLength(1));
+    expect((value.widgets.first.root as ConstructorCall).arguments.keys, <Object?>['c']);
+    expect((value.widgets.first.root as ConstructorCall).arguments['c'], hasLength(1));
+    expect(((value.widgets.first.root as ConstructorCall).arguments['c']! as DynamicList)[0], isA<Loop>());
+    expect((((value.widgets.first.root as ConstructorCall).arguments['c']! as DynamicList)[0]! as Loop).input, isEmpty);
+    expect((((value.widgets.first.root as ConstructorCall).arguments['c']! as DynamicList)[0]! as Loop).output, isA<ConstructorCall>());
+    expect(((((value.widgets.first.root as ConstructorCall).arguments['c']! as DynamicList)[0]! as Loop).output as ConstructorCall).name, 'd');
+    expect(((((value.widgets.first.root as ConstructorCall).arguments['c']! as DynamicList)[0]! as Loop).output as ConstructorCall).arguments, hasLength(1));
+    expect(((((value.widgets.first.root as ConstructorCall).arguments['c']! as DynamicList)[0]! as Loop).output as ConstructorCall).arguments['e'], isA<LoopReference>());
+    expect((((((value.widgets.first.root as ConstructorCall).arguments['c']! as DynamicList)[0]! as Loop).output as ConstructorCall).arguments['e']! as LoopReference).loop, 0);
+    expect((((((value.widgets.first.root as ConstructorCall).arguments['c']! as DynamicList)[0]! as Loop).output as ConstructorCall).arguments['e']! as LoopReference).parts, isEmpty);
+  });
+
+  testWidgets('Library encoder: data references', (WidgetTester tester) async {
+    final Uint8List bytes = encodeLibraryBlob(const RemoteWidgetLibrary(<Import>[], <WidgetDeclaration>[
+      WidgetDeclaration('a', null, ConstructorCall('b', <String, Object?>{ 'c': DataReference(<Object>['d']) })),
+    ]));
+    expect(bytes, <int>[
+      0xfe, 0x52, 0x46, 0x57, 0x00, 0x00, 0x00, 0x00,  0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+      0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,  0x00, 0x00, 0x00, 0x00, 0x61, 0x00, 0x00, 0x00,
+      0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x01, 0x00,  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x62, 0x01,
+      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63,
+      0x0B, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  0x00, 0x04, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
+      0x00, 0x00, 0x64,
+    ]);
+    final RemoteWidgetLibrary value = decodeLibraryBlob(bytes);
+    expect(value.imports, isEmpty);
+    expect(value.widgets, hasLength(1));
+    expect(value.widgets.first.name, 'a');
+    expect(value.widgets.first.initialState, isNull);
+    expect(value.widgets.first.root, isA<ConstructorCall>());
+    expect((value.widgets.first.root as ConstructorCall).name, 'b');
+    expect((value.widgets.first.root as ConstructorCall).arguments, hasLength(1));
+    expect((value.widgets.first.root as ConstructorCall).arguments.keys, <Object?>['c']);
+    expect((value.widgets.first.root as ConstructorCall).arguments['c'], isA<DataReference>());
+    expect(((value.widgets.first.root as ConstructorCall).arguments['c']! as DataReference).parts, const <Object>[ 'd' ]);
+  });
+
+  testWidgets('Library encoder: state references', (WidgetTester tester) async {
+    final Uint8List bytes = encodeLibraryBlob(const RemoteWidgetLibrary(<Import>[], <WidgetDeclaration>[
+      WidgetDeclaration('a', null, ConstructorCall('b', <String, Object?>{ 'c': StateReference(<Object>['d']) })),
+    ]));
+    expect(bytes, <int>[
+      0xfe, 0x52, 0x46, 0x57, 0x00, 0x00, 0x00, 0x00,  0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+      0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,  0x00, 0x00, 0x00, 0x00, 0x61, 0x00, 0x00, 0x00,
+      0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x01, 0x00,  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x62, 0x01,
+      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63,
+      0x0D, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  0x00, 0x04, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
+      0x00, 0x00, 0x64,
+    ]);
+    final RemoteWidgetLibrary value = decodeLibraryBlob(bytes);
+    expect(value.imports, isEmpty);
+    expect(value.widgets, hasLength(1));
+    expect(value.widgets.first.name, 'a');
+    expect(value.widgets.first.initialState, isNull);
+    expect(value.widgets.first.root, isA<ConstructorCall>());
+    expect((value.widgets.first.root as ConstructorCall).name, 'b');
+    expect((value.widgets.first.root as ConstructorCall).arguments, hasLength(1));
+    expect((value.widgets.first.root as ConstructorCall).arguments.keys, <Object?>['c']);
+    expect((value.widgets.first.root as ConstructorCall).arguments['c'], isA<StateReference>());
+    expect(((value.widgets.first.root as ConstructorCall).arguments['c']! as StateReference).parts, const <Object>[ 'd' ]);
+  });
+
+  testWidgets('Library encoder: event handler', (WidgetTester tester) async {
+    final Uint8List bytes = encodeLibraryBlob(const RemoteWidgetLibrary(<Import>[], <WidgetDeclaration>[
+      WidgetDeclaration('a', null, ConstructorCall('b', <String, Object?>{ 'c': EventHandler('d', <String, Object?>{}) })),
+    ]));
+    expect(bytes, <int>[
+      0xfe, 0x52, 0x46, 0x57, 0x00, 0x00, 0x00, 0x00,  0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+      0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,  0x00, 0x00, 0x00, 0x00, 0x61, 0x00, 0x00, 0x00,
+      0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x01, 0x00,  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x62, 0x01,
+      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63,
+      0x0E, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  0x00, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+      0x00, 0x00,
+    ]);
+    final RemoteWidgetLibrary value = decodeLibraryBlob(bytes);
+    expect(value.imports, isEmpty);
+    expect(value.widgets, hasLength(1));
+    expect(value.widgets.first.name, 'a');
+    expect(value.widgets.first.initialState, isNull);
+    expect(value.widgets.first.root, isA<ConstructorCall>());
+    expect((value.widgets.first.root as ConstructorCall).name, 'b');
+    expect((value.widgets.first.root as ConstructorCall).arguments, hasLength(1));
+    expect((value.widgets.first.root as ConstructorCall).arguments.keys, <Object?>['c']);
+    expect((value.widgets.first.root as ConstructorCall).arguments['c'], isA<EventHandler>());
+    expect(((value.widgets.first.root as ConstructorCall).arguments['c']! as EventHandler).eventName, 'd');
+    expect(((value.widgets.first.root as ConstructorCall).arguments['c']! as EventHandler).eventArguments, isEmpty);
+  });
+
+  testWidgets('Library encoder: state setter', (WidgetTester tester) async {
+    final Uint8List bytes = encodeLibraryBlob(const RemoteWidgetLibrary(<Import>[], <WidgetDeclaration>[
+      WidgetDeclaration('a', null, ConstructorCall('b', <String, Object?>{ 'c': SetStateHandler(StateReference(<Object>['d']), false) })),
+    ]));
+    expect(bytes, <int>[
+      0xfe, 0x52, 0x46, 0x57, 0x00, 0x00, 0x00, 0x00,  0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+      0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,  0x00, 0x00, 0x00, 0x00, 0x61, 0x00, 0x00, 0x00,
+      0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x01, 0x00,  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x62, 0x01,
+      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63,
+      0x11, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  0x00, 0x04, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
+      0x00, 0x00, 0x64, 0x00,
+    ]);
+    final RemoteWidgetLibrary value = decodeLibraryBlob(bytes);
+    expect(value.imports, isEmpty);
+    expect(value.widgets, hasLength(1));
+    expect(value.widgets.first.name, 'a');
+    expect(value.widgets.first.initialState, isNull);
+    expect(value.widgets.first.root, isA<ConstructorCall>());
+    expect((value.widgets.first.root as ConstructorCall).name, 'b');
+    expect((value.widgets.first.root as ConstructorCall).arguments, hasLength(1));
+    expect((value.widgets.first.root as ConstructorCall).arguments.keys, <Object?>['c']);
+    expect((value.widgets.first.root as ConstructorCall).arguments['c'], isA<SetStateHandler>());
+    expect(((value.widgets.first.root as ConstructorCall).arguments['c']! as SetStateHandler).stateReference.parts, <Object>['d']);
+    expect(((value.widgets.first.root as ConstructorCall).arguments['c']! as SetStateHandler).value, false);
+  });
+
+  testWidgets('Library encoder: switch', (WidgetTester tester) async {
+    final Uint8List bytes = encodeLibraryBlob(const RemoteWidgetLibrary(<Import>[], <WidgetDeclaration>[
+      WidgetDeclaration('a', null, ConstructorCall('b', <String, Object?>{ 'c': Switch(false, <Object?, Object>{} ) })),
+    ]));
+    expect(bytes, <int>[
+      0xfe, 0x52, 0x46, 0x57, 0x00, 0x00, 0x00, 0x00,  0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+      0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,  0x00, 0x00, 0x00, 0x00, 0x61, 0x00, 0x00, 0x00,
+      0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x01, 0x00,  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x62, 0x01,
+      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63,
+      0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  0x00, 0x00,
+    ]);
+    final RemoteWidgetLibrary value = decodeLibraryBlob(bytes);
+    expect(value.imports, isEmpty);
+    expect(value.widgets, hasLength(1));
+    expect(value.widgets.first.name, 'a');
+    expect(value.widgets.first.initialState, isNull);
+    expect(value.widgets.first.root, isA<ConstructorCall>());
+    expect((value.widgets.first.root as ConstructorCall).name, 'b');
+    expect((value.widgets.first.root as ConstructorCall).arguments, hasLength(1));
+    expect((value.widgets.first.root as ConstructorCall).arguments.keys, <Object?>['c']);
+    expect((value.widgets.first.root as ConstructorCall).arguments['c'], isA<Switch>());
+    expect(((value.widgets.first.root as ConstructorCall).arguments['c']! as Switch).input, false);
+    expect(((value.widgets.first.root as ConstructorCall).arguments['c']! as Switch).outputs, isEmpty);
+  });
+
+  testWidgets('Library encoder: initial state', (WidgetTester tester) async {
+    final Uint8List bytes = encodeLibraryBlob(const RemoteWidgetLibrary(<Import>[], <WidgetDeclaration>[
+      WidgetDeclaration('a', <String, Object?>{}, ConstructorCall('b', <String, Object?>{})),
+    ]));
+    expect(bytes, <int>[
+      0xfe, 0x52, 0x46, 0x57, 0x00, 0x00, 0x00, 0x00,  0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+      0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,  0x00, 0x00, 0x00, 0x00, 0x61, 0x00, 0x00, 0x00,
+      0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x01, 0x00,  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x62, 0x00,
+      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    ]);
+    final RemoteWidgetLibrary value = decodeLibraryBlob(bytes);
+    expect(value.imports, isEmpty);
+    expect(value.widgets, hasLength(1));
+    expect(value.widgets.first.name, 'a');
+    expect(value.widgets.first.initialState, isNull);
+    expect(value.widgets.first.root, isA<ConstructorCall>());
+    expect((value.widgets.first.root as ConstructorCall).name, 'b');
+    expect((value.widgets.first.root as ConstructorCall).arguments, isEmpty);
+  });
+
+  testWidgets('Library encoder: initial state', (WidgetTester tester) async {
+    final Uint8List bytes = encodeLibraryBlob(const RemoteWidgetLibrary(<Import>[], <WidgetDeclaration>[
+      WidgetDeclaration('a', <String, Object?>{ 'b': false }, ConstructorCall('c', <String, Object?>{})),
+    ]));
+    expect(bytes, <int>[
+      0xfe, 0x52, 0x46, 0x57, 0x00, 0x00, 0x00, 0x00,  0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+      0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,  0x00, 0x00, 0x00, 0x00, 0x61, 0x01, 0x00, 0x00,
+      0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00,  0x00, 0x00, 0x00, 0x00, 0x00, 0x62, 0x00, 0x09,
+      0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63,  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+      0x00,
+    ]);
+    final RemoteWidgetLibrary value = decodeLibraryBlob(bytes);
+    expect(value.imports, isEmpty);
+    expect(value.widgets, hasLength(1));
+    expect(value.widgets.first.name, 'a');
+    expect(value.widgets.first.initialState, isNotNull);
+    expect(value.widgets.first.initialState!, hasLength(1));
+    expect(value.widgets.first.initialState!['b'], false);
+    expect(value.widgets.first.root, isA<ConstructorCall>());
+    expect((value.widgets.first.root as ConstructorCall).name, 'c');
+    expect((value.widgets.first.root as ConstructorCall).arguments, isEmpty);
+  });
+}
diff --git a/packages/rfw/test/core_widgets_test.dart b/packages/rfw/test/core_widgets_test.dart
new file mode 100644
index 0000000..39967d5
--- /dev/null
+++ b/packages/rfw/test/core_widgets_test.dart
@@ -0,0 +1,264 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This file is hand-formatted.
+
+import 'dart:typed_data';
+
+import 'package:flutter/material.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:rfw/formats.dart' show parseLibraryFile;
+import 'package:rfw/rfw.dart';
+
+void main() {
+  testWidgets('Core widgets', (WidgetTester tester) async {
+    final Runtime runtime = Runtime()
+      ..update(const LibraryName(<String>['core']), createCoreWidgets());
+    final DynamicContent data = DynamicContent();
+    final List<String> eventLog = <String>[];
+    await tester.pumpWidget(
+      RemoteWidget(
+        runtime: runtime,
+        data: data,
+        widget: const FullyQualifiedWidgetName(LibraryName(<String>['test']), 'root'),
+        onEvent: (String eventName, DynamicMap eventArguments) {
+          eventLog.add('$eventName $eventArguments');
+        },
+      ),
+    );
+    expect(tester.takeException().toString(), contains('Could not find remote widget named'));
+
+    runtime.update(const LibraryName(<String>['test']), parseLibraryFile('''
+      import core;
+      widget root = GestureDetector(
+        onTapDown: event 'tapdown' { },
+        onTapUp: event 'tapup' { },
+        onTap: event 'tap' { },
+        child: ColoredBox(),
+      );
+    '''));
+    await tester.pump();
+    await tester.tap(find.byType(ColoredBox));
+    expect(eventLog, <String>['tapdown {}', 'tapup {}', 'tap {}']);
+    eventLog.clear();
+
+    runtime.update(const LibraryName(<String>['test']), parseLibraryFile('''
+      import core;
+      widget root = IntrinsicHeight();
+    '''));
+    await tester.pump();
+    expect(find.byType(IntrinsicHeight), findsOneWidget);
+
+    runtime.update(const LibraryName(<String>['test']), parseLibraryFile('''
+      import core;
+      widget root = IntrinsicWidth();
+    '''));
+    await tester.pump();
+    expect(find.byType(IntrinsicWidth), findsOneWidget);
+
+    ArgumentDecoders.imageProviderDecoders['beepboop'] = (DataSource source, List<Object> key) {
+      return MemoryImage(Uint8List.fromList(<int>[
+        0x47, 0x49, 0x46, 0x38, 0x39, 0x61, 0x01, 0x00,  0x01, 0x00, 0x80, 0xff, 0x00, 0xc0, 0xc0, 0xc0,
+        0x00, 0x00, 0x00, 0x21, 0xf9, 0x04, 0x01, 0x00,  0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x00,
+        0x01, 0x00, 0x01, 0x00, 0x00, 0x02, 0x02, 0x44,  0x01, 0x00, 0x3b,
+      ]));
+    };
+    runtime.update(const LibraryName(<String>['test']), parseLibraryFile('''
+      import core;
+      widget root = Image(source: 'beepboop');
+    '''));
+    await tester.pump();
+    expect(find.byType(Image), findsOneWidget);
+
+    runtime.update(const LibraryName(<String>['test']), parseLibraryFile('''
+      import core;
+      widget root = SingleChildScrollView(child: ListBody());
+    '''));
+    await tester.pump();
+    expect(find.byType(SingleChildScrollView), findsOneWidget);
+    expect(find.byType(ListBody), findsOneWidget);
+
+    runtime.update(const LibraryName(<String>['test']), parseLibraryFile('''
+      import core;
+      widget root = Directionality(
+        textDirection: "rtl",
+        child: ListView(
+          children: [
+            Container(height: 67.0),
+            Container(height: 67.0),
+            Container(height: 67.0),
+            Container(height: 67.0),
+            Container(height: 67.0),
+            Container(height: 67.0),
+            Container(height: 67.0),
+            Container(height: 67.0),
+            Container(height: 67.0),
+            Container(height: 67.0), // number 10 is not visible
+          ],
+        ),
+      );
+    '''));
+    await tester.pump();
+    expect(find.byType(ListView), findsOneWidget);
+    expect(find.byType(Container), findsNWidgets(9));
+
+    runtime.update(const LibraryName(<String>['test']), parseLibraryFile('''
+      import core;
+      widget root = Opacity(onEnd: event 'end' {});
+    '''));
+    await tester.pump();
+    expect(tester.widget<AnimatedOpacity>(find.byType(AnimatedOpacity)).onEnd, isNot(isNull));
+    expect(eventLog, isEmpty);
+
+    runtime.update(const LibraryName(<String>['test']), parseLibraryFile('''
+      import core;
+      widget root = Directionality(textDirection: "ltr", child: Padding(padding: [12.0]));
+    '''));
+    await tester.pump();
+    expect(tester.widget<Padding>(find.byType(Padding)).padding.resolve(TextDirection.ltr), const EdgeInsets.all(12.0));
+
+    runtime.update(const LibraryName(<String>['test']), parseLibraryFile('''
+      import core;
+      widget root = Directionality(textDirection: "ltr", child: Padding(padding: [24.0]));
+    '''));
+    await tester.pump();
+    expect(tester.widget<Padding>(find.byType(Padding)).padding.resolve(TextDirection.ltr), const EdgeInsets.all(12.0));
+    await tester.pump(const Duration(seconds: 4));
+    expect(tester.widget<Padding>(find.byType(Padding)).padding.resolve(TextDirection.ltr), const EdgeInsets.all(24.0));
+
+    runtime.update(const LibraryName(<String>['test']), parseLibraryFile('''
+      import core;
+      widget root = Placeholder();
+    '''));
+    await tester.pump();
+    expect(find.byType(Placeholder), findsOneWidget);
+
+    runtime.update(const LibraryName(<String>['test']), parseLibraryFile('''
+      import core;
+      widget root = Directionality(
+        textDirection: "ltr",
+        child: Stack(
+          children: [
+            Positioned(
+              start: 0.0,
+              top: 0.0,
+              width: 10.0,
+              height: 10.0,
+              child: ColoredBox(),
+            ),
+          ],
+        ),
+      );
+    '''));
+    await tester.pump();
+    expect(find.byType(Stack), findsOneWidget);
+    expect(find.byType(Positioned), findsOneWidget);
+
+    runtime.update(const LibraryName(<String>['test']), parseLibraryFile('''
+      import core;
+      widget root = Directionality(
+        textDirection: "rtl",
+        child: Rotation(turns: 0.0),
+      );
+    '''));
+    await tester.pump();
+    expect(find.byType(AnimatedRotation), findsOneWidget);
+
+    runtime.update(const LibraryName(<String>['test']), parseLibraryFile('''
+      import core;
+      widget root = Directionality(
+        textDirection: "rtl",
+        child: Rotation(turns: 1.0, onEnd: event 'end' { from: "rotation" }),
+      );
+    '''));
+    await tester.pump();
+    expect(find.byType(AnimatedRotation), findsOneWidget);
+    expect(eventLog, isEmpty);
+    await tester.pump(const Duration(seconds: 1));
+    expect(eventLog, <String>['end {from: rotation}']);
+
+    runtime.update(const LibraryName(<String>['test']), parseLibraryFile('''
+      import core;
+      widget root = Directionality(
+        textDirection: "rtl",
+        child: Row(
+          crossAxisAlignment: "start",
+          children: [SizedBox(width: 10.0)]
+        ),
+      );
+    '''));
+    await tester.pump();
+    expect(tester.getTopLeft(find.byType(SizedBox)), const Offset(790.0, 0.0));
+
+    runtime.update(const LibraryName(<String>['test']), parseLibraryFile('''
+      import core;
+      widget root = Directionality(
+        textDirection: "rtl",
+        child: Row(
+          crossAxisAlignment: "start",
+          children: [Spacer()]
+        ),
+      );
+    '''));
+    await tester.pump();
+    expect(tester.getTopLeft(find.byType(SizedBox)), const Offset(0.0, 0.0));
+
+    runtime.update(const LibraryName(<String>['test']), parseLibraryFile('''
+      import core;
+      widget root = SizedBoxExpand();
+    '''));
+    await tester.pump();
+    expect(find.byType(SizedBox), findsOneWidget);
+
+    runtime.update(const LibraryName(<String>['test']), parseLibraryFile('''
+      import core;
+      widget root = SizedBoxShrink();
+    '''));
+    await tester.pump();
+    expect(find.byType(SizedBox), findsOneWidget);
+  });
+
+  testWidgets('More core widgets', (WidgetTester tester) async {
+    final Runtime runtime = Runtime()
+      ..update(const LibraryName(<String>['core']), createCoreWidgets());
+    final DynamicContent data = DynamicContent();
+    final List<String> eventLog = <String>[];
+    await tester.pumpWidget(
+      MaterialApp(
+        home: RemoteWidget(
+          runtime: runtime,
+          data: data,
+          widget: const FullyQualifiedWidgetName(LibraryName(<String>['test']), 'root'),
+          onEvent: (String eventName, DynamicMap eventArguments) {
+            eventLog.add('$eventName $eventArguments');
+          },
+        ),
+      ),
+    );
+    expect(tester.takeException().toString(), contains('Could not find remote widget named'));
+
+    runtime.update(const LibraryName(<String>['test']), parseLibraryFile('''
+      import core;
+      widget root = SafeArea(
+        child: SizedBoxShrink(),
+      );
+    '''));
+    await tester.pump();
+    expect(find.byType(SafeArea), findsOneWidget);
+
+    runtime.update(const LibraryName(<String>['test']), parseLibraryFile('''
+      import core;
+      widget root = Scale();
+    '''));
+    await tester.pump();
+    expect(find.byType(AnimatedScale), findsOneWidget);
+
+    runtime.update(const LibraryName(<String>['test']), parseLibraryFile('''
+      import core;
+      widget root = Wrap();
+    '''));
+    await tester.pump();
+    expect(find.byType(Wrap), findsOneWidget);
+  });
+}
diff --git a/packages/rfw/test/goldens/argument_decoders_test.containers.png b/packages/rfw/test/goldens/argument_decoders_test.containers.png
new file mode 100644
index 0000000..01d4ef7
--- /dev/null
+++ b/packages/rfw/test/goldens/argument_decoders_test.containers.png
Binary files differ
diff --git a/packages/rfw/test/goldens/argument_decoders_test.gridview.custom.png b/packages/rfw/test/goldens/argument_decoders_test.gridview.custom.png
new file mode 100644
index 0000000..d879701
--- /dev/null
+++ b/packages/rfw/test/goldens/argument_decoders_test.gridview.custom.png
Binary files differ
diff --git a/packages/rfw/test/goldens/argument_decoders_test.gridview.fixed.png b/packages/rfw/test/goldens/argument_decoders_test.gridview.fixed.png
new file mode 100644
index 0000000..687b9c2
--- /dev/null
+++ b/packages/rfw/test/goldens/argument_decoders_test.gridview.fixed.png
Binary files differ
diff --git a/packages/rfw/test/goldens/argument_decoders_test.gridview.max.png b/packages/rfw/test/goldens/argument_decoders_test.gridview.max.png
new file mode 100644
index 0000000..5c117b7
--- /dev/null
+++ b/packages/rfw/test/goldens/argument_decoders_test.gridview.max.png
Binary files differ
diff --git a/packages/rfw/test/goldens/argument_decoders_test.text.png b/packages/rfw/test/goldens/argument_decoders_test.text.png
new file mode 100644
index 0000000..3107fe1
--- /dev/null
+++ b/packages/rfw/test/goldens/argument_decoders_test.text.png
Binary files differ
diff --git a/packages/rfw/test/goldens/material_test.drawer.png b/packages/rfw/test/goldens/material_test.drawer.png
new file mode 100644
index 0000000..db50253
--- /dev/null
+++ b/packages/rfw/test/goldens/material_test.drawer.png
Binary files differ
diff --git a/packages/rfw/test/goldens/material_test.scaffold.png b/packages/rfw/test/goldens/material_test.scaffold.png
new file mode 100644
index 0000000..befab3c
--- /dev/null
+++ b/packages/rfw/test/goldens/material_test.scaffold.png
Binary files differ
diff --git a/packages/rfw/test/material_widgets_test.dart b/packages/rfw/test/material_widgets_test.dart
new file mode 100644
index 0000000..9ce6bd0
--- /dev/null
+++ b/packages/rfw/test/material_widgets_test.dart
@@ -0,0 +1,144 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:flutter/material.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:rfw/formats.dart' show parseLibraryFile;
+import 'package:rfw/rfw.dart';
+
+void main() {
+  testWidgets('Material widgets', (WidgetTester tester) async {
+    final Runtime runtime = Runtime()
+      ..update(const LibraryName(<String>['core']), createCoreWidgets())
+      ..update(
+          const LibraryName(<String>['material']), createMaterialWidgets());
+    final DynamicContent data = DynamicContent();
+    final List<String> eventLog = <String>[];
+    await tester.pumpWidget(
+      MaterialApp(
+        home: RemoteWidget(
+          runtime: runtime,
+          data: data,
+          widget: const FullyQualifiedWidgetName(
+              LibraryName(<String>['test']), 'root'),
+          onEvent: (String eventName, DynamicMap eventArguments) {
+            eventLog.add('$eventName $eventArguments');
+          },
+        ),
+      ),
+    );
+    expect(tester.takeException().toString(),
+        contains('Could not find remote widget named'));
+
+    runtime.update(const LibraryName(<String>['test']), parseLibraryFile('''
+      import core;
+      import material;
+      widget root = Scaffold(
+        appBar: AppBar(
+          title: Text(text: 'Title'),
+          flexibleSpace: Placeholder(),
+          bottom: SizedBox(height: 56.0, child: Placeholder()),
+        ),
+        drawer: Drawer(
+          child: ListView(
+            children: [
+              DrawerHeader(),
+              ListTile(
+                visualDensity: 'adaptivePlatformDensity',
+                title: Text(text: 'title'),
+                subtitle: Text(text: 'title'),
+              ),
+              ListTile(
+                visualDensity: 'comfortable',
+                title: Text(text: 'title'),
+                subtitle: Text(text: 'title'),
+              ),
+              ListTile(
+                visualDensity: 'compact',
+                title: Text(text: 'title'),
+                subtitle: Text(text: 'title'),
+              ),
+              ListTile(
+                visualDensity: 'standard',
+                title: Text(text: 'title'),
+                subtitle: Text(text: 'title'),
+              ),
+              ListTile(
+                visualDensity: { horizontal: -4.0, vertical: 4.0 },
+                title: Text(text: 'title'),
+                subtitle: Text(text: 'title'),
+              ),
+              AboutListTile(),
+            ],
+          ),
+        ),
+        body: ListView(
+          children: [
+            Card(
+              margin: [20.0],
+              child: ListBody(
+                children: [
+                  ButtonBar(
+                    children: [
+                      ElevatedButton(
+                        onPressed: event 'button' { },
+                        child: Text(text: 'Elevated'),
+                      ),
+                      OutlinedButton(
+                        onPressed: event 'button' { },
+                        child: Text(text: 'Outlined'),
+                      ),
+                      TextButton(
+                        onPressed: event 'button' { },
+                        child: Text(text: 'Text'),
+                      ),
+                      VerticalDivider(),
+                      InkWell(
+                        child: Text(text: 'Ink'),
+                      ),
+                    ],
+                  ),
+                ],
+              ),
+            ),
+            Divider(),
+            Padding(
+              padding: [20.0],
+              child: Center(
+                child: CircularProgressIndicator(
+                  value: 0.6,
+                ),
+              ),
+            ),
+            Divider(),
+            Padding(
+              padding: [20.0],
+              child: Center(
+                child: LinearProgressIndicator(
+                  value: 0.6,
+                ),
+              ),
+            ),
+          ],
+        ),
+        floatingActionButton: FloatingActionButton(
+          onPressed: event 'fab' {},
+          child: Placeholder(),
+        ),
+      );
+    '''));
+    await tester.pump();
+    await expectLater(
+      find.byType(RemoteWidget),
+      matchesGoldenFile('goldens/material_test.scaffold.png'),
+    );
+    await tester.tapAt(const Offset(20.0, 20.0));
+    await tester.pump();
+    await tester.pump(const Duration(seconds: 1));
+    await expectLater(
+      find.byType(RemoteWidget),
+      matchesGoldenFile('goldens/material_test.drawer.png'),
+    );
+  });
+}
diff --git a/packages/rfw/test/model_test.dart b/packages/rfw/test/model_test.dart
new file mode 100644
index 0000000..3931b66
--- /dev/null
+++ b/packages/rfw/test/model_test.dart
@@ -0,0 +1,104 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This file is hand-formatted.
+
+import 'package:flutter_test/flutter_test.dart';
+import 'package:rfw/formats.dart';
+
+void main() {
+  testWidgets('$LibraryName', (WidgetTester tester) async {
+    T deconst<T>(T value) => value;
+    final LibraryName a = LibraryName(<String>['core', deconst<String>('widgets')]);
+    final LibraryName b = LibraryName(<String>['core', deconst<String>('widgets')]);
+    final LibraryName c = LibraryName(<String>['core', deconst<String>('material')]);
+    const LibraryName d = LibraryName(<String>['core']);
+    expect('$a', 'core.widgets');
+    expect('$c', 'core.material');
+    expect(a, equals(b));
+    expect(a.hashCode, equals(b.hashCode));
+    expect(a, isNot(equals(c)));
+    expect(a.hashCode, isNot(equals(c.hashCode)));
+    expect(a.compareTo(b), 0);
+    expect(b.compareTo(a), 0);
+    expect(a.compareTo(c), 1);
+    expect(c.compareTo(a), -1);
+    expect(b.compareTo(c), 1);
+    expect(c.compareTo(b), -1);
+    expect(a.compareTo(d), 1);
+    expect(b.compareTo(d), 1);
+    expect(c.compareTo(d), 1);
+    expect(d.compareTo(a), -1);
+    expect(d.compareTo(b), -1);
+    expect(d.compareTo(c), -1);
+  });
+
+  testWidgets('$FullyQualifiedWidgetName', (WidgetTester tester) async {
+    const FullyQualifiedWidgetName aa = FullyQualifiedWidgetName(LibraryName(<String>['a']), 'a');
+    const FullyQualifiedWidgetName ab = FullyQualifiedWidgetName(LibraryName(<String>['a']), 'b');
+    const FullyQualifiedWidgetName bb = FullyQualifiedWidgetName(LibraryName(<String>['b']), 'b');
+    expect('$aa', 'a:a');
+    expect(aa, isNot(equals(bb)));
+    expect(aa.hashCode, isNot(equals(bb.hashCode)));
+    expect(aa.compareTo(aa), 0);
+    expect(aa.compareTo(ab), -1);
+    expect(aa.compareTo(bb), -1);
+    expect(ab.compareTo(aa), 1);
+    expect(ab.compareTo(ab), 0);
+    expect(ab.compareTo(bb), -1);
+    expect(bb.compareTo(aa), 1);
+    expect(bb.compareTo(ab), 1);
+    expect(bb.compareTo(bb), 0);
+  });
+
+  testWidgets('toStrings', (WidgetTester tester) async {
+    expect('$missing', '<missing>');
+    expect('${const Loop(0, 1)}', '...for loop in 0: 1');
+    expect('${const Switch(0, <Object?, Object>{1: 2})}', 'switch 0 {1: 2}');
+    expect('${const ConstructorCall("a", <String, Object>{})}', 'a({})');
+    expect('${const ArgsReference(<Object>["a"])}', 'args.a');
+    expect('${const BoundArgsReference(false, <Object>["a"])}', 'args(false).a');
+    expect('${const DataReference(<Object>["a"])}', 'data.a');
+    expect('${const LoopReference(0, <Object>["a"])}', 'loop0.a');
+    expect('${const BoundLoopReference(0, <Object>["a"])}', 'loop(0).a');
+    expect('${const StateReference(<Object>["a"])}', 'state.a');
+    expect('${const BoundStateReference(0, <Object>["a"])}', 'state^0.a');
+    expect('${const EventHandler("a", <String, Object?>{})}', 'event a {}');
+    expect('${const SetStateHandler(StateReference(<Object>["a"]), false)}', 'set state.a = false');
+    expect('${const Import(LibraryName(<String>["a"]))}', 'import a;');
+    expect('${const WidgetDeclaration("a", null, ConstructorCall("b", <String, Object>{}))}', 'widget a = b({});');
+    expect('${const WidgetDeclaration("a", <String, Object?>{ "x": false }, ConstructorCall("b", <String, Object>{}))}', 'widget a = b({});');
+    expect('${const RemoteWidgetLibrary(<Import>[Import(LibraryName(<String>["a"]))], <WidgetDeclaration>[WidgetDeclaration("a", null, ConstructorCall("b", <String, Object>{}))])}', 'import a;\nwidget a = b({});');
+  });
+
+  testWidgets('$BoundArgsReference', (WidgetTester tester) async {
+    final Object target = Object();
+    final BoundArgsReference result = const ArgsReference(<Object>[0]).bind(target);
+    expect(result.arguments, target);
+    expect(result.parts, const <Object>[0]);
+  });
+
+  testWidgets('$DataReference', (WidgetTester tester) async {
+    final DataReference result = const DataReference(<Object>[0]).constructReference(<Object>[1]);
+    expect(result.parts, const <Object>[0, 1]);
+  });
+
+  testWidgets('$LoopReference', (WidgetTester tester) async {
+    final LoopReference result = const LoopReference(9, <Object>[0]).constructReference(<Object>[1]);
+    expect(result.parts, const <Object>[0, 1]);
+  });
+
+  testWidgets('$BoundLoopReference', (WidgetTester tester) async {
+    final Object target = Object();
+    final BoundLoopReference result = const LoopReference(9, <Object>[0]).bind(target).constructReference(<Object>[1]);
+    expect(result.value, target);
+    expect(result.parts, const <Object>[0, 1]);
+  });
+
+  testWidgets('$BoundStateReference', (WidgetTester tester) async {
+    final BoundStateReference result = const StateReference(<Object>[0]).bind(9).constructReference(<Object>[1]);
+    expect(result.depth, 9);
+    expect(result.parts, const <Object>[0, 1]);
+  });
+}
diff --git a/packages/rfw/test/remote_widget_test.dart b/packages/rfw/test/remote_widget_test.dart
new file mode 100644
index 0000000..a83f569
--- /dev/null
+++ b/packages/rfw/test/remote_widget_test.dart
@@ -0,0 +1,49 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:flutter/widgets.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:rfw/formats.dart' show parseLibraryFile;
+import 'package:rfw/rfw.dart';
+
+void main() {
+  testWidgets('RemoteWidget', (WidgetTester tester) async {
+    final Runtime runtime1 = Runtime()
+      ..update(const LibraryName(<String>['core']), createCoreWidgets())
+      ..update(const LibraryName(<String>['test']), parseLibraryFile('''
+        import core;
+        widget root = Placeholder();
+      '''));
+    final Runtime runtime2 = Runtime()
+      ..update(const LibraryName(<String>['core']), createCoreWidgets())
+      ..update(const LibraryName(<String>['test']), parseLibraryFile('''
+        import core;
+        widget root = Container();
+      '''));
+    final DynamicContent data = DynamicContent();
+    await tester.pumpWidget(
+      RemoteWidget(
+        runtime: runtime1,
+        data: data,
+        widget: const FullyQualifiedWidgetName(
+            LibraryName(<String>['test']), 'root'),
+      ),
+    );
+    expect(find.byType(RemoteWidget), findsOneWidget);
+    expect(find.byType(Placeholder), findsOneWidget);
+    expect(find.byType(Container), findsNothing);
+
+    await tester.pumpWidget(
+      RemoteWidget(
+        runtime: runtime2,
+        data: data,
+        widget: const FullyQualifiedWidgetName(
+            LibraryName(<String>['test']), 'root'),
+      ),
+    );
+    expect(find.byType(RemoteWidget), findsOneWidget);
+    expect(find.byType(Placeholder), findsNothing);
+    expect(find.byType(Container), findsOneWidget);
+  });
+}
diff --git a/packages/rfw/test/runtime_test.dart b/packages/rfw/test/runtime_test.dart
new file mode 100644
index 0000000..0e3f476
--- /dev/null
+++ b/packages/rfw/test/runtime_test.dart
@@ -0,0 +1,1063 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This file is hand-formatted.
+
+import 'dart:convert';
+import 'dart:typed_data';
+
+import 'package:flutter/widgets.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:rfw/formats.dart' show parseDataFile, parseLibraryFile;
+import 'package:rfw/rfw.dart';
+
+void main() {
+  testWidgets('list lookup', (WidgetTester tester) async {
+    final Runtime runtime = Runtime()
+      ..update(const LibraryName(<String>['core']), createCoreWidgets());
+    final DynamicContent data = DynamicContent(<String, Object?>{
+      'list': <Object?>[ 0, 1, 2, 3, 4 ],
+    });
+    await tester.pumpWidget(
+      RemoteWidget(
+        runtime: runtime,
+        data: data,
+        widget: const FullyQualifiedWidgetName(LibraryName(<String>['test']), 'root'),
+      ),
+    );
+    expect(find.byType(RemoteWidget), findsOneWidget);
+    expect(tester.takeException().toString(), contains('Could not find remote widget named'));
+    expect(find.byType(ErrorWidget), findsOneWidget);
+
+    runtime.update(const LibraryName(<String>['test']), parseLibraryFile('''
+      import core;
+      widget root = Column(
+        children: [
+          ...for v in data.list: Text(text: v, textDirection: "ltr"),
+        ],
+      );
+    '''));
+    await tester.pump();
+    expect(find.byType(Text), findsNWidgets(5));
+  });
+
+  testWidgets('data updates', (WidgetTester tester) async {
+    int buildCount = 0;
+    int? lastValue;
+    final Runtime runtime = Runtime()
+      ..update(const LibraryName(<String>['core']), LocalWidgetLibrary(<String, LocalWidgetBuilder>{
+        'Test': (BuildContext context, DataSource source) {
+          buildCount += 1;
+          lastValue = source.v<int>(<Object>['value']);
+          return const SizedBox.shrink();
+        },
+      }));
+    final DynamicContent data = DynamicContent(<String, Object?>{
+      'list': <Object?>[
+        <String, Object?>{ 'a': 0 },
+        <String, Object?>{ 'a': 1 },
+        <String, Object?>{ 'a': 2 },
+      ],
+    });
+    await tester.pumpWidget(
+      RemoteWidget(
+        runtime: runtime,
+        data: data,
+        widget: const FullyQualifiedWidgetName(LibraryName(<String>['test']), 'root'),
+      ),
+    );
+    expect(tester.takeException().toString(), contains('Could not find remote widget named'));
+    expect(buildCount, 0);
+    expect(lastValue, isNull);
+
+    runtime.update(const LibraryName(<String>['test']), parseLibraryFile('''
+      import core;
+      widget root = Test(value: data.list.1.a);
+    '''));
+    await tester.pump();
+    expect(buildCount, 1);
+    expect(lastValue, 1);
+
+    data.update('list', <Object?>[
+      <String, Object?>{ 'a': 0 },
+      <String, Object?>{ 'a': 3 },
+      <String, Object?>{ 'a': 2 },
+    ]);
+    await tester.pump();
+    expect(buildCount, 2);
+    expect(lastValue, 3);
+
+    data.update('list', <Object?>[
+      <String, Object?>{ 'a': 1 },
+      <String, Object?>{ 'a': 3 },
+    ]);
+    await tester.pump();
+    expect(buildCount, 2);
+    expect(lastValue, 3);
+
+    data.update('list', <Object?>[
+      <String, Object?>{ 'a': 1 },
+      <String, Object?>{ },
+    ]);
+    await tester.pump();
+    expect(buildCount, 3);
+    expect(lastValue, null);
+
+    data.update('list', <Object?>[
+      <String, Object?>{ 'a': 1 },
+    ]);
+    await tester.pump();
+    expect(buildCount, 3);
+    expect(lastValue, null);
+  });
+
+  testWidgets('$RemoteFlutterWidgetsException', (WidgetTester tester) async {
+    expect(const RemoteFlutterWidgetsException('test').toString(), 'test');
+  });
+
+  testWidgets('deepClone', (WidgetTester tester) async {
+    final Map<String, Object> map = <String, Object>{
+      'outer': <String, Object>{
+        'inner': true,
+      }
+    };
+    expect(identical(deepClone(map), map), isFalse);
+    expect(deepClone(map), equals(map));
+  });
+
+  testWidgets('updateText, updateBinary, clearLibraries', (WidgetTester tester) async {
+    final Runtime runtime = Runtime()
+      ..update(const LibraryName(<String>['core']), createCoreWidgets());
+    final DynamicContent data = DynamicContent();
+    await tester.pumpWidget(
+      RemoteWidget(
+        runtime: runtime,
+        data: data,
+        widget: const FullyQualifiedWidgetName(LibraryName(<String>['test']), 'root'),
+      ),
+    );
+    expect(find.byType(RemoteWidget), findsOneWidget);
+    expect(tester.takeException().toString(), contains('Could not find remote widget named'));
+    expect(find.byType(ErrorWidget), findsOneWidget);
+
+    runtime.update(const LibraryName(<String>['test']), parseLibraryFile('''
+      import core;
+      widget root = ColoredBox(color: 0xFF000000);
+    '''));
+    await tester.pump();
+    expect(tester.widget<ColoredBox>(find.byType(ColoredBox)).color, const Color(0xFF000000));
+
+    runtime.update(const LibraryName(<String>['test']), decodeLibraryBlob(encodeLibraryBlob(parseLibraryFile('''
+      import core;
+      widget root = ColoredBox(color: 0xFF000001);
+    '''))));
+    await tester.pump();
+    expect(tester.widget<ColoredBox>(find.byType(ColoredBox)).color, const Color(0xFF000001));
+
+    runtime.clearLibraries();
+    await tester.pump();
+    expect(find.byType(RemoteWidget), findsOneWidget);
+    expect(tester.takeException().toString(), contains('Could not find remote widget named'));
+    expect(find.byType(ErrorWidget), findsOneWidget);
+  });
+
+  testWidgets('Runtime cached build', (WidgetTester tester) async {
+    final Runtime runtime = Runtime()
+      ..update(const LibraryName(<String>['core']), createCoreWidgets());
+    final DynamicContent data = DynamicContent();
+    await tester.pumpWidget(
+      RemoteWidget(
+        runtime: runtime,
+        data: data,
+        widget: const FullyQualifiedWidgetName(LibraryName(<String>['core']), 'Placeholder'),
+      ),
+    );
+
+    expect(find.byType(Placeholder), findsOneWidget);
+    await tester.pumpWidget(
+      RemoteWidget(
+        runtime: runtime,
+        data: data,
+        widget: const FullyQualifiedWidgetName(LibraryName(<String>['core']), 'SizedBoxShrink'),
+      ),
+    );
+    expect(find.byType(Placeholder), findsNothing);
+    await tester.pumpWidget(
+      RemoteWidget(
+        runtime: runtime,
+        data: data,
+        widget: const FullyQualifiedWidgetName(LibraryName(<String>['core']), 'Placeholder'),
+      ),
+    );
+    expect(find.byType(Placeholder), findsOneWidget);
+  });
+
+  testWidgets('Import loops', (WidgetTester tester) async {
+    final Runtime runtime = Runtime()
+      ..update(const LibraryName(<String>['a']), parseLibraryFile('''
+        import b;
+      '''))
+      ..update(const LibraryName(<String>['b']), parseLibraryFile('''
+        import a;
+      '''));
+    final DynamicContent data = DynamicContent();
+    await tester.pumpWidget(
+      RemoteWidget(
+        runtime: runtime,
+        data: data,
+        widget: const FullyQualifiedWidgetName(LibraryName(<String>['a']), 'widget'),
+      ),
+    );
+    expect(tester.takeException().toString(), 'Library a indirectly depends on itself via b which depends on a.');
+  });
+
+  testWidgets('Import loops', (WidgetTester tester) async {
+    final Runtime runtime = Runtime()
+      ..update(const LibraryName(<String>['a']), parseLibraryFile('''
+        import a;
+      '''));
+    final DynamicContent data = DynamicContent();
+    await tester.pumpWidget(
+      RemoteWidget(
+        runtime: runtime,
+        data: data,
+        widget: const FullyQualifiedWidgetName(LibraryName(<String>['a']), 'widget'),
+      ),
+    );
+    expect(tester.takeException().toString(), 'Library a depends on itself.');
+  });
+
+  testWidgets('Missing libraries in import', (WidgetTester tester) async {
+    final Runtime runtime = Runtime()
+      ..update(const LibraryName(<String>['a']), parseLibraryFile('''
+        import b;
+      '''));
+    final DynamicContent data = DynamicContent();
+    await tester.pumpWidget(
+      RemoteWidget(
+        runtime: runtime,
+        data: data,
+        widget: const FullyQualifiedWidgetName(LibraryName(<String>['a']), 'widget'),
+      ),
+    );
+    expect(tester.takeException().toString(), contains('Could not find remote widget named'));
+    expect(tester.widget<ErrorWidget>(find.byType(ErrorWidget)).message, 'Could not find remote widget named widget in a, possibly because some dependencies were missing: b');
+  });
+
+  testWidgets('Missing libraries in specified widget', (WidgetTester tester) async {
+    final Runtime runtime = Runtime();
+    final DynamicContent data = DynamicContent();
+    await tester.pumpWidget(
+      RemoteWidget(
+        runtime: runtime,
+        data: data,
+        widget: const FullyQualifiedWidgetName(LibraryName(<String>['a']), 'widget'),
+      ),
+    );
+    expect(tester.takeException().toString(), contains('Could not find remote widget named'));
+    expect(tester.widget<ErrorWidget>(find.byType(ErrorWidget)).message, 'Could not find remote widget named widget in a, possibly because some dependencies were missing: a');
+  });
+
+  testWidgets('Missing libraries in import via dependency', (WidgetTester tester) async {
+    final Runtime runtime = Runtime()
+      ..update(const LibraryName(<String>['a']), parseLibraryFile('''
+        import b;
+        widget widget = test();
+      '''));
+    final DynamicContent data = DynamicContent();
+    await tester.pumpWidget(
+      RemoteWidget(
+        runtime: runtime,
+        data: data,
+        widget: const FullyQualifiedWidgetName(LibraryName(<String>['a']), 'widget'),
+      ),
+    );
+    expect(tester.takeException().toString(), contains('Could not find remote widget named'));
+    expect(tester.widget<ErrorWidget>(find.byType(ErrorWidget)).message, 'Could not find remote widget named test in a, possibly because some dependencies were missing: b');
+  });
+
+  testWidgets('Missing widget', (WidgetTester tester) async {
+    final Runtime runtime = Runtime()
+      ..update(const LibraryName(<String>['a']), parseLibraryFile(''));
+    final DynamicContent data = DynamicContent();
+    await tester.pumpWidget(
+      RemoteWidget(
+        runtime: runtime,
+        data: data,
+        widget: const FullyQualifiedWidgetName(LibraryName(<String>['a']), 'widget'),
+      ),
+    );
+    expect(tester.takeException().toString(), contains('Could not find remote widget named'));
+    expect(tester.widget<ErrorWidget>(find.byType(ErrorWidget)).message, 'Could not find remote widget named widget in a.');
+  });
+
+  testWidgets('Runtime', (WidgetTester tester) async {
+    final Runtime runtime = Runtime()
+      ..update(const LibraryName(<String>['core']), createCoreWidgets());
+    final DynamicContent data = DynamicContent();
+    await tester.pumpWidget(
+      RemoteWidget(
+        runtime: runtime,
+        data: data,
+        widget: const FullyQualifiedWidgetName(LibraryName(<String>['test']), 'root'),
+      ),
+    );
+    expect(tester.takeException().toString(), contains('Could not find remote widget named'));
+
+    runtime.update(const LibraryName(<String>['test']), parseLibraryFile('''
+      import core;
+      widget root { level: 0 } = inner(level: state.level);
+      widget inner { level: 1 } = ColoredBox(color: args.level);
+    '''));
+    await tester.pump();
+    expect(tester.widget<ColoredBox>(find.byType(ColoredBox)).color, const Color(0x00000000));
+
+    runtime.update(const LibraryName(<String>['test']), parseLibraryFile('''
+      import core;
+      widget root { level: 0 } = inner(level: state.level);
+      widget inner { level: 1 } = ColoredBox(color: state.level);
+    '''));
+    await tester.pump();
+    expect(tester.widget<ColoredBox>(find.byType(ColoredBox)).color, const Color(0x00000001));
+  });
+
+  testWidgets('Runtime', (WidgetTester tester) async {
+    final Runtime runtime = Runtime()
+      ..update(const LibraryName(<String>['core']), createCoreWidgets());
+    final DynamicContent data = DynamicContent();
+    await tester.pumpWidget(
+      RemoteWidget(
+        runtime: runtime,
+        data: data,
+        widget: const FullyQualifiedWidgetName(LibraryName(<String>['test']), 'root'),
+      ),
+    );
+    expect(tester.takeException().toString(), contains('Could not find remote widget named'));
+
+    runtime.update(const LibraryName(<String>['test']), parseLibraryFile('''
+      import core;
+      widget root { level: 0 } = switch state.level {
+        0: ColoredBox(color: 2),
+      };
+    '''));
+    await tester.pump();
+    expect(tester.widget<ColoredBox>(find.byType(ColoredBox)).color, const Color(0x00000002));
+  });
+
+  testWidgets('Runtime', (WidgetTester tester) async {
+    final Runtime runtime = Runtime()
+      ..update(const LibraryName(<String>['core']), createCoreWidgets());
+    final DynamicContent data = DynamicContent();
+    await tester.pumpWidget(
+      RemoteWidget(
+        runtime: runtime,
+        data: data,
+        widget: const FullyQualifiedWidgetName(LibraryName(<String>['test']), 'root'),
+      ),
+    );
+    expect(tester.takeException().toString(), contains('Could not find remote widget named'));
+
+    runtime.update(const LibraryName(<String>['test']), parseLibraryFile('''
+      import core;
+      widget root { level: 0 } = GestureDetector(
+        onTap: set state.level = 1,
+        child: ColoredBox(color: state.level),
+      );
+    '''));
+    await tester.pump();
+    expect(tester.widget<ColoredBox>(find.byType(ColoredBox)).color, const Color(0x00000000));
+    await tester.tap(find.byType(ColoredBox));
+    await tester.pump();
+    expect(tester.widget<ColoredBox>(find.byType(ColoredBox)).color, const Color(0x00000001));
+  });
+
+  testWidgets('DynamicContent', (WidgetTester tester) async {
+    final Runtime runtime = Runtime()
+      ..update(const LibraryName(<String>['core']), createCoreWidgets());
+    final DynamicContent data = DynamicContent();
+    await tester.pumpWidget(
+      RemoteWidget(
+        runtime: runtime,
+        data: data,
+        widget: const FullyQualifiedWidgetName(LibraryName(<String>['test']), 'root'),
+      ),
+    );
+    expect(tester.takeException().toString(), contains('Could not find remote widget named'));
+
+    runtime.update(const LibraryName(<String>['test']), parseLibraryFile('''
+      import core;
+      widget root = ColoredBox(color: data.color.value);
+    '''));
+    await tester.pump();
+    expect(tester.widget<ColoredBox>(find.byType(ColoredBox)).color, const Color(0xFF000000));
+
+    data.update('color', json.decode('{"value":1}'));
+    await tester.pump();
+    expect(tester.widget<ColoredBox>(find.byType(ColoredBox)).color, const Color(0x00000001));
+
+    data.update('color', parseDataFile('{value:2}'));
+    await tester.pump();
+    expect(tester.widget<ColoredBox>(find.byType(ColoredBox)).color, const Color(0x00000002));
+
+    data.update('color', decodeDataBlob(Uint8List.fromList(<int>[
+      0xFE, 0x52, 0x57, 0x44, // signature
+      0x07, // data is a map
+      0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ...which has one key
+      0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ...which has five letters
+      0x76, 0x61, 0x6c, 0x75, 0x65, // ...which are "value"
+      0x02, // and the value is an integer
+      0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ...which is the number 2
+    ])));
+    await tester.pump();
+    expect(tester.widget<ColoredBox>(find.byType(ColoredBox)).color, const Color(0x00000002));
+  });
+
+  testWidgets('DynamicContent', (WidgetTester tester) async {
+    final DynamicContent data = DynamicContent(<String, Object?>{'hello': 'world'});
+    expect(data.toString(), 'DynamicContent({hello: world})');
+  });
+
+  testWidgets('binding loop variables', (WidgetTester tester) async {
+    final Runtime runtime = Runtime()
+      ..update(const LibraryName(<String>['core']), createCoreWidgets());
+    final DynamicContent data = DynamicContent(<String, Object?>{
+      'list': <Object?>[
+        <String, Object?>{
+          'a': <String, Object?>{ 'b': 0xEE },
+          'c': <Object?>[ 0xDD ],
+        },
+      ],
+    });
+    final List<String> eventLog = <String>[];
+    await tester.pumpWidget(
+      RemoteWidget(
+        runtime: runtime,
+        data: data,
+        widget: const FullyQualifiedWidgetName(LibraryName(<String>['test']), 'root'),
+        onEvent: (String eventName, DynamicMap eventArguments) {
+          eventLog.add('$eventName $eventArguments');
+        },
+      ),
+    );
+    expect(find.byType(RemoteWidget), findsOneWidget);
+    expect(tester.takeException().toString(), contains('Could not find remote widget named'));
+    expect(find.byType(ErrorWidget), findsOneWidget);
+
+    runtime.update(const LibraryName(<String>['test']), parseLibraryFile('''
+      import core;
+      widget verify = ColoredBox(color: args.value.0.q.0);
+      widget root = verify(
+        value: [
+          ...for v in data.list: {
+            q: [ v.a.b ],
+          },
+        ],
+      );
+    '''));
+    await tester.pump();
+    expect(tester.widget<ColoredBox>(find.byType(ColoredBox)).color, const Color(0x000000EE));
+
+    runtime.update(const LibraryName(<String>['test']), parseLibraryFile('''
+      import core;
+      widget verify = ColoredBox(color: args.value.0.q.0);
+      widget root = verify(
+        value: [
+          ...for v in data.list: {
+            q: [ ...for w in v.c: w ],
+          },
+        ],
+      );
+    '''));
+    await tester.pump();
+    expect(tester.widget<ColoredBox>(find.byType(ColoredBox)).color, const Color(0x000000DD));
+
+    runtime.update(const LibraryName(<String>['test']), parseLibraryFile('''
+      import core;
+      widget verify = ColoredBox(color: args.value.0.q);
+      widget root = verify(
+        value: [
+          ...for v in data.list: {
+            q: switch v.a.b {
+              0xEE: 0xCC,
+              default: 0xFF,
+            },
+          },
+        ],
+      );
+    '''));
+    await tester.pump();
+    expect(tester.widget<ColoredBox>(find.byType(ColoredBox)).color, const Color(0x000000CC));
+
+    runtime.update(const LibraryName(<String>['test']), parseLibraryFile('''
+      import core;
+      widget verify { state: true } = ColoredBox(color: args.value.c.0);
+      widget remote = SizedBox(child: args.corn.0);
+      widget root = remote(
+        corn: [
+          ...for v in data.list:
+            verify(value: v),
+        ],
+      );
+    '''));
+    await tester.pump();
+    expect(tester.widget<ColoredBox>(find.byType(ColoredBox)).color, const Color(0x000000DD));
+
+    runtime.update(const LibraryName(<String>['test']), parseLibraryFile('''
+      import core;
+      widget verify { state: true } = ColoredBox(color: args.value);
+      widget remote = SizedBox(child: args.corn.0);
+      widget root = remote(
+        corn: [
+          ...for v in data.list:
+            verify(value: switch v.c.0 {
+              0: 0xFF000000,
+              0xDD: 0xFF0D0D0D,
+              default: v,
+            }),
+        ],
+      );
+    '''));
+    await tester.pump();
+    expect(tester.widget<ColoredBox>(find.byType(ColoredBox)).color, const Color(0xFF0D0D0D));
+
+    runtime.update(const LibraryName(<String>['test']), parseLibraryFile('''
+      import core;
+      widget verify { state: true } = switch args.value.c.0 {
+        0xDD: ColoredBox(color: 0xFF0D0D0D),
+        default: ColoredBox(color: args.value),
+      };
+      widget remote = SizedBox(child: args.corn.0);
+      widget root = remote(
+        corn: [
+          ...for v in data.list:
+            verify(value: v),
+        ],
+      );
+    '''));
+    await tester.pump();
+    expect(tester.widget<ColoredBox>(find.byType(ColoredBox)).color, const Color(0xFF0D0D0D));
+
+    runtime.update(const LibraryName(<String>['test']), parseLibraryFile('''
+      import core;
+      widget verify { state: true } = GestureDetector(
+        onTap: event 'test' { test: args.value.a.b },
+        child: ColoredBox(),
+      );
+      widget remote = SizedBox(child: args.corn.0);
+      widget root = remote(
+        corn: [
+          ...for v in data.list:
+            verify(value: v),
+        ],
+      );
+    '''));
+    expect(eventLog, isEmpty);
+    await tester.pump();
+    await tester.tap(find.byType(ColoredBox));
+    expect(eventLog, <String>['test {test: ${0xEE}}']);
+    eventLog.clear();
+
+    runtime.update(const LibraryName(<String>['test']), parseLibraryFile('''
+      import core;
+      widget verify { state: 0x00 } = GestureDetector(
+        onTap: set state.state = args.value.a.b,
+        child: ColoredBox(color: switch state.state {
+          0x00: 0xFF000001,
+          0xEE: 0xFF000002,
+        }),
+      );
+      widget remote = SizedBox(child: args.corn.0);
+      widget root = remote(
+        corn: [
+          ...for v in data.list:
+            verify(value: v),
+        ],
+      );
+    '''));
+    await tester.pump();
+    expect(tester.widget<ColoredBox>(find.byType(ColoredBox)).color, const Color(0xFF000001));
+    await tester.tap(find.byType(ColoredBox));
+    await tester.pump();
+    expect(tester.widget<ColoredBox>(find.byType(ColoredBox)).color, const Color(0xFF000002));
+
+  });
+
+  testWidgets('list lookup of esoteric values', (WidgetTester tester) async {
+    final Runtime runtime = Runtime()
+      ..update(const LibraryName(<String>['core']), createCoreWidgets());
+    final DynamicContent data = DynamicContent();
+    await tester.pumpWidget(
+      RemoteWidget(
+        runtime: runtime,
+        data: data,
+        widget: const FullyQualifiedWidgetName(LibraryName(<String>['test']), 'root'),
+      ),
+    );
+    expect(tester.takeException().toString(), contains('Could not find remote widget named'));
+
+    runtime.update(const LibraryName(<String>['test']), parseLibraryFile('''
+      import core;
+      widget root = test(list: ['A'], loop: [...for b in ["B", "C"]: b]);
+      widget test = Text(
+        textDirection: "ltr",
+        text: [
+          '>',
+          ...for a in args.list: a,
+          ...for b in args.loop: b,
+          '<',
+        ],
+      );
+    '''));
+    await tester.pump();
+    expect(find.text('>ABC<'), findsOneWidget);
+
+    runtime.update(const LibraryName(<String>['test']), parseLibraryFile('''
+      import core;
+      widget root = Text(
+        textDirection: "ltr",
+        text: [
+          '>',
+          ...for a in root(): a,
+          '<',
+        ],
+      );
+    '''));
+    await tester.pump();
+    expect(find.text('><'), findsOneWidget);
+
+    runtime.update(const LibraryName(<String>['test']), parseLibraryFile('''
+      import core;
+      widget root = test(list: [test()]);
+      widget test = Text(
+        textDirection: "ltr",
+        text: [
+          '>',
+          ...for a in args.list: a,
+          '<',
+        ],
+      );
+    '''));
+    await tester.pump();
+    expect(find.text('><'), findsOneWidget);
+
+    runtime.update(const LibraryName(<String>['test']), parseLibraryFile('''
+      import core;
+      widget root { list: [ 0x01 ] } = GestureDetector(
+        onTap: set state.list = [ 0x02, 0x03 ],
+        child: Column(
+          children: [
+            ...for v in state.list:
+              SizedBox(height: 10.0, width: 10.0, child: ColoredBox(color: v)),
+          ],
+        ),
+      );
+    '''));
+    await tester.pump();
+    expect(tester.widget<ColoredBox>(find.byType(ColoredBox)).color, const Color(0x00000001));
+    await tester.tap(find.byType(ColoredBox));
+    await tester.pump();
+    expect(tester.firstWidget<ColoredBox>(find.byType(ColoredBox)).color, const Color(0x00000002));
+    expect(find.byType(ColoredBox), findsNWidgets(2));
+
+    runtime.update(const LibraryName(<String>['test']), parseLibraryFile('''
+      import core;
+      widget root = Column(
+        children: [
+          ...for v in switch 0 {
+            0: [ColoredBox(color: 0x00000001)],
+            default: [ColoredBox(color: 0xFFFF0000)],
+          }: v,
+        ],
+      );
+    '''));
+    await tester.pump();
+    expect(tester.widget<ColoredBox>(find.byType(ColoredBox)).color, const Color(0x00000001));
+
+    runtime.update(const LibraryName(<String>['test']), parseLibraryFile('''
+      import core;
+      widget root = Column(
+        children: [
+          ...for v in {
+            a: [ColoredBox(color: 0x00000001)],
+            b: [ColoredBox(color: 0xFFFF0000)],
+          }: v,
+        ],
+      );
+    '''));
+    await tester.pump();
+    expect(find.byType(ColoredBox), findsNothing);
+
+    runtime.update(const LibraryName(<String>['test']), parseLibraryFile('''
+      import core;
+      widget root = test(
+        list: [...for w in [ColoredBox(color: 0xFF00FF00)]: w],
+      );
+      widget test = Column(
+        children: [
+          ...for v in args.list: v,
+        ],
+      );
+    '''));
+    await tester.pump();
+    expect(tester.widget<ColoredBox>(find.byType(ColoredBox)).color, const Color(0xFF00FF00));
+  });
+
+  testWidgets('data lookup', (WidgetTester tester) async {
+    final Runtime runtime = Runtime()
+      ..update(const LibraryName(<String>['core']), createCoreWidgets());
+    final DynamicContent data = DynamicContent(<String, Object?>{
+      'map': <String, Object?>{ 'list': <Object?>[ 0xAB ] },
+    });
+    await tester.pumpWidget(
+      RemoteWidget(
+        runtime: runtime,
+        data: data,
+        widget: const FullyQualifiedWidgetName(LibraryName(<String>['test']), 'root'),
+      ),
+    );
+    expect(tester.takeException().toString(), contains('Could not find remote widget named'));
+
+    runtime.update(const LibraryName(<String>['test']), parseLibraryFile('''
+      import core;
+      widget root = test(list: data.map.list);
+      widget test = ColoredBox(color: args.list.0);
+    '''));
+    await tester.pump();
+    expect(tester.widget<ColoredBox>(find.byType(ColoredBox)).color, const Color(0x000000AB));
+  });
+
+  testWidgets('args lookup', (WidgetTester tester) async {
+    final Runtime runtime = Runtime()
+      ..update(const LibraryName(<String>['core']), createCoreWidgets());
+    final DynamicContent data = DynamicContent();
+    await tester.pumpWidget(
+      RemoteWidget(
+        runtime: runtime,
+        data: data,
+        widget: const FullyQualifiedWidgetName(LibraryName(<String>['test']), 'root'),
+      ),
+    );
+    expect(tester.takeException().toString(), contains('Could not find remote widget named'));
+
+    runtime.update(const LibraryName(<String>['test']), parseLibraryFile('''
+      import core;
+      widget root = test1(map: { 'list': [ 0xAC ] });
+      widget test1 = test2(list: args.map.list);
+      widget test2 = ColoredBox(color: args.list.0);
+    '''));
+    await tester.pump();
+    expect(tester.widget<ColoredBox>(find.byType(ColoredBox)).color, const Color(0x000000AC));
+  });
+
+  testWidgets('state lookup', (WidgetTester tester) async {
+    final Runtime runtime = Runtime()
+      ..update(const LibraryName(<String>['core']), createCoreWidgets());
+    final DynamicContent data = DynamicContent();
+    await tester.pumpWidget(
+      RemoteWidget(
+        runtime: runtime,
+        data: data,
+        widget: const FullyQualifiedWidgetName(LibraryName(<String>['test']), 'root'),
+      ),
+    );
+    expect(tester.takeException().toString(), contains('Could not find remote widget named'));
+
+    runtime.update(const LibraryName(<String>['test']), parseLibraryFile('''
+      import core;
+      widget root { map: { 'list': [ 0xAD ] } } = test(list: state.map.list);
+      widget test = ColoredBox(color: args.list.0);
+    '''));
+    await tester.pump();
+    expect(tester.widget<ColoredBox>(find.byType(ColoredBox)).color, const Color(0x000000AD));
+  });
+
+  testWidgets('switch', (WidgetTester tester) async {
+    final Runtime runtime = Runtime()
+      ..update(const LibraryName(<String>['core']), createCoreWidgets());
+    final DynamicContent data = DynamicContent();
+    await tester.pumpWidget(
+      RemoteWidget(
+        runtime: runtime,
+        data: data,
+        widget: const FullyQualifiedWidgetName(LibraryName(<String>['test']), 'root'),
+      ),
+    );
+    expect(tester.takeException().toString(), contains('Could not find remote widget named'));
+
+    runtime.update(const LibraryName(<String>['test']), parseLibraryFile('''
+      import core;
+      widget root = ColoredBox(color: switch data.a.b {
+        0: 0x11111111,
+        default: 0x22222222,
+      });
+    '''));
+    data.update('a', parseDataFile('{ b: 1 }'));
+    await tester.pump();
+    expect(tester.widget<ColoredBox>(find.byType(ColoredBox)).color, const Color(0x22222222));
+    data.update('a', parseDataFile('{ b: 0 }'));
+    await tester.pump();
+    expect(tester.widget<ColoredBox>(find.byType(ColoredBox)).color, const Color(0x11111111));
+
+    runtime.update(const LibraryName(<String>['test']), parseLibraryFile('''
+      import core;
+      widget root = switch true {};
+    '''));
+    await tester.pump();
+    expect(tester.takeException().toString(), 'Switch in test:root did not resolve to a widget (got <missing>).');
+  });
+
+  testWidgets('events with arguments', (WidgetTester tester) async {
+    final Runtime runtime = Runtime()
+      ..update(const LibraryName(<String>['core']), createCoreWidgets());
+    final DynamicContent data = DynamicContent();
+    final List<String> eventLog = <String>[];
+    await tester.pumpWidget(
+      RemoteWidget(
+        runtime: runtime,
+        data: data,
+        widget: const FullyQualifiedWidgetName(LibraryName(<String>['test']), 'root'),
+        onEvent: (String eventName, DynamicMap eventArguments) {
+          eventLog.add('$eventName $eventArguments');
+        },
+      ),
+    );
+    expect(tester.takeException().toString(), contains('Could not find remote widget named'));
+
+    runtime.update(const LibraryName(<String>['test']), parseLibraryFile('''
+      import core;
+      widget root = GestureDetector(
+        onTap: event 'tap' {
+          list: [...for a in [0,1]: a],
+        },
+        child: ColoredBox(),
+      );
+    '''));
+    await tester.pump();
+    expect(tester.widget<ColoredBox>(find.byType(ColoredBox)).color, const Color(0xFF000000));
+    expect(eventLog, isEmpty);
+    await tester.tap(find.byType(ColoredBox));
+    expect(eventLog, <String>['tap {list: [0, 1]}']);
+    eventLog.clear();
+
+    runtime.update(const LibraryName(<String>['test']), parseLibraryFile('''
+      import core;
+      widget root = GestureDetector(
+        onTap: [ event 'tap' { a: 1 }, event 'tap' { a: 2 }, event 'final tap' { } ],
+        child: ColoredBox(),
+      );
+    '''));
+    await tester.pump();
+    expect(tester.widget<ColoredBox>(find.byType(ColoredBox)).color, const Color(0xFF000000));
+    expect(eventLog, isEmpty);
+    await tester.tap(find.byType(ColoredBox));
+    expect(eventLog, <String>['tap {a: 1}', 'tap {a: 2}', 'final tap {}']);
+    eventLog.clear();
+  });
+
+  testWidgets('_CurriedWidget toStrings', (WidgetTester tester) async {
+    final Runtime runtime = Runtime()
+      ..update(const LibraryName(<String>['core']), createCoreWidgets());
+    final DynamicContent data = DynamicContent();
+    runtime.update(const LibraryName(<String>['test']), parseLibraryFile('''
+      import core;
+      widget stateless = ColoredBox(color: 0xAA);
+      widget stateful { test: false } = ColoredBox(color: 0xBB);
+      widget switchy = switch true { default: ColoredBox(color: 0xCC) };
+    '''));
+    expect(
+      (runtime.build(
+        tester.element(find.byType(Container)),
+        const FullyQualifiedWidgetName(LibraryName(<String>['test']), 'stateless'),
+        data,
+        (String eventName, DynamicMap eventArguments) {},
+      ) as dynamic).curriedWidget.toString(),
+      'core:ColoredBox {} {color: 170}',
+    );
+    expect(
+      (runtime.build(
+        tester.element(find.byType(Container)),
+        const FullyQualifiedWidgetName(LibraryName(<String>['test']), 'stateful'),
+        data,
+        (String eventName, DynamicMap eventArguments) {},
+      ) as dynamic).curriedWidget.toString(),
+      'test:stateful {test: false} {} = core:ColoredBox {} {color: 187}',
+    );
+    expect(
+      (runtime.build(
+        tester.element(find.byType(Container)),
+        const FullyQualifiedWidgetName(LibraryName(<String>['test']), 'switchy'),
+        data,
+        (String eventName, DynamicMap eventArguments) {},
+      ) as dynamic).curriedWidget.toString(),
+      'test:switchy {} {} = switch true {null: core:ColoredBox {} {color: 204}}',
+    );
+  });
+
+  testWidgets('state setting', (WidgetTester tester) async {
+    final Runtime runtime = Runtime()
+      ..update(const LibraryName(<String>['core']), createCoreWidgets());
+    final DynamicContent data = DynamicContent();
+    await tester.pumpWidget(
+      RemoteWidget(
+        runtime: runtime,
+        data: data,
+        widget: const FullyQualifiedWidgetName(LibraryName(<String>['test']), 'root'),
+      ),
+    );
+    expect(tester.takeException().toString(), contains('Could not find remote widget named'));
+
+    runtime.update(const LibraryName(<String>['test']), parseLibraryFile('''
+      import core;
+      widget root { a: 0 } = GestureDetector(
+        onTap: set state.b = 0,
+        child: ColoredBox(),
+      );
+    '''));
+    await tester.pump();
+    await tester.tap(find.byType(ColoredBox));
+    expect(tester.takeException().toString(), 'b does not identify existing state.');
+
+    runtime.update(const LibraryName(<String>['test']), parseLibraryFile('''
+      import core;
+      widget root { a: 0 } = GestureDetector(
+        onTap: set state.0 = 0,
+        child: ColoredBox(),
+      );
+    '''));
+    await tester.pump();
+    await tester.tap(find.byType(ColoredBox));
+    expect(tester.takeException().toString(), '0 does not identify existing state.');
+
+    runtime.update(const LibraryName(<String>['test']), parseLibraryFile('''
+      import core;
+      widget root { a: [] } = GestureDetector(
+        onTap: set state.a.b = 0,
+        child: ColoredBox(),
+      );
+    '''));
+    await tester.pump();
+    await tester.tap(find.byType(ColoredBox));
+    expect(tester.takeException().toString(), 'a.b does not identify existing state.');
+
+    runtime.update(const LibraryName(<String>['test']), parseLibraryFile('''
+      import core;
+      widget root { a: [] } = GestureDetector(
+        onTap: set state.a.0 = 0,
+        child: ColoredBox(),
+      );
+    '''));
+    await tester.pump();
+    await tester.tap(find.byType(ColoredBox));
+    expect(tester.takeException().toString(), 'a.0 does not identify existing state.');
+
+    runtime.update(const LibraryName(<String>['test']), parseLibraryFile('''
+      import core;
+      widget root { a: true } = GestureDetector(
+        onTap: set state.a.0 = 0,
+        child: ColoredBox(),
+      );
+    '''));
+    await tester.pump();
+    await tester.tap(find.byType(ColoredBox));
+    expect(tester.takeException().toString(), 'a.0 does not identify existing state.');
+
+    runtime.update(const LibraryName(<String>['test']), parseLibraryFile('''
+      import core;
+      widget root { a: true } = GestureDetector(
+        onTap: set state.a.b = 0,
+        child: ColoredBox(),
+      );
+    '''));
+    await tester.pump();
+    await tester.tap(find.byType(ColoredBox));
+    expect(tester.takeException().toString(), 'a.b does not identify existing state.');
+
+    runtime.update(const LibraryName(<String>['test']), parseLibraryFile('''
+      import core;
+      widget root { a: { b: 0 } } = GestureDetector(
+        onTap: set state.a = 15,
+        child: ColoredBox(color: state.a),
+      );
+    '''));
+    await tester.pump();
+    expect(tester.widget<ColoredBox>(find.byType(ColoredBox)).color, const Color(0xFF000000));
+    await tester.tap(find.byType(ColoredBox));
+    await tester.pump();
+    expect(tester.widget<ColoredBox>(find.byType(ColoredBox)).color, const Color(0x0000000F));
+
+    runtime.update(const LibraryName(<String>['test']), parseLibraryFile('''
+      import core;
+      widget root { a: [ 0 ] } = GestureDetector(
+        onTap: set state.a = 10,
+        child: ColoredBox(color: state.a),
+      );
+    '''));
+    await tester.pump();
+    expect(tester.widget<ColoredBox>(find.byType(ColoredBox)).color, const Color(0xFF000000));
+    await tester.tap(find.byType(ColoredBox));
+    await tester.pump();
+    expect(tester.widget<ColoredBox>(find.byType(ColoredBox)).color, const Color(0x0000000A));
+
+    runtime.update(const LibraryName(<String>['test']), parseLibraryFile('''
+      import core;
+      widget root { a: [ [ 1 ] ] } = GestureDetector(
+        onTap: set state.a.0.0 = 11,
+        child: ColoredBox(color: state.a.0.0),
+      );
+    '''));
+    await tester.pump();
+    expect(tester.widget<ColoredBox>(find.byType(ColoredBox)).color, const Color(0x00000001));
+    await tester.tap(find.byType(ColoredBox));
+    await tester.pump();
+    expect(tester.widget<ColoredBox>(find.byType(ColoredBox)).color, const Color(0x0000000B));
+  });
+
+  testWidgets('DataSource', (WidgetTester tester) async {
+    final Runtime runtime = Runtime();
+    final DynamicContent data = DynamicContent();
+    final List<String> eventLog = <String>[];
+    await tester.pumpWidget(
+      RemoteWidget(
+        runtime: runtime,
+        data: data,
+        widget: const FullyQualifiedWidgetName(LibraryName(<String>['remote']), 'test'),
+        onEvent: (String name, DynamicMap arguments) {
+          eventLog.add('$name $arguments');
+        },
+      ),
+    );
+    expect(tester.takeException().toString(), contains('Could not find remote widget named'));
+
+    runtime.update(const LibraryName(<String>['local']), LocalWidgetLibrary(<String, LocalWidgetBuilder>{
+      'Test': (BuildContext context, DataSource source) {
+        expect(source.isList(<Object>['a']), isFalse);
+        expect(source.isList(<Object>['b']), isTrue);
+        expect(source.length(<Object>['b']), 1);
+        expect(source.child(<Object>['missing']), isA<ErrorWidget>());
+        expect(tester.takeException().toString(), 'Not a widget at [missing] (got <missing>) for local:Test.');
+        expect(source.childList(<Object>['a']), <Matcher>[isA<ErrorWidget>()]);
+        expect(tester.takeException().toString(), 'Not a widget list at [a] (got 0) for local:Test.');
+        expect(source.childList(<Object>['b']), <Matcher>[isA<ErrorWidget>()]);
+        expect(tester.takeException().toString(), 'Not a widget at [b] (got 1) for local:Test.');
+        expect(eventLog, isEmpty);
+        source.voidHandler(<Object>['callback'], <String, Object?>{ 'extra': 4, 'b': 3 })!();
+        expect(eventLog, <String>['e {a: 1, b: 3, extra: 4}']);
+        return const ColoredBox(color: Color(0xAABBCCDD));
+      },
+    }));
+    runtime.update(const LibraryName(<String>['remote']), parseLibraryFile('''
+      import local;
+      widget test = Test(a: 0, b: [1], callback: event 'e' { a: 1, b: 2 });
+    '''));
+    await tester.pump();
+    expect(tester.widget<ColoredBox>(find.byType(ColoredBox)).color, const Color(0xAABBCCDD));
+    bool tested = false;
+    tester.element(find.byType(ColoredBox)).visitAncestorElements((Element node) {
+      expect(node.toString(), equalsIgnoringHashCodes('_Widget(state: _WidgetState#00000(name: "local:Test"))'));
+      tested = true;
+      return false;
+    });
+    expect(tested, isTrue);
+  });
+}
diff --git a/packages/rfw/test/text_test.dart b/packages/rfw/test/text_test.dart
new file mode 100644
index 0000000..3ab1ca9
--- /dev/null
+++ b/packages/rfw/test/text_test.dart
@@ -0,0 +1,147 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This file is hand-formatted.
+
+import 'package:flutter_test/flutter_test.dart';
+import 'package:rfw/formats.dart';
+
+void main() {
+  testWidgets('empty parseDataFile', (WidgetTester tester) async {
+    final DynamicMap result = parseDataFile('{}');
+    expect(result, <String, Object?>{ });
+  });
+
+  testWidgets('empty parseLibraryFile', (WidgetTester tester) async {
+    final RemoteWidgetLibrary result = parseLibraryFile('');
+    expect(result.imports, isEmpty);
+    expect(result.widgets, isEmpty);
+  });
+
+  testWidgets('space parseDataFile', (WidgetTester tester) async {
+    final DynamicMap result = parseDataFile(' \n {} \n ');
+    expect(result, <String, Object?>{ });
+  });
+
+  testWidgets('space parseLibraryFile', (WidgetTester tester) async {
+    final RemoteWidgetLibrary result = parseLibraryFile(' \n ');
+    expect(result.imports, isEmpty);
+    expect(result.widgets, isEmpty);
+  });
+
+  testWidgets('error handling in parseDataFile', (WidgetTester tester) async {
+    void test(String input, String expectedMessage) {
+      try {
+        parseDataFile(input);
+        fail('parsing `$input` did not result in an error (expected "$expectedMessage").');
+      } on ParserException catch (e) {
+        expect('$e', expectedMessage);
+      }
+    }
+    test('', 'Expected symbol "{" but found <EOF> at line 1 column 0.');
+    test('}', 'Expected symbol "{" but found } at line 1 column 1.');
+    test('1', 'Expected symbol "{" but found 1 at line 1 column 1.');
+    test('1.0', 'Expected symbol "{" but found 1.0 at line 1 column 3.');
+    test('a', 'Expected symbol "{" but found a at line 1 column 1.');
+    test('"a"', 'Expected symbol "{" but found "a" at line 1 column 3.');
+    test('&', 'Unexpected character U+0026 ("&") at line 1 column 1.');
+    test('\t', 'Unexpected character U+0009 at line 1 column 1.');
+    test('{ a: 0, a: 0 }', 'Duplicate key "a" in map at line 1 column 10.');
+    test('{ a: 0; }', 'Expected symbol "}" but found ; at line 1 column 7.');
+    test('{ a: [ 0 ; ] }', 'Expected comma but found ; at line 1 column 10.');
+    test('{ } x', 'Expected end of file but found x at line 1 column 5.');
+    test('{ a: a }', 'Unexpected a at line 1 column 7.');
+    test('{ ... }', 'Expected symbol "}" but found … at line 1 column 5.');
+    test('{ a: ... }', 'Unexpected … at line 1 column 8.');
+  });
+
+  testWidgets('valid values in parseDataFile', (WidgetTester tester) async {
+    expect(parseDataFile('{ }\n\n  \n\n'), <String, Object?>{ });
+    expect(parseDataFile('{ a: "b" }'), <String, Object?>{ 'a': 'b' });
+    expect(parseDataFile('{ a: [ "b", 9 ] }'), <String, Object?>{ 'a': <Object?>[ 'b', 9 ] });
+    expect(parseDataFile('{ a: { } }'), <String, Object?>{ 'a': <String, Object?>{ } });
+    expect(parseDataFile('{ a: 123.456e7 }'), <String, Object?>{ 'a': 123.456e7 });
+    expect(parseDataFile('{ a: true }'), <String, Object?>{ 'a': true });
+    expect(parseDataFile('{ a: false }'), <String, Object?>{ 'a': false });
+    expect(parseDataFile('{ "a": 0 }'), <String, Object?>{ 'a': 0 });
+    expect(parseDataFile('{ "a": null }'), <String, Object?>{ });
+  });
+
+  testWidgets('error handling in parseLibraryFile', (WidgetTester tester) async {
+    void test(String input, String expectedMessage) {
+      try {
+        parseLibraryFile(input);
+        fail('parsing `$input` did not result in an error (expected "$expectedMessage").');
+      } on ParserException catch (e) {
+        expect('$e', expectedMessage);
+      }
+    }
+    test('2', 'Expected keywords "import" or "widget", or end of file but found 2 at line 1 column 1.');
+    test('impor', 'Expected keywords "import" or "widget", or end of file but found impor at line 1 column 5.');
+    test('import', 'Expected string but found <EOF> at line 1 column 6.');
+    test('import 2', 'Expected string but found 2 at line 1 column 8.');
+    test('import foo', 'Expected symbol ";" but found <EOF> at line 1 column 10.');
+    test('import foo.', 'Expected string but found <EOF> at line 1 column 11.');
+    test('import foo,', 'Expected symbol ";" but found , at line 1 column 11.');
+    test('import foo+', 'Unexpected character U+002B ("+") inside identifier at line 1 column 11.');
+    test('import foo.1', 'Expected string but found 1 at line 1 column 12.');
+    test('import foo.+', 'Unexpected character U+002B ("+") after period at line 1 column 12.');
+    test('widget a = b(c: [ ...for args in []: "e" ]);', 'args is a reserved word at line 1 column 30.');
+    test('widget a = switch 0 { 0: a(), 0: b() };', 'Switch has duplicate cases for key 0 at line 1 column 32.');
+    test('widget a = switch 0 { default: a(), default: b() };', 'Switch has multiple default cases at line 1 column 44.');
+    test('widget a = b(c: args)', 'Expected symbol "." but found ) at line 1 column 21.');
+    test('widget a = b(c: args.=)', 'Unexpected = at line 1 column 22.');
+    test('widget a = b(c: [ ... ]);', 'Expected identifier but found ] at line 1 column 23.');
+    test('widget a = b(c: [ ...baa ]);', 'Expected for but found baa at line 1 column 25.');
+    test('widget a = 0;', 'Expected identifier but found 0 at line 1 column 13.');
+  });
+
+  testWidgets('parseLibraryFile: imports', (WidgetTester tester) async {
+    final RemoteWidgetLibrary result = parseLibraryFile('import foo.bar;');
+    expect(result.imports, hasLength(1));
+    expect(result.imports.single.toString(), 'import foo.bar;');
+    expect(result.widgets, isEmpty);
+  });
+
+  testWidgets('parseLibraryFile: loops', (WidgetTester tester) async {
+    final RemoteWidgetLibrary result = parseLibraryFile('widget a = b(c: [ ...for d in []: "e" ]);');
+    expect(result.imports, isEmpty);
+    expect(result.widgets, hasLength(1));
+    expect(result.widgets.single.toString(), 'widget a = b({c: [...for loop in []: e]});');
+  });
+
+  testWidgets('parseLibraryFile: switch', (WidgetTester tester) async {
+    expect(parseLibraryFile('widget a = switch 0 { 0: a() };').toString(), 'widget a = switch 0 {0: a({})};');
+    expect(parseLibraryFile('widget a = switch 0 { default: a() };').toString(), 'widget a = switch 0 {null: a({})};');
+    expect(parseLibraryFile('widget a = b(c: switch 1 { 2: 3 });').toString(), 'widget a = b({c: switch 1 {2: 3}});');
+  });
+
+  testWidgets('parseLibraryFile: references', (WidgetTester tester) async {
+    expect(parseLibraryFile('widget a = b(c: [...for d in []: d]);').toString(), 'widget a = b({c: [...for loop in []: loop0.]});');
+    expect(parseLibraryFile('widget a = b(c:args.foo.bar);').toString(), 'widget a = b({c: args.foo.bar});');
+    expect(parseLibraryFile('widget a = b(c:data.foo.bar);').toString(), 'widget a = b({c: data.foo.bar});');
+    expect(parseLibraryFile('widget a = b(c:state.foo.bar);').toString(), 'widget a = b({c: state.foo.bar});');
+    expect(parseLibraryFile('widget a = b(c: [...for d in []: d.bar]);').toString(), 'widget a = b({c: [...for loop in []: loop0.bar]});');
+    expect(parseLibraryFile('widget a = b(c:args.foo."bar");').toString(), 'widget a = b({c: args.foo.bar});');
+    expect(parseLibraryFile('widget a = b(c:data.foo."bar");').toString(), 'widget a = b({c: data.foo.bar});');
+    expect(parseLibraryFile('widget a = b(c:state.foo."bar");').toString(), 'widget a = b({c: state.foo.bar});');
+    expect(parseLibraryFile('widget a = b(c: [...for d in []: d."bar"]);').toString(), 'widget a = b({c: [...for loop in []: loop0.bar]});');
+    expect(parseLibraryFile('widget a = b(c:args.foo.9);').toString(), 'widget a = b({c: args.foo.9});');
+    expect(parseLibraryFile('widget a = b(c:data.foo.9);').toString(), 'widget a = b({c: data.foo.9});');
+    expect(parseLibraryFile('widget a = b(c:state.foo.9);').toString(), 'widget a = b({c: state.foo.9});');
+    expect(parseLibraryFile('widget a = b(c: [...for d in []: d.9]);').toString(), 'widget a = b({c: [...for loop in []: loop0.9]});');
+  });
+
+  testWidgets('parseLibraryFile: event handlers', (WidgetTester tester) async {
+    expect(parseLibraryFile('widget a = b(c: event "d" { });').toString(), 'widget a = b({c: event d {}});');
+    expect(parseLibraryFile('widget a = b(c: set state.d = 0);').toString(), 'widget a = b({c: set state.d = 0});');
+  });
+
+  testWidgets('parseLibraryFile: stateful widgets', (WidgetTester tester) async {
+    expect(parseLibraryFile('widget a {} = c();').toString(), 'widget a = c({});');
+    expect(parseLibraryFile('widget a {b: 0} = c();').toString(), 'widget a = c({});');
+    final RemoteWidgetLibrary result = parseLibraryFile('widget a {b: 0} = c();');
+    expect(result.widgets.single.initialState, <String, Object?>{'b': 0});
+  });
+}