Move stocks example app into dev/benchmarks/test_apps (#49559)

The stocks example app is outdated and deprecated, but we still use it for some benchmark tests. Moving it into the benchmarks directory to indicate its status.
diff --git a/dev/benchmarks/microbenchmarks/pubspec.yaml b/dev/benchmarks/microbenchmarks/pubspec.yaml
index 98b3cc1..fee9a0e 100644
--- a/dev/benchmarks/microbenchmarks/pubspec.yaml
+++ b/dev/benchmarks/microbenchmarks/pubspec.yaml
@@ -12,7 +12,7 @@
   flutter_test:
     sdk: flutter
   stocks:
-    path: ../../../examples/stocks
+    path: ../test_apps/stocks
   test: 1.9.4
 
   _fe_analyzer_shared: 1.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
diff --git a/dev/benchmarks/test_apps/stocks/README.md b/dev/benchmarks/test_apps/stocks/README.md
new file mode 100644
index 0000000..d8d0785
--- /dev/null
+++ b/dev/benchmarks/test_apps/stocks/README.md
@@ -0,0 +1,44 @@
+# Stocks
+
+Demo app for the material design widgets and other features provided by Flutter.
+
+## Building
+
+You can follow these instructions to build the stocks app
+and install it onto your device.
+
+### Prerequisites
+
+If you are new to Flutter, please first follow
+the [Flutter Setup](https://flutter.dev/setup/) guide.
+
+### Building and installing the stocks demo app
+
+* `cd $FLUTTER_ROOT/examples/stocks`
+* `flutter pub get`
+* `flutter run --release`
+
+The `flutter run --release` command both builds and installs the Flutter app.
+
+## Internationalization
+
+This app has been internationalized (just enough to show how it's
+done). It's an example of how one can do so with the
+[Dart intl package](https://pub.dev/packages/intl).
+
+The [Flutter Internationalization Tutorial](https://flutter.dev/tutorials/internationalization/)
+covers Flutter app internationalization in general.
+
+See [regenerate.md](lib/i18n/regenerate.md) for an explanation
+of how the Dart internationalization tools, like
+`intl_translation:generate_from_arb`, were used to generate
+localizations for this app.
+
+## Icon
+
+Icon was created using Android Asset Studio:
+https://romannurik.github.io/AndroidAssetStudio/icons-launcher.html#foreground.type=image&foreground.space.trim=0&foreground.space.pad=0&foreColor=607d8b%2C0&crop=0&backgroundShape=square&backColor=fff%2C100&effects=none
+
+From this clipart:
+https://openclipart.org/detail/30403/tango-go-up
+Which is public domain.
diff --git a/dev/benchmarks/test_apps/stocks/android/app/build.gradle b/dev/benchmarks/test_apps/stocks/android/app/build.gradle
new file mode 100644
index 0000000..160ac2d
--- /dev/null
+++ b/dev/benchmarks/test_apps/stocks/android/app/build.gradle
@@ -0,0 +1,64 @@
+// Copyright 2014 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.
+
+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 from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
+
+android {
+    compileSdkVersion 28
+
+    lintOptions {
+        disable 'InvalidPackage'
+    }
+
+    defaultConfig {
+        applicationId "io.flutter.examples.stocks"
+        minSdkVersion 16
+        targetSdkVersion 28
+        versionCode flutterVersionCode.toInteger()
+        versionName flutterVersionName
+        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+    }
+
+    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 {
+    testImplementation 'junit:junit:4.12'
+    androidTestImplementation 'androidx.test:runner:1.1.1'
+    androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
+}
diff --git a/dev/benchmarks/test_apps/stocks/android/app/src/main/AndroidManifest.xml b/dev/benchmarks/test_apps/stocks/android/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..e779929
--- /dev/null
+++ b/dev/benchmarks/test_apps/stocks/android/app/src/main/AndroidManifest.xml
@@ -0,0 +1,31 @@
+<!-- Copyright 2014 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. -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="io.flutter.examples.stocks">
+
+    <!-- The INTERNET permission is required for development. Specifically,
+         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"/>
+
+    <application android:label="Stocks" android:icon="@mipmap/ic_launcher">
+        <activity android:name="io.flutter.embedding.android.FlutterActivity"
+                  android:theme="@android:style/Theme.Black.NoTitleBar"
+                  android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
+                  android:hardwareAccelerated="true"
+                  android:windowSoftInputMode="adjustResize">
+            <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/dev/benchmarks/test_apps/stocks/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/dev/benchmarks/test_apps/stocks/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..f244429
--- /dev/null
+++ b/dev/benchmarks/test_apps/stocks/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
Binary files differ
diff --git a/dev/benchmarks/test_apps/stocks/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/dev/benchmarks/test_apps/stocks/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..ac6f2d0
--- /dev/null
+++ b/dev/benchmarks/test_apps/stocks/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
Binary files differ
diff --git a/dev/benchmarks/test_apps/stocks/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/dev/benchmarks/test_apps/stocks/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..d0c9cd2
--- /dev/null
+++ b/dev/benchmarks/test_apps/stocks/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
Binary files differ
diff --git a/dev/benchmarks/test_apps/stocks/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/dev/benchmarks/test_apps/stocks/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..d907cf4
--- /dev/null
+++ b/dev/benchmarks/test_apps/stocks/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/dev/benchmarks/test_apps/stocks/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/dev/benchmarks/test_apps/stocks/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..3643033
--- /dev/null
+++ b/dev/benchmarks/test_apps/stocks/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Binary files differ
diff --git a/dev/benchmarks/test_apps/stocks/android/build.gradle b/dev/benchmarks/test_apps/stocks/android/build.gradle
new file mode 100644
index 0000000..5df71fc
--- /dev/null
+++ b/dev/benchmarks/test_apps/stocks/android/build.gradle
@@ -0,0 +1,33 @@
+// Copyright 2014 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.
+
+buildscript {
+    repositories {
+        google()
+        jcenter()
+    }
+
+    dependencies {
+        classpath 'com.android.tools.build:gradle:3.5.0'
+    }
+}
+
+allprojects {
+    repositories {
+        google()
+        jcenter()
+    }
+}
+
+rootProject.buildDir = '../build'
+subprojects {
+    project.buildDir = "${rootProject.buildDir}/${project.name}"
+}
+subprojects {
+    project.evaluationDependsOn(':app')
+}
+
+task clean(type: Delete) {
+    delete rootProject.buildDir
+}
diff --git a/dev/benchmarks/test_apps/stocks/android/gradle.properties b/dev/benchmarks/test_apps/stocks/android/gradle.properties
new file mode 100644
index 0000000..a673820
--- /dev/null
+++ b/dev/benchmarks/test_apps/stocks/android/gradle.properties
@@ -0,0 +1,4 @@
+org.gradle.jvmargs=-Xmx1536M
+android.useAndroidX=true
+android.enableJetifier=true
+android.enableR8=true
diff --git a/dev/benchmarks/test_apps/stocks/android/gradle/wrapper/gradle-wrapper.properties b/dev/benchmarks/test_apps/stocks/android/gradle/wrapper/gradle-wrapper.properties
new file mode 100755
index 0000000..296b146
--- /dev/null
+++ b/dev/benchmarks/test_apps/stocks/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-5.6.2-all.zip
diff --git a/dev/benchmarks/test_apps/stocks/android/settings.gradle b/dev/benchmarks/test_apps/stocks/android/settings.gradle
new file mode 100644
index 0000000..dbc3b58
--- /dev/null
+++ b/dev/benchmarks/test_apps/stocks/android/settings.gradle
@@ -0,0 +1,19 @@
+// Copyright 2014 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 ':app'
+
+def flutterProjectRoot = rootProject.projectDir.parentFile.toPath()
+
+def plugins = new Properties()
+def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins')
+if (pluginsFile.exists()) {
+    pluginsFile.withInputStream { stream -> plugins.load(stream) }
+}
+
+plugins.each { name, path ->
+    def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile()
+    include ":$name"
+    project(":$name").projectDir = pluginDirectory
+}
diff --git a/dev/benchmarks/test_apps/stocks/fuchsia/meta/stocks.cmx b/dev/benchmarks/test_apps/stocks/fuchsia/meta/stocks.cmx
new file mode 100644
index 0000000..6a1acee
--- /dev/null
+++ b/dev/benchmarks/test_apps/stocks/fuchsia/meta/stocks.cmx
@@ -0,0 +1,22 @@
+{
+    "program": {
+        "data": "data/stocks"
+    },
+    "sandbox": {
+        "services": [
+            "fuchsia.cobalt.LoggerFactory",
+            "fuchsia.fonts.Provider",
+            "fuchsia.logger.LogSink",
+            "fuchsia.modular.Clipboard",
+            "fuchsia.modular.ContextWriter",
+            "fuchsia.modular.DeviceMap",
+            "fuchsia.modular.ModuleContext",
+            "fuchsia.sys.Environment",
+            "fuchsia.sys.Launcher",
+            "fuchsia.testing.runner.TestRunner",
+            "fuchsia.ui.input.ImeService",
+            "fuchsia.ui.policy.Presenter",
+            "fuchsia.ui.scenic.Scenic"
+        ]
+    }
+}
diff --git a/dev/benchmarks/test_apps/stocks/ios/Flutter/AppFrameworkInfo.plist b/dev/benchmarks/test_apps/stocks/ios/Flutter/AppFrameworkInfo.plist
new file mode 100644
index 0000000..6b4c0f7
--- /dev/null
+++ b/dev/benchmarks/test_apps/stocks/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>$(DEVELOPMENT_LANGUAGE)</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>8.0</string>
+</dict>
+</plist>
diff --git a/dev/benchmarks/test_apps/stocks/ios/Flutter/Debug.xcconfig b/dev/benchmarks/test_apps/stocks/ios/Flutter/Debug.xcconfig
new file mode 100644
index 0000000..592ceee
--- /dev/null
+++ b/dev/benchmarks/test_apps/stocks/ios/Flutter/Debug.xcconfig
@@ -0,0 +1 @@
+#include "Generated.xcconfig"
diff --git a/dev/benchmarks/test_apps/stocks/ios/Flutter/Release.xcconfig b/dev/benchmarks/test_apps/stocks/ios/Flutter/Release.xcconfig
new file mode 100644
index 0000000..592ceee
--- /dev/null
+++ b/dev/benchmarks/test_apps/stocks/ios/Flutter/Release.xcconfig
@@ -0,0 +1 @@
+#include "Generated.xcconfig"
diff --git a/dev/benchmarks/test_apps/stocks/ios/Runner.xcodeproj/project.pbxproj b/dev/benchmarks/test_apps/stocks/ios/Runner.xcodeproj/project.pbxproj
new file mode 100644
index 0000000..83c8e59
--- /dev/null
+++ b/dev/benchmarks/test_apps/stocks/ios/Runner.xcodeproj/project.pbxproj
@@ -0,0 +1,510 @@
+// !$*UTF8*$!
+{
+	archiveVersion = 1;
+	classes = {
+	};
+	objectVersion = 46;
+	objects = {
+
+/* Begin PBXBuildFile section */
+		3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
+		3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; };
+		3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
+		74970F6E1EDC1810000507F3 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 74970F6D1EDC1810000507F3 /* GeneratedPluginRegistrant.m */; };
+		9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; };
+		9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
+		978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; };
+		97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; };
+		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 = (
+				3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */,
+				9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */,
+			);
+			name = "Embed Frameworks";
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXCopyFilesBuildPhase section */
+
+/* Begin PBXFileReference section */
+		3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
+		3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = "<group>"; };
+		74970F6C1EDC1810000507F3 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
+		74970F6D1EDC1810000507F3 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
+		7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
+		7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; };
+		7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; 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>"; };
+		9740EEBA1CF902C7004384FC /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Flutter.framework; path = Flutter/Flutter.framework; sourceTree = "<group>"; };
+		97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
+		97C146F21CF9000F007C117D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
+		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 = (
+				9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */,
+				3B80C3941E831B6300D905FE /* App.framework in Frameworks */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+		9740EEB11CF90186004384FC /* Flutter */ = {
+			isa = PBXGroup;
+			children = (
+				3B80C3931E831B6300D905FE /* App.framework */,
+				3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
+				9740EEBA1CF902C7004384FC /* Flutter.framework */,
+				9740EEB21CF90195004384FC /* Debug.xcconfig */,
+				7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
+				9740EEB31CF90195004384FC /* Generated.xcconfig */,
+			);
+			name = Flutter;
+			sourceTree = "<group>";
+		};
+		97C146E51CF9000F007C117D = {
+			isa = PBXGroup;
+			children = (
+				9740EEB11CF90186004384FC /* Flutter */,
+				97C146F01CF9000F007C117D /* Runner */,
+				97C146EF1CF9000F007C117D /* Products */,
+				CF3B75C9A7D2FA2A4C99F110 /* Frameworks */,
+			);
+			sourceTree = "<group>";
+		};
+		97C146EF1CF9000F007C117D /* Products */ = {
+			isa = PBXGroup;
+			children = (
+				97C146EE1CF9000F007C117D /* Runner.app */,
+			);
+			name = Products;
+			sourceTree = "<group>";
+		};
+		97C146F01CF9000F007C117D /* Runner */ = {
+			isa = PBXGroup;
+			children = (
+				74970F6C1EDC1810000507F3 /* GeneratedPluginRegistrant.h */,
+				74970F6D1EDC1810000507F3 /* GeneratedPluginRegistrant.m */,
+				7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */,
+				7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */,
+				97C146FA1CF9000F007C117D /* Main.storyboard */,
+				97C146FD1CF9000F007C117D /* Assets.xcassets */,
+				97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
+				97C147021CF9000F007C117D /* Info.plist */,
+				97C146F11CF9000F007C117D /* Supporting Files */,
+			);
+			path = Runner;
+			sourceTree = "<group>";
+		};
+		97C146F11CF9000F007C117D /* Supporting Files */ = {
+			isa = PBXGroup;
+			children = (
+				97C146F21CF9000F007C117D /* main.m */,
+			);
+			name = "Supporting Files";
+			sourceTree = "<group>";
+		};
+		CF3B75C9A7D2FA2A4C99F110 /* Frameworks */ = {
+			isa = PBXGroup;
+			children = (
+			);
+			name = Frameworks;
+			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 = 1020;
+				ORGANIZATIONNAME = "The Flutter Authors";
+				TargetAttributes = {
+					97C146ED1CF9000F007C117D = {
+						CreatedOnToolsVersion = 7.3.1;
+					};
+				};
+			};
+			buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
+			compatibilityVersion = "Xcode 3.2";
+			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\" 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 = (
+				978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */,
+				97C146F31CF9000F007C117D /* main.m in Sources */,
+				74970F6E1EDC1810000507F3 /* 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 */
+		2445AC6C21828DE2009C639E /* Profile */ = {
+			isa = XCBuildConfiguration;
+			baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
+			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 = 8.0;
+				MTL_ENABLE_DEBUG_INFO = NO;
+				SDKROOT = iphoneos;
+				TARGETED_DEVICE_FAMILY = "1,2";
+				VALIDATE_PRODUCT = YES;
+			};
+			name = Profile;
+		};
+		2445AC6D21828DE2009C639E /* Profile */ = {
+			isa = XCBuildConfiguration;
+			baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
+			buildSettings = {
+				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+				ENABLE_BITCODE = NO;
+				FRAMEWORK_SEARCH_PATHS = (
+					"$(inherited)",
+					"$(PROJECT_DIR)/Flutter",
+				);
+				INFOPLIST_FILE = Runner/Info.plist;
+				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
+				LIBRARY_SEARCH_PATHS = (
+					"$(inherited)",
+					"$(PROJECT_DIR)/Flutter",
+				);
+				PRODUCT_BUNDLE_IDENTIFIER = io.flutter.examples.stocks;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+			};
+			name = Profile;
+		};
+		97C147031CF9000F007C117D /* Debug */ = {
+			isa = XCBuildConfiguration;
+			baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
+			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 = 8.0;
+				MTL_ENABLE_DEBUG_INFO = YES;
+				ONLY_ACTIVE_ARCH = YES;
+				SDKROOT = iphoneos;
+				TARGETED_DEVICE_FAMILY = "1,2";
+			};
+			name = Debug;
+		};
+		97C147041CF9000F007C117D /* Release */ = {
+			isa = XCBuildConfiguration;
+			baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
+			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 = 8.0;
+				MTL_ENABLE_DEBUG_INFO = NO;
+				SDKROOT = iphoneos;
+				TARGETED_DEVICE_FAMILY = "1,2";
+				VALIDATE_PRODUCT = YES;
+			};
+			name = Release;
+		};
+		97C147061CF9000F007C117D /* Debug */ = {
+			isa = XCBuildConfiguration;
+			baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
+			buildSettings = {
+				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+				ENABLE_BITCODE = NO;
+				FRAMEWORK_SEARCH_PATHS = (
+					"$(inherited)",
+					"$(PROJECT_DIR)/Flutter",
+				);
+				INFOPLIST_FILE = Runner/Info.plist;
+				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
+				LIBRARY_SEARCH_PATHS = (
+					"$(inherited)",
+					"$(PROJECT_DIR)/Flutter",
+				);
+				PRODUCT_BUNDLE_IDENTIFIER = io.flutter.examples.stocks;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+			};
+			name = Debug;
+		};
+		97C147071CF9000F007C117D /* Release */ = {
+			isa = XCBuildConfiguration;
+			baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
+			buildSettings = {
+				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+				ENABLE_BITCODE = NO;
+				FRAMEWORK_SEARCH_PATHS = (
+					"$(inherited)",
+					"$(PROJECT_DIR)/Flutter",
+				);
+				INFOPLIST_FILE = Runner/Info.plist;
+				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
+				LIBRARY_SEARCH_PATHS = (
+					"$(inherited)",
+					"$(PROJECT_DIR)/Flutter",
+				);
+				PRODUCT_BUNDLE_IDENTIFIER = io.flutter.examples.stocks;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+			};
+			name = Release;
+		};
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+		97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				97C147031CF9000F007C117D /* Debug */,
+				97C147041CF9000F007C117D /* Release */,
+				2445AC6C21828DE2009C639E /* Profile */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
+		};
+		97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				97C147061CF9000F007C117D /* Debug */,
+				97C147071CF9000F007C117D /* Release */,
+				2445AC6D21828DE2009C639E /* Profile */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
+		};
+/* End XCConfigurationList section */
+	};
+	rootObject = 97C146E61CF9000F007C117D /* Project object */;
+}
diff --git a/dev/benchmarks/test_apps/stocks/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/dev/benchmarks/test_apps/stocks/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 0000000..1d526a1
--- /dev/null
+++ b/dev/benchmarks/test_apps/stocks/ios/Runner.xcodeproj/project.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/dev/benchmarks/test_apps/stocks/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/dev/benchmarks/test_apps/stocks/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
new file mode 100644
index 0000000..18d9810
--- /dev/null
+++ b/dev/benchmarks/test_apps/stocks/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/dev/benchmarks/test_apps/stocks/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/dev/benchmarks/test_apps/stocks/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
new file mode 100644
index 0000000..a28140c
--- /dev/null
+++ b/dev/benchmarks/test_apps/stocks/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
@@ -0,0 +1,91 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Scheme
+   LastUpgradeVersion = "1020"
+   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">
+      <Testables>
+      </Testables>
+      <MacroExpansion>
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "97C146ED1CF9000F007C117D"
+            BuildableName = "Runner.app"
+            BlueprintName = "Runner"
+            ReferencedContainer = "container:Runner.xcodeproj">
+         </BuildableReference>
+      </MacroExpansion>
+      <AdditionalOptions>
+      </AdditionalOptions>
+   </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>
+      <AdditionalOptions>
+      </AdditionalOptions>
+   </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/dev/benchmarks/test_apps/stocks/ios/Runner.xcworkspace/contents.xcworkspacedata b/dev/benchmarks/test_apps/stocks/ios/Runner.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 0000000..1d526a1
--- /dev/null
+++ b/dev/benchmarks/test_apps/stocks/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/dev/benchmarks/test_apps/stocks/ios/Runner/AppDelegate.h b/dev/benchmarks/test_apps/stocks/ios/Runner/AppDelegate.h
new file mode 100644
index 0000000..a78a945
--- /dev/null
+++ b/dev/benchmarks/test_apps/stocks/ios/Runner/AppDelegate.h
@@ -0,0 +1,10 @@
+// Copyright 2014 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/UIKit.h>
+#import <Flutter/Flutter.h>
+
+@interface AppDelegate : FlutterAppDelegate
+
+@end
diff --git a/dev/benchmarks/test_apps/stocks/ios/Runner/AppDelegate.m b/dev/benchmarks/test_apps/stocks/ios/Runner/AppDelegate.m
new file mode 100644
index 0000000..d41031a
--- /dev/null
+++ b/dev/benchmarks/test_apps/stocks/ios/Runner/AppDelegate.m
@@ -0,0 +1,15 @@
+// Copyright 2014 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 "AppDelegate.h"
+#import "GeneratedPluginRegistrant.h"
+
+@implementation AppDelegate
+
+- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
+  [GeneratedPluginRegistrant registerWithRegistry:self];
+  // Override point for customization after application launch.
+  return [super application:application didFinishLaunchingWithOptions:launchOptions];
+}
+@end
diff --git a/dev/benchmarks/test_apps/stocks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/dev/benchmarks/test_apps/stocks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
new file mode 100644
index 0000000..7c0c222
--- /dev/null
+++ b/dev/benchmarks/test_apps/stocks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -0,0 +1,106 @@
+{
+  "images" : [
+    {
+      "size" : "20x20",
+      "idiom" : "iphone",
+      "filename" : "Icon-Notification@2x.png",
+      "scale" : "2x"
+    },
+    {
+      "size" : "20x20",
+      "idiom" : "iphone",
+      "filename" : "Icon-Notification@3x.png",
+      "scale" : "3x"
+    },
+    {
+      "size" : "29x29",
+      "idiom" : "iphone",
+      "filename" : "Icon-Small@2x.png",
+      "scale" : "2x"
+    },
+    {
+      "size" : "29x29",
+      "idiom" : "iphone",
+      "filename" : "Icon-Small@3x.png",
+      "scale" : "3x"
+    },
+    {
+      "size" : "40x40",
+      "idiom" : "iphone",
+      "filename" : "Icon-Small-40@2x.png",
+      "scale" : "2x"
+    },
+    {
+      "size" : "40x40",
+      "idiom" : "iphone",
+      "filename" : "Icon-Small-40@3x.png",
+      "scale" : "3x"
+    },
+    {
+      "size" : "60x60",
+      "idiom" : "iphone",
+      "filename" : "Icon-60@2x.png",
+      "scale" : "2x"
+    },
+    {
+      "size" : "60x60",
+      "idiom" : "iphone",
+      "filename" : "Icon-60@3x.png",
+      "scale" : "3x"
+    },
+    {
+      "size" : "20x20",
+      "idiom" : "ipad",
+      "filename" : "Icon-Notification.png",
+      "scale" : "1x"
+    },
+    {
+      "size" : "20x20",
+      "idiom" : "ipad",
+      "filename" : "Icon-Notification@2x.png",
+      "scale" : "2x"
+    },
+    {
+      "size" : "29x29",
+      "idiom" : "ipad",
+      "filename" : "Icon-Small.png",
+      "scale" : "1x"
+    },
+    {
+      "size" : "29x29",
+      "idiom" : "ipad",
+      "filename" : "Icon-Small@2x.png",
+      "scale" : "2x"
+    },
+    {
+      "size" : "40x40",
+      "idiom" : "ipad",
+      "filename" : "Icon-Small-40.png",
+      "scale" : "1x"
+    },
+    {
+      "size" : "40x40",
+      "idiom" : "ipad",
+      "filename" : "Icon-Small-40@2x.png",
+      "scale" : "2x"
+    },
+    {
+      "size" : "76x76",
+      "idiom" : "ipad",
+      "filename" : "Icon-76.png",
+      "scale" : "1x"
+    },
+    {
+      "size" : "76x76",
+      "idiom" : "ipad",
+      "filename" : "Icon-76@2x.png",
+      "scale" : "2x"
+    },
+    {
+      "size" : "83.5x83.5",
+      "idiom" : "ipad",
+      "filename" : "Icon-83.5@2x.png",
+      "scale" : "2x"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/dev/benchmarks/test_apps/stocks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-60@2x.png b/dev/benchmarks/test_apps/stocks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-60@2x.png
new file mode 100644
index 0000000..bf55f17
--- /dev/null
+++ b/dev/benchmarks/test_apps/stocks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-60@2x.png
Binary files differ
diff --git a/dev/benchmarks/test_apps/stocks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-60@3x.png b/dev/benchmarks/test_apps/stocks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-60@3x.png
new file mode 100644
index 0000000..062b37a
--- /dev/null
+++ b/dev/benchmarks/test_apps/stocks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-60@3x.png
Binary files differ
diff --git a/dev/benchmarks/test_apps/stocks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-76.png b/dev/benchmarks/test_apps/stocks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-76.png
new file mode 100644
index 0000000..ddbe932
--- /dev/null
+++ b/dev/benchmarks/test_apps/stocks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-76.png
Binary files differ
diff --git a/dev/benchmarks/test_apps/stocks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-76@2x.png b/dev/benchmarks/test_apps/stocks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-76@2x.png
new file mode 100644
index 0000000..929edb5
--- /dev/null
+++ b/dev/benchmarks/test_apps/stocks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-76@2x.png
Binary files differ
diff --git a/dev/benchmarks/test_apps/stocks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-83.5@2x.png b/dev/benchmarks/test_apps/stocks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-83.5@2x.png
new file mode 100644
index 0000000..1b8f3de
--- /dev/null
+++ b/dev/benchmarks/test_apps/stocks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-83.5@2x.png
Binary files differ
diff --git a/dev/benchmarks/test_apps/stocks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-Notification.png b/dev/benchmarks/test_apps/stocks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-Notification.png
new file mode 100644
index 0000000..810eda2
--- /dev/null
+++ b/dev/benchmarks/test_apps/stocks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-Notification.png
Binary files differ
diff --git a/dev/benchmarks/test_apps/stocks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-Notification@2x.png b/dev/benchmarks/test_apps/stocks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-Notification@2x.png
new file mode 100644
index 0000000..e9a6b95
--- /dev/null
+++ b/dev/benchmarks/test_apps/stocks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-Notification@2x.png
Binary files differ
diff --git a/dev/benchmarks/test_apps/stocks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-Notification@3x.png b/dev/benchmarks/test_apps/stocks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-Notification@3x.png
new file mode 100644
index 0000000..28db17e
--- /dev/null
+++ b/dev/benchmarks/test_apps/stocks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-Notification@3x.png
Binary files differ
diff --git a/dev/benchmarks/test_apps/stocks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-Small-40.png b/dev/benchmarks/test_apps/stocks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-Small-40.png
new file mode 100644
index 0000000..e9a6b95
--- /dev/null
+++ b/dev/benchmarks/test_apps/stocks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-Small-40.png
Binary files differ
diff --git a/dev/benchmarks/test_apps/stocks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-Small-40@2x.png b/dev/benchmarks/test_apps/stocks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-Small-40@2x.png
new file mode 100644
index 0000000..865962d
--- /dev/null
+++ b/dev/benchmarks/test_apps/stocks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-Small-40@2x.png
Binary files differ
diff --git a/dev/benchmarks/test_apps/stocks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-Small-40@3x.png b/dev/benchmarks/test_apps/stocks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-Small-40@3x.png
new file mode 100644
index 0000000..bf55f17
--- /dev/null
+++ b/dev/benchmarks/test_apps/stocks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-Small-40@3x.png
Binary files differ
diff --git a/dev/benchmarks/test_apps/stocks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-Small.png b/dev/benchmarks/test_apps/stocks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-Small.png
new file mode 100644
index 0000000..61342bc
--- /dev/null
+++ b/dev/benchmarks/test_apps/stocks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-Small.png
Binary files differ
diff --git a/dev/benchmarks/test_apps/stocks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-Small@2x.png b/dev/benchmarks/test_apps/stocks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-Small@2x.png
new file mode 100644
index 0000000..a6cb12f
--- /dev/null
+++ b/dev/benchmarks/test_apps/stocks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-Small@2x.png
Binary files differ
diff --git a/dev/benchmarks/test_apps/stocks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-Small@3x.png b/dev/benchmarks/test_apps/stocks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-Small@3x.png
new file mode 100644
index 0000000..bc5f294
--- /dev/null
+++ b/dev/benchmarks/test_apps/stocks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-Small@3x.png
Binary files differ
diff --git a/dev/benchmarks/test_apps/stocks/ios/Runner/Base.lproj/LaunchScreen.storyboard b/dev/benchmarks/test_apps/stocks/ios/Runner/Base.lproj/LaunchScreen.storyboard
new file mode 100644
index 0000000..ebf48f6
--- /dev/null
+++ b/dev/benchmarks/test_apps/stocks/ios/Runner/Base.lproj/LaunchScreen.storyboard
@@ -0,0 +1,27 @@
+<?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" launchScreen="YES" useTraitCollections="YES" initialViewController="01J-lp-oVM">
+    <dependencies>
+        <deployment identifier="iOS"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
+    </dependencies>
+    <scenes>
+        <!--View Controller-->
+        <scene sceneID="EHf-IW-A2E">
+            <objects>
+                <viewController id="01J-lp-oVM" sceneMemberID="viewController">
+                    <layoutGuides>
+                        <viewControllerLayoutGuide type="top" id="Llm-lL-Icb"/>
+                        <viewControllerLayoutGuide type="bottom" id="xb3-aO-Qok"/>
+                    </layoutGuides>
+                    <view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
+                        <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="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
+            </objects>
+            <point key="canvasLocation" x="53" y="375"/>
+        </scene>
+    </scenes>
+</document>
diff --git a/dev/benchmarks/test_apps/stocks/ios/Runner/Base.lproj/Main.storyboard b/dev/benchmarks/test_apps/stocks/ios/Runner/Base.lproj/Main.storyboard
new file mode 100644
index 0000000..f3c2851
--- /dev/null
+++ b/dev/benchmarks/test_apps/stocks/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/dev/benchmarks/test_apps/stocks/ios/Runner/Info.plist b/dev/benchmarks/test_apps/stocks/ios/Runner/Info.plist
new file mode 100644
index 0000000..0a1d774
--- /dev/null
+++ b/dev/benchmarks/test_apps/stocks/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>Stocks</string>
+	<key>CFBundlePackageType</key>
+	<string>APPL</string>
+	<key>CFBundleShortVersionString</key>
+	<string>1.0</string>
+	<key>CFBundleSignature</key>
+	<string>????</string>
+	<key>CFBundleVersion</key>
+	<string>1</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/dev/benchmarks/test_apps/stocks/ios/Runner/main.m b/dev/benchmarks/test_apps/stocks/ios/Runner/main.m
new file mode 100644
index 0000000..8607072
--- /dev/null
+++ b/dev/benchmarks/test_apps/stocks/ios/Runner/main.m
@@ -0,0 +1,14 @@
+// Copyright 2014 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/UIKit.h>
+#import <Flutter/Flutter.h>
+#import "AppDelegate.h"
+
+int main(int argc, char * argv[]) {
+    @autoreleasepool {
+        return UIApplicationMain(argc, argv, nil,
+                                 NSStringFromClass([AppDelegate class]));
+    }
+}
diff --git a/dev/benchmarks/test_apps/stocks/lib/i18n/.dartignore b/dev/benchmarks/test_apps/stocks/lib/i18n/.dartignore
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/dev/benchmarks/test_apps/stocks/lib/i18n/.dartignore
diff --git a/dev/benchmarks/test_apps/stocks/lib/i18n/messages_all.dart b/dev/benchmarks/test_apps/stocks/lib/i18n/messages_all.dart
new file mode 100644
index 0000000..867afc7
--- /dev/null
+++ b/dev/benchmarks/test_apps/stocks/lib/i18n/messages_all.dart
@@ -0,0 +1,67 @@
+// DO NOT EDIT. This is code generated via package:intl/generate_localized.dart
+// This is a library that looks up messages for specific locales by
+// delegating to the appropriate library.
+
+// Ignore issues from commonly used lints in this file.
+// ignore_for_file:implementation_imports, file_names, unnecessary_new
+// ignore_for_file:unnecessary_brace_in_string_interps, directives_ordering
+// ignore_for_file:argument_type_not_assignable, invalid_assignment
+// ignore_for_file:prefer_single_quotes, prefer_generic_function_type_aliases
+// ignore_for_file:comment_references
+
+import 'dart:async';
+
+import 'package:intl/intl.dart';
+import 'package:intl/message_lookup_by_library.dart';
+import 'package:intl/src/intl_helpers.dart';
+
+import 'messages_en_US.dart' as messages_en_us;
+import 'messages_es_ES.dart' as messages_es_es;
+
+typedef Future<dynamic> LibraryLoader();
+Map<String, LibraryLoader> _deferredLibraries = {
+  'en_US': () => new Future.value(null),
+  'es_ES': () => new Future.value(null),
+};
+
+MessageLookupByLibrary _findExact(String localeName) {
+  switch (localeName) {
+    case 'en_US':
+      return messages_en_us.messages;
+    case 'es_ES':
+      return messages_es_es.messages;
+    default:
+      return null;
+  }
+}
+
+/// User programs should call this before using [localeName] for messages.
+Future<bool> initializeMessages(String localeName) async {
+  var availableLocale = Intl.verifiedLocale(
+    localeName,
+    (locale) => _deferredLibraries[locale] != null,
+    onFailure: (_) => null);
+  if (availableLocale == null) {
+    return new Future.value(false);
+  }
+  var lib = _deferredLibraries[availableLocale];
+  await (lib == null ? new Future.value(false) : lib());
+  initializeInternalMessageLookup(() => new CompositeMessageLookup());
+  messageLookup.addLocale(availableLocale, _findGeneratedMessagesFor);
+  return new Future.value(true);
+}
+
+bool _messagesExistFor(String locale) {
+  try {
+    return _findExact(locale) != null;
+  } catch (e) {
+    return false;
+  }
+}
+
+MessageLookupByLibrary _findGeneratedMessagesFor(String locale) {
+  var actualLocale = Intl.verifiedLocale(locale, _messagesExistFor,
+      onFailure: (_) => null);
+  if (actualLocale == null) return null;
+  return _findExact(actualLocale);
+}
diff --git a/dev/benchmarks/test_apps/stocks/lib/i18n/messages_en_US.dart b/dev/benchmarks/test_apps/stocks/lib/i18n/messages_en_US.dart
new file mode 100644
index 0000000..fedb57c
--- /dev/null
+++ b/dev/benchmarks/test_apps/stocks/lib/i18n/messages_en_US.dart
@@ -0,0 +1,28 @@
+// DO NOT EDIT. This is code generated via package:intl/generate_localized.dart
+// This is a library that provides messages for a en_US locale. All the
+// messages from the main program should be duplicated here with the same
+// function name.
+
+// Ignore issues from commonly used lints in this file.
+// ignore_for_file:unnecessary_brace_in_string_interps, unnecessary_new
+// ignore_for_file:prefer_single_quotes,comment_references, directives_ordering
+// ignore_for_file:annotate_overrides,prefer_generic_function_type_aliases
+// ignore_for_file:unused_import, file_names
+
+import 'package:intl/intl.dart';
+import 'package:intl/message_lookup_by_library.dart';
+
+final messages = new MessageLookup();
+
+typedef String MessageIfAbsent(String messageStr, List<dynamic> args);
+
+class MessageLookup extends MessageLookupByLibrary {
+  String get localeName => 'en_US';
+
+  final messages = _notInlinedMessages(_notInlinedMessages);
+  static _notInlinedMessages(_) => <String, Function> {
+    "market" : MessageLookupByLibrary.simpleMessage("MARKET"),
+    "portfolio" : MessageLookupByLibrary.simpleMessage("PORTFOLIO"),
+    "title" : MessageLookupByLibrary.simpleMessage("Stocks")
+  };
+}
diff --git a/dev/benchmarks/test_apps/stocks/lib/i18n/messages_es_ES.dart b/dev/benchmarks/test_apps/stocks/lib/i18n/messages_es_ES.dart
new file mode 100644
index 0000000..b2c0fd3
--- /dev/null
+++ b/dev/benchmarks/test_apps/stocks/lib/i18n/messages_es_ES.dart
@@ -0,0 +1,28 @@
+// DO NOT EDIT. This is code generated via package:intl/generate_localized.dart
+// This is a library that provides messages for a es_ES locale. All the
+// messages from the main program should be duplicated here with the same
+// function name.
+
+// Ignore issues from commonly used lints in this file.
+// ignore_for_file:unnecessary_brace_in_string_interps, unnecessary_new
+// ignore_for_file:prefer_single_quotes,comment_references, directives_ordering
+// ignore_for_file:annotate_overrides,prefer_generic_function_type_aliases
+// ignore_for_file:unused_import, file_names
+
+import 'package:intl/intl.dart';
+import 'package:intl/message_lookup_by_library.dart';
+
+final messages = new MessageLookup();
+
+typedef String MessageIfAbsent(String messageStr, List<dynamic> args);
+
+class MessageLookup extends MessageLookupByLibrary {
+  String get localeName => 'es_ES';
+
+  final messages = _notInlinedMessages(_notInlinedMessages);
+  static _notInlinedMessages(_) => <String, Function> {
+    "market" : MessageLookupByLibrary.simpleMessage("MERCADO"),
+    "portfolio" : MessageLookupByLibrary.simpleMessage("CARTERA"),
+    "title" : MessageLookupByLibrary.simpleMessage("Acciones")
+  };
+}
diff --git a/dev/benchmarks/test_apps/stocks/lib/i18n/regenerate.md b/dev/benchmarks/test_apps/stocks/lib/i18n/regenerate.md
new file mode 100644
index 0000000..e75b945
--- /dev/null
+++ b/dev/benchmarks/test_apps/stocks/lib/i18n/regenerate.md
@@ -0,0 +1,26 @@
+# Regenerating the i18n files
+
+The files in this directory are used to generate `stock_strings.dart`, which
+is used by the stocks application to look up localized message strings. The
+stocks app uses the [Dart `intl` package](https://github.com/dart-lang/intl).
+
+Rebuilding everything requires two steps.
+
+1. Create or update the English and Spanish localizations, `stocks_en_US.arb`
+and `stocks_es_ES.arb`. See the [ARB specification](https://github.com/google/app-resource-bundle/wiki/ApplicationResourceBundleSpecification)
+for more info.
+
+2. With `examples/stocks` as the current directory, generate a
+`messages_<locale>.dart` for each `stocks_<locale>.arb` file,
+`messages_all.dart`, and `stock_strings.dart` with the following command:
+
+```dart
+dart ${FLUTTER_PATH}/dev/tools/localization/bin/gen_l10n.dart --arb-dir=lib/i18n \
+    --template-arb-file=stocks_en_US.arb --output-localization-file=stock_strings.dart \
+    --output-class=StockStrings
+```
+
+The `StockStrings` class uses the generated `initializeMessages()`function
+(`messages_all.dart`) to load the localized messages and `Intl.message()`
+to look them up. The generated class's API documentation explains how to add
+the new localizations delegate and supported locales to the Flutter application.
diff --git a/dev/benchmarks/test_apps/stocks/lib/i18n/stock_strings.dart b/dev/benchmarks/test_apps/stocks/lib/i18n/stock_strings.dart
new file mode 100644
index 0000000..8c12e5a
--- /dev/null
+++ b/dev/benchmarks/test_apps/stocks/lib/i18n/stock_strings.dart
@@ -0,0 +1,136 @@
+import 'dart:async';
+
+import 'package:flutter/widgets.dart';
+import 'package:flutter_localizations/flutter_localizations.dart';
+import 'package:intl/intl.dart';
+
+import 'messages_all.dart';
+
+/// Callers can lookup localized strings with an instance of StockStrings returned
+/// by `StockStrings.of(context)`.
+///
+/// Applications need to include `StockStrings.delegate()` in their app's
+/// localizationDelegates list, and the locales they support in the app's
+/// supportedLocales list. For example:
+///
+/// ```
+/// import 'i18n/stock_strings.dart';
+///
+/// return MaterialApp(
+///   localizationsDelegates: StockStrings.localizationsDelegates,
+///   supportedLocales: StockStrings.supportedLocales,
+///   home: MyApplicationHome(),
+/// );
+/// ```
+///
+/// ## Update pubspec.yaml
+///
+/// Please make sure to update your pubspec.yaml to include the following
+/// packages:
+///
+/// ```
+/// dependencies:
+///   # Internationalization support.
+///   flutter_localizations:
+///     sdk: flutter
+///   intl: 0.16.0
+///   intl_translation: 0.17.7
+///
+///   # rest of dependencies
+/// ```
+///
+/// ## iOS Applications
+///
+/// iOS applications define key application metadata, including supported
+/// locales, in an Info.plist file that is built into the application bundle.
+/// To configure the locales supported by your app, you’ll need to edit this
+/// file.
+///
+/// First, open your project’s ios/Runner.xcworkspace Xcode workspace file.
+/// Then, in the Project Navigator, open the Info.plist file under the Runner
+/// project’s Runner folder.
+///
+/// Next, select the Information Property List item, select Add Item from the
+/// Editor menu, then select Localizations from the pop-up menu.
+///
+/// Select and expand the newly-created Localizations item then, for each
+/// locale your application supports, add a new item and select the locale
+/// you wish to add from the pop-up menu in the Value field. This list should
+/// be consistent with the languages listed in the StockStrings.supportedLocales
+/// property.
+class StockStrings {
+  StockStrings(Locale locale) : _localeName = Intl.canonicalizedLocale(locale.toString());
+
+  final String _localeName;
+
+  static Future<StockStrings> load(Locale locale) {
+    return initializeMessages(locale.toString())
+      .then<StockStrings>((_) => StockStrings(locale));
+  }
+
+  static StockStrings of(BuildContext context) {
+    return Localizations.of<StockStrings>(context, StockStrings);
+  }
+
+  static const LocalizationsDelegate<StockStrings> delegate = _StockStringsDelegate();
+
+  /// A list of this localizations delegate along with the default localizations
+  /// delegates.
+  ///
+  /// Returns a list of localizations delegates containing this delegate along with
+  /// GlobalMaterialLocalizations.delegate, GlobalCupertinoLocalizations.delegate,
+  /// and GlobalWidgetsLocalizations.delegate.
+  static const List<LocalizationsDelegate<dynamic>> localizationsDelegates = <LocalizationsDelegate<dynamic>>[
+    delegate,
+    GlobalMaterialLocalizations.delegate,
+    GlobalCupertinoLocalizations.delegate,
+    GlobalWidgetsLocalizations.delegate,
+  ];
+
+  /// A list of this localizations delegate's supported locales.
+  static const List<Locale> supportedLocales = <Locale>[
+    Locale('en', 'US'),
+    Locale('es', 'ES'),
+  ];
+
+  String get market {
+    return Intl.message(
+      r'MARKET',
+      locale: _localeName,
+      name: 'market',
+      desc: r'Label for the Market tab'
+    );
+  }
+
+  String get portfolio {
+    return Intl.message(
+      r'PORTFOLIO',
+      locale: _localeName,
+      name: 'portfolio',
+      desc: r'Label for the Portfolio tab'
+    );
+  }
+
+  String get title {
+    return Intl.message(
+      r'Stocks',
+      locale: _localeName,
+      name: 'title',
+      desc: r'Title for the Stocks application'
+    );
+  }
+
+}
+
+class _StockStringsDelegate extends LocalizationsDelegate<StockStrings> {
+  const _StockStringsDelegate();
+
+  @override
+  Future<StockStrings> load(Locale locale) => StockStrings.load(locale);
+
+  @override
+  bool isSupported(Locale locale) => <String>['en', 'es'].contains(locale.languageCode);
+
+  @override
+  bool shouldReload(_StockStringsDelegate old) => false;
+}
diff --git a/dev/benchmarks/test_apps/stocks/lib/i18n/stocks_en_US.arb b/dev/benchmarks/test_apps/stocks/lib/i18n/stocks_en_US.arb
new file mode 100644
index 0000000..2d4b4f2
--- /dev/null
+++ b/dev/benchmarks/test_apps/stocks/lib/i18n/stocks_en_US.arb
@@ -0,0 +1,16 @@
+{
+  "title": "Stocks",
+  "@title": {
+    "description": "Title for the Stocks application"
+  },
+
+  "market": "MARKET",
+  "@market": {
+    "description": "Label for the Market tab"
+  },
+
+  "portfolio": "PORTFOLIO",
+  "@portfolio": {
+    "description": "Label for the Portfolio tab"
+  }
+}
diff --git a/dev/benchmarks/test_apps/stocks/lib/i18n/stocks_es_ES.arb b/dev/benchmarks/test_apps/stocks/lib/i18n/stocks_es_ES.arb
new file mode 100644
index 0000000..3d41a88
--- /dev/null
+++ b/dev/benchmarks/test_apps/stocks/lib/i18n/stocks_es_ES.arb
@@ -0,0 +1,5 @@
+{
+  "title": "Acciones",
+  "market": "MERCADO",
+  "portfolio": "CARTERA"
+}
diff --git a/dev/benchmarks/test_apps/stocks/lib/main.dart b/dev/benchmarks/test_apps/stocks/lib/main.dart
new file mode 100644
index 0000000..a066621
--- /dev/null
+++ b/dev/benchmarks/test_apps/stocks/lib/main.dart
@@ -0,0 +1,113 @@
+// Copyright 2014 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.
+
+library stocks;
+
+import 'package:flutter/material.dart';
+import 'package:flutter/rendering.dart' show
+  debugPaintSizeEnabled,
+  debugPaintBaselinesEnabled,
+  debugPaintLayerBordersEnabled,
+  debugPaintPointersEnabled,
+  debugRepaintRainbowEnabled;
+
+import 'i18n/stock_strings.dart';
+import 'stock_data.dart';
+import 'stock_home.dart';
+import 'stock_settings.dart';
+import 'stock_symbol_viewer.dart';
+import 'stock_types.dart';
+
+class StocksApp extends StatefulWidget {
+  @override
+  StocksAppState createState() => StocksAppState();
+}
+
+class StocksAppState extends State<StocksApp> {
+  StockData stocks;
+
+  StockConfiguration _configuration = StockConfiguration(
+    stockMode: StockMode.optimistic,
+    backupMode: BackupMode.enabled,
+    debugShowGrid: false,
+    debugShowSizes: false,
+    debugShowBaselines: false,
+    debugShowLayers: false,
+    debugShowPointers: false,
+    debugShowRainbow: false,
+    showPerformanceOverlay: false,
+    showSemanticsDebugger: false,
+  );
+
+  @override
+  void initState() {
+    super.initState();
+    stocks = StockData();
+  }
+
+  void configurationUpdater(StockConfiguration value) {
+    setState(() {
+      _configuration = value;
+    });
+  }
+
+  ThemeData get theme {
+    switch (_configuration.stockMode) {
+      case StockMode.optimistic:
+        return ThemeData(
+          brightness: Brightness.light,
+          primarySwatch: Colors.purple,
+        );
+      case StockMode.pessimistic:
+        return ThemeData(
+          brightness: Brightness.dark,
+          accentColor: Colors.redAccent,
+        );
+    }
+    assert(_configuration.stockMode != null);
+    return null;
+  }
+
+  Route<dynamic> _getRoute(RouteSettings settings) {
+    if (settings.name == '/stock') {
+      final String symbol = settings.arguments as String;
+      return MaterialPageRoute<void>(
+        settings: settings,
+        builder: (BuildContext context) => StockSymbolPage(symbol: symbol, stocks: stocks),
+      );
+    }
+    // The other paths we support are in the routes table.
+    return null;
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    assert(() {
+      debugPaintSizeEnabled = _configuration.debugShowSizes;
+      debugPaintBaselinesEnabled = _configuration.debugShowBaselines;
+      debugPaintLayerBordersEnabled = _configuration.debugShowLayers;
+      debugPaintPointersEnabled = _configuration.debugShowPointers;
+      debugRepaintRainbowEnabled = _configuration.debugShowRainbow;
+      return true;
+    }());
+    return MaterialApp(
+      title: 'Stocks',
+      theme: theme,
+      localizationsDelegates: StockStrings.localizationsDelegates,
+      supportedLocales: StockStrings.supportedLocales,
+      debugShowMaterialGrid: _configuration.debugShowGrid,
+      showPerformanceOverlay: _configuration.showPerformanceOverlay,
+      showSemanticsDebugger: _configuration.showSemanticsDebugger,
+      routes: <String, WidgetBuilder>{
+         '/':         (BuildContext context) => StockHome(stocks, _configuration, configurationUpdater),
+         '/settings': (BuildContext context) => StockSettings(_configuration, configurationUpdater),
+      },
+      onGenerateRoute: _getRoute,
+    );
+  }
+}
+
+void main() {
+  runApp(StocksApp());
+}
diff --git a/dev/benchmarks/test_apps/stocks/lib/stock_arrow.dart b/dev/benchmarks/test_apps/stocks/lib/stock_arrow.dart
new file mode 100644
index 0000000..e969edd
--- /dev/null
+++ b/dev/benchmarks/test_apps/stocks/lib/stock_arrow.dart
@@ -0,0 +1,87 @@
+// Copyright 2014 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:math' as math;
+
+import 'package:flutter/material.dart';
+
+class StockArrowPainter extends CustomPainter {
+  StockArrowPainter({ this.color, this.percentChange });
+
+  final Color color;
+  final double percentChange;
+
+  @override
+  void paint(Canvas canvas, Size size) {
+    final Paint paint = Paint()..color = color;
+    paint.strokeWidth = 1.0;
+    const double padding = 2.0;
+    assert(padding > paint.strokeWidth / 2.0); // make sure the circle remains inside the box
+    final double r = (size.shortestSide - padding) / 2.0; // radius of the circle
+    final double centerX = padding + r;
+    final double centerY = padding + r;
+
+    // Draw the arrow.
+    const double w = 8.0;
+    double h = 5.0;
+    double arrowY;
+    if (percentChange < 0.0) {
+      h = -h;
+      arrowY = centerX + 1.0;
+    } else {
+      arrowY = centerX - 1.0;
+    }
+    final Path path = Path();
+    path.moveTo(centerX, arrowY - h); // top of the arrow
+    path.lineTo(centerX + w, arrowY + h);
+    path.lineTo(centerX - w, arrowY + h);
+    path.close();
+    paint.style = PaintingStyle.fill;
+    canvas.drawPath(path, paint);
+
+    // Draw a circle that circumscribes the arrow.
+    paint.style = PaintingStyle.stroke;
+    canvas.drawCircle(Offset(centerX, centerY), r, paint);
+  }
+
+  @override
+  bool shouldRepaint(StockArrowPainter oldDelegate) {
+    return oldDelegate.color != color
+        || oldDelegate.percentChange != percentChange;
+  }
+}
+
+class StockArrow extends StatelessWidget {
+  const StockArrow({ Key key, this.percentChange }) : super(key: key);
+
+  final double percentChange;
+
+  int _colorIndexForPercentChange(double percentChange) {
+    const double maxPercent = 10.0;
+    final double normalizedPercentChange = math.min(percentChange.abs(), maxPercent) / maxPercent;
+    return 100 + (normalizedPercentChange * 8.0).floor() * 100;
+  }
+
+  Color _colorForPercentChange(double percentChange) {
+    if (percentChange > 0)
+      return Colors.green[_colorIndexForPercentChange(percentChange)];
+    return Colors.red[_colorIndexForPercentChange(percentChange)];
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return Container(
+      width: 40.0,
+      height: 40.0,
+      margin: const EdgeInsets.symmetric(horizontal: 5.0),
+      child: CustomPaint(
+        painter: StockArrowPainter(
+          // TODO(jackson): This should change colors with the theme
+          color: _colorForPercentChange(percentChange),
+          percentChange: percentChange,
+        ),
+      ),
+    );
+  }
+}
diff --git a/dev/benchmarks/test_apps/stocks/lib/stock_data.dart b/dev/benchmarks/test_apps/stocks/lib/stock_data.dart
new file mode 100644
index 0000000..35a79ca
--- /dev/null
+++ b/dev/benchmarks/test_apps/stocks/lib/stock_data.dart
@@ -0,0 +1,101 @@
+// Copyright 2014 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.
+
+// Snapshot from http://www.nasdaq.com/screening/company-list.aspx
+// Fetched 2/23/2014.
+// "Symbol","Name","LastSale","MarketCap","IPOyear","Sector","industry","Summary Quote",
+// Data in stock_data.json
+
+import 'dart:convert';
+import 'dart:math' as math;
+
+import 'package:flutter/foundation.dart';
+import 'package:http/http.dart' as http;
+
+final math.Random _rng = math.Random();
+
+class Stock {
+  Stock(this.symbol, this.name, this.lastSale, this.marketCap, this.percentChange);
+
+  Stock.fromFields(List<String> fields) {
+    // FIXME: This class should only have static data, not lastSale, etc.
+    // "Symbol","Name","LastSale","MarketCap","IPOyear","Sector","industry","Summary Quote",
+    lastSale = 0.0;
+    try {
+      lastSale = double.parse(fields[2]);
+    } catch (_) { }
+    symbol = fields[0];
+    name = fields[1];
+    marketCap = fields[4];
+    percentChange = (_rng.nextDouble() * 20) - 10;
+  }
+
+  String symbol;
+  String name;
+  double lastSale;
+  String marketCap;
+  double percentChange;
+}
+
+class StockData extends ChangeNotifier {
+  StockData() {
+    if (actuallyFetchData) {
+      _httpClient = http.Client();
+      _fetchNextChunk();
+    }
+  }
+
+  final List<String> _symbols = <String>[];
+  final Map<String, Stock> _stocks = <String, Stock>{};
+
+  List<String> get allSymbols => _symbols;
+
+  Stock operator [](String symbol) => _stocks[symbol];
+
+  bool get loading => _httpClient != null;
+
+  void add(List<dynamic> data) {
+    for (final List<dynamic> fields in data.cast<List<dynamic>>()) {
+      final Stock stock = Stock.fromFields(fields.cast<String>());
+      _symbols.add(stock.symbol);
+      _stocks[stock.symbol] = stock;
+    }
+    _symbols.sort();
+    notifyListeners();
+  }
+
+  static const int _chunkCount = 30;
+  int _nextChunk = 0;
+
+  String _urlToFetch(int chunk) {
+    return 'https://domokit.github.io/examples/stocks/data/stock_data_$chunk.json';
+  }
+
+  http.Client _httpClient;
+
+  static bool actuallyFetchData = true;
+
+  void _fetchNextChunk() {
+    _httpClient.get(_urlToFetch(_nextChunk++)).then<void>((http.Response response) {
+      final String json = response.body;
+      if (json == null) {
+        debugPrint('Failed to load stock data chunk ${_nextChunk - 1}');
+        _end();
+        return;
+      }
+      const JsonDecoder decoder = JsonDecoder();
+      add(decoder.convert(json) as List<dynamic>);
+      if (_nextChunk < _chunkCount) {
+        _fetchNextChunk();
+      } else {
+        _end();
+      }
+    });
+  }
+
+  void _end() {
+    _httpClient?.close();
+    _httpClient = null;
+  }
+}
diff --git a/dev/benchmarks/test_apps/stocks/lib/stock_home.dart b/dev/benchmarks/test_apps/stocks/lib/stock_home.dart
new file mode 100644
index 0000000..48046cd
--- /dev/null
+++ b/dev/benchmarks/test_apps/stocks/lib/stock_home.dart
@@ -0,0 +1,357 @@
+// Copyright 2014 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/rendering.dart' show debugDumpRenderTree, debugDumpLayerTree, debugDumpSemanticsTree, DebugSemanticsDumpOrder;
+import 'package:flutter/scheduler.dart' show timeDilation;
+import 'package:flutter/gestures.dart' show DragStartBehavior;
+
+import 'i18n/stock_strings.dart';
+import 'stock_data.dart';
+import 'stock_list.dart';
+import 'stock_symbol_viewer.dart';
+import 'stock_types.dart';
+
+typedef ModeUpdater = void Function(StockMode mode);
+
+enum _StockMenuItem { autorefresh, refresh, speedUp, speedDown }
+enum StockHomeTab { market, portfolio }
+
+class _NotImplementedDialog extends StatelessWidget {
+  @override
+  Widget build(BuildContext context) {
+    return AlertDialog(
+      title: const Text('Not Implemented'),
+      content: const Text('This feature has not yet been implemented.'),
+      actions: <Widget>[
+        FlatButton(
+          onPressed: debugDumpApp,
+          child: Row(
+            children: <Widget>[
+              const Icon(
+                Icons.dvr,
+                size: 18.0,
+              ),
+              Container(
+                width: 8.0,
+              ),
+              const Text('DUMP APP TO CONSOLE'),
+            ],
+          ),
+        ),
+        FlatButton(
+          onPressed: () {
+            Navigator.pop(context, false);
+          },
+          child: const Text('OH WELL'),
+        ),
+      ],
+    );
+  }
+}
+
+class StockHome extends StatefulWidget {
+  const StockHome(this.stocks, this.configuration, this.updater);
+
+  final StockData stocks;
+  final StockConfiguration configuration;
+  final ValueChanged<StockConfiguration> updater;
+
+  @override
+  StockHomeState createState() => StockHomeState();
+}
+
+class StockHomeState extends State<StockHome> {
+  final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
+  final TextEditingController _searchQuery = TextEditingController();
+  bool _isSearching = false;
+  bool _autorefresh = false;
+
+  void _handleSearchBegin() {
+    ModalRoute.of(context).addLocalHistoryEntry(LocalHistoryEntry(
+      onRemove: () {
+        setState(() {
+          _isSearching = false;
+          _searchQuery.clear();
+        });
+      },
+    ));
+    setState(() {
+      _isSearching = true;
+    });
+  }
+
+  void _handleStockModeChange(StockMode value) {
+    if (widget.updater != null)
+      widget.updater(widget.configuration.copyWith(stockMode: value));
+  }
+
+  void _handleStockMenu(BuildContext context, _StockMenuItem value) {
+    switch (value) {
+      case _StockMenuItem.autorefresh:
+        setState(() {
+          _autorefresh = !_autorefresh;
+        });
+        break;
+      case _StockMenuItem.refresh:
+        showDialog<void>(
+          context: context,
+          builder: (BuildContext context) => _NotImplementedDialog(),
+        );
+        break;
+      case _StockMenuItem.speedUp:
+        timeDilation /= 5.0;
+        break;
+      case _StockMenuItem.speedDown:
+        timeDilation *= 5.0;
+        break;
+    }
+  }
+
+  Widget _buildDrawer(BuildContext context) {
+    return Drawer(
+      child: ListView(
+        dragStartBehavior: DragStartBehavior.down,
+        children: <Widget>[
+          const DrawerHeader(child: Center(child: Text('Stocks'))),
+          const ListTile(
+            leading: Icon(Icons.assessment),
+            title: Text('Stock List'),
+            selected: true,
+          ),
+          const ListTile(
+            leading: Icon(Icons.account_balance),
+            title: Text('Account Balance'),
+            enabled: false,
+          ),
+          ListTile(
+            leading: const Icon(Icons.dvr),
+            title: const Text('Dump App to Console'),
+            onTap: () {
+              try {
+                debugDumpApp();
+                debugDumpRenderTree();
+                debugDumpLayerTree();
+                debugDumpSemanticsTree(DebugSemanticsDumpOrder.traversalOrder);
+              } catch (e, stack) {
+                debugPrint('Exception while dumping app:\n$e\n$stack');
+              }
+            },
+          ),
+          const Divider(),
+          ListTile(
+            leading: const Icon(Icons.thumb_up),
+            title: const Text('Optimistic'),
+            trailing: Radio<StockMode>(
+              value: StockMode.optimistic,
+              groupValue: widget.configuration.stockMode,
+              onChanged: _handleStockModeChange,
+            ),
+            onTap: () {
+              _handleStockModeChange(StockMode.optimistic);
+            },
+          ),
+          ListTile(
+            leading: const Icon(Icons.thumb_down),
+            title: const Text('Pessimistic'),
+            trailing: Radio<StockMode>(
+              value: StockMode.pessimistic,
+              groupValue: widget.configuration.stockMode,
+              onChanged: _handleStockModeChange,
+            ),
+            onTap: () {
+              _handleStockModeChange(StockMode.pessimistic);
+            },
+          ),
+          const Divider(),
+          ListTile(
+            leading: const Icon(Icons.settings),
+            title: const Text('Settings'),
+            onTap: _handleShowSettings,
+          ),
+          ListTile(
+            leading: const Icon(Icons.help),
+            title: const Text('About'),
+            onTap: _handleShowAbout,
+          ),
+        ],
+      ),
+    );
+  }
+
+  void _handleShowSettings() {
+    Navigator.popAndPushNamed(context, '/settings');
+  }
+
+  void _handleShowAbout() {
+    showAboutDialog(context: context);
+  }
+
+  AppBar buildAppBar() {
+    return AppBar(
+      elevation: 0.0,
+      title: Text(StockStrings.of(context).title),
+      actions: <Widget>[
+        IconButton(
+          icon: const Icon(Icons.search),
+          onPressed: _handleSearchBegin,
+          tooltip: 'Search',
+        ),
+        PopupMenuButton<_StockMenuItem>(
+          onSelected: (_StockMenuItem value) { _handleStockMenu(context, value); },
+          itemBuilder: (BuildContext context) => <PopupMenuItem<_StockMenuItem>>[
+            CheckedPopupMenuItem<_StockMenuItem>(
+              value: _StockMenuItem.autorefresh,
+              checked: _autorefresh,
+              child: const Text('Autorefresh'),
+            ),
+            const PopupMenuItem<_StockMenuItem>(
+              value: _StockMenuItem.refresh,
+              child: Text('Refresh'),
+            ),
+            const PopupMenuItem<_StockMenuItem>(
+              value: _StockMenuItem.speedUp,
+              child: Text('Increase animation speed'),
+            ),
+            const PopupMenuItem<_StockMenuItem>(
+              value: _StockMenuItem.speedDown,
+              child: Text('Decrease animation speed'),
+            ),
+          ],
+        ),
+      ],
+      bottom: TabBar(
+        tabs: <Widget>[
+          Tab(text: StockStrings.of(context).market),
+          Tab(text: StockStrings.of(context).portfolio),
+        ],
+      ),
+    );
+  }
+
+  static Iterable<Stock> _getStockList(StockData stocks, Iterable<String> symbols) {
+    return symbols.map<Stock>((String symbol) => stocks[symbol])
+        .where((Stock stock) => stock != null);
+  }
+
+  Iterable<Stock> _filterBySearchQuery(Iterable<Stock> stocks) {
+    if (_searchQuery.text.isEmpty)
+      return stocks;
+    final RegExp regexp = RegExp(_searchQuery.text, caseSensitive: false);
+    return stocks.where((Stock stock) => stock.symbol.contains(regexp));
+  }
+
+  void _buyStock(Stock stock) {
+    setState(() {
+      stock.percentChange = 100.0 * (1.0 / stock.lastSale);
+      stock.lastSale += 1.0;
+    });
+    _scaffoldKey.currentState.showSnackBar(SnackBar(
+      content: Text('Purchased ${stock.symbol} for ${stock.lastSale}'),
+      action: SnackBarAction(
+        label: 'BUY MORE',
+        onPressed: () {
+          _buyStock(stock);
+        },
+      ),
+    ));
+  }
+
+  Widget _buildStockList(BuildContext context, Iterable<Stock> stocks, StockHomeTab tab) {
+    return StockList(
+      stocks: stocks.toList(),
+      onAction: _buyStock,
+      onOpen: (Stock stock) {
+        Navigator.pushNamed(context, '/stock', arguments: stock.symbol);
+      },
+      onShow: (Stock stock) {
+        _scaffoldKey.currentState.showBottomSheet<void>((BuildContext context) => StockSymbolBottomSheet(stock: stock));
+      },
+    );
+  }
+
+  Widget _buildStockTab(BuildContext context, StockHomeTab tab, List<String> stockSymbols) {
+    return AnimatedBuilder(
+      key: ValueKey<StockHomeTab>(tab),
+      animation: Listenable.merge(<Listenable>[_searchQuery, widget.stocks]),
+      builder: (BuildContext context, Widget child) {
+        return _buildStockList(context, _filterBySearchQuery(_getStockList(widget.stocks, stockSymbols)).toList(), tab);
+      },
+    );
+  }
+
+  static const List<String> portfolioSymbols = <String>['AAPL','FIZZ', 'FIVE', 'FLAT', 'ZINC', 'ZNGA'];
+
+  AppBar buildSearchBar() {
+    return AppBar(
+      leading: BackButton(
+        color: Theme.of(context).accentColor,
+      ),
+      title: TextField(
+        controller: _searchQuery,
+        autofocus: true,
+        decoration: const InputDecoration(
+          hintText: 'Search stocks',
+        ),
+      ),
+      backgroundColor: Theme.of(context).canvasColor,
+    );
+  }
+
+  void _handleCreateCompany() {
+    showModalBottomSheet<void>(
+      context: context,
+      builder: (BuildContext context) => _CreateCompanySheet(),
+    );
+  }
+
+  Widget buildFloatingActionButton() {
+    return FloatingActionButton(
+      tooltip: 'Create company',
+      child: const Icon(Icons.add),
+      backgroundColor: Theme.of(context).accentColor,
+      onPressed: _handleCreateCompany,
+    );
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return DefaultTabController(
+      length: 2,
+      child: Scaffold(
+        drawerDragStartBehavior: DragStartBehavior.down,
+        key: _scaffoldKey,
+        appBar: _isSearching ? buildSearchBar() : buildAppBar(),
+        floatingActionButton: buildFloatingActionButton(),
+        drawer: _buildDrawer(context),
+        body: TabBarView(
+          dragStartBehavior: DragStartBehavior.down,
+          children: <Widget>[
+            _buildStockTab(context, StockHomeTab.market, widget.stocks.allSymbols),
+            _buildStockTab(context, StockHomeTab.portfolio, portfolioSymbols),
+          ],
+        ),
+      ),
+    );
+  }
+}
+
+class _CreateCompanySheet extends StatelessWidget {
+  @override
+  Widget build(BuildContext context) {
+    return Column(
+      children: const <Widget>[
+        TextField(
+          autofocus: true,
+          decoration: InputDecoration(
+            hintText: 'Company Name',
+          ),
+        ),
+        Text('(This demo is not yet complete.)'),
+        // For example, we could add a button that actually updates the list
+        // and then contacts the server, etc.
+      ],
+    );
+  }
+}
diff --git a/dev/benchmarks/test_apps/stocks/lib/stock_list.dart b/dev/benchmarks/test_apps/stocks/lib/stock_list.dart
new file mode 100644
index 0000000..17c87ea
--- /dev/null
+++ b/dev/benchmarks/test_apps/stocks/lib/stock_list.dart
@@ -0,0 +1,34 @@
+// Copyright 2014 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 'stock_data.dart';
+import 'stock_row.dart';
+
+class StockList extends StatelessWidget {
+  const StockList({ Key key, this.stocks, this.onOpen, this.onShow, this.onAction }) : super(key: key);
+
+  final List<Stock> stocks;
+  final StockRowActionCallback onOpen;
+  final StockRowActionCallback onShow;
+  final StockRowActionCallback onAction;
+
+  @override
+  Widget build(BuildContext context) {
+    return ListView.builder(
+      key: const ValueKey<String>('stock-list'),
+      itemExtent: StockRow.kHeight,
+      itemCount: stocks.length,
+      itemBuilder: (BuildContext context, int index) {
+        return StockRow(
+          stock: stocks[index],
+          onPressed: onOpen,
+          onDoubleTap: onShow,
+          onLongPressed: onAction,
+        );
+      },
+    );
+  }
+}
diff --git a/dev/benchmarks/test_apps/stocks/lib/stock_row.dart b/dev/benchmarks/test_apps/stocks/lib/stock_row.dart
new file mode 100644
index 0000000..aeb1e09
--- /dev/null
+++ b/dev/benchmarks/test_apps/stocks/lib/stock_row.dart
@@ -0,0 +1,89 @@
+// Copyright 2014 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 'stock_arrow.dart';
+import 'stock_data.dart';
+
+typedef StockRowActionCallback = void Function(Stock stock);
+
+class StockRow extends StatelessWidget {
+  StockRow({
+    this.stock,
+    this.onPressed,
+    this.onDoubleTap,
+    this.onLongPressed,
+  }) : super(key: ObjectKey(stock));
+
+  final Stock stock;
+  final StockRowActionCallback onPressed;
+  final StockRowActionCallback onDoubleTap;
+  final StockRowActionCallback onLongPressed;
+
+  static const double kHeight = 79.0;
+
+  GestureTapCallback _getHandler(StockRowActionCallback callback) {
+    return callback == null ? null : () => callback(stock);
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    final String lastSale = '\$${stock.lastSale.toStringAsFixed(2)}';
+    String changeInPrice = '${stock.percentChange.toStringAsFixed(2)}%';
+    if (stock.percentChange > 0)
+      changeInPrice = '+' + changeInPrice;
+    return InkWell(
+      key: ValueKey<String>(stock.symbol),
+      onTap: _getHandler(onPressed),
+      onDoubleTap: _getHandler(onDoubleTap),
+      onLongPress: _getHandler(onLongPressed),
+      child: Container(
+        padding: const EdgeInsets.fromLTRB(16.0, 16.0, 16.0, 20.0),
+        decoration: BoxDecoration(
+          border: Border(
+            bottom: BorderSide(color: Theme.of(context).dividerColor)
+          )
+        ),
+        child: Row(
+          children: <Widget>[
+            Container(
+              margin: const EdgeInsets.only(right: 5.0),
+              child: Hero(
+                tag: stock,
+                child: StockArrow(percentChange: stock.percentChange),
+              ),
+            ),
+            Expanded(
+              child: Row(
+                children: <Widget>[
+                  Expanded(
+                    flex: 2,
+                    child: Text(
+                      stock.symbol
+                    ),
+                  ),
+                  Expanded(
+                    child: Text(
+                      lastSale,
+                      textAlign: TextAlign.right,
+                    ),
+                  ),
+                  Expanded(
+                    child: Text(
+                      changeInPrice,
+                      textAlign: TextAlign.right,
+                    ),
+                  ),
+                ],
+                crossAxisAlignment: CrossAxisAlignment.baseline,
+                textBaseline: DefaultTextStyle.of(context).style.textBaseline,
+              ),
+            ),
+          ],
+        ),
+      ),
+    );
+  }
+}
diff --git a/dev/benchmarks/test_apps/stocks/lib/stock_settings.dart b/dev/benchmarks/test_apps/stocks/lib/stock_settings.dart
new file mode 100644
index 0000000..aa95ad8
--- /dev/null
+++ b/dev/benchmarks/test_apps/stocks/lib/stock_settings.dart
@@ -0,0 +1,218 @@
+// Copyright 2014 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 'stock_types.dart';
+
+class StockSettings extends StatefulWidget {
+  const StockSettings(this.configuration, this.updater);
+
+  final StockConfiguration configuration;
+  final ValueChanged<StockConfiguration> updater;
+
+  @override
+  StockSettingsState createState() => StockSettingsState();
+}
+
+class StockSettingsState extends State<StockSettings> {
+  void _handleOptimismChanged(bool value) {
+    value ??= false;
+    sendUpdates(widget.configuration.copyWith(stockMode: value ? StockMode.optimistic : StockMode.pessimistic));
+  }
+
+  void _handleBackupChanged(bool value) {
+    sendUpdates(widget.configuration.copyWith(backupMode: value ? BackupMode.enabled : BackupMode.disabled));
+  }
+
+  void _handleShowGridChanged(bool value) {
+    sendUpdates(widget.configuration.copyWith(debugShowGrid: value));
+  }
+
+  void _handleShowSizesChanged(bool value) {
+    sendUpdates(widget.configuration.copyWith(debugShowSizes: value));
+  }
+
+  void _handleShowBaselinesChanged(bool value) {
+    sendUpdates(widget.configuration.copyWith(debugShowBaselines: value));
+  }
+
+  void _handleShowLayersChanged(bool value) {
+    sendUpdates(widget.configuration.copyWith(debugShowLayers: value));
+  }
+
+  void _handleShowPointersChanged(bool value) {
+    sendUpdates(widget.configuration.copyWith(debugShowPointers: value));
+  }
+
+  void _handleShowRainbowChanged(bool value) {
+    sendUpdates(widget.configuration.copyWith(debugShowRainbow: value));
+  }
+
+
+  void _handleShowPerformanceOverlayChanged(bool value) {
+    sendUpdates(widget.configuration.copyWith(showPerformanceOverlay: value));
+  }
+
+  void _handleShowSemanticsDebuggerChanged(bool value) {
+    sendUpdates(widget.configuration.copyWith(showSemanticsDebugger: value));
+  }
+
+  void _confirmOptimismChange() {
+    switch (widget.configuration.stockMode) {
+      case StockMode.optimistic:
+        _handleOptimismChanged(false);
+        break;
+      case StockMode.pessimistic:
+        showDialog<bool>(
+          context: context,
+          builder: (BuildContext context) {
+            return AlertDialog(
+              title: const Text('Change mode?'),
+              content: const Text('Optimistic mode means everything is awesome. Are you sure you can handle that?'),
+              actions: <Widget>[
+                FlatButton(
+                  child: const Text('NO THANKS'),
+                  onPressed: () {
+                    Navigator.pop(context, false);
+                  },
+                ),
+                FlatButton(
+                  child: const Text('AGREE'),
+                  onPressed: () {
+                    Navigator.pop(context, true);
+                  },
+                ),
+              ],
+            );
+          },
+        ).then<void>(_handleOptimismChanged);
+        break;
+    }
+  }
+
+  void sendUpdates(StockConfiguration value) {
+    if (widget.updater != null)
+      widget.updater(value);
+  }
+
+  AppBar buildAppBar(BuildContext context) {
+    return AppBar(
+      title: const Text('Settings'),
+    );
+  }
+
+  Widget buildSettingsPane(BuildContext context) {
+    final List<Widget> rows = <Widget>[
+      ListTile(
+        leading: const Icon(Icons.thumb_up),
+        title: const Text('Everything is awesome'),
+        onTap: _confirmOptimismChange,
+        trailing: Checkbox(
+          value: widget.configuration.stockMode == StockMode.optimistic,
+          onChanged: (bool value) => _confirmOptimismChange(),
+        ),
+      ),
+      ListTile(
+        leading: const Icon(Icons.backup),
+        title: const Text('Back up stock list to the cloud'),
+        onTap: () { _handleBackupChanged(!(widget.configuration.backupMode == BackupMode.enabled)); },
+        trailing: Switch(
+          value: widget.configuration.backupMode == BackupMode.enabled,
+          onChanged: _handleBackupChanged,
+        ),
+      ),
+      ListTile(
+        leading: const Icon(Icons.picture_in_picture),
+        title: const Text('Show rendering performance overlay'),
+        onTap: () { _handleShowPerformanceOverlayChanged(!widget.configuration.showPerformanceOverlay); },
+        trailing: Switch(
+          value: widget.configuration.showPerformanceOverlay,
+          onChanged: _handleShowPerformanceOverlayChanged,
+        ),
+      ),
+      ListTile(
+        leading: const Icon(Icons.accessibility),
+        title: const Text('Show semantics overlay'),
+        onTap: () { _handleShowSemanticsDebuggerChanged(!widget.configuration.showSemanticsDebugger); },
+        trailing: Switch(
+          value: widget.configuration.showSemanticsDebugger,
+          onChanged: _handleShowSemanticsDebuggerChanged,
+        ),
+      ),
+    ];
+    assert(() {
+      // material grid and size construction lines are only available in checked mode
+      rows.addAll(<Widget>[
+        ListTile(
+          leading: const Icon(Icons.border_clear),
+          title: const Text('Show material grid (for debugging)'),
+          onTap: () { _handleShowGridChanged(!widget.configuration.debugShowGrid); },
+          trailing: Switch(
+            value: widget.configuration.debugShowGrid,
+            onChanged: _handleShowGridChanged,
+          ),
+        ),
+        ListTile(
+          leading: const Icon(Icons.border_all),
+          title: const Text('Show construction lines (for debugging)'),
+          onTap: () { _handleShowSizesChanged(!widget.configuration.debugShowSizes); },
+          trailing: Switch(
+            value: widget.configuration.debugShowSizes,
+            onChanged: _handleShowSizesChanged,
+          ),
+        ),
+        ListTile(
+          leading: const Icon(Icons.format_color_text),
+          title: const Text('Show baselines (for debugging)'),
+          onTap: () { _handleShowBaselinesChanged(!widget.configuration.debugShowBaselines); },
+          trailing: Switch(
+            value: widget.configuration.debugShowBaselines,
+            onChanged: _handleShowBaselinesChanged,
+          ),
+        ),
+        ListTile(
+          leading: const Icon(Icons.filter_none),
+          title: const Text('Show layer boundaries (for debugging)'),
+          onTap: () { _handleShowLayersChanged(!widget.configuration.debugShowLayers); },
+          trailing: Switch(
+            value: widget.configuration.debugShowLayers,
+            onChanged: _handleShowLayersChanged,
+          ),
+        ),
+        ListTile(
+          leading: const Icon(Icons.mouse),
+          title: const Text('Show pointer hit-testing (for debugging)'),
+          onTap: () { _handleShowPointersChanged(!widget.configuration.debugShowPointers); },
+          trailing: Switch(
+            value: widget.configuration.debugShowPointers,
+            onChanged: _handleShowPointersChanged,
+          ),
+        ),
+        ListTile(
+          leading: const Icon(Icons.gradient),
+          title: const Text('Show repaint rainbow (for debugging)'),
+          onTap: () { _handleShowRainbowChanged(!widget.configuration.debugShowRainbow); },
+          trailing: Switch(
+            value: widget.configuration.debugShowRainbow,
+            onChanged: _handleShowRainbowChanged,
+          ),
+        ),
+      ]);
+      return true;
+    }());
+    return ListView(
+      padding: const EdgeInsets.symmetric(vertical: 20.0),
+      children: rows,
+    );
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return Scaffold(
+      appBar: buildAppBar(context),
+      body: buildSettingsPane(context),
+    );
+  }
+}
diff --git a/dev/benchmarks/test_apps/stocks/lib/stock_symbol_viewer.dart b/dev/benchmarks/test_apps/stocks/lib/stock_symbol_viewer.dart
new file mode 100644
index 0000000..e375ace
--- /dev/null
+++ b/dev/benchmarks/test_apps/stocks/lib/stock_symbol_viewer.dart
@@ -0,0 +1,133 @@
+// Copyright 2014 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 'stock_arrow.dart';
+import 'stock_data.dart';
+
+class _StockSymbolView extends StatelessWidget {
+  const _StockSymbolView({ this.stock, this.arrow });
+
+  final Stock stock;
+  final Widget arrow;
+
+  @override
+  Widget build(BuildContext context) {
+    assert(stock != null);
+    final String lastSale = '\$${stock.lastSale.toStringAsFixed(2)}';
+    String changeInPrice = '${stock.percentChange.toStringAsFixed(2)}%';
+    if (stock.percentChange > 0)
+      changeInPrice = '+' + changeInPrice;
+
+    final TextStyle headings = Theme.of(context).textTheme.bodyText1;
+    return Container(
+      padding: const EdgeInsets.all(20.0),
+      child: Column(
+        children: <Widget>[
+          Row(
+            children: <Widget>[
+              Text(
+                '${stock.symbol}',
+                key: ValueKey<String>('${stock.symbol}_symbol_name'),
+                style: Theme.of(context).textTheme.headline3,
+              ),
+              arrow,
+            ],
+            mainAxisAlignment: MainAxisAlignment.spaceBetween,
+          ),
+          Text('Last Sale', style: headings),
+          Text('$lastSale ($changeInPrice)'),
+          Container(
+            height: 8.0
+          ),
+          Text('Market Cap', style: headings),
+          Text('${stock.marketCap}'),
+          Container(
+            height: 8.0
+          ),
+          RichText(
+            text: TextSpan(
+              style: DefaultTextStyle.of(context).style.merge(const TextStyle(fontSize: 8.0)),
+              text: 'Prices may be delayed by ',
+              children: const <TextSpan>[
+                TextSpan(text: 'several', style: TextStyle(fontStyle: FontStyle.italic)),
+                TextSpan(text: ' years.'),
+              ],
+            ),
+          ),
+        ],
+        mainAxisSize: MainAxisSize.min,
+      ),
+    );
+  }
+}
+
+class StockSymbolPage extends StatelessWidget {
+  const StockSymbolPage({ this.symbol, this.stocks });
+
+  final String symbol;
+  final StockData stocks;
+
+  @override
+  Widget build(BuildContext context) {
+    return AnimatedBuilder(
+      animation: stocks,
+      builder: (BuildContext context, Widget child) {
+        final Stock stock = stocks[symbol];
+        return Scaffold(
+          appBar: AppBar(
+            title: Text(stock?.name ?? symbol),
+          ),
+          body: SingleChildScrollView(
+            child: Container(
+              margin: const EdgeInsets.all(20.0),
+              child: Card(
+                child: AnimatedCrossFade(
+                  duration: const Duration(milliseconds: 300),
+                  firstChild: const Padding(
+                    padding: EdgeInsets.all(20.0),
+                    child: Center(child: CircularProgressIndicator()),
+                  ),
+                  secondChild: stock != null
+                    ? _StockSymbolView(
+                      stock: stock,
+                      arrow: Hero(
+                        tag: stock,
+                        child: StockArrow(percentChange: stock.percentChange),
+                      ),
+                    ) : Padding(
+                        padding: const EdgeInsets.all(20.0),
+                        child: Center(child: Text('$symbol not found')),
+                    ),
+                  crossFadeState: stock == null && stocks.loading ? CrossFadeState.showFirst : CrossFadeState.showSecond,
+                ),
+              ),
+            ),
+          ),
+        );
+      },
+    );
+  }
+}
+
+class StockSymbolBottomSheet extends StatelessWidget {
+  const StockSymbolBottomSheet({ this.stock });
+
+  final Stock stock;
+
+  @override
+  Widget build(BuildContext context) {
+    return Container(
+      padding: const EdgeInsets.all(10.0),
+      decoration: const BoxDecoration(
+        border: Border(top: BorderSide(color: Colors.black26))
+      ),
+      child: _StockSymbolView(
+        stock: stock,
+        arrow: StockArrow(percentChange: stock.percentChange),
+      ),
+   );
+  }
+}
diff --git a/dev/benchmarks/test_apps/stocks/lib/stock_types.dart b/dev/benchmarks/test_apps/stocks/lib/stock_types.dart
new file mode 100644
index 0000000..132bc46
--- /dev/null
+++ b/dev/benchmarks/test_apps/stocks/lib/stock_types.dart
@@ -0,0 +1,69 @@
+// Copyright 2014 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/foundation.dart';
+
+enum StockMode { optimistic, pessimistic }
+enum BackupMode { enabled, disabled }
+
+class StockConfiguration {
+  StockConfiguration({
+    @required this.stockMode,
+    @required this.backupMode,
+    @required this.debugShowGrid,
+    @required this.debugShowSizes,
+    @required this.debugShowBaselines,
+    @required this.debugShowLayers,
+    @required this.debugShowPointers,
+    @required this.debugShowRainbow,
+    @required this.showPerformanceOverlay,
+    @required this.showSemanticsDebugger,
+  }) : assert(stockMode != null),
+       assert(backupMode != null),
+       assert(debugShowGrid != null),
+       assert(debugShowSizes != null),
+       assert(debugShowBaselines != null),
+       assert(debugShowLayers != null),
+       assert(debugShowPointers != null),
+       assert(debugShowRainbow != null),
+       assert(showPerformanceOverlay != null),
+       assert(showSemanticsDebugger != null);
+
+  final StockMode stockMode;
+  final BackupMode backupMode;
+  final bool debugShowGrid;
+  final bool debugShowSizes;
+  final bool debugShowBaselines;
+  final bool debugShowLayers;
+  final bool debugShowPointers;
+  final bool debugShowRainbow;
+  final bool showPerformanceOverlay;
+  final bool showSemanticsDebugger;
+
+  StockConfiguration copyWith({
+    StockMode stockMode,
+    BackupMode backupMode,
+    bool debugShowGrid,
+    bool debugShowSizes,
+    bool debugShowBaselines,
+    bool debugShowLayers,
+    bool debugShowPointers,
+    bool debugShowRainbow,
+    bool showPerformanceOverlay,
+    bool showSemanticsDebugger,
+  }) {
+    return StockConfiguration(
+      stockMode: stockMode ?? this.stockMode,
+      backupMode: backupMode ?? this.backupMode,
+      debugShowGrid: debugShowGrid ?? this.debugShowGrid,
+      debugShowSizes: debugShowSizes ?? this.debugShowSizes,
+      debugShowBaselines: debugShowBaselines ?? this.debugShowBaselines,
+      debugShowLayers: debugShowLayers ?? this.debugShowLayers,
+      debugShowPointers: debugShowPointers ?? this.debugShowPointers,
+      debugShowRainbow: debugShowRainbow ?? this.debugShowRainbow,
+      showPerformanceOverlay: showPerformanceOverlay ?? this.showPerformanceOverlay,
+      showSemanticsDebugger: showSemanticsDebugger ?? this.showSemanticsDebugger,
+    );
+  }
+}
diff --git a/dev/benchmarks/test_apps/stocks/pubspec.yaml b/dev/benchmarks/test_apps/stocks/pubspec.yaml
new file mode 100644
index 0000000..ec51a59
--- /dev/null
+++ b/dev/benchmarks/test_apps/stocks/pubspec.yaml
@@ -0,0 +1,90 @@
+name: stocks
+
+environment:
+  # The pub client defaults to an <2.0.0 sdk constraint which we need to explicitly overwrite.
+  sdk: ">=2.0.0-dev.68.0 <3.0.0"
+
+dependencies:
+  flutter:
+    sdk: flutter
+  flutter_localizations:
+    sdk: flutter
+  intl: 0.16.1
+  intl_translation: 0.17.9
+  http: 0.12.0+4
+  isolate: 2.0.2
+
+  _fe_analyzer_shared: 1.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+  analyzer: 0.39.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+  args: 1.5.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+  async: 2.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+  charcode: 1.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+  collection: 1.14.11 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+  convert: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+  crypto: 2.1.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+  csslib: 0.16.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+  dart_style: 1.3.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+  glob: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+  html: 0.14.0+3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+  http_parser: 3.1.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+  js: 0.6.1+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+  meta: 1.1.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+  node_interop: 1.0.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+  node_io: 1.0.1+2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+  package_config: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+  path: 1.6.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+  pedantic: 1.8.0+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+  petitparser: 2.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+  pub_semver: 1.4.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+  source_span: 1.5.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+  string_scanner: 1.0.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+  term_glyph: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+  typed_data: 1.1.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+  vector_math: 2.0.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+  watcher: 0.9.7+13 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+  yaml: 2.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+
+dev_dependencies:
+  flutter_test:
+    sdk: flutter
+  flutter_driver:
+    sdk: flutter
+  test: 1.9.4
+
+  archive: 2.0.11 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+  boolean_selector: 1.0.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+  coverage: 0.13.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+  file: 5.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+  http_multi_server: 2.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+  image: 2.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+  io: 0.3.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+  json_rpc_2: 2.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+  logging: 0.11.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+  matcher: 0.12.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+  mime: 0.9.6+3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+  multi_server_socket: 1.0.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+  node_preamble: 1.4.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+  package_resolver: 1.0.10 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+  pool: 1.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+  quiver: 2.0.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+  shelf: 0.7.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+  shelf_packages_handler: 1.0.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+  shelf_static: 0.2.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+  shelf_web_socket: 0.2.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+  source_map_stack_trace: 1.1.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+  source_maps: 0.10.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+  stack_trace: 1.9.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+  stream_channel: 2.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+  sync_http: 0.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+  test_api: 0.2.11 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+  test_core: 0.2.15 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+  vm_service: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+  vm_service_client: 0.2.6+2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+  web_socket_channel: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+  webdriver: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+  xml: 3.5.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
+
+flutter:
+  uses-material-design: true
+
+# PUBSPEC CHECKSUM: 9b6f
diff --git a/dev/benchmarks/test_apps/stocks/test/icon_color_test.dart b/dev/benchmarks/test_apps/stocks/test/icon_color_test.dart
new file mode 100644
index 0000000..9e965f2
--- /dev/null
+++ b/dev/benchmarks/test_apps/stocks/test/icon_color_test.dart
@@ -0,0 +1,91 @@
+// Copyright 2014 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:ui' as ui show window;
+
+import 'package:flutter/material.dart';
+import 'package:flutter/rendering.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:stocks/main.dart' as stocks;
+import 'package:stocks/stock_data.dart' as stock_data;
+
+Element findElementOfExactWidgetTypeGoingDown(Element node, Type targetType) {
+  void walker(Element child) {
+    if (child.widget.runtimeType == targetType)
+      throw child;
+    child.visitChildElements(walker);
+  }
+  try {
+    walker(node);
+  } on Element catch (result) {
+    return result;
+  }
+  return null;
+}
+
+Element findElementOfExactWidgetTypeGoingUp(Element node, Type targetType) {
+  Element result;
+  bool walker(Element ancestor) {
+    if (ancestor.widget.runtimeType == targetType)
+      result = ancestor;
+    return result == null;
+  }
+  node.visitAncestorElements(walker);
+  return result;
+}
+
+final RegExp materialIconAssetNameColorExtractor = RegExp(r'[^/]+/ic_.+_(white|black)_[0-9]+dp\.png');
+
+void checkIconColor(WidgetTester tester, String label, Color color) {
+  final Element listTile = findElementOfExactWidgetTypeGoingUp(tester.element(find.text(label)), ListTile);
+  expect(listTile, isNotNull);
+  final Element asset = findElementOfExactWidgetTypeGoingDown(listTile, RichText);
+  final RichText richText = asset.widget as RichText;
+  expect(richText.text.style.color, equals(color));
+}
+
+void main() {
+  stock_data.StockData.actuallyFetchData = false;
+
+  testWidgets('Icon colors', (WidgetTester tester) async {
+    stocks.main(); // builds the app and schedules a frame but doesn't trigger one
+    await tester.pump(); // see https://github.com/flutter/flutter/issues/1865
+    await tester.pump(); // triggers a frame
+
+    // sanity check
+    expect(find.text('MARKET'), findsOneWidget);
+    expect(find.text('Account Balance'), findsNothing);
+    await tester.pump(const Duration(seconds: 2));
+    expect(find.text('MARKET'), findsOneWidget);
+    expect(find.text('Account Balance'), findsNothing);
+
+    // drag the drawer out
+    final Offset left = Offset(0.0, (ui.window.physicalSize / ui.window.devicePixelRatio).height / 2.0);
+    final Offset right = Offset((ui.window.physicalSize / ui.window.devicePixelRatio).width, left.dy);
+    final TestGesture gesture = await tester.startGesture(left);
+    await tester.pump();
+    await gesture.moveTo(right);
+    await tester.pump();
+    await gesture.up();
+    await tester.pump();
+    expect(find.text('MARKET'), findsOneWidget);
+    expect(find.text('Account Balance'), findsOneWidget);
+
+    // check the color of the icon - light mode
+    checkIconColor(tester, 'Stock List', Colors.purple); // theme primary color
+    checkIconColor(tester, 'Account Balance', Colors.black38); // disabled
+    checkIconColor(tester, 'About', Colors.black45); // enabled
+
+    // switch to dark mode
+    await tester.tap(find.text('Pessimistic'));
+    await tester.pump(); // get the tap and send the notification that the theme has changed
+    await tester.pump(); // start the theme transition
+    await tester.pump(const Duration(seconds: 5)); // end the transition
+
+    // check the color of the icon - dark mode
+    checkIconColor(tester, 'Stock List', Colors.redAccent); // theme accent color
+    checkIconColor(tester, 'Account Balance', Colors.white38); // disabled
+    checkIconColor(tester, 'About', Colors.white); // enabled
+  });
+}
diff --git a/dev/benchmarks/test_apps/stocks/test/locale_test.dart b/dev/benchmarks/test_apps/stocks/test/locale_test.dart
new file mode 100644
index 0000000..a17e925
--- /dev/null
+++ b/dev/benchmarks/test_apps/stocks/test/locale_test.dart
@@ -0,0 +1,32 @@
+// Copyright 2014 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_test/flutter_test.dart';
+import 'package:stocks/main.dart' as stocks;
+import 'package:stocks/stock_data.dart' as stock_data;
+
+void main() {
+  stock_data.StockData.actuallyFetchData = false;
+
+  testWidgets('Changing locale', (WidgetTester tester) async {
+    stocks.main();
+    await tester.idle(); // see https://github.com/flutter/flutter/issues/1865
+    await tester.pump();
+    // The initial test app's locale is "_", so we're seeing the fallback translation here.
+    expect(find.text('MARKET'), findsOneWidget);
+    await tester.binding.setLocale('es', 'US');
+    await tester.idle();
+
+    // The Localizations widget has been built with the new locale. The
+    // new locale's strings are loaded asynchronously, so we're still
+    // displaying the previous locale's strings.
+    await tester.pump();
+    expect(find.text('MARKET'), findsOneWidget);
+
+    // The localized strings have finished loading and dependent
+    // widgets have been updated.
+    await tester.pump();
+    expect(find.text('MERCADO'), findsOneWidget);
+  });
+}
diff --git a/dev/benchmarks/test_apps/stocks/test/search_test.dart b/dev/benchmarks/test_apps/stocks/test/search_test.dart
new file mode 100644
index 0000000..f9c2388
--- /dev/null
+++ b/dev/benchmarks/test_apps/stocks/test/search_test.dart
@@ -0,0 +1,53 @@
+// Copyright 2014 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:stocks/main.dart' as stocks;
+import 'package:stocks/stock_data.dart' as stock_data;
+
+void main() {
+  stock_data.StockData.actuallyFetchData = false;
+
+  testWidgets('Search', (WidgetTester tester) async {
+    stocks.main(); // builds the app and schedules a frame but doesn't trigger one
+    await tester.pump(); // see https://github.com/flutter/flutter/issues/1865
+    await tester.pump(); // triggers a frame
+
+    expect(find.text('AAPL'), findsNothing);
+    expect(find.text('BANA'), findsNothing);
+
+    final stocks.StocksAppState app = tester.state<stocks.StocksAppState>(find.byType(stocks.StocksApp));
+    app.stocks.add(<List<String>>[
+      // "Symbol","Name","LastSale","MarketCap","IPOyear","Sector","industry","Summary Quote"
+      <String>['AAPL', 'Apple', '', '', '', '', '', ''],
+      <String>['BANA', 'Banana', '', '', '', '', '', ''],
+    ]);
+    await tester.pump();
+
+    expect(find.text('AAPL'), findsOneWidget);
+    expect(find.text('BANA'), findsOneWidget);
+
+    await tester.tap(find.byTooltip('Search'));
+    // We skip a minute at a time so that each phase of the animation
+    // is done in two frames, the start frame and the end frame.
+    // There are two phases currently, so that results in three frames.
+    expect(await tester.pumpAndSettle(const Duration(minutes: 1)), 3);
+
+    expect(find.text('AAPL'), findsOneWidget);
+    expect(find.text('BANA'), findsOneWidget);
+
+    await tester.enterText(find.byType(EditableText), 'B');
+    await tester.pump();
+
+    expect(find.text('AAPL'), findsNothing);
+    expect(find.text('BANA'), findsOneWidget);
+
+    await tester.enterText(find.byType(EditableText), 'X');
+    await tester.pump();
+
+    expect(find.text('AAPL'), findsNothing);
+    expect(find.text('BANA'), findsNothing);
+  });
+}
diff --git a/dev/benchmarks/test_apps/stocks/test_driver/scroll_perf.dart b/dev/benchmarks/test_apps/stocks/test_driver/scroll_perf.dart
new file mode 100644
index 0000000..332e06f
--- /dev/null
+++ b/dev/benchmarks/test_apps/stocks/test_driver/scroll_perf.dart
@@ -0,0 +1,11 @@
+// Copyright 2014 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_driver/driver_extension.dart';
+import 'package:stocks/main.dart' as app;
+
+void main() {
+  enableFlutterDriverExtension();
+  app.main();
+}
diff --git a/dev/benchmarks/test_apps/stocks/test_driver/scroll_perf_test.dart b/dev/benchmarks/test_apps/stocks/test_driver/scroll_perf_test.dart
new file mode 100644
index 0000000..d9ec350
--- /dev/null
+++ b/dev/benchmarks/test_apps/stocks/test_driver/scroll_perf_test.dart
@@ -0,0 +1,47 @@
+// Copyright 2014 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:async';
+
+import 'package:flutter_driver/flutter_driver.dart';
+import 'package:test/test.dart' hide TypeMatcher, isInstanceOf;
+
+void main() {
+  group('scrolling performance test', () {
+    FlutterDriver driver;
+
+    setUpAll(() async {
+      driver = await FlutterDriver.connect();
+    });
+
+    tearDownAll(() async {
+      if (driver != null)
+        driver.close();
+    });
+
+    test('measure', () async {
+      final Timeline timeline = await driver.traceAction(() async {
+        // Find the scrollable stock list
+        final SerializableFinder stockList = find.byValueKey('stock-list');
+        expect(stockList, isNotNull);
+
+        // Scroll down
+        for (int i = 0; i < 5; i++) {
+          await driver.scroll(stockList, 0.0, -300.0, const Duration(milliseconds: 300));
+          await Future<void>.delayed(const Duration(milliseconds: 500));
+        }
+
+        // Scroll up
+        for (int i = 0; i < 5; i++) {
+          await driver.scroll(stockList, 0.0, 300.0, const Duration(milliseconds: 300));
+          await Future<void>.delayed(const Duration(milliseconds: 500));
+        }
+      });
+
+      final TimelineSummary summary = TimelineSummary.summarize(timeline);
+      summary.writeSummaryToFile('stocks_scroll_perf', pretty: true);
+      summary.writeTimelineToFile('stocks_scroll_perf', pretty: true);
+    });
+  });
+}
diff --git a/dev/benchmarks/test_apps/stocks/test_driver/stock_view.dart b/dev/benchmarks/test_apps/stocks/test_driver/stock_view.dart
new file mode 100644
index 0000000..332e06f
--- /dev/null
+++ b/dev/benchmarks/test_apps/stocks/test_driver/stock_view.dart
@@ -0,0 +1,11 @@
+// Copyright 2014 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_driver/driver_extension.dart';
+import 'package:stocks/main.dart' as app;
+
+void main() {
+  enableFlutterDriverExtension();
+  app.main();
+}
diff --git a/dev/benchmarks/test_apps/stocks/test_driver/stock_view_test.dart b/dev/benchmarks/test_apps/stocks/test_driver/stock_view_test.dart
new file mode 100644
index 0000000..28d8b9d
--- /dev/null
+++ b/dev/benchmarks/test_apps/stocks/test_driver/stock_view_test.dart
@@ -0,0 +1,39 @@
+// Copyright 2014 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:async';
+
+import 'package:flutter_driver/flutter_driver.dart';
+import 'package:test/test.dart' hide TypeMatcher, isInstanceOf;
+
+void main() {
+  group('basic stock view test', () {
+    FlutterDriver driver;
+
+    setUpAll(() async {
+      driver = await FlutterDriver.connect();
+    });
+
+    tearDownAll(() async {
+      if (driver != null)
+        driver.close();
+    });
+
+    test('open AAPL stock', () async {
+      final SerializableFinder stockList = find.byValueKey('stock-list');
+      expect(stockList, isNotNull);
+
+      final SerializableFinder aaplStockRow = find.byValueKey('AAPL');
+      await driver.scrollUntilVisible(stockList, aaplStockRow);
+
+      await driver.tap(aaplStockRow);
+      await Future<void>.delayed(const Duration(milliseconds: 500));
+
+      final SerializableFinder stockOption = find.byValueKey('AAPL_symbol_name');
+      final String symbol = await driver.getText(stockOption, timeout: const Duration(milliseconds: 500));
+
+      expect(symbol, 'AAPL');
+    });
+  });
+}
diff --git a/dev/bots/analyze.dart b/dev/bots/analyze.dart
index 6c0f607..325666e 100644
--- a/dev/bots/analyze.dart
+++ b/dev/bots/analyze.dart
@@ -908,57 +908,57 @@
 
   // STOCKS ICONS
 
-  // examples/stocks/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
+  // dev/benchmarks/test_apps/stocks/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
   Hash256(0x74052AB5241D4418, 0x7085180608BC3114, 0xD12493C50CD8BBC7, 0x56DED186C37ACE84),
 
-  // examples/stocks/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
+  // dev/benchmarks/test_apps/stocks/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
   Hash256(0xE37947332E3491CB, 0x82920EE86A086FEA, 0xE1E0A70B3700A7DA, 0xDCAFBDD8F40E2E19),
 
-  // examples/stocks/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
+  // dev/benchmarks/test_apps/stocks/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
   Hash256(0xE608CDFC0C8579FB, 0xE38873BAAF7BC944, 0x9C9D2EE3685A4FAE, 0x671EF0C8BC41D17C),
 
-  // examples/stocks/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
+  // dev/benchmarks/test_apps/stocks/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
   Hash256(0xBD53D86977DF9C54, 0xF605743C5ABA114C, 0x9D51D1A8BB917E1A, 0x14CAA26C335CAEBD),
 
-  // examples/stocks/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
+  // dev/benchmarks/test_apps/stocks/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
   Hash256(0x64E4D02262C4F3D0, 0xBB4FDC21CD0A816C, 0x4CD2A0194E00FB0F, 0x1C3AE4142FAC0D15),
 
-  // examples/stocks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-60@2x.png
-  // examples/stocks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-Small-40@3x.png
+  // dev/benchmarks/test_apps/stocks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-60@2x.png
+  // dev/benchmarks/test_apps/stocks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-Small-40@3x.png
   Hash256(0x5BA3283A76918FC0, 0xEE127D0F22D7A0B6, 0xDF03DAED61669427, 0x93D89DDD87A08117),
 
-  // examples/stocks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-60@3x.png
+  // dev/benchmarks/test_apps/stocks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-60@3x.png
   Hash256(0xCD7F26ED31DEA42A, 0x535D155EC6261499, 0x34E6738255FDB2C4, 0xBD8D4BDDE9A99B05),
 
-  // examples/stocks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-76.png
+  // dev/benchmarks/test_apps/stocks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-76.png
   Hash256(0x3FA1225FC9A96A7E, 0xCD071BC42881AB0E, 0x7747EB72FFB72459, 0xA37971BBAD27EE24),
 
-  // examples/stocks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-76@2x.png
+  // dev/benchmarks/test_apps/stocks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-76@2x.png
   Hash256(0xCD867001ACD7BBDB, 0x25CDFD452AE89FA2, 0x8C2DC980CAF55F48, 0x0B16C246CFB389BC),
 
-  // examples/stocks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-83.5@2x.png
+  // dev/benchmarks/test_apps/stocks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-83.5@2x.png
   Hash256(0x848E9736E5C4915A, 0x7945BCF6B32FD56B, 0x1F1E7CDDD914352E, 0xC9681D38EF2A70DA),
 
-  // examples/stocks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-Notification.png
+  // dev/benchmarks/test_apps/stocks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-Notification.png
   Hash256(0x654BA7D6C4E05CA0, 0x7799878884EF8F11, 0xA383E1F24CEF5568, 0x3C47604A966983C8),
 
-  // examples/stocks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-Notification@2x.png
-  // examples/stocks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-Small-40.png
+  // dev/benchmarks/test_apps/stocks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-Notification@2x.png
+  // dev/benchmarks/test_apps/stocks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-Small-40.png
   Hash256(0x743056FE7D83FE42, 0xA2990825B6AD0415, 0x1AF73D0D43B227AA, 0x07EBEA9B767381D9),
 
-  // examples/stocks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-Notification@3x.png
+  // dev/benchmarks/test_apps/stocks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-Notification@3x.png
   Hash256(0xA7E1570812D119CF, 0xEF4B602EF28DD0A4, 0x100D066E66F5B9B9, 0x881765DC9303343B),
 
-  // examples/stocks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-Small-40@2x.png
+  // dev/benchmarks/test_apps/stocks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-Small-40@2x.png
   Hash256(0xB4102839A1E41671, 0x62DACBDEFA471953, 0xB1EE89A0AB7594BE, 0x1D9AC1E67DC2B2CE),
 
-  // examples/stocks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-Small.png
+  // dev/benchmarks/test_apps/stocks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-Small.png
   Hash256(0x70AC6571B593A967, 0xF1CBAEC9BC02D02D, 0x93AD766D8290ADE6, 0x840139BF9F219019),
 
-  // examples/stocks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-Small@2x.png
+  // dev/benchmarks/test_apps/stocks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-Small@2x.png
   Hash256(0x5D87A78386DA2C43, 0xDDA8FEF2CA51438C, 0xE5A276FE28C6CF0A, 0xEBE89085B56665B6),
 
-  // examples/stocks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-Small@3x.png
+  // dev/benchmarks/test_apps/stocks/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-Small@3x.png
   Hash256(0x4D9F5E81F668DA44, 0xB20A77F8BF7BA2E1, 0xF384533B5AD58F07, 0xB3A2F93F8635CD96),
 
 
diff --git a/dev/bots/test.dart b/dev/bots/test.dart
index ab4087e..b3b3b8b 100644
--- a/dev/bots/test.dart
+++ b/dev/bots/test.dart
@@ -453,7 +453,7 @@
     await _runFlutterTest(path.join(flutterRoot, 'examples', 'catalog'), tableData: bigqueryApi?.tabledata);
     await _runFlutterTest(path.join(flutterRoot, 'examples', 'hello_world'), tableData: bigqueryApi?.tabledata);
     await _runFlutterTest(path.join(flutterRoot, 'examples', 'layers'), tableData: bigqueryApi?.tabledata);
-    await _runFlutterTest(path.join(flutterRoot, 'examples', 'stocks'), tableData: bigqueryApi?.tabledata);
+    await _runFlutterTest(path.join(flutterRoot, 'dev', 'benchmarks', 'test_apps', 'stocks'), tableData: bigqueryApi?.tabledata);
     await _runFlutterTest(path.join(flutterRoot, 'packages', 'flutter_driver'), tableData: bigqueryApi?.tabledata, tests: <String>[path.join('test', 'src', 'real_tests')]);
     await _runFlutterTest(path.join(flutterRoot, 'packages', 'flutter_goldens'), tableData: bigqueryApi?.tabledata);
     await _runFlutterTest(path.join(flutterRoot, 'packages', 'flutter_localizations'), tableData: bigqueryApi?.tabledata);