[embedder] Workflow improvements.

- Add missing prerequisite step to get started.
- Support multiple examples with build_and_run script.
- Rename flutter_sample_app -> hello_flutter to try to make it more
  clear that it's the default flutter app for a new project.
- Add hello_dart and spinning_cube examples. hello_dart works if you
  run it in the session but doesn't work in headless mode. spinning_cube
  doesn't work at all right now.
- Move engine artifacts into a debug_x64 folder to prepare for release
  artifacts in a follow-up CL, which will be required for AOT support.
- Make the sync_engine_artifacts script fail on any error during
  the first part of the script.
- Remove unused infra scripts. They're not the recommended approach
  for setting up automated tests.

Bug: 46971
Change-Id: I79784b2959c44d17e18bdb6c95119ba540e0d1da
diff --git a/README.md b/README.md
index 17b2f05..4186e7a 100644
--- a/README.md
+++ b/README.md
@@ -29,6 +29,9 @@
     [[ -f "${HOME}/.ssh/fuchsia_authorized_keys" ]] || ssh-keygen -y -f "${HOME}/.ssh/fuchsia_ed25519" > "${HOME}/.ssh/fuchsia_authorized_keys"
    ```
 
+4. Set `$FUCHSIA_EMBEDDER_DIR` to your flutter-embedder.git checkout location,
+   for example `~/flutter-embedder`.
+
 ## Build and package the sample
 
 Now the repository is ready to build the sample.
@@ -49,15 +52,15 @@
    tools/ffx log
    ```
 
-3. Run the sample app component:
+3. Run an example app:
 
    ```sh
-   scripts/build_and_run_sample_app.sh
+   scripts/build_and_run_example.sh hello_flutter
    ```
 
-TODO(akbiggs): The app occasionally gets stuck on a loading screen
+**TODO(akbiggs): The app occasionally gets stuck on a loading screen
 instead of rendering. Re-running the app usually fixes it. We need to
-fix this.
+fix this.**
 
 ## Syncing engine artifacts
 
@@ -65,31 +68,33 @@
 libengine_flutter.so) that are used by the embedder to run Flutter apps. A script
 is provided for this workflow.
 
-### Prerequisites
+### Requirements
 
-1. You will [need to get the Flutter Engine source code](https://github.com/flutter/flutter/wiki/Setting-up-the-Engine-development-environment). **Note that this is not just cloning
-https://github.com/flutter/engine.**
-2. `$ENGINE_DIR` should be set to the `src` folder of your Flutter Engine checkout, for example
-`~/engine/src`.
-3. `$FUCHSIA_EMBEDDER_DIR` should be set to your flutter-embedder.git checkout, for example
-`~/flutter-embedder`.
-4. `$DEPOT_TOOLS` should be set to your [`depot_tools`](https://commondatastorage.googleapis.com/chrome-infra-docs/flat/depot_tools/docs/html/depot_tools_tutorial.html#_setting_up) location, for example `~/depot_tools`.
-5. You will need to `git stash` or `git commit` any local changes to the Flutter Engine or the
-script will fail.
+1. You will
+   [need to get the Flutter Engine source code](https://github.com/flutter/flutter/wiki/Setting-up-the-Engine-development-environment).
+   **Note that this is not just cloning https://github.com/flutter/engine.**
+2. Set `$ENGINE_DIR` to the `src` folder of your Flutter Engine checkout location,
+   for example `~/engine/src`.
+3. `$FUCHSIA_EMBEDDER_DIR` should be set to your flutter-embedder.git checkout
+   location, for example `~/flutter-embedder`.
+4. `$DEPOT_TOOLS` should be set to your
+    [`depot_tools`](https://commondatastorage.googleapis.com/chrome-infra-docs/flat/depot_tools/docs/html/depot_tools_tutorial.html#_setting_up)
+    location, for example `~/depot_tools`.
+5. You will need to `git stash` or `git commit` any local changes to the Flutter
+   Engine or the script will fail.
 
 ### Running the script
 
-You can run the script with a `flutter/engine` Git commit to sync the Engine artifacts to that
-revision.
+You can run the script with a `flutter/engine` Git commit to sync the Engine artifacts to
+that revision.
 
 ```sh
 scripts/sync_engine_artifacts_to_revision.sh <ENGINE_COMMIT_SHA>
 ```
 
-A common workflow is to sync your Engine commit to the Flutter tool in this repository. To do this:
+A common workflow is to sync your Engine commit to the Flutter tool in this repository,
+for example when updating the Flutter tool to a new version. To do this:
 
 ```sh
 scripts/sync_engine_artifacts_to_revision.sh $(cat $FUCHSIA_EMBEDDER_DIR/third_party/dart-pkg/internal/flutter/flutter/bin/internal/engine.version)
 ```
-
-
diff --git a/scripts/build_and_copy_engine_artifacts.sh b/scripts/build_and_copy_engine_artifacts.sh
index 255fb06..29b2b88 100755
--- a/scripts/build_and_copy_engine_artifacts.sh
+++ b/scripts/build_and_copy_engine_artifacts.sh
@@ -15,6 +15,8 @@
 #   2. $FUCHSIA_EMBEDDER_DIR is set to your flutter-embedder.git checkout.
 #   3. $DEPOT_TOOLS is set to your depot_tools directory.
 
+set -e # Fail on any error.
+
 # Returns true if colors are supported.
 function is-stderr-tty {
   [[ -t 2 ]]
@@ -29,11 +31,13 @@
   fi
 }
 
-echo-info "Building the Flutter Engine embedding for Fuchsia (libflutter_engine.so)..."
+echo-info "Building the debug Flutter Engine embedding for Fuchsia (libflutter_engine.so)..."
 "${ENGINE_DIR}"/flutter/tools/gn --fuchsia --embedder-for-target --unopt
-out_dir="${ENGINE_DIR}"/out/fuchsia_debug_unopt_x64
-"${DEPOT_TOOLS}"/ninja -C "${out_dir}"
+debug_out_dir="${ENGINE_DIR}"/out/fuchsia_debug_unopt_x64
+"${DEPOT_TOOLS}"/ninja -C "${debug_out_dir}" flutter/shell/platform/fuchsia
 
-echo-info "Copying Flutter Engine artifacts to ${FUCHSIA_EMBEDDER_DIR}/src/embedder..."
-cp "${out_dir}"/libflutter_engine.so "${FUCHSIA_EMBEDDER_DIR}"/src/embedder/engine/libflutter_engine.so
-cp "${ENGINE_DIR}"/flutter/shell/platform/embedder/embedder.h "${FUCHSIA_EMBEDDER_DIR}"/src/embedder/engine/embedder.h
+echo-info "Copying debug Flutter Engine artifacts to ${FUCHSIA_EMBEDDER_DIR}/src/embedder/engine/debug_x64..."
+cp "${debug_out_dir}"/libflutter_engine.so "${FUCHSIA_EMBEDDER_DIR}"/src/embedder/engine/debug_x64/libflutter_engine.so
+
+echo-info "Copying embedder.h to ${FUCHSIA_EMBEDDER_DIR}/src/embedder/engine..."
+cp "${ENGINE_DIR}"/flutter/shell/platform/embedder/embedder.h "${FUCHSIA_EMBEDDER_DIR}"/src/embedder/engine
diff --git a/scripts/build_and_run_sample_app.sh b/scripts/build_and_run_example.sh
similarity index 74%
rename from scripts/build_and_run_sample_app.sh
rename to scripts/build_and_run_example.sh
index fa0652c..523bdcf 100755
--- a/scripts/build_and_run_sample_app.sh
+++ b/scripts/build_and_run_example.sh
@@ -10,10 +10,35 @@
 # TODO(akbiggs): Port this workflow to Bazel.
 
 set -e # Exit if any program returns an error.
-app_name=flutter_sample_app
+
 far_debug_dir=/tmp/"${app_name}"_far_contents
 far_tool="${FUCHSIA_EMBEDDER_DIR}"/bazel-flutter-embedder/external/fuchsia_sdk/tools/x64/far
 
+# Parse arguments.
+headless=0
+app_name="hello_flutter"
+while [[ $# -gt 0 ]]; do
+  case $1 in
+    --headless)
+      echo "--headless apps will not work yet because there's no collection to run JIT components in."
+      headless=1
+      shift # past argument
+      ;;
+    *)
+      app_name="$1"
+      shift # past value
+      ;;
+  esac
+done
+
+session_args="-- --session"
+release_args=
+if [[ "${headless}" -ne 0 ]]
+then
+  session_args=
+  release_args="--release"
+fi
+
 function is-stderr-tty {
   [[ -t 2 ]]
 }
@@ -35,7 +60,7 @@
 
 echo-info "Running Flutter sample app."
 cd "${FUCHSIA_EMBEDDER_DIR}"
-bazel run --config=fuchsia_x64 //src/examples/"${app_name}":"${app_name}"_pkg.component -- --session
+bazel run --config=fuchsia_x64 //src/examples/"${app_name}":"${app_name}"_pkg.component ${session_args}
 
 echo-info "Package contents for debugging:"
 "${far_tool}" extract --archive="${FUCHSIA_EMBEDDER_DIR}"/bazel-bin/src/examples/"${app_name}"/"${app_name}".far --output="${far_debug_dir}"
diff --git a/scripts/infra/build.sh b/scripts/infra/build.sh
deleted file mode 100644
index 6a0af99..0000000
--- a/scripts/infra/build.sh
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/bin/bash
-
-# Copyright 2022 The Fuchsia 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 -e
-
-# This script is used by infra to validate the package can be built and tests
-# can pass.
-# TODO(akbiggs): Set up infra.
-
-readonly REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)"/..
-readonly BAZELISK_TOOL="${REPO_ROOT}/third_party/sdk-integration/tools/bazel"
-
-main() {
-  "$BAZELISK_TOOL" build --config=fuchsia_x64 //src/examples:examples_repository
-}
-
-main "$@"
diff --git a/scripts/infra/upload.sh b/scripts/infra/upload.sh
deleted file mode 100644
index 5b3fa0f..0000000
--- a/scripts/infra/upload.sh
+++ /dev/null
@@ -1,52 +0,0 @@
-#!/bin/bash
-
-# Copyright 2022 The Fuchsia 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 -e
-
-# This script is used by infra to validate the package can be built and tests
-# can pass.
-# TODO(akbiggs): Set up infra.
-
-readonly REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)"/..
-readonly BAZELISK_TOOL="${REPO_ROOT}/third_party/sdk-integration/tools/bazel"
-
-run_bazel() {
-  local bazel="${REPO_ROOT}/tools/bazel"
-  if [[ -z "$output_base" ]]; then
-    "$BAZELISK_TOOL" "$@"
-  else
-    "$BAZELISK_TOOL" --output_base "${output_base}" "$@"
-  fi
-}
-
-generate_upload_manifest() {
-  (
-    cd "${REPO_ROOT}"
-    run_bazel clean --expunge
-    if [[ -z "$version_number" ]]; then
-      run_bazel build --config=fuchsia_x64 //src:test_artifact_release
-    else
-      run_bazel build --config=fuchsia_x64 //src:test_artifact_release --@rules_fuchsia//fuchsia/private:build_version="${version_number}"
-    fi
-  ) || exit 1
-}
-
-main() {
-  while getopts ":ho:v:" opt; do
-    case ${opt} in
-      'h' | '?')
-        help
-        exit 1
-        ;;
-      'o') output_base=$OPTARG ;;
-      'v') version_number=$OPTARG ;;
-    esac
-  done
-
-  generate_upload_manifest
-}
-
-main "$@"
diff --git a/scripts/sync_engine_artifacts_to_revision.sh b/scripts/sync_engine_artifacts_to_revision.sh
index a3fe129..9a0e4cc 100755
--- a/scripts/sync_engine_artifacts_to_revision.sh
+++ b/scripts/sync_engine_artifacts_to_revision.sh
@@ -16,6 +16,8 @@
 #   3. $DEPOT_TOOLS is set to your depot_tools directory.
 #   4. You don't have any uncommited changes to your Engine code.
 
+set -e # Fail on any error.
+
 # Returns true if colors are supported.
 function is-stderr-tty {
   [[ -t 2 ]]
@@ -43,6 +45,10 @@
 echo-info "Syncing ${ENGINE_DIR} dependencies..."
 gclient sync -D
 
+# Stop failing on any error because grabbing fixes will fail
+# when the fixes are already present in the user's engine checkout.
+set +e
+
 # Get buildroot fix.
 # TODO(akbiggs): Submit this fix.
 buildroot_fix=c7deba3773ed645c29f4518cc3990bc02f0f53cd
@@ -94,9 +100,7 @@
 
 popd  # flutter
 
-# Fail on any error.
-# We start failing here because the previous commands throw errors even
-# when things are fine.
+# Start failing on any error again now that we're done grabbing fixes.
 set -e
 
 # Build and copy over the artifacts we need and update our
diff --git a/src/embedder/BUILD.bazel b/src/embedder/BUILD.bazel
index 5ac7ccd..845ae0f 100644
--- a/src/embedder/BUILD.bazel
+++ b/src/embedder/BUILD.bazel
@@ -28,7 +28,10 @@
     ],
     deps = [
         "//src/embedder/engine:embedder_header",
-        "//src/embedder/engine:libflutter_engine",
+        # TODO(akbiggs): Enable switching between debug and profile
+        # builds of libflutter_engine.so without manually editing this
+        # file.
+        "//src/embedder/engine/debug_x64:libflutter_engine",
         "@fuchsia_sdk//fidl/fuchsia.sysmem:fuchsia.sysmem_cc",
         "@fuchsia_sdk//fidl/fuchsia.ui.app:fuchsia.ui.app_cc",
         "@fuchsia_sdk//fidl/fuchsia.ui.composition:fuchsia.ui.composition_cc",
diff --git a/src/embedder/engine/BUILD.bazel b/src/embedder/engine/BUILD.bazel
index fe3f73e..711fe7a 100644
--- a/src/embedder/engine/BUILD.bazel
+++ b/src/embedder/engine/BUILD.bazel
@@ -4,11 +4,6 @@
 #
 # Artifacts from the Flutter Engine repository: https://github.com/flutter/engine
 
-load(
-    "@rules_fuchsia//fuchsia:defs.bzl",
-    "fuchsia_package_resource",
-)
-
 # ABI-stable interface for the Flutter embedder platform. Copied from the Flutter Engine.
 cc_library(
     name = "embedder_header",
@@ -16,26 +11,3 @@
     visibility = ["//src/embedder:__pkg__"],
 )
 
-# Shared library implementing the embedder.h header.
-#
-# TODO(akbiggs): This should come from a CIPD bucket instead.
-cc_library(
-    name = "libflutter_engine",
-    srcs = ["libflutter_engine.so"],
-    visibility = ["//src/embedder:__pkg__"],
-    deps = [
-        "@fuchsia_sdk//pkg/memfs",
-        "@fuchsia_sdk//pkg/vulkan",
-    ]
-)
-
-# Package resource to include libflutter_engine into a Fuchsia package's
-# libraries.
-# You must include this in your package's dependencies if your package's binary
-# has a dependency on `:libflutter_engine`.
-fuchsia_package_resource(
-    name = "libflutter_engine_pkg_resource",
-    visibility = ["//visibility:public"],
-    src = ":libflutter_engine.so",
-    dest = "lib/libflutter_engine.so",
-)
diff --git a/src/embedder/engine/README.md b/src/embedder/engine/README.md
index 239fc6d..ef32d32 100644
--- a/src/embedder/engine/README.md
+++ b/src/embedder/engine/README.md
@@ -2,18 +2,26 @@
 
 This folder contains artifacts from the [Flutter Engine repository](https://github.com/flutter/engine).
 
+## Updating the artifacts
+
+See ["Syncing Engine Artifacts" in the top-level `README.md`](https://fuchsia.googlesource.com/flutter-embedder#syncing-engine-artifacts).
+
 ## Code breakdown
 
-### `libflutter_engine.so`
+### `<runtime_mode>_<arch>/libflutter_engine.so`
 
-This is an unoptimized build of the Flutter embedder platform built for `OS_FUCHSIA`.
-This build contains a few changes that have not been landed into the Flutter Engine repository:
+These are builds of the Flutter embedder platform for `OS_FUCHSIA` with `--runtime-mode=<runtime_mode>` and `--cpu=<arch>`.
+
+These builds contain a few changes that have not been landed into the
+Flutter Engine repository:
 
 1. [Add `-fPIC` to Fuchsia builds](https://github.com/flutter/buildroot/pull/552).
 2. [Fix JIT snapshot support for the embedder platform](https://github.com/flutter/flutter/issues/100640).
+3. [Hack](https://github.com/flutter/engine/pull/33472) to workaround http://fxbug.dev/75282.
+4. [Add support for acquiring software surface to embedder API](https://github.com/flutter/engine/commit/dadf7a7b06b60f36fdce66759bfd74c01d74e27b).
 
-These changes should be landed and `libflutter_engine.so` should be pulled from a CIPD bucket
-instead.
+These changes should be landed and `libflutter_engine.so` should be pulled from
+a CIPD bucket instead.
 
 ### `embedder.h`
 
@@ -24,8 +32,3 @@
 
 The git hash of [Flutter Engine](https://github.com/flutter/engine) that the Engine artifacts come from.
 Currently, this is purely for developer reference and is not used by any tooling.
-
-## Updating the artifacts
-
-See ["Syncing Engine Artifacts" in the top-level `README.md`](https://fuchsia.googlesource.com/flutter-embedder#syncing-engine-artifacts).
-
diff --git a/src/embedder/engine/debug_x64/BUILD.bazel b/src/embedder/engine/debug_x64/BUILD.bazel
new file mode 100644
index 0000000..254bcef
--- /dev/null
+++ b/src/embedder/engine/debug_x64/BUILD.bazel
@@ -0,0 +1,36 @@
+# Copyright 2022 The Fuchsia Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+#
+# Artifacts from the Flutter Engine repository built in
+# debug mode for the x64 architecture: https://github.com/flutter/engine
+
+load(
+    "@rules_fuchsia//fuchsia:defs.bzl",
+    "fuchsia_package_resource",
+)
+
+# Shared library implementing the embedder.h header.
+#
+# TODO(akbiggs): This should come from a CIPD bucket instead.
+cc_library(
+    name = "libflutter_engine",
+    srcs = ["libflutter_engine.so"],
+    visibility = ["//src/embedder:__pkg__"],
+    deps = [
+        "@fuchsia_sdk//pkg/memfs",
+        "@fuchsia_sdk//pkg/vulkan",
+    ]
+)
+
+# Package resource to include libflutter_engine.so into a Fuchsia package's
+# libraries.
+#
+# You must include this in your package's dependencies if your package's binary
+# has a dependency on `:libflutter_engine`.
+fuchsia_package_resource(
+    name = "libflutter_engine_pkg_resource",
+    visibility = ["//visibility:public"],
+    src = ":libflutter_engine.so",
+    dest = "lib/libflutter_engine.so",
+)
diff --git a/src/embedder/engine/libflutter_engine.so b/src/embedder/engine/debug_x64/libflutter_engine.so
similarity index 100%
rename from src/embedder/engine/libflutter_engine.so
rename to src/embedder/engine/debug_x64/libflutter_engine.so
Binary files differ
diff --git a/src/examples/BUILD.bazel b/src/examples/BUILD.bazel
index 1e2a0d6..042fc1e 100644
--- a/src/examples/BUILD.bazel
+++ b/src/examples/BUILD.bazel
@@ -11,8 +11,8 @@
     name = "examples_repository",
     repo_name = "flutter-embedder-examples.com",
     deps = [
-        "//src/examples/flutter_sample_app:flutter_sample_app_pkg",
+        "//src/examples/hello_dart:hello_dart_pkg",
+        "//src/examples/hello_flutter:hello_flutter_pkg",
+        "//src/examples/spinning_cube:spinning_cube_pkg",
     ],
 )
-
-# TODO(akbiggs): Add fuchsia_artifact_release target (see fortune-teller repo).
diff --git a/src/examples/README.md b/src/examples/README.md
new file mode 100644
index 0000000..1b2d666
--- /dev/null
+++ b/src/examples/README.md
@@ -0,0 +1,55 @@
+# Flutter embedder examples
+
+These are example apps that can be used to manually
+test the behavior of the Flutter embedder.
+
+TODO(akbiggs): Write integration tests that automatically
+verify the behavior of these example apps on each commit.
+
+## How to run an example
+
+Follow the setup in the
+[root README](https://fuchsia.googlesource.com/flutter-embedder/+/refs/heads/main/README.md#requirements),
+then run from $FUCHSIA_EMBEDDER_DIR:
+
+```sh
+scripts/build_and_run_example.sh <example_folder_name>
+```
+
+For example:
+
+```sh
+scripts/build_and_run_example.sh hello_flutter
+```
+
+If the example is marked as `Headless` in the table below,
+run the example with the `--headless` flag to turn off rendering
+because the component does not support rendering (it is a Dart app
+with no UI instead of a Flutter app).
+For example:
+
+```sh
+scripts/build_and_run_example.sh hello_dart --headless
+```
+
+This script relies on the `fuchsia_package` BUILD rule for the
+example being called `<example_folder_name>_pkg`.
+
+**TODO(akbiggs): Migrate this workflow to Bazel and remove
+`build_and_run_example.sh`. The ideal workflow is just
+`blaze run //src/examples/my_example/my_example_pkg` because
+it's consistent with other platforms and discoverable.**
+
+**TODO(akbiggs): Headless examples will not work yet because
+there's no environment with the JIT allowlist to run them in.
+Find a suitable collection somewhere and run them there. For
+now, you can run without `--headless`, which will lead to
+an infinite loading spinner but run the app.**
+
+## Example catalog
+
+| Example       | Headless? | Desired behavior                   | Does it work?  |
+|---------------|-----------|------------------------------------|----------------|
+| hello_flutter | No        | Renders with clickable button      | Sort of (renders without text or input) |
+| hello_dart    | Yes       | Prints "Hello World!" to `ffx log` | Yes |
+| spinning_cube | No        | Renders animation without jank     | No (hangs) |
diff --git a/src/examples/flutter_sample_app/README.md b/src/examples/flutter_sample_app/README.md
deleted file mode 100644
index 2c33ffc..0000000
--- a/src/examples/flutter_sample_app/README.md
+++ /dev/null
@@ -1,16 +0,0 @@
-# flutter_sample_app
-
-A new Flutter project.
-
-## Getting Started
-
-This project is a starting point for a Flutter application.
-
-A few resources to get you started if this is your first Flutter project:
-
-- [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab)
-- [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook)
-
-For help getting started with Flutter, view our
-[online documentation](https://flutter.dev/docs), which offers tutorials,
-samples, guidance on mobile development, and a full API reference.
diff --git a/src/examples/hello_dart/.gitignore b/src/examples/hello_dart/.gitignore
new file mode 100644
index 0000000..3c8a157
--- /dev/null
+++ b/src/examples/hello_dart/.gitignore
@@ -0,0 +1,6 @@
+# Files and directories created by pub.
+.dart_tool/
+.packages
+
+# Conventional directory for build output.
+build/
diff --git a/src/examples/flutter_sample_app/BUILD.bazel b/src/examples/hello_dart/BUILD.bazel
similarity index 66%
copy from src/examples/flutter_sample_app/BUILD.bazel
copy to src/examples/hello_dart/BUILD.bazel
index 4520f59..0763319 100644
--- a/src/examples/flutter_sample_app/BUILD.bazel
+++ b/src/examples/hello_dart/BUILD.bazel
@@ -4,9 +4,9 @@
     "fuchsia_package",
 )
 
-# To rebuild the Flutter app with new changes:
-#   ~/flutter-embedder $ cd examples/flutter_sample_app
-#   flutter_sample_app $ flutter build bundle
+# To rebuild the app with new changes:
+#   ~/flutter-embedder $ cd examples/hello_dart
+#   .../hello_dart $ flutter build bundle
 #
 # TODO(akbiggs): Grab subdirectories and ensure their names are preserved
 # in the output package's data.
@@ -20,13 +20,12 @@
     ]),
 )
 
-# The embedder manifest specifies that the bundled assets for the Flutter application
+# The embedder manifest specifies that the bundled assets for the application
 # to run will be located at /pkg/data/flutter_assets. To create a component that
-# runs a specific Flutter app, we package the embedder manifest together with the
-# embedder binary and the assets for the specific Flutter app we want to run.
+# runs a specific app, we package the embedder manifest together with the
+# embedder binary and the assets for the specific app we want to run.
 #
-# TODO(akbiggs): After writing a few more example apps, simplify into a build rule
-# like flutter_application.
+# TODO(akbiggs): After writing a few more example apps, simplify into a build rule.
 fuchsia_component(
     name = "component",
     manifest = "//src/embedder:embedder_manifest",
@@ -39,11 +38,11 @@
 )
 
 fuchsia_package(
-    name = "flutter_sample_app_pkg",
-    package_name = "flutter_sample_app",
+    name = "hello_dart_pkg",
+    package_name = "hello_dart",
     visibility = ["//visibility:public"],
     deps = [
         ":component",
-        "//src/embedder/engine:libflutter_engine_pkg_resource",
+        "//src/embedder/engine/debug_x64:libflutter_engine_pkg_resource",
     ],
 )
diff --git a/src/examples/hello_dart/analysis_options.yaml b/src/examples/hello_dart/analysis_options.yaml
new file mode 100644
index 0000000..dee8927
--- /dev/null
+++ b/src/examples/hello_dart/analysis_options.yaml
@@ -0,0 +1,30 @@
+# This file configures the static analysis results for your project (errors,
+# warnings, and lints).
+#
+# This enables the 'recommended' set of lints from `package:lints`.
+# This set helps identify many issues that may lead to problems when running
+# or consuming Dart code, and enforces writing Dart using a single, idiomatic
+# style and format.
+#
+# If you want a smaller set of lints you can change this to specify
+# 'package:lints/core.yaml'. These are just the most critical lints
+# (the recommended set includes the core lints).
+# The core lints are also what is used by pub.dev for scoring packages.
+
+include: package:lints/recommended.yaml
+
+# Uncomment the following section to specify additional rules.
+
+# linter:
+#   rules:
+#     - camel_case_types
+
+# analyzer:
+#   exclude:
+#     - path/to/excluded/files/**
+
+# For more information about the core and recommended set of lints, see
+# https://dart.dev/go/core-lints
+
+# For additional information about configuring this file, see
+# https://dart.dev/guides/language/analysis-options
diff --git a/src/examples/hello_dart/lib/main.dart b/src/examples/hello_dart/lib/main.dart
new file mode 100644
index 0000000..62eb800
--- /dev/null
+++ b/src/examples/hello_dart/lib/main.dart
@@ -0,0 +1,3 @@
+void main(List<String> arguments) {
+  print('Hello World!');
+}
diff --git a/src/examples/hello_dart/pubspec.lock b/src/examples/hello_dart/pubspec.lock
new file mode 100644
index 0000000..67ec6c6
--- /dev/null
+++ b/src/examples/hello_dart/pubspec.lock
@@ -0,0 +1,327 @@
+# Generated by pub
+# See https://dart.dev/tools/pub/glossary#lockfile
+packages:
+  _fe_analyzer_shared:
+    dependency: transitive
+    description:
+      name: _fe_analyzer_shared
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "41.0.0"
+  analyzer:
+    dependency: transitive
+    description:
+      name: analyzer
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "4.2.0"
+  args:
+    dependency: transitive
+    description:
+      name: args
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "2.3.1"
+  async:
+    dependency: transitive
+    description:
+      name: async
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "2.9.0"
+  boolean_selector:
+    dependency: transitive
+    description:
+      name: boolean_selector
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "2.1.0"
+  collection:
+    dependency: transitive
+    description:
+      name: collection
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "1.16.0"
+  convert:
+    dependency: transitive
+    description:
+      name: convert
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "3.0.2"
+  coverage:
+    dependency: transitive
+    description:
+      name: coverage
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "1.5.0"
+  crypto:
+    dependency: transitive
+    description:
+      name: crypto
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "3.0.2"
+  file:
+    dependency: transitive
+    description:
+      name: file
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "6.1.2"
+  frontend_server_client:
+    dependency: transitive
+    description:
+      name: frontend_server_client
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "2.1.3"
+  glob:
+    dependency: transitive
+    description:
+      name: glob
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "2.1.0"
+  http_multi_server:
+    dependency: transitive
+    description:
+      name: http_multi_server
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "3.2.1"
+  http_parser:
+    dependency: transitive
+    description:
+      name: http_parser
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "4.0.1"
+  io:
+    dependency: transitive
+    description:
+      name: io
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "1.0.3"
+  js:
+    dependency: transitive
+    description:
+      name: js
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "0.6.4"
+  lints:
+    dependency: "direct dev"
+    description:
+      name: lints
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "2.0.0"
+  logging:
+    dependency: transitive
+    description:
+      name: logging
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "1.0.2"
+  matcher:
+    dependency: transitive
+    description:
+      name: matcher
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "0.12.12"
+  meta:
+    dependency: transitive
+    description:
+      name: meta
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "1.8.0"
+  mime:
+    dependency: transitive
+    description:
+      name: mime
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "1.0.2"
+  node_preamble:
+    dependency: transitive
+    description:
+      name: node_preamble
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "2.0.1"
+  package_config:
+    dependency: transitive
+    description:
+      name: package_config
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "2.1.0"
+  path:
+    dependency: transitive
+    description:
+      name: path
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "1.8.2"
+  pool:
+    dependency: transitive
+    description:
+      name: pool
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "1.5.1"
+  pub_semver:
+    dependency: transitive
+    description:
+      name: pub_semver
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "2.1.1"
+  shelf:
+    dependency: transitive
+    description:
+      name: shelf
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "1.3.1"
+  shelf_packages_handler:
+    dependency: transitive
+    description:
+      name: shelf_packages_handler
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "3.0.1"
+  shelf_static:
+    dependency: transitive
+    description:
+      name: shelf_static
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "1.1.1"
+  shelf_web_socket:
+    dependency: transitive
+    description:
+      name: shelf_web_socket
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "1.0.2"
+  source_map_stack_trace:
+    dependency: transitive
+    description:
+      name: source_map_stack_trace
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "2.1.0"
+  source_maps:
+    dependency: transitive
+    description:
+      name: source_maps
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "0.10.10"
+  source_span:
+    dependency: transitive
+    description:
+      name: source_span
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "1.9.0"
+  stack_trace:
+    dependency: transitive
+    description:
+      name: stack_trace
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "1.10.0"
+  stream_channel:
+    dependency: transitive
+    description:
+      name: stream_channel
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "2.1.0"
+  string_scanner:
+    dependency: transitive
+    description:
+      name: string_scanner
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "1.1.1"
+  term_glyph:
+    dependency: transitive
+    description:
+      name: term_glyph
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "1.2.1"
+  test:
+    dependency: "direct dev"
+    description:
+      name: test
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "1.21.4"
+  test_api:
+    dependency: transitive
+    description:
+      name: test_api
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "0.4.12"
+  test_core:
+    dependency: transitive
+    description:
+      name: test_core
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "0.4.16"
+  typed_data:
+    dependency: transitive
+    description:
+      name: typed_data
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "1.3.1"
+  vm_service:
+    dependency: transitive
+    description:
+      name: vm_service
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "9.0.0"
+  watcher:
+    dependency: transitive
+    description:
+      name: watcher
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "1.0.1"
+  web_socket_channel:
+    dependency: transitive
+    description:
+      name: web_socket_channel
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "2.2.0"
+  webkit_inspection_protocol:
+    dependency: transitive
+    description:
+      name: webkit_inspection_protocol
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "1.1.0"
+  yaml:
+    dependency: transitive
+    description:
+      name: yaml
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "3.1.1"
+sdks:
+  dart: ">=2.18.0-137.0.dev <3.0.0"
diff --git a/src/examples/hello_dart/pubspec.yaml b/src/examples/hello_dart/pubspec.yaml
new file mode 100644
index 0000000..25a5ee6
--- /dev/null
+++ b/src/examples/hello_dart/pubspec.yaml
@@ -0,0 +1,14 @@
+name: hello_dart
+description: A sample command-line application.
+version: 1.0.0
+# homepage: https://www.example.com
+
+environment:
+  sdk: '>=2.18.0-137.0.dev <3.0.0'
+
+# dependencies:
+#   path: ^1.8.0
+
+dev_dependencies:
+  lints: ^2.0.0
+  test: ^1.16.0
diff --git a/src/examples/flutter_sample_app/.gitignore b/src/examples/hello_flutter/.gitignore
similarity index 100%
rename from src/examples/flutter_sample_app/.gitignore
rename to src/examples/hello_flutter/.gitignore
diff --git a/src/examples/flutter_sample_app/.metadata b/src/examples/hello_flutter/.metadata
similarity index 100%
rename from src/examples/flutter_sample_app/.metadata
rename to src/examples/hello_flutter/.metadata
diff --git a/src/examples/flutter_sample_app/BUILD.bazel b/src/examples/hello_flutter/BUILD.bazel
similarity index 77%
rename from src/examples/flutter_sample_app/BUILD.bazel
rename to src/examples/hello_flutter/BUILD.bazel
index 4520f59..7cc5c0c 100644
--- a/src/examples/flutter_sample_app/BUILD.bazel
+++ b/src/examples/hello_flutter/BUILD.bazel
@@ -5,8 +5,8 @@
 )
 
 # To rebuild the Flutter app with new changes:
-#   ~/flutter-embedder $ cd examples/flutter_sample_app
-#   flutter_sample_app $ flutter build bundle
+#   ~/flutter-embedder $ cd examples/hello_flutter
+#   .../hello_flutter $ flutter build bundle
 #
 # TODO(akbiggs): Grab subdirectories and ensure their names are preserved
 # in the output package's data.
@@ -39,11 +39,14 @@
 )
 
 fuchsia_package(
-    name = "flutter_sample_app_pkg",
-    package_name = "flutter_sample_app",
+    name = "hello_flutter_pkg",
+    package_name = "hello_flutter",
     visibility = ["//visibility:public"],
     deps = [
         ":component",
-        "//src/embedder/engine:libflutter_engine_pkg_resource",
+        # TODO(akbiggs): Enable switching between debug/release
+        # builds of libflutter_engine.so without manually modifying
+        # this file.
+        "//src/embedder/engine/debug_x64:libflutter_engine_pkg_resource",
     ],
 )
diff --git a/src/examples/flutter_sample_app/analysis_options.yaml b/src/examples/hello_flutter/analysis_options.yaml
similarity index 100%
rename from src/examples/flutter_sample_app/analysis_options.yaml
rename to src/examples/hello_flutter/analysis_options.yaml
diff --git a/src/examples/flutter_sample_app/lib/main.dart b/src/examples/hello_flutter/lib/main.dart
similarity index 100%
rename from src/examples/flutter_sample_app/lib/main.dart
rename to src/examples/hello_flutter/lib/main.dart
diff --git a/src/examples/flutter_sample_app/pubspec.lock b/src/examples/hello_flutter/pubspec.lock
similarity index 100%
rename from src/examples/flutter_sample_app/pubspec.lock
rename to src/examples/hello_flutter/pubspec.lock
diff --git a/src/examples/flutter_sample_app/pubspec.yaml b/src/examples/hello_flutter/pubspec.yaml
similarity index 98%
rename from src/examples/flutter_sample_app/pubspec.yaml
rename to src/examples/hello_flutter/pubspec.yaml
index 73305c0..a8c0a11 100644
--- a/src/examples/flutter_sample_app/pubspec.yaml
+++ b/src/examples/hello_flutter/pubspec.yaml
@@ -1,4 +1,4 @@
-name: flutter_sample_app
+name: hello_flutter
 description: A new Flutter project.
 
 # The following line prevents the package from being accidentally published to
diff --git a/src/examples/flutter_sample_app/test/widget_test.dart b/src/examples/hello_flutter/test/widget_test.dart
similarity index 95%
rename from src/examples/flutter_sample_app/test/widget_test.dart
rename to src/examples/hello_flutter/test/widget_test.dart
index 9fe6f6d..b080016 100644
--- a/src/examples/flutter_sample_app/test/widget_test.dart
+++ b/src/examples/hello_flutter/test/widget_test.dart
@@ -8,7 +8,7 @@
 import 'package:flutter/material.dart';
 import 'package:flutter_test/flutter_test.dart';
 
-import 'package:flutter_sample_app_2/main.dart';
+import 'package:hello_flutter/main.dart';
 
 void main() {
   testWidgets('Counter increments smoke test', (WidgetTester tester) async {
diff --git a/src/examples/flutter_sample_app/.gitignore b/src/examples/spinning_cube/.gitignore
similarity index 100%
copy from src/examples/flutter_sample_app/.gitignore
copy to src/examples/spinning_cube/.gitignore
diff --git a/src/examples/spinning_cube/.metadata b/src/examples/spinning_cube/.metadata
new file mode 100644
index 0000000..5c516d6
--- /dev/null
+++ b/src/examples/spinning_cube/.metadata
@@ -0,0 +1,45 @@
+# 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.
+
+version:
+  revision: 1e1f4bcfb56105912a90ffa1a3a317dfb724612b
+  channel: unknown
+
+project_type: app
+
+# Tracks metadata for the flutter migrate command
+migration:
+  platforms:
+    - platform: root
+      create_revision: 1e1f4bcfb56105912a90ffa1a3a317dfb724612b
+      base_revision: 1e1f4bcfb56105912a90ffa1a3a317dfb724612b
+    - platform: android
+      create_revision: 1e1f4bcfb56105912a90ffa1a3a317dfb724612b
+      base_revision: 1e1f4bcfb56105912a90ffa1a3a317dfb724612b
+    - platform: ios
+      create_revision: 1e1f4bcfb56105912a90ffa1a3a317dfb724612b
+      base_revision: 1e1f4bcfb56105912a90ffa1a3a317dfb724612b
+    - platform: linux
+      create_revision: 1e1f4bcfb56105912a90ffa1a3a317dfb724612b
+      base_revision: 1e1f4bcfb56105912a90ffa1a3a317dfb724612b
+    - platform: macos
+      create_revision: 1e1f4bcfb56105912a90ffa1a3a317dfb724612b
+      base_revision: 1e1f4bcfb56105912a90ffa1a3a317dfb724612b
+    - platform: web
+      create_revision: 1e1f4bcfb56105912a90ffa1a3a317dfb724612b
+      base_revision: 1e1f4bcfb56105912a90ffa1a3a317dfb724612b
+    - platform: windows
+      create_revision: 1e1f4bcfb56105912a90ffa1a3a317dfb724612b
+      base_revision: 1e1f4bcfb56105912a90ffa1a3a317dfb724612b
+
+  # User provided section
+
+  # List of Local paths (relative to this file) that should be
+  # ignored by the migrate tool.
+  #
+  # Files that are not part of the templates will be ignored by default.
+  unmanaged_files:
+    - 'lib/main.dart'
+    - 'ios/Runner.xcodeproj/project.pbxproj'
diff --git a/src/examples/flutter_sample_app/BUILD.bazel b/src/examples/spinning_cube/BUILD.bazel
similarity index 77%
copy from src/examples/flutter_sample_app/BUILD.bazel
copy to src/examples/spinning_cube/BUILD.bazel
index 4520f59..a83f0c0 100644
--- a/src/examples/flutter_sample_app/BUILD.bazel
+++ b/src/examples/spinning_cube/BUILD.bazel
@@ -5,8 +5,8 @@
 )
 
 # To rebuild the Flutter app with new changes:
-#   ~/flutter-embedder $ cd examples/flutter_sample_app
-#   flutter_sample_app $ flutter build bundle
+#   ~/flutter-embedder $ cd examples/spinning_cube
+#   .../spinning_cube $ flutter build bundle
 #
 # TODO(akbiggs): Grab subdirectories and ensure their names are preserved
 # in the output package's data.
@@ -39,11 +39,14 @@
 )
 
 fuchsia_package(
-    name = "flutter_sample_app_pkg",
-    package_name = "flutter_sample_app",
+    name = "spinning_cube_pkg",
+    package_name = "spinning_cube",
     visibility = ["//visibility:public"],
     deps = [
         ":component",
-        "//src/embedder/engine:libflutter_engine_pkg_resource",
+        # TODO(akbiggs): Enable switching between debug/release
+        # builds of libflutter_engine.so without manually modifying
+        # this file.
+        "//src/embedder/engine/debug_x64:libflutter_engine_pkg_resource",
     ],
 )
diff --git a/src/examples/flutter_sample_app/analysis_options.yaml b/src/examples/spinning_cube/analysis_options.yaml
similarity index 100%
copy from src/examples/flutter_sample_app/analysis_options.yaml
copy to src/examples/spinning_cube/analysis_options.yaml
diff --git a/src/examples/spinning_cube/lib/main.dart b/src/examples/spinning_cube/lib/main.dart
new file mode 100644
index 0000000..5229e23
--- /dev/null
+++ b/src/examples/spinning_cube/lib/main.dart
@@ -0,0 +1,42 @@
+// Copyright 2022 The Fuchsia 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/scheduler.dart';
+import 'spinning_cube_gem.dart';
+
+class _TickerProviderImpl implements TickerProvider {
+  @override
+  Ticker createTicker(TickerCallback onTick) => Ticker(onTick);
+}
+
+const Duration _kCubeRotationAnimationPeriod = Duration(
+  milliseconds: 12000,
+);
+
+void main() {
+  AnimationController controller = AnimationController(
+    vsync: _TickerProviderImpl(),
+    duration: _kCubeRotationAnimationPeriod,
+  );
+  runApp(
+    MaterialApp(
+      home: Container(
+        color: Colors.deepPurple,
+        child: FractionallySizedBox(
+          alignment: FractionalOffset.center,
+          widthFactor: 0.75,
+          heightFactor: 0.75,
+          child: Center(
+            child: SpinningCubeGem(
+              controller: controller,
+              color: Colors.pinkAccent[400] ?? Colors.pink,
+            ),
+          ),
+        ),
+      ),
+    ),
+  );
+  controller.repeat();
+}
diff --git a/src/examples/spinning_cube/lib/spinning_cube_gem.dart b/src/examples/spinning_cube/lib/spinning_cube_gem.dart
new file mode 100644
index 0000000..49dfc0e
--- /dev/null
+++ b/src/examples/spinning_cube/lib/spinning_cube_gem.dart
@@ -0,0 +1,159 @@
+// Copyright 2017 The Fuchsia 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:math' as math;
+
+import 'package:flutter/material.dart';
+import 'package:flutter/widgets.dart';
+import 'package:vector_math/vector_math_64.dart';
+
+const double _kGemCornerRadius = 16.0;
+const double _kFaceRotation = math.pi / 2.0;
+const double _kPerspectiveFieldOfViewRadians = math.pi / 6.0;
+const double _kPerspectiveNearZ = 100.0;
+const double _kPerspectiveAspectRatio = 1.0;
+const double _kCubeScaleFactor = 50.0;
+const double _kCubeAnimationYRotation = 2.0 * math.pi;
+const double _kCubeAnimationXRotation = 6.0 * math.pi;
+
+/// Creates a spinning unicolor cube with rounded corners.
+class SpinningCubeGem extends StatelessWidget {
+  /// Controls the spinning animation.
+  final AnimationController controller;
+
+  /// The color of the cube faces.
+  final Color color;
+
+  /// Constructor.
+  const SpinningCubeGem({required this.controller, required this.color});
+
+  // The six cube faces are:
+  //   1. Placed in a stack and rotated and translated into different positions
+  //      to form a cube.
+  //   2. Manipulated into a perspective view.
+  //   3. Rotated based on the animation.
+  @override
+  Widget build(BuildContext context) {
+    return LayoutBuilder(
+      builder: (BuildContext context, BoxConstraints constraints) {
+        final gemSize = math.min(
+          constraints.maxWidth,
+          constraints.maxHeight,
+        );
+        final faceSize = gemSize;
+        final halfFaceSize = faceSize / 2.0;
+        final perspectiveFarZ = _kPerspectiveNearZ + (2.0 * faceSize);
+        final cubeZ = _kPerspectiveNearZ + faceSize;
+
+        /// We draw the cube with a 3D perspective.  This matrix manipulates the cube
+        /// faces into this perspective.
+        final cubePerspective = (Matrix4.diagonal3Values(
+              _kCubeScaleFactor,
+              _kCubeScaleFactor,
+              _kCubeScaleFactor,
+            ) *
+            makePerspectiveMatrix(
+              _kPerspectiveFieldOfViewRadians,
+              _kPerspectiveAspectRatio,
+              _kPerspectiveNearZ,
+              perspectiveFarZ,
+            ))
+          ..translate(0.0, 0.0, cubeZ);
+
+        final face = SizedBox(
+          width: gemSize,
+          height: gemSize,
+          child: DecoratedBox(
+            decoration: BoxDecoration(
+              color: color,
+              borderRadius: BorderRadius.circular(_kGemCornerRadius),
+            ),
+          ),
+        );
+
+        final rightFaceTransform = Matrix4.identity()
+          ..translate(halfFaceSize)
+          ..rotateY(_kFaceRotation);
+
+        final leftFaceTransform = Matrix4.identity()
+          ..translate(-halfFaceSize)
+          ..rotateY(_kFaceRotation);
+
+        final backFaceTransform = Matrix4.identity()
+          ..translate(0.0, 0.0, halfFaceSize);
+
+        final frontFaceTransform = Matrix4.identity()
+          ..translate(0.0, 0.0, -halfFaceSize);
+
+        final bottomFaceTransform = Matrix4.identity()
+          ..translate(0.0, halfFaceSize)
+          ..rotateX(_kFaceRotation);
+
+        final topFaceTransform = Matrix4.identity()
+          ..translate(0.0, -halfFaceSize)
+          ..rotateX(_kFaceRotation);
+
+        return RepaintBoundary(
+          child: AnimatedBuilder(
+            animation: controller,
+            builder: (BuildContext context, Widget? child) {
+              final baseTransform = cubePerspective.clone()
+                ..rotateY(
+                  _kCubeAnimationYRotation * controller.value,
+                )
+                ..rotateX(
+                  _kCubeAnimationXRotation * controller.value,
+                );
+              return Stack(
+                children: <Widget>[
+                  // Right face.
+                  Transform(
+                    alignment: FractionalOffset.center,
+                    child: face,
+                    transform: baseTransform * rightFaceTransform,
+                  ),
+
+                  // Left face.
+                  Transform(
+                    alignment: FractionalOffset.center,
+                    child: face,
+                    transform: baseTransform * leftFaceTransform,
+                  ),
+
+                  // Back face.
+                  Transform(
+                    alignment: FractionalOffset.center,
+                    child: face,
+                    transform: baseTransform * backFaceTransform,
+                  ),
+
+                  // Front face.
+                  Transform(
+                    alignment: FractionalOffset.center,
+                    child: face,
+                    transform: baseTransform * frontFaceTransform,
+                  ),
+
+                  // Bottom face.
+                  Transform(
+                    alignment: FractionalOffset.center,
+                    child: face,
+                    transform: baseTransform * bottomFaceTransform,
+                  ),
+
+                  // Top face.
+                  Transform(
+                    alignment: FractionalOffset.center,
+                    child: face,
+                    transform: baseTransform * topFaceTransform,
+                  ),
+                ],
+              );
+            },
+          ),
+        );
+      },
+    );
+  }
+}
diff --git a/src/examples/spinning_cube/pubspec.lock b/src/examples/spinning_cube/pubspec.lock
new file mode 100644
index 0000000..7f6662a
--- /dev/null
+++ b/src/examples/spinning_cube/pubspec.lock
@@ -0,0 +1,160 @@
+# Generated by pub
+# See https://dart.dev/tools/pub/glossary#lockfile
+packages:
+  async:
+    dependency: transitive
+    description:
+      name: async
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "2.9.0"
+  boolean_selector:
+    dependency: transitive
+    description:
+      name: boolean_selector
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "2.1.0"
+  characters:
+    dependency: transitive
+    description:
+      name: characters
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "1.2.1"
+  clock:
+    dependency: transitive
+    description:
+      name: clock
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "1.1.0"
+  collection:
+    dependency: transitive
+    description:
+      name: collection
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "1.16.0"
+  cupertino_icons:
+    dependency: "direct main"
+    description:
+      name: cupertino_icons
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "1.0.5"
+  fake_async:
+    dependency: transitive
+    description:
+      name: fake_async
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "1.3.0"
+  flutter:
+    dependency: "direct main"
+    description: flutter
+    source: sdk
+    version: "0.0.0"
+  flutter_lints:
+    dependency: "direct dev"
+    description:
+      name: flutter_lints
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "2.0.1"
+  flutter_test:
+    dependency: "direct dev"
+    description: flutter
+    source: sdk
+    version: "0.0.0"
+  lints:
+    dependency: transitive
+    description:
+      name: lints
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "2.0.0"
+  matcher:
+    dependency: transitive
+    description:
+      name: matcher
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "0.12.11"
+  material_color_utilities:
+    dependency: transitive
+    description:
+      name: material_color_utilities
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "0.1.4"
+  meta:
+    dependency: transitive
+    description:
+      name: meta
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "1.7.0"
+  path:
+    dependency: transitive
+    description:
+      name: path
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "1.8.1"
+  sky_engine:
+    dependency: transitive
+    description: flutter
+    source: sdk
+    version: "0.0.99"
+  source_span:
+    dependency: transitive
+    description:
+      name: source_span
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "1.9.0"
+  stack_trace:
+    dependency: transitive
+    description:
+      name: stack_trace
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "1.10.0"
+  stream_channel:
+    dependency: transitive
+    description:
+      name: stream_channel
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "2.1.0"
+  string_scanner:
+    dependency: transitive
+    description:
+      name: string_scanner
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "1.1.1"
+  term_glyph:
+    dependency: transitive
+    description:
+      name: term_glyph
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "1.2.0"
+  test_api:
+    dependency: transitive
+    description:
+      name: test_api
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "0.4.9"
+  vector_math:
+    dependency: transitive
+    description:
+      name: vector_math
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "2.1.2"
+sdks:
+  dart: ">=2.18.0-137.0.dev <3.0.0"
diff --git a/src/examples/flutter_sample_app/pubspec.yaml b/src/examples/spinning_cube/pubspec.yaml
similarity index 94%
copy from src/examples/flutter_sample_app/pubspec.yaml
copy to src/examples/spinning_cube/pubspec.yaml
index 73305c0..47d5437 100644
--- a/src/examples/flutter_sample_app/pubspec.yaml
+++ b/src/examples/spinning_cube/pubspec.yaml
@@ -1,4 +1,4 @@
-name: flutter_sample_app
+name: spinning_cube
 description: A new Flutter project.
 
 # The following line prevents the package from being accidentally published to
@@ -18,7 +18,7 @@
 version: 1.0.0+1
 
 environment:
-  sdk: ">=2.16.2 <3.0.0"
+  sdk: '>=2.18.0-137.0.dev <3.0.0'
 
 # Dependencies specify other packages that your package needs in order to work.
 # To automatically upgrade your package dependencies to the latest versions
@@ -44,12 +44,12 @@
   # activated in the `analysis_options.yaml` file located at the root of your
   # package. See that file for information about deactivating specific lint
   # rules and activating additional ones.
-  flutter_lints: ^1.0.0
+  flutter_lints: ^2.0.0
 
 # For information on the generic Dart part of this file, see the
 # following page: https://dart.dev/tools/pub/pubspec
 
-# The following section is specific to Flutter.
+# The following section is specific to Flutter packages.
 flutter:
 
   # The following line ensures that the Material Icons font is
@@ -63,7 +63,7 @@
   #   - images/a_dot_ham.jpeg
 
   # An image asset can refer to one or more resolution-specific "variants", see
-  # https://flutter.dev/assets-and-images/#resolution-aware.
+  # https://flutter.dev/assets-and-images/#resolution-aware
 
   # For details regarding adding assets from package dependencies, see
   # https://flutter.dev/assets-and-images/#from-packages