[flutter_releases] Flutter beta 2.8.0-3.1.pre Framework Cherrypicks (#93448)

* Added SharedAppData to the widgets library (#93175)

* 'add branch flutter-2.8-candidate.3 to enabled_branches in .ci.yaml'

* 'Update Engine revision to 09f1520e8b9585d133faf1eccced9357670c6d11 for beta release 2.8.0-3.1.pre'

* Pin to specific plugin version in multidex test (#93148)

Co-authored-by: Hans Muller <hans.muller@gmail.com>
Co-authored-by: Emmanuel Garcia <egarciad@google.com>
diff --git a/.ci.yaml b/.ci.yaml
index 62e48dc..3be70a9 100755
--- a/.ci.yaml
+++ b/.ci.yaml
@@ -6,6 +6,7 @@
 # More information at:
 #  * https://github.com/flutter/cocoon/blob/main/CI_YAML.md
 enabled_branches:
+  - flutter-2.8-candidate.3
   - main
   - master
   - dev
diff --git a/bin/internal/engine.version b/bin/internal/engine.version
index c0b9666..d2dd5b3 100644
--- a/bin/internal/engine.version
+++ b/bin/internal/engine.version
@@ -1 +1 @@
-43561d8820e03e08959f791bfbb3097ce6be5756
+09f1520e8b9585d133faf1eccced9357670c6d11
diff --git a/examples/api/lib/widgets/shared_app_data/shared_app_data.0.dart b/examples/api/lib/widgets/shared_app_data/shared_app_data.0.dart
new file mode 100644
index 0000000..04a9d3e
--- /dev/null
+++ b/examples/api/lib/widgets/shared_app_data/shared_app_data.0.dart
@@ -0,0 +1,75 @@
+// 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.
+
+// Flutter code sample for SharedAppData
+
+import 'package:flutter/material.dart';
+
+class ShowSharedValue extends StatelessWidget {
+  const ShowSharedValue({ Key? key, required this.appDataKey }) : super(key: key);
+
+  final String appDataKey;
+
+  @override
+  Widget build(BuildContext context) {
+    // The SharedAppData.getValue() call here causes this widget to depend
+    // on the value of the SharedAppData's 'foo' key. If it's changed, with
+    // SharedAppData.setValue(), then this widget will be rebuilt.
+    final String value = SharedAppData.getValue<String, String>(context, appDataKey, () => 'initial');
+    return Text('$appDataKey: $value');
+  }
+}
+
+// Demonstrates that changes to the SharedAppData _only_ cause the dependent widgets
+// to be rebuilt. In this case that's the ShowSharedValue widget that's
+// displaying the value of a key whose value has been updated.
+class Home extends StatefulWidget {
+  const Home({ Key? key }) : super(key: key);
+
+  @override
+  State<Home> createState() => _HomeState();
+}
+
+class _HomeState extends State<Home> {
+  int _fooVersion = 0;
+  int _barVersion = 0;
+
+  @override
+  Widget build(BuildContext context) {
+    return Scaffold(
+      body: Center(
+        child: Column(
+          mainAxisSize: MainAxisSize.min,
+          children: <Widget>[
+            const ShowSharedValue(appDataKey: 'foo'),
+            const SizedBox(height: 16),
+            const ShowSharedValue(appDataKey: 'bar'),
+            const SizedBox(height: 16),
+            ElevatedButton(
+              child: const Text('change foo'),
+              onPressed: () {
+                _fooVersion += 1;
+                // Changing the SharedAppData's value for 'foo' causes the widgets that
+                // depend on 'foo' to be rebuilt.
+                SharedAppData.setValue<String, String?>(context, 'foo', 'FOO $_fooVersion'); // note: no setState()
+              },
+            ),
+            const SizedBox(height: 16),
+            ElevatedButton(
+              child: const Text('change bar'),
+              onPressed: () {
+                _barVersion += 1;
+                SharedAppData.setValue<String, String?>(context, 'bar', 'BAR $_barVersion');  // note: no setState()
+              },
+            ),
+          ],
+        ),
+      ),
+    );
+  }
+}
+
+void main() {
+  runApp(const MaterialApp(home: Home()));
+}
diff --git a/examples/api/lib/widgets/shared_app_data/shared_app_data.1.dart b/examples/api/lib/widgets/shared_app_data/shared_app_data.1.dart
new file mode 100644
index 0000000..03bb83b
--- /dev/null
+++ b/examples/api/lib/widgets/shared_app_data/shared_app_data.1.dart
@@ -0,0 +1,66 @@
+// 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.
+
+// Flutter code sample for SharedAppData
+
+import 'package:flutter/foundation.dart';
+import 'package:flutter/material.dart';
+
+// A single lazily constructed object that's shared with the entire
+// application via `SharedObject.of(context)`. The value of the object
+// can be changed with `SharedObject.reset(context)`. Resetting the value
+// will cause all of the widgets that depend on it to be rebuilt.
+class SharedObject {
+  SharedObject._();
+
+  static final Object _sharedObjectKey = Object();
+
+  @override
+  String toString() => describeIdentity(this);
+
+  static void reset(BuildContext context) {
+    // Calling SharedAppData.setValue() causes dependent widgets to be rebuilt.
+    SharedAppData.setValue<Object, SharedObject>(context, _sharedObjectKey, SharedObject._());
+  }
+
+  static SharedObject of(BuildContext context) {
+    // If a value for _sharedObjectKey has never been set then the third
+    // callback parameter is used to generate an initial value.
+    return SharedAppData.getValue<Object, SharedObject>(context, _sharedObjectKey, () => SharedObject._());
+  }
+}
+
+// An example of a widget which depends on the SharedObject's value,
+// which might be provided - along with SharedObject - in a Dart package.
+class CustomWidget extends StatelessWidget {
+  const CustomWidget({ Key? key }) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    // Will be rebuilt if the shared object's value is changed.
+    return ElevatedButton(
+      child: Text('Replace ${SharedObject.of(context)}'),
+      onPressed: () {
+        SharedObject.reset(context);
+      },
+    );
+  }
+}
+
+class Home extends StatelessWidget {
+  const Home({ Key? key }) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return const Scaffold(
+      body: Center(
+        child: CustomWidget()
+      ),
+    );
+  }
+}
+
+void main() {
+  runApp(const MaterialApp(home: Home()));
+}
diff --git a/packages/flutter/lib/src/widgets/app.dart b/packages/flutter/lib/src/widgets/app.dart
index 96acb77..9b8a141 100644
--- a/packages/flutter/lib/src/widgets/app.dart
+++ b/packages/flutter/lib/src/widgets/app.dart
@@ -25,6 +25,7 @@
 import 'router.dart';
 import 'scrollable.dart';
 import 'semantics_debugger.dart';
+import 'shared_app_data.dart';
 import 'shortcuts.dart';
 import 'text.dart';
 import 'title.dart';
@@ -1668,18 +1669,20 @@
 
     return RootRestorationScope(
       restorationId: widget.restorationScopeId,
-      child: Shortcuts(
-        debugLabel: '<Default WidgetsApp Shortcuts>',
-        shortcuts: widget.shortcuts ?? WidgetsApp.defaultShortcuts,
-        // DefaultTextEditingShortcuts is nested inside Shortcuts so that it can
-        // fall through to the defaultShortcuts.
-        child: DefaultTextEditingShortcuts(
-          child: Actions(
-            actions: widget.actions ?? WidgetsApp.defaultActions,
-            child: DefaultTextEditingActions(
-              child: FocusTraversalGroup(
-                policy: ReadingOrderTraversalPolicy(),
-                child: child,
+      child: SharedAppData(
+        child: Shortcuts(
+          debugLabel: '<Default WidgetsApp Shortcuts>',
+          shortcuts: widget.shortcuts ?? WidgetsApp.defaultShortcuts,
+          // DefaultTextEditingShortcuts is nested inside Shortcuts so that it can
+          // fall through to the defaultShortcuts.
+          child: DefaultTextEditingShortcuts(
+            child: Actions(
+              actions: widget.actions ?? WidgetsApp.defaultActions,
+	      child: DefaultTextEditingActions(
+                child: FocusTraversalGroup(
+                  policy: ReadingOrderTraversalPolicy(),
+                  child: child,
+                ),
               ),
             ),
           ),
diff --git a/packages/flutter/lib/src/widgets/shared_app_data.dart b/packages/flutter/lib/src/widgets/shared_app_data.dart
new file mode 100644
index 0000000..58666cb
--- /dev/null
+++ b/packages/flutter/lib/src/widgets/shared_app_data.dart
@@ -0,0 +1,201 @@
+// 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';
+
+import 'framework.dart';
+import 'inherited_model.dart';
+
+/// The type of the [SharedAppData.getValue] `init` parameter.
+///
+/// This callback is used to lazily create the initial value for
+/// a [SharedAppData] keyword.
+typedef SharedAppDataInitCallback<T> = T Function();
+
+/// Enables sharing key/value data with its `child` and all of the
+/// child's descendants.
+///
+/// - `SharedAppData.getValue(context, key, initCallback)` creates a dependency
+/// on the key and returns the value for the key from the shared data table.
+/// If no value exists for key then the initCallback is used to create
+/// the initial value.
+///
+/// - `SharedAppData.setValue(context, key, value)` changes the value of an entry
+/// in the shared data table and forces widgets that depend on that entry
+/// to be rebuilt.
+///
+/// A widget whose build method uses SharedAppData.getValue(context,
+/// keyword, initCallback) creates a dependency on the SharedAppData. When
+/// the value of keyword changes with SharedAppData.setValue(), the widget
+/// will be rebuilt. The values managed by the SharedAppData are expected
+/// to be immutable: intrinsic changes to values will not cause
+/// dependent widgets to be rebuilt.
+///
+/// An instance of this widget is created automatically by [WidgetsApp].
+///
+/// There are many ways to share data with a widget subtree. This
+/// class is based on [InheritedModel], which is an [InheritedWidget].
+/// It's intended to be used by packages that need to share a modest
+/// number of values among their own components.
+///
+/// SharedAppData is not intended to be a substitute for Provider or any of
+/// the other general purpose application state systems. SharedAppData is
+/// for situations where a package's custom widgets need to share one
+/// or a handful of immutable data objects that can be lazily
+/// initialized. It exists so that packages like that can deliver
+/// custom widgets without requiring the developer to add a
+/// package-specific umbrella widget to their application.
+///
+/// A good way to create an SharedAppData key that avoids potential
+/// collisions with other packages is to use a static `Object()` value.
+/// The `SharedObject` example below does this.
+///
+/// {@tool dartpad}
+/// The following sample demonstrates using the automatically created
+/// `SharedAppData`. Button presses cause changes to the values for keys
+/// 'foo', and 'bar', and those changes only cause the widgets that
+/// depend on those keys to be rebuilt.
+///
+/// ** See code in examples/api/lib/widgets/shared_app_data/shared_app_data.0.dart **
+/// {@end-tool}
+///
+/// {@tool dartpad}
+/// The following sample demonstrates how a single lazily computed
+/// value could be shared within an app. A Flutter package that
+/// provided custom widgets might use this approach to share a (possibly
+/// private) value with instances of those widgets.
+///
+/// ** See code in examples/api/lib/widgets/shared_app_data/shared_app_data.1.dart **
+/// {@end-tool}
+class SharedAppData extends StatefulWidget {
+  /// Creates a widget based on [InheritedModel] that supports build
+  /// dependencies qualified by keywords. Descendant widgets create
+  /// such dependencies with [SharedAppData.getValue] and they trigger
+  /// rebuilds with [SharedAppData.setValue].
+  ///
+  /// This widget is automatically created by the [WidgetsApp].
+  const SharedAppData({ Key? key, required this.child }) : super(key: key);
+
+  /// The widget below this widget in the tree.
+  ///
+  /// {@macro flutter.widgets.ProxyWidget.child}
+  final Widget child;
+
+  @override
+  State<StatefulWidget> createState() => _SharedAppDataState();
+
+  /// Returns the app model's value for `key` and ensures that each
+  /// time the value of `key` is changed with [SharedAppData.setValue], the
+  /// specified context will be rebuilt.
+  ///
+  /// If no value for `key` exists then the `init` callback is used to
+  /// generate an initial value. The callback is expected to return
+  /// an immutable value because intrinsic changes to the value will
+  /// not cause dependent widgets to be rebuilt.
+  ///
+  /// A widget that depends on the app model's value for `key` should use
+  /// this method in their `build` methods to ensure that they are rebuilt
+  /// if the value changes.
+  ///
+  /// The type parameter `K` is the type of the keyword and `V`
+  /// is the type of the value.
+  static V getValue<K extends Object, V>(BuildContext context, K key, SharedAppDataInitCallback<V> init) {
+    final _SharedAppModel? model = InheritedModel.inheritFrom<_SharedAppModel>(context, aspect: key);
+    assert(_debugHasSharedAppData(model, context, 'getValue'));
+    return model!.sharedAppDataState.getValue<K, V>(key, init);
+  }
+
+  /// Changes the app model's `value` for `key` and rebuilds any widgets
+  /// that have created a dependency on `key` with [SharedAppData.getValue].
+  ///
+  /// If `value` is `==` to the current value of `key` then nothing
+  /// is rebuilt.
+  ///
+  /// The `value` is expected to be immutable because intrinsic
+  /// changes to the value will not cause dependent widgets to be
+  /// rebuilt.
+  ///
+  /// Unlike [SharedAppData.getValue], this method does _not_ create a dependency
+  /// between `context` and `key`.
+  ///
+  /// The type parameter `K` is the type of the value's keyword and `V`
+  /// is the type of the value.
+  static void setValue<K extends Object, V>(BuildContext context, K key, V value) {
+    final _SharedAppModel? model = context.getElementForInheritedWidgetOfExactType<_SharedAppModel>()?.widget as _SharedAppModel?;
+    assert(_debugHasSharedAppData(model, context, 'setValue'));
+    model!.sharedAppDataState.setValue<K, V>(key, value);
+  }
+
+  static bool _debugHasSharedAppData(_SharedAppModel? model, BuildContext context, String methodName) {
+    assert(() {
+      if (model == null) {
+        throw FlutterError.fromParts(
+          <DiagnosticsNode>[
+            ErrorSummary('No SharedAppData widget found.'),
+            ErrorDescription('SharedAppData.$methodName requires an SharedAppData widget ancestor.\n'),
+            context.describeWidget('The specific widget that could not find an SharedAppData ancestor was'),
+            context.describeOwnershipChain('The ownership chain for the affected widget is'),
+            ErrorHint(
+              'Typically, the SharedAppData widget is introduced by the MaterialApp '
+              'or WidgetsApp widget at the top of your application widget tree. It '
+              'provides a key/value map of data that is shared with the entire '
+              'application.',
+            ),
+          ],
+        );
+      }
+      return true;
+    }());
+    return true;
+  }
+}
+
+class _SharedAppDataState extends State<SharedAppData> {
+  late Map<Object, Object?> data = <Object, Object?>{};
+
+  @override
+  Widget build(BuildContext context) {
+    return _SharedAppModel(sharedAppDataState: this, child: widget.child);
+  }
+
+  V getValue<K extends Object, V>(K key, SharedAppDataInitCallback<V> init) {
+    data[key] ??= init();
+    return data[key] as V;
+  }
+
+  void setValue<K extends Object, V>(K key, V value) {
+    if (data[key] != value) {
+      setState(() {
+        data = Map<Object, Object?>.from(data);
+        data[key] = value;
+      });
+    }
+  }
+}
+
+class _SharedAppModel extends InheritedModel<Object> {
+  _SharedAppModel({
+    Key? key,
+    required this.sharedAppDataState,
+    required Widget child
+  }) : data = sharedAppDataState.data, super(key: key, child: child);
+
+  final _SharedAppDataState sharedAppDataState;
+  final Map<Object, Object?> data;
+
+  @override
+  bool updateShouldNotify(_SharedAppModel old) {
+    return data != old.data;
+  }
+
+  @override
+  bool updateShouldNotifyDependent(_SharedAppModel old, Set<Object> keys) {
+    for (final Object key in keys) {
+      if (data[key] != old.data[key]) {
+        return true;
+      }
+    }
+    return false;
+  }
+}
diff --git a/packages/flutter/lib/widgets.dart b/packages/flutter/lib/widgets.dart
index db20ccc..b58c40f 100644
--- a/packages/flutter/lib/widgets.dart
+++ b/packages/flutter/lib/widgets.dart
@@ -108,6 +108,7 @@
 export 'src/widgets/scrollable.dart';
 export 'src/widgets/scrollbar.dart';
 export 'src/widgets/semantics_debugger.dart';
+export 'src/widgets/shared_app_data.dart';
 export 'src/widgets/shortcuts.dart';
 export 'src/widgets/single_child_scroll_view.dart';
 export 'src/widgets/size_changed_layout_notifier.dart';
diff --git a/packages/flutter/test/material/debug_test.dart b/packages/flutter/test/material/debug_test.dart
index 934dd92..246b575 100644
--- a/packages/flutter/test/material/debug_test.dart
+++ b/packages/flutter/test/material/debug_test.dart
@@ -202,6 +202,8 @@
       '     _FocusMarker\n'
       '     Focus\n'
       '     Shortcuts\n'
+      '     _SharedAppModel\n'
+      '     SharedAppData\n'
       '     UnmanagedRestorationScope\n'
       '     RestorationScope\n'
       '     UnmanagedRestorationScope\n'
diff --git a/packages/flutter/test/widgets/shared_app_data_test.dart b/packages/flutter/test/widgets/shared_app_data_test.dart
new file mode 100644
index 0000000..6d57734
--- /dev/null
+++ b/packages/flutter/test/widgets/shared_app_data_test.dart
@@ -0,0 +1,205 @@
+// 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/widgets.dart';
+import 'package:flutter_test/flutter_test.dart';
+
+void main() {
+  testWidgets('SharedAppData basics', (WidgetTester tester) async {
+    int columnBuildCount = 0;
+    int child1BuildCount = 0;
+    int child2BuildCount = 0;
+    late void Function(BuildContext context) setSharedAppDataValue;
+
+    await tester.pumpWidget(
+      Directionality(
+        textDirection: TextDirection.ltr,
+        child: SharedAppData(
+          child: Builder(
+            builder: (BuildContext context) {
+              columnBuildCount += 1;
+              return GestureDetector(
+                behavior: HitTestBehavior.opaque,
+                onTap: () {
+                  setSharedAppDataValue.call(context);
+                },
+                child: Column(
+                  children: <Widget>[
+                    Builder(
+                      builder: (BuildContext context) {
+                        child1BuildCount += 1;
+                        return Text(SharedAppData.getValue<String, String>(context, 'child1Text', () => 'null'));
+                      },
+                    ),
+                    Builder(
+                      builder: (BuildContext context) {
+                        child2BuildCount += 1;
+                        return Text(SharedAppData.getValue<String, String>(context, 'child2Text', () => 'null'));
+                      }
+                    ),
+                  ],
+                ),
+              );
+            },
+          ),
+        ),
+      ),
+    );
+
+    expect(columnBuildCount, 1);
+    expect(child1BuildCount, 1);
+    expect(child2BuildCount, 1);
+    expect(find.text('null').evaluate().length, 2);
+
+    // SharedAppData.setValue<String, String>(context, 'child1Text', 'child1')
+    // causes the first Text widget to be rebuilt with its text to be
+    // set to 'child1'. Nothing else is rebuilt.
+    setSharedAppDataValue = (BuildContext context) {
+      SharedAppData.setValue<String, String>(context, 'child1Text', 'child1');
+    };
+    await tester.tap(find.byType(GestureDetector));
+    await tester.pump();
+    expect(columnBuildCount, 1);
+    expect(child1BuildCount, 2);
+    expect(child2BuildCount, 1);
+    expect(find.text('child1'), findsOneWidget);
+    expect(find.text('null'), findsOneWidget);
+
+    // SharedAppData.setValue<String, String>(context, 'child2Text', 'child1')
+    // causes the second Text widget to be rebuilt with its text to be
+    // set to 'child2'. Nothing else is rebuilt.
+    setSharedAppDataValue = (BuildContext context) {
+      SharedAppData.setValue<String, String>(context, 'child2Text', 'child2');
+    };
+    await tester.tap(find.byType(GestureDetector));
+    await tester.pump();
+    expect(columnBuildCount, 1);
+    expect(child1BuildCount, 2);
+    expect(child2BuildCount, 2);
+    expect(find.text('child1'), findsOneWidget);
+    expect(find.text('child2'), findsOneWidget);
+
+    // Resetting a key's value to the same value does not
+    // cause any widgets to be rebuilt.
+    setSharedAppDataValue = (BuildContext context) {
+      SharedAppData.setValue<String, String>(context, 'child1Text', 'child1');
+      SharedAppData.setValue<String, String>(context, 'child2Text', 'child2');
+    };
+    await tester.tap(find.byType(GestureDetector));
+    await tester.pump();
+    expect(columnBuildCount, 1);
+    expect(child1BuildCount, 2);
+    expect(child2BuildCount, 2);
+
+    // More of the same, resetting the values to null..
+
+    setSharedAppDataValue = (BuildContext context) {
+      SharedAppData.setValue<String, String>(context, 'child1Text', 'null');
+    };
+    await tester.tap(find.byType(GestureDetector));
+    await tester.pump();
+    expect(columnBuildCount, 1);
+    expect(child1BuildCount, 3);
+    expect(child2BuildCount, 2);
+    expect(find.text('null'), findsOneWidget);
+    expect(find.text('child2'), findsOneWidget);
+
+    setSharedAppDataValue = (BuildContext context) {
+      SharedAppData.setValue<String, String>(context, 'child2Text', 'null');
+    };
+    await tester.tap(find.byType(GestureDetector));
+    await tester.pump();
+    expect(columnBuildCount, 1);
+    expect(child1BuildCount, 3);
+    expect(child2BuildCount, 3);
+    expect(find.text('null').evaluate().length, 2);
+  });
+
+  testWidgets('WidgetsApp SharedAppData ', (WidgetTester tester) async {
+    int parentBuildCount = 0;
+    int childBuildCount = 0;
+
+    await tester.pumpWidget(
+      WidgetsApp(
+        color: const Color(0xff00ff00),
+        builder: (BuildContext context, Widget? child) {
+          parentBuildCount += 1;
+          return GestureDetector(
+            behavior: HitTestBehavior.opaque,
+            onTap: () {
+              SharedAppData.setValue<String, String>(context, 'childText', 'child');
+            },
+            child: Center(
+              child: Builder(
+                builder: (BuildContext context) {
+                  childBuildCount += 1;
+                  return Text(SharedAppData.getValue<String, String>(context, 'childText', () => 'null'));
+                },
+              ),
+            ),
+          );
+        },
+      ),
+    );
+
+    expect(find.text('null'), findsOneWidget);
+    expect(parentBuildCount, 1);
+    expect(childBuildCount, 1);
+
+    await tester.tap(find.byType(GestureDetector));
+    await tester.pump();
+    expect(parentBuildCount, 1);
+    expect(childBuildCount, 2);
+    expect(find.text('child'), findsOneWidget);
+  });
+
+  testWidgets('WidgetsApp SharedAppData Shadowing', (WidgetTester tester) async {
+    int innerTapCount = 0;
+    int outerTapCount = 0;
+
+    await tester.pumpWidget(
+      WidgetsApp(
+        color: const Color(0xff00ff00),
+        builder: (BuildContext context, Widget? child) {
+          return GestureDetector(
+            behavior: HitTestBehavior.opaque,
+            onTap: () {
+              outerTapCount += 1;
+              SharedAppData.setValue<String, String>(context, 'childText', 'child');
+            },
+            child: Center(
+              child: SharedAppData(
+                child: Builder(
+                  builder: (BuildContext context) {
+                    return GestureDetector(
+                      onTap: () {
+                        innerTapCount += 1;
+                        SharedAppData.setValue<String, String>(context, 'childText', 'child');
+                      },
+                      child: Text(SharedAppData.getValue<String, String>(context, 'childText', () => 'null')),
+                    );
+                  },
+                ),
+              ),
+            ),
+          );
+        },
+      ),
+    );
+
+    expect(find.text('null'), findsOneWidget);
+
+    await tester.tapAt(const Offset(10, 10));
+    await tester.pump();
+    expect(outerTapCount, 1);
+    expect(innerTapCount, 0);
+    expect(find.text('null'), findsOneWidget);
+
+    await tester.tap(find.text('null'));
+    await tester.pump();
+    expect(outerTapCount, 1);
+    expect(innerTapCount, 1);
+    expect(find.text('child'), findsOneWidget);
+  });
+}
diff --git a/packages/flutter_tools/test/integration.shard/test_data/multidex_project.dart b/packages/flutter_tools/test/integration.shard/test_data/multidex_project.dart
index 3bd3f82..7db55f9 100644
--- a/packages/flutter_tools/test/integration.shard/test_data/multidex_project.dart
+++ b/packages/flutter_tools/test/integration.shard/test_data/multidex_project.dart
@@ -61,8 +61,9 @@
   dependencies:
     flutter:
       sdk: flutter
-    cloud_firestore: ^2.5.3
-    firebase_core: ^1.6.0
+    # Pin to specific plugin versions to avoid out-of-band failures.
+    cloud_firestore: 2.5.3
+    firebase_core: 1.6.0
   ''';
 
   @override