make sync script Posix-compliant; update framework to 98b3e48ed4e722acb0c4bb897397fc817057b5f6
diff --git a/engine/analysis_options.yaml b/engine/analysis_options.yaml
index 92e155d..edeb29f 100644
--- a/engine/analysis_options.yaml
+++ b/engine/analysis_options.yaml
@@ -17,9 +17,6 @@
   exclude:
     # Fixture depends on dart:ui and raises false positives.
     - flutter_frontend_server/test/fixtures/lib/main.dart
-  strong-mode:
-    implicit-casts: false
-    implicit-dynamic: false
   errors:
     # treat missing required parameters as a warning (not a hint)
     missing_required_param: warning
diff --git a/engine/lib/src/math.dart b/engine/lib/src/math.dart
index a52fdc0..240e79b 100644
--- a/engine/lib/src/math.dart
+++ b/engine/lib/src/math.dart
@@ -24,6 +24,20 @@
   return x;
 }
 
+double clampDouble(double x, double min, double max) {
+  assert(min <= max && !max.isNaN && !min.isNaN);
+  if (x < min) {
+    return min;
+  }
+  if (x > max) {
+    return max;
+  }
+  if (x.isNaN) {
+    return max;
+  }
+  return x;
+}
+
 Float32List _toMatrix32(Float64List matrix64) {
   final Float32List matrix32 = Float32List(16);
   matrix32[15] = matrix64[15];
diff --git a/engine/lib/src/painting.dart b/engine/lib/src/painting.dart
index 08e5750..78e8bc3 100644
--- a/engine/lib/src/painting.dart
+++ b/engine/lib/src/painting.dart
@@ -1360,7 +1360,8 @@
   }
 }
 class ImageShader extends Shader {
-    ImageShader(Image image, TileMode tmx, TileMode tmy, Float64List matrix4) :
+    // ignore: avoid_unused_constructor_parameters
+    ImageShader(Image image, TileMode tmx, TileMode tmy, Float64List matrix4, { FilterQuality? filterQuality }) :
     // ignore: unnecessary_null_comparison
     assert(image != null), // image is checked on the engine side
     assert(tmx != null), // ignore: unnecessary_null_comparison
@@ -2152,7 +2153,12 @@
     throw UnsupportedError('ImmutableBuffer.fromFilePath is not supported in flute.');
   }
 
-  void _init(Uint8List list, _Callback<void> callback) {  }
+  void _init(Uint8List list, _Callback<void> callback) {
+    _list = Uint8List.fromList(list);
+  }
+
+  Uint8List? _list;
+
   final int length;
   void dispose() {  }
 }
@@ -2315,3 +2321,83 @@
     targetHeight: targetHeight,
   );
 }
+
+
+Future<Codec> instantiateImageCodecWithSize(
+  ImmutableBuffer buffer, {
+  TargetImageSizeCallback? getTargetSize,
+}) async {
+  if (getTargetSize == null) {
+    return instantiateImageCodec(buffer._list!);
+  } else {
+    final Codec codec = await instantiateImageCodec(buffer._list!);
+    try {
+      final FrameInfo info = await codec.getNextFrame();
+      try {
+        final int width = info.image.width;
+        final int height = info.image.height;
+        final TargetImageSize targetSize = getTargetSize(width, height);
+        return instantiateImageCodec(buffer._list!,
+            targetWidth: targetSize.width, targetHeight: targetSize.height, allowUpscaling: false);
+      } finally {
+        info.image.dispose();
+      }
+    } finally {
+      codec.dispose();
+    }
+  }
+}
+
+/// Signature for a callback that determines the size to which an image should
+/// be decoded given its intrinsic size.
+///
+/// See also:
+///
+///  * [instantiateImageCodecWithSize], which used this signature for its
+///    `getTargetSize` argument.
+typedef TargetImageSizeCallback = TargetImageSize Function(
+  int intrinsicWidth,
+  int intrinsicHeight,
+);
+
+/// A specification of the size to which an image should be decoded.
+///
+/// See also:
+///
+///  * [TargetImageSizeCallback], a callback that returns instances of this
+///    class when consulted by image decoding methods such as
+///    [instantiateImageCodecWithSize].
+class TargetImageSize {
+  /// Creates a new instance of this class.
+  ///
+  /// The `width` and `height` may both be null, but if they're non-null, they
+  /// must be positive.
+  const TargetImageSize({this.width, this.height})
+      : assert(width == null || width > 0),
+        assert(height == null || height > 0);
+
+  /// The width into which to load the image.
+  ///
+  /// If this is non-null, the image will be decoded into the specified width.
+  /// If this is null and [height] is also null, the image will be decoded into
+  /// its intrinsic size. If this is null and [height] is non-null, the image
+  /// will be decoded into a width that maintains its intrinsic aspect ratio
+  /// while respecting the [height] value.
+  ///
+  /// If this value is non-null, it must be positive.
+  final int? width;
+
+  /// The height into which to load the image.
+  ///
+  /// If this is non-null, the image will be decoded into the specified height.
+  /// If this is null and [width] is also null, the image will be decoded into
+  /// its intrinsic size. If this is null and [width] is non-null, the image
+  /// will be decoded into a height that maintains its intrinsic aspect ratio
+  /// while respecting the [width] value.
+  ///
+  /// If this value is non-null, it must be positive.
+  final int? height;
+
+  @override
+  String toString() => 'TargetImageSize($width x $height)';
+}
diff --git a/framework/lib/foundation.dart b/framework/lib/foundation.dart
index 40b35f2..dff7553 100644
--- a/framework/lib/foundation.dart
+++ b/framework/lib/foundation.dart
@@ -24,6 +24,7 @@
 export 'src/foundation/basic_types.dart';
 export 'src/foundation/binding.dart';
 export 'src/foundation/bitfield.dart';
+export 'src/foundation/capabilities.dart';
 export 'src/foundation/change_notifier.dart';
 export 'src/foundation/collections.dart';
 export 'src/foundation/consolidate_response.dart';
diff --git a/framework/lib/material.dart b/framework/lib/material.dart
index 99b9a67..fb07102 100644
--- a/framework/lib/material.dart
+++ b/framework/lib/material.dart
@@ -65,6 +65,7 @@
 export 'src/material/data_table_theme.dart';
 export 'src/material/date.dart';
 export 'src/material/date_picker.dart';
+export 'src/material/date_picker_theme.dart';
 export 'src/material/debug.dart';
 export 'src/material/desktop_text_selection.dart';
 export 'src/material/desktop_text_selection_toolbar.dart';
@@ -159,6 +160,8 @@
 export 'src/material/slider_theme.dart';
 export 'src/material/snack_bar.dart';
 export 'src/material/snack_bar_theme.dart';
+export 'src/material/spell_check_suggestions_toolbar.dart';
+export 'src/material/spell_check_suggestions_toolbar_layout_delegate.dart';
 export 'src/material/stepper.dart';
 export 'src/material/switch.dart';
 export 'src/material/switch_list_tile.dart';
diff --git a/framework/lib/painting.dart b/framework/lib/painting.dart
index bd3e029..35f056e 100644
--- a/framework/lib/painting.dart
+++ b/framework/lib/painting.dart
@@ -47,6 +47,7 @@
 export 'src/painting/image_resolution.dart';
 export 'src/painting/image_stream.dart';
 export 'src/painting/inline_span.dart';
+export 'src/painting/linear_border.dart';
 export 'src/painting/matrix_utils.dart';
 export 'src/painting/notched_shapes.dart';
 export 'src/painting/oval_border.dart';
diff --git a/framework/lib/services.dart b/framework/lib/services.dart
index a0ff42f..7e7fb02 100644
--- a/framework/lib/services.dart
+++ b/framework/lib/services.dart
@@ -11,15 +11,18 @@
 library services;
 
 export 'src/services/asset_bundle.dart';
+export 'src/services/asset_manifest.dart';
 export 'src/services/autofill.dart';
 export 'src/services/binary_messenger.dart';
 export 'src/services/binding.dart';
+export 'src/services/browser_context_menu.dart';
 export 'src/services/clipboard.dart';
 export 'src/services/debug.dart';
 export 'src/services/deferred_component.dart';
 export 'src/services/font_loader.dart';
 export 'src/services/haptic_feedback.dart';
 export 'src/services/hardware_keyboard.dart';
+export 'src/services/keyboard_inserted_content.dart';
 export 'src/services/keyboard_key.g.dart';
 export 'src/services/keyboard_maps.g.dart';
 export 'src/services/message_codec.dart';
diff --git a/framework/lib/src/animation/animation.dart b/framework/lib/src/animation/animation.dart
index 09cf78c..cafcdfe 100644
--- a/framework/lib/src/animation/animation.dart
+++ b/framework/lib/src/animation/animation.dart
@@ -81,9 +81,9 @@
   ///       final double opacity = (value / 1000).clamp(0, 1);
   ///       return Opacity(opacity: opacity, child: child);
   ///     },
-  ///     child: Container(
+  ///     child: const ColoredBox(
   ///       color: Colors.red,
-  ///       child: const Text('Hello, Animation'),
+  ///       child: Text('Hello, Animation'),
   ///     ),
   ///   );
   /// }
@@ -100,9 +100,9 @@
   ///     opacity: Animation<double>.fromValueListenable(_scrollPosition, transformer: (double value) {
   ///       return (value / 1000).clamp(0, 1);
   ///     }),
-  ///     child: Container(
+  ///     child: const ColoredBox(
   ///       color: Colors.red,
-  ///       child: const Text('Hello, Animation'),
+  ///       child: Text('Hello, Animation'),
   ///     ),
   ///   );
   /// }
@@ -269,7 +269,6 @@
   /// * "&#x23ED;": [AnimationStatus.completed] ([value] == 1.0)
   /// * "&#x23EE;": [AnimationStatus.dismissed] ([value] == 0.0)
   String toStringDetails() {
-    assert(status != null);
     switch (status) {
       case AnimationStatus.forward:
         return '\u25B6'; // >
diff --git a/framework/lib/src/animation/animation_controller.dart b/framework/lib/src/animation/animation_controller.dart
index a048589..4cc7e83 100644
--- a/framework/lib/src/animation/animation_controller.dart
+++ b/framework/lib/src/animation/animation_controller.dart
@@ -242,10 +242,7 @@
     this.upperBound = 1.0,
     this.animationBehavior = AnimationBehavior.normal,
     required TickerProvider vsync,
-  }) : assert(lowerBound != null),
-       assert(upperBound != null),
-       assert(upperBound >= lowerBound),
-       assert(vsync != null),
+  }) : assert(upperBound >= lowerBound),
        _direction = _AnimationDirection.forward {
     _ticker = vsync.createTicker(_tick);
     _internalSetValue(value ?? lowerBound);
@@ -275,9 +272,7 @@
     this.debugLabel,
     required TickerProvider vsync,
     this.animationBehavior = AnimationBehavior.preserve,
-  }) : assert(value != null),
-       assert(vsync != null),
-       lowerBound = double.negativeInfinity,
+  }) : lowerBound = double.negativeInfinity,
        upperBound = double.infinity,
        _direction = _AnimationDirection.forward {
     _ticker = vsync.createTicker(_tick);
@@ -363,7 +358,6 @@
   ///  * [forward], [reverse], [animateTo], [animateWith], [fling], and [repeat],
   ///    which start the animation controller.
   set value(double newValue) {
-    assert(newValue != null);
     stop();
     _internalSetValue(newValue);
     notifyListeners();
@@ -657,7 +651,6 @@
     }());
     assert(max >= min);
     assert(max <= upperBound && min >= lowerBound);
-    assert(reverse != null);
     stop();
     return _startSimulation(_RepeatingSimulation(_value, min, max, reverse, period!, _directionSetter));
   }
@@ -744,7 +737,6 @@
   }
 
   TickerFuture _startSimulation(Simulation simulation) {
-    assert(simulation != null);
     assert(!isAnimating);
     _simulation = simulation;
     _lastElapsedDuration = Duration.zero;
@@ -856,9 +848,7 @@
 
 class _InterpolationSimulation extends Simulation {
   _InterpolationSimulation(this._begin, this._end, Duration duration, this._curve, double scale)
-    : assert(_begin != null),
-      assert(_end != null),
-      assert(duration != null && duration.inMicroseconds > 0),
+    : assert(duration.inMicroseconds > 0),
       _durationInSeconds = (duration.inMicroseconds * scale) / Duration.microsecondsPerSecond;
 
   final double _durationInSeconds;
diff --git a/framework/lib/src/animation/animations.dart b/framework/lib/src/animation/animations.dart
index a4e17c5..47fc7f2 100644
--- a/framework/lib/src/animation/animations.dart
+++ b/framework/lib/src/animation/animations.dart
@@ -255,8 +255,8 @@
 /// If the parent animation is running forward from 0.0 to 1.0, this animation
 /// is running in reverse from 1.0 to 0.0.
 ///
-/// Using a [ReverseAnimation] is different from simply using a [Tween] with a
-/// begin of 1.0 and an end of 0.0 because the tween does not change the status
+/// Using a [ReverseAnimation] is different from using a [Tween] with a
+/// `begin` of 1.0 and an `end` of 0.0 because the tween does not change the status
 /// or direction of the animation.
 ///
 /// See also:
@@ -271,8 +271,7 @@
   /// Creates a reverse animation.
   ///
   /// The parent argument must not be null.
-  ReverseAnimation(this.parent)
-    : assert(parent != null);
+  ReverseAnimation(this.parent);
 
   /// The animation whose value and direction this animation is reversing.
   final Animation<double> parent;
@@ -310,7 +309,6 @@
   double get value => 1.0 - parent.value;
 
   AnimationStatus _reverseStatus(AnimationStatus status) {
-    assert(status != null);
     switch (status) {
       case AnimationStatus.forward: return AnimationStatus.reverse;
       case AnimationStatus.reverse: return AnimationStatus.forward;
@@ -384,8 +382,7 @@
     required this.parent,
     required this.curve,
     this.reverseCurve,
-  }) : assert(parent != null),
-       assert(curve != null) {
+  }) {
     _updateCurveDirection(parent.status);
     parent.addStatusListener(_updateCurveDirection);
   }
@@ -518,7 +515,7 @@
     Animation<double> this._currentTrain,
     this._nextTrain, {
     this.onSwitchedTrain,
-  }) : assert(_currentTrain != null) {
+  }) {
     if (_nextTrain != null) {
       if (_currentTrain!.value == _nextTrain!.value) {
         _currentTrain = _nextTrain;
@@ -644,8 +641,7 @@
   CompoundAnimation({
     required this.first,
     required this.next,
-  }) : assert(first != null),
-       assert(next != null);
+  });
 
   /// The first sub-animation. Its status takes precedence if neither are
   /// animating.
diff --git a/framework/lib/src/animation/curves.dart b/framework/lib/src/animation/curves.dart
index bc52684..29755f8 100644
--- a/framework/lib/src/animation/curves.dart
+++ b/framework/lib/src/animation/curves.dart
@@ -35,7 +35,6 @@
   /// implementation of [transform], which delegates the remaining logic to
   /// [transformInternal].
   T transform(double t) {
-    assert(t != null);
     assert(t >= 0.0 && t <= 1.0, 'parametric value $t is outside of [0, 1] range.');
     return transformInternal(t);
   }
@@ -129,7 +128,7 @@
   /// Creates a sawtooth curve.
   ///
   /// The [count] argument must not be null.
-  const SawTooth(this.count) : assert(count != null);
+  const SawTooth(this.count);
 
   /// The number of repetitions of the sawtooth pattern in the unit interval.
   final int count;
@@ -159,10 +158,7 @@
   /// Creates an interval curve.
   ///
   /// The arguments must not be null.
-  const Interval(this.begin, this.end, { this.curve = Curves.linear })
-    : assert(begin != null),
-      assert(end != null),
-      assert(curve != null);
+  const Interval(this.begin, this.end, { this.curve = Curves.linear });
 
   /// The largest value for which this interval is 0.0.
   ///
@@ -207,7 +203,7 @@
   /// Creates a threshold curve.
   ///
   /// The [threshold] argument must not be null.
-  const Threshold(this.threshold) : assert(threshold != null);
+  const Threshold(this.threshold);
 
   /// The value before which the curve is 0.0 and after which the curve is 1.0.
   ///
@@ -307,11 +303,7 @@
   /// cubic curves in [Curves].
   ///
   /// The [a] (x1), [b] (y1), [c] (x2) and [d] (y2) arguments must not be null.
-  const Cubic(this.a, this.b, this.c, this.d)
-    : assert(a != null),
-      assert(b != null),
-      assert(c != null),
-      assert(d != null);
+  const Cubic(this.a, this.b, this.c, this.d);
 
   /// The x coordinate of the first control point.
   ///
@@ -518,9 +510,6 @@
     // 4. Recursively sample the two parts.
     //
     // This algorithm concentrates samples in areas of high curvature.
-    assert(tolerance != null);
-    assert(start != null);
-    assert(end != null);
     assert(end > start);
     // We want to pick a random seed that will keep the result stable if
     // evaluated again, so we use the first non-generated control point.
@@ -577,7 +566,6 @@
   /// single-valued, it will return the parameter for only one of the values at
   /// the given `x` location.
   double findInverse(double x) {
-    assert(x != null);
     double start = 0.0;
     double end = 1.0;
     late double mid;
@@ -612,7 +600,7 @@
   /// Creates an object that holds a sample; used with [Curve2D] subclasses.
   ///
   /// All arguments must not be null.
-  const Curve2DSample(this.t, this.value) : assert(t != null), assert(value != null);
+  const Curve2DSample(this.t, this.value);
 
   /// The parametric location of this sample point along the curve.
   final double t;
@@ -682,9 +670,7 @@
         double tension = 0.0,
         Offset? startHandle,
         Offset? endHandle,
-      }) : assert(controlPoints != null),
-           assert(tension != null),
-           assert(tension <= 1.0, 'tension $tension must not be greater than 1.0.'),
+      }) : assert(tension <= 1.0, 'tension $tension must not be greater than 1.0.'),
            assert(tension >= 0.0, 'tension $tension must not be negative.'),
            assert(controlPoints.length > 3, 'There must be at least four control points to create a CatmullRomSpline.'),
            _controlPoints = controlPoints,
@@ -702,9 +688,7 @@
         double tension = 0.0,
         Offset? startHandle,
         Offset? endHandle,
-      }) : assert(controlPoints != null),
-           assert(tension != null),
-           assert(tension <= 1.0, 'tension $tension must not be greater than 1.0.'),
+      }) : assert(tension <= 1.0, 'tension $tension must not be greater than 1.0.'),
            assert(tension >= 0.0, 'tension $tension must not be negative.'),
            assert(controlPoints.length > 3, 'There must be at least four control points to create a CatmullRomSpline.'),
            _controlPoints = null,
@@ -860,8 +844,7 @@
   ///
   ///  * This [paper on using Catmull-Rom splines](http://faculty.cs.tamu.edu/schaefer/research/cr_cad.pdf).
   CatmullRomCurve(this.controlPoints, {this.tension = 0.0})
-      : assert(tension != null),
-        assert(() {
+      : assert(() {
           return validateControlPoints(
             controlPoints,
             tension: tension,
@@ -877,8 +860,7 @@
   /// Same as [CatmullRomCurve.new], but it precomputes the internal curve data
   /// structures for a more predictable computation load.
   CatmullRomCurve.precompute(this.controlPoints, {this.tension = 0.0})
-      : assert(tension != null),
-        assert(() {
+      : assert(() {
           return validateControlPoints(
             controlPoints,
             tension: tension,
@@ -963,7 +945,6 @@
     double tension = 0.0,
     List<String>? reasons,
   }) {
-    assert(tension != null);
     if (controlPoints == null) {
       assert(() {
         reasons?.add('Supplied control points cannot be null');
@@ -1143,7 +1124,7 @@
   /// Creates a flipped curve.
   ///
   /// The [curve] argument must not be null.
-  const FlippedCurve(this.curve) : assert(curve != null);
+  const FlippedCurve(this.curve);
 
   /// The curve that is being flipped.
   final Curve curve;
diff --git a/framework/lib/src/animation/tween.dart b/framework/lib/src/animation/tween.dart
index d6c12b9..817e948 100644
--- a/framework/lib/src/animation/tween.dart
+++ b/framework/lib/src/animation/tween.dart
@@ -364,8 +364,7 @@
 class ReverseTween<T extends Object?> extends Tween<T> {
   /// Construct a [Tween] that evaluates its [parent] in reverse.
   ReverseTween(this.parent)
-    : assert(parent != null),
-      super(begin: parent.end, end: parent.begin);
+    : super(begin: parent.end, end: parent.begin);
 
   /// This tween's value is the same as the parent's value evaluated in reverse.
   ///
@@ -544,8 +543,7 @@
   /// Creates a curve tween.
   ///
   /// The [curve] argument must not be null.
-  CurveTween({ required this.curve })
-    : assert(curve != null);
+  CurveTween({ required this.curve });
 
   /// The curve to use when transforming the value of the animation.
   Curve curve;
diff --git a/framework/lib/src/animation/tween_sequence.dart b/framework/lib/src/animation/tween_sequence.dart
index 252d273..a8e3208 100644
--- a/framework/lib/src/animation/tween_sequence.dart
+++ b/framework/lib/src/animation/tween_sequence.dart
@@ -51,8 +51,7 @@
   /// best to reuse one, rather than rebuilding it on every frame, when that's
   /// possible.
   TweenSequence(List<TweenSequenceItem<T>> items)
-      : assert(items != null),
-        assert(items.isNotEmpty) {
+      : assert(items.isNotEmpty) {
     _items.addAll(items);
 
     double totalWeight = 0.0;
@@ -113,8 +112,7 @@
   /// There's a small cost associated with building a `TweenSequence` so it's
   /// best to reuse one, rather than rebuilding it on every frame, when that's
   /// possible.
-  FlippedTweenSequence(super.items)
-    : assert(items != null);
+  FlippedTweenSequence(super.items);
 
   @override
   double transform(double t) => 1 - super.transform(1 - t);
@@ -128,9 +126,7 @@
   const TweenSequenceItem({
     required this.tween,
     required this.weight,
-  }) : assert(tween != null),
-       assert(weight != null),
-       assert(weight > 0.0);
+  }) : assert(weight > 0.0);
 
   /// Defines the value of the [TweenSequence] for the interval within the
   /// animation's duration indicated by [weight] and this item's position
diff --git a/framework/lib/src/cupertino/activity_indicator.dart b/framework/lib/src/cupertino/activity_indicator.dart
index 8089d10..b4d3799 100644
--- a/framework/lib/src/cupertino/activity_indicator.dart
+++ b/framework/lib/src/cupertino/activity_indicator.dart
@@ -36,9 +36,7 @@
     this.color,
     this.animating = true,
     this.radius = _kDefaultIndicatorRadius,
-  })  : assert(animating != null),
-        assert(radius != null),
-        assert(radius > 0.0),
+  })  : assert(radius > 0.0),
         progress = 1.0;
 
   /// Creates a non-animated iOS-style activity indicator that displays
@@ -52,9 +50,7 @@
     this.color,
     this.radius = _kDefaultIndicatorRadius,
     this.progress = 1.0,
-  })  : assert(radius != null),
-        assert(radius > 0.0),
-        assert(progress != null),
+  })  : assert(radius > 0.0),
         assert(progress >= 0.0),
         assert(progress <= 1.0),
         animating = false;
diff --git a/framework/lib/src/cupertino/adaptive_text_selection_toolbar.dart b/framework/lib/src/cupertino/adaptive_text_selection_toolbar.dart
index 7f64616..43abb02 100644
--- a/framework/lib/src/cupertino/adaptive_text_selection_toolbar.dart
+++ b/framework/lib/src/cupertino/adaptive_text_selection_toolbar.dart
@@ -157,7 +157,7 @@
   final List<ContextMenuButtonItem>? buttonItems;
 
   /// Returns a List of Widgets generated by turning [buttonItems] into the
-  /// the default context menu buttons for Cupertino on the current platform.
+  /// default context menu buttons for Cupertino on the current platform.
   ///
   /// This is useful when building a text selection toolbar with the default
   /// button appearance for the given platform, but where the toolbar and/or the
@@ -165,7 +165,7 @@
   ///
   /// Does not build Material buttons. On non-Apple platforms, Cupertino buttons
   /// will still be used, because the Cupertino library does not access the
-  /// Material library. To get the native-looking buttons on every platform, use
+  /// Material library. To get the native-looking buttons on every platform,
   /// use [AdaptiveTextSelectionToolbar.getAdaptiveButtons] in the Material
   /// library.
   ///
diff --git a/framework/lib/src/cupertino/app.dart b/framework/lib/src/cupertino/app.dart
index 1e98a03..dab2763 100644
--- a/framework/lib/src/cupertino/app.dart
+++ b/framework/lib/src/cupertino/app.dart
@@ -176,16 +176,13 @@
     this.actions,
     this.restorationScopeId,
     this.scrollBehavior,
+    @Deprecated(
+      'Remove this parameter as it is now ignored. '
+      'CupertinoApp never introduces its own MediaQuery; the View widget takes care of that. '
+      'This feature was deprecated after v3.7.0-29.0.pre.'
+    )
     this.useInheritedMediaQuery = false,
-  }) : assert(routes != null),
-       assert(navigatorObservers != null),
-       assert(title != null),
-       assert(showPerformanceOverlay != null),
-       assert(checkerboardRasterCacheImages != null),
-       assert(checkerboardOffscreenLayers != null),
-       assert(showSemanticsDebugger != null),
-       assert(debugShowCheckedModeBanner != null),
-       routeInformationProvider = null,
+  }) : routeInformationProvider = null,
        routeInformationParser = null,
        routerDelegate = null,
        backButtonDispatcher = null,
@@ -220,14 +217,13 @@
     this.actions,
     this.restorationScopeId,
     this.scrollBehavior,
+    @Deprecated(
+      'Remove this parameter as it is now ignored. '
+      'CupertinoApp never introduces its own MediaQuery; the View widget takes care of that. '
+      'This feature was deprecated after v3.7.0-29.0.pre.'
+    )
     this.useInheritedMediaQuery = false,
   }) : assert(routerDelegate != null || routerConfig != null),
-       assert(title != null),
-       assert(showPerformanceOverlay != null),
-       assert(checkerboardRasterCacheImages != null),
-       assert(checkerboardOffscreenLayers != null),
-       assert(showSemanticsDebugger != null),
-       assert(debugShowCheckedModeBanner != null),
        navigatorObservers = null,
        navigatorKey = null,
        onGenerateRoute = null,
@@ -422,6 +418,11 @@
   final ScrollBehavior? scrollBehavior;
 
   /// {@macro flutter.widgets.widgetsApp.useInheritedMediaQuery}
+  @Deprecated(
+    'This setting is now ignored. '
+    'CupertinoApp never introduces its own MediaQuery; the View widget takes care of that. '
+    'This feature was deprecated after v3.7.0-29.0.pre.'
+  )
   final bool useInheritedMediaQuery;
 
   @override
@@ -555,7 +556,6 @@
         shortcuts: widget.shortcuts,
         actions: widget.actions,
         restorationScopeId: widget.restorationScopeId,
-        useInheritedMediaQuery: widget.useInheritedMediaQuery,
       );
     }
     return WidgetsApp(
@@ -590,7 +590,6 @@
       shortcuts: widget.shortcuts,
       actions: widget.actions,
       restorationScopeId: widget.restorationScopeId,
-      useInheritedMediaQuery: widget.useInheritedMediaQuery,
     );
   }
 
diff --git a/framework/lib/src/cupertino/bottom_tab_bar.dart b/framework/lib/src/cupertino/bottom_tab_bar.dart
index bc96fb1..286cd02 100644
--- a/framework/lib/src/cupertino/bottom_tab_bar.dart
+++ b/framework/lib/src/cupertino/bottom_tab_bar.dart
@@ -74,16 +74,12 @@
         width: 0.0, // 0.0 means one physical pixel
       ),
     ),
-  }) : assert(items != null),
-       assert(
+  }) : assert(
          items.length >= 2,
          "Tabs need at least 2 items to conform to Apple's HIG",
        ),
-       assert(currentIndex != null),
        assert(0 <= currentIndex && currentIndex < items.length),
-       assert(iconSize != null),
-       assert(height != null && height >= 0.0),
-       assert(inactiveColor != null);
+       assert(height >= 0.0);
 
   /// The interactive items laid out within the bottom navigation bar.
   ///
@@ -156,7 +152,7 @@
   @override
   Widget build(BuildContext context) {
     assert(debugCheckHasMediaQuery(context));
-    final double bottomPadding = MediaQuery.of(context).viewPadding.bottom;
+    final double bottomPadding = MediaQuery.viewPaddingOf(context).bottom;
 
     final Color backgroundColor = CupertinoDynamicColor.resolve(
       this.backgroundColor ?? CupertinoTheme.of(context).barBackgroundColor,
diff --git a/framework/lib/src/cupertino/button.dart b/framework/lib/src/cupertino/button.dart
index ac84891..917481d 100644
--- a/framework/lib/src/cupertino/button.dart
+++ b/framework/lib/src/cupertino/button.dart
@@ -50,8 +50,6 @@
     this.alignment = Alignment.center,
     required this.onPressed,
   }) : assert(pressedOpacity == null || (pressedOpacity >= 0.0 && pressedOpacity <= 1.0)),
-       assert(disabledColor != null),
-       assert(alignment != null),
        _filled = false;
 
   /// Creates an iOS-style button with a filled background.
@@ -71,8 +69,6 @@
     this.alignment = Alignment.center,
     required this.onPressed,
   }) : assert(pressedOpacity == null || (pressedOpacity >= 0.0 && pressedOpacity <= 1.0)),
-       assert(disabledColor != null),
-       assert(alignment != null),
        color = null,
        _filled = true;
 
diff --git a/framework/lib/src/cupertino/colors.dart b/framework/lib/src/cupertino/colors.dart
index 7c5b9f6..2249ed2 100644
--- a/framework/lib/src/cupertino/colors.dart
+++ b/framework/lib/src/cupertino/colors.dart
@@ -144,6 +144,21 @@
     darkHighContrastColor: Color.fromARGB(255, 48, 219, 91),
   );
 
+  /// A mint color that can adapt to the given [BuildContext].
+  ///
+  /// See also:
+  ///
+  ///  * [UIColor.systemMint](https://developer.apple.com/documentation/uikit/uicolor/3852741-systemmint),
+  ///    the `UIKit` equivalent.
+  static const CupertinoDynamicColor systemMint =
+      CupertinoDynamicColor.withBrightnessAndContrast(
+    debugLabel: 'systemMint',
+    color: Color.fromARGB(255, 0, 199, 190),
+    darkColor: Color.fromARGB(255, 99, 230, 226),
+    highContrastColor: Color.fromARGB(255, 12, 129, 123),
+    darkHighContrastColor: Color.fromARGB(255, 102, 212, 207),
+  );
+
   /// An indigo color that can adapt to the given [BuildContext].
   ///
   /// See also:
@@ -186,6 +201,21 @@
     darkHighContrastColor: Color.fromARGB(255, 255, 100, 130),
   );
 
+  /// A brown color that can adapt to the given [BuildContext].
+  ///
+  /// See also:
+  ///
+  ///  * [UIColor.systemBrown](https://developer.apple.com/documentation/uikit/uicolor/3173142-systembrown),
+  ///    the `UIKit` equivalent.
+  static const CupertinoDynamicColor systemBrown =
+      CupertinoDynamicColor.withBrightnessAndContrast(
+    debugLabel: 'systemBrown',
+    color: Color.fromARGB(255, 162, 132, 94),
+    darkColor: Color.fromARGB(255, 172, 142, 104),
+    highContrastColor: Color.fromARGB(255, 127, 101, 69),
+    darkHighContrastColor: Color.fromARGB(255, 181, 148, 105),
+  );
+
   /// A purple color that can adapt to the given [BuildContext].
   ///
   /// See also:
@@ -228,6 +258,21 @@
     darkHighContrastColor: Color.fromARGB(255, 112, 215, 255),
   );
 
+  /// A cyan color that can adapt to the given [BuildContext].
+  ///
+  /// See also:
+  ///
+  ///  * [UIColor.systemCyan](https://developer.apple.com/documentation/uikit/uicolor/3852740-systemcyan),
+  ///    the `UIKit` equivalent.
+  static const CupertinoDynamicColor systemCyan =
+      CupertinoDynamicColor.withBrightnessAndContrast(
+    debugLabel: 'systemCyan',
+    color: Color.fromARGB(255, 50, 173, 230),
+    darkColor: Color.fromARGB(255, 100, 210, 255),
+    highContrastColor: Color.fromARGB(255, 0, 113, 164),
+    darkHighContrastColor: Color.fromARGB(255, 112, 215, 255),
+  );
+
   /// A yellow color that can adapt to the given [BuildContext].
   ///
   /// See also:
@@ -780,16 +825,7 @@
     this.darkHighContrastElevatedColor,
     this._debugResolveContext,
     this._debugLabel,
-  ) : assert(color != null),
-      assert(darkColor != null),
-      assert(highContrastColor != null),
-      assert(darkHighContrastColor != null),
-      assert(elevatedColor != null),
-      assert(darkElevatedColor != null),
-      assert(highContrastElevatedColor != null),
-      assert(darkHighContrastElevatedColor != null),
-      assert(_effectiveColor != null),
-      // The super constructor has to be called with a dummy value in order to mark
+  ) : // The super constructor has to be called with a dummy value in order to mark
       // this constructor const.
       // The field `value` is overridden in the class implementation.
       super(0);
@@ -908,7 +944,6 @@
   ///  * [maybeResolve], which is similar to this function, but will allow a
   ///    null `resolvable` color.
   static Color resolve(Color resolvable, BuildContext context) {
-    assert(context != null);
     return (resolvable is CupertinoDynamicColor)
       ? resolvable.resolveFrom(context)
       : resolvable;
@@ -931,7 +966,6 @@
     if (resolvable == null) {
       return null;
     }
-    assert(context != null);
     return (resolvable is CupertinoDynamicColor)
       ? resolvable.resolveFrom(context)
       : resolvable;
@@ -990,11 +1024,11 @@
   CupertinoDynamicColor resolveFrom(BuildContext context) {
     Brightness brightness = Brightness.light;
     if (_isPlatformBrightnessDependent) {
-      brightness =  CupertinoTheme.maybeBrightnessOf(context) ?? Brightness.light;
+      brightness = CupertinoTheme.maybeBrightnessOf(context) ?? Brightness.light;
     }
     bool isHighContrastEnabled = false;
     if (_isHighContrastDependent) {
-      isHighContrastEnabled = MediaQuery.maybeOf(context)?.highContrast ?? false;
+      isHighContrastEnabled = MediaQuery.maybeHighContrastOf(context) ?? false;
     }
 
     final CupertinoUserInterfaceLevelData level = _isInterfaceElevationDependent
diff --git a/framework/lib/src/cupertino/context_menu.dart b/framework/lib/src/cupertino/context_menu.dart
index 5909065..15788df 100644
--- a/framework/lib/src/cupertino/context_menu.dart
+++ b/framework/lib/src/cupertino/context_menu.dart
@@ -99,10 +99,10 @@
 /// [Expanded] widget so that it will grow to fill the Overlay if its size is
 /// unconstrained.
 ///
-/// When closed, the CupertinoContextMenu simply displays the child as if the
-/// CupertinoContextMenu were not there. Sizing and positioning is unaffected.
+/// When closed, the [CupertinoContextMenu] displays the child as if the
+/// [CupertinoContextMenu] were not there. Sizing and positioning is unaffected.
 /// The menu can be closed like other [PopupRoute]s, such as by tapping the
-/// background or by calling `Navigator.pop(context)`. Unlike PopupRoute, it can
+/// background or by calling `Navigator.pop(context)`. Unlike [PopupRoute], it can
 /// also be closed by swiping downwards.
 ///
 /// The [previewBuilder] parameter is most commonly used to display a slight
@@ -111,8 +111,8 @@
 /// Photos app on iOS.
 ///
 /// {@tool dartpad}
-/// This sample shows a very simple CupertinoContextMenu for the Flutter logo.
-/// Simply long press on it to open.
+/// This sample shows a very simple [CupertinoContextMenu] for the Flutter logo.
+/// Long press on it to open.
 ///
 /// ** See code in examples/api/lib/cupertino/context_menu/cupertino_context_menu.0.dart **
 /// {@end-tool}
@@ -142,8 +142,7 @@
       'This feature was deprecated after v3.4.0-34.1.pre.',
     )
     this.previewBuilder = _defaultPreviewBuilder,
-  }) : assert(actions != null && actions.isNotEmpty),
-       assert(child != null),
+  }) : assert(actions.isNotEmpty),
        builder = ((BuildContext context, Animation<double> animation) => child);
 
   /// Creates a context menu with a custom [builder] controlling the widget.
@@ -158,7 +157,7 @@
     super.key,
     required this.actions,
     required this.builder,
-  }) : assert(actions != null && actions.isNotEmpty),
+  }) : assert(actions.isNotEmpty),
        child = null,
        previewBuilder = null;
 
@@ -256,10 +255,9 @@
   /// and when it is fully open. This will overwrite the default animation that
   /// matches the behavior of an iOS 16.0 context menu.
   ///
-  /// This builder can be used instead of the child when either the intended
-  /// child has a property that would conflict with the default animation, like
-  /// a border radius or a shadow, or if simply a more custom animation is
-  /// needed.
+  /// This builder can be used instead of the child when the intended child has
+  /// a property that would conflict with the default animation, such as a
+  /// border radius or a shadow, or if a more custom animation is needed.
   ///
   /// In addition to the current [BuildContext], the function is also called
   /// with an [Animation]. The complete animation goes from 0 to 1 when
@@ -493,7 +491,7 @@
   // it.
   _ContextMenuLocation get _contextMenuLocation {
     final Rect childRect = _getRect(_childGlobalKey);
-    final double screenWidth = MediaQuery.of(context).size.width;
+    final double screenWidth = MediaQuery.sizeOf(context).width;
 
     final double center = screenWidth / 2;
     final bool centerDividesChild = childRect.left < center
@@ -795,8 +793,7 @@
     super.filter,
     required Rect previousChildRect,
     super.settings,
-  }) : assert(actions != null && actions.isNotEmpty),
-       assert(contextMenuLocation != null),
+  }) : assert(actions.isNotEmpty),
        _actions = actions,
        _builder = builder,
        _contextMenuLocation = contextMenuLocation,
@@ -1082,8 +1079,7 @@
     this.onDismiss,
     required this.orientation,
     this.sheetGlobalKey,
-  }) : assert(contextMenuLocation != null),
-       assert(orientation != null);
+  });
 
   final List<Widget>? actions;
   final Widget child;
@@ -1311,7 +1307,7 @@
   Widget _buildChildAnimation(BuildContext context, Widget? child) {
     _lastScale = _getScale(
       widget.orientation,
-      MediaQuery.of(context).size.height,
+      MediaQuery.sizeOf(context).height,
       _moveAnimation.value.dy,
     );
     return Transform.scale(
@@ -1410,9 +1406,7 @@
     required this.actions,
     required _ContextMenuLocation contextMenuLocation,
     required Orientation orientation,
-  }) : assert(actions != null && actions.isNotEmpty),
-       assert(contextMenuLocation != null),
-       assert(orientation != null),
+  }) : assert(actions.isNotEmpty),
        _contextMenuLocation = contextMenuLocation,
        _orientation = orientation;
 
diff --git a/framework/lib/src/cupertino/context_menu_action.dart b/framework/lib/src/cupertino/context_menu_action.dart
index 2906e4f..0f011ea 100644
--- a/framework/lib/src/cupertino/context_menu_action.dart
+++ b/framework/lib/src/cupertino/context_menu_action.dart
@@ -20,9 +20,7 @@
     this.isDestructiveAction = false,
     this.onPressed,
     this.trailingIcon,
-  }) : assert(child != null),
-       assert(isDefaultAction != null),
-       assert(isDestructiveAction != null);
+  });
 
   /// The widget that will be placed inside the action.
   final Widget child;
diff --git a/framework/lib/src/cupertino/date_picker.dart b/framework/lib/src/cupertino/date_picker.dart
index 92234d3..94522f1 100644
--- a/framework/lib/src/cupertino/date_picker.dart
+++ b/framework/lib/src/cupertino/date_picker.dart
@@ -81,8 +81,7 @@
   _DatePickerLayoutDelegate({
     required this.columnWidths,
     required this.textDirectionFactor,
-  }) : assert(columnWidths != null),
-       assert(textDirectionFactor != null);
+  });
 
   // The list containing widths of all columns.
   final List<double> columnWidths;
@@ -277,14 +276,10 @@
     this.dateOrder,
     this.backgroundColor,
   }) : initialDateTime = initialDateTime ?? DateTime.now(),
-       assert(mode != null),
-       assert(onDateTimeChanged != null),
-       assert(minimumYear != null),
        assert(
          minuteInterval > 0 && 60 % minuteInterval == 0,
          'minute interval is not a positive integer factor of 60',
        ) {
-    assert(this.initialDateTime != null);
     assert(
       mode != CupertinoDatePickerMode.dateAndTime || minimumDate == null || !this.initialDateTime.isBefore(minimumDate!),
       'initial date is before minimum date',
@@ -944,7 +939,6 @@
   }
 
   void _scrollToDate(DateTime newDate, DateTime fromDate, bool minCheck) {
-    assert(newDate != null);
     SchedulerBinding.instance.addPostFrameCallback((Duration timestamp) {
       if (fromDate.year != newDate.year || fromDate.month != newDate.month || fromDate.day != newDate.day) {
         _animateColumnControllerToItem(dateController, selectedDayFromInitial);
@@ -1349,7 +1343,6 @@
   }
 
   void _scrollToDate(DateTime newDate) {
-    assert(newDate != null);
     SchedulerBinding.instance.addPostFrameCallback((Duration timestamp) {
       if (selectedYear != newDate.year) {
         _animateColumnControllerToItem(yearController, newDate.year);
@@ -1551,15 +1544,12 @@
     this.alignment = Alignment.center,
     this.backgroundColor,
     required this.onTimerDurationChanged,
-  }) : assert(mode != null),
-       assert(onTimerDurationChanged != null),
-       assert(initialTimerDuration >= Duration.zero),
+  }) : assert(initialTimerDuration >= Duration.zero),
        assert(initialTimerDuration < const Duration(days: 1)),
        assert(minuteInterval > 0 && 60 % minuteInterval == 0),
        assert(secondInterval > 0 && 60 % secondInterval == 0),
        assert(initialTimerDuration.inMinutes % minuteInterval == 0),
-       assert(initialTimerDuration.inSeconds % secondInterval == 0),
-       assert(alignment != null);
+       assert(initialTimerDuration.inSeconds % secondInterval == 0);
 
   /// The mode of the timer picker.
   final CupertinoTimerPickerMode mode;
diff --git a/framework/lib/src/cupertino/desktop_text_selection.dart b/framework/lib/src/cupertino/desktop_text_selection.dart
index 6f529bc..b5dbada 100644
--- a/framework/lib/src/cupertino/desktop_text_selection.dart
+++ b/framework/lib/src/cupertino/desktop_text_selection.dart
@@ -158,12 +158,12 @@
     }
 
     assert(debugCheckHasMediaQuery(context));
-    final MediaQueryData mediaQuery = MediaQuery.of(context);
+    final EdgeInsets mediaQueryPadding = MediaQuery.paddingOf(context);
 
     final Offset midpointAnchor = Offset(
       clampDouble(widget.selectionMidpoint.dx - widget.globalEditableRegion.left,
-        mediaQuery.padding.left,
-        mediaQuery.size.width - mediaQuery.padding.right,
+        mediaQueryPadding.left,
+        MediaQuery.sizeOf(context).width - mediaQueryPadding.right,
       ),
       widget.selectionMidpoint.dy - widget.globalEditableRegion.top,
     );
@@ -171,7 +171,7 @@
     final List<Widget> items = <Widget>[];
     final CupertinoLocalizations localizations = CupertinoLocalizations.of(context);
     final Widget onePhysicalPixelVerticalDivider =
-        SizedBox(width: 1.0 / MediaQuery.of(context).devicePixelRatio);
+        SizedBox(width: 1.0 / MediaQuery.devicePixelRatioOf(context));
 
     void addToolbarButton(
       String text,
diff --git a/framework/lib/src/cupertino/desktop_text_selection_toolbar.dart b/framework/lib/src/cupertino/desktop_text_selection_toolbar.dart
index 831a758..939b3e4 100644
--- a/framework/lib/src/cupertino/desktop_text_selection_toolbar.dart
+++ b/framework/lib/src/cupertino/desktop_text_selection_toolbar.dart
@@ -85,9 +85,8 @@
   @override
   Widget build(BuildContext context) {
     assert(debugCheckHasMediaQuery(context));
-    final MediaQueryData mediaQuery = MediaQuery.of(context);
 
-    final double paddingAbove = mediaQuery.padding.top + _kToolbarScreenPadding;
+    final double paddingAbove = MediaQuery.paddingOf(context).top + _kToolbarScreenPadding;
     final Offset localAdjustment = Offset(_kToolbarScreenPadding, paddingAbove);
 
     return Padding(
diff --git a/framework/lib/src/cupertino/desktop_text_selection_toolbar_button.dart b/framework/lib/src/cupertino/desktop_text_selection_toolbar_button.dart
index f9e11cb..0a88961 100644
--- a/framework/lib/src/cupertino/desktop_text_selection_toolbar_button.dart
+++ b/framework/lib/src/cupertino/desktop_text_selection_toolbar_button.dart
@@ -37,8 +37,7 @@
     super.key,
     required this.onPressed,
     required Widget this.child,
-  }) : assert(child != null),
-       buttonItem = null;
+  }) : buttonItem = null;
 
   /// Create an instance of [CupertinoDesktopTextSelectionToolbarButton] whose child is
   /// a [Text] widget styled like the default Mac context menu button.
@@ -66,8 +65,7 @@
   CupertinoDesktopTextSelectionToolbarButton.buttonItem({
     super.key,
     required ContextMenuButtonItem this.buttonItem,
-  }) : assert(buttonItem != null),
-       onPressed = buttonItem.onPressed,
+  }) : onPressed = buttonItem.onPressed,
        child = null;
 
   /// {@macro flutter.cupertino.CupertinoTextSelectionToolbarButton.onPressed}
diff --git a/framework/lib/src/cupertino/dialog.dart b/framework/lib/src/cupertino/dialog.dart
index 876026b..d398462 100644
--- a/framework/lib/src/cupertino/dialog.dart
+++ b/framework/lib/src/cupertino/dialog.dart
@@ -149,8 +149,8 @@
 // Accessibility mode on iOS is determined by the text scale factor that the
 // user has selected.
 bool _isInAccessibilityMode(BuildContext context) {
-  final MediaQueryData? data = MediaQuery.maybeOf(context);
-  return data != null && data.textScaleFactor > _kMaxRegularTextScaleFactor;
+  final double? factor = MediaQuery.maybeTextScaleFactorOf(context);
+  return factor != null && factor > _kMaxRegularTextScaleFactor;
 }
 
 /// An iOS-style alert dialog.
@@ -201,7 +201,7 @@
     this.actionScrollController,
     this.insetAnimationDuration = const Duration(milliseconds: 100),
     this.insetAnimationCurve = Curves.decelerate,
-  }) : assert(actions != null);
+  });
 
   /// The (optional) title of the dialog is displayed in a large font at the top
   /// of the dialog.
@@ -257,7 +257,7 @@
   final Curve insetAnimationCurve;
 
   Widget _buildContent(BuildContext context) {
-    final double textScaleFactor = MediaQuery.of(context).textScaleFactor;
+    final double textScaleFactor = MediaQuery.textScaleFactorOf(context);
 
     final List<Widget> children = <Widget>[
       if (title != null || content != null)
@@ -289,7 +289,7 @@
         ),
     ];
 
-    return Container(
+    return ColoredBox(
       color: CupertinoDynamicColor.resolve(_kDialogColor, context),
       child: Column(
         mainAxisSize: MainAxisSize.min,
@@ -317,7 +317,7 @@
   Widget build(BuildContext context) {
     final CupertinoLocalizations localizations = CupertinoLocalizations.of(context);
     final bool isInAccessibilityMode = _isInAccessibilityMode(context);
-    final double textScaleFactor = MediaQuery.of(context).textScaleFactor;
+    final double textScaleFactor = MediaQuery.textScaleFactorOf(context);
     return CupertinoUserInterfaceLevel(
       data: CupertinoUserInterfaceLevelData.elevated,
       child: MediaQuery(
@@ -331,7 +331,7 @@
           child: LayoutBuilder(
             builder: (BuildContext context, BoxConstraints constraints) {
               return AnimatedPadding(
-                padding: MediaQuery.of(context).viewInsets +
+                padding: MediaQuery.viewInsetsOf(context) +
                     const EdgeInsets.symmetric(horizontal: 40.0, vertical: 24.0),
                 duration: insetAnimationDuration,
                 curve: insetAnimationCurve,
@@ -555,7 +555,7 @@
       content.add(Flexible(child: titleSection));
     }
 
-    return Container(
+    return ColoredBox(
       color: CupertinoDynamicColor.resolve(_kActionSheetBackgroundColor, context),
       child: Column(
         mainAxisSize: MainAxisSize.min,
@@ -611,12 +611,12 @@
       if (cancelButton != null) _buildCancelButton(),
     ];
 
-    final Orientation orientation = MediaQuery.of(context).orientation;
+    final Orientation orientation = MediaQuery.orientationOf(context);
     final double actionSheetWidth;
     if (orientation == Orientation.portrait) {
-      actionSheetWidth = MediaQuery.of(context).size.width - (_kActionSheetEdgeHorizontalPadding * 2);
+      actionSheetWidth = MediaQuery.sizeOf(context).width - (_kActionSheetEdgeHorizontalPadding * 2);
     } else {
-      actionSheetWidth = MediaQuery.of(context).size.height - (_kActionSheetEdgeHorizontalPadding * 2);
+      actionSheetWidth = MediaQuery.sizeOf(context).height - (_kActionSheetEdgeHorizontalPadding * 2);
     }
 
     return SafeArea(
@@ -665,8 +665,7 @@
     this.isDefaultAction = false,
     this.isDestructiveAction = false,
     required this.child,
-  }) : assert(child != null),
-       assert(onPressed != null);
+  });
 
   /// The callback that is called when the button is tapped.
   ///
@@ -701,7 +700,7 @@
     }
 
     return MouseRegion(
-      cursor: onPressed != null && kIsWeb ? SystemMouseCursors.click : MouseCursor.defer,
+      cursor: kIsWeb ? SystemMouseCursors.click : MouseCursor.defer,
       child: GestureDetector(
         onTap: onPressed,
         behavior: HitTestBehavior.opaque,
@@ -797,7 +796,7 @@
   @override
   RenderObject createRenderObject(BuildContext context) {
     return _RenderCupertinoDialog(
-      dividerThickness: _kDividerThickness / MediaQuery.of(context).devicePixelRatio,
+      dividerThickness: _kDividerThickness / MediaQuery.devicePixelRatioOf(context),
       isInAccessibilityMode: _isInAccessibilityMode(context) && !isActionSheet,
       dividerColor: CupertinoDynamicColor.resolve(dividerColor, context),
       isActionSheet: isActionSheet,
@@ -896,7 +895,6 @@
   }
 
   void _placeChildInSlot(RenderObject child, _AlertDialogSections slot) {
-    assert(slot != null);
     switch (slot) {
       case _AlertDialogSections.contentSection:
         renderObject.contentSection = child as RenderBox;
@@ -1443,7 +1441,7 @@
     this.scrollController,
     this.hasCancelButton = false,
     this.isActionSheet = false,
-  }) : assert(children != null);
+  });
 
   final List<Widget> children;
 
@@ -1464,7 +1462,7 @@
 
   @override
   Widget build(BuildContext context) {
-    final double devicePixelRatio = MediaQuery.of(context).devicePixelRatio;
+    final double devicePixelRatio = MediaQuery.devicePixelRatioOf(context);
 
     final List<Widget> interactiveButtons = <Widget>[];
     for (int i = 0; i < children.length; i += 1) {
@@ -1593,9 +1591,7 @@
     this.isDestructiveAction = false,
     this.textStyle,
     required this.child,
-  }) : assert(child != null),
-       assert(isDefaultAction != null),
-       assert(isDestructiveAction != null);
+  });
 
   /// The callback that is called when the button is tapped or otherwise
   /// activated.
@@ -1767,7 +1763,7 @@
 //
 // See [_RenderCupertinoDialogActions] for specific layout policy details.
 class _CupertinoDialogActionsRenderWidget extends MultiChildRenderObjectWidget {
-  _CupertinoDialogActionsRenderWidget({
+  const _CupertinoDialogActionsRenderWidget({
     required List<Widget> actionButtons,
     double dividerThickness = 0.0,
     bool hasCancelButton = false,
diff --git a/framework/lib/src/cupertino/icons.dart b/framework/lib/src/cupertino/icons.dart
index 94188e6..3332770 100644
--- a/framework/lib/src/cupertino/icons.dart
+++ b/framework/lib/src/cupertino/icons.dart
@@ -30,9 +30,9 @@
 /// ![The following code snippet would generate a row of icons consisting of a pink heart, a green bell, and a blue umbrella, each progressively bigger than the last.](https://flutter.github.io/assets-for-api-docs/assets/cupertino/cupertino_icon.png)
 ///
 /// ```dart
-/// Row(
+/// const Row(
 ///   mainAxisAlignment: MainAxisAlignment.spaceAround,
-///   children: const <Widget>[
+///   children: <Widget>[
 ///     Icon(
 ///       CupertinoIcons.heart_fill,
 ///       color: Colors.pink,
@@ -59,6 +59,7 @@
 /// See also:
 ///
 ///  * [Icon], used to show these icons.
+@staticIconProvider
 class CupertinoIcons {
   // This class is not meant to be instantiated or extended; this constructor
   // prevents instantiation and extension.
diff --git a/framework/lib/src/cupertino/interface_level.dart b/framework/lib/src/cupertino/interface_level.dart
index 6f35554..c11f17f 100644
--- a/framework/lib/src/cupertino/interface_level.dart
+++ b/framework/lib/src/cupertino/interface_level.dart
@@ -45,8 +45,7 @@
     super.key,
     required CupertinoUserInterfaceLevelData data,
     required super.child,
-  }) : assert(data != null),
-      _data = data;
+  }) : _data = data;
 
   final CupertinoUserInterfaceLevelData _data;
 
@@ -65,7 +64,6 @@
   ///  * [maybeOf], which is similar, but will return null if no
   ///    [CupertinoUserInterfaceLevel] encloses the given context.
   static CupertinoUserInterfaceLevelData of(BuildContext context) {
-    assert(context != null);
     final CupertinoUserInterfaceLevel? query = context.dependOnInheritedWidgetOfExactType<CupertinoUserInterfaceLevel>();
     if (query != null) {
       return query._data;
@@ -95,7 +93,6 @@
   ///  * [of], which is similar, but will throw an exception if no
   ///    [CupertinoUserInterfaceLevel] encloses the given context.
   static CupertinoUserInterfaceLevelData? maybeOf(BuildContext context) {
-    assert(context != null);
     final CupertinoUserInterfaceLevel? query = context.dependOnInheritedWidgetOfExactType<CupertinoUserInterfaceLevel>();
     if (query != null) {
       return query._data;
diff --git a/framework/lib/src/cupertino/list_section.dart b/framework/lib/src/cupertino/list_section.dart
index 26bf0d9..e917133 100644
--- a/framework/lib/src/cupertino/list_section.dart
+++ b/framework/lib/src/cupertino/list_section.dart
@@ -347,7 +347,7 @@
   @override
   Widget build(BuildContext context) {
     final Color dividerColor = CupertinoColors.separator.resolveFrom(context);
-    final double dividerHeight = 1.0 / MediaQuery.of(context).devicePixelRatio;
+    final double dividerHeight = 1.0 / MediaQuery.devicePixelRatioOf(context);
 
     // Long divider is used for wrapping the top and bottom of rows.
     // Only used in CupertinoListSectionType.base mode.
diff --git a/framework/lib/src/cupertino/magnifier.dart b/framework/lib/src/cupertino/magnifier.dart
index a1ed06e..05a92b9 100644
--- a/framework/lib/src/cupertino/magnifier.dart
+++ b/framework/lib/src/cupertino/magnifier.dart
@@ -174,7 +174,7 @@
               CupertinoMagnifier.kMagnifierAboveFocalPoint),
     );
 
-    final Rect screenRect = Offset.zero & MediaQuery.of(context).size;
+    final Rect screenRect = Offset.zero & MediaQuery.sizeOf(context);
 
     // Adjust the magnifier position so that it never exists outside the horizontal
     // padding.
diff --git a/framework/lib/src/cupertino/nav_bar.dart b/framework/lib/src/cupertino/nav_bar.dart
index 405e02b..6d73f55 100644
--- a/framework/lib/src/cupertino/nav_bar.dart
+++ b/framework/lib/src/cupertino/nav_bar.dart
@@ -33,6 +33,8 @@
 
 const double _kNavBarEdgePadding = 16.0;
 
+const double _kNavBarBottomPadding = 8.0;
+
 const double _kNavBarBackButtonTapWidth = 50.0;
 
 /// Title text transfer fade.
@@ -147,8 +149,20 @@
         overlayStyle = SystemUiOverlayStyle.dark;
         break;
     }
+    // [SystemUiOverlayStyle.light] and [SystemUiOverlayStyle.dark] set some system
+    // navigation bar properties,
+    // Before https://github.com/flutter/flutter/pull/104827 those properties
+    // had no effect, now they are used if there is no AnnotatedRegion on the
+    // bottom of the screen.
+    // For backward compatibility, create a `SystemUiOverlayStyle` without the
+    // system navigation bar properties.
     result = AnnotatedRegion<SystemUiOverlayStyle>(
-      value: overlayStyle,
+      value: SystemUiOverlayStyle(
+        statusBarColor: overlayStyle.statusBarColor,
+        statusBarBrightness: overlayStyle.statusBarBrightness,
+        statusBarIconBrightness: overlayStyle.statusBarIconBrightness,
+        systemStatusBarContrastEnforced: overlayStyle.systemStatusBarContrastEnforced,
+      ),
       child: result,
     );
   }
@@ -255,15 +269,7 @@
     this.padding,
     this.transitionBetweenRoutes = true,
     this.heroTag = _defaultHeroTag,
-  }) : assert(automaticallyImplyLeading != null),
-       assert(automaticallyImplyMiddle != null),
-       assert(transitionBetweenRoutes != null),
-       assert(
-         heroTag != null,
-         'heroTag cannot be null. Use transitionBetweenRoutes = false to '
-         'disable Hero transition on this navigation bar.',
-       ),
-       assert(
+  }) : assert(
          !transitionBetweenRoutes || identical(heroTag, _defaultHeroTag),
          'Cannot specify a heroTag override if this navigation bar does not '
          'transition due to transitionBetweenRoutes = false.',
@@ -597,9 +603,7 @@
     this.transitionBetweenRoutes = true,
     this.heroTag = _defaultHeroTag,
     this.stretch = false,
-  }) : assert(automaticallyImplyLeading != null),
-       assert(automaticallyImplyTitle != null),
-       assert(
+  }) : assert(
          automaticallyImplyTitle == true || largeTitle != null,
          'No largeTitle has been provided but automaticallyImplyTitle is also '
          'false. Either provide a largeTitle or set automaticallyImplyTitle to '
@@ -754,7 +758,7 @@
           actionsForegroundColor: CupertinoTheme.of(context).primaryColor,
           transitionBetweenRoutes: widget.transitionBetweenRoutes,
           heroTag: widget.heroTag,
-          persistentHeight: _kNavBarPersistentHeight + MediaQuery.of(context).padding.top,
+          persistentHeight: _kNavBarPersistentHeight + MediaQuery.paddingOf(context).top,
           alwaysShowMiddle: widget.alwaysShowMiddle && widget.middle != null,
           stretchConfiguration: widget.stretch ? OverScrollHeaderStretchConfiguration() : null,
         ),
@@ -779,9 +783,7 @@
     required this.persistentHeight,
     required this.alwaysShowMiddle,
     required this.stretchConfiguration,
-  }) : assert(persistentHeight != null),
-       assert(alwaysShowMiddle != null),
-       assert(transitionBetweenRoutes != null);
+  });
 
   final _NavigationBarStaticComponentsKeys keys;
   final _NavigationBarStaticComponents components;
@@ -833,31 +835,27 @@
               right: 0.0,
               bottom: 0.0,
               child: ClipRect(
-                // The large title starts at the persistent bar.
-                // It's aligned with the bottom of the sliver and expands clipped
-                // and behind the persistent bar.
-                child: OverflowBox(
-                  minHeight: 0.0,
-                  maxHeight: double.infinity,
-                  alignment: AlignmentDirectional.bottomStart,
-                  child: Padding(
-                    padding: const EdgeInsetsDirectional.only(
-                      start: _kNavBarEdgePadding,
-                      bottom: 8.0, // Bottom has a different padding.
-                    ),
-                    child: SafeArea(
-                      top: false,
-                      bottom: false,
-                      child: AnimatedOpacity(
-                        opacity: showLargeTitle ? 1.0 : 0.0,
-                        duration: _kNavBarTitleFadeDuration,
-                        child: Semantics(
-                          header: true,
-                          child: DefaultTextStyle(
-                            style: CupertinoTheme.of(context).textTheme.navLargeTitleTextStyle,
-                            maxLines: 1,
-                            overflow: TextOverflow.ellipsis,
-                            child: components.largeTitle!,
+                child: Padding(
+                  padding: const EdgeInsetsDirectional.only(
+                    start: _kNavBarEdgePadding,
+                    bottom: _kNavBarBottomPadding
+                  ),
+                  child: SafeArea(
+                    top: false,
+                    bottom: false,
+                    child: AnimatedOpacity(
+                      opacity: showLargeTitle ? 1.0 : 0.0,
+                      duration: _kNavBarTitleFadeDuration,
+                      child: Semantics(
+                        header: true,
+                        child: DefaultTextStyle(
+                          style: CupertinoTheme.of(context)
+                              .textTheme
+                              .navLargeTitleTextStyle,
+                          maxLines: 1,
+                          overflow: TextOverflow.ellipsis,
+                          child: _LargeTitle(
+                            child: components.largeTitle,
                           ),
                         ),
                       ),
@@ -921,6 +919,123 @@
   }
 }
 
+/// The large title of the navigation bar.
+///
+/// Magnifies on over-scroll when [CupertinoSliverNavigationBar.stretch]
+/// parameter is true.
+class _LargeTitle extends SingleChildRenderObjectWidget {
+  const _LargeTitle({ super.child });
+
+  @override
+  _RenderLargeTitle createRenderObject(BuildContext context) {
+    return _RenderLargeTitle(alignment: AlignmentDirectional.bottomStart.resolve(Directionality.of(context)));
+  }
+
+  @override
+  void updateRenderObject(BuildContext context, _RenderLargeTitle renderObject) {
+    renderObject.alignment = AlignmentDirectional.bottomStart.resolve(Directionality.of(context));
+  }
+}
+
+class _RenderLargeTitle extends RenderShiftedBox {
+  _RenderLargeTitle({
+    required Alignment alignment,
+  })  : _alignment = alignment,
+        super(null);
+
+  Alignment get alignment => _alignment;
+  Alignment _alignment;
+  set alignment(Alignment value) {
+    if (_alignment == value) {
+      return;
+    }
+    _alignment = value;
+
+    markNeedsLayout();
+  }
+
+  double _scale = 1.0;
+
+  @override
+  void performLayout() {
+    final RenderBox? child = this.child;
+    Size childSize = Size.zero;
+
+    size = constraints.biggest;
+
+    if (child == null) {
+      return;
+    }
+
+    final BoxConstraints childConstriants = constraints.widthConstraints().loosen();
+    child.layout(childConstriants, parentUsesSize: true);
+
+    final double maxScale = child.size.width != 0.0
+      ? clampDouble(constraints.maxWidth / child.size.width, 1.0, 1.1)
+      : 1.1;
+    _scale = clampDouble(
+      1.0 + (constraints.maxHeight - (_kNavBarLargeTitleHeightExtension - _kNavBarBottomPadding)) / (_kNavBarLargeTitleHeightExtension - _kNavBarBottomPadding) * 0.03,
+      1.0,
+      maxScale,
+    );
+
+    childSize = child.size * _scale;
+    final BoxParentData childParentData = child.parentData! as BoxParentData;
+    childParentData.offset = alignment.alongOffset(size - childSize as Offset);
+  }
+
+  @override
+  void applyPaintTransform(RenderBox child, Matrix4 transform) {
+    assert(child == this.child);
+
+    super.applyPaintTransform(child, transform);
+
+    transform.scale(_scale, _scale);
+  }
+
+  @override
+  void paint(PaintingContext context, Offset offset) {
+    final RenderBox? child = this.child;
+
+    if (child == null) {
+      layer = null;
+    } else {
+      final BoxParentData childParentData = child.parentData! as BoxParentData;
+
+      layer = context.pushTransform(
+        needsCompositing,
+        offset + childParentData.offset,
+        Matrix4.diagonal3Values(_scale, _scale, 1.0),
+        (PaintingContext context, Offset offset) => context.paintChild(child, offset),
+        oldLayer: layer as TransformLayer?,
+      );
+    }
+  }
+
+  @override
+  bool hitTestChildren(BoxHitTestResult result, {required Offset position}) {
+    final RenderBox? child = this.child;
+
+    if (child == null) {
+      return false;
+    }
+
+    final Offset childOffset = (child.parentData! as BoxParentData).offset;
+
+    final Matrix4 transform = Matrix4.identity()
+      ..scale(1.0/_scale, 1.0/_scale, 1.0)
+      ..translate(-childOffset.dx, -childOffset.dy);
+
+    return result.addWithRawTransform(
+      transform: transform,
+      position: position,
+      hitTest: (BoxHitTestResult result, Offset transformed) {
+        return child.hitTest(result, position: transformed);
+      }
+    );
+  }
+}
+
 /// The top part of the navigation bar that's never scrolled away.
 ///
 /// Consists of the entire navigation bar without background and border when used
@@ -989,7 +1104,7 @@
     }
 
     return SizedBox(
-      height: _kNavBarPersistentHeight + MediaQuery.of(context).padding.top,
+      height: _kNavBarPersistentHeight + MediaQuery.paddingOf(context).top,
       child: SafeArea(
         bottom: false,
         child: paddedToolbar,
@@ -1453,7 +1568,7 @@
   // null here and unused.
   Widget _buildPreviousTitleWidget(BuildContext context, String? previousTitle, Widget? child) {
     if (previousTitle == null) {
-      return const SizedBox(height: 0.0, width: 0.0);
+      return const SizedBox.shrink();
     }
 
     Text textWidget = Text(
@@ -1487,7 +1602,7 @@
         builder: _buildPreviousTitleWidget,
       );
     } else {
-      return const SizedBox(height: 0.0, width: 0.0);
+      return const SizedBox.shrink();
     }
   }
 }
@@ -1511,9 +1626,7 @@
     required this.hasUserMiddle,
     required this.largeExpanded,
     required this.child,
-  }) : assert(componentsKeys != null),
-       assert(largeExpanded != null),
-       assert(!largeExpanded || largeTitleTextStyle != null),
+  }) : assert(!largeExpanded || largeTitleTextStyle != null),
        super(key: componentsKeys.navBarBoxKey);
 
   final _NavigationBarStaticComponentsKeys componentsKeys;
@@ -1655,12 +1768,16 @@
     // The actual outer box is big enough to contain both the bottom and top
     // navigation bars. It's not a direct Rect lerp because some components
     // can actually be outside the linearly lerp'ed Rect in the middle of
-    // the animation, such as the topLargeTitle.
-    return SizedBox(
-      height: math.max(heightTween.begin!, heightTween.end!) + MediaQuery.of(context).padding.top,
-      width: double.infinity,
-      child: Stack(
-        children: children,
+    // the animation, such as the topLargeTitle. The textScaleFactor is kept
+    // at 1 to avoid odd transitions between pages.
+    return MediaQuery(
+      data: MediaQuery.of(context).copyWith(textScaleFactor: 1),
+      child: SizedBox(
+        height: math.max(heightTween.begin!, heightTween.end!) + MediaQuery.paddingOf(context).top,
+        width: double.infinity,
+        child: Stack(
+          children: children,
+        ),
       ),
     );
   }
@@ -1981,7 +2098,7 @@
       return null;
     }
 
-    if (bottomLargeTitle != null && topBackLabel != null) {
+    if (topBackLabel != null) {
       // Move from current position to the top page's back label position.
       return slideFromLeadingEdge(
         fromKey: bottomComponents.largeTitleKey,
@@ -2008,7 +2125,7 @@
       );
     }
 
-    if (bottomLargeTitle != null && topLeading != null) {
+    if (topLeading != null) {
       // Unlike bottom middle, the bottom large title moves when it can't
       // transition to the top back label position.
       final RelativeRect from = positionInTransitionBox(bottomComponents.largeTitleKey, from: bottomNavBarBox);
@@ -2139,7 +2256,6 @@
     // text is too long, the topBackLabel will say 'Back' instead of the original
     // text.
     if (bottomLargeTitle != null &&
-        topBackLabel != null &&
         bottomLargeExpanded) {
       return slideFromLeadingEdge(
         fromKey: bottomComponents.largeTitleKey,
@@ -2163,7 +2279,7 @@
 
     // The topBackLabel always comes from the large title first if available
     // and expanded instead of middle.
-    if (bottomMiddle != null && topBackLabel != null) {
+    if (bottomMiddle != null) {
       return slideFromLeadingEdge(
         fromKey: bottomComponents.middleKey,
         fromNavBarBox: bottomNavBarBox,
@@ -2333,10 +2449,6 @@
   BuildContext fromHeroContext,
   BuildContext toHeroContext,
 ) {
-  assert(animation != null);
-  assert(flightDirection != null);
-  assert(fromHeroContext != null);
-  assert(toHeroContext != null);
   assert(fromHeroContext.widget is Hero);
   assert(toHeroContext.widget is Hero);
 
@@ -2349,8 +2461,6 @@
   final _TransitionableNavigationBar fromNavBar = fromHeroWidget.child as _TransitionableNavigationBar;
   final _TransitionableNavigationBar toNavBar = toHeroWidget.child as _TransitionableNavigationBar;
 
-  assert(fromNavBar.componentsKeys != null);
-  assert(toNavBar.componentsKeys != null);
 
   assert(
     fromNavBar.componentsKeys.navBarBoxKey.currentContext!.owner != null,
diff --git a/framework/lib/src/cupertino/page_scaffold.dart b/framework/lib/src/cupertino/page_scaffold.dart
index 05e740b..a605143 100644
--- a/framework/lib/src/cupertino/page_scaffold.dart
+++ b/framework/lib/src/cupertino/page_scaffold.dart
@@ -39,8 +39,7 @@
     this.backgroundColor,
     this.resizeToAvoidBottomInset = true,
     required this.child,
-  }) : assert(child != null),
-       assert(resizeToAvoidBottomInset != null);
+  });
 
   /// The [navigationBar], typically a [CupertinoNavigationBar], is drawn at the
   /// top of the screen.
diff --git a/framework/lib/src/cupertino/picker.dart b/framework/lib/src/cupertino/picker.dart
index b6fa83c..7f42a1e 100644
--- a/framework/lib/src/cupertino/picker.dart
+++ b/framework/lib/src/cupertino/picker.dart
@@ -83,13 +83,9 @@
     required List<Widget> children,
     this.selectionOverlay = const CupertinoPickerDefaultSelectionOverlay(),
     bool looping = false,
-  }) : assert(children != null),
-       assert(diameterRatio != null),
-       assert(diameterRatio > 0.0, RenderListWheelViewport.diameterRatioZeroMessage),
+  }) : assert(diameterRatio > 0.0, RenderListWheelViewport.diameterRatioZeroMessage),
        assert(magnification > 0),
-       assert(itemExtent != null),
        assert(itemExtent > 0),
-       assert(squeeze != null),
        assert(squeeze > 0),
        childDelegate = looping
                        ? ListWheelChildLoopingListDelegate(children: children)
@@ -126,13 +122,9 @@
     required NullableIndexedWidgetBuilder itemBuilder,
     int? childCount,
     this.selectionOverlay = const CupertinoPickerDefaultSelectionOverlay(),
-  }) : assert(itemBuilder != null),
-       assert(diameterRatio != null),
-       assert(diameterRatio > 0.0, RenderListWheelViewport.diameterRatioZeroMessage),
+  }) : assert(diameterRatio > 0.0, RenderListWheelViewport.diameterRatioZeroMessage),
        assert(magnification > 0),
-       assert(itemExtent != null),
        assert(itemExtent > 0),
-       assert(squeeze != null),
        assert(squeeze > 0),
        childDelegate = ListWheelChildBuilderDelegate(builder: itemBuilder, childCount: childCount);
 
@@ -254,7 +246,6 @@
         hasSuitableHapticHardware = false;
         break;
     }
-    assert(hasSuitableHapticHardware != null);
     if (hasSuitableHapticHardware && index != _lastHapticIndex) {
       _lastHapticIndex = index;
       HapticFeedback.selectionClick();
@@ -355,9 +346,7 @@
     this.background = CupertinoColors.tertiarySystemFill,
     this.capStartEdge = true,
     this.capEndEdge = true,
-  }) : assert(background != null),
-       assert(capStartEdge != null),
-       assert(capEndEdge != null);
+  });
 
   /// Whether to use the default use rounded corners and margin on the start side.
   final bool capStartEdge;
diff --git a/framework/lib/src/cupertino/refresh.dart b/framework/lib/src/cupertino/refresh.dart
index 0c122ba..3ada10c 100644
--- a/framework/lib/src/cupertino/refresh.dart
+++ b/framework/lib/src/cupertino/refresh.dart
@@ -20,9 +20,7 @@
     this.refreshIndicatorLayoutExtent = 0.0,
     this.hasLayoutExtent = false,
     super.child,
-  }) : assert(refreshIndicatorLayoutExtent != null),
-       assert(refreshIndicatorLayoutExtent >= 0.0),
-       assert(hasLayoutExtent != null);
+  }) : assert(refreshIndicatorLayoutExtent >= 0.0);
 
   // The amount of space the indicator should occupy in the sliver in a
   // resting state when in the refreshing mode.
@@ -61,9 +59,7 @@
     required double refreshIndicatorExtent,
     required bool hasLayoutExtent,
     RenderBox? child,
-  }) : assert(refreshIndicatorExtent != null),
-       assert(refreshIndicatorExtent >= 0.0),
-       assert(hasLayoutExtent != null),
+  }) : assert(refreshIndicatorExtent >= 0.0),
        _refreshIndicatorExtent = refreshIndicatorExtent,
        _hasLayoutExtent = hasLayoutExtent {
     this.child = child;
@@ -74,7 +70,6 @@
   double get refreshIndicatorLayoutExtent => _refreshIndicatorExtent;
   double _refreshIndicatorExtent;
   set refreshIndicatorLayoutExtent(double value) {
-    assert(value != null);
     assert(value >= 0.0);
     if (value == _refreshIndicatorExtent) {
       return;
@@ -89,7 +84,6 @@
   bool get hasLayoutExtent => _hasLayoutExtent;
   bool _hasLayoutExtent;
   set hasLayoutExtent(bool value) {
-    assert(value != null);
     if (value == _hasLayoutExtent) {
       return;
     }
@@ -303,9 +297,7 @@
     this.refreshIndicatorExtent = _defaultRefreshIndicatorExtent,
     this.builder = buildRefreshIndicator,
     this.onRefresh,
-  }) : assert(refreshTriggerPullDistance != null),
-       assert(refreshTriggerPullDistance > 0.0),
-       assert(refreshIndicatorExtent != null),
+  }) : assert(refreshTriggerPullDistance > 0.0),
        assert(refreshIndicatorExtent >= 0.0),
        assert(
          refreshTriggerPullDistance >= refreshIndicatorExtent,
diff --git a/framework/lib/src/cupertino/route.dart b/framework/lib/src/cupertino/route.dart
index 587fa13..bb158ad 100644
--- a/framework/lib/src/cupertino/route.dart
+++ b/framework/lib/src/cupertino/route.dart
@@ -225,21 +225,11 @@
   @override
   Widget buildPage(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
     final Widget child = buildContent(context);
-    final Widget result = Semantics(
+    return Semantics(
       scopesRoute: true,
       explicitChildNodes: true,
       child: child,
     );
-    assert(() {
-      if (child == null) {
-        throw FlutterError.fromParts(<DiagnosticsNode>[
-          ErrorSummary('The builder for route "${settings.name}" returned null.'),
-          ErrorDescription('Route builders must never return null.'),
-        ]);
-      }
-      return true;
-    }());
-    return result;
   }
 
   // Called by _CupertinoBackGestureDetector when a pop ("back") drag start
@@ -343,9 +333,7 @@
     this.maintainState = true,
     super.fullscreenDialog,
     super.allowSnapshotting = true,
-  }) : assert(builder != null),
-       assert(maintainState != null),
-       assert(fullscreenDialog != null) {
+  }) {
     assert(opaque);
   }
 
@@ -373,8 +361,7 @@
   _PageBasedCupertinoPageRoute({
     required CupertinoPage<T> page,
     super.allowSnapshotting = true,
-  }) : assert(page != null),
-       super(settings: page) {
+  }) : super(settings: page) {
     assert(opaque);
   }
 
@@ -424,9 +411,7 @@
     super.name,
     super.arguments,
     super.restorationId,
-  }) : assert(child != null),
-       assert(maintainState != null),
-       assert(fullscreenDialog != null);
+  });
 
   /// The content to be shown in the [Route] created by this page.
   final Widget child;
@@ -468,8 +453,7 @@
     required Animation<double> secondaryRouteAnimation,
     required this.child,
     required bool linearTransition,
-  }) : assert(linearTransition != null),
-       _primaryPositionAnimation =
+  }) : _primaryPositionAnimation =
            (linearTransition
              ? primaryRouteAnimation
              : CurvedAnimation(
@@ -607,9 +591,7 @@
     required this.enabledCallback,
     required this.onStartPopGesture,
     required this.child,
-  }) : assert(enabledCallback != null),
-       assert(onStartPopGesture != null),
-       assert(child != null);
+  });
 
   final Widget child;
 
@@ -690,8 +672,8 @@
     // For devices with notches, the drag area needs to be larger on the side
     // that has the notch.
     double dragAreaWidth = Directionality.of(context) == TextDirection.ltr ?
-                           MediaQuery.of(context).padding.left :
-                           MediaQuery.of(context).padding.right;
+                           MediaQuery.paddingOf(context).left :
+                           MediaQuery.paddingOf(context).right;
     dragAreaWidth = max(dragAreaWidth, _kBackGestureWidth);
     return Stack(
       fit: StackFit.passthrough,
@@ -731,8 +713,7 @@
   _CupertinoBackGestureController({
     required this.navigator,
     required this.controller,
-  }) : assert(navigator != null),
-       assert(controller != null) {
+  }) {
     navigator.didStartUserGesture();
   }
 
@@ -854,7 +835,6 @@
     _CupertinoEdgeShadowDecoration? b,
     double t,
   ) {
-    assert(t != null);
     if (a == null && b == null) {
       return null;
     }
@@ -921,8 +901,7 @@
   _CupertinoEdgeShadowPainter(
     this._decoration,
     super.onChange,
-  ) : assert(_decoration != null),
-      assert(_decoration._colors == null || _decoration._colors!.length > 1);
+  ) : assert(_decoration._colors == null || _decoration._colors!.length > 1);
 
   final _CupertinoEdgeShadowDecoration _decoration;
 
@@ -1031,14 +1010,12 @@
     this.barrierLabel = 'Dismiss',
     this.barrierColor = kCupertinoModalBarrierColor,
     bool barrierDismissible = true,
-    bool? semanticsDismissible,
+    bool semanticsDismissible = false,
     super.filter,
     super.settings,
     this.anchorPoint,
-  }) {
-    _barrierDismissible = barrierDismissible;
-    _semanticsDismissible = semanticsDismissible;
-  }
+  }) : _barrierDismissible = barrierDismissible,
+       _semanticsDismissible = semanticsDismissible;
 
   /// A builder that builds the widget tree for the [CupertinoModalPopupRoute].
   ///
@@ -1050,9 +1027,9 @@
   /// widget needs to update dynamically.
   final WidgetBuilder builder;
 
-  bool? _barrierDismissible;
+  final bool _barrierDismissible;
 
-  bool? _semanticsDismissible;
+  final bool _semanticsDismissible;
 
   @override
   final String barrierLabel;
@@ -1061,10 +1038,10 @@
   final Color? barrierColor;
 
   @override
-  bool get barrierDismissible => _barrierDismissible ?? true;
+  bool get barrierDismissible => _barrierDismissible;
 
   @override
-  bool get semanticsDismissible => _semanticsDismissible ?? false;
+  bool get semanticsDismissible => _semanticsDismissible;
 
   @override
   Duration get transitionDuration => _kModalPopupTransitionDuration;
@@ -1188,11 +1165,10 @@
   Color barrierColor = kCupertinoModalBarrierColor,
   bool barrierDismissible = true,
   bool useRootNavigator = true,
-  bool? semanticsDismissible,
+  bool semanticsDismissible = false,
   RouteSettings? routeSettings,
   Offset? anchorPoint,
 }) {
-  assert(useRootNavigator != null);
   return Navigator.of(context, rootNavigator: useRootNavigator).push(
     CupertinoModalPopupRoute<T>(
       builder: builder,
@@ -1297,8 +1273,6 @@
   RouteSettings? routeSettings,
   Offset? anchorPoint,
 }) {
-  assert(builder != null);
-  assert(useRootNavigator != null);
 
   return Navigator.of(context, rootNavigator: useRootNavigator).push<T>(CupertinoDialogRoute<T>(
     builder: builder,
@@ -1361,8 +1335,7 @@
     super.transitionBuilder = _buildCupertinoDialogTransitions,
     super.settings,
     super.anchorPoint,
-  }) : assert(barrierDismissible != null),
-      super(
+  }) : super(
         pageBuilder: (BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
           return builder(context);
         },
diff --git a/framework/lib/src/cupertino/scrollbar.dart b/framework/lib/src/cupertino/scrollbar.dart
index b7bce96..ee75dc1 100644
--- a/framework/lib/src/cupertino/scrollbar.dart
+++ b/framework/lib/src/cupertino/scrollbar.dart
@@ -29,7 +29,7 @@
 
 /// An iOS style scrollbar.
 ///
-/// To add a scrollbar to a [ScrollView], simply wrap the scroll view widget in
+/// To add a scrollbar to a [ScrollView], wrap the scroll view widget in
 /// a [CupertinoScrollbar] widget.
 ///
 /// {@youtube 560 315 https://www.youtube.com/watch?v=DbkIQSvwnZc}
@@ -87,12 +87,8 @@
       'This feature was deprecated after v2.9.0-1.0.pre.',
     )
     bool? isAlwaysShown,
-  }) : assert(thickness != null),
-       assert(thickness < double.infinity),
-       assert(thicknessWhileDragging != null),
+  }) : assert(thickness < double.infinity),
        assert(thicknessWhileDragging < double.infinity),
-       assert(radius != null),
-       assert(radiusWhileDragging != null),
        assert(
          isAlwaysShown == null || thumbVisibility == null,
          'Scrollbar thumb appearance should only be controlled with thumbVisibility, '
@@ -171,7 +167,7 @@
       ..mainAxisMargin = _kScrollbarMainAxisMargin
       ..crossAxisMargin = _kScrollbarCrossAxisMargin
       ..radius = _radius
-      ..padding = MediaQuery.of(context).padding
+      ..padding = MediaQuery.paddingOf(context)
       ..minLength = _kScrollbarMinLength
       ..minOverscrollLength = _kScrollbarMinOverscrollLength
       ..scrollbarOrientation = widget.scrollbarOrientation;
diff --git a/framework/lib/src/cupertino/search_field.dart b/framework/lib/src/cupertino/search_field.dart
index e42ac78..e973781 100644
--- a/framework/lib/src/cupertino/search_field.dart
+++ b/framework/lib/src/cupertino/search_field.dart
@@ -122,18 +122,12 @@
     this.focusNode,
     this.smartQuotesType,
     this.smartDashesType,
+    this.enableIMEPersonalizedLearning = true,
     this.autofocus = false,
     this.onTap,
     this.autocorrect = true,
     this.enabled,
-  })  : assert(padding != null),
-        assert(itemColor != null),
-        assert(itemSize != null),
-        assert(prefixInsets != null),
-        assert(suffixInsets != null),
-        assert(suffixIcon != null),
-        assert(suffixMode != null),
-        assert(
+  })  : assert(
           !((decoration != null) && (backgroundColor != null)),
           'Cannot provide both a background color and a decoration\n'
           'To provide both, use "decoration: BoxDecoration(color: '
@@ -318,6 +312,9 @@
   ///  * <https://developer.apple.com/documentation/uikit/uitextinputtraits>
   final SmartDashesType? smartDashesType;
 
+  /// {@macro flutter.services.TextInputConfiguration.enableIMEPersonalizedLearning}
+  final bool enableIMEPersonalizedLearning;
+
   /// Disables the text field when false.
   ///
   /// Text fields in disabled states have a light grey background and don't
@@ -460,6 +457,7 @@
       autocorrect: widget.autocorrect,
       smartQuotesType: widget.smartQuotesType,
       smartDashesType: widget.smartDashesType,
+      enableIMEPersonalizedLearning: widget.enableIMEPersonalizedLearning,
       textInputAction: TextInputAction.search,
     );
   }
diff --git a/framework/lib/src/cupertino/segmented_control.dart b/framework/lib/src/cupertino/segmented_control.dart
index 4df14ac..1292642 100644
--- a/framework/lib/src/cupertino/segmented_control.dart
+++ b/framework/lib/src/cupertino/segmented_control.dart
@@ -99,9 +99,7 @@
     this.borderColor,
     this.pressedColor,
     this.padding,
-  }) : assert(children != null),
-       assert(children.length >= 2),
-       assert(onValueChanged != null),
+  }) : assert(children.length >= 2),
        assert(
          groupValue == null || children.keys.any((T child) => child == groupValue),
          'The groupValue must be either null or one of the keys in the children map.',
@@ -407,7 +405,7 @@
 }
 
 class _SegmentedControlRenderWidget<T> extends MultiChildRenderObjectWidget {
-  _SegmentedControlRenderWidget({
+  const _SegmentedControlRenderWidget({
     super.key,
     super.children,
     required this.selectedIndex,
@@ -458,8 +456,7 @@
     required TextDirection textDirection,
     required List<Color> backgroundColors,
     required Color borderColor,
-  }) : assert(textDirection != null),
-       _textDirection = textDirection,
+  }) : _textDirection = textDirection,
        _selectedIndex = selectedIndex,
        _pressedIndex = pressedIndex,
        _backgroundColors = backgroundColors,
@@ -685,7 +682,6 @@
   }
 
   void _paintChild(PaintingContext context, Offset offset, RenderBox child, int childIndex) {
-    assert(child != null);
 
     final _SegmentedControlContainerBoxParentData childParentData = child.parentData! as _SegmentedControlContainerBoxParentData;
 
@@ -708,7 +704,6 @@
 
   @override
   bool hitTestChildren(BoxHitTestResult result, { required Offset position }) {
-    assert(position != null);
     RenderBox? child = lastChild;
     while (child != null) {
       final _SegmentedControlContainerBoxParentData childParentData = child.parentData! as _SegmentedControlContainerBoxParentData;
diff --git a/framework/lib/src/cupertino/slider.dart b/framework/lib/src/cupertino/slider.dart
index ecc6cb0..cd962c9 100644
--- a/framework/lib/src/cupertino/slider.dart
+++ b/framework/lib/src/cupertino/slider.dart
@@ -71,12 +71,8 @@
     this.divisions,
     this.activeColor,
     this.thumbColor = CupertinoColors.white,
-  }) : assert(value != null),
-       assert(min != null),
-       assert(max != null),
-       assert(value >= min && value <= max),
-       assert(divisions == null || divisions > 0),
-       assert(thumbColor != null);
+  }) : assert(value >= min && value <= max),
+       assert(divisions == null || divisions > 0);
 
   /// The currently selected value for this slider.
   ///
@@ -337,9 +333,7 @@
     required TickerProvider vsync,
     required TextDirection textDirection,
     MouseCursor cursor = MouseCursor.defer,
-  }) : assert(value != null && value >= 0.0 && value <= 1.0),
-       assert(textDirection != null),
-       assert(cursor != null),
+  }) : assert(value >= 0.0 && value <= 1.0),
        _cursor = cursor,
        _value = value,
        _divisions = divisions,
@@ -363,7 +357,7 @@
   double get value => _value;
   double _value;
   set value(double newValue) {
-    assert(newValue != null && newValue >= 0.0 && newValue <= 1.0);
+    assert(newValue >= 0.0 && newValue <= 1.0);
     if (newValue == _value) {
       return;
     }
@@ -435,7 +429,6 @@
   TextDirection get textDirection => _textDirection;
   TextDirection _textDirection;
   set textDirection(TextDirection value) {
-    assert(value != null);
     if (_textDirection == value) {
       return;
     }
diff --git a/framework/lib/src/cupertino/sliding_segmented_control.dart b/framework/lib/src/cupertino/sliding_segmented_control.dart
index 1e94b55..ca0affe 100644
--- a/framework/lib/src/cupertino/sliding_segmented_control.dart
+++ b/framework/lib/src/cupertino/sliding_segmented_control.dart
@@ -320,10 +320,7 @@
     this.thumbColor = _kThumbColor,
     this.padding = _kHorizontalItemPadding,
     this.backgroundColor = CupertinoColors.tertiarySystemFill,
-  }) : assert(children != null),
-       assert(children.length >= 2),
-       assert(padding != null),
-       assert(onValueChanged != null),
+  }) : assert(children.length >= 2),
        assert(
          groupValue == null || children.keys.contains(groupValue),
          'The groupValue must be either null or one of the keys in the children map.',
@@ -523,7 +520,6 @@
   // _Segment widget) to make the overall animation look natural when the thumb
   // is not sliding.
   void _playThumbScaleAnimation({ required bool isExpanding }) {
-    assert(isExpanding != null);
     thumbScaleAnimation = thumbScaleController.drive(
       Tween<double>(
         begin: thumbScaleAnimation.value,
@@ -715,7 +711,7 @@
 }
 
 class _SegmentedControlRenderWidget<T> extends MultiChildRenderObjectWidget {
-  _SegmentedControlRenderWidget({
+  const _SegmentedControlRenderWidget({
     super.key,
     super.children,
     required this.highlightedIndex,
@@ -793,8 +789,7 @@
     required this.state,
   }) : _highlightedIndex = highlightedIndex,
        _thumbColor = thumbColor,
-       _thumbScale = thumbScale,
-       assert(state != null);
+       _thumbScale = thumbScale;
 
   final _SegmentedControlState<T> state;
 
@@ -1074,13 +1069,11 @@
   // Paint the separator to the right of the given child.
   final Paint separatorPaint = Paint();
   void _paintSeparator(PaintingContext context, Offset offset, RenderBox child) {
-    assert(child != null);
     final _SegmentedControlContainerBoxParentData childParentData = child.parentData! as _SegmentedControlContainerBoxParentData;
     context.paintChild(child, offset + childParentData.offset);
   }
 
   void _paintChild(PaintingContext context, Offset offset, RenderBox child) {
-    assert(child != null);
     final _SegmentedControlContainerBoxParentData childParentData = child.parentData! as _SegmentedControlContainerBoxParentData;
     context.paintChild(child, childParentData.offset + offset);
   }
@@ -1119,7 +1112,6 @@
 
   @override
   bool hitTestChildren(BoxHitTestResult result, { required Offset position }) {
-    assert(position != null);
     RenderBox? child = lastChild;
     while (child != null) {
       final _SegmentedControlContainerBoxParentData childParentData =
diff --git a/framework/lib/src/cupertino/switch.dart b/framework/lib/src/cupertino/switch.dart
index bec0371..dedaf4e 100644
--- a/framework/lib/src/cupertino/switch.dart
+++ b/framework/lib/src/cupertino/switch.dart
@@ -14,6 +14,7 @@
 import 'package:flute/widgets.dart';
 
 import 'colors.dart';
+import 'theme.dart';
 import 'thumb_painter.dart';
 
 // Examples can assume:
@@ -72,9 +73,10 @@
     this.activeColor,
     this.trackColor,
     this.thumbColor,
+    this.applyTheme,
+    this.focusColor,
     this.dragStartBehavior = DragStartBehavior.start,
-  }) : assert(value != null),
-       assert(dragStartBehavior != null);
+  });
 
   /// Whether this switch is on or off.
   ///
@@ -105,13 +107,15 @@
   /// ```
   final ValueChanged<bool>? onChanged;
 
-  /// The color to use when this switch is on.
+  /// The color to use for the track when the switch is on.
   ///
-  /// Defaults to [CupertinoColors.systemGreen] when null and ignores
-  /// the [CupertinoTheme] in accordance to native iOS behavior.
+  /// If null and [applyTheme] is false, defaults to [CupertinoColors.systemGreen]
+  /// in accordance to native iOS behavior. Otherwise, defaults to
+  /// [CupertinoThemeData.primaryColor].
   final Color? activeColor;
 
-  /// The color to use for the background when the switch is off.
+
+  /// The color to use for the track when the switch is off.
   ///
   /// Defaults to [CupertinoColors.secondarySystemFill] when null.
   final Color? trackColor;
@@ -121,6 +125,21 @@
   /// Defaults to [CupertinoColors.white] when null.
   final Color? thumbColor;
 
+  /// The color to use for the focus highlight for keyboard interactions.
+  ///
+  /// Defaults to a slightly transparent [activeColor].
+  final Color? focusColor;
+
+  /// {@template flutter.cupertino.CupertinoSwitch.applyTheme}
+  /// Whether to apply the ambient [CupertinoThemeData].
+  ///
+  /// If true, the track uses [CupertinoThemeData.primaryColor] for the track
+  /// when the switch is on.
+  ///
+  /// Defaults to [CupertinoThemeData.applyThemeToAll].
+  /// {@endtemplate}
+  final bool? applyTheme;
+
   /// {@template flutter.cupertino.CupertinoSwitch.dragStartBehavior}
   /// Determines the way that drag start behavior is handled.
   ///
@@ -164,8 +183,14 @@
   late AnimationController _reactionController;
   late Animation<double> _reaction;
 
+  late bool isFocused;
+
   bool get isInteractive => widget.onChanged != null;
 
+  late final Map<Type, Action<Intent>> _actionMap = <Type, Action<Intent>>{
+    ActivateIntent: CallbackAction<ActivateIntent>(onInvoke: _handleTap),
+  };
+
   // A non-null boolean value that changes to true at the end of a drag if the
   // switch must be animated to the position indicated by the widget's value.
   bool needsPositionAnimation = false;
@@ -174,6 +199,8 @@
   void initState() {
     super.initState();
 
+    isFocused = false;
+
     _tap = TapGestureRecognizer()
       ..onTapDown = _handleTapDown
       ..onTapUp = _handleTapUp
@@ -239,7 +266,7 @@
       _reactionController.forward();
   }
 
-  void _handleTap() {
+  void _handleTap([Intent? _]) {
     if (isInteractive) {
       widget.onChanged!(!widget.value);
       _emitVibration();
@@ -308,8 +335,19 @@
     }
   }
 
+  void _onShowFocusHighlight(bool showHighlight) {
+    setState(() { isFocused = showHighlight; });
+  }
+
   @override
   Widget build(BuildContext context) {
+    final CupertinoThemeData theme = CupertinoTheme.of(context);
+    final Color activeColor = CupertinoDynamicColor.resolve(
+      widget.activeColor
+      ?? ((widget.applyTheme ?? theme.applyThemeToAll) ? theme.primaryColor : null)
+      ?? CupertinoColors.systemGreen,
+      context,
+    );
     if (needsPositionAnimation) {
       _resumePositionAnimation();
     }
@@ -317,17 +355,29 @@
       cursor: isInteractive && kIsWeb ? SystemMouseCursors.click : MouseCursor.defer,
       child: Opacity(
         opacity: widget.onChanged == null ? _kCupertinoSwitchDisabledOpacity : 1.0,
-        child: _CupertinoSwitchRenderObjectWidget(
-          value: widget.value,
-          activeColor: CupertinoDynamicColor.resolve(
-            widget.activeColor ?? CupertinoColors.systemGreen,
-            context,
+        child: FocusableActionDetector(
+          onShowFocusHighlight: _onShowFocusHighlight,
+          actions: _actionMap,
+          enabled: isInteractive,
+          child: _CupertinoSwitchRenderObjectWidget(
+            value: widget.value,
+            activeColor: activeColor,
+            trackColor: CupertinoDynamicColor.resolve(widget.trackColor ?? CupertinoColors.secondarySystemFill, context),
+            thumbColor: CupertinoDynamicColor.resolve(widget.thumbColor ?? CupertinoColors.white, context),
+            // Opacity, lightness, and saturation values were aproximated with
+            // color pickers on the switches in the macOS settings.
+            focusColor: CupertinoDynamicColor.resolve(
+              widget.focusColor ??
+              HSLColor
+                    .fromColor(activeColor.withOpacity(0.80))
+                    .withLightness(0.69).withSaturation(0.835)
+                    .toColor(),
+              context),
+            onChanged: widget.onChanged,
+            textDirection: Directionality.of(context),
+            isFocused: isFocused,
+            state: this,
           ),
-          trackColor: CupertinoDynamicColor.resolve(widget.trackColor ?? CupertinoColors.secondarySystemFill, context),
-          thumbColor: CupertinoDynamicColor.resolve(widget.thumbColor ?? CupertinoColors.white, context),
-          onChanged: widget.onChanged,
-          textDirection: Directionality.of(context),
-          state: this,
         ),
       ),
     );
@@ -350,8 +400,10 @@
     required this.activeColor,
     required this.trackColor,
     required this.thumbColor,
+    required this.focusColor,
     required this.onChanged,
     required this.textDirection,
+    required this.isFocused,
     required this.state,
   });
 
@@ -359,9 +411,11 @@
   final Color activeColor;
   final Color trackColor;
   final Color thumbColor;
+  final Color focusColor;
   final ValueChanged<bool>? onChanged;
   final _CupertinoSwitchState state;
   final TextDirection textDirection;
+  final bool isFocused;
 
   @override
   _RenderCupertinoSwitch createRenderObject(BuildContext context) {
@@ -370,8 +424,10 @@
       activeColor: activeColor,
       trackColor: trackColor,
       thumbColor: thumbColor,
+      focusColor: focusColor,
       onChanged: onChanged,
       textDirection: textDirection,
+      isFocused: isFocused,
       state: state,
     );
   }
@@ -384,8 +440,10 @@
       ..activeColor = activeColor
       ..trackColor = trackColor
       ..thumbColor = thumbColor
+      ..focusColor = focusColor
       ..onChanged = onChanged
-      ..textDirection = textDirection;
+      ..textDirection = textDirection
+      ..isFocused = isFocused;
   }
 }
 
@@ -409,18 +467,19 @@
     required Color activeColor,
     required Color trackColor,
     required Color thumbColor,
+    required Color focusColor,
     ValueChanged<bool>? onChanged,
     required TextDirection textDirection,
+    required bool isFocused,
     required _CupertinoSwitchState state,
-  }) : assert(value != null),
-       assert(activeColor != null),
-       assert(state != null),
-       _value = value,
+  }) : _value = value,
        _activeColor = activeColor,
        _trackColor = trackColor,
+       _focusColor = focusColor,
        _thumbPainter = CupertinoThumbPainter.switchThumb(color: thumbColor),
        _onChanged = onChanged,
        _textDirection = textDirection,
+       _isFocused = isFocused,
        _state = state,
        super(additionalConstraints: const BoxConstraints.tightFor(width: _kSwitchWidth, height: _kSwitchHeight)) {
          state.position.addListener(markNeedsPaint);
@@ -432,7 +491,6 @@
   bool get value => _value;
   bool _value;
   set value(bool value) {
-    assert(value != null);
     if (value == _value) {
       return;
     }
@@ -443,7 +501,6 @@
   Color get activeColor => _activeColor;
   Color _activeColor;
   set activeColor(Color value) {
-    assert(value != null);
     if (value == _activeColor) {
       return;
     }
@@ -454,7 +511,6 @@
   Color get trackColor => _trackColor;
   Color _trackColor;
   set trackColor(Color value) {
-    assert(value != null);
     if (value == _trackColor) {
       return;
     }
@@ -465,7 +521,6 @@
   Color get thumbColor => _thumbPainter.color;
   CupertinoThumbPainter _thumbPainter;
   set thumbColor(Color value) {
-    assert(value != null);
     if (value == thumbColor) {
       return;
     }
@@ -473,6 +528,16 @@
     markNeedsPaint();
   }
 
+  Color get focusColor => _focusColor;
+  Color _focusColor;
+  set focusColor(Color value) {
+    if (value == _focusColor) {
+      return;
+    }
+    _focusColor = value;
+    markNeedsPaint();
+  }
+
   ValueChanged<bool>? get onChanged => _onChanged;
   ValueChanged<bool>? _onChanged;
   set onChanged(ValueChanged<bool>? value) {
@@ -490,7 +555,6 @@
   TextDirection get textDirection => _textDirection;
   TextDirection _textDirection;
   set textDirection(TextDirection value) {
-    assert(value != null);
     if (_textDirection == value) {
       return;
     }
@@ -498,6 +562,16 @@
     markNeedsPaint();
   }
 
+  bool get isFocused => _isFocused;
+  bool _isFocused;
+  set isFocused(bool value) {
+    if(value == _isFocused) {
+      return;
+    }
+    _isFocused = value;
+    markNeedsPaint();
+  }
+
   bool get isInteractive => onChanged != null;
 
   @override
@@ -553,6 +627,18 @@
     final RRect trackRRect = RRect.fromRectAndRadius(trackRect, const Radius.circular(_kTrackRadius));
     canvas.drawRRect(trackRRect, paint);
 
+    if(_isFocused) {
+      // Paints a border around the switch in the focus color.
+      final RRect borderTrackRRect = trackRRect.inflate(1.75);
+
+      final Paint borderPaint = Paint()
+        ..color = focusColor
+        ..style = PaintingStyle.stroke
+        ..strokeWidth = 3.5;
+
+      canvas.drawRRect(borderTrackRRect, borderPaint);
+    }
+
     final double currentThumbExtension = CupertinoThumbPainter.extension * currentReactionValue;
     final double thumbLeft = lerpDouble(
       trackRect.left + _kTrackInnerStart - CupertinoThumbPainter.radius,
diff --git a/framework/lib/src/cupertino/tab_scaffold.dart b/framework/lib/src/cupertino/tab_scaffold.dart
index 8eddfe8..20ce8b8 100644
--- a/framework/lib/src/cupertino/tab_scaffold.dart
+++ b/framework/lib/src/cupertino/tab_scaffold.dart
@@ -36,7 +36,6 @@
   /// greater than or equal to 0, and less than the total number of tabs.
   CupertinoTabController({ int initialIndex = 0 })
     : _index = initialIndex,
-      assert(initialIndex != null),
       assert(initialIndex >= 0);
 
   bool _isDisposed = false;
@@ -52,7 +51,6 @@
   int get index => _index;
   int _index;
   set index(int value) {
-    assert(value != null);
     assert(value >= 0);
     if (_index == value) {
       return;
@@ -135,9 +133,7 @@
     this.backgroundColor,
     this.resizeToAvoidBottomInset = true,
     this.restorationId,
-  }) : assert(tabBar != null),
-       assert(tabBuilder != null),
-       assert(
+  }) : assert(
          controller == null || controller.index < tabBar.items.length,
          "The CupertinoTabController's current index ${controller.index} is "
          'out of bounds for the tab bar with ${tabBar.items.length} tabs',
@@ -324,12 +320,10 @@
       contentPadding = EdgeInsets.only(bottom: existingMediaQuery.viewInsets.bottom);
     }
 
-    if (widget.tabBar != null &&
-        // Only pad the content with the height of the tab bar if the tab
-        // isn't already entirely obstructed by a keyboard or other view insets.
-        // Don't double pad.
-        (!widget.resizeToAvoidBottomInset ||
-            widget.tabBar.preferredSize.height > existingMediaQuery.viewInsets.bottom)) {
+    // Only pad the content with the height of the tab bar if the tab
+    // isn't already entirely obstructed by a keyboard or other view insets.
+    // Don't double pad.
+    if (!widget.resizeToAvoidBottomInset || widget.tabBar.preferredSize.height > existingMediaQuery.viewInsets.bottom) {
       // TODO(xster): Use real size after partial layout instead of preferred size.
       // https://github.com/flutter/flutter/issues/12912
       final double bottomPadding =
@@ -406,9 +400,7 @@
     required this.currentTabIndex,
     required this.tabCount,
     required this.tabBuilder,
-  }) : assert(currentTabIndex != null),
-       assert(tabCount != null && tabCount > 0),
-       assert(tabBuilder != null);
+  }) : assert(tabCount > 0);
 
   final int currentTabIndex;
   final int tabCount;
@@ -530,8 +522,7 @@
   /// The `initialIndex` must not be null and defaults to 0. The value must be
   /// greater than or equal to 0, and less than the total number of tabs.
   RestorableCupertinoTabController({ int initialIndex = 0 })
-    : assert(initialIndex != null),
-      assert(initialIndex >= 0),
+    : assert(initialIndex >= 0),
       _initialIndex = initialIndex;
 
   final int _initialIndex;
diff --git a/framework/lib/src/cupertino/tab_view.dart b/framework/lib/src/cupertino/tab_view.dart
index 4c4d791..d091c86 100644
--- a/framework/lib/src/cupertino/tab_view.dart
+++ b/framework/lib/src/cupertino/tab_view.dart
@@ -50,7 +50,7 @@
     this.onUnknownRoute,
     this.navigatorObservers = const <NavigatorObserver>[],
     this.restorationScopeId,
-  }) : assert(navigatorObservers != null);
+  });
 
   /// The widget builder for the default route of the tab view
   /// ([Navigator.defaultRouteName], which is `/`).
@@ -175,13 +175,13 @@
 
   Route<dynamic>? _onGenerateRoute(RouteSettings settings) {
     final String? name = settings.name;
-    WidgetBuilder? routeBuilder;
+    final WidgetBuilder? routeBuilder;
     String? title;
     if (name == Navigator.defaultRouteName && widget.builder != null) {
       routeBuilder = widget.builder;
       title = widget.defaultTitle;
-    } else if (widget.routes != null) {
-      routeBuilder = widget.routes![name];
+    } else {
+      routeBuilder = widget.routes?[name];
     }
     if (routeBuilder != null) {
       return CupertinoPageRoute<dynamic>(
@@ -190,10 +190,7 @@
         settings: settings,
       );
     }
-    if (widget.onGenerateRoute != null) {
-      return widget.onGenerateRoute!(settings);
-    }
-    return null;
+    return widget.onGenerateRoute?.call(settings);
   }
 
   Route<dynamic>? _onUnknownRoute(RouteSettings settings) {
diff --git a/framework/lib/src/cupertino/text_field.dart b/framework/lib/src/cupertino/text_field.dart
index 9ac4d24..a8ec7e6 100644
--- a/framework/lib/src/cupertino/text_field.dart
+++ b/framework/lib/src/cupertino/text_field.dart
@@ -102,7 +102,7 @@
   final _CupertinoTextFieldState _state;
 
   @override
-  void onSingleTapUp(TapUpDetails details) {
+  void onSingleTapUp(TapDragUpDetails details) {
     // Because TextSelectionGestureDetector listens to taps that happen on
     // widgets in front of it, tapping the clear button will also trigger
     // this handler. If the clear button widget recognizes the up event,
@@ -120,7 +120,7 @@
   }
 
   @override
-  void onDragSelectionEnd(DragEndDetails details) {
+  void onDragSelectionEnd(TapDragEndDetails details) {
     _state._requestKeyboard();
   }
 }
@@ -274,6 +274,7 @@
     this.scrollController,
     this.scrollPhysics,
     this.autofillHints = const <String>[],
+    this.contentInsertionConfiguration,
     this.clipBehavior = Clip.hardEdge,
     this.restorationId,
     this.scribbleEnabled = true,
@@ -281,35 +282,21 @@
     this.contextMenuBuilder = _defaultContextMenuBuilder,
     this.spellCheckConfiguration,
     this.magnifierConfiguration,
-  }) : assert(textAlign != null),
-       assert(readOnly != null),
-       assert(autofocus != null),
-       assert(obscuringCharacter != null && obscuringCharacter.length == 1),
-       assert(obscureText != null),
-       assert(autocorrect != null),
+  }) : assert(obscuringCharacter.length == 1),
        smartDashesType = smartDashesType ?? (obscureText ? SmartDashesType.disabled : SmartDashesType.enabled),
        smartQuotesType = smartQuotesType ?? (obscureText ? SmartQuotesType.disabled : SmartQuotesType.enabled),
-       assert(enableSuggestions != null),
-       assert(scrollPadding != null),
-       assert(dragStartBehavior != null),
-       assert(selectionHeightStyle != null),
-       assert(selectionWidthStyle != null),
        assert(maxLines == null || maxLines > 0),
        assert(minLines == null || minLines > 0),
        assert(
          (maxLines == null) || (minLines == null) || (maxLines >= minLines),
          "minLines can't be greater than maxLines",
        ),
-       assert(expands != null),
        assert(
          !expands || (maxLines == null && minLines == null),
          'minLines and maxLines must be null when expands is true.',
        ),
        assert(!obscureText || maxLines == 1, 'Obscured fields cannot be multiline.'),
        assert(maxLength == null || maxLength > 0),
-       assert(clearButtonMode != null),
-       assert(prefixMode != null),
-       assert(suffixMode != null),
        // Assert the following instead of setting it directly to avoid surprising the user by silently changing the value they set.
        assert(
          !identical(textInputAction, TextInputAction.newline) ||
@@ -317,7 +304,6 @@
          !identical(keyboardType, TextInputType.text),
          'Use keyboardType TextInputType.multiline when using TextInputAction.newline on a multiline TextField.',
        ),
-       assert(enableIMEPersonalizedLearning != null),
        keyboardType = keyboardType ?? (maxLines == 1 ? TextInputType.text : TextInputType.multiline),
        enableInteractiveSelection = enableInteractiveSelection ?? (!readOnly || !obscureText);
 
@@ -418,6 +404,7 @@
     this.scrollController,
     this.scrollPhysics,
     this.autofillHints = const <String>[],
+    this.contentInsertionConfiguration,
     this.clipBehavior = Clip.hardEdge,
     this.restorationId,
     this.scribbleEnabled = true,
@@ -425,35 +412,21 @@
     this.contextMenuBuilder = _defaultContextMenuBuilder,
     this.spellCheckConfiguration,
     this.magnifierConfiguration,
-  }) : assert(textAlign != null),
-       assert(readOnly != null),
-       assert(autofocus != null),
-       assert(obscuringCharacter != null && obscuringCharacter.length == 1),
-       assert(obscureText != null),
-       assert(autocorrect != null),
+  }) : assert(obscuringCharacter.length == 1),
        smartDashesType = smartDashesType ?? (obscureText ? SmartDashesType.disabled : SmartDashesType.enabled),
        smartQuotesType = smartQuotesType ?? (obscureText ? SmartQuotesType.disabled : SmartQuotesType.enabled),
-       assert(enableSuggestions != null),
-       assert(scrollPadding != null),
-       assert(dragStartBehavior != null),
-       assert(selectionHeightStyle != null),
-       assert(selectionWidthStyle != null),
        assert(maxLines == null || maxLines > 0),
        assert(minLines == null || minLines > 0),
        assert(
          (maxLines == null) || (minLines == null) || (maxLines >= minLines),
          "minLines can't be greater than maxLines",
        ),
-       assert(expands != null),
        assert(
          !expands || (maxLines == null && minLines == null),
          'minLines and maxLines must be null when expands is true.',
        ),
        assert(!obscureText || maxLines == 1, 'Obscured fields cannot be multiline.'),
        assert(maxLength == null || maxLength > 0),
-       assert(clearButtonMode != null),
-       assert(prefixMode != null),
-       assert(suffixMode != null),
        // Assert the following instead of setting it directly to avoid surprising the user by silently changing the value they set.
        assert(
          !identical(textInputAction, TextInputAction.newline) ||
@@ -461,8 +434,6 @@
          !identical(keyboardType, TextInputType.text),
          'Use keyboardType TextInputType.multiline when using TextInputAction.newline on a multiline TextField.',
        ),
-       assert(clipBehavior != null),
-       assert(enableIMEPersonalizedLearning != null),
        keyboardType = keyboardType ?? (maxLines == 1 ? TextInputType.text : TextInputType.multiline),
        enableInteractiveSelection = enableInteractiveSelection ?? (!readOnly || !obscureText);
 
@@ -754,6 +725,9 @@
   /// {@macro flutter.services.TextInputConfiguration.enableIMEPersonalizedLearning}
   final bool enableIMEPersonalizedLearning;
 
+  /// {@macro flutter.widgets.editableText.contentInsertionConfiguration}
+  final ContentInsertionConfiguration? contentInsertionConfiguration;
+
   /// {@macro flutter.widgets.EditableText.contextMenuBuilder}
   ///
   /// If not provided, will build a default menu based on the platform.
@@ -850,6 +824,7 @@
     properties.add(DiagnosticsProperty<bool>('scribbleEnabled', scribbleEnabled, defaultValue: true));
     properties.add(DiagnosticsProperty<bool>('enableIMEPersonalizedLearning', enableIMEPersonalizedLearning, defaultValue: true));
     properties.add(DiagnosticsProperty<SpellCheckConfiguration>('spellCheckConfiguration', spellCheckConfiguration, defaultValue: null));
+    properties.add(DiagnosticsProperty<List<String>>('contentCommitMimeTypes', contentInsertionConfiguration?.allowedMimeTypes ?? const <String>[], defaultValue: contentInsertionConfiguration == null ? const <String>[] : kDefaultContentInsertionMimeTypes));
   }
 
   static final TextMagnifierConfiguration _iosMagnifierConfiguration = TextMagnifierConfiguration(
@@ -1102,9 +1077,6 @@
   }
 
   Widget _addTextDependentAttachments(Widget editableText, TextStyle textStyle, TextStyle placeholderStyle) {
-    assert(editableText != null);
-    assert(textStyle != null);
-    assert(placeholderStyle != null);
     // If there are no surrounding widgets, just return the core editable text
     // part.
     if (!_hasDecoration) {
@@ -1224,7 +1196,7 @@
     }
 
     final bool enabled = widget.enabled ?? true;
-    final Offset cursorOffset = Offset(_iOSHorizontalCursorOffsetPixels / MediaQuery.of(context).devicePixelRatio, 0);
+    final Offset cursorOffset = Offset(_iOSHorizontalCursorOffsetPixels / MediaQuery.devicePixelRatioOf(context), 0);
     final List<TextInputFormatter> formatters = <TextInputFormatter>[
       ...?widget.inputFormatters,
       if (widget.maxLength != null)
@@ -1267,7 +1239,7 @@
           ? side
           : side.copyWith(color: CupertinoDynamicColor.resolve(side.color, context));
       }
-      resolvedBorder = border == null || border.runtimeType != Border
+      resolvedBorder = border.runtimeType != Border
         ? border
         : Border(
           top: resolveBorderSide(border.top),
@@ -1362,6 +1334,7 @@
             restorationId: 'editable',
             scribbleEnabled: widget.scribbleEnabled,
             enableIMEPersonalizedLearning: widget.enableIMEPersonalizedLearning,
+            contentInsertionConfiguration: widget.contentInsertionConfiguration,
             contextMenuBuilder: widget.contextMenuBuilder,
             spellCheckConfiguration: spellCheckConfiguration,
           ),
diff --git a/framework/lib/src/cupertino/text_form_field_row.dart b/framework/lib/src/cupertino/text_form_field_row.dart
index 17227c2..f7991e3 100644
--- a/framework/lib/src/cupertino/text_form_field_row.dart
+++ b/framework/lib/src/cupertino/text_form_field_row.dart
@@ -13,7 +13,7 @@
 /// Creates a [CupertinoFormRow] containing a [FormField] that wraps
 /// a [CupertinoTextField].
 ///
-/// A [Form] ancestor is not required. The [Form] simply makes it easier to
+/// A [Form] ancestor is not required. The [Form] allows one to
 /// save, reset, or validate multiple fields at once. To use without a [Form],
 /// pass a [GlobalKey] to the constructor and use [GlobalKey.currentState] to
 /// save or reset the form field.
@@ -158,28 +158,19 @@
     ),
     EditableTextContextMenuBuilder? contextMenuBuilder = _defaultContextMenuBuilder,
   })  : assert(initialValue == null || controller == null),
-        assert(textAlign != null),
-        assert(autofocus != null),
-        assert(readOnly != null),
-        assert(obscuringCharacter != null && obscuringCharacter.length == 1),
-        assert(obscureText != null),
-        assert(autocorrect != null),
-        assert(enableSuggestions != null),
-        assert(scrollPadding != null),
+        assert(obscuringCharacter.length == 1),
         assert(maxLines == null || maxLines > 0),
         assert(minLines == null || minLines > 0),
         assert(
           (maxLines == null) || (minLines == null) || (maxLines >= minLines),
           "minLines can't be greater than maxLines",
         ),
-        assert(expands != null),
         assert(
           !expands || (maxLines == null && minLines == null),
           'minLines and maxLines must be null when expands is true.',
         ),
         assert(!obscureText || maxLines == 1, 'Obscured fields cannot be multiline.'),
         assert(maxLength == null || maxLength > 0),
-        assert(enableInteractiveSelection != null),
         super(
           initialValue: controller?.text ?? initialValue ?? '',
           builder: (FormFieldState<String> field) {
diff --git a/framework/lib/src/cupertino/text_selection.dart b/framework/lib/src/cupertino/text_selection.dart
index 7aef851..3cf5d19 100644
--- a/framework/lib/src/cupertino/text_selection.dart
+++ b/framework/lib/src/cupertino/text_selection.dart
@@ -260,14 +260,14 @@
     }
 
     assert(debugCheckHasMediaQuery(context));
-    final MediaQueryData mediaQuery = MediaQuery.of(context);
+    final EdgeInsets mediaQueryPadding = MediaQuery.paddingOf(context);
 
     // The toolbar should appear below the TextField when there is not enough
     // space above the TextField to show it, assuming there's always enough
     // space at the bottom in this case.
     final double anchorX = clampDouble(widget.selectionMidpoint.dx + widget.globalEditableRegion.left,
-      _kArrowScreenPadding + mediaQuery.padding.left,
-      mediaQuery.size.width - mediaQuery.padding.right - _kArrowScreenPadding,
+      _kArrowScreenPadding + mediaQueryPadding.left,
+      MediaQuery.sizeOf(context).width - mediaQueryPadding.right - _kArrowScreenPadding,
     );
 
     final double topAmountInEditableRegion = widget.endpoints.first.point.dy - widget.textLineHeight;
@@ -289,7 +289,7 @@
     final List<Widget> items = <Widget>[];
     final CupertinoLocalizations localizations = CupertinoLocalizations.of(context);
     final Widget onePhysicalPixelVerticalDivider =
-        SizedBox(width: 1.0 / MediaQuery.of(context).devicePixelRatio);
+        SizedBox(width: 1.0 / MediaQuery.devicePixelRatioOf(context));
 
     void addToolbarButton(
       String text,
diff --git a/framework/lib/src/cupertino/text_selection_toolbar.dart b/framework/lib/src/cupertino/text_selection_toolbar.dart
index b92ea6a..3d55da0 100644
--- a/framework/lib/src/cupertino/text_selection_toolbar.dart
+++ b/framework/lib/src/cupertino/text_selection_toolbar.dart
@@ -5,11 +5,13 @@
 import 'dart:collection';
 import 'package:engine/ui.dart' as ui;
 
-import 'package:flute/foundation.dart' show clampDouble;
+import 'package:flute/foundation.dart' show Brightness, clampDouble;
 import 'package:flute/rendering.dart';
 import 'package:flute/widgets.dart';
 
+import 'colors.dart';
 import 'text_selection_toolbar_button.dart';
+import 'theme.dart';
 
 // Values extracted from https://developer.apple.com/design/resources/.
 // The height of the toolbar, including the arrow.
@@ -17,9 +19,6 @@
 // Vertical distance between the tip of the arrow and the line of text the arrow
 // is pointing to. The value used here is eyeballed.
 const double _kToolbarContentDistance = 8.0;
-// Minimal padding from all edges of the selection toolbar to all edges of the
-// screen.
-const double _kToolbarScreenPadding = 8.0;
 const Size _kToolbarArrowSize = Size(14.0, 7.0);
 
 // Minimal padding from tip of the selection toolbar arrow to horizontal edges of the
@@ -29,9 +28,27 @@
 // Values extracted from https://developer.apple.com/design/resources/.
 const Radius _kToolbarBorderRadius = Radius.circular(8);
 
-// Colors extracted from https://developer.apple.com/design/resources/.
-// TODO(LongCatIsLooong): https://github.com/flutter/flutter/issues/41507.
-const Color _kToolbarDividerColor = Color(0xFF808080);
+const CupertinoDynamicColor _kToolbarDividerColor = CupertinoDynamicColor.withBrightness(
+  // This value was extracted from a screenshot of iOS 16.0.3, as light mode
+  // didn't appear in the Apple design resources assets linked below.
+  color: Color(0xFFB6B6B6),
+  // Color extracted from https://developer.apple.com/design/resources/.
+  // TODO(LongCatIsLooong): https://github.com/flutter/flutter/issues/41507.
+  darkColor: Color(0xFF808080),
+);
+
+// These values were extracted from a screenshot of iOS 16.0.3, as light mode
+// didn't appear in the Apple design resources assets linked above.
+final BoxDecoration _kToolbarShadow = BoxDecoration(
+  borderRadius: const BorderRadius.all(_kToolbarBorderRadius),
+  boxShadow: <BoxShadow>[
+    BoxShadow(
+      color: CupertinoColors.black.withOpacity(0.1),
+      blurRadius: 16.0,
+      offset: Offset(0, _kToolbarArrowSize.height / 2),
+    ),
+  ],
+);
 
 /// The type for a Function that builds a toolbar's container with the given
 /// child.
@@ -53,7 +70,7 @@
 class _CupertinoToolbarButtonDivider extends StatelessWidget {
   @override
   Widget build(BuildContext context) {
-    return SizedBox(width: 1.0 / MediaQuery.of(context).devicePixelRatio);
+    return SizedBox(width: 1.0 / MediaQuery.devicePixelRatioOf(context));
   }
 }
 
@@ -103,7 +120,17 @@
   /// default Cupertino toolbar.
   final CupertinoToolbarBuilder toolbarBuilder;
 
-  // Add the visial vertical line spacer between children buttons.
+  /// Minimal padding from all edges of the selection toolbar to all edges of the
+  /// viewport.
+  ///
+  /// See also:
+  ///
+  ///  * [SpellCheckSuggestionsToolbar], which uses this same value for its
+  ///    padding from the edges of the viewport.
+  ///  * [TextSelectionToolbar], which uses this same value as well.
+  static const double kToolbarScreenPadding = 8.0;
+
+  // Add the visual vertical line spacer between children buttons.
   static List<Widget> _addChildrenSpacers(List<Widget> children) {
     final List<Widget> nextChildren = <Widget>[];
     for (int i = 0; i < children.length; i++) {
@@ -119,22 +146,31 @@
   // Builds a toolbar just like the default iOS toolbar, with the right color
   // background and a rounded cutout with an arrow.
   static Widget _defaultToolbarBuilder(BuildContext context, Offset anchor, bool isAbove, Widget child) {
-    return _CupertinoTextSelectionToolbarShape(
+    final Widget outputChild = _CupertinoTextSelectionToolbarShape(
       anchor: anchor,
       isAbove: isAbove,
       child: DecoratedBox(
-        decoration: const BoxDecoration(color: _kToolbarDividerColor),
+        decoration: BoxDecoration(
+          color: _kToolbarDividerColor.resolveFrom(context),
+        ),
         child: child,
       ),
     );
+    if (CupertinoTheme.brightnessOf(context) == Brightness.dark) {
+      return outputChild;
+    }
+    return DecoratedBox(
+      decoration: _kToolbarShadow,
+      child: outputChild,
+    );
   }
 
   @override
   Widget build(BuildContext context) {
     assert(debugCheckHasMediaQuery(context));
-    final MediaQueryData mediaQuery = MediaQuery.of(context);
+    final EdgeInsets mediaQueryPadding = MediaQuery.paddingOf(context);
 
-    final double paddingAbove = mediaQuery.padding.top + _kToolbarScreenPadding;
+    final double paddingAbove = mediaQueryPadding.top + kToolbarScreenPadding;
     final double toolbarHeightNeeded = paddingAbove
         + _kToolbarContentDistance
         + _kToolbarHeight;
@@ -142,8 +178,8 @@
 
     // The arrow, which points to the anchor, has some margin so it can't get
     // too close to the horizontal edges of the screen.
-    final double leftMargin = _kArrowScreenPadding + mediaQuery.padding.left;
-    final double rightMargin = mediaQuery.size.width - mediaQuery.padding.right - _kArrowScreenPadding;
+    final double leftMargin = _kArrowScreenPadding + mediaQueryPadding.left;
+    final double rightMargin = MediaQuery.sizeOf(context).width - mediaQueryPadding.right - _kArrowScreenPadding;
 
     final Offset anchorAboveAdjusted = Offset(
       clampDouble(anchorAbove.dx, leftMargin, rightMargin),
@@ -151,15 +187,15 @@
     );
     final Offset anchorBelowAdjusted = Offset(
       clampDouble(anchorBelow.dx, leftMargin, rightMargin),
-      anchorBelow.dy - _kToolbarContentDistance + paddingAbove,
+      anchorBelow.dy + _kToolbarContentDistance - paddingAbove,
     );
 
     return Padding(
       padding: EdgeInsets.fromLTRB(
-        _kToolbarScreenPadding,
+        kToolbarScreenPadding,
         paddingAbove,
-        _kToolbarScreenPadding,
-        _kToolbarScreenPadding,
+        kToolbarScreenPadding,
+        kToolbarScreenPadding,
       ),
       child: CustomSingleChildLayout(
         delegate: TextSelectionToolbarLayoutDelegate(
@@ -226,7 +262,6 @@
     super.child,
   );
 
-
   @override
   bool get isRepaintBoundary => true;
 
@@ -398,8 +433,7 @@
     required this.isAbove,
     required this.toolbarBuilder,
     required this.children,
-  }) : assert(children != null),
-       assert(children.length > 0);
+  }) : assert(children.length > 0);
 
   final Offset anchor;
   final List<Widget> children;
@@ -480,12 +514,12 @@
           onPressed: _handlePreviousPage,
           text: 'â—€',
         ),
-        dividerWidth: 1.0 / MediaQuery.of(context).devicePixelRatio,
+        dividerWidth: 1.0 / MediaQuery.devicePixelRatioOf(context),
         nextButton: CupertinoTextSelectionToolbarButton.text(
           onPressed: _handleNextPage,
           text: 'â–¶',
         ),
-        nextButtonDisabled: CupertinoTextSelectionToolbarButton.text(
+        nextButtonDisabled: const CupertinoTextSelectionToolbarButton.text(
           text: 'â–¶',
         ),
         children: widget.children,
@@ -505,13 +539,7 @@
     required this.dividerWidth,
     required this.nextButton,
     required this.nextButtonDisabled,
-  }) : assert(children != null),
-       assert(children.isNotEmpty),
-       assert(backButton != null),
-       assert(dividerWidth != null),
-       assert(nextButton != null),
-       assert(nextButtonDisabled != null),
-       assert(page != null);
+  }) : assert(children.isNotEmpty);
 
   final Widget backButton;
   final List<Widget> children;
@@ -704,9 +732,7 @@
   _RenderCupertinoTextSelectionToolbarItems({
     required double dividerWidth,
     required int page,
-  }) : assert(dividerWidth != null),
-       assert(page != null),
-       _dividerWidth = dividerWidth,
+  }) : _dividerWidth = dividerWidth,
        _page = page,
        super();
 
@@ -801,8 +827,9 @@
       double paginationButtonsWidth = 0.0;
       if (currentPage == 0) {
         // If this is the last child, it's ok to fit without a forward button.
+        // Note childCount doesn't include slotted children which come before the list ones.
         paginationButtonsWidth =
-            i == childCount - 1 ? 0.0 : _nextButton!.size.width;
+            i == childCount + 2 ? 0.0 : _nextButton!.size.width;
       } else {
         paginationButtonsWidth = subsequentPageButtonsWidth;
       }
diff --git a/framework/lib/src/cupertino/text_selection_toolbar_button.dart b/framework/lib/src/cupertino/text_selection_toolbar_button.dart
index c302fe3..0807b4d 100644
--- a/framework/lib/src/cupertino/text_selection_toolbar_button.dart
+++ b/framework/lib/src/cupertino/text_selection_toolbar_button.dart
@@ -18,7 +18,17 @@
 
 // Colors extracted from https://developer.apple.com/design/resources/.
 // TODO(LongCatIsLooong): https://github.com/flutter/flutter/issues/41507.
-const Color _kToolbarBackgroundColor = Color(0xEB202020);
+const CupertinoDynamicColor _kToolbarBackgroundColor = CupertinoDynamicColor.withBrightness(
+  // This value was extracted from a screenshot of iOS 16.0.3, as light mode
+  // didn't appear in the Apple design resources assets linked above.
+  color: Color(0xEBF7F7F7),
+  darkColor: Color(0xEB202020),
+);
+
+const CupertinoDynamicColor _kToolbarTextColor = CupertinoDynamicColor.withBrightness(
+  color: CupertinoColors.black,
+  darkColor: CupertinoColors.white,
+);
 
 // Eyeballed value.
 const EdgeInsets _kToolbarButtonPadding = EdgeInsets.symmetric(vertical: 16.0, horizontal: 18.0);
@@ -32,23 +42,17 @@
     super.key,
     this.onPressed,
     required Widget this.child,
-  }) : assert(child != null),
+  }) : text = null,
        buttonItem = null;
 
   /// Create an instance of [CupertinoTextSelectionToolbarButton] whose child is
   /// a [Text] widget styled like the default iOS text selection toolbar button.
-  CupertinoTextSelectionToolbarButton.text({
+  const CupertinoTextSelectionToolbarButton.text({
     super.key,
     this.onPressed,
-    required String text,
+    required this.text,
   }) : buttonItem = null,
-       child = Text(
-         text,
-         overflow: TextOverflow.ellipsis,
-         style: _kToolbarButtonFontStyle.copyWith(
-           color: onPressed != null ? CupertinoColors.white : CupertinoColors.inactiveGray,
-         ),
-       );
+       child = null;
 
   /// Create an instance of [CupertinoTextSelectionToolbarButton] from the given
   /// [ContextMenuButtonItem].
@@ -57,8 +61,8 @@
   CupertinoTextSelectionToolbarButton.buttonItem({
     super.key,
     required ContextMenuButtonItem this.buttonItem,
-  }) : assert(buttonItem != null),
-       child = null,
+  }) : child = null,
+       text = null,
        onPressed = buttonItem.onPressed;
 
   /// {@template flutter.cupertino.CupertinoTextSelectionToolbarButton.child}
@@ -79,6 +83,10 @@
   /// {@endtemplate}
   final ContextMenuButtonItem? buttonItem;
 
+  /// The text used in the button's label when using
+  /// [CupertinoTextSelectionToolbarButton.text].
+  final String? text;
+
   /// Returns the default button label String for the button of the given
   /// [ContextMenuButtonItem]'s [ContextMenuButtonType].
   static String getButtonLabel(BuildContext context, ContextMenuButtonItem buttonItem) {
@@ -97,6 +105,7 @@
         return localizations.pasteButtonLabel;
       case ContextMenuButtonType.selectAll:
         return localizations.selectAllButtonLabel;
+      case ContextMenuButtonType.delete:
       case ContextMenuButtonType.custom:
         return '';
     }
@@ -105,12 +114,15 @@
   @override
   Widget build(BuildContext context) {
     final Widget child = this.child ?? Text(
-      getButtonLabel(context, buttonItem!),
-      overflow: TextOverflow.ellipsis,
-      style: _kToolbarButtonFontStyle.copyWith(
-        color: onPressed != null ? CupertinoColors.white : CupertinoColors.inactiveGray,
-      ),
-    );
+       text ?? getButtonLabel(context, buttonItem!),
+       overflow: TextOverflow.ellipsis,
+       style: _kToolbarButtonFontStyle.copyWith(
+         color: onPressed != null
+             ? _kToolbarTextColor.resolveFrom(context)
+             : CupertinoColors.inactiveGray,
+       ),
+     );
+
     return CupertinoButton(
       borderRadius: null,
       color: _kToolbarBackgroundColor,
diff --git a/framework/lib/src/cupertino/text_theme.dart b/framework/lib/src/cupertino/text_theme.dart
index 5252fc8..908504e 100644
--- a/framework/lib/src/cupertino/text_theme.dart
+++ b/framework/lib/src/cupertino/text_theme.dart
@@ -260,6 +260,41 @@
     properties.add(DiagnosticsProperty<TextStyle>('pickerTextStyle', pickerTextStyle, defaultValue: defaultData.pickerTextStyle));
     properties.add(DiagnosticsProperty<TextStyle>('dateTimePickerTextStyle', dateTimePickerTextStyle, defaultValue: defaultData.dateTimePickerTextStyle));
   }
+
+  @override
+  bool operator == (Object other) {
+    if (identical(this, other)) {
+      return true;
+    }
+    if (other.runtimeType != runtimeType) {
+      return false;
+    }
+    return other is CupertinoTextThemeData
+      && other._defaults == _defaults
+      && other._primaryColor == _primaryColor
+      && other._textStyle == _textStyle
+      && other._actionTextStyle == _actionTextStyle
+      && other._tabLabelTextStyle == _tabLabelTextStyle
+      && other._navTitleTextStyle == _navTitleTextStyle
+      && other._navLargeTitleTextStyle == _navLargeTitleTextStyle
+      && other._navActionTextStyle == _navActionTextStyle
+      && other._pickerTextStyle == _pickerTextStyle
+      && other._dateTimePickerTextStyle == _dateTimePickerTextStyle;
+  }
+
+  @override
+  int get hashCode => Object.hash(
+    _defaults,
+    _primaryColor,
+    _textStyle,
+    _actionTextStyle,
+    _tabLabelTextStyle,
+    _navTitleTextStyle,
+    _navLargeTitleTextStyle,
+    _navActionTextStyle,
+    _pickerTextStyle,
+    _dateTimePickerTextStyle,
+  );
 }
 
 
@@ -268,8 +303,7 @@
   const _TextThemeDefaultsBuilder(
     this.labelColor,
     this.inactiveGrayColor,
-  ) : assert(labelColor != null),
-      assert(inactiveGrayColor != null);
+  );
 
   final Color labelColor;
   final Color inactiveGrayColor;
@@ -297,4 +331,20 @@
       ? this
       : _TextThemeDefaultsBuilder(resolvedLabelColor, resolvedInactiveGray);
   }
+
+  @override
+  bool operator == (Object other) {
+    if (identical(this, other)) {
+      return true;
+    }
+    if (other.runtimeType != runtimeType) {
+      return false;
+    }
+    return other is _TextThemeDefaultsBuilder
+      && other.labelColor == labelColor
+      && other.inactiveGrayColor == inactiveGrayColor;
+  }
+
+  @override
+  int get hashCode => Object.hash(labelColor, inactiveGrayColor);
 }
diff --git a/framework/lib/src/cupertino/theme.dart b/framework/lib/src/cupertino/theme.dart
index 78729d7..1eb09aa 100644
--- a/framework/lib/src/cupertino/theme.dart
+++ b/framework/lib/src/cupertino/theme.dart
@@ -22,6 +22,7 @@
     // Values extracted from navigation bar. For toolbar or tabbar the dark color is 0xF0161616.
   ),
   CupertinoColors.systemBackground,
+  false,
   _CupertinoTextThemeDefaults(CupertinoColors.label, CupertinoColors.inactiveGray),
 );
 
@@ -52,8 +53,7 @@
     super.key,
     required this.data,
     required this.child,
-  }) : assert(child != null),
-       assert(data != null);
+  });
 
   /// The [CupertinoThemeData] styling for this theme.
   final CupertinoThemeData data;
@@ -86,7 +86,7 @@
   ///   [MediaQueryData.platformBrightness] for descendant Cupertino widgets.
   static Brightness brightnessOf(BuildContext context) {
     final _InheritedCupertinoTheme? inheritedTheme = context.dependOnInheritedWidgetOfExactType<_InheritedCupertinoTheme>();
-    return inheritedTheme?.theme.data.brightness ?? MediaQuery.of(context).platformBrightness;
+    return inheritedTheme?.theme.data.brightness ?? MediaQuery.platformBrightnessOf(context);
   }
 
   /// Retrieves the [Brightness] to use for descendant Cupertino widgets, based
@@ -106,7 +106,7 @@
   ///   [MediaQuery] exists, instead of returning null.
   static Brightness? maybeBrightnessOf(BuildContext context) {
     final _InheritedCupertinoTheme? inheritedTheme = context.dependOnInheritedWidgetOfExactType<_InheritedCupertinoTheme>();
-    return inheritedTheme?.theme.data.brightness ?? MediaQuery.maybeOf(context)?.platformBrightness;
+    return inheritedTheme?.theme.data.brightness ?? MediaQuery.maybePlatformBrightnessOf(context);
   }
 
   /// The widget below this widget in the tree.
@@ -136,7 +136,7 @@
   const _InheritedCupertinoTheme({
     required this.theme,
     required super.child,
-  }) : assert(theme != null);
+  });
 
   final CupertinoTheme theme;
 
@@ -172,6 +172,7 @@
     CupertinoTextThemeData? textTheme,
     Color? barBackgroundColor,
     Color? scaffoldBackgroundColor,
+    bool? applyThemeToAll,
   }) : this.raw(
         brightness,
         primaryColor,
@@ -179,6 +180,7 @@
         textTheme,
         barBackgroundColor,
         scaffoldBackgroundColor,
+        applyThemeToAll,
       );
 
   /// Same as the default constructor but with positional arguments to avoid
@@ -193,6 +195,7 @@
     CupertinoTextThemeData? textTheme,
     Color? barBackgroundColor,
     Color? scaffoldBackgroundColor,
+    bool? applyThemeToAll,
   ) : this._rawWithDefaults(
     brightness,
     primaryColor,
@@ -200,6 +203,7 @@
     textTheme,
     barBackgroundColor,
     scaffoldBackgroundColor,
+    applyThemeToAll,
     _kDefaultTheme,
   );
 
@@ -210,6 +214,7 @@
     CupertinoTextThemeData? textTheme,
     Color? barBackgroundColor,
     Color? scaffoldBackgroundColor,
+    bool? applyThemeToAll,
     this._defaults,
   ) : super(
     brightness: brightness,
@@ -218,6 +223,7 @@
     textTheme: textTheme,
     barBackgroundColor: barBackgroundColor,
     scaffoldBackgroundColor: scaffoldBackgroundColor,
+    applyThemeToAll: applyThemeToAll,
   );
 
   final _CupertinoThemeDefaults _defaults;
@@ -240,6 +246,9 @@
   Color get scaffoldBackgroundColor => super.scaffoldBackgroundColor ?? _defaults.scaffoldBackgroundColor;
 
   @override
+  bool get applyThemeToAll => super.applyThemeToAll ?? _defaults.applyThemeToAll;
+
+  @override
   NoDefaultCupertinoThemeData noDefault() {
     return NoDefaultCupertinoThemeData(
       brightness: super.brightness,
@@ -248,6 +257,7 @@
       textTheme: super.textTheme,
       barBackgroundColor: super.barBackgroundColor,
       scaffoldBackgroundColor: super.scaffoldBackgroundColor,
+      applyThemeToAll: super.applyThemeToAll,
     );
   }
 
@@ -262,6 +272,7 @@
       super.textTheme?.resolveFrom(context),
       convertColor(super.barBackgroundColor),
       convertColor(super.scaffoldBackgroundColor),
+      applyThemeToAll,
       _defaults.resolveFrom(context, super.textTheme == null),
     );
   }
@@ -274,6 +285,7 @@
     CupertinoTextThemeData? textTheme,
     Color? barBackgroundColor,
     Color? scaffoldBackgroundColor,
+    bool? applyThemeToAll,
   }) {
     return CupertinoThemeData._rawWithDefaults(
       brightness ?? super.brightness,
@@ -282,6 +294,7 @@
       textTheme ?? super.textTheme,
       barBackgroundColor ?? super.barBackgroundColor,
       scaffoldBackgroundColor ?? super.scaffoldBackgroundColor,
+      applyThemeToAll ?? super.applyThemeToAll,
       _defaults,
     );
   }
@@ -295,8 +308,38 @@
     properties.add(createCupertinoColorProperty('primaryContrastingColor', primaryContrastingColor, defaultValue: defaultData.primaryContrastingColor));
     properties.add(createCupertinoColorProperty('barBackgroundColor', barBackgroundColor, defaultValue: defaultData.barBackgroundColor));
     properties.add(createCupertinoColorProperty('scaffoldBackgroundColor', scaffoldBackgroundColor, defaultValue: defaultData.scaffoldBackgroundColor));
+    properties.add(DiagnosticsProperty<bool>('applyThemeToAll', applyThemeToAll, defaultValue: defaultData.applyThemeToAll));
     textTheme.debugFillProperties(properties);
   }
+
+  @override
+  bool operator == (Object other) {
+    if (identical(this, other)) {
+      return true;
+    }
+    if (other.runtimeType != runtimeType) {
+      return false;
+    }
+    return other is CupertinoThemeData
+      && other.brightness == brightness
+      && other.primaryColor == primaryColor
+      && other.primaryContrastingColor == primaryContrastingColor
+      && other.textTheme == textTheme
+      && other.barBackgroundColor == barBackgroundColor
+      && other.scaffoldBackgroundColor == scaffoldBackgroundColor
+      && other.applyThemeToAll == applyThemeToAll;
+  }
+
+  @override
+  int get hashCode => Object.hash(
+    brightness,
+    primaryColor,
+    primaryContrastingColor,
+    textTheme,
+    barBackgroundColor,
+    scaffoldBackgroundColor,
+    applyThemeToAll,
+  );
 }
 
 /// Styling specifications for a cupertino theme without default values for
@@ -322,6 +365,7 @@
     this.textTheme,
     this.barBackgroundColor,
     this.scaffoldBackgroundColor,
+    this.applyThemeToAll,
   });
 
   /// The brightness override for Cupertino descendants.
@@ -389,6 +433,22 @@
   /// Defaults to [CupertinoColors.systemBackground].
   final Color? scaffoldBackgroundColor;
 
+  /// Flag to apply this theme to all descendant Cupertino widgets.
+  ///
+  /// Certain Cupertino widgets previously didn't use theming, matching past
+  /// versions of iOS. For example, [CupertinoSwitch]s always used
+  /// [CupertinoColors.systemGreen] when active.
+  ///
+  /// Today, however, these widgets can indeed be themed on iOS. Moreover on
+  /// macOS, the accent color is reflected in these widgets. Turning this flag
+  /// on ensures that descendant Cupertino widgets will be themed accordingly.
+  ///
+  /// This flag currently applies to the following widgets:
+  /// - [CupertinoSwitch] & [Switch.adaptive]
+  ///
+  /// Defaults to false.
+  final bool? applyThemeToAll;
+
   /// Returns an instance of the theme data whose property getters only return
   /// the construction time specifications with no derived values.
   ///
@@ -412,6 +472,7 @@
       textTheme: textTheme?.resolveFrom(context),
       barBackgroundColor: convertColor(barBackgroundColor),
       scaffoldBackgroundColor: convertColor(scaffoldBackgroundColor),
+      applyThemeToAll: applyThemeToAll,
     );
   }
 
@@ -428,6 +489,7 @@
     CupertinoTextThemeData? textTheme,
     Color? barBackgroundColor ,
     Color? scaffoldBackgroundColor,
+    bool? applyThemeToAll,
   }) {
     return NoDefaultCupertinoThemeData(
       brightness: brightness ?? this.brightness,
@@ -436,6 +498,7 @@
       textTheme: textTheme ?? this.textTheme,
       barBackgroundColor: barBackgroundColor ?? this.barBackgroundColor,
       scaffoldBackgroundColor: scaffoldBackgroundColor ?? this.scaffoldBackgroundColor,
+      applyThemeToAll: applyThemeToAll ?? this.applyThemeToAll,
     );
   }
 }
@@ -448,6 +511,7 @@
     this.primaryContrastingColor,
     this.barBackgroundColor,
     this.scaffoldBackgroundColor,
+    this.applyThemeToAll,
     this.textThemeDefaults,
   );
 
@@ -456,6 +520,7 @@
   final Color primaryContrastingColor;
   final Color barBackgroundColor;
   final Color scaffoldBackgroundColor;
+  final bool applyThemeToAll;
   final _CupertinoTextThemeDefaults textThemeDefaults;
 
   _CupertinoThemeDefaults resolveFrom(BuildContext context, bool resolveTextTheme) {
@@ -467,6 +532,7 @@
       convertColor(primaryContrastingColor),
       convertColor(barBackgroundColor),
       convertColor(scaffoldBackgroundColor),
+      applyThemeToAll,
       resolveTextTheme ? textThemeDefaults.resolveFrom(context) : textThemeDefaults,
     );
   }
@@ -490,7 +556,6 @@
   }
 
   CupertinoTextThemeData createDefaults({ required Color primaryColor }) {
-    assert(primaryColor != null);
     return _DefaultCupertinoTextThemeData(
       primaryColor: primaryColor,
       labelColor: labelColor,
@@ -507,9 +572,7 @@
     required this.labelColor,
     required this.inactiveGray,
     required super.primaryColor,
-  }) : assert(labelColor != null),
-       assert(inactiveGray != null),
-       assert(primaryColor != null);
+  });
 
   final Color labelColor;
   final Color inactiveGray;
diff --git a/framework/lib/src/cupertino/thumb_painter.dart b/framework/lib/src/cupertino/thumb_painter.dart
index feeab3e..b0ebeca 100644
--- a/framework/lib/src/cupertino/thumb_painter.dart
+++ b/framework/lib/src/cupertino/thumb_painter.dart
@@ -47,7 +47,7 @@
   const CupertinoThumbPainter({
     this.color = CupertinoColors.white,
     this.shadows = _kSliderBoxShadows,
-  }) : assert(shadows != null);
+  });
 
   /// Creates an object that paints an iOS-style switch thumb.
   const CupertinoThumbPainter.switchThumb({
diff --git a/framework/lib/src/foundation/_capabilities_io.dart b/framework/lib/src/foundation/_capabilities_io.dart
new file mode 100644
index 0000000..202dc7b
--- /dev/null
+++ b/framework/lib/src/foundation/_capabilities_io.dart
@@ -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.
+
+/// The dart:io implementation of [isCanvasKit].
+///
+/// This bool shouldn't be used outside of web.
+bool get isCanvasKit {
+  throw UnimplementedError('isCanvasKit is not implemented for dart:io.');
+}
diff --git a/framework/lib/src/foundation/_capabilities_web.dart b/framework/lib/src/foundation/_capabilities_web.dart
new file mode 100644
index 0000000..5b5de84
--- /dev/null
+++ b/framework/lib/src/foundation/_capabilities_web.dart
@@ -0,0 +1,13 @@
+// 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:js/js.dart';
+
+// This value is set by the engine. It is used to determine if the application is
+// using canvaskit.
+@JS('window.flutterCanvasKit')
+external Object? get _windowFlutterCanvasKit;
+
+/// The web implementation of [isCanvasKit]
+bool get isCanvasKit => _windowFlutterCanvasKit != null;
diff --git a/framework/lib/src/foundation/_isolates_io.dart b/framework/lib/src/foundation/_isolates_io.dart
index 63c5f54..08bd8ca 100644
--- a/framework/lib/src/foundation/_isolates_io.dart
+++ b/framework/lib/src/foundation/_isolates_io.dart
@@ -11,7 +11,8 @@
 export 'isolates.dart' show ComputeCallback;
 
 /// The dart:io implementation of [isolate.compute].
-Future<R> compute<Q, R>(isolates.ComputeCallback<Q, R> callback, Q message, {String? debugLabel}) async {
+@pragma('vm:prefer-inline')
+Future<R> compute<M, R>(isolates.ComputeCallback<M, R> callback, M message, {String? debugLabel}) async {
   debugLabel ??= kReleaseMode ? 'compute' : callback.toString();
 
   return Isolate.run<R>(() {
diff --git a/framework/lib/src/foundation/_isolates_web.dart b/framework/lib/src/foundation/_isolates_web.dart
index 2a2d343..4350e54 100644
--- a/framework/lib/src/foundation/_isolates_web.dart
+++ b/framework/lib/src/foundation/_isolates_web.dart
@@ -7,7 +7,8 @@
 export 'isolates.dart' show ComputeCallback;
 
 /// The dart:html implementation of [isolate.compute].
-Future<R> compute<Q, R>(isolates.ComputeCallback<Q, R> callback, Q message, { String? debugLabel }) async {
+@pragma('dart2js:tryInline')
+Future<R> compute<M, R>(isolates.ComputeCallback<M, R> callback, M message, { String? debugLabel }) async {
   // To avoid blocking the UI immediately for an expensive function call, we
   // pump a single frame to allow the framework to complete the current set
   // of work.
diff --git a/framework/lib/src/foundation/annotations.dart b/framework/lib/src/foundation/annotations.dart
index b57bfac..3bc5e02 100644
--- a/framework/lib/src/foundation/annotations.dart
+++ b/framework/lib/src/foundation/annotations.dart
@@ -39,7 +39,7 @@
 ///    class that overrides the inline documentations' own description.
 class Category {
   /// Create an annotation to provide a categorization of a class.
-  const Category(this.sections) : assert(sections != null);
+  const Category(this.sections);
 
   /// The strings the correspond to the section and subsection of the
   /// category represented by this object.
@@ -74,7 +74,7 @@
 ///    class that overrides the inline documentations' own description.
 class DocumentationIcon {
   /// Create an annotation to provide a URL to an image describing a class.
-  const DocumentationIcon(this.url) : assert(url != null);
+  const DocumentationIcon(this.url);
 
   /// The URL to an image that represents the annotated class.
   final String url;
@@ -111,7 +111,7 @@
 ///    represents the class.
 class Summary {
   /// Create an annotation to provide a short description of a class.
-  const Summary(this.text) : assert(text != null);
+  const Summary(this.text);
 
   /// The text of the summary of the annotated class.
   final String text;
diff --git a/framework/lib/src/foundation/assertions.dart b/framework/lib/src/foundation/assertions.dart
index 67455b2..59abde7 100644
--- a/framework/lib/src/foundation/assertions.dart
+++ b/framework/lib/src/foundation/assertions.dart
@@ -54,9 +54,7 @@
     required this.package,
     required this.className,
     required this.method,
-  }) : assert(className != null),
-       assert(method != null),
-       assert(package != null);
+  });
 
   /// An `<asynchronous suspension>` line in a stack trace.
   static const PartialStackFrame asynchronousSuspension = PartialStackFrame(
@@ -128,8 +126,7 @@
   const RepetitiveStackFrameFilter({
     required this.frames,
     required this.replacement,
-  }) : assert(frames != null),
-       assert(replacement != null);
+  });
 
   /// The shape of this repetitive stack pattern.
   final List<PartialStackFrame> frames;
@@ -140,7 +137,7 @@
   /// The string to replace the frames with.
   ///
   /// If the same replacement string is used multiple times in a row, the
-  /// [FlutterError.defaultStackFilter] will simply update a counter after this
+  /// [FlutterError.defaultStackFilter] will insert a repeat count after this
   /// line rather than repeating it.
   final String replacement;
 
@@ -177,8 +174,7 @@
     String message, {
     DiagnosticsTreeStyle style = DiagnosticsTreeStyle.flat,
     DiagnosticLevel level = DiagnosticLevel.info,
-  }) : assert(message != null),
-       super(
+  }) : super(
          null,
          <Object>[message],
          showName: false,
@@ -217,8 +213,7 @@
     List<Object> messageParts, {
     DiagnosticsTreeStyle style = DiagnosticsTreeStyle.flat,
     DiagnosticLevel level = DiagnosticLevel.info,
-  }) : assert(messageParts != null),
-       super(
+  }) : super(
          null,
          messageParts,
          showName: false,
@@ -406,7 +401,7 @@
     this.stackFilter,
     this.informationCollector,
     this.silent = false,
-  }) : assert(exception != null);
+  });
 
   /// Creates a copy of the error details but with the given fields replaced
   /// with new values.
@@ -672,9 +667,7 @@
     super.debugFillProperties(properties);
     final DiagnosticsNode verb = ErrorDescription('thrown${ context != null ? ErrorDescription(" $context") : ""}');
     final Diagnosticable? diagnosticable = _exceptionToDiagnosticable();
-    if (false) { // ignore: deprecated_member_use
-      properties.add(ErrorDescription('The null value was $verb.'));
-    } else if (exception is num) {
+    if (exception is num) {
       properties.add(ErrorDescription('The number $exception was $verb.'));
     } else {
       final DiagnosticsNode errorName;
@@ -1003,8 +996,6 @@
   ///
   /// The default behavior for the [onError] handler is to call this function.
   static void dumpErrorToConsole(FlutterErrorDetails details, { bool forceReport = false }) {
-    assert(details != null);
-    assert(details.exception != null);
     bool isInDebugMode = false;
     assert(() {
       // In debug mode, we ignore the "silent" flag.
@@ -1184,8 +1175,6 @@
   /// ```
   /// {@end-tool}
   static void reportError(FlutterErrorDetails details) {
-    assert(details != null);
-    assert(details.exception != null);
     onError?.call(details);
   }
 }
diff --git a/framework/lib/src/foundation/basic_types.dart b/framework/lib/src/foundation/basic_types.dart
index dc58411..9bc8c6a 100644
--- a/framework/lib/src/foundation/basic_types.dart
+++ b/framework/lib/src/foundation/basic_types.dart
@@ -231,7 +231,7 @@
   /// Creates a new factory.
   ///
   /// The `constructor` parameter must not be null.
-  const Factory(this.constructor) : assert(constructor != null);
+  const Factory(this.constructor);
 
   /// Creates a new object of type T.
   final ValueGetter<T> constructor;
diff --git a/framework/lib/src/foundation/binding.dart b/framework/lib/src/foundation/binding.dart
index 4babc7f..ac6d4ac 100644
--- a/framework/lib/src/foundation/binding.dart
+++ b/framework/lib/src/foundation/binding.dart
@@ -187,7 +187,7 @@
   /// such as a [TestWindow].
   ///
   /// The [window] is a singleton meant for use by applications that only have a
-  /// single main window. In addition to the properties of [ui.FlutterWindow],
+  /// single main window. In addition to the properties of [ui.FlutterView],
   /// [window] provides access to platform-specific properties and callbacks
   /// available on the [platformDispatcher].
   ///
@@ -327,7 +327,6 @@
         ]);
       }
       try {
-        assert(instance != null);
         if (instance._debugConstructed && _debugInitializedType == null) {
           throw FlutterError.fromParts(<DiagnosticsNode>[
             ErrorSummary('Binding initialized without calling initInstances.'),
@@ -553,10 +552,8 @@
   Future<void> lockEvents(Future<void> Function() callback) {
     final developer.TimelineTask timelineTask = developer.TimelineTask()..start('Lock events');
 
-    assert(callback != null);
     _lockCount += 1;
     final Future<void> future = callback();
-    assert(future != null, 'The lockEvents() callback returned null; it should return a Future<void> that completes when the lock is to expire.');
     future.whenComplete(() {
       _lockCount -= 1;
       if (!locked) {
@@ -627,8 +624,6 @@
     required String name,
     required AsyncCallback callback,
   }) {
-    assert(name != null);
-    assert(callback != null);
     registerServiceExtension(
       name: name,
       callback: (Map<String, String> parameters) async {
@@ -658,9 +653,6 @@
     required AsyncValueGetter<bool> getter,
     required AsyncValueSetter<bool> setter,
   }) {
-    assert(name != null);
-    assert(getter != null);
-    assert(setter != null);
     registerServiceExtension(
       name: name,
       callback: (Map<String, String> parameters) async {
@@ -692,9 +684,6 @@
     required AsyncValueGetter<double> getter,
     required AsyncValueSetter<double> setter,
   }) {
-    assert(name != null);
-    assert(getter != null);
-    assert(setter != null);
     registerServiceExtension(
       name: name,
       callback: (Map<String, String> parameters) async {
@@ -754,9 +743,6 @@
     required AsyncValueGetter<String> getter,
     required AsyncValueSetter<String> setter,
   }) {
-    assert(name != null);
-    assert(getter != null);
-    assert(setter != null);
     registerServiceExtension(
       name: name,
       callback: (Map<String, String> parameters) async {
@@ -825,8 +811,6 @@
     required String name,
     required ServiceExtensionCallback callback,
   }) {
-    assert(name != null);
-    assert(callback != null);
     final String methodName = 'ext.flutter.$name';
     developer.registerExtension(methodName, (String method, Map<String, String> parameters) async {
       assert(method == methodName);
diff --git a/framework/lib/src/foundation/capabilities.dart b/framework/lib/src/foundation/capabilities.dart
new file mode 100644
index 0000000..11c82ee
--- /dev/null
+++ b/framework/lib/src/foundation/capabilities.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 '_capabilities_io.dart'
+    as capabilities;
+
+/// Returns true if the application is using CanvasKit.
+///
+/// Only to be used for web.
+bool get isCanvasKit => capabilities.isCanvasKit;
diff --git a/framework/lib/src/foundation/change_notifier.dart b/framework/lib/src/foundation/change_notifier.dart
index a65df16..1706bc8 100644
--- a/framework/lib/src/foundation/change_notifier.dart
+++ b/framework/lib/src/foundation/change_notifier.dart
@@ -104,7 +104,7 @@
 /// It is O(1) for adding listeners and O(N) for removing listeners and dispatching
 /// notifications (where N is the number of listeners).
 ///
-/// {@macro flutter.flutter.animatedbuilder_changenotifier.rebuild}
+/// {@macro flutter.flutter.ListenableBuilder.ChangeNotifier.rebuild}
 ///
 /// See also:
 ///
diff --git a/framework/lib/src/foundation/consolidate_response.dart b/framework/lib/src/foundation/consolidate_response.dart
index 4d0f44c..94160ec 100644
--- a/framework/lib/src/foundation/consolidate_response.dart
+++ b/framework/lib/src/foundation/consolidate_response.dart
@@ -51,7 +51,6 @@
   bool autoUncompress = true,
   BytesReceivedCallback? onBytesReceived,
 }) {
-  assert(autoUncompress != null);
   final Completer<Uint8List> completer = Completer<Uint8List>.sync();
 
   final _OutputBuffer output = _OutputBuffer();
diff --git a/framework/lib/src/foundation/constants.dart b/framework/lib/src/foundation/constants.dart
index 0e5ccb6..576de1e 100644
--- a/framework/lib/src/foundation/constants.dart
+++ b/framework/lib/src/foundation/constants.dart
@@ -69,6 +69,3 @@
 
 /// A constant that is true if the application was compiled to run on the web.
 const bool kIsWeb = bool.fromEnvironment('dart.library.js_util');
-
-/// A constant that is true if the application is using canvasKit
-const bool isCanvasKit = bool.fromEnvironment('FLUTTER_WEB_USE_SKIA');
diff --git a/framework/lib/src/foundation/diagnostics.dart b/framework/lib/src/foundation/diagnostics.dart
index 80906dd..fa15201 100644
--- a/framework/lib/src/foundation/diagnostics.dart
+++ b/framework/lib/src/foundation/diagnostics.dart
@@ -253,28 +253,7 @@
     this.beforeName = '',
     this.suffixLineOne = '',
     this.mandatoryFooter = '',
-  }) : assert(prefixLineOne != null),
-       assert(prefixOtherLines != null),
-       assert(prefixLastChildLineOne != null),
-       assert(prefixOtherLinesRootNode != null),
-       assert(linkCharacter != null),
-       assert(propertyPrefixIfChildren != null),
-       assert(propertyPrefixNoChildren != null),
-       assert(lineBreak != null),
-       assert(lineBreakProperties != null),
-       assert(afterName != null),
-       assert(afterDescriptionIfBody != null),
-       assert(afterDescription != null),
-       assert(beforeProperties != null),
-       assert(afterProperties != null),
-       assert(propertySeparator != null),
-       assert(bodyIndent != null),
-       assert(footer != null),
-       assert(showChildren != null),
-       assert(addBlankLineIfNoChildren != null),
-       assert(isNameOnOwnLine != null),
-       assert(isBlankLineBetweenPropertiesAndChildren != null),
-       childLinkSpace = ' ' * linkCharacter.length;
+  }) : childLinkSpace = ' ' * linkCharacter.length;
 
   /// Prefix to add to the first line to display a child with this style.
   final String prefixLineOne;
@@ -1113,8 +1092,7 @@
     int wrapWidth = 100,
     int wrapWidthProperties = 65,
     int maxDescendentsTruncatableNode = -1,
-  }) : assert(minLevel != null),
-       _minLevel = minLevel,
+  }) : _minLevel = minLevel,
        _wrapWidth = wrapWidth,
        _wrapWidthProperties = wrapWidthProperties,
        _maxDescendentsTruncatableNode = maxDescendentsTruncatableNode;
@@ -1218,7 +1196,7 @@
 
     List<DiagnosticsNode> children = node.getChildren();
 
-    String? description = node.toDescription(parentConfiguration: parentConfiguration);
+    String description = node.toDescription(parentConfiguration: parentConfiguration);
     if (config.beforeName.isNotEmpty) {
       builder.write(config.beforeName);
     }
@@ -1229,7 +1207,7 @@
     if (uppercaseTitle) {
       name = name?.toUpperCase();
     }
-    if (description == null || description.isEmpty) {
+    if (description.isEmpty) {
       if (node.showName && name != null) {
         builder.write(name, allowWrap: wrapName);
       }
@@ -1388,7 +1366,6 @@
 
       for (int i = 0; i < children.length; i++) {
         final DiagnosticsNode child = children[i];
-        assert(child != null);
         final TextTreeConfiguration childConfig = _childTextConfiguration(child, config)!;
         if (i == children.length - 1) {
           final String lastChildPrefixLineOne = '$prefixChildrenRaw${childConfig.prefixLastChildLineOne}';
@@ -1463,12 +1440,10 @@
     this.showName = true,
     this.showSeparator = true,
     this.linePrefix,
-  }) : assert(showName != null),
-       assert(showSeparator != null),
-       // A name ending with ':' indicates that the user forgot that the ':' will
-       // be automatically added for them when generating descriptions of the
-       // property.
-       assert(
+  }) : assert(
+         // A name ending with ':' indicates that the user forgot that the ':' will
+         // be automatically added for them when generating descriptions of the
+         // property.
          name == null || !name.endsWith(':'),
          'Names of diagnostic nodes must not end with colons.\n'
          'name:\n'
@@ -1490,8 +1465,6 @@
     DiagnosticLevel level = DiagnosticLevel.info,
     bool allowWrap = true,
   }) {
-    assert(style != null);
-    assert(level != null);
     return DiagnosticsProperty<void>(
       '',
       null,
@@ -1728,7 +1701,6 @@
   }) {
     String result = super.toString();
     assert(style != null);
-    assert(minLevel != null);
     assert(() {
       if (_isSingleLine(style)) {
         result = toStringDeep(parentConfiguration: parentConfiguration, minLevel: minLevel);
@@ -1866,11 +1838,7 @@
     String message, {
     DiagnosticsTreeStyle style = DiagnosticsTreeStyle.singleLine,
     DiagnosticLevel level = DiagnosticLevel.info,
-  }) : assert(name != null),
-       assert(message != null),
-       assert(style != null),
-       assert(level != null),
-       super(name, null, description: message, style: style, level: level);
+  }) : super(name, null, description: message, style: style, level: level);
 }
 
 /// Property which encloses its string [value] in quotes.
@@ -1894,10 +1862,7 @@
     super.ifEmpty,
     super.style,
     super.level,
-  }) : assert(showName != null),
-       assert(quoted != null),
-       assert(style != null),
-       assert(level != null);
+  });
 
   /// Whether the value is enclosed in double quotes.
   final bool quoted;
@@ -2005,9 +1970,7 @@
     super.showName,
     super.style,
     super.level,
-  }) : assert(showName != null),
-       assert(style != null),
-       assert(level != null);
+  });
 
   /// Property with a [value] that is computed only when needed.
   ///
@@ -2024,9 +1987,7 @@
     super.tooltip,
     super.defaultValue,
     super.level,
-  }) : assert(showName != null),
-       assert(level != null),
-       super.lazy();
+  }) : super.lazy();
 
   @override
   String numberToString() => debugFormatDouble(value);
@@ -2048,9 +2009,7 @@
     super.defaultValue,
     super.style,
     super.level,
-  }) : assert(showName != null),
-       assert(level != null),
-       assert(style != null);
+  });
 
   @override
   String numberToString() => value.toString();
@@ -2075,8 +2034,7 @@
     super.tooltip,
     super.unit,
     super.level,
-  }) : assert(showName != null),
-       assert(level != null);
+  });
 
   @override
   String valueToString({ TextTreeConfiguration? parentConfiguration }) {
@@ -2150,9 +2108,7 @@
     bool showName = false,
     Object? defaultValue,
     DiagnosticLevel level = DiagnosticLevel.info,
-  }) : assert(showName != null),
-       assert(level != null),
-       assert(ifTrue != null || ifFalse != null),
+  }) : assert(ifTrue != null || ifFalse != null),
        super(
          name,
          value,
@@ -2254,10 +2210,7 @@
     super.showName,
     super.showSeparator,
     super.level,
-  }) : assert(style != null),
-       assert(showName != null),
-       assert(showSeparator != null),
-       assert(level != null);
+  });
 
   @override
   String valueToString({TextTreeConfiguration? parentConfiguration}) {
@@ -2335,7 +2288,7 @@
     super.value, {
     super.defaultValue,
     super.level,
-  }) : assert(level != null);
+  });
 
   @override
   String valueToString({ TextTreeConfiguration? parentConfiguration }) {
@@ -2382,9 +2335,7 @@
     super.ifNull,
     super.showName = false,
     super.level,
-  }) : assert(ifPresent != null || ifNull != null),
-       assert(showName != null),
-       assert(level != null);
+  }) : assert(ifPresent != null || ifNull != null);
 
   /// Shorthand constructor to describe whether the property has a value.
   ///
@@ -2396,9 +2347,7 @@
     String super.name,
     super.value, {
     super.level,
-  }) : assert(name != null),
-       assert(level != null),
-       ifPresent = 'has $name',
+  }) : ifPresent = 'has $name',
        super(
     showName: false,
   );
@@ -2495,17 +2444,13 @@
     super.showName,
     super.showSeparator,
     super.level,
-  }) : assert(value != null),
-       assert(showName != null),
-       assert(showSeparator != null),
-       assert(level != null);
+  });
 
   @override
   Map<String, T?> get value => super.value!;
 
   @override
   String valueToString({TextTreeConfiguration? parentConfiguration}) {
-    assert(value != null);
     if (!_hasNonNullEntry() && ifEmpty != null) {
       return ifEmpty!;
     }
@@ -2598,11 +2543,7 @@
     this.allowNameWrap = true,
     DiagnosticsTreeStyle super.style = DiagnosticsTreeStyle.singleLine,
     DiagnosticLevel level = DiagnosticLevel.info,
-  }) : assert(showName != null),
-       assert(showSeparator != null),
-       assert(style != null),
-       assert(level != null),
-       _description = description,
+  }) : _description = description,
        _valueComputed = true,
        _value = value,
        _computeValue = null,
@@ -2620,7 +2561,7 @@
   /// The [showName], [showSeparator], [style], [missingIfNull], and [level]
   /// arguments must not be null.
   ///
-  /// The [level] argument is just a suggestion and can be overridden if
+  /// The [level] argument is just a suggestion and can be overridden
   /// if something else about the property causes it to have a lower or higher
   /// level. For example, if calling `computeValue` throws an exception, [level]
   /// will always return [DiagnosticLevel.error].
@@ -2640,12 +2581,7 @@
     this.allowNameWrap = true,
     DiagnosticsTreeStyle super.style = DiagnosticsTreeStyle.singleLine,
     DiagnosticLevel level = DiagnosticLevel.info,
-  }) : assert(showName != null),
-       assert(showSeparator != null),
-       assert(defaultValue == kNoDefaultValue || defaultValue is T?),
-       assert(missingIfNull != null),
-       assert(style != null),
-       assert(level != null),
+  }) : assert(defaultValue == kNoDefaultValue || defaultValue is T?),
        _description = description,
        _valueComputed = false,
        _value = null,
@@ -2765,7 +2701,6 @@
   ///
   /// `text` must not be null.
   String _addTooltip(String text) {
-    assert(text != null);
     return tooltip == null ? text : '$text ($tooltip)';
   }
 
@@ -2938,7 +2873,7 @@
     super.name,
     required this.value,
     required super.style,
-  }) : assert(value != null);
+  });
 
   @override
   final T value;
@@ -3153,6 +3088,8 @@
 
   /// Add additional properties associated with the node.
   ///
+  /// {@youtube 560 315 https://www.youtube.com/watch?v=DnC7eT-vh1k}
+  ///
   /// Use the most specific [DiagnosticsProperty] existing subclass to describe
   /// each property instead of the [DiagnosticsProperty] base class. There are
   /// only a small number of [DiagnosticsProperty] subclasses each covering a
diff --git a/framework/lib/src/foundation/isolates.dart b/framework/lib/src/foundation/isolates.dart
index 29c9991..cea2408 100644
--- a/framework/lib/src/foundation/isolates.dart
+++ b/framework/lib/src/foundation/isolates.dart
@@ -9,33 +9,17 @@
 
 /// Signature for the callback passed to [compute].
 ///
-/// {@macro flutter.foundation.compute.types}
-///
-/// Instances of [ComputeCallback] must be functions that can be sent to an
-/// isolate.
 /// {@macro flutter.foundation.compute.callback}
 ///
-/// {@macro flutter.foundation.compute.types}
-typedef ComputeCallback<Q, R> = FutureOr<R> Function(Q message);
+typedef ComputeCallback<M, R> = FutureOr<R> Function(M message);
 
 /// The signature of [compute], which spawns an isolate, runs `callback` on
 /// that isolate, passes it `message`, and (eventually) returns the value
 /// returned by `callback`.
-///
-/// {@macro flutter.foundation.compute.usecase}
-///
-/// The function used as `callback` must be one that can be sent to an isolate.
-/// {@macro flutter.foundation.compute.callback}
-///
-/// {@macro flutter.foundation.compute.types}
-///
-/// The `debugLabel` argument can be specified to provide a name to add to the
-/// [Timeline]. This is useful when profiling an application.
-typedef ComputeImpl = Future<R> Function<Q, R>(ComputeCallback<Q, R> callback, Q message, { String? debugLabel });
+typedef ComputeImpl = Future<R> Function<M, R>(ComputeCallback<M, R> callback, M message, { String? debugLabel });
 
-/// A function that spawns an isolate and runs the provided `callback` on that
-/// isolate, passes it the provided `message`, and (eventually) returns the
-/// value returned by `callback`.
+/// Asynchronously runs the given [callback] - with the provided [message] -
+/// in the background and completes with the result.
 ///
 /// {@template flutter.foundation.compute.usecase}
 /// This is useful for operations that take longer than a few milliseconds, and
@@ -68,34 +52,26 @@
 /// ```
 /// {@end-tool}
 ///
-/// The function used as `callback` must be one that can be sent to an isolate.
+/// On web platforms this will run [callback] on the current eventloop.
+/// On native platforms this will run [callback] in a separate isolate.
+///
 /// {@template flutter.foundation.compute.callback}
-/// Qualifying functions include:
 ///
-///   * top-level functions
-///   * static methods
-///   * closures that only capture objects that can be sent to an isolate
+/// The `callback`, the `message` given to it as well as the result have to be
+/// objects that can be sent across isolates (as they may be transitively copied
+/// if needed). The majority of objects can be sent across isolates.
 ///
-/// Using closures must be done with care. Due to
-/// [dart-lang/sdk#36983](https://github.com/dart-lang/sdk/issues/36983) a
-/// closure may capture objects that, while not directly used in the closure
-/// itself, may prevent it from being sent to an isolate.
+/// See [SendPort.send] for more information about exceptions as well as a note
+/// of warning about sending closures, which can capture more state than needed.
+///
 /// {@endtemplate}
 ///
-/// {@template flutter.foundation.compute.types}
-/// The [compute] method accepts the following parameters:
+/// On native platforms `await compute(fun, message)` is equivalent to
+/// `await Isolate.run(() => fun(message))`. See also [Isolate.run].
 ///
-///  * `Q` is the type of the message that kicks off the computation.
-///  * `R` is the type of the value returned.
-///
-/// There are limitations on the values that can be sent and received to and
-/// from isolates. These limitations constrain the values of `Q` and `R` that
-/// are possible. See the discussion at [SendPort.send].
-///
-/// The same limitations apply to any errors generated by the computation.
-/// {@endtemplate}
-///
-/// See also:
-///
-///   * [ComputeImpl], for the [compute] function's signature.
-const ComputeImpl compute = isolates.compute;
+/// The `debugLabel` - if provided - is used as name for the isolate that
+/// executes `callback`. [Timeline] events produced by that isolate will have
+/// the name associated with them. This is useful when profiling an application.
+Future<R> compute<M, R>(ComputeCallback<M, R> callback, M message, {String? debugLabel}) {
+  return isolates.compute<M, R>(callback, message, debugLabel: debugLabel);
+}
diff --git a/framework/lib/src/foundation/math.dart b/framework/lib/src/foundation/math.dart
index 053192a..7c559bb 100644
--- a/framework/lib/src/foundation/math.dart
+++ b/framework/lib/src/foundation/math.dart
@@ -8,16 +8,5 @@
 /// floating point numbers.
 //
 // See also: //dev/benchmarks/microbenchmarks/lib/foundation/clamp.dart
-double clampDouble(double x, double min, double max) {
-  assert(min <= max && !max.isNaN && !min.isNaN);
-  if (x < min) {
-    return min;
-  }
-  if (x > max) {
-    return max;
-  }
-  if (x.isNaN) {
-    return max;
-  }
-  return x;
-}
+
+export 'package:engine/ui.dart' show clampDouble;
\ No newline at end of file
diff --git a/framework/lib/src/foundation/node.dart b/framework/lib/src/foundation/node.dart
index f8c377b..6e830f1 100644
--- a/framework/lib/src/foundation/node.dart
+++ b/framework/lib/src/foundation/node.dart
@@ -92,7 +92,6 @@
   /// method, as in `super.attach(owner)`.
   @mustCallSuper
   void attach(covariant Object owner) {
-    assert(owner != null);
     assert(_owner == null);
     _owner = owner;
   }
@@ -124,7 +123,6 @@
   @protected
   @mustCallSuper
   void adoptChild(covariant AbstractNode child) {
-    assert(child != null);
     assert(child._parent == null);
     assert(() {
       AbstractNode node = this;
@@ -147,7 +145,6 @@
   @protected
   @mustCallSuper
   void dropChild(covariant AbstractNode child) {
-    assert(child != null);
     assert(child._parent == this);
     assert(child.attached == attached);
     child._parent = null;
diff --git a/framework/lib/src/foundation/serialization.dart b/framework/lib/src/foundation/serialization.dart
index b0ade7d..bdeaecc 100644
--- a/framework/lib/src/foundation/serialization.dart
+++ b/framework/lib/src/foundation/serialization.dart
@@ -170,8 +170,7 @@
 /// The byte order used is [Endian.host] throughout.
 class ReadBuffer {
   /// Creates a [ReadBuffer] for reading from the specified [data].
-  ReadBuffer(this.data)
-    : assert(data != null);
+  ReadBuffer(this.data);
 
   /// The underlying data being read.
   final ByteData data;
diff --git a/framework/lib/src/foundation/stack_frame.dart b/framework/lib/src/foundation/stack_frame.dart
index 1a18d2d..5e22e1e 100644
--- a/framework/lib/src/foundation/stack_frame.dart
+++ b/framework/lib/src/foundation/stack_frame.dart
@@ -35,16 +35,7 @@
     required this.method,
     this.isConstructor = false,
     required this.source,
-  })  : assert(number != null),
-        assert(column != null),
-        assert(line != null),
-        assert(method != null),
-        assert(packageScheme != null),
-        assert(package != null),
-        assert(packagePath != null),
-        assert(className != null),
-        assert(isConstructor != null),
-        assert(source != null);
+  });
 
   /// A stack frame representing an asynchronous suspension.
   static const StackFrame asynchronousSuspension = StackFrame(
@@ -74,13 +65,11 @@
   ///
   /// This is normally useful with [StackTrace.current].
   static List<StackFrame> fromStackTrace(StackTrace stack) {
-    assert(stack != null);
     return fromStackString(stack.toString());
   }
 
   /// Parses a list of [StackFrame]s from the [StackTrace.toString] method.
   static List<StackFrame> fromStackString(String stack) {
-    assert(stack != null);
     return stack
         .trim()
         .split('\n')
@@ -181,7 +170,6 @@
 
   /// Parses a single [StackFrame] from a single line of a [StackTrace].
   static StackFrame? fromStackTraceLine(String line) {
-    assert(line != null);
     if (line == '<asynchronous suspension>') {
       return asynchronousSuspension;
     } else if (line == '...') {
diff --git a/framework/lib/src/gestures/arena.dart b/framework/lib/src/gestures/arena.dart
index f8a399e..b832a5d 100644
--- a/framework/lib/src/gestures/arena.dart
+++ b/framework/lib/src/gestures/arena.dart
@@ -97,6 +97,10 @@
   }
 }
 
+/// Used for disambiguating the meaning of sequences of pointer events.
+///
+/// {@youtube 560 315 https://www.youtube.com/watch?v=Q85LBtBdi0U}
+///
 /// The first member to accept or the last member to not reject wins.
 ///
 /// See <https://flutter.dev/gestures/#gesture-disambiguation> for more
@@ -266,7 +270,6 @@
 
   void _resolveInFavorOf(int pointer, _GestureArena state, GestureArenaMember member) {
     assert(state == _arenas[pointer]);
-    assert(state != null);
     assert(state.eagerWinner == null || state.eagerWinner == member);
     assert(!state.isOpen);
     _arenas.remove(pointer);
diff --git a/framework/lib/src/gestures/binding.dart b/framework/lib/src/gestures/binding.dart
index 707381c..c937a63 100644
--- a/framework/lib/src/gestures/binding.dart
+++ b/framework/lib/src/gestures/binding.dart
@@ -83,8 +83,6 @@
   // Add `event` for resampling or dispatch it directly if
   // not a touch event.
   void addOrDispatch(PointerEvent event) {
-    final SchedulerBinding scheduler = SchedulerBinding.instance;
-    assert(scheduler != null);
     // Add touch event to resampler or dispatch pointer event directly.
     if (event.kind == PointerDeviceKind.touch) {
       // Save last event time for debugPrint of resampling margin.
@@ -108,7 +106,6 @@
   // The `samplingClock` is the clock used to determine frame time age.
   void sample(Duration samplingOffset, SamplingClock clock) {
     final SchedulerBinding scheduler = SchedulerBinding.instance;
-    assert(scheduler != null);
 
     // Initialize `_frameTime` if needed. This will be used for periodic
     // sampling when frame callbacks are not received.
@@ -290,9 +287,18 @@
   void _handlePointerDataPacket(ui.PointerDataPacket packet) {
     // We convert pointer data to logical pixels so that e.g. the touch slop can be
     // defined in a device-independent manner.
-    _pendingPointerEvents.addAll(PointerEventConverter.expand(packet.data, window.devicePixelRatio));
-    if (!locked) {
-      _flushPointerEventQueue();
+    try {
+      _pendingPointerEvents.addAll(PointerEventConverter.expand(packet.data, window.devicePixelRatio));
+      if (!locked) {
+        _flushPointerEventQueue();
+      }
+    } catch (error, stack) {
+      FlutterError.reportError(FlutterErrorDetails(
+        exception: error,
+        stack: stack,
+        library: 'gestures library',
+        context: ErrorDescription('while handling a pointer data packet'),
+      ));
     }
   }
 
@@ -360,7 +366,7 @@
   void _handlePointerEventImmediately(PointerEvent event) {
     HitTestResult? hitTestResult;
     if (event is PointerDownEvent || event is PointerSignalEvent || event is PointerHoverEvent || event is PointerPanZoomStartEvent) {
-      assert(!_hitTests.containsKey(event.pointer), 'Pointer of $event unexpectedly has a HitTestResult associated with it.');
+      assert(!_hitTests.containsKey(event.pointer), 'Pointer of ${event.toString(minLevel: DiagnosticLevel.debug)} unexpectedly has a HitTestResult associated with it.');
       hitTestResult = HitTestResult();
       hitTest(hitTestResult, event.position);
       if (event is PointerDownEvent || event is PointerPanZoomStartEvent) {
@@ -368,7 +374,7 @@
       }
       assert(() {
         if (debugPrintHitTestResults) {
-          debugPrint('$event: $hitTestResult');
+          debugPrint('${event.toString(minLevel: DiagnosticLevel.debug)}: $hitTestResult');
         }
         return true;
       }());
@@ -391,7 +397,6 @@
     if (hitTestResult != null ||
         event is PointerAddedEvent ||
         event is PointerRemovedEvent) {
-      assert(event.position != null);
       dispatchEvent(event, hitTestResult);
     }
   }
diff --git a/framework/lib/src/gestures/converter.dart b/framework/lib/src/gestures/converter.dart
index 9d00c3a..6aa7741 100644
--- a/framework/lib/src/gestures/converter.dart
+++ b/framework/lib/src/gestures/converter.dart
@@ -52,9 +52,8 @@
   static Iterable<PointerEvent> expand(Iterable<ui.PointerData> data, double devicePixelRatio) {
     return data
         .where((ui.PointerData datum) => datum.signalKind != ui.PointerSignalKind.unknown)
-        .map((ui.PointerData datum) {
+        .map<PointerEvent?>((ui.PointerData datum) {
           final Offset position = Offset(datum.physicalX, datum.physicalY) / devicePixelRatio;
-          assert(position != null);
           final Offset delta = Offset(datum.physicalDeltaX, datum.physicalDeltaY) / devicePixelRatio;
           final double radiusMinor = _toLogicalPixels(datum.radiusMinor, devicePixelRatio);
           final double radiusMajor = _toLogicalPixels(datum.radiusMajor, devicePixelRatio);
@@ -62,7 +61,6 @@
           final double radiusMax = _toLogicalPixels(datum.radiusMax, devicePixelRatio);
           final Duration timeStamp = datum.timeStamp;
           final PointerDeviceKind kind = datum.kind;
-          assert(datum.change != null);
           switch (datum.signalKind ?? ui.PointerSignalKind.none) {
             case ui.PointerSignalKind.none:
               switch (datum.change) {
@@ -249,6 +247,9 @@
                   );
               }
             case ui.PointerSignalKind.scroll:
+              if (!datum.scrollDeltaX.isFinite || !datum.scrollDeltaY.isFinite || devicePixelRatio <= 0) {
+                return null;
+              }
               final Offset scrollDelta =
                   Offset(datum.scrollDeltaX, datum.scrollDeltaY) / devicePixelRatio;
               return PointerScrollEvent(
@@ -282,7 +283,7 @@
               // enumeration to PointerSignalKind.
               throw StateError('Unreachable');
           }
-        });
+        }).whereType<PointerEvent>();
   }
 
   static double _toLogicalPixels(double physicalPixels, double devicePixelRatio) => physicalPixels / devicePixelRatio;
diff --git a/framework/lib/src/gestures/drag_details.dart b/framework/lib/src/gestures/drag_details.dart
index cd85feb..39cf290 100644
--- a/framework/lib/src/gestures/drag_details.dart
+++ b/framework/lib/src/gestures/drag_details.dart
@@ -25,8 +25,7 @@
   DragDownDetails({
     this.globalPosition = Offset.zero,
     Offset? localPosition,
-  }) : assert(globalPosition != null),
-       localPosition = localPosition ?? globalPosition;
+  }) : localPosition = localPosition ?? globalPosition;
 
   /// The global position at which the pointer contacted the screen.
   ///
@@ -73,8 +72,7 @@
     this.globalPosition = Offset.zero,
     Offset? localPosition,
     this.kind,
-  }) : assert(globalPosition != null),
-       localPosition = localPosition ?? globalPosition;
+  }) : localPosition = localPosition ?? globalPosition;
 
   /// Recorded timestamp of the source pointer event that triggered the drag
   /// event.
@@ -109,10 +107,12 @@
   String toString() => '${objectRuntimeType(this, 'DragStartDetails')}($globalPosition)';
 }
 
+/// {@template flutter.gestures.dragdetails.GestureDragStartCallback}
 /// Signature for when a pointer has contacted the screen and has begun to move.
 ///
 /// The `details` object provides the position of the touch when it first
 /// touched the surface.
+/// {@endtemplate}
 ///
 /// See [DragGestureRecognizer.onStart].
 typedef GestureDragStartCallback = void Function(DragStartDetails details);
@@ -126,7 +126,7 @@
 ///  * [DragStartDetails], the details for [GestureDragStartCallback].
 ///  * [DragEndDetails], the details for [GestureDragEndCallback].
 class DragUpdateDetails {
-  /// Creates details for a [DragUpdateDetails].
+  /// Creates details for a [GestureDragUpdateCallback].
   ///
   /// The [delta] argument must not be null.
   ///
@@ -140,8 +140,7 @@
     this.primaryDelta,
     required this.globalPosition,
     Offset? localPosition,
-  }) : assert(delta != null),
-       assert(
+  }) : assert(
          primaryDelta == null
            || (primaryDelta == delta.dx && delta.dy == 0.0)
            || (primaryDelta == delta.dy && delta.dx == 0.0),
@@ -195,11 +194,13 @@
   String toString() => '${objectRuntimeType(this, 'DragUpdateDetails')}($delta)';
 }
 
+/// {@template flutter.gestures.dragdetails.GestureDragUpdateCallback}
 /// Signature for when a pointer that is in contact with the screen and moving
 /// has moved again.
 ///
 /// The `details` object provides the position of the touch and the distance it
 /// has traveled since the last update.
+/// {@endtemplate}
 ///
 /// See [DragGestureRecognizer.onUpdate].
 typedef GestureDragUpdateCallback = void Function(DragUpdateDetails details);
@@ -219,8 +220,7 @@
   DragEndDetails({
     this.velocity = Velocity.zero,
     this.primaryVelocity,
-  }) : assert(velocity != null),
-       assert(
+  }) : assert(
          primaryVelocity == null
            || primaryVelocity == velocity.pixelsPerSecond.dx
            || primaryVelocity == velocity.pixelsPerSecond.dy,
diff --git a/framework/lib/src/gestures/eager.dart b/framework/lib/src/gestures/eager.dart
index 6193a20..3c83435 100644
--- a/framework/lib/src/gestures/eager.dart
+++ b/framework/lib/src/gestures/eager.dart
@@ -18,12 +18,8 @@
   ///
   /// {@macro flutter.gestures.GestureRecognizer.supportedDevices}
   EagerGestureRecognizer({
-    @Deprecated(
-      'Migrate to supportedDevices. '
-      'This feature was deprecated after v2.3.0-1.0.pre.',
-    )
-    super.kind,
     super.supportedDevices,
+    super.allowedButtonsFilter,
   });
 
   @override
diff --git a/framework/lib/src/gestures/events.dart b/framework/lib/src/gestures/events.dart
index 7e02867..7e6277c 100644
--- a/framework/lib/src/gestures/events.dart
+++ b/framework/lib/src/gestures/events.dart
@@ -843,8 +843,7 @@
 }
 
 class _TransformedPointerAddedEvent extends _TransformedPointerEvent with _CopyPointerAddedEvent implements PointerAddedEvent {
-  _TransformedPointerAddedEvent(this.original, this.transform)
-    : assert(original != null), assert(transform != null);
+  _TransformedPointerAddedEvent(this.original, this.transform);
 
   @override
   final PointerAddedEvent original;
@@ -934,8 +933,7 @@
 }
 
 class _TransformedPointerRemovedEvent extends _TransformedPointerEvent with _CopyPointerRemovedEvent implements PointerRemovedEvent {
-  _TransformedPointerRemovedEvent(this.original, this.transform)
-    : assert(original != null), assert(transform != null);
+  _TransformedPointerRemovedEvent(this.original, this.transform);
 
   @override
   final PointerRemovedEvent original;
@@ -1051,8 +1049,7 @@
 }
 
 class _TransformedPointerHoverEvent extends _TransformedPointerEvent with _CopyPointerHoverEvent implements PointerHoverEvent {
-  _TransformedPointerHoverEvent(this.original, this.transform)
-    : assert(original != null), assert(transform != null);
+  _TransformedPointerHoverEvent(this.original, this.transform);
 
   @override
   final PointerHoverEvent original;
@@ -1198,8 +1195,7 @@
 }
 
 class _TransformedPointerEnterEvent extends _TransformedPointerEvent with _CopyPointerEnterEvent implements PointerEnterEvent {
-  _TransformedPointerEnterEvent(this.original, this.transform)
-    : assert(original != null), assert(transform != null);
+  _TransformedPointerEnterEvent(this.original, this.transform);
 
   @override
   final PointerEnterEvent original;
@@ -1344,8 +1340,7 @@
 }
 
 class _TransformedPointerExitEvent extends _TransformedPointerEvent with _CopyPointerExitEvent implements PointerExitEvent {
-  _TransformedPointerExitEvent(this.original, this.transform)
-    : assert(original != null), assert(transform != null);
+  _TransformedPointerExitEvent(this.original, this.transform);
 
   @override
   final PointerExitEvent original;
@@ -1453,8 +1448,7 @@
 }
 
 class _TransformedPointerDownEvent extends _TransformedPointerEvent with _CopyPointerDownEvent implements PointerDownEvent {
-  _TransformedPointerDownEvent(this.original, this.transform)
-    : assert(original != null), assert(transform != null);
+  _TransformedPointerDownEvent(this.original, this.transform);
 
   @override
   final PointerDownEvent original;
@@ -1571,8 +1565,7 @@
 }
 
 class _TransformedPointerMoveEvent extends _TransformedPointerEvent with _CopyPointerMoveEvent implements PointerMoveEvent {
-  _TransformedPointerMoveEvent(this.original, this.transform)
-    : assert(original != null), assert(transform != null);
+  _TransformedPointerMoveEvent(this.original, this.transform);
 
   @override
   final PointerMoveEvent original;
@@ -1684,8 +1677,7 @@
 }
 
 class _TransformedPointerUpEvent extends _TransformedPointerEvent with _CopyPointerUpEvent implements PointerUpEvent {
-  _TransformedPointerUpEvent(this.original, this.transform)
-    : assert(original != null), assert(transform != null);
+  _TransformedPointerUpEvent(this.original, this.transform);
 
   @override
   final PointerUpEvent original;
@@ -1784,12 +1776,7 @@
     super.position,
     this.scrollDelta = Offset.zero,
     super.embedderId,
-  }) : assert(timeStamp != null),
-       assert(kind != null),
-       assert(device != null),
-       assert(position != null),
-       assert(scrollDelta != null),
-       assert(!identical(kind, PointerDeviceKind.trackpad));
+  });
 
   @override
   final Offset scrollDelta;
@@ -1810,8 +1797,7 @@
 }
 
 class _TransformedPointerScrollEvent extends _TransformedPointerEvent with _CopyPointerScrollEvent implements PointerScrollEvent {
-  _TransformedPointerScrollEvent(this.original, this.transform)
-    : assert(original != null), assert(transform != null);
+  _TransformedPointerScrollEvent(this.original, this.transform);
 
   @override
   final PointerScrollEvent original;
@@ -1889,10 +1875,7 @@
     super.device,
     super.position,
     super.embedderId,
-  }) : assert(timeStamp != null),
-       assert(kind != null),
-       assert(device != null),
-       assert(position != null);
+  });
 
   @override
   PointerScrollInertiaCancelEvent transformed(Matrix4? transform) {
@@ -1904,8 +1887,7 @@
 }
 
 class _TransformedPointerScrollInertiaCancelEvent extends _TransformedPointerEvent with _CopyPointerScrollInertiaCancelEvent implements PointerScrollInertiaCancelEvent {
-  _TransformedPointerScrollInertiaCancelEvent(this.original, this.transform)
-    : assert(original != null), assert(transform != null);
+  _TransformedPointerScrollInertiaCancelEvent(this.original, this.transform);
 
   @override
   final PointerScrollInertiaCancelEvent original;
@@ -1980,12 +1962,7 @@
     super.position,
     super.embedderId,
     this.scale = 1.0,
-  }) : assert(timeStamp != null),
-       assert(kind != null),
-       assert(device != null),
-       assert(position != null),
-       assert(embedderId != null),
-       assert(scale != null);
+  });
 
   @override
   final double scale;
@@ -2000,8 +1977,7 @@
 }
 
 class _TransformedPointerScaleEvent extends _TransformedPointerEvent with _CopyPointerScaleEvent implements PointerScaleEvent {
-  _TransformedPointerScaleEvent(this.original, this.transform)
-    : assert(original != null), assert(transform != null);
+  _TransformedPointerScaleEvent(this.original, this.transform);
 
   @override
   final PointerScaleEvent original;
@@ -2069,13 +2045,7 @@
     super.position,
     super.embedderId,
     super.synthesized,
-  }) : assert(timeStamp != null),
-       assert(device != null),
-       assert(pointer != null),
-       assert(position != null),
-       assert(embedderId != null),
-       assert(synthesized != null),
-       super(kind: PointerDeviceKind.trackpad);
+  }) : super(kind: PointerDeviceKind.trackpad);
 
   @override
   PointerPanZoomStartEvent transformed(Matrix4? transform) {
@@ -2087,8 +2057,7 @@
 }
 
 class _TransformedPointerPanZoomStartEvent extends _TransformedPointerEvent with _CopyPointerPanZoomStartEvent implements PointerPanZoomStartEvent {
-  _TransformedPointerPanZoomStartEvent(this.original, this.transform)
-    : assert(original != null), assert(transform != null);
+  _TransformedPointerPanZoomStartEvent(this.original, this.transform);
 
   @override
   final PointerPanZoomStartEvent original;
@@ -2180,17 +2149,7 @@
     this.scale = 1.0,
     this.rotation = 0.0,
     super.synthesized,
-  }) : assert(timeStamp != null),
-       assert(device != null),
-       assert(pointer != null),
-       assert(position != null),
-       assert(embedderId != null),
-       assert(pan != null),
-       assert(panDelta != null),
-       assert(scale != null),
-       assert(rotation != null),
-       assert(synthesized != null),
-       super(kind: PointerDeviceKind.trackpad);
+  }) : super(kind: PointerDeviceKind.trackpad);
 
   @override
   final Offset pan;
@@ -2215,8 +2174,7 @@
 }
 
 class _TransformedPointerPanZoomUpdateEvent extends _TransformedPointerEvent with _CopyPointerPanZoomUpdateEvent implements PointerPanZoomUpdateEvent {
-  _TransformedPointerPanZoomUpdateEvent(this.original, this.transform)
-    : assert(original != null), assert(transform != null);
+  _TransformedPointerPanZoomUpdateEvent(this.original, this.transform);
 
   @override
   Offset get pan => original.pan;
@@ -2304,13 +2262,7 @@
     super.position,
     super.embedderId,
     super.synthesized,
-  }) : assert(timeStamp != null),
-       assert(device != null),
-       assert(pointer != null),
-       assert(position != null),
-       assert(embedderId != null),
-       assert(synthesized != null),
-       super(kind: PointerDeviceKind.trackpad);
+  }) : super(kind: PointerDeviceKind.trackpad);
 
   @override
   PointerPanZoomEndEvent transformed(Matrix4? transform) {
@@ -2322,8 +2274,7 @@
 }
 
 class _TransformedPointerPanZoomEndEvent extends _TransformedPointerEvent with _CopyPointerPanZoomEndEvent implements PointerPanZoomEndEvent {
-  _TransformedPointerPanZoomEndEvent(this.original, this.transform)
-    : assert(original != null), assert(transform != null);
+  _TransformedPointerPanZoomEndEvent(this.original, this.transform);
 
   @override
   final PointerPanZoomEndEvent original;
@@ -2473,8 +2424,7 @@
 }
 
 class _TransformedPointerCancelEvent extends _TransformedPointerEvent with _CopyPointerCancelEvent implements PointerCancelEvent {
-  _TransformedPointerCancelEvent(this.original, this.transform)
-    : assert(original != null), assert(transform != null);
+  _TransformedPointerCancelEvent(this.original, this.transform);
 
   @override
   final PointerCancelEvent original;
diff --git a/framework/lib/src/gestures/force_press.dart b/framework/lib/src/gestures/force_press.dart
index 5fe458b..b2d27e0 100644
--- a/framework/lib/src/gestures/force_press.dart
+++ b/framework/lib/src/gestures/force_press.dart
@@ -52,9 +52,7 @@
     required this.globalPosition,
     Offset? localPosition,
     required this.pressure,
-  }) : assert(globalPosition != null),
-       assert(pressure != null),
-       localPosition = localPosition ?? globalPosition;
+  }) : localPosition = localPosition ?? globalPosition;
 
   /// The global position at which the function was called.
   final Offset globalPosition;
@@ -127,16 +125,9 @@
     this.peakPressure = 0.85,
     this.interpolation = _inverseLerp,
     super.debugOwner,
-    @Deprecated(
-      'Migrate to supportedDevices. '
-      'This feature was deprecated after v2.3.0-1.0.pre.',
-    )
-    super.kind,
     super.supportedDevices,
-  }) : assert(startPressure != null),
-       assert(peakPressure != null),
-       assert(interpolation != null),
-       assert(peakPressure > startPressure);
+    super.allowedButtonsFilter,
+  }) : assert(peakPressure > startPressure);
 
   /// A pointer is in contact with the screen and has just pressed with a force
   /// exceeding the [startPressure]. Consequently, if there were other gesture
diff --git a/framework/lib/src/gestures/gesture_settings.dart b/framework/lib/src/gestures/gesture_settings.dart
index 496a2d2..afaa5ed 100644
--- a/framework/lib/src/gestures/gesture_settings.dart
+++ b/framework/lib/src/gestures/gesture_settings.dart
@@ -24,11 +24,11 @@
     this.touchSlop,
   });
 
-  /// Create a new [DeviceGestureSettings] from the provided [window].
-  factory DeviceGestureSettings.fromWindow(ui.FlutterView window) {
-    final double? physicalTouchSlop = window.viewConfiguration.gestureSettings.physicalTouchSlop;
+  /// Create a new [DeviceGestureSettings] from the provided [view].
+  factory DeviceGestureSettings.fromView(ui.FlutterView view) {
+    final double? physicalTouchSlop = view.viewConfiguration.gestureSettings.physicalTouchSlop;
     return DeviceGestureSettings(
-      touchSlop: physicalTouchSlop == null ? null : physicalTouchSlop / window.devicePixelRatio
+      touchSlop: physicalTouchSlop == null ? null : physicalTouchSlop / view.devicePixelRatio
     );
   }
 
diff --git a/framework/lib/src/gestures/hit_test.dart b/framework/lib/src/gestures/hit_test.dart
index 221d9d6..547c9d8 100644
--- a/framework/lib/src/gestures/hit_test.dart
+++ b/framework/lib/src/gestures/hit_test.dart
@@ -215,7 +215,6 @@
   ///    around this function for hit testing on [RenderBox]s.
   @protected
   void pushTransform(Matrix4 transform) {
-    assert(transform != null);
     assert(
       _debugVectorMoreOrLessEquals(transform.getRow(2), Vector4(0, 0, 1, 0)) &&
       _debugVectorMoreOrLessEquals(transform.getColumn(2), Vector4(0, 0, 1, 0)),
@@ -255,7 +254,6 @@
   ///    around this function for hit testing on [RenderSliver]s.
   @protected
   void pushOffset(Offset offset) {
-    assert(offset != null);
     _localTransforms.add(_OffsetTransformPart(offset));
   }
 
diff --git a/framework/lib/src/gestures/long_press.dart b/framework/lib/src/gestures/long_press.dart
index febef8c..d91b8c9 100644
--- a/framework/lib/src/gestures/long_press.dart
+++ b/framework/lib/src/gestures/long_press.dart
@@ -115,8 +115,7 @@
     this.globalPosition = Offset.zero,
     Offset? localPosition,
     this.kind,
-  }) : assert(globalPosition != null),
-       localPosition = localPosition ?? globalPosition;
+  }) : localPosition = localPosition ?? globalPosition;
 
   /// The global position at which the pointer contacted the screen.
   final Offset globalPosition;
@@ -142,8 +141,7 @@
   const LongPressStartDetails({
     this.globalPosition = Offset.zero,
     Offset? localPosition,
-  }) : assert(globalPosition != null),
-       localPosition = localPosition ?? globalPosition;
+  }) : localPosition = localPosition ?? globalPosition;
 
   /// The global position at which the pointer initially contacted the screen.
   final Offset globalPosition;
@@ -168,9 +166,7 @@
     Offset? localPosition,
     this.offsetFromOrigin = Offset.zero,
     Offset? localOffsetFromOrigin,
-  }) : assert(globalPosition != null),
-       assert(offsetFromOrigin != null),
-       localPosition = localPosition ?? globalPosition,
+  }) : localPosition = localPosition ?? globalPosition,
        localOffsetFromOrigin = localOffsetFromOrigin ?? offsetFromOrigin;
 
   /// The global position of the pointer when it triggered this update.
@@ -205,8 +201,7 @@
     this.globalPosition = Offset.zero,
     Offset? localPosition,
     this.velocity = Velocity.zero,
-  }) : assert(globalPosition != null),
-       localPosition = localPosition ?? globalPosition;
+  }) : localPosition = localPosition ?? globalPosition;
 
   /// The global position at which the pointer lifted from the screen.
   final Offset globalPosition;
@@ -247,17 +242,15 @@
   /// The [duration] argument can be used to overwrite the default duration
   /// after which the long press will be recognized.
   ///
+  /// {@macro flutter.gestures.tap.TapGestureRecognizer.allowedButtonsFilter}
+  ///
   /// {@macro flutter.gestures.GestureRecognizer.supportedDevices}
   LongPressGestureRecognizer({
     Duration? duration,
     super.postAcceptSlopTolerance = null,
-    @Deprecated(
-      'Migrate to supportedDevices. '
-      'This feature was deprecated after v2.3.0-1.0.pre.',
-    )
-    super.kind,
     super.supportedDevices,
     super.debugOwner,
+    super.allowedButtonsFilter = _defaultButtonAcceptBehavior,
   }) : super(
          deadline: duration ?? kLongPressTimeout,
        );
@@ -268,6 +261,12 @@
   // different set of buttons, the gesture is canceled.
   int? _initialButtons;
 
+  // Accept the input if, and only if, a single button is pressed.
+  static bool _defaultButtonAcceptBehavior(int buttons) =>
+      buttons == kPrimaryButton ||
+      buttons == kSecondaryButton ||
+      buttons == kTertiaryButton;
+
   /// Called when a pointer has contacted the screen at a particular location
   /// with a primary button, which might be the start of a long-press.
   ///
diff --git a/framework/lib/src/gestures/monodrag.dart b/framework/lib/src/gestures/monodrag.dart
index 5b87812..4930910 100644
--- a/framework/lib/src/gestures/monodrag.dart
+++ b/framework/lib/src/gestures/monodrag.dart
@@ -26,11 +26,13 @@
   accepted,
 }
 
+/// {@template flutter.gestures.monodrag.GestureDragEndCallback}
 /// Signature for when a pointer that was previously in contact with the screen
 /// and moving is no longer in contact with the screen.
 ///
 /// The velocity at which the pointer was moving when it stopped contacting
 /// the screen is available in the `details`.
+/// {@endtemplate}
 ///
 /// Used by [DragGestureRecognizer.onEnd].
 typedef GestureDragEndCallback = void Function(DragEndDetails details);
@@ -57,9 +59,8 @@
 /// consider using one of its subclasses to recognize specific types for drag
 /// gestures.
 ///
-/// [DragGestureRecognizer] competes on pointer events of [kPrimaryButton]
-/// only when it has at least one non-null callback. If it has no callbacks, it
-/// is a no-op.
+/// [DragGestureRecognizer] competes on pointer events only when it has at
+/// least one non-null callback. If it has no callbacks, it is a no-op.
 ///
 /// See also:
 ///
@@ -74,18 +75,17 @@
   /// {@macro flutter.gestures.GestureRecognizer.supportedDevices}
   DragGestureRecognizer({
     super.debugOwner,
-    @Deprecated(
-      'Migrate to supportedDevices. '
-      'This feature was deprecated after v2.3.0-1.0.pre.',
-    )
-    super.kind,
     this.dragStartBehavior = DragStartBehavior.start,
     this.velocityTrackerBuilder = _defaultBuilder,
     super.supportedDevices,
-  }) : assert(dragStartBehavior != null);
+    super.allowedButtonsFilter = _defaultButtonAcceptBehavior,
+  });
 
   static VelocityTracker _defaultBuilder(PointerEvent event) => VelocityTracker.withKind(event.kind);
 
+  // Accept the input if, and only if, [kPrimaryButton] is pressed.
+  static bool _defaultButtonAcceptBehavior(int buttons) => buttons == kPrimaryButton;
+
   /// Configure the behavior of offsets passed to [onStart].
   ///
   /// If set to [DragStartBehavior.start], the [onStart] callback will be called
@@ -120,12 +120,14 @@
   ///
   /// See also:
   ///
-  ///  * [kPrimaryButton], the button this callback responds to.
+  ///  * [allowedButtonsFilter], which decides which button will be allowed.
   ///  * [DragDownDetails], which is passed as an argument to this callback.
   GestureDragDownCallback? onDown;
 
+  /// {@template flutter.gestures.monodrag.DragGestureRecognizer.onStart}
   /// A pointer has contacted the screen with a primary button and has begun to
   /// move.
+  /// {@endtemplate}
   ///
   /// The position of the pointer is provided in the callback's `details`
   /// argument, which is a [DragStartDetails] object. The [dragStartBehavior]
@@ -133,32 +135,36 @@
   ///
   /// See also:
   ///
-  ///  * [kPrimaryButton], the button this callback responds to.
+  ///  * [allowedButtonsFilter], which decides which button will be allowed.
   ///  * [DragStartDetails], which is passed as an argument to this callback.
   GestureDragStartCallback? onStart;
 
+  /// {@template flutter.gestures.monodrag.DragGestureRecognizer.onUpdate}
   /// A pointer that is in contact with the screen with a primary button and
   /// moving has moved again.
+  /// {@endtemplate}
   ///
   /// The distance traveled by the pointer since the last update is provided in
   /// the callback's `details` argument, which is a [DragUpdateDetails] object.
   ///
   /// See also:
   ///
-  ///  * [kPrimaryButton], the button this callback responds to.
+  ///  * [allowedButtonsFilter], which decides which button will be allowed.
   ///  * [DragUpdateDetails], which is passed as an argument to this callback.
   GestureDragUpdateCallback? onUpdate;
 
+  /// {@template flutter.gestures.monodrag.DragGestureRecognizer.onEnd}
   /// A pointer that was previously in contact with the screen with a primary
   /// button and moving is no longer in contact with the screen and was moving
   /// at a specific velocity when it stopped contacting the screen.
+  /// {@endtemplate}
   ///
   /// The velocity is provided in the callback's `details` argument, which is a
   /// [DragEndDetails] object.
   ///
   /// See also:
   ///
-  ///  * [kPrimaryButton], the button this callback responds to.
+  ///  * [allowedButtonsFilter], which decides which button will be allowed.
   ///  * [DragEndDetails], which is passed as an argument to this callback.
   GestureDragEndCallback? onEnd;
 
@@ -166,10 +172,10 @@
   ///
   /// See also:
   ///
-  ///  * [kPrimaryButton], the button this callback responds to.
+  ///  * [allowedButtonsFilter], which decides which button will be allowed.
   GestureDragCancelCallback? onCancel;
 
-  /// The minimum distance an input pointer drag must have moved to
+  /// The minimum distance an input pointer drag must have moved
   /// to be considered a fling gesture.
   ///
   /// This value is typically compared with the distance traveled along the
@@ -243,18 +249,12 @@
   @override
   bool isPointerAllowed(PointerEvent event) {
     if (_initialButtons == null) {
-      switch (event.buttons) {
-        case kPrimaryButton:
-          if (onDown == null &&
-              onStart == null &&
-              onUpdate == null &&
-              onEnd == null &&
-              onCancel == null) {
-            return false;
-          }
-          break;
-        default:
-          return false;
+      if (onDown == null &&
+          onStart == null &&
+          onUpdate == null &&
+          onEnd == null &&
+          onCancel == null) {
+        return false;
       }
     } else {
       // There can be multiple drags simultaneously. Their effects are combined.
@@ -308,7 +308,6 @@
          event is PointerPanZoomStartEvent ||
          event is PointerPanZoomUpdateEvent)) {
       final VelocityTracker tracker = _velocityTrackers[event.pointer]!;
-      assert(tracker != null);
       if (event is PointerPanZoomStartEvent) {
         tracker.addPosition(event.timeStamp, Offset.zero);
       } else if (event is PointerPanZoomUpdateEvent) {
@@ -441,7 +440,6 @@
   }
 
   void _checkDown() {
-    assert(_initialButtons == kPrimaryButton);
     if (onDown != null) {
       final DragDownDetails details = DragDownDetails(
         globalPosition: _initialPosition.global,
@@ -452,7 +450,6 @@
   }
 
   void _checkStart(Duration timestamp, int pointer) {
-    assert(_initialButtons == kPrimaryButton);
     if (onStart != null) {
       final DragStartDetails details = DragStartDetails(
         sourceTimeStamp: timestamp,
@@ -471,7 +468,6 @@
     required Offset globalPosition,
     Offset? localPosition,
   }) {
-    assert(_initialButtons == kPrimaryButton);
     if (onUpdate != null) {
       final DragUpdateDetails details = DragUpdateDetails(
         sourceTimeStamp: sourceTimeStamp,
@@ -485,13 +481,11 @@
   }
 
   void _checkEnd(int pointer) {
-    assert(_initialButtons == kPrimaryButton);
     if (onEnd == null) {
       return;
     }
 
     final VelocityTracker tracker = _velocityTrackers[pointer]!;
-    assert(tracker != null);
 
     final DragEndDetails details;
     final String Function() debugReport;
@@ -522,7 +516,6 @@
   }
 
   void _checkCancel() {
-    assert(_initialButtons == kPrimaryButton);
     if (onCancel != null) {
       invokeCallback<void>('onCancel', onCancel!);
     }
@@ -556,12 +549,8 @@
   /// {@macro flutter.gestures.GestureRecognizer.supportedDevices}
   VerticalDragGestureRecognizer({
     super.debugOwner,
-    @Deprecated(
-      'Migrate to supportedDevices. '
-      'This feature was deprecated after v2.3.0-1.0.pre.',
-    )
-    super.kind,
     super.supportedDevices,
+    super.allowedButtonsFilter,
   });
 
   @override
@@ -602,12 +591,8 @@
   /// {@macro flutter.gestures.GestureRecognizer.supportedDevices}
   HorizontalDragGestureRecognizer({
     super.debugOwner,
-    @Deprecated(
-      'Migrate to supportedDevices. '
-      'This feature was deprecated after v2.3.0-1.0.pre.',
-    )
-    super.kind,
     super.supportedDevices,
+    super.allowedButtonsFilter,
   });
 
   @override
@@ -646,6 +631,7 @@
   PanGestureRecognizer({
     super.debugOwner,
     super.supportedDevices,
+    super.allowedButtonsFilter,
   });
 
   @override
diff --git a/framework/lib/src/gestures/multidrag.dart b/framework/lib/src/gestures/multidrag.dart
index 197fcfb..19d61a0 100644
--- a/framework/lib/src/gestures/multidrag.dart
+++ b/framework/lib/src/gestures/multidrag.dart
@@ -35,8 +35,7 @@
   ///
   /// The [initialPosition] argument must not be null.
   MultiDragPointerState(this.initialPosition, this.kind, this.gestureSettings)
-    : assert(initialPosition != null),
-      _velocityTracker = VelocityTracker.withKind(kind);
+    : _velocityTracker = VelocityTracker.withKind(kind);
 
   /// Device specific gesture configuration that should be preferred over
   /// framework constants.
@@ -133,7 +132,6 @@
   void _startDrag(Drag client) {
     assert(_arenaEntry != null);
     assert(_client == null);
-    assert(client != null);
     assert(pendingDelta != null);
     _client = client;
     final DragUpdateDetails details = DragUpdateDetails(
@@ -217,14 +215,13 @@
   /// {@macro flutter.gestures.GestureRecognizer.supportedDevices}
   MultiDragGestureRecognizer({
     required super.debugOwner,
-    @Deprecated(
-      'Migrate to supportedDevices. '
-      'This feature was deprecated after v2.3.0-1.0.pre.',
-    )
-    super.kind,
     super.supportedDevices,
+    super.allowedButtonsFilter = _defaultButtonAcceptBehavior,
   });
 
+  // Accept the input if, and only if, [kPrimaryButton] is pressed.
+  static bool _defaultButtonAcceptBehavior(int buttons) => buttons == kPrimaryButton;
+
   /// Called when this class recognizes the start of a drag gesture.
   ///
   /// The remaining notifications for this drag gesture are delivered to the
@@ -236,8 +233,6 @@
   @override
   void addAllowedPointer(PointerDownEvent event) {
     assert(_pointers != null);
-    assert(event.pointer != null);
-    assert(event.position != null);
     assert(!_pointers!.containsKey(event.pointer));
     final MultiDragPointerState state = createNewPointerState(event);
     _pointers![event.pointer] = state;
@@ -253,9 +248,6 @@
 
   void _handleEvent(PointerEvent event) {
     assert(_pointers != null);
-    assert(event.pointer != null);
-    assert(event.timeStamp != null);
-    assert(event.position != null);
     assert(_pointers!.containsKey(event.pointer));
     final MultiDragPointerState state = _pointers![event.pointer]!;
     if (event is PointerMoveEvent) {
@@ -292,7 +284,6 @@
   Drag? _startDrag(Offset initialPosition, int pointer) {
     assert(_pointers != null);
     final MultiDragPointerState state = _pointers![pointer]!;
-    assert(state != null);
     assert(state._pendingDelta != null);
     Drag? drag;
     if (onStart != null) {
@@ -311,7 +302,6 @@
     assert(_pointers != null);
     if (_pointers!.containsKey(pointer)) {
       final MultiDragPointerState state = _pointers![pointer]!;
-      assert(state != null);
       state.rejected();
       _removeState(pointer);
     } // else we already preemptively forgot about it (e.g. we got an up event)
@@ -376,12 +366,8 @@
   /// {@macro flutter.gestures.GestureRecognizer.supportedDevices}
   ImmediateMultiDragGestureRecognizer({
     super.debugOwner,
-    @Deprecated(
-      'Migrate to supportedDevices. '
-      'This feature was deprecated after v2.3.0-1.0.pre.',
-    )
-    super.kind,
     super.supportedDevices,
+    super.allowedButtonsFilter,
   });
 
   @override
@@ -433,12 +419,8 @@
   /// {@macro flutter.gestures.GestureRecognizer.supportedDevices}
   HorizontalMultiDragGestureRecognizer({
     super.debugOwner,
-    @Deprecated(
-      'Migrate to supportedDevices. '
-      'This feature was deprecated after v2.3.0-1.0.pre.',
-    )
-    super.kind,
     super.supportedDevices,
+    super.allowedButtonsFilter,
   });
 
   @override
@@ -490,12 +472,8 @@
   /// {@macro flutter.gestures.GestureRecognizer.supportedDevices}
   VerticalMultiDragGestureRecognizer({
     super.debugOwner,
-    @Deprecated(
-      'Migrate to supportedDevices. '
-      'This feature was deprecated after v2.3.0-1.0.pre.',
-    )
-    super.kind,
     super.supportedDevices,
+    super.allowedButtonsFilter,
   });
 
   @override
@@ -508,8 +486,7 @@
 }
 
 class _DelayedPointerState extends MultiDragPointerState {
-  _DelayedPointerState(super.initialPosition, Duration delay, super.kind, super.deviceGestureSettings)
-      : assert(delay != null) {
+  _DelayedPointerState(super.initialPosition, Duration delay, super.kind, super.deviceGestureSettings) {
     _timer = Timer(delay, _delayPassed);
   }
 
@@ -600,13 +577,9 @@
   DelayedMultiDragGestureRecognizer({
     this.delay = kLongPressTimeout,
     super.debugOwner,
-    @Deprecated(
-      'Migrate to supportedDevices. '
-      'This feature was deprecated after v2.3.0-1.0.pre.',
-    )
-    super.kind,
     super.supportedDevices,
-  }) : assert(delay != null);
+    super.allowedButtonsFilter,
+  });
 
   /// The amount of time the pointer must remain in the same place for the drag
   /// to be recognized.
diff --git a/framework/lib/src/gestures/multitap.dart b/framework/lib/src/gestures/multitap.dart
index 426e00b..b4d6c84 100644
--- a/framework/lib/src/gestures/multitap.dart
+++ b/framework/lib/src/gestures/multitap.dart
@@ -43,8 +43,7 @@
 /// CountdownZoned tracks whether the specified duration has elapsed since
 /// creation, honoring [Zone].
 class _CountdownZoned {
-  _CountdownZoned({ required Duration duration })
-       : assert(duration != null) {
+  _CountdownZoned({ required Duration duration }) {
     Timer(duration, _onTimeout);
   }
 
@@ -65,10 +64,7 @@
     required this.entry,
     required Duration doubleTapMinTime,
     required this.gestureSettings,
-  }) : assert(doubleTapMinTime != null),
-       assert(event != null),
-       assert(event.buttons != null),
-       pointer = event.pointer,
+  }) : pointer = event.pointer,
        _initialGlobalPosition = event.position,
        initialButtons = event.buttons,
        _doubleTapMinTimeCountdown = _CountdownZoned(duration: doubleTapMinTime);
@@ -113,8 +109,8 @@
 /// Recognizes when the user has tapped the screen at the same location twice in
 /// quick succession.
 ///
-/// [DoubleTapGestureRecognizer] competes on pointer events of [kPrimaryButton]
-/// only when it has a non-null callback. If it has no callbacks, it is a no-op.
+/// [DoubleTapGestureRecognizer] competes on pointer events when it
+/// has a non-null callback. If it has no callbacks, it is a no-op.
 ///
 class DoubleTapGestureRecognizer extends GestureRecognizer {
   /// Create a gesture recognizer for double taps.
@@ -122,14 +118,14 @@
   /// {@macro flutter.gestures.GestureRecognizer.supportedDevices}
   DoubleTapGestureRecognizer({
     super.debugOwner,
-    @Deprecated(
-      'Migrate to supportedDevices. '
-      'This feature was deprecated after v2.3.0-1.0.pre.',
-    )
-    super.kind,
     super.supportedDevices,
+    super.allowedButtonsFilter = _defaultButtonAcceptBehavior,
   });
 
+  // The default value for [allowedButtonsFilter].
+  // Accept the input if, and only if, [kPrimaryButton] is pressed.
+  static bool _defaultButtonAcceptBehavior(int buttons) => buttons == kPrimaryButton;
+
   // Implementation notes:
   //
   // The double tap recognizer can be in one of four states. There's no
@@ -165,7 +161,7 @@
   ///
   /// See also:
   ///
-  ///  * [kPrimaryButton], the button this callback responds to.
+  ///  * [allowedButtonsFilter], which decides which button will be allowed.
   ///  * [TapDownDetails], which is passed as an argument to this callback.
   ///  * [GestureDetector.onDoubleTapDown], which exposes this callback.
   GestureTapDownCallback? onDoubleTapDown;
@@ -178,7 +174,7 @@
   ///
   /// See also:
   ///
-  ///  * [kPrimaryButton], the button this callback responds to.
+  ///  * [allowedButtonsFilter], which decides which button will be allowed.
   ///  * [GestureDetector.onDoubleTap], which exposes this callback.
   GestureDoubleTapCallback? onDoubleTap;
 
@@ -192,7 +188,7 @@
   ///
   /// See also:
   ///
-  ///  * [kPrimaryButton], the button this callback responds to.
+  ///  * [allowedButtonsFilter], which decides which button will be allowed.
   ///  * [GestureDetector.onDoubleTapCancel], which exposes this callback.
   GestureTapCancelCallback? onDoubleTapCancel;
 
@@ -203,19 +199,19 @@
   @override
   bool isPointerAllowed(PointerDownEvent event) {
     if (_firstTap == null) {
-      switch (event.buttons) {
-        case kPrimaryButton:
-          if (onDoubleTapDown == null &&
-              onDoubleTap == null &&
-              onDoubleTapCancel == null) {
-            return false;
-          }
-          break;
-        default:
-          return false;
+      if (onDoubleTapDown == null &&
+          onDoubleTap == null &&
+          onDoubleTapCancel == null) {
+        return false;
       }
     }
-    return super.isPointerAllowed(event);
+
+    // If second tap is not allowed, reset the state.
+    final bool isPointerAllowed = super.isPointerAllowed(event);
+    if (isPointerAllowed == false) {
+      _reset();
+    }
+    return isPointerAllowed;
   }
 
   @override
@@ -367,7 +363,6 @@
   }
 
   void _checkUp(int buttons) {
-    assert(buttons == kPrimaryButton);
     if (onDoubleTap != null) {
       invokeCallback<void>('onDoubleTap', onDoubleTap!);
     }
@@ -486,12 +481,8 @@
   MultiTapGestureRecognizer({
     this.longTapDelay = Duration.zero,
     super.debugOwner,
-    @Deprecated(
-      'Migrate to supportedDevices. '
-      'This feature was deprecated after v2.3.0-1.0.pre.',
-    )
-    super.kind,
     super.supportedDevices,
+    super.allowedButtonsFilter,
   });
 
   /// A pointer that might cause a tap has contacted the screen at a particular
@@ -679,8 +670,7 @@
   /// The `count` argument must be greater than zero.
   SerialTapCancelDetails({
     this.count = 1,
-  }) : assert(count != null),
-       assert(count > 0);
+  }) : assert(count > 0);
 
   /// The number of consecutive taps that were in progress when the gesture was
   /// interrupted.
@@ -813,6 +803,7 @@
   SerialTapGestureRecognizer({
     super.debugOwner,
     super.supportedDevices,
+    super.allowedButtonsFilter,
   });
 
   /// A pointer has contacted the screen at a particular location, which might
diff --git a/framework/lib/src/gestures/pointer_signal_resolver.dart b/framework/lib/src/gestures/pointer_signal_resolver.dart
index 6cf84e0..6ffcf76 100644
--- a/framework/lib/src/gestures/pointer_signal_resolver.dart
+++ b/framework/lib/src/gestures/pointer_signal_resolver.dart
@@ -64,8 +64,6 @@
   /// See the documentation for the [PointerSignalResolver] class on when and
   /// how this method should be used.
   void register(PointerSignalEvent event, PointerSignalResolvedCallback callback) {
-    assert(event != null);
-    assert(callback != null);
     assert(_currentEvent == null || _isSameEvent(_currentEvent!, event));
     if (_firstRegisteredCallback != null) {
       return;
diff --git a/framework/lib/src/gestures/recognizer.dart b/framework/lib/src/gestures/recognizer.dart
index 243c86a..0a30fbf 100644
--- a/framework/lib/src/gestures/recognizer.dart
+++ b/framework/lib/src/gestures/recognizer.dart
@@ -48,6 +48,11 @@
   start,
 }
 
+/// Signature for `allowedButtonsFilter` in [GestureRecognizer].
+/// Used to filter the input buttons of incoming pointer events.
+/// The parameter `buttons` comes from [PointerEvent.buttons].
+typedef AllowedButtonsFilter = bool Function(int buttons);
+
 /// The base class that all gesture recognizers inherit from.
 ///
 /// Provides a basic API that can be used by classes that work with
@@ -73,14 +78,10 @@
   /// {@endtemplate}
   GestureRecognizer({
     this.debugOwner,
-    @Deprecated(
-      'Migrate to supportedDevices. '
-      'This feature was deprecated after v2.3.0-1.0.pre.',
-    )
-    PointerDeviceKind? kind,
     Set<PointerDeviceKind>? supportedDevices,
-  }) : assert(kind == null || supportedDevices == null),
-       _supportedDevices = kind == null ? supportedDevices : <PointerDeviceKind>{ kind };
+    AllowedButtonsFilter? allowedButtonsFilter,
+  }) : _supportedDevices = supportedDevices,
+       _allowedButtonsFilter = allowedButtonsFilter ?? _defaultButtonAcceptBehavior;
 
   /// The recognizer's owner.
   ///
@@ -98,6 +99,29 @@
   /// tracked and recognized.
   final Set<PointerDeviceKind>? _supportedDevices;
 
+  /// {@template flutter.gestures.multidrag._allowedButtonsFilter}
+  /// Called when interaction starts. This limits the dragging behavior
+  /// for custom clicks (such as scroll click). Its parameter comes
+  /// from [PointerEvent.buttons].
+  ///
+  /// Due to how [kPrimaryButton], [kSecondaryButton], etc., use integers,
+  /// bitwise operations can help filter how buttons are pressed.
+  /// For example, if someone simultaneously presses the primary and secondary
+  /// buttons, the default behavior will return false. The following code
+  /// accepts any button press with primary:
+  /// `(int buttons) => buttons & kPrimaryButton != 0`.
+  ///
+  /// When value is `(int buttons) => false`, allow no interactions.
+  /// When value is `(int buttons) => true`, allow all interactions.
+  ///
+  /// Defaults to all buttons.
+  /// {@endtemplate}
+  final AllowedButtonsFilter _allowedButtonsFilter;
+
+  // The default value for [allowedButtonsFilter].
+  // Accept any input.
+  static bool _defaultButtonAcceptBehavior(int buttons) => true;
+
   /// Holds a mapping between pointer IDs and the kind of devices they are
   /// coming from.
   final Map<int, PointerDeviceKind> _pointerToKind = <int, PointerDeviceKind>{};
@@ -185,9 +209,9 @@
   /// Checks whether or not a pointer is allowed to be tracked by this recognizer.
   @protected
   bool isPointerAllowed(PointerDownEvent event) {
-    // Currently, it only checks for device kind. But in the future we could check
-    // for other things e.g. mouse button.
-    return _supportedDevices == null || _supportedDevices!.contains(event.kind);
+    return (_supportedDevices == null ||
+            _supportedDevices!.contains(event.kind)) &&
+        _allowedButtonsFilter(event.buttons);
   }
 
   /// Handles a pointer pan/zoom being added that's not allowed by this recognizer.
@@ -237,7 +261,6 @@
   @protected
   @pragma('vm:notify-debugger-on-exception')
   T? invokeCallback<T>(String name, RecognizerCallback<T> callback, { String Function()? debugReport }) {
-    assert(callback != null);
     T? result;
     try {
       assert(() {
@@ -292,12 +315,8 @@
   /// {@macro flutter.gestures.GestureRecognizer.supportedDevices}
   OneSequenceGestureRecognizer({
     super.debugOwner,
-    @Deprecated(
-      'Migrate to supportedDevices. '
-      'This feature was deprecated after v2.3.0-1.0.pre.',
-    )
-    super.kind,
     super.supportedDevices,
+    super.allowedButtonsFilter,
   });
 
   final Map<int, GestureArenaEntry> _entries = <int, GestureArenaEntry>{};
@@ -427,7 +446,8 @@
   void startTrackingPointer(int pointer, [Matrix4? transform]) {
     GestureBinding.instance.pointerRouter.addRoute(pointer, handleEvent, transform);
     _trackedPointers.add(pointer);
-    assert(!_entries.containsValue(pointer));
+    // TODO(goderbauer): Enable assert after recognizers properly clean up their defunct `_entries`, see https://github.com/flutter/flutter/issues/117356.
+    // assert(!_entries.containsKey(pointer));
     _entries[pointer] = _addPointerToArena(pointer);
   }
 
@@ -504,12 +524,8 @@
     this.preAcceptSlopTolerance = kTouchSlop,
     this.postAcceptSlopTolerance = kTouchSlop,
     super.debugOwner,
-    @Deprecated(
-      'Migrate to supportedDevices. '
-      'This feature was deprecated after v2.3.0-1.0.pre.',
-    )
-    super.kind,
     super.supportedDevices,
+    super.allowedButtonsFilter,
   }) : assert(
          preAcceptSlopTolerance == null || preAcceptSlopTolerance >= 0,
          'The preAcceptSlopTolerance must be positive or null',
diff --git a/framework/lib/src/gestures/scale.dart b/framework/lib/src/gestures/scale.dart
index 8ee9c51..8d7e0ab 100644
--- a/framework/lib/src/gestures/scale.dart
+++ b/framework/lib/src/gestures/scale.dart
@@ -15,6 +15,18 @@
 export 'recognizer.dart' show DragStartBehavior;
 export 'velocity_tracker.dart' show Velocity;
 
+/// The default conversion factor when treating mouse scrolling as scaling.
+///
+/// The value was arbitrarily chosen to feel natural for most mousewheels on
+/// all supported platforms.
+const double kDefaultMouseScrollToScaleFactor = 200;
+
+/// The default conversion factor when treating trackpad scrolling as scaling.
+///
+/// This factor matches the default [kDefaultMouseScrollToScaleFactor] of 200 to
+/// feel natural for most trackpads, and the convention that scrolling up means
+/// zooming in.
+const Offset kDefaultTrackpadScrollToScaleFactor = Offset(0, -1/kDefaultMouseScrollToScaleFactor);
 
 /// The possible states of a [ScaleGestureRecognizer].
 enum _ScaleState {
@@ -36,17 +48,49 @@
 }
 
 class _PointerPanZoomData {
-  _PointerPanZoomData({
-    required this.focalPoint,
-    required this.scale,
-    required this.rotation
-  });
-  Offset focalPoint;
-  double scale;
-  double rotation;
+  _PointerPanZoomData.fromStartEvent(
+    this.parent,
+    PointerPanZoomStartEvent event
+  ) : _position = event.position,
+      _pan = Offset.zero,
+      _scale = 1,
+      _rotation = 0;
+
+  _PointerPanZoomData.fromUpdateEvent(
+    this.parent,
+    PointerPanZoomUpdateEvent event
+  ) : _position = event.position,
+      _pan = event.pan,
+      _scale = event.scale,
+      _rotation = event.rotation;
+
+  final ScaleGestureRecognizer parent;
+  final Offset _position;
+  final Offset _pan;
+  final double _scale;
+  final double _rotation;
+
+  Offset get focalPoint {
+    if (parent.trackpadScrollCausesScale) {
+      return _position;
+    }
+    return _position + _pan;
+  }
+
+  double get scale {
+    if (parent.trackpadScrollCausesScale) {
+      return _scale * math.exp(
+        (_pan.dx * parent.trackpadScrollToScaleFactor.dx) +
+        (_pan.dy * parent.trackpadScrollToScaleFactor.dy)
+      );
+    }
+    return _scale;
+  }
+
+  double get rotation => _rotation;
 
   @override
-  String toString() => '_PointerPanZoomData(focalPoint: $focalPoint, scale: $scale, angle: $rotation)';
+  String toString() => '_PointerPanZoomData(parent: $parent, _position: $_position, _pan: $_pan, _scale: $_scale, _rotation: $_rotation)';
 }
 
 /// Details for [GestureScaleStartCallback].
@@ -54,8 +98,11 @@
   /// Creates details for [GestureScaleStartCallback].
   ///
   /// The [focalPoint] argument must not be null.
-  ScaleStartDetails({ this.focalPoint = Offset.zero, Offset? localFocalPoint, this.pointerCount = 0 })
-    : assert(focalPoint != null), localFocalPoint = localFocalPoint ?? focalPoint;
+  ScaleStartDetails({
+    this.focalPoint = Offset.zero,
+    Offset? localFocalPoint,
+    this.pointerCount = 0,
+  }) : localFocalPoint = localFocalPoint ?? focalPoint;
 
   /// The initial focal point of the pointers in contact with the screen.
   ///
@@ -104,12 +151,9 @@
     this.rotation = 0.0,
     this.pointerCount = 0,
     this.focalPointDelta = Offset.zero,
-  }) : assert(focalPoint != null),
-       assert(focalPointDelta != null),
-       assert(scale != null && scale >= 0.0),
-       assert(horizontalScale != null && horizontalScale >= 0.0),
-       assert(verticalScale != null && verticalScale >= 0.0),
-       assert(rotation != null),
+  }) : assert(scale >= 0.0),
+       assert(horizontalScale >= 0.0),
+       assert(verticalScale >= 0.0),
        localFocalPoint = localFocalPoint ?? focalPoint;
 
   /// The amount the gesture's focal point has moved in the coordinate space of
@@ -201,12 +245,14 @@
   /// Creates details for [GestureScaleEndCallback].
   ///
   /// The [velocity] argument must not be null.
-  ScaleEndDetails({ this.velocity = Velocity.zero, this.pointerCount = 0 })
-    : assert(velocity != null);
+  ScaleEndDetails({ this.velocity = Velocity.zero, this.scaleVelocity = 0, this.pointerCount = 0 });
 
   /// The velocity of the last pointer to be lifted off of the screen.
   final Velocity velocity;
 
+  /// The final velocity of the scale factor reported by the gesture.
+  final double scaleVelocity;
+
   /// The number of pointers being tracked by the gesture recognizer.
   ///
   /// Typically this is the number of fingers being used to pan the widget using the gesture
@@ -214,7 +260,7 @@
   final int pointerCount;
 
   @override
-  String toString() => 'ScaleEndDetails(velocity: $velocity, pointerCount: $pointerCount)';
+  String toString() => 'ScaleEndDetails(velocity: $velocity, scaleVelocity: $scaleVelocity, pointerCount: $pointerCount)';
 }
 
 /// Signature for when the pointers in contact with the screen have established
@@ -229,7 +275,6 @@
 typedef GestureScaleEndCallback = void Function(ScaleEndDetails details);
 
 bool _isFlingGesture(Velocity velocity) {
-  assert(velocity != null);
   final double speedSquared = velocity.pixelsPerSecond.distanceSquared;
   return speedSquared > kMinFlingVelocity * kMinFlingVelocity;
 }
@@ -249,9 +294,7 @@
     this.pointerStartId = 0,
     this.pointerEndLocation = Offset.zero,
     this.pointerEndId = 1,
-  }) : assert(pointerStartLocation != null && pointerEndLocation != null),
-       assert(pointerStartId != null && pointerEndId != null),
-       assert(pointerStartId != pointerEndId);
+  }) : assert(pointerStartId != pointerEndId);
 
   // The location and the id of the pointer that marks the start of the line.
   final Offset pointerStartLocation;
@@ -268,23 +311,22 @@
 ///
 /// [ScaleGestureRecognizer] tracks the pointers in contact with the screen and
 /// calculates their focal point, indicated scale, and rotation. When a focal
-/// pointer is established, the recognizer calls [onStart]. As the focal point,
-/// scale, rotation change, the recognizer calls [onUpdate]. When the pointers
-/// are no longer in contact with the screen, the recognizer calls [onEnd].
+/// point is established, the recognizer calls [onStart]. As the focal point,
+/// scale, and rotation change, the recognizer calls [onUpdate]. When the
+/// pointers are no longer in contact with the screen, the recognizer calls
+/// [onEnd].
 class ScaleGestureRecognizer extends OneSequenceGestureRecognizer {
   /// Create a gesture recognizer for interactions intended for scaling content.
   ///
   /// {@macro flutter.gestures.GestureRecognizer.supportedDevices}
   ScaleGestureRecognizer({
     super.debugOwner,
-    @Deprecated(
-      'Migrate to supportedDevices. '
-      'This feature was deprecated after v2.3.0-1.0.pre.',
-    )
-    super.kind,
     super.supportedDevices,
+    super.allowedButtonsFilter,
     this.dragStartBehavior = DragStartBehavior.down,
-  }) : assert(dragStartBehavior != null);
+    this.trackpadScrollCausesScale = false,
+    this.trackpadScrollToScaleFactor = kDefaultTrackpadScrollToScaleFactor,
+  });
 
   /// Determines what point is used as the starting point in all calculations
   /// involving this gesture.
@@ -331,6 +373,26 @@
 
   Matrix4? _lastTransform;
 
+  /// {@template flutter.gestures.scale.trackpadScrollCausesScale}
+  /// Whether scrolling up/down on a trackpad should cause scaling instead of
+  /// panning.
+  ///
+  /// Defaults to false.
+  /// {@endtemplate}
+  bool trackpadScrollCausesScale;
+
+  /// {@template flutter.gestures.scale.trackpadScrollToScaleFactor}
+  /// A factor to control the direction and magnitude of scale when converting
+  /// trackpad scrolling.
+  ///
+  /// Incoming trackpad pan offsets will be divided by this factor to get scale
+  /// values. Increasing this offset will reduce the amount of scaling caused by
+  /// a fixed amount of trackpad scrolling.
+  ///
+  /// Defaults to [kDefaultTrackpadScrollToScaleFactor].
+  /// {@endtemplate}
+  Offset trackpadScrollToScaleFactor;
+
   late Offset _initialFocalPoint;
   Offset? _currentFocalPoint;
   late double _initialSpan;
@@ -345,6 +407,7 @@
   final Map<int, Offset> _pointerLocations = <int, Offset>{};
   final List<int> _pointerQueue = <int>[]; // A queue to sort pointers in order of entrance
   final Map<int, VelocityTracker> _velocityTrackers = <int, VelocityTracker>{};
+  VelocityTracker? _scaleVelocityTracker;
   late Offset _delta;
   final Map<int, _PointerPanZoomData> _pointerPanZooms = <int, _PointerPanZoomData>{};
   double _initialPanZoomScaleFactor = 1;
@@ -465,23 +528,16 @@
       _lastTransform = event.transform;
     } else if (event is PointerPanZoomStartEvent) {
       assert(_pointerPanZooms[event.pointer] == null);
-      _pointerPanZooms[event.pointer] = _PointerPanZoomData(
-        focalPoint: event.position,
-        scale: 1,
-        rotation: 0
-      );
+      _pointerPanZooms[event.pointer] = _PointerPanZoomData.fromStartEvent(this, event);
       didChangeConfiguration = true;
       shouldStartIfAccepted = true;
+      _lastTransform = event.transform;
     } else if (event is PointerPanZoomUpdateEvent) {
       assert(_pointerPanZooms[event.pointer] != null);
-      if (!event.synthesized) {
+      if (!event.synthesized && !trackpadScrollCausesScale) {
         _velocityTrackers[event.pointer]!.addPosition(event.timeStamp, event.pan);
       }
-      _pointerPanZooms[event.pointer] = _PointerPanZoomData(
-        focalPoint: event.position + event.pan,
-        scale: event.scale,
-        rotation: event.rotation
-      );
+      _pointerPanZooms[event.pointer] = _PointerPanZoomData.fromUpdateEvent(this, event);
       _lastTransform = event.transform;
       shouldStartIfAccepted = true;
     } else if (event is PointerPanZoomEndEvent) {
@@ -494,7 +550,7 @@
     _update();
 
     if (!didChangeConfiguration || _reconfigure(event.pointer)) {
-      _advanceStateMachine(shouldStartIfAccepted, event.kind);
+      _advanceStateMachine(shouldStartIfAccepted, event);
     }
     stopTrackingIfPointerNoLongerDown(event);
   }
@@ -606,18 +662,20 @@
           if (pixelsPerSecond.distanceSquared > kMaxFlingVelocity * kMaxFlingVelocity) {
             velocity = Velocity(pixelsPerSecond: (pixelsPerSecond / pixelsPerSecond.distance) * kMaxFlingVelocity);
           }
-          invokeCallback<void>('onEnd', () => onEnd!(ScaleEndDetails(velocity: velocity, pointerCount: _pointerCount)));
+          invokeCallback<void>('onEnd', () => onEnd!(ScaleEndDetails(velocity: velocity, scaleVelocity: _scaleVelocityTracker?.getVelocity().pixelsPerSecond.dx ?? -1, pointerCount: _pointerCount)));
         } else {
-          invokeCallback<void>('onEnd', () => onEnd!(ScaleEndDetails(pointerCount: _pointerCount)));
+          invokeCallback<void>('onEnd', () => onEnd!(ScaleEndDetails(scaleVelocity: _scaleVelocityTracker?.getVelocity().pixelsPerSecond.dx ?? -1, pointerCount: _pointerCount)));
         }
       }
       _state = _ScaleState.accepted;
+      _scaleVelocityTracker = VelocityTracker.withKind(PointerDeviceKind.touch); // arbitrary PointerDeviceKind
       return false;
     }
+    _scaleVelocityTracker = VelocityTracker.withKind(PointerDeviceKind.touch); // arbitrary PointerDeviceKind
     return true;
   }
 
-  void _advanceStateMachine(bool shouldStartIfAccepted, PointerDeviceKind pointerDeviceKind) {
+  void _advanceStateMachine(bool shouldStartIfAccepted, PointerEvent event) {
     if (_state == _ScaleState.ready) {
       _state = _ScaleState.possible;
     }
@@ -625,7 +683,7 @@
     if (_state == _ScaleState.possible) {
       final double spanDelta = (_currentSpan - _initialSpan).abs();
       final double focalPointDelta = (_currentFocalPoint! - _initialFocalPoint).distance;
-      if (spanDelta > computeScaleSlop(pointerDeviceKind) || focalPointDelta > computePanSlop(pointerDeviceKind, gestureSettings) || math.max(_scaleFactor / _pointerScaleFactor, _pointerScaleFactor / _scaleFactor) > 1.05) {
+      if (spanDelta > computeScaleSlop(event.kind) || focalPointDelta > computePanSlop(event.kind, gestureSettings) || math.max(_scaleFactor / _pointerScaleFactor, _pointerScaleFactor / _scaleFactor) > 1.05) {
         resolve(GestureDisposition.accepted);
       }
     } else if (_state.index >= _ScaleState.accepted.index) {
@@ -637,19 +695,22 @@
       _dispatchOnStartCallbackIfNeeded();
     }
 
-    if (_state == _ScaleState.started && onUpdate != null) {
-      invokeCallback<void>('onUpdate', () {
-        onUpdate!(ScaleUpdateDetails(
-          scale: _scaleFactor,
-          horizontalScale: _horizontalScaleFactor,
-          verticalScale: _verticalScaleFactor,
-          focalPoint: _currentFocalPoint!,
-          localFocalPoint: _localFocalPoint,
-          rotation: _computeRotationFactor(),
-          pointerCount: _pointerCount,
-          focalPointDelta: _delta,
-        ));
-      });
+    if (_state == _ScaleState.started) {
+      _scaleVelocityTracker?.addPosition(event.timeStamp, Offset(_scaleFactor, 0));
+      if (onUpdate != null) {
+        invokeCallback<void>('onUpdate', () {
+          onUpdate!(ScaleUpdateDetails(
+            scale: _scaleFactor,
+            horizontalScale: _horizontalScaleFactor,
+            verticalScale: _verticalScaleFactor,
+            focalPoint: _currentFocalPoint!,
+            localFocalPoint: _localFocalPoint,
+            rotation: _computeRotationFactor(),
+            pointerCount: _pointerCount,
+            focalPointDelta: _delta,
+          ));
+        });
+      }
     }
   }
 
diff --git a/framework/lib/src/gestures/tap.dart b/framework/lib/src/gestures/tap.dart
index b58dfff..efa3a05 100644
--- a/framework/lib/src/gestures/tap.dart
+++ b/framework/lib/src/gestures/tap.dart
@@ -32,8 +32,7 @@
     this.globalPosition = Offset.zero,
     Offset? localPosition,
     this.kind,
-  }) : assert(globalPosition != null),
-       localPosition = localPosition ?? globalPosition;
+  }) : localPosition = localPosition ?? globalPosition;
 
   /// The global position at which the pointer contacted the screen.
   final Offset globalPosition;
@@ -45,11 +44,13 @@
   final Offset localPosition;
 }
 
+/// {@template flutter.gestures.tap.GestureTapDownCallback}
 /// Signature for when a pointer that might cause a tap has contacted the
 /// screen.
 ///
 /// The position at which the pointer contacted the screen is available in the
 /// `details`.
+/// {@endtemplate}
 ///
 /// See also:
 ///
@@ -69,8 +70,7 @@
     required this.kind,
     this.globalPosition = Offset.zero,
     Offset? localPosition,
-  }) : assert(globalPosition != null),
-       localPosition = localPosition ?? globalPosition;
+  }) : localPosition = localPosition ?? globalPosition;
 
   /// The global position at which the pointer contacted the screen.
   final Offset globalPosition;
@@ -82,11 +82,13 @@
   final PointerDeviceKind kind;
 }
 
+/// {@template flutter.gestures.tap.GestureTapUpCallback}
 /// Signature for when a pointer that will trigger a tap has stopped contacting
 /// the screen.
 ///
 /// The position at which the pointer stopped contacting the screen is available
 /// in the `details`.
+/// {@endtemplate}
 ///
 /// See also:
 ///
@@ -145,7 +147,11 @@
   /// Creates a tap gesture recognizer.
   ///
   /// {@macro flutter.gestures.GestureRecognizer.supportedDevices}
-  BaseTapGestureRecognizer({ super.debugOwner, super.supportedDevices })
+  BaseTapGestureRecognizer({
+    super.debugOwner,
+    super.supportedDevices,
+    super.allowedButtonsFilter,
+  })
     : super(deadline: kPressTimeout);
 
   bool _sentTapDown = false;
@@ -198,7 +204,6 @@
 
   @override
   void addAllowedPointer(PointerDownEvent event) {
-    assert(event != null);
     if (state == GestureRecognizerState.ready) {
       // If there is no result in the previous gesture arena,
       // we ignore them and prepare to accept a new pointer.
@@ -216,7 +221,7 @@
     if (_down != null) {
       // This happens when this tap gesture has been rejected while the pointer
       // is down (i.e. due to movement), when another allowed pointer is added,
-      // in which case all pointers are simply ignored. The `_down` being null
+      // in which case all pointers are ignored. The `_down` being null
       // means that _reset() has been called, since it is always set at the
       // first allowed down event and will not be cleared except for reset(),
       super.addAllowedPointer(event);
@@ -350,6 +355,16 @@
 /// one non-null `onTertiaryTap*` callback. If it has no callbacks, it is a
 /// no-op.
 ///
+/// {@template flutter.gestures.tap.TapGestureRecognizer.allowedButtonsFilter}
+/// The [allowedButtonsFilter] argument only gives this recognizer the
+/// ability to limit the buttons it accepts. It does not provide the
+/// ability to recognize any buttons beyond the ones it already accepts:
+/// kPrimaryButton, kSecondaryButton or kTertiaryButton. Therefore, a
+/// combined value of `kPrimaryButton & kSecondaryButton` would be ignored,
+/// but `kPrimaryButton | kSecondaryButton` would be allowed, as long as
+/// only one of them is selected at a time.
+/// {@endtemplate}
+///
 /// See also:
 ///
 ///  * [GestureDetector.onTap], which uses this recognizer.
@@ -358,10 +373,16 @@
   /// Creates a tap gesture recognizer.
   ///
   /// {@macro flutter.gestures.GestureRecognizer.supportedDevices}
-  TapGestureRecognizer({ super.debugOwner, super.supportedDevices });
+  TapGestureRecognizer({
+    super.debugOwner,
+    super.supportedDevices,
+    super.allowedButtonsFilter,
+  });
 
+  /// {@template flutter.gestures.tap.TapGestureRecognizer.onTapDown}
   /// A pointer has contacted the screen at a particular location with a primary
   /// button, which might be the start of a tap.
+  /// {@endtemplate}
   ///
   /// This triggers after the down event, once a short timeout ([deadline]) has
   /// elapsed, or once the gestures has won the arena, whichever comes first.
@@ -378,8 +399,10 @@
   ///  * [GestureDetector.onTapDown], which exposes this callback.
   GestureTapDownCallback? onTapDown;
 
+  /// {@template flutter.gestures.tap.TapGestureRecognizer.onTapUp}
   /// A pointer has stopped contacting the screen at a particular location,
   /// which is recognized as a tap of a primary button.
+  /// {@endtemplate}
   ///
   /// This triggers on the up event, if the recognizer wins the arena with it
   /// or has previously won, immediately followed by [onTap].
@@ -411,8 +434,10 @@
   ///  * [GestureDetector.onTap], which exposes this callback.
   GestureTapCallback? onTap;
 
+  /// {@template flutter.gestures.tap.TapGestureRecognizer.onTapCancel}
   /// A pointer that previously triggered [onTapDown] will not end up causing
   /// a tap.
+  /// {@endtemplate}
   ///
   /// This triggers once the gesture loses the arena if [onTapDown] has
   /// previously been triggered.
@@ -428,8 +453,10 @@
   ///  * [GestureDetector.onTapCancel], which exposes this callback.
   GestureTapCancelCallback? onTapCancel;
 
+  /// {@template flutter.gestures.tap.TapGestureRecognizer.onSecondaryTap}
   /// A pointer has stopped contacting the screen, which is recognized as a tap
   /// of a secondary button.
+  /// {@endtemplate}
   ///
   /// This triggers on the up event, if the recognizer wins the arena with it or
   /// has previously won, immediately following [onSecondaryTapUp].
@@ -444,8 +471,10 @@
   ///  * [GestureDetector.onSecondaryTap], which exposes this callback.
   GestureTapCallback? onSecondaryTap;
 
+  /// {@template flutter.gestures.tap.TapGestureRecognizer.onSecondaryTapDown}
   /// A pointer has contacted the screen at a particular location with a
   /// secondary button, which might be the start of a secondary tap.
+  /// {@endtemplate}
   ///
   /// This triggers after the down event, once a short timeout ([deadline]) has
   /// elapsed, or once the gestures has won the arena, whichever comes first.
@@ -462,8 +491,10 @@
   ///  * [GestureDetector.onSecondaryTapDown], which exposes this callback.
   GestureTapDownCallback? onSecondaryTapDown;
 
+  /// {@template flutter.gestures.tap.TapGestureRecognizer.onSecondaryTapUp}
   /// A pointer has stopped contacting the screen at a particular location,
   /// which is recognized as a tap of a secondary button.
+  /// {@endtemplate}
   ///
   /// This triggers on the up event if the recognizer wins the arena with it
   /// or has previously won.
@@ -482,8 +513,10 @@
   ///  * [GestureDetector.onSecondaryTapUp], which exposes this callback.
   GestureTapUpCallback? onSecondaryTapUp;
 
+  /// {@template flutter.gestures.tap.TapGestureRecognizer.onSecondaryTapCancel}
   /// A pointer that previously triggered [onSecondaryTapDown] will not end up
   /// causing a tap.
+  /// {@endtemplate}
   ///
   /// This triggers once the gesture loses the arena if [onSecondaryTapDown]
   /// has previously been triggered.
diff --git a/framework/lib/src/gestures/velocity_tracker.dart b/framework/lib/src/gestures/velocity_tracker.dart
index 1e61fda..973e7cd 100644
--- a/framework/lib/src/gestures/velocity_tracker.dart
+++ b/framework/lib/src/gestures/velocity_tracker.dart
@@ -18,7 +18,7 @@
   /// The [pixelsPerSecond] argument must not be null.
   const Velocity({
     required this.pixelsPerSecond,
-  }) : assert(pixelsPerSecond != null);
+  });
 
   /// A velocity that isn't moving at all.
   static const Velocity zero = Velocity(pixelsPerSecond: Offset.zero);
@@ -50,8 +50,8 @@
   /// If the magnitude of this Velocity is within the specified bounds then
   /// just return this.
   Velocity clampMagnitude(double minValue, double maxValue) {
-    assert(minValue != null && minValue >= 0.0);
-    assert(maxValue != null && maxValue >= 0.0 && maxValue >= minValue);
+    assert(minValue >= 0.0);
+    assert(maxValue >= 0.0 && maxValue >= minValue);
     final double valueSquared = pixelsPerSecond.distanceSquared;
     if (valueSquared > maxValue * maxValue) {
       return Velocity(pixelsPerSecond: (pixelsPerSecond / pixelsPerSecond.distance) * maxValue);
@@ -97,10 +97,7 @@
     required this.confidence,
     required this.duration,
     required this.offset,
-  }) : assert(pixelsPerSecond != null),
-       assert(confidence != null),
-       assert(duration != null),
-       assert(offset != null);
+  });
 
   /// The number of pixels per second of velocity in the x and y directions.
   final Offset pixelsPerSecond;
@@ -124,9 +121,7 @@
 }
 
 class _PointAtTime {
-  const _PointAtTime(this.point, this.time)
-    : assert(point != null),
-      assert(time != null);
+  const _PointAtTime(this.point, this.time);
 
   final Duration time;
   final Offset point;
diff --git a/framework/lib/src/material/about.dart b/framework/lib/src/material/about.dart
index fd6b1bb..dea95b4 100644
--- a/framework/lib/src/material/about.dart
+++ b/framework/lib/src/material/about.dart
@@ -184,8 +184,6 @@
   RouteSettings? routeSettings,
   Offset? anchorPoint,
 }) {
-  assert(context != null);
-  assert(useRootNavigator != null);
   showDialog<void>(
     context: context,
     useRootNavigator: useRootNavigator,
@@ -230,8 +228,6 @@
   String? applicationLegalese,
   bool useRootNavigator = false,
 }) {
-  assert(context != null);
-  assert(useRootNavigator != null);
   Navigator.of(context, rootNavigator: useRootNavigator).push(MaterialPageRoute<void>(
     builder: (BuildContext context) => LicensePage(
       applicationName: applicationName,
@@ -486,8 +482,7 @@
     required this.version,
     this.icon,
     this.legalese,
-  })  : assert(name != null),
-        assert(version != null);
+  });
 
   final String name;
   final String version;
@@ -542,8 +537,7 @@
     required this.about,
     required this.isLateral,
     required this.selectedId,
-  })  : assert(about != null),
-        assert(isLateral != null);
+  });
 
   final Widget about;
   final bool isLateral;
@@ -571,6 +565,17 @@
           builder: (BuildContext context, BoxConstraints constraints) {
             switch (snapshot.connectionState) {
               case ConnectionState.done:
+                if (snapshot.hasError) {
+                  assert(() {
+                    FlutterError.reportError(FlutterErrorDetails(
+                      exception: snapshot.error!,
+                      stack: snapshot.stackTrace,
+                      context: ErrorDescription('while decoding the license file'),
+                    ));
+                    return true;
+                  }());
+                  return Center(child: Text(snapshot.error.toString()));
+                }
                 _initDefaultDetailPage(snapshot.data!, context);
                 return ValueListenableBuilder<int?>(
                   valueListenable: widget.selectedId,
@@ -745,7 +750,7 @@
   final List<LicenseEntry> licenseEntries;
 
   @override
-  bool operator ==(final dynamic other) {
+  bool operator ==(final Object other) {
     if (other is _DetailArguments) {
       return other.packageName == packageName;
     }
@@ -867,7 +872,8 @@
           title: _PackageLicensePageTitle(
             title,
             subtitle,
-            theme.appBarTheme.textTheme ?? theme.primaryTextTheme,
+            theme.primaryTextTheme,
+            theme.appBarTheme.titleTextStyle,
           ),
         ),
         body: Center(
@@ -899,7 +905,7 @@
             automaticallyImplyLeading: false,
             pinned: true,
             backgroundColor: theme.cardColor,
-            title: _PackageLicensePageTitle(title, subtitle, theme.textTheme),
+            title: _PackageLicensePageTitle(title, subtitle, theme.textTheme, theme.textTheme.titleLarge),
           ),
           SliverPadding(
             padding: padding,
@@ -929,21 +935,24 @@
     this.title,
     this.subtitle,
     this.theme,
+    this.titleTextStyle,
   );
 
   final String title;
   final String subtitle;
   final TextTheme theme;
+  final TextStyle? titleTextStyle;
 
   @override
   Widget build(BuildContext context) {
     final Color? color = Theme.of(context).appBarTheme.foregroundColor;
+    final TextStyle? effectiveTitleTextStyle = titleTextStyle ?? theme.titleLarge;
 
     return Column(
       mainAxisAlignment: MainAxisAlignment.center,
       crossAxisAlignment: CrossAxisAlignment.start,
       children: <Widget>[
-        Text(title, style: theme.titleLarge?.copyWith(color: color)),
+        Text(title, style: effectiveTitleTextStyle?.copyWith(color: color)),
         Text(subtitle, style: theme.titleSmall?.copyWith(color: color)),
       ],
     );
@@ -976,7 +985,7 @@
 const double _narrowGutterSize = 12.0;
 
 double _getGutterSize(BuildContext context) =>
-    MediaQuery.of(context).size.width >= _materialGutterThreshold ? _wideGutterSize : _narrowGutterSize;
+    MediaQuery.sizeOf(context).width >= _materialGutterThreshold ? _wideGutterSize : _narrowGutterSize;
 
 /// Signature for the builder callback used by [_MasterDetailFlow].
 typedef _MasterViewBuilder = Widget Function(BuildContext context, bool isLateralUI);
@@ -1033,14 +1042,11 @@
   const _MasterDetailFlow({
     required this.detailPageBuilder,
     required this.masterViewBuilder,
-    this.automaticallyImplyLeading = true,
+    this.automaticallyImplyLeading = true, // ignore: unused_element
     this.detailPageFABlessGutterWidth,
-    this.displayMode = _LayoutMode.auto,
+    this.displayMode = _LayoutMode.auto, // ignore: unused_element
     this.title,
-  })  : assert(masterViewBuilder != null),
-        assert(automaticallyImplyLeading != null),
-        assert(detailPageBuilder != null),
-        assert(displayMode != null);
+  });
 
   /// Builder for the master view for lateral navigation.
   ///
@@ -1301,8 +1307,7 @@
     this.title,
     required this.automaticallyImplyLeading,
     this.detailPageFABlessGutterWidth,
-  })  : assert(detailPageBuilder != null),
-        assert(masterViewBuilder != null);
+  });
 
   final _MasterViewBuilder masterViewBuilder;
 
@@ -1451,8 +1456,7 @@
   const _DetailView({
     required _DetailPageBuilder builder,
     Object? arguments,
-  })  : assert(builder != null),
-        _builder = builder,
+  })  : _builder = builder,
         _arguments = arguments;
 
   final _DetailPageBuilder _builder;
@@ -1463,7 +1467,7 @@
     if (_arguments == null) {
       return const SizedBox.shrink();
     }
-    final double screenHeight = MediaQuery.of(context).size.height;
+    final double screenHeight = MediaQuery.sizeOf(context).height;
     final double minHeight = (screenHeight - kToolbarHeight) / screenHeight;
 
     return DraggableScrollableSheet(
diff --git a/framework/lib/src/material/action_chip.dart b/framework/lib/src/material/action_chip.dart
index 7d3c17f..e3e30a1 100644
--- a/framework/lib/src/material/action_chip.dart
+++ b/framework/lib/src/material/action_chip.dart
@@ -20,7 +20,7 @@
 /// Action chips can be tapped to trigger an action or show progress and
 /// confirmation. For Material 3, a disabled state is supported for Action
 /// chips and is specified with [onPressed] being null. For previous versions
-/// of Material Design, it is recommended to remove the Action chip from the
+/// of Material Design, it is recommended to remove the Action chip from
 /// the interface entirely rather than display a disabled chip.
 ///
 /// Action chips are displayed after primary content, such as below a card or
@@ -86,10 +86,7 @@
     this.shadowColor,
     this.surfaceTintColor,
     this.iconTheme,
-  }) : assert(label != null),
-       assert(clipBehavior != null),
-       assert(autofocus != null),
-       assert(pressElevation == null || pressElevation >= 0.0),
+  }) : assert(pressElevation == null || pressElevation >= 0.0),
        assert(elevation == null || elevation >= 0.0);
 
   @override
@@ -178,7 +175,7 @@
 // Design token database by the script:
 //   dev/tools/gen_defaults/bin/gen_defaults.dart.
 
-// Token database version: v0_143
+// Token database version: v0_158
 
 class _ActionChipDefaultsM3 extends ChipThemeData {
   const _ActionChipDefaultsM3(this.context, this.isEnabled)
@@ -239,7 +236,7 @@
   EdgeInsetsGeometry? get labelPadding => EdgeInsets.lerp(
     const EdgeInsets.symmetric(horizontal: 8.0),
     const EdgeInsets.symmetric(horizontal: 4.0),
-    clampDouble(MediaQuery.of(context).textScaleFactor - 1.0, 0.0, 1.0),
+    clampDouble(MediaQuery.textScaleFactorOf(context) - 1.0, 0.0, 1.0),
   )!;
 }
 
diff --git a/framework/lib/src/material/adaptive_text_selection_toolbar.dart b/framework/lib/src/material/adaptive_text_selection_toolbar.dart
index 5f9af12..5b94777 100644
--- a/framework/lib/src/material/adaptive_text_selection_toolbar.dart
+++ b/framework/lib/src/material/adaptive_text_selection_toolbar.dart
@@ -211,6 +211,8 @@
             return localizations.pasteButtonLabel;
           case ContextMenuButtonType.selectAll:
             return localizations.selectAllButtonLabel;
+          case ContextMenuButtonType.delete:
+            return localizations.deleteButtonTooltip.toUpperCase();
           case ContextMenuButtonType.custom:
             return '';
         }
@@ -218,7 +220,7 @@
   }
 
   /// Returns a List of Widgets generated by turning [buttonItems] into the
-  /// the default context menu buttons for the current platform.
+  /// default context menu buttons for the current platform.
   ///
   /// This is useful when building a text selection toolbar with the default
   /// button appearance for the given platform, but where the toolbar and/or the
diff --git a/framework/lib/src/material/animated_icons/animated_icons.dart b/framework/lib/src/material/animated_icons/animated_icons.dart
index a28f015..bc8ea27 100644
--- a/framework/lib/src/material/animated_icons/animated_icons.dart
+++ b/framework/lib/src/material/animated_icons/animated_icons.dart
@@ -2,7 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-part of material_animated_icons;
+// TODO(goderbauer): Clean up the part-of hack currently used for testing the private implementation.
+part of material_animated_icons; // ignore: use_string_in_part_of_directives
 
 // The code for drawing animated icons is kept in a private API, as we are not
 // yet ready for exposing a public API for (partial) vector graphics support.
@@ -38,8 +39,7 @@
     this.size,
     this.semanticLabel,
     this.textDirection,
-  }) : assert(progress != null),
-       assert(icon != null);
+  });
 
   /// The animation progress for the animated icon.
   ///
diff --git a/framework/lib/src/material/animated_icons/animated_icons_data.dart b/framework/lib/src/material/animated_icons/animated_icons_data.dart
index 242ba5a..6efca7d 100644
--- a/framework/lib/src/material/animated_icons/animated_icons_data.dart
+++ b/framework/lib/src/material/animated_icons/animated_icons_data.dart
@@ -8,7 +8,7 @@
 // while the _AnimatedIconData interface which used to deliver the icon data is
 // kept private.
 
-part of material_animated_icons;
+part of material_animated_icons; // ignore: use_string_in_part_of_directives
 
 /// Identifier for the supported Material Design animated icons.
 ///
diff --git a/framework/lib/src/material/animated_icons/data/add_event.g.dart b/framework/lib/src/material/animated_icons/data/add_event.g.dart
index 27305b8..88d4600 100644
--- a/framework/lib/src/material/animated_icons/data/add_event.g.dart
+++ b/framework/lib/src/material/animated_icons/data/add_event.g.dart
@@ -4,7 +4,7 @@
 
 // AUTOGENERATED FILE DO NOT EDIT!
 // This file was generated by vitool.
-part of material_animated_icons;
+part of material_animated_icons; // ignore: use_string_in_part_of_directives
 const _AnimatedIconData _$add_event = _AnimatedIconData(
   Size(48.0, 48.0),
   <_PathFrames>[
diff --git a/framework/lib/src/material/animated_icons/data/arrow_menu.g.dart b/framework/lib/src/material/animated_icons/data/arrow_menu.g.dart
index 2649d8c..549c870 100644
--- a/framework/lib/src/material/animated_icons/data/arrow_menu.g.dart
+++ b/framework/lib/src/material/animated_icons/data/arrow_menu.g.dart
@@ -4,7 +4,7 @@
 
 // AUTOGENERATED FILE DO NOT EDIT!
 // This file was generated by vitool.
-part of material_animated_icons;
+part of material_animated_icons; // ignore: use_string_in_part_of_directives
 const _AnimatedIconData _$arrow_menu = _AnimatedIconData(
   Size(48.0, 48.0),
   <_PathFrames>[
diff --git a/framework/lib/src/material/animated_icons/data/close_menu.g.dart b/framework/lib/src/material/animated_icons/data/close_menu.g.dart
index a0b86c0..311a745 100644
--- a/framework/lib/src/material/animated_icons/data/close_menu.g.dart
+++ b/framework/lib/src/material/animated_icons/data/close_menu.g.dart
@@ -4,7 +4,7 @@
 
 // AUTOGENERATED FILE DO NOT EDIT!
 // This file was generated by vitool.
-part of material_animated_icons;
+part of material_animated_icons; // ignore: use_string_in_part_of_directives
 const _AnimatedIconData _$close_menu = _AnimatedIconData(
   Size(48.0, 48.0),
   <_PathFrames>[
diff --git a/framework/lib/src/material/animated_icons/data/ellipsis_search.g.dart b/framework/lib/src/material/animated_icons/data/ellipsis_search.g.dart
index ef8802d..9f539c6 100644
--- a/framework/lib/src/material/animated_icons/data/ellipsis_search.g.dart
+++ b/framework/lib/src/material/animated_icons/data/ellipsis_search.g.dart
@@ -4,7 +4,7 @@
 
 // AUTOGENERATED FILE DO NOT EDIT!
 // This file was generated by vitool.
-part of material_animated_icons;
+part of material_animated_icons; // ignore: use_string_in_part_of_directives
 const _AnimatedIconData _$ellipsis_search = _AnimatedIconData(
   Size(96.0, 96.0),
   <_PathFrames>[
diff --git a/framework/lib/src/material/animated_icons/data/event_add.g.dart b/framework/lib/src/material/animated_icons/data/event_add.g.dart
index 8a012f2..1b64f89 100644
--- a/framework/lib/src/material/animated_icons/data/event_add.g.dart
+++ b/framework/lib/src/material/animated_icons/data/event_add.g.dart
@@ -4,7 +4,7 @@
 
 // AUTOGENERATED FILE DO NOT EDIT!
 // This file was generated by vitool.
-part of material_animated_icons;
+part of material_animated_icons; // ignore: use_string_in_part_of_directives
 const _AnimatedIconData _$event_add = _AnimatedIconData(
   Size(48.0, 48.0),
   <_PathFrames>[
diff --git a/framework/lib/src/material/animated_icons/data/home_menu.g.dart b/framework/lib/src/material/animated_icons/data/home_menu.g.dart
index 447a370..559c453 100644
--- a/framework/lib/src/material/animated_icons/data/home_menu.g.dart
+++ b/framework/lib/src/material/animated_icons/data/home_menu.g.dart
@@ -4,7 +4,7 @@
 
 // AUTOGENERATED FILE DO NOT EDIT!
 // This file was generated by vitool.
-part of material_animated_icons;
+part of material_animated_icons; // ignore: use_string_in_part_of_directives
 const _AnimatedIconData _$home_menu = _AnimatedIconData(
   Size(48.0, 48.0),
   <_PathFrames>[
diff --git a/framework/lib/src/material/animated_icons/data/list_view.g.dart b/framework/lib/src/material/animated_icons/data/list_view.g.dart
index 35814a0..25d544c 100644
--- a/framework/lib/src/material/animated_icons/data/list_view.g.dart
+++ b/framework/lib/src/material/animated_icons/data/list_view.g.dart
@@ -4,7 +4,7 @@
 
 // AUTOGENERATED FILE DO NOT EDIT!
 // This file was generated by vitool.
-part of material_animated_icons;
+part of material_animated_icons; // ignore: use_string_in_part_of_directives
 const _AnimatedIconData _$list_view = _AnimatedIconData(
   Size(48.0, 48.0),
   <_PathFrames>[
diff --git a/framework/lib/src/material/animated_icons/data/menu_arrow.g.dart b/framework/lib/src/material/animated_icons/data/menu_arrow.g.dart
index 78613e3..4717a93 100644
--- a/framework/lib/src/material/animated_icons/data/menu_arrow.g.dart
+++ b/framework/lib/src/material/animated_icons/data/menu_arrow.g.dart
@@ -4,7 +4,7 @@
 
 // AUTOGENERATED FILE DO NOT EDIT!
 // This file was generated by vitool.
-part of material_animated_icons;
+part of material_animated_icons; // ignore: use_string_in_part_of_directives
 const _AnimatedIconData _$menu_arrow = _AnimatedIconData(
   Size(48.0, 48.0),
   <_PathFrames>[
diff --git a/framework/lib/src/material/animated_icons/data/menu_close.g.dart b/framework/lib/src/material/animated_icons/data/menu_close.g.dart
index 43e194f..27d3408 100644
--- a/framework/lib/src/material/animated_icons/data/menu_close.g.dart
+++ b/framework/lib/src/material/animated_icons/data/menu_close.g.dart
@@ -4,7 +4,7 @@
 
 // AUTOGENERATED FILE DO NOT EDIT!
 // This file was generated by vitool.
-part of material_animated_icons;
+part of material_animated_icons; // ignore: use_string_in_part_of_directives
 const _AnimatedIconData _$menu_close = _AnimatedIconData(
   Size(48.0, 48.0),
   <_PathFrames>[
diff --git a/framework/lib/src/material/animated_icons/data/menu_home.g.dart b/framework/lib/src/material/animated_icons/data/menu_home.g.dart
index dfdcc79..53c3ec7 100644
--- a/framework/lib/src/material/animated_icons/data/menu_home.g.dart
+++ b/framework/lib/src/material/animated_icons/data/menu_home.g.dart
@@ -4,7 +4,7 @@
 
 // AUTOGENERATED FILE DO NOT EDIT!
 // This file was generated by vitool.
-part of material_animated_icons;
+part of material_animated_icons; // ignore: use_string_in_part_of_directives
 const _AnimatedIconData _$menu_home = _AnimatedIconData(
   Size(48.0, 48.0),
   <_PathFrames>[
diff --git a/framework/lib/src/material/animated_icons/data/pause_play.g.dart b/framework/lib/src/material/animated_icons/data/pause_play.g.dart
index b9d117e..2d9d926 100644
--- a/framework/lib/src/material/animated_icons/data/pause_play.g.dart
+++ b/framework/lib/src/material/animated_icons/data/pause_play.g.dart
@@ -4,7 +4,7 @@
 
 // AUTOGENERATED FILE DO NOT EDIT!
 // This file was generated by vitool.
-part of material_animated_icons;
+part of material_animated_icons; // ignore: use_string_in_part_of_directives
 const _AnimatedIconData _$pause_play = _AnimatedIconData(
   Size(48.0, 48.0),
   <_PathFrames>[
diff --git a/framework/lib/src/material/animated_icons/data/play_pause.g.dart b/framework/lib/src/material/animated_icons/data/play_pause.g.dart
index a850aa3..71fa193 100644
--- a/framework/lib/src/material/animated_icons/data/play_pause.g.dart
+++ b/framework/lib/src/material/animated_icons/data/play_pause.g.dart
@@ -4,7 +4,7 @@
 
 // AUTOGENERATED FILE DO NOT EDIT!
 // This file was generated by vitool.
-part of material_animated_icons;
+part of material_animated_icons; // ignore: use_string_in_part_of_directives
 const _AnimatedIconData _$play_pause = _AnimatedIconData(
   Size(48.0, 48.0),
   <_PathFrames>[
diff --git a/framework/lib/src/material/animated_icons/data/search_ellipsis.g.dart b/framework/lib/src/material/animated_icons/data/search_ellipsis.g.dart
index 93ae867..6760e2a 100644
--- a/framework/lib/src/material/animated_icons/data/search_ellipsis.g.dart
+++ b/framework/lib/src/material/animated_icons/data/search_ellipsis.g.dart
@@ -4,7 +4,7 @@
 
 // AUTOGENERATED FILE DO NOT EDIT!
 // This file was generated by vitool.
-part of material_animated_icons;
+part of material_animated_icons; // ignore: use_string_in_part_of_directives
 const _AnimatedIconData _$search_ellipsis = _AnimatedIconData(
   Size(96.0, 96.0),
   <_PathFrames>[
diff --git a/framework/lib/src/material/animated_icons/data/view_list.g.dart b/framework/lib/src/material/animated_icons/data/view_list.g.dart
index af9606b..65ec982 100644
--- a/framework/lib/src/material/animated_icons/data/view_list.g.dart
+++ b/framework/lib/src/material/animated_icons/data/view_list.g.dart
@@ -4,7 +4,7 @@
 
 // AUTOGENERATED FILE DO NOT EDIT!
 // This file was generated by vitool.
-part of material_animated_icons;
+part of material_animated_icons; // ignore: use_string_in_part_of_directives
 const _AnimatedIconData _$view_list = _AnimatedIconData(
   Size(48.0, 48.0),
   <_PathFrames>[
diff --git a/framework/lib/src/material/app.dart b/framework/lib/src/material/app.dart
index 1defb57..fb82772 100644
--- a/framework/lib/src/material/app.dart
+++ b/framework/lib/src/material/app.dart
@@ -241,17 +241,13 @@
     this.actions,
     this.restorationScopeId,
     this.scrollBehavior,
+    @Deprecated(
+      'Remove this parameter as it is now ignored. '
+      'MaterialApp never introduces its own MediaQuery; the View widget takes care of that. '
+      'This feature was deprecated after v3.7.0-29.0.pre.'
+    )
     this.useInheritedMediaQuery = false,
-  }) : assert(routes != null),
-       assert(navigatorObservers != null),
-       assert(title != null),
-       assert(debugShowMaterialGrid != null),
-       assert(showPerformanceOverlay != null),
-       assert(checkerboardRasterCacheImages != null),
-       assert(checkerboardOffscreenLayers != null),
-       assert(showSemanticsDebugger != null),
-       assert(debugShowCheckedModeBanner != null),
-       routeInformationProvider = null,
+  }) : routeInformationProvider = null,
        routeInformationParser = null,
        routerDelegate = null,
        backButtonDispatcher = null,
@@ -294,15 +290,13 @@
     this.actions,
     this.restorationScopeId,
     this.scrollBehavior,
+    @Deprecated(
+      'Remove this parameter as it is now ignored. '
+      'MaterialApp never introduces its own MediaQuery; the View widget takes care of that. '
+      'This feature was deprecated after v3.7.0-29.0.pre.'
+    )
     this.useInheritedMediaQuery = false,
   }) : assert(routerDelegate != null || routerConfig != null),
-       assert(title != null),
-       assert(debugShowMaterialGrid != null),
-       assert(showPerformanceOverlay != null),
-       assert(checkerboardRasterCacheImages != null),
-       assert(checkerboardOffscreenLayers != null),
-       assert(showSemanticsDebugger != null),
-       assert(debugShowCheckedModeBanner != null),
        navigatorObservers = null,
        navigatorKey = null,
        onGenerateRoute = null,
@@ -749,6 +743,11 @@
   final bool debugShowMaterialGrid;
 
   /// {@macro flutter.widgets.widgetsApp.useInheritedMediaQuery}
+  @Deprecated(
+    'This setting is now ignored. '
+    'MaterialApp never introduces its own MediaQuery; the View widget takes care of that. '
+    'This feature was deprecated after v3.7.0-29.0.pre.'
+  )
   final bool useInheritedMediaQuery;
 
   @override
@@ -999,7 +998,6 @@
         shortcuts: widget.shortcuts,
         actions: widget.actions,
         restorationScopeId: widget.restorationScopeId,
-        useInheritedMediaQuery: widget.useInheritedMediaQuery,
       );
     }
 
@@ -1035,7 +1033,6 @@
       shortcuts: widget.shortcuts,
       actions: widget.actions,
       restorationScopeId: widget.restorationScopeId,
-      useInheritedMediaQuery: widget.useInheritedMediaQuery,
     );
   }
 
diff --git a/framework/lib/src/material/app_bar.dart b/framework/lib/src/material/app_bar.dart
index cce53af..884b05f 100644
--- a/framework/lib/src/material/app_bar.dart
+++ b/framework/lib/src/material/app_bar.dart
@@ -11,12 +11,14 @@
 
 import 'app_bar_theme.dart';
 import 'back_button.dart';
+import 'button_style.dart';
 import 'color_scheme.dart';
 import 'colors.dart';
 import 'constants.dart';
 import 'debug.dart';
 import 'flexible_space_bar.dart';
 import 'icon_button.dart';
+import 'icon_button_theme.dart';
 import 'icons.dart';
 import 'material.dart';
 import 'material_localizations.dart';
@@ -127,7 +129,7 @@
 ///
 /// If the app bar's [actions] contains [TextButton]s, they will not
 /// be visible if their foreground (text) color is the same as the
-/// the app bar's background color.
+/// app bar's background color.
 ///
 /// The default app bar [backgroundColor] is the overall theme's
 /// [ColorScheme.primary] if the overall theme's brightness is
@@ -199,11 +201,6 @@
     this.brightness,
     this.iconTheme,
     this.actionsIconTheme,
-    @Deprecated(
-      'This property is no longer used, please use toolbarTextStyle and titleTextStyle instead. '
-      'This feature was deprecated after v2.4.0-0.0.pre.',
-    )
-    this.textTheme,
     this.primary = true,
     this.centerTitle,
     this.excludeHeaderSemantics = false,
@@ -220,12 +217,8 @@
     this.toolbarTextStyle,
     this.titleTextStyle,
     this.systemOverlayStyle,
-  }) : assert(automaticallyImplyLeading != null),
-       assert(elevation == null || elevation >= 0.0),
-       assert(notificationPredicate != null),
-       assert(primary != null),
-       assert(toolbarOpacity != null),
-       assert(bottomOpacity != null),
+    this.forceMaterialTransparency = false,
+  }) : assert(elevation == null || elevation >= 0.0),
        preferredSize = _PreferredAppBarSize(toolbarHeight, bottom?.preferredSize.height);
 
   /// Used by [Scaffold] to compute its [AppBar]'s overall height. The returned value is
@@ -593,7 +586,7 @@
   /// See also:
   ///
   ///  * [actionsIconTheme], which defines the appearance of icons in
-  ///    in the [actions] list.
+  ///    the [actions] list.
   final IconThemeData? iconTheme;
 
   /// {@template flutter.material.appbar.actionsIconTheme}
@@ -614,23 +607,6 @@
   ///  * [iconTheme], which defines the appearance of all of the toolbar icons.
   final IconThemeData? actionsIconTheme;
 
-  /// {@template flutter.material.appbar.textTheme}
-  /// This property is deprecated, please use [toolbarTextStyle] and
-  /// [titleTextStyle] instead.
-  ///
-  /// The typographic styles to use for text in the app bar. Typically this is
-  /// set along with [backgroundColor], [iconTheme].
-  ///
-  /// If this property is null, then [AppBarTheme.textTheme] of
-  /// [ThemeData.appBarTheme] is used. If that is also null, then
-  /// [ThemeData.primaryTextTheme] is used.
-  /// {@endtemplate}
-  @Deprecated(
-    'This property is no longer used, please use toolbarTextStyle and titleTextStyle instead. '
-    'This feature was deprecated after v2.4.0-0.0.pre.',
-  )
-  final TextTheme? textTheme;
-
   /// {@template flutter.material.appbar.primary}
   /// Whether this app bar is being displayed at the top of the screen.
   ///
@@ -717,7 +693,7 @@
   ///
   /// If true, preserves the original defaults for the [backgroundColor],
   /// [iconTheme], [actionsIconTheme] properties, and the original use of
-  /// the [textTheme] and [brightness] properties.
+  /// the [brightness] property.
   ///
   /// If this property is null, then [AppBarTheme.backwardsCompatibility] of
   /// [ThemeData.appBarTheme] is used. If that is also null, the default
@@ -748,7 +724,7 @@
   ///
   ///  * [titleTextStyle], which overrides the default text style for the [title].
   ///  * [DefaultTextStyle], which overrides the default text style for all of the
-  ///    the widgets in a subtree.
+  ///    widgets in a subtree.
   final TextStyle? toolbarTextStyle;
 
   /// {@template flutter.material.appbar.titleTextStyle}
@@ -766,7 +742,7 @@
   ///    [title], [leading], and [actions] widgets, also known as the
   ///    AppBar's "toolbar".
   ///  * [DefaultTextStyle], which overrides the default text style for all of the
-  ///    the widgets in a subtree.
+  ///    widgets in a subtree.
   final TextStyle? titleTextStyle;
 
   /// {@template flutter.material.appbar.systemOverlayStyle}
@@ -789,9 +765,23 @@
   ///  * [SystemChrome.setSystemUIOverlayStyle]
   final SystemUiOverlayStyle? systemOverlayStyle;
 
+  /// {@template flutter.material.appbar.forceMaterialTransparency}
+  /// Forces the AppBar's Material widget type to be [MaterialType.transparency]
+  /// (instead of Material's default type).
+  ///
+  /// This will remove the visual display of [backgroundColor] and [elevation],
+  /// and affect other characteristics of the AppBar's Material widget.
+  ///
+  /// Provided for cases where the app bar is to be transparent, and gestures
+  /// must pass through the app bar to widgets beneath the app bar (i.e. with
+  /// [Scaffold.extendBodyBehindAppBar] set to true).
+  ///
+  /// Defaults to false.
+  /// {@endtemplate}
+  final bool forceMaterialTransparency;
+
   bool _getEffectiveCenterTitle(ThemeData theme) {
     bool platformCenter() {
-      assert(theme.platform != null);
       switch (theme.platform) {
         case TargetPlatform.android:
         case TargetPlatform.fuchsia:
@@ -893,6 +883,7 @@
     assert(!widget.primary || debugCheckHasMediaQuery(context));
     assert(debugCheckHasMaterialLocalizations(context));
     final ThemeData theme = Theme.of(context);
+    final IconButtonThemeData iconButtonTheme = IconButtonTheme.of(context);
     final AppBarTheme appBarTheme = AppBarTheme.of(context);
     final AppBarTheme defaults = theme.useMaterial3 ? _AppBarDefaultsM3(context) : _AppBarDefaultsM2(context);
     final ScaffoldState? scaffold = Scaffold.maybeOf(context);
@@ -955,16 +946,16 @@
       ?? overallIconTheme;
 
     TextStyle? toolbarTextStyle = backwardsCompatibility
-      ? widget.textTheme?.bodyMedium
-        ?? appBarTheme.textTheme?.bodyMedium
+      ? widget.toolbarTextStyle
+        ?? appBarTheme.toolbarTextStyle
         ?? theme.primaryTextTheme.bodyMedium
       : widget.toolbarTextStyle
         ?? appBarTheme.toolbarTextStyle
         ?? defaults.toolbarTextStyle?.copyWith(color: foregroundColor);
 
     TextStyle? titleTextStyle = backwardsCompatibility
-      ? widget.textTheme?.titleLarge
-        ?? appBarTheme.textTheme?.titleLarge
+      ? widget.titleTextStyle
+        ?? appBarTheme.titleTextStyle
         ?? theme.primaryTextTheme.titleLarge
       : widget.titleTextStyle
         ?? appBarTheme.titleTextStyle
@@ -1003,13 +994,42 @@
       }
     }
     if (leading != null) {
-      // Based on the Material Design 3 specs, the leading IconButton should have
-      // a size of 48x48, and a highlight size of 40x40. Users can also put other
-      // type of widgets on leading with the original config.
       if (theme.useMaterial3) {
-        leading =  ConstrainedBox(
+        final IconButtonThemeData effectiveIconButtonTheme;
+
+        // This comparison is to check if there is a custom [overallIconTheme]. If true, it means that no
+        // custom [overallIconTheme] is provided, so [iconButtonTheme] is applied. Otherwise, we generate
+        // a new [IconButtonThemeData] based on the values from [overallIconTheme]. If [iconButtonTheme] only
+        // has null values, the default [overallIconTheme] will be applied below by [IconTheme.merge]
+        if (overallIconTheme == defaults.iconTheme) {
+          effectiveIconButtonTheme = iconButtonTheme;
+        } else {
+          // The [IconButton.styleFrom] method is used to generate a correct [overlayColor] based on the [foregroundColor].
+          final ButtonStyle leadingIconButtonStyle = IconButton.styleFrom(
+            foregroundColor: overallIconTheme.color,
+            iconSize: overallIconTheme.size,
+          );
+
+          effectiveIconButtonTheme = IconButtonThemeData(
+            style: iconButtonTheme.style?.copyWith(
+              foregroundColor: leadingIconButtonStyle.foregroundColor,
+              overlayColor: leadingIconButtonStyle.overlayColor,
+              iconSize: leadingIconButtonStyle.iconSize,
+            )
+          );
+        }
+
+        leading = IconButtonTheme(
+            data: effectiveIconButtonTheme,
+            child: leading is IconButton ? Center(child: leading) : leading,
+        );
+
+        // Based on the Material Design 3 specs, the leading IconButton should have
+        // a size of 48x48, and a highlight size of 40x40. Users can also put other
+        // type of widgets on leading with the original config.
+        leading = ConstrainedBox(
           constraints: BoxConstraints.tightFor(width: widget.leadingWidth ?? _kLeadingWidth),
-          child: leading is IconButton ? Center(child: leading) : leading,
+          child: leading,
         );
       } else {
         leading = ConstrainedBox(
@@ -1085,9 +1105,30 @@
 
     // Allow the trailing actions to have their own theme if necessary.
     if (actions != null) {
-      actions = IconTheme.merge(
-        data: actionsIconTheme,
-        child: actions,
+      final IconButtonThemeData effectiveActionsIconButtonTheme;
+      if (actionsIconTheme == defaults.actionsIconTheme) {
+        effectiveActionsIconButtonTheme = iconButtonTheme;
+      } else {
+        final ButtonStyle actionsIconButtonStyle = IconButton.styleFrom(
+          foregroundColor: actionsIconTheme.color,
+          iconSize: actionsIconTheme.size,
+        );
+
+        effectiveActionsIconButtonTheme = IconButtonThemeData(
+          style: iconButtonTheme.style?.copyWith(
+            foregroundColor: actionsIconButtonStyle.foregroundColor,
+            overlayColor: actionsIconButtonStyle.overlayColor,
+            iconSize: actionsIconButtonStyle.iconSize,
+          )
+        );
+      }
+
+      actions = IconButtonTheme(
+        data: effectiveActionsIconButtonTheme,
+        child: IconTheme.merge(
+          data: actionsIconTheme,
+          child: actions,
+        ),
       );
     }
 
@@ -1193,6 +1234,9 @@
         child: Material(
           color: backgroundColor,
           elevation: effectiveElevation,
+          type: widget.forceMaterialTransparency
+              ? MaterialType.transparency
+              : MaterialType.canvas,
           shadowColor: widget.shadowColor
             ?? appBarTheme.shadowColor
             ?? defaults.shadowColor,
@@ -1228,7 +1272,6 @@
     required this.brightness,
     required this.iconTheme,
     required this.actionsIconTheme,
-    required this.textTheme,
     required this.primary,
     required this.centerTitle,
     required this.excludeHeaderSemantics,
@@ -1249,11 +1292,8 @@
     required this.toolbarTextStyle,
     required this.titleTextStyle,
     required this.systemOverlayStyle,
+    required this.forceMaterialTransparency,
   }) : assert(primary || topPadding == 0.0),
-       assert(
-         !floating || (snapConfiguration == null && showOnScreenConfiguration == null) || vsync != null,
-         'vsync cannot be null when snapConfiguration or showOnScreenConfiguration is not null, and floating is true',
-       ),
        _bottomHeight = bottom?.preferredSize.height ?? 0.0;
 
   final Widget? leading;
@@ -1272,7 +1312,6 @@
   final Brightness? brightness;
   final IconThemeData? iconTheme;
   final IconThemeData? actionsIconTheme;
-  final TextTheme? textTheme;
   final bool primary;
   final bool? centerTitle;
   final bool excludeHeaderSemantics;
@@ -1290,6 +1329,7 @@
   final TextStyle? titleTextStyle;
   final SystemUiOverlayStyle? systemOverlayStyle;
   final double _bottomHeight;
+  final bool forceMaterialTransparency;
 
   @override
   double get minExtent => collapsedHeight;
@@ -1348,7 +1388,6 @@
         brightness: brightness,
         iconTheme: iconTheme,
         actionsIconTheme: actionsIconTheme,
-        textTheme: textTheme,
         primary: primary,
         centerTitle: centerTitle,
         excludeHeaderSemantics: excludeHeaderSemantics,
@@ -1362,6 +1401,7 @@
         toolbarTextStyle: toolbarTextStyle,
         titleTextStyle: titleTextStyle,
         systemOverlayStyle: systemOverlayStyle,
+        forceMaterialTransparency: forceMaterialTransparency,
       ),
     );
     return appBar;
@@ -1383,7 +1423,6 @@
         || brightness != oldDelegate.brightness
         || iconTheme != oldDelegate.iconTheme
         || actionsIconTheme != oldDelegate.actionsIconTheme
-        || textTheme != oldDelegate.textTheme
         || primary != oldDelegate.primary
         || centerTitle != oldDelegate.centerTitle
         || titleSpacing != oldDelegate.titleSpacing
@@ -1401,7 +1440,8 @@
         || backwardsCompatibility != oldDelegate.backwardsCompatibility
         || toolbarTextStyle != oldDelegate.toolbarTextStyle
         || titleTextStyle != oldDelegate.titleTextStyle
-        || systemOverlayStyle != oldDelegate.systemOverlayStyle;
+        || systemOverlayStyle != oldDelegate.systemOverlayStyle
+        || forceMaterialTransparency != oldDelegate.forceMaterialTransparency;
   }
 
   @override
@@ -1522,11 +1562,6 @@
     this.brightness,
     this.iconTheme,
     this.actionsIconTheme,
-    @Deprecated(
-      'This property is no longer used, please use toolbarTextStyle and titleTextStyle instead. '
-      'This feature was deprecated after v2.4.0-0.0.pre.',
-    )
-    this.textTheme,
     this.primary = true,
     this.centerTitle,
     this.excludeHeaderSemantics = false,
@@ -1550,15 +1585,8 @@
     this.toolbarTextStyle,
     this.titleTextStyle,
     this.systemOverlayStyle,
-  }) : assert(automaticallyImplyLeading != null),
-       assert(forceElevated != null),
-       assert(primary != null),
-       assert(floating != null),
-       assert(pinned != null),
-       assert(snap != null),
-       assert(stretch != null),
-       assert(toolbarHeight != null),
-       assert(floating || !snap, 'The "snap" argument only makes sense for floating app bars.'),
+    this.forceMaterialTransparency = false,
+  }) : assert(floating || !snap, 'The "snap" argument only makes sense for floating app bars.'),
        assert(stretchTriggerOffset > 0.0),
        assert(collapsedHeight == null || collapsedHeight >= toolbarHeight, 'The "collapsedHeight" argument has to be larger than or equal to [toolbarHeight].');
 
@@ -1628,6 +1656,7 @@
       actions: actions,
       flexibleSpace: flexibleSpace ?? _ScrollUnderFlexibleSpace(
         title: title,
+        foregroundColor: foregroundColor,
         variant: _ScrollUnderFlexibleVariant.medium,
         centerCollapsedTitle: centerTitle,
         primary: primary,
@@ -1729,6 +1758,7 @@
       actions: actions,
       flexibleSpace: flexibleSpace ?? _ScrollUnderFlexibleSpace(
         title: title,
+        foregroundColor: foregroundColor,
         variant: _ScrollUnderFlexibleVariant.large,
         centerCollapsedTitle: centerTitle,
         primary: primary,
@@ -1854,15 +1884,6 @@
   /// This property is used to configure an [AppBar].
   final IconThemeData? actionsIconTheme;
 
-  /// {@macro flutter.material.appbar.textTheme}
-  ///
-  /// This property is used to configure an [AppBar].
-  @Deprecated(
-    'This property is no longer used, please use toolbarTextStyle and titleTextStyle instead. '
-    'This feature was deprecated after v2.4.0-0.0.pre.',
-  )
-  final TextTheme? textTheme;
-
   /// {@macro flutter.material.appbar.primary}
   ///
   /// This property is used to configure an [AppBar].
@@ -2038,6 +2059,11 @@
   /// This property is used to configure an [AppBar].
   final SystemUiOverlayStyle? systemOverlayStyle;
 
+  /// {@macro flutter.material.appbar.forceMaterialTransparency}
+  ///
+  /// This property is used to configure an [AppBar].
+  final bool forceMaterialTransparency;
+
   @override
   State<SliverAppBar> createState() => _SliverAppBarState();
 }
@@ -2097,7 +2123,7 @@
   Widget build(BuildContext context) {
     assert(!widget.primary || debugCheckHasMediaQuery(context));
     final double bottomHeight = widget.bottom?.preferredSize.height ?? 0.0;
-    final double topPadding = widget.primary ? MediaQuery.of(context).padding.top : 0.0;
+    final double topPadding = widget.primary ? MediaQuery.paddingOf(context).top : 0.0;
     final double collapsedHeight = (widget.pinned && widget.floating && widget.bottom != null)
       ? (widget.collapsedHeight ?? 0.0) + bottomHeight + topPadding
       : (widget.collapsedHeight ?? widget.toolbarHeight) + bottomHeight + topPadding;
@@ -2126,7 +2152,6 @@
           brightness: widget.brightness,
           iconTheme: widget.iconTheme,
           actionsIconTheme: widget.actionsIconTheme,
-          textTheme: widget.textTheme,
           primary: widget.primary,
           centerTitle: widget.centerTitle,
           excludeHeaderSemantics: widget.excludeHeaderSemantics,
@@ -2146,6 +2171,7 @@
           toolbarTextStyle: widget.toolbarTextStyle,
           titleTextStyle: widget.titleTextStyle,
           systemOverlayStyle: widget.systemOverlayStyle,
+          forceMaterialTransparency: widget.forceMaterialTransparency,
         ),
       ),
     );
@@ -2156,7 +2182,7 @@
 // center it within its (NavigationToolbar) parent, and allow the
 // parent to constrain the title's actual height.
 class _AppBarTitleBox extends SingleChildRenderObjectWidget {
-  const _AppBarTitleBox({ required Widget super.child }) : assert(child != null);
+  const _AppBarTitleBox({ required Widget super.child });
 
   @override
   _RenderAppBarTitleBox createRenderObject(BuildContext context) {
@@ -2197,12 +2223,14 @@
 class _ScrollUnderFlexibleSpace extends StatelessWidget {
   const _ScrollUnderFlexibleSpace({
     this.title,
+    this.foregroundColor,
     required this.variant,
     this.centerCollapsedTitle,
     this.primary = true,
   });
 
   final Widget? title;
+  final Color? foregroundColor;
   final _ScrollUnderFlexibleVariant variant;
   final bool? centerCollapsedTitle;
   final bool primary;
@@ -2210,8 +2238,9 @@
   @override
   Widget build(BuildContext context) {
     late final ThemeData theme = Theme.of(context);
+    late final AppBarTheme appBarTheme = AppBarTheme.of(context);
     final FlexibleSpaceBarSettings settings = context.dependOnInheritedWidgetOfExactType<FlexibleSpaceBarSettings>()!;
-    final double topPadding = primary ? MediaQuery.of(context).viewPadding.top : 0;
+    final double topPadding = primary ? MediaQuery.viewPaddingOf(context).top : 0;
     final double collapsedHeight = settings.minExtent - topPadding;
     final double scrollUnderHeight = settings.maxExtent - settings.minExtent;
     final _ScrollUnderFlexibleConfig config;
@@ -2229,13 +2258,13 @@
     if (title != null) {
       collapsedTitle = config.collapsedTextStyle != null
         ? DefaultTextStyle(
-            style: config.collapsedTextStyle!,
+            style: config.collapsedTextStyle!.copyWith(color: foregroundColor ?? appBarTheme.foregroundColor),
             child: title!,
           )
         : title;
       expandedTitle = config.expandedTextStyle != null
         ? DefaultTextStyle(
-            style: config.expandedTextStyle!,
+            style: config.expandedTextStyle!.copyWith(color: foregroundColor ?? appBarTheme.foregroundColor),
             child: title!,
           )
         : title;
@@ -2244,7 +2273,6 @@
     late final bool centerTitle;
     {
       bool platformCenter() {
-        assert(theme.platform != null);
         switch (theme.platform) {
           case TargetPlatform.android:
           case TargetPlatform.fuchsia:
@@ -2256,9 +2284,7 @@
             return true;
         }
       }
-      centerTitle = centerCollapsedTitle
-        ?? theme.appBarTheme.centerTitle
-        ?? platformCenter();
+      centerTitle = centerCollapsedTitle ?? appBarTheme.centerTitle ?? platformCenter();
     }
 
     final bool isCollapsed = settings.isScrolledUnder ?? false;
@@ -2277,7 +2303,7 @@
                 alignment: centerTitle
                   ? Alignment.center
                   : AlignmentDirectional.centerStart,
-                child: collapsedTitle
+                child: collapsedTitle,
               ),
             ),
           ),
@@ -2346,7 +2372,7 @@
 // Design token database by the script:
 //   dev/tools/gen_defaults/bin/gen_defaults.dart.
 
-// Token database version: v0_143
+// Token database version: v0_158
 
 class _AppBarDefaultsM3 extends AppBarTheme {
   _AppBarDefaultsM3(this.context)
diff --git a/framework/lib/src/material/app_bar_theme.dart b/framework/lib/src/material/app_bar_theme.dart
index bb4734c..0229bcd 100644
--- a/framework/lib/src/material/app_bar_theme.dart
+++ b/framework/lib/src/material/app_bar_theme.dart
@@ -8,7 +8,6 @@
 import 'package:flute/services.dart';
 import 'package:flute/widgets.dart';
 
-import 'text_theme.dart';
 import 'theme.dart';
 
 /// Overrides the default values of visual properties for descendant
@@ -43,11 +42,6 @@
     this.shape,
     this.iconTheme,
     this.actionsIconTheme,
-    @Deprecated(
-      'This property is no longer used, please use toolbarTextStyle and titleTextStyle instead. '
-      'This feature was deprecated after v2.4.0-0.0.pre.',
-    )
-    this.textTheme,
     this.centerTitle,
     this.titleSpacing,
     this.toolbarHeight,
@@ -161,24 +155,6 @@
   ///    [AppBar.foregroundColor] in all descendant [AppBar] widgets.
   final IconThemeData? actionsIconTheme;
 
-  /// This property is deprecated, please use [toolbarTextStyle] and
-  /// [titleTextStyle] instead.
-  ///
-  /// Overrides the default value of the obsolete [AppBar.textTheme]
-  /// property in all descendant [AppBar] widgets.
-  ///
-  /// See also:
-  ///
-  ///  * [toolbarTextStyle], which overrides the default value of
-  ///    [AppBar.toolbarTextStyle in all descendant [AppBar] widgets.
-  ///  * [titleTextStyle], which overrides the default value of
-  ///    [AppBar.titleTextStyle in all descendant [AppBar] widgets.
-  @Deprecated(
-    'This property is no longer used, please use toolbarTextStyle and titleTextStyle instead. '
-    'This feature was deprecated after v2.4.0-0.0.pre.',
-  )
-  final TextTheme? textTheme;
-
   /// Overrides the default value of [AppBar.centerTitle]
   /// property in all descendant [AppBar] widgets.
   final bool? centerTitle;
@@ -252,11 +228,6 @@
     Color? surfaceTintColor,
     ShapeBorder? shape,
     IconThemeData? iconTheme,
-    @Deprecated(
-      'This property is no longer used, please use toolbarTextStyle and titleTextStyle instead. '
-      'This feature was deprecated after v2.4.0-0.0.pre.',
-    )
-    TextTheme? textTheme,
     bool? centerTitle,
     double? titleSpacing,
     double? toolbarHeight,
@@ -284,7 +255,6 @@
       shape: shape ?? this.shape,
       iconTheme: iconTheme ?? this.iconTheme,
       actionsIconTheme: actionsIconTheme ?? this.actionsIconTheme,
-      textTheme: textTheme ?? this.textTheme,
       centerTitle: centerTitle ?? this.centerTitle,
       titleSpacing: titleSpacing ?? this.titleSpacing,
       toolbarHeight: toolbarHeight ?? this.toolbarHeight,
@@ -306,7 +276,6 @@
   ///
   /// {@macro dart.ui.shadow.lerp}
   static AppBarTheme lerp(AppBarTheme? a, AppBarTheme? b, double t) {
-    assert(t != null);
     return AppBarTheme(
       brightness: t < 0.5 ? a?.brightness : b?.brightness,
       backgroundColor: Color.lerp(a?.backgroundColor, b?.backgroundColor, t),
@@ -318,7 +287,6 @@
       shape: ShapeBorder.lerp(a?.shape, b?.shape, t),
       iconTheme: IconThemeData.lerp(a?.iconTheme, b?.iconTheme, t),
       actionsIconTheme: IconThemeData.lerp(a?.actionsIconTheme, b?.actionsIconTheme, t),
-      textTheme: TextTheme.lerp(a?.textTheme, b?.textTheme, t),
       centerTitle: t < 0.5 ? a?.centerTitle : b?.centerTitle,
       titleSpacing: lerpDouble(a?.titleSpacing, b?.titleSpacing, t),
       toolbarHeight: lerpDouble(a?.toolbarHeight, b?.toolbarHeight, t),
@@ -341,7 +309,6 @@
     shape,
     iconTheme,
     actionsIconTheme,
-    textTheme,
     centerTitle,
     titleSpacing,
     toolbarHeight,
@@ -370,7 +337,6 @@
         && other.shape == shape
         && other.iconTheme == iconTheme
         && other.actionsIconTheme == actionsIconTheme
-        && other.textTheme == textTheme
         && other.centerTitle == centerTitle
         && other.titleSpacing == titleSpacing
         && other.toolbarHeight == toolbarHeight
@@ -393,7 +359,6 @@
     properties.add(DiagnosticsProperty<ShapeBorder>('shape', shape, defaultValue: null));
     properties.add(DiagnosticsProperty<IconThemeData>('iconTheme', iconTheme, defaultValue: null));
     properties.add(DiagnosticsProperty<IconThemeData>('actionsIconTheme', actionsIconTheme, defaultValue: null));
-    properties.add(DiagnosticsProperty<TextTheme>('textTheme', textTheme, defaultValue: null));
     properties.add(DiagnosticsProperty<bool>('centerTitle', centerTitle, defaultValue: null));
     properties.add(DiagnosticsProperty<double>('titleSpacing', titleSpacing, defaultValue: null));
     properties.add(DiagnosticsProperty<double>('toolbarHeight', toolbarHeight, defaultValue: null));
diff --git a/framework/lib/src/material/autocomplete.dart b/framework/lib/src/material/autocomplete.dart
index 50c7112..2e465cf 100644
--- a/framework/lib/src/material/autocomplete.dart
+++ b/framework/lib/src/material/autocomplete.dart
@@ -43,8 +43,7 @@
     this.optionsMaxHeight = 200.0,
     this.optionsViewBuilder,
     this.initialValue,
-  }) : assert(displayStringForOption != null),
-       assert(optionsBuilder != null);
+  });
 
   /// {@macro flutter.widgets.RawAutocomplete.displayStringForOption}
   final AutocompleteOptionToString<T> displayStringForOption;
diff --git a/framework/lib/src/material/back_button.dart b/framework/lib/src/material/back_button.dart
index 04cffca..3e88494 100644
--- a/framework/lib/src/material/back_button.dart
+++ b/framework/lib/src/material/back_button.dart
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import 'package:flute/foundation.dart';
 import 'package:flute/widgets.dart';
 
 import 'debug.dart';
@@ -27,22 +28,39 @@
   /// the current platform (as obtained from the [Theme]).
   const BackButtonIcon({ super.key });
 
-  /// Returns the appropriate "back" icon for the given `platform`.
-  static IconData _getIconData(TargetPlatform platform) {
-    switch (platform) {
+  @override
+  Widget build(BuildContext context) {
+    final String? semanticsLabel;
+    final IconData data;
+    switch (Theme.of(context).platform) {
       case TargetPlatform.android:
       case TargetPlatform.fuchsia:
       case TargetPlatform.linux:
       case TargetPlatform.windows:
-        return Icons.arrow_back;
+        data = Icons.arrow_back;
+        break;
       case TargetPlatform.iOS:
       case TargetPlatform.macOS:
-        return Icons.arrow_back_ios;
+        data = Icons.arrow_back_ios;
+        break;
     }
-  }
+    // This can't use the platform from Theme because it is the Android OS that
+    // expects the duplicated tooltip and label.
+    switch (defaultTargetPlatform) {
+      case TargetPlatform.android:
+        semanticsLabel = MaterialLocalizations.of(context).backButtonTooltip;
+        break;
+      case TargetPlatform.fuchsia:
+      case TargetPlatform.linux:
+      case TargetPlatform.windows:
+      case TargetPlatform.iOS:
+      case TargetPlatform.macOS:
+        semanticsLabel = null;
+        break;
+    }
 
-  @override
-  Widget build(BuildContext context) => Icon(_getIconData(Theme.of(context).platform));
+    return Icon(data, semanticLabel: semanticsLabel);
+  }
 }
 
 /// A Material Design back button.
@@ -149,8 +167,23 @@
   @override
   Widget build(BuildContext context) {
     assert(debugCheckHasMaterialLocalizations(context));
+    final String? semanticsLabel;
+    // This can't use the platform from Theme because it is the Android OS that
+    // expects the duplicated tooltip and label.
+    switch (defaultTargetPlatform) {
+      case TargetPlatform.android:
+        semanticsLabel = MaterialLocalizations.of(context).closeButtonTooltip;
+        break;
+      case TargetPlatform.fuchsia:
+      case TargetPlatform.linux:
+      case TargetPlatform.windows:
+      case TargetPlatform.iOS:
+      case TargetPlatform.macOS:
+        semanticsLabel = null;
+        break;
+    }
     return IconButton(
-      icon: const Icon(Icons.close),
+      icon: Icon(Icons.close, semanticLabel: semanticsLabel),
       color: color,
       tooltip: MaterialLocalizations.of(context).closeButtonTooltip,
       onPressed: () {
diff --git a/framework/lib/src/material/badge.dart b/framework/lib/src/material/badge.dart
index ffa202c..2d8d65d 100644
--- a/framework/lib/src/material/badge.dart
+++ b/framework/lib/src/material/badge.dart
@@ -193,7 +193,7 @@
 // Design token database by the script:
 //   dev/tools/gen_defaults/bin/gen_defaults.dart.
 
-// Token database version: v0_143
+// Token database version: v0_158
 
 class _BadgeDefaultsM3 extends BadgeThemeData {
   _BadgeDefaultsM3(this.context) : super(
diff --git a/framework/lib/src/material/badge_theme.dart b/framework/lib/src/material/badge_theme.dart
index 29ee237..1b0298a 100644
--- a/framework/lib/src/material/badge_theme.dart
+++ b/framework/lib/src/material/badge_theme.dart
@@ -153,7 +153,7 @@
     super.key,
     required this.data,
     required super.child,
-  }) : assert(data != null);
+  });
 
   /// Specifies the default color and size overrides for descendant [Badge] widgets.
   final BadgeThemeData data;
diff --git a/framework/lib/src/material/banner.dart b/framework/lib/src/material/banner.dart
index 14617bb..45c19bf 100644
--- a/framework/lib/src/material/banner.dart
+++ b/framework/lib/src/material/banner.dart
@@ -106,15 +106,13 @@
     this.shadowColor,
     this.dividerColor,
     this.padding,
+    this.margin,
     this.leadingPadding,
     this.forceActionsBelow = false,
     this.overflowAlignment = OverflowBarAlignment.end,
     this.animation,
     this.onVisible,
-  }) : assert(elevation == null || elevation >= 0.0),
-       assert(content != null),
-       assert(actions != null),
-       assert(forceActionsBelow != null);
+  }) : assert(elevation == null || elevation >= 0.0);
 
   /// The content of the [MaterialBanner].
   ///
@@ -188,6 +186,12 @@
   /// `EdgeInsetsDirectional.only(start: 16.0, top: 2.0)`.
   final EdgeInsetsGeometry? padding;
 
+  /// Empty space to surround the [MaterialBanner].
+  ///
+  /// If the [margin] is null then this defaults to
+  /// 0 if the banner's [elevation] is 0, 10 otherwise.
+  final EdgeInsetsGeometry? margin;
+
   /// The amount of space by which to inset the [leading] widget.
   ///
   /// This defaults to `EdgeInsetsDirectional.only(end: 16.0)`.
@@ -240,6 +244,7 @@
       leading: leading,
       backgroundColor: backgroundColor,
       padding: padding,
+      margin: margin,
       leadingPadding: leadingPadding,
       forceActionsBelow: forceActionsBelow,
       overflowAlignment: overflowAlignment,
@@ -293,7 +298,7 @@
   @override
   Widget build(BuildContext context) {
     assert(debugCheckHasMediaQuery(context));
-    final MediaQueryData mediaQueryData = MediaQuery.of(context);
+    final bool accessibleNavigation = MediaQuery.accessibleNavigationOf(context);
 
     assert(widget.actions.isNotEmpty);
 
@@ -321,6 +326,7 @@
     );
 
     final double elevation = widget.elevation ?? bannerTheme.elevation ?? 0.0;
+    final EdgeInsetsGeometry margin = widget.margin ?? EdgeInsets.only(bottom: elevation > 0 ? 10.0 : 0.0);
     final Color backgroundColor = widget.backgroundColor
         ?? bannerTheme.backgroundColor
         ?? defaults.backgroundColor!;
@@ -337,7 +343,7 @@
         ?? defaults.contentTextStyle;
 
     Widget materialBanner = Container(
-      margin: EdgeInsets.only(bottom: elevation > 0 ? 10.0 : 0.0),
+      margin: margin,
       child: Material(
         elevation: elevation,
         color: backgroundColor,
@@ -399,7 +405,7 @@
       onDismiss: () {
         ScaffoldMessenger.of(context).removeCurrentMaterialBanner(reason: MaterialBannerClosedReason.dismiss);
       },
-      child: mediaQueryData.accessibleNavigation
+      child: accessibleNavigation
           ? materialBanner
           : SlideTransition(
         position: slideOutAnimation,
@@ -408,7 +414,7 @@
     );
 
     final Widget materialBannerTransition;
-    if (mediaQueryData.accessibleNavigation) {
+    if (accessibleNavigation) {
       materialBannerTransition = materialBanner;
     } else {
       materialBannerTransition = AnimatedBuilder(
@@ -453,7 +459,7 @@
 // Design token database by the script:
 //   dev/tools/gen_defaults/bin/gen_defaults.dart.
 
-// Token database version: v0_143
+// Token database version: v0_158
 
 class _BannerDefaultsM3 extends MaterialBannerThemeData {
   const _BannerDefaultsM3(this.context)
diff --git a/framework/lib/src/material/banner_theme.dart b/framework/lib/src/material/banner_theme.dart
index d52edd6..0083340 100644
--- a/framework/lib/src/material/banner_theme.dart
+++ b/framework/lib/src/material/banner_theme.dart
@@ -101,7 +101,6 @@
   ///
   /// {@macro dart.ui.shadow.lerp}
   static MaterialBannerThemeData lerp(MaterialBannerThemeData? a, MaterialBannerThemeData? b, double t) {
-    assert(t != null);
     return MaterialBannerThemeData(
       backgroundColor: Color.lerp(a?.backgroundColor, b?.backgroundColor, t),
       surfaceTintColor: Color.lerp(a?.surfaceTintColor, b?.surfaceTintColor, t),
diff --git a/framework/lib/src/material/bottom_app_bar.dart b/framework/lib/src/material/bottom_app_bar.dart
index d37e99d..41e83cb 100644
--- a/framework/lib/src/material/bottom_app_bar.dart
+++ b/framework/lib/src/material/bottom_app_bar.dart
@@ -72,9 +72,7 @@
     this.padding,
     this.surfaceTintColor,
     this.height,
-  }) : assert(elevation == null || elevation >= 0.0),
-       assert(notchMargin != null),
-       assert(clipBehavior != null);
+  }) : assert(elevation == null || elevation >= 0.0);
 
   /// The widget below this widget in the tree.
   ///
@@ -196,10 +194,9 @@
           key: materialKey,
           type: isMaterial3 ? MaterialType.canvas : MaterialType.transparency,
           elevation: elevation,
+          color: isMaterial3 ? effectiveColor : null,
           surfaceTintColor: surfaceTintColor,
-          child: child == null
-            ? null
-            : SafeArea(child: child),
+          child: SafeArea(child: child),
         ),
       ),
     );
@@ -212,10 +209,7 @@
     required this.shape,
     required this.materialKey,
     required this.notchMargin,
-  }) : assert(geometry != null),
-       assert(shape != null),
-       assert(notchMargin != null),
-       super(reclip: geometry);
+  }) : super(reclip: geometry);
 
   final ValueListenable<ScaffoldGeometry> geometry;
   final NotchedShape shape;
@@ -275,7 +269,7 @@
 // Design token database by the script:
 //   dev/tools/gen_defaults/bin/gen_defaults.dart.
 
-// Token database version: v0_143
+// Token database version: v0_158
 
 class _BottomAppBarDefaultsM3 extends BottomAppBarTheme {
   const _BottomAppBarDefaultsM3(this.context)
diff --git a/framework/lib/src/material/bottom_app_bar_theme.dart b/framework/lib/src/material/bottom_app_bar_theme.dart
index ff45376..e59c538 100644
--- a/framework/lib/src/material/bottom_app_bar_theme.dart
+++ b/framework/lib/src/material/bottom_app_bar_theme.dart
@@ -94,7 +94,6 @@
   ///
   /// {@macro dart.ui.shadow.lerp}
   static BottomAppBarTheme lerp(BottomAppBarTheme? a, BottomAppBarTheme? b, double t) {
-    assert(t != null);
     return BottomAppBarTheme(
       color: Color.lerp(a?.color, b?.color, t),
       elevation: lerpDouble(a?.elevation, b?.elevation, t),
diff --git a/framework/lib/src/material/bottom_navigation_bar.dart b/framework/lib/src/material/bottom_navigation_bar.dart
index 46990f9..8895478 100644
--- a/framework/lib/src/material/bottom_navigation_bar.dart
+++ b/framework/lib/src/material/bottom_navigation_bar.dart
@@ -199,21 +199,20 @@
     this.enableFeedback,
     this.landscapeLayout,
     this.useLegacyColorScheme = true,
-  }) : assert(items != null),
-       assert(items.length >= 2),
+  }) : assert(items.length >= 2),
        assert(
         items.every((BottomNavigationBarItem item) => item.label != null),
         'Every item must have a non-null label',
        ),
        assert(0 <= currentIndex && currentIndex < items.length),
        assert(elevation == null || elevation >= 0.0),
-       assert(iconSize != null && iconSize >= 0.0),
+       assert(iconSize >= 0.0),
        assert(
          selectedItemColor == null || fixedColor == null,
          'Either selectedItemColor or fixedColor can be specified, but not both',
        ),
-       assert(selectedFontSize != null && selectedFontSize >= 0.0),
-       assert(unselectedFontSize != null && unselectedFontSize >= 0.0),
+       assert(selectedFontSize >= 0.0),
+       assert(unselectedFontSize >= 0.0),
        selectedItemColor = selectedItemColor ?? fixedColor;
 
   /// Defines the appearance of the button items that are arrayed within the
@@ -421,13 +420,7 @@
     required this.mouseCursor,
     required this.enableFeedback,
     required this.layout,
-  }) : assert(type != null),
-       assert(item != null),
-       assert(animation != null),
-       assert(selected != null),
-       assert(selectedLabelStyle != null),
-       assert(unselectedLabelStyle != null),
-       assert(mouseCursor != null);
+  });
 
   final BottomNavigationBarType type;
   final BottomNavigationBarItem item;
@@ -607,8 +600,7 @@
 
   @override
   Widget build(BuildContext context) {
-    final MediaQueryData data = MediaQuery.of(context);
-    if (data.orientation == Orientation.landscape && layout == BottomNavigationBarLandscapeLayout.linear) {
+    if (MediaQuery.orientationOf(context) == Orientation.landscape && layout == BottomNavigationBarLandscapeLayout.linear) {
       return Align(
         heightFactor: 1,
         child: Row(
@@ -634,8 +626,7 @@
     required this.item,
     required this.selectedIconTheme,
     required this.unselectedIconTheme,
-  }) : assert(selected != null),
-       assert(item != null);
+  });
 
   final ColorTween colorTween;
   final Animation<double> animation;
@@ -678,13 +669,7 @@
     required this.unselectedLabelStyle,
     required this.showSelectedLabels,
     required this.showUnselectedLabels,
-  }) : assert(colorTween != null),
-       assert(animation != null),
-       assert(item != null),
-       assert(selectedLabelStyle != null),
-       assert(unselectedLabelStyle != null),
-       assert(showSelectedLabels != null),
-       assert(showUnselectedLabels != null);
+  });
 
   final ColorTween colorTween;
   final Animation<double> animation;
@@ -935,7 +920,6 @@
 
   List<Widget> _createTiles(BottomNavigationBarLandscapeLayout layout) {
     final MaterialLocalizations localizations = MaterialLocalizations.of(context);
-    assert(localizations != null);
 
     final ThemeData themeData = Theme.of(context);
     final BottomNavigationBarThemeData bottomTheme = BottomNavigationBarTheme.of(context);
@@ -1114,7 +1098,7 @@
     final BottomNavigationBarLandscapeLayout layout = widget.landscapeLayout
       ?? bottomTheme.landscapeLayout
       ?? BottomNavigationBarLandscapeLayout.spread;
-    final double additionalBottomPadding = MediaQuery.of(context).viewPadding.bottom;
+    final double additionalBottomPadding = MediaQuery.viewPaddingOf(context).bottom;
 
     Color? backgroundColor;
     switch (_effectiveType) {
@@ -1180,14 +1164,13 @@
 
   @override
   Widget build(BuildContext context) {
-    final MediaQueryData data = MediaQuery.of(context);
     Widget alignedChild = child;
-    if (data.orientation == Orientation.landscape && layout == BottomNavigationBarLandscapeLayout.centered) {
+    if (MediaQuery.orientationOf(context) == Orientation.landscape && layout == BottomNavigationBarLandscapeLayout.centered) {
       alignedChild = Align(
         alignment: Alignment.bottomCenter,
         heightFactor: 1,
         child: SizedBox(
-          width: data.size.height,
+          width: MediaQuery.sizeOf(context).height,
           child: child,
         ),
       );
@@ -1207,9 +1190,7 @@
     required this.index,
     required this.color,
     required TickerProvider vsync,
-  }) : assert(state != null),
-       assert(index != null),
-       assert(color != null) {
+  }) {
     controller = AnimationController(
       duration: kThemeAnimationDuration,
       vsync: vsync,
@@ -1252,8 +1233,7 @@
   _RadialPainter({
     required this.circles,
     required this.textDirection,
-  }) : assert(circles != null),
-       assert(textDirection != null);
+  });
 
   final List<_Circle> circles;
   final TextDirection textDirection;
diff --git a/framework/lib/src/material/bottom_navigation_bar_theme.dart b/framework/lib/src/material/bottom_navigation_bar_theme.dart
index e4e6ac9..7d150ef 100644
--- a/framework/lib/src/material/bottom_navigation_bar_theme.dart
+++ b/framework/lib/src/material/bottom_navigation_bar_theme.dart
@@ -174,7 +174,6 @@
   ///
   /// {@macro dart.ui.shadow.lerp}
   static BottomNavigationBarThemeData lerp(BottomNavigationBarThemeData? a, BottomNavigationBarThemeData? b, double t) {
-    assert(t != null);
     return BottomNavigationBarThemeData(
       backgroundColor: Color.lerp(a?.backgroundColor, b?.backgroundColor, t),
       elevation: lerpDouble(a?.elevation, b?.elevation, t),
@@ -280,7 +279,7 @@
     super.key,
     required this.data,
     required super.child,
-  }) : assert(data != null);
+  });
 
   /// The properties used for all descendant [BottomNavigationBar] widgets.
   final BottomNavigationBarThemeData data;
diff --git a/framework/lib/src/material/bottom_sheet.dart b/framework/lib/src/material/bottom_sheet.dart
index 1ba592f..887d7e2 100644
--- a/framework/lib/src/material/bottom_sheet.dart
+++ b/framework/lib/src/material/bottom_sheet.dart
@@ -5,6 +5,7 @@
 import 'package:engine/ui.dart' show lerpDouble;
 
 import 'package:flute/foundation.dart';
+import 'package:flute/rendering.dart';
 import 'package:flute/widgets.dart';
 
 import 'bottom_sheet_theme.dart';
@@ -83,10 +84,7 @@
     this.constraints,
     required this.onClosing,
     required this.builder,
-  }) : assert(enableDrag != null),
-       assert(onClosing != null),
-       assert(builder != null),
-       assert(elevation == null || elevation >= 0.0);
+  }) : assert(elevation == null || elevation >= 0.0);
 
   /// The animation controller that controls the bottom sheet's entrance and
   /// exit animations.
@@ -319,16 +317,130 @@
 
 // See scaffold.dart
 
+typedef _SizeChangeCallback<Size> = void Function(Size);
 
-// MODAL BOTTOM SHEETS
-class _ModalBottomSheetLayout extends SingleChildLayoutDelegate {
-  _ModalBottomSheetLayout(this.progress, this.isScrollControlled);
+class _BottomSheetLayoutWithSizeListener extends SingleChildRenderObjectWidget {
 
-  final double progress;
+  const _BottomSheetLayoutWithSizeListener({
+    required this.animationValue,
+    required this.isScrollControlled,
+    required this.onChildSizeChanged,
+    super.child,
+  });
+
+  final double animationValue;
   final bool isScrollControlled;
+  final _SizeChangeCallback<Size> onChildSizeChanged;
 
   @override
-  BoxConstraints getConstraintsForChild(BoxConstraints constraints) {
+  _RenderBottomSheetLayoutWithSizeListener createRenderObject(BuildContext context) {
+    return _RenderBottomSheetLayoutWithSizeListener(
+      animationValue: animationValue,
+      isScrollControlled: isScrollControlled,
+      onChildSizeChanged: onChildSizeChanged,
+    );
+  }
+
+  @override
+  void updateRenderObject(BuildContext context, _RenderBottomSheetLayoutWithSizeListener renderObject) {
+    renderObject.onChildSizeChanged = onChildSizeChanged;
+    renderObject.animationValue = animationValue;
+    renderObject.isScrollControlled = isScrollControlled;
+  }
+}
+
+class _RenderBottomSheetLayoutWithSizeListener extends RenderShiftedBox {
+  _RenderBottomSheetLayoutWithSizeListener({
+    RenderBox? child,
+    required _SizeChangeCallback<Size> onChildSizeChanged,
+    required double animationValue,
+    required bool isScrollControlled,
+  }) : _animationValue = animationValue,
+       _isScrollControlled = isScrollControlled,
+       _onChildSizeChanged = onChildSizeChanged,
+       super(child);
+
+  Size _lastSize = Size.zero;
+
+  _SizeChangeCallback<Size> get onChildSizeChanged => _onChildSizeChanged;
+  _SizeChangeCallback<Size> _onChildSizeChanged;
+    set onChildSizeChanged(_SizeChangeCallback<Size> newCallback) {
+    if (_onChildSizeChanged == newCallback) {
+      return;
+    }
+
+    _onChildSizeChanged = newCallback;
+    markNeedsLayout();
+  }
+
+  double get animationValue => _animationValue;
+  double _animationValue;
+  set animationValue(double newValue) {
+    if (_animationValue == newValue) {
+      return;
+    }
+
+    _animationValue = newValue;
+    markNeedsLayout();
+  }
+
+  bool get isScrollControlled => _isScrollControlled;
+  bool _isScrollControlled;
+  set isScrollControlled(bool newValue) {
+    if (_isScrollControlled == newValue) {
+      return;
+    }
+
+    _isScrollControlled = newValue;
+    markNeedsLayout();
+  }
+
+  Size _getSize(BoxConstraints constraints) {
+    return constraints.constrain(constraints.biggest);
+  }
+
+  @override
+  double computeMinIntrinsicWidth(double height) {
+    final double width = _getSize(BoxConstraints.tightForFinite(height: height)).width;
+    if (width.isFinite) {
+      return width;
+    }
+    return 0.0;
+  }
+
+  @override
+  double computeMaxIntrinsicWidth(double height) {
+    final double width = _getSize(BoxConstraints.tightForFinite(height: height)).width;
+    if (width.isFinite) {
+      return width;
+    }
+    return 0.0;
+  }
+
+  @override
+  double computeMinIntrinsicHeight(double width) {
+    final double height = _getSize(BoxConstraints.tightForFinite(width: width)).height;
+    if (height.isFinite) {
+      return height;
+    }
+    return 0.0;
+  }
+
+  @override
+  double computeMaxIntrinsicHeight(double width) {
+    final double height = _getSize(BoxConstraints.tightForFinite(width: width)).height;
+    if (height.isFinite) {
+      return height;
+    }
+    return 0.0;
+  }
+
+  @override
+  Size computeDryLayout(BoxConstraints constraints) {
+    return _getSize(constraints);
+  }
+
+    BoxConstraints _getConstraintsForChild(BoxConstraints constraints) {
     return BoxConstraints(
       minWidth: constraints.maxWidth,
       maxWidth: constraints.maxWidth,
@@ -338,14 +450,26 @@
     );
   }
 
-  @override
-  Offset getPositionForChild(Size size, Size childSize) {
-    return Offset(0.0, size.height - childSize.height * progress);
+  Offset _getPositionForChild(Size size, Size childSize) {
+    return Offset(0.0, size.height - childSize.height * animationValue);
   }
 
   @override
-  bool shouldRelayout(_ModalBottomSheetLayout oldDelegate) {
-    return progress != oldDelegate.progress;
+  void performLayout() {
+    size = _getSize(constraints);
+    if (child != null) {
+      final BoxConstraints childConstraints = _getConstraintsForChild(constraints);
+      assert(childConstraints.debugAssertIsValid(isAppliedConstraint: true));
+      child!.layout(childConstraints, parentUsesSize: !childConstraints.isTight);
+      final BoxParentData childParentData = child!.parentData! as BoxParentData;
+      childParentData.offset = _getPositionForChild(size, childConstraints.isTight ? childConstraints.smallest : child!.size);
+      final Size childSize = childConstraints.isTight ? childConstraints.smallest : child!.size;
+
+      if (_lastSize != childSize) {
+        _lastSize = childSize;
+        _onChildSizeChanged.call(_lastSize);
+      }
+    }
   }
 }
 
@@ -360,8 +484,7 @@
     this.constraints,
     this.isScrollControlled = false,
     this.enableDrag = true,
-  }) : assert(isScrollControlled != null),
-       assert(enableDrag != null);
+  });
 
   final ModalBottomSheetRoute<T> route;
   final bool isScrollControlled;
@@ -392,6 +515,10 @@
     }
   }
 
+  EdgeInsets _getNewClipDetails(Size topLayerSize) {
+    return EdgeInsets.fromLTRB(0, 0, 0, topLayerSize.height);
+  }
+
   void handleDragStart(DragStartDetails details) {
     // Allow the bottom sheet to track the user's finger accurately.
     animationCurve = Curves.linear;
@@ -409,7 +536,6 @@
   Widget build(BuildContext context) {
     assert(debugCheckHasMediaQuery(context));
     assert(debugCheckHasMaterialLocalizations(context));
-    final MediaQueryData mediaQuery = MediaQuery.of(context);
     final MaterialLocalizations localizations = MaterialLocalizations.of(context);
     final String routeLabel = _getRouteLabel(localizations);
 
@@ -436,7 +562,7 @@
         // Disable the initial animation when accessible navigation is on so
         // that the semantics are added to the tree at the correct time.
         final double animationValue = animationCurve.transform(
-            mediaQuery.accessibleNavigation ? 1.0 : widget.route.animation!.value,
+            MediaQuery.accessibleNavigationOf(context) ? 1.0 : widget.route.animation!.value,
         );
         return Semantics(
           scopesRoute: true,
@@ -444,8 +570,14 @@
           label: routeLabel,
           explicitChildNodes: true,
           child: ClipRect(
-            child: CustomSingleChildLayout(
-              delegate: _ModalBottomSheetLayout(animationValue, widget.isScrollControlled),
+            child: _BottomSheetLayoutWithSizeListener(
+              onChildSizeChanged: (Size size) {
+                widget.route._didChangeBarrierSemanticsClip(
+                  _getNewClipDetails(size),
+                );
+              },
+              animationValue: animationValue,
+              isScrollControlled: widget.isScrollControlled,
               child: child,
             ),
           ),
@@ -517,6 +649,7 @@
     required this.builder,
     this.capturedThemes,
     this.barrierLabel,
+    this.barrierOnTapHint,
     this.backgroundColor,
     this.elevation,
     this.shape,
@@ -530,9 +663,7 @@
     this.transitionAnimationController,
     this.anchorPoint,
     this.useSafeArea = false,
-  }) : assert(isScrollControlled != null),
-       assert(isDismissible != null),
-       assert(enableDrag != null);
+  });
 
   /// A builder for the contents of the sheet.
   ///
@@ -647,6 +778,35 @@
   /// Default is false.
   final bool useSafeArea;
 
+  /// {@template flutter.material.ModalBottomSheetRoute.barrierOnTapHint}
+  /// The semantic hint text that informs users what will happen if they
+  /// tap on the widget. Announced in the format of 'Double tap to ...'.
+  ///
+  /// If the field is null, the default hint will be used, which results in
+  /// announcement of 'Double tap to activate'.
+  /// {@endtemplate}
+  ///
+  /// See also:
+  ///
+  ///  * [barrierDismissible], which controls the behavior of the barrier when
+  ///    tapped.
+  ///  * [ModalBarrier], which uses this field as onTapHint when it has an onTap action.
+  final String? barrierOnTapHint;
+
+  final ValueNotifier<EdgeInsets> _clipDetailsNotifier = ValueNotifier<EdgeInsets>(EdgeInsets.zero);
+
+  /// Updates the details regarding how the [SemanticsNode.rect] (focus) of
+  /// the barrier for this [ModalBottomSheetRoute] should be clipped.
+  ///
+  /// returns true if the clipDetails did change and false otherwise.
+  bool _didChangeBarrierSemanticsClip(EdgeInsets newClipDetails) {
+    if (_clipDetailsNotifier.value == newClipDetails) {
+      return false;
+    }
+    _clipDetailsNotifier.value = newClipDetails;
+    return true;
+  }
+
   @override
   Duration get transitionDuration => _bottomSheetEnterDuration;
 
@@ -711,6 +871,35 @@
 
     return capturedThemes?.wrap(bottomSheet) ?? bottomSheet;
   }
+
+  @override
+  Widget buildModalBarrier() {
+    if (barrierColor.alpha != 0 && !offstage) { // changedInternalState is called if barrierColor or offstage updates
+      assert(barrierColor != barrierColor.withOpacity(0.0));
+      final Animation<Color?> color = animation!.drive(
+        ColorTween(
+          begin: barrierColor.withOpacity(0.0),
+          end: barrierColor, // changedInternalState is called if barrierColor updates
+        ).chain(CurveTween(curve: barrierCurve)), // changedInternalState is called if barrierCurve updates
+      );
+      return AnimatedModalBarrier(
+        color: color,
+        dismissible: barrierDismissible, // changedInternalState is called if barrierDismissible updates
+        semanticsLabel: barrierLabel, // changedInternalState is called if barrierLabel updates
+        barrierSemanticsDismissible: semanticsDismissible,
+        clipDetailsNotifier: _clipDetailsNotifier,
+        semanticsOnTapHint: barrierOnTapHint,
+      );
+    } else {
+      return ModalBarrier(
+        dismissible: barrierDismissible, // changedInternalState is called if barrierDismissible updates
+        semanticsLabel: barrierLabel, // changedInternalState is called if barrierLabel updates
+        barrierSemanticsDismissible: semanticsDismissible,
+        clipDetailsNotifier: _clipDetailsNotifier,
+        semanticsOnTapHint: barrierOnTapHint,
+      );
+    }
+  }
 }
 
 // TODO(guidezpl): Look into making this public. A copy of this class is in
@@ -736,15 +925,14 @@
   const _BottomSheetSuspendedCurve(
     this.startingPoint, {
     this.curve = Curves.easeOutCubic,
-  }) : assert(startingPoint != null),
-       assert(curve != null);
+  });
 
   /// The progress value at which [curve] should begin.
-  ///
-  /// This defaults to [Curves.easeOutCubic].
   final double startingPoint;
 
   /// The curve to use when [startingPoint] is reached.
+  ///
+  /// This defaults to [Curves.easeOutCubic].
   final Curve curve;
 
   @override
@@ -835,21 +1023,17 @@
   AnimationController? transitionAnimationController,
   Offset? anchorPoint,
 }) {
-  assert(context != null);
-  assert(builder != null);
-  assert(isScrollControlled != null);
-  assert(useRootNavigator != null);
-  assert(isDismissible != null);
-  assert(enableDrag != null);
   assert(debugCheckHasMediaQuery(context));
   assert(debugCheckHasMaterialLocalizations(context));
 
   final NavigatorState navigator = Navigator.of(context, rootNavigator: useRootNavigator);
+  final MaterialLocalizations localizations = MaterialLocalizations.of(context);
   return navigator.push(ModalBottomSheetRoute<T>(
     builder: builder,
     capturedThemes: InheritedTheme.capture(from: context, to: navigator.context),
     isScrollControlled: isScrollControlled,
-    barrierLabel: MaterialLocalizations.of(context).modalBarrierDismissLabel,
+    barrierLabel: localizations.scrimLabel,
+    barrierOnTapHint: localizations.scrimOnTapHint(localizations.bottomSheetLabel),
     backgroundColor: backgroundColor,
     elevation: elevation,
     shape: shape,
@@ -920,8 +1104,6 @@
   bool? enableDrag,
   AnimationController? transitionAnimationController,
 }) {
-  assert(context != null);
-  assert(builder != null);
   assert(debugCheckHasScaffold(context));
 
   return Scaffold.of(context).showBottomSheet<T>(
@@ -945,7 +1127,7 @@
 // Design token database by the script:
 //   dev/tools/gen_defaults/bin/gen_defaults.dart.
 
-// Token database version: v0_143
+// Token database version: v0_158
 
 class _BottomSheetDefaultsM3 extends BottomSheetThemeData {
    const _BottomSheetDefaultsM3(this.context)
diff --git a/framework/lib/src/material/bottom_sheet_theme.dart b/framework/lib/src/material/bottom_sheet_theme.dart
index 1c52523..c0ebc22 100644
--- a/framework/lib/src/material/bottom_sheet_theme.dart
+++ b/framework/lib/src/material/bottom_sheet_theme.dart
@@ -118,7 +118,6 @@
   ///
   /// {@macro dart.ui.shadow.lerp}
   static BottomSheetThemeData? lerp(BottomSheetThemeData? a, BottomSheetThemeData? b, double t) {
-    assert(t != null);
     if (a == null && b == null) {
       return null;
     }
diff --git a/framework/lib/src/material/button.dart b/framework/lib/src/material/button.dart
index 41b6ff0..3ee3642 100644
--- a/framework/lib/src/material/button.dart
+++ b/framework/lib/src/material/button.dart
@@ -74,17 +74,11 @@
     this.child,
     this.enableFeedback = true,
   }) : materialTapTargetSize = materialTapTargetSize ?? MaterialTapTargetSize.padded,
-       assert(shape != null),
-       assert(elevation != null && elevation >= 0.0),
-       assert(focusElevation != null && focusElevation >= 0.0),
-       assert(hoverElevation != null && hoverElevation >= 0.0),
-       assert(highlightElevation != null && highlightElevation >= 0.0),
-       assert(disabledElevation != null && disabledElevation >= 0.0),
-       assert(padding != null),
-       assert(constraints != null),
-       assert(animationDuration != null),
-       assert(clipBehavior != null),
-       assert(autofocus != null);
+       assert(elevation >= 0.0),
+       assert(focusElevation >= 0.0),
+       assert(hoverElevation >= 0.0),
+       assert(highlightElevation >= 0.0),
+       assert(disabledElevation >= 0.0);
 
   /// Called when the button is tapped or otherwise activated.
   ///
diff --git a/framework/lib/src/material/button_bar.dart b/framework/lib/src/material/button_bar.dart
index 4542083..069c285 100644
--- a/framework/lib/src/material/button_bar.dart
+++ b/framework/lib/src/material/button_bar.dart
@@ -233,7 +233,7 @@
 class _ButtonBarRow extends Flex {
   /// Creates a button bar that attempts to display in a row, but displays in
   /// a column if there is insufficient horizontal space.
-  _ButtonBarRow({
+  const _ButtonBarRow({
     required super.children,
     super.mainAxisSize,
     super.mainAxisAlignment,
@@ -300,8 +300,7 @@
     super.verticalDirection,
     super.textBaseline,
     this.overflowButtonSpacing,
-  }) : assert(textDirection != null),
-       assert(overflowButtonSpacing == null || overflowButtonSpacing >= 0);
+  }) : assert(overflowButtonSpacing == null || overflowButtonSpacing >= 0);
 
   bool _hasCheckedLayoutWidth = false;
   double? overflowButtonSpacing;
diff --git a/framework/lib/src/material/button_bar_theme.dart b/framework/lib/src/material/button_bar_theme.dart
index 2efbcdd..20df2ae 100644
--- a/framework/lib/src/material/button_bar_theme.dart
+++ b/framework/lib/src/material/button_bar_theme.dart
@@ -146,7 +146,6 @@
   ///
   /// {@macro dart.ui.shadow.lerp}
   static ButtonBarThemeData? lerp(ButtonBarThemeData? a, ButtonBarThemeData? b, double t) {
-    assert(t != null);
     if (a == null && b == null) {
       return null;
     }
@@ -240,7 +239,7 @@
     super.key,
     required this.data,
     required super.child,
-  }) : assert(data != null);
+  });
 
   /// The properties used for all descendant [ButtonBar] widgets.
   final ButtonBarThemeData data;
diff --git a/framework/lib/src/material/button_style.dart b/framework/lib/src/material/button_style.dart
index b15cba5..18cf679 100644
--- a/framework/lib/src/material/button_style.dart
+++ b/framework/lib/src/material/button_style.dart
@@ -492,7 +492,6 @@
 
   /// Linearly interpolate between two [ButtonStyle]s.
   static ButtonStyle? lerp(ButtonStyle? a, ButtonStyle? b, double t) {
-    assert (t != null);
     if (a == null && b == null) {
       return null;
     }
diff --git a/framework/lib/src/material/button_style_button.dart b/framework/lib/src/material/button_style_button.dart
index 0d975eb..eafdde4 100644
--- a/framework/lib/src/material/button_style_button.dart
+++ b/framework/lib/src/material/button_style_button.dart
@@ -43,8 +43,7 @@
     required this.clipBehavior,
     this.statesController,
     required this.child,
-  }) : assert(autofocus != null),
-       assert(clipBehavior != null);
+  });
 
   /// Called when the button is tapped or otherwise activated.
   ///
@@ -175,10 +174,6 @@
     EdgeInsetsGeometry geometry3x,
     double textScaleFactor,
   ) {
-    assert(geometry1x != null);
-    assert(geometry2x != null);
-    assert(geometry3x != null);
-    assert(textScaleFactor != null);
 
     if (textScaleFactor <= 1) {
       return geometry1x;
@@ -260,7 +255,6 @@
     final ButtonStyle? widgetStyle = widget.style;
     final ButtonStyle? themeStyle = widget.themeStyleOf(context);
     final ButtonStyle defaultStyle = widget.defaultStyleOf(context);
-    assert(defaultStyle != null);
 
     T? effectiveValue<T>(T? Function(ButtonStyle? style) getProperty) {
       final T? widgetValue  = getProperty(widgetStyle);
diff --git a/framework/lib/src/material/button_theme.dart b/framework/lib/src/material/button_theme.dart
index dae7eaf..7e38bf1 100644
--- a/framework/lib/src/material/button_theme.dart
+++ b/framework/lib/src/material/button_theme.dart
@@ -87,11 +87,8 @@
     ColorScheme? colorScheme,
     MaterialTapTargetSize? materialTapTargetSize,
     required super.child,
-  }) : assert(textTheme != null),
-       assert(minWidth != null && minWidth >= 0.0),
-       assert(height != null && height >= 0.0),
-       assert(alignedDropdown != null),
-       assert(layoutBehavior != null),
+  }) : assert(minWidth >= 0.0),
+       assert(height >= 0.0),
        data = ButtonThemeData(
          textTheme: textTheme,
          minWidth: minWidth,
@@ -117,7 +114,7 @@
     super.key,
     required this.data,
     required super.child,
-  }) : assert(data != null);
+  });
 
   /// Specifies the color and geometry of buttons.
   final ButtonThemeData data;
@@ -194,11 +191,8 @@
     Color? splashColor,
     this.colorScheme,
     MaterialTapTargetSize? materialTapTargetSize,
-  }) : assert(textTheme != null),
-       assert(minWidth != null && minWidth >= 0.0),
-       assert(height != null && height >= 0.0),
-       assert(alignedDropdown != null),
-       assert(layoutBehavior != null),
+  }) : assert(minWidth >= 0.0),
+       assert(height >= 0.0),
        _buttonColor = buttonColor,
        _disabledColor = disabledColor,
        _focusColor = focusColor,
@@ -235,7 +229,7 @@
   /// Defaults to [ButtonBarLayoutBehavior.padded].
   final ButtonBarLayoutBehavior layoutBehavior;
 
-  /// Simply a convenience that returns [minWidth] and [height] as a
+  /// Convenience that returns [minWidth] and [height] as a
   /// [BoxConstraints] object.
   BoxConstraints get constraints {
     return BoxConstraints(
@@ -382,13 +376,12 @@
   /// A set of thirteen colors that can be used to derive the button theme's
   /// colors.
   ///
-  /// This property was added much later than the theme's set of highly
-  /// specific colors, like [ThemeData.buttonColor], [ThemeData.highlightColor],
-  /// [ThemeData.splashColor] etc.
+  /// This property was added much later than the theme's set of highly specific
+  /// colors, like [ThemeData.highlightColor] and [ThemeData.splashColor] etc.
   ///
-  /// The colors for new button classes can be defined exclusively in terms
-  /// of [colorScheme]. When it's possible, the existing buttons will
-  /// (continue to) gradually migrate to it.
+  /// The colors for new button classes can be defined exclusively in terms of
+  /// [colorScheme]. When it's possible, the existing buttons will (continue to)
+  /// gradually migrate to it.
   final ColorScheme? colorScheme;
 
   // The minimum size of a button's tap target.
diff --git a/framework/lib/src/material/calendar_date_picker.dart b/framework/lib/src/material/calendar_date_picker.dart
index cfe617e..754fa2e 100644
--- a/framework/lib/src/material/calendar_date_picker.dart
+++ b/framework/lib/src/material/calendar_date_picker.dart
@@ -11,12 +11,14 @@
 
 import 'color_scheme.dart';
 import 'date.dart';
+import 'date_picker_theme.dart';
 import 'debug.dart';
 import 'divider.dart';
 import 'icon_button.dart';
 import 'icons.dart';
 import 'ink_well.dart';
 import 'material_localizations.dart';
+import 'material_state.dart';
 import 'text_theme.dart';
 import 'theme.dart';
 
@@ -93,15 +95,10 @@
     this.onDisplayedMonthChanged,
     this.initialCalendarMode = DatePickerMode.day,
     this.selectableDayPredicate,
-  }) : assert(initialDate != null),
-       assert(firstDate != null),
-       assert(lastDate != null),
-       initialDate = DateUtils.dateOnly(initialDate),
+  }) : initialDate = DateUtils.dateOnly(initialDate),
        firstDate = DateUtils.dateOnly(firstDate),
        lastDate = DateUtils.dateOnly(lastDate),
-       currentDate = DateUtils.dateOnly(currentDate ?? DateTime.now()),
-       assert(onDateChanged != null),
-       assert(initialCalendarMode != null) {
+       currentDate = DateUtils.dateOnly(currentDate ?? DateTime.now()) {
     assert(
       !this.lastDate.isBefore(this.firstDate),
       'lastDate ${this.lastDate} must be on or after firstDate ${this.firstDate}.',
@@ -188,8 +185,10 @@
     _textDirection = Directionality.of(context);
     if (!_announcedInitialDate) {
       _announcedInitialDate = true;
+      final bool isToday = DateUtils.isSameDay(widget.currentDate, _selectedDate);
+      final String semanticLabelSuffix = isToday ? ', ${_localizations.currentDateLabel}' : '';
       SemanticsService.announce(
-        _localizations.formatFullDate(_selectedDate),
+        '${_localizations.formatFullDate(_selectedDate)}$semanticLabelSuffix',
         _textDirection,
       );
     }
@@ -282,7 +281,7 @@
             firstDate: widget.firstDate,
             lastDate: widget.lastDate,
             initialDate: _currentDisplayedMonthDate,
-            selectedDate: _selectedDate,
+            selectedDate: _currentDisplayedMonthDate,
             onChanged: _handleYearChanged,
           ),
         );
@@ -440,12 +439,7 @@
     required this.onChanged,
     required this.onDisplayedMonthChanged,
     this.selectableDayPredicate,
-  }) : assert(selectedDate != null),
-       assert(currentDate != null),
-       assert(onChanged != null),
-       assert(firstDate != null),
-       assert(lastDate != null),
-       assert(!firstDate.isAfter(lastDate)),
+  }) : assert(!firstDate.isAfter(lastDate)),
        assert(!selectedDate.isBefore(firstDate)),
        assert(!selectedDate.isAfter(lastDate));
 
@@ -827,13 +821,7 @@
     required this.selectedDate,
     required this.onChanged,
     this.selectableDayPredicate,
-  }) : assert(currentDate != null),
-       assert(displayedMonth != null),
-       assert(firstDate != null),
-       assert(lastDate != null),
-       assert(selectedDate != null),
-       assert(onChanged != null),
-       assert(!firstDate.isAfter(lastDate)),
+  }) : assert(!firstDate.isAfter(lastDate)),
        assert(!selectedDate.isBefore(firstDate)),
        assert(!selectedDate.isAfter(lastDate));
 
@@ -934,18 +922,11 @@
 
   @override
   Widget build(BuildContext context) {
-    final ColorScheme colorScheme = Theme.of(context).colorScheme;
     final MaterialLocalizations localizations = MaterialLocalizations.of(context);
-    final TextTheme textTheme = Theme.of(context).textTheme;
-    final TextStyle? headerStyle = textTheme.bodySmall?.apply(
-      color: colorScheme.onSurface.withOpacity(0.60),
-    );
-    final TextStyle dayStyle = textTheme.bodySmall!;
-    final Color enabledDayColor = colorScheme.onSurface.withOpacity(0.87);
-    final Color disabledDayColor = colorScheme.onSurface.withOpacity(0.38);
-    final Color selectedDayColor = colorScheme.onPrimary;
-    final Color selectedDayBackground = colorScheme.primary;
-    final Color todayColor = colorScheme.primary;
+    final DatePickerThemeData datePickerTheme = DatePickerTheme.of(context);
+    final DatePickerThemeData defaults = DatePickerTheme.defaults(context);
+    final TextStyle? weekdayStyle = datePickerTheme.weekdayStyle ?? defaults.weekdayStyle;
+    final TextStyle? dayStyle = datePickerTheme.dayStyle ?? defaults.dayStyle;
 
     final int year = widget.displayedMonth.year;
     final int month = widget.displayedMonth.month;
@@ -953,7 +934,19 @@
     final int daysInMonth = DateUtils.getDaysInMonth(year, month);
     final int dayOffset = DateUtils.firstDayOffset(year, month, localizations);
 
-    final List<Widget> dayItems = _dayHeaders(headerStyle, localizations);
+    T? effectiveValue<T>(T? Function(DatePickerThemeData? theme) getProperty) {
+      return getProperty(datePickerTheme) ?? getProperty(defaults);
+    }
+
+    T? resolve<T>(MaterialStateProperty<T>? Function(DatePickerThemeData? theme) getProperty, Set<MaterialState> states) {
+      return effectiveValue(
+        (DatePickerThemeData? theme) {
+          return getProperty(theme)?.resolve(states);
+        },
+      );
+    }
+
+    final List<Widget> dayItems = _dayHeaders(weekdayStyle, localizations);
     // 1-based day of month, e.g. 1-31 for January, and 1-29 for February on
     // a leap year.
     int day = -dayOffset;
@@ -963,42 +956,42 @@
         dayItems.add(Container());
       } else {
         final DateTime dayToBuild = DateTime(year, month, day);
-        final bool isDisabled = dayToBuild.isAfter(widget.lastDate) ||
-            dayToBuild.isBefore(widget.firstDate) ||
-            (widget.selectableDayPredicate != null && !widget.selectableDayPredicate!(dayToBuild));
+        final bool isDisabled =
+          dayToBuild.isAfter(widget.lastDate) ||
+          dayToBuild.isBefore(widget.firstDate) ||
+          (widget.selectableDayPredicate != null && !widget.selectableDayPredicate!(dayToBuild));
         final bool isSelectedDay = DateUtils.isSameDay(widget.selectedDate, dayToBuild);
         final bool isToday = DateUtils.isSameDay(widget.currentDate, dayToBuild);
+        final String semanticLabelSuffix = isToday ? ', ${localizations.currentDateLabel}' : '';
 
-        BoxDecoration? decoration;
-        Color dayColor = enabledDayColor;
-        if (isSelectedDay) {
-          // The selected day gets a circle background highlight, and a
-          // contrasting text color.
-          dayColor = selectedDayColor;
-          decoration = BoxDecoration(
-            color: selectedDayBackground,
-            shape: BoxShape.circle,
-          );
-        } else if (isToday) {
-          // The current day gets a different text color (if enabled) and a circle stroke
-          // border.
-          if (isDisabled) {
-            dayColor = disabledDayColor;
-          } else {
-            dayColor = todayColor;
-          }
-          decoration = BoxDecoration(
-            border: Border.all(color: dayColor),
-            shape: BoxShape.circle,
-          );
-        } else if (isDisabled) {
-          dayColor = disabledDayColor;
-        }
+        final Set<MaterialState> states = <MaterialState>{
+          if (isDisabled) MaterialState.disabled,
+          if (isSelectedDay) MaterialState.selected,
+        };
+
+        final Color? dayForegroundColor = resolve<Color?>((DatePickerThemeData? theme) => isToday ? theme?.todayForegroundColor : theme?.dayForegroundColor, states);
+        final Color? dayBackgroundColor = resolve<Color?>((DatePickerThemeData? theme) => isToday ? theme?.todayBackgroundColor : theme?.dayBackgroundColor, states);
+        final MaterialStateProperty<Color?> dayOverlayColor = MaterialStateProperty.resolveWith<Color?>(
+          (Set<MaterialState> states) => effectiveValue((DatePickerThemeData? theme) => theme?.dayOverlayColor?.resolve(states)),
+        );
+        final BoxDecoration decoration = isToday
+          ? BoxDecoration(
+              color: dayBackgroundColor,
+              border: Border.fromBorderSide(
+                (datePickerTheme.todayBorder ?? defaults.todayBorder!)
+                  .copyWith(color: dayForegroundColor)
+              ),
+              shape: BoxShape.circle,
+            )
+          : BoxDecoration(
+              color: dayBackgroundColor,
+              shape: BoxShape.circle,
+            );
 
         Widget dayWidget = Container(
           decoration: decoration,
           child: Center(
-            child: Text(localizations.formatDecimal(day), style: dayStyle.apply(color: dayColor)),
+            child: Text(localizations.formatDecimal(day), style: dayStyle?.apply(color: dayForegroundColor)),
           ),
         );
 
@@ -1011,7 +1004,8 @@
             focusNode: _dayFocusNodes[day - 1],
             onTap: () => widget.onChanged(dayToBuild),
             radius: _dayPickerRowHeight / 2 + 4,
-            splashColor: selectedDayBackground.withOpacity(0.38),
+            statesController: MaterialStatesController(states),
+            overlayColor: dayOverlayColor,
             child: Semantics(
               // We want the day of month to be spoken first irrespective of the
               // locale-specific preferences or TextDirection. This is because
@@ -1019,7 +1013,7 @@
               // day of month before the rest of the date, as they are looking
               // for the day of month. To do that we prepend day of month to the
               // formatted full date.
-              label: '${localizations.formatDecimal(day)}, ${localizations.formatFullDate(dayToBuild)}',
+              label: '${localizations.formatDecimal(day)}, ${localizations.formatFullDate(dayToBuild)}$semanticLabelSuffix',
               selected: isSelectedDay,
               excludeSemantics: true,
               child: dayWidget,
@@ -1101,11 +1095,7 @@
     required this.selectedDate,
     required this.onChanged,
     this.dragStartBehavior = DragStartBehavior.start,
-  }) : assert(firstDate != null),
-       assert(lastDate != null),
-       assert(selectedDate != null),
-       assert(onChanged != null),
-       assert(!firstDate.isAfter(lastDate)),
+  }) : assert(!firstDate.isAfter(lastDate)),
        currentDate = DateUtils.dateOnly(currentDate ?? DateTime.now()),
        initialDate = DateUtils.dateOnly(initialDate ?? selectedDate);
 
@@ -1167,8 +1157,20 @@
   }
 
   Widget _buildYearItem(BuildContext context, int index) {
-    final ColorScheme colorScheme = Theme.of(context).colorScheme;
-    final TextTheme textTheme = Theme.of(context).textTheme;
+    final DatePickerThemeData datePickerTheme = DatePickerTheme.of(context);
+    final DatePickerThemeData defaults = DatePickerTheme.defaults(context);
+
+    T? effectiveValue<T>(T? Function(DatePickerThemeData? theme) getProperty) {
+      return getProperty(datePickerTheme) ?? getProperty(defaults);
+    }
+
+    T? resolve<T>(MaterialStateProperty<T>? Function(DatePickerThemeData? theme) getProperty, Set<MaterialState> states) {
+      return effectiveValue(
+        (DatePickerThemeData? theme) {
+          return getProperty(theme)?.resolve(states);
+        },
+      );
+    }
 
     // Backfill the _YearPicker with disabled years if necessary.
     final int offset = _itemCount < minYears ? (minYears - _itemCount) ~/ 2 : 0;
@@ -1179,33 +1181,32 @@
     const double decorationHeight = 36.0;
     const double decorationWidth = 72.0;
 
-    final Color textColor;
-    if (isSelected) {
-      textColor = colorScheme.onPrimary;
-    } else if (isDisabled) {
-      textColor = colorScheme.onSurface.withOpacity(0.38);
-    } else if (isCurrentYear) {
-      textColor = colorScheme.primary;
-    } else {
-      textColor = colorScheme.onSurface.withOpacity(0.87);
-    }
-    final TextStyle? itemStyle = textTheme.bodyLarge?.apply(color: textColor);
+    final Set<MaterialState> states = <MaterialState>{
+      if (isDisabled) MaterialState.disabled,
+      if (isSelected) MaterialState.selected,
+    };
 
-    BoxDecoration? decoration;
-    if (isSelected) {
-      decoration = BoxDecoration(
-        color: colorScheme.primary,
-        borderRadius: BorderRadius.circular(decorationHeight / 2),
+    final Color? textColor = resolve<Color?>((DatePickerThemeData? theme) => isCurrentYear ? theme?.todayForegroundColor : theme?.yearForegroundColor, states);
+    final Color? background = resolve<Color?>((DatePickerThemeData? theme) => isCurrentYear ? theme?.todayBackgroundColor : theme?.yearBackgroundColor, states);
+    final MaterialStateProperty<Color?> overlayColor =
+      MaterialStateProperty.resolveWith<Color?>((Set<MaterialState> states) =>
+        effectiveValue((DatePickerThemeData? theme) => theme?.dayOverlayColor?.resolve(states)),
       );
-    } else if (isCurrentYear && !isDisabled) {
-      decoration = BoxDecoration(
-        border: Border.all(
-          color: colorScheme.primary,
-        ),
-        borderRadius: BorderRadius.circular(decorationHeight / 2),
-      );
-    }
 
+    BoxBorder? border;
+    if (isCurrentYear) {
+      final BorderSide? todayBorder = datePickerTheme.todayBorder ?? defaults.todayBorder;
+      if (todayBorder != null) {
+        border = Border.fromBorderSide(todayBorder.copyWith(color: textColor));
+      }
+    }
+    final BoxDecoration decoration = BoxDecoration(
+      border: border,
+      color: background,
+      borderRadius: BorderRadius.circular(decorationHeight / 2),
+    );
+
+    final TextStyle? itemStyle = (datePickerTheme.yearStyle ?? defaults.yearStyle)?.apply(color: textColor);
     Widget yearItem = Center(
       child: Container(
         decoration: decoration,
@@ -1229,6 +1230,8 @@
       yearItem = InkWell(
         key: ValueKey<int>(year),
         onTap: () => widget.onChanged(DateTime(year, widget.initialDate.month)),
+        statesController: MaterialStatesController(states),
+        overlayColor: overlayColor,
         child: yearItem,
       );
     }
diff --git a/framework/lib/src/material/card.dart b/framework/lib/src/material/card.dart
index 00ef5a7..998f4e4 100644
--- a/framework/lib/src/material/card.dart
+++ b/framework/lib/src/material/card.dart
@@ -71,15 +71,15 @@
     this.clipBehavior,
     this.child,
     this.semanticContainer = true,
-  }) : assert(elevation == null || elevation >= 0.0),
-       assert(borderOnForeground != null);
+  }) : assert(elevation == null || elevation >= 0.0);
 
   /// The card's background color.
   ///
   /// Defines the card's [Material.color].
   ///
-  /// If this property is null then [CardTheme.color] of [ThemeData.cardTheme]
-  /// is used. If that's null then [ThemeData.cardColor] is used.
+  /// If this property is null then the ambient [CardTheme.color] is used. If that is null,
+  /// and [ThemeData.useMaterial3] is true, then [ColorScheme.surface] of
+  /// [ThemeData.colorScheme] is used. Otherwise, [ThemeData.cardColor] is used.
   final Color? color;
 
   /// The color to paint the shadow below the card.
@@ -214,7 +214,7 @@
 // Design token database by the script:
 //   dev/tools/gen_defaults/bin/gen_defaults.dart.
 
-// Token database version: v0_143
+// Token database version: v0_158
 
 class _CardDefaultsM3 extends CardTheme {
   const _CardDefaultsM3(this.context)
diff --git a/framework/lib/src/material/card_theme.dart b/framework/lib/src/material/card_theme.dart
index aa6f80e..db849ee 100644
--- a/framework/lib/src/material/card_theme.dart
+++ b/framework/lib/src/material/card_theme.dart
@@ -114,7 +114,6 @@
   ///
   /// {@macro dart.ui.shadow.lerp}
   static CardTheme lerp(CardTheme? a, CardTheme? b, double t) {
-    assert(t != null);
     return CardTheme(
       clipBehavior: t < 0.5 ? a?.clipBehavior : b?.clipBehavior,
       color: Color.lerp(a?.color, b?.color, t),
diff --git a/framework/lib/src/material/checkbox.dart b/framework/lib/src/material/checkbox.dart
index 3f83fec..d4e8346 100644
--- a/framework/lib/src/material/checkbox.dart
+++ b/framework/lib/src/material/checkbox.dart
@@ -34,7 +34,7 @@
 ///
 /// {@tool dartpad}
 /// This example shows how you can override the default theme of
-/// of a [Checkbox] with a [MaterialStateProperty].
+/// a [Checkbox] with a [MaterialStateProperty].
 /// In this example, the checkbox's color will be `Colors.blue` when the [Checkbox]
 /// is being pressed, hovered, or focused. Otherwise, the checkbox's color will
 /// be `Colors.red`.
@@ -88,9 +88,7 @@
     this.shape,
     this.side,
     this.isError = false,
-  }) : assert(tristate != null),
-       assert(tristate || value != null),
-       assert(autofocus != null);
+  }) : assert(tristate || value != null);
 
   /// Whether this checkbox is checked.
   ///
@@ -321,6 +319,7 @@
   ///  * [MaterialState.hovered].
   ///  * [MaterialState.focused].
   ///  * [MaterialState.disabled].
+  ///  * [MaterialState.error].
   ///
   /// If this property is not a [MaterialStateBorderSide] and it is
   /// non-null, then it is only rendered when the checkbox's value is
@@ -396,7 +395,8 @@
 
   BorderSide? _resolveSide(BorderSide? side) {
     if (side is MaterialStateBorderSide) {
-      return MaterialStateProperty.resolveAs<BorderSide?>(side, states);
+      final Set<MaterialState> sideStates = widget.isError ? (states..add(MaterialState.error)) : states;
+      return MaterialStateProperty.resolveAs<BorderSide?>(side, sideStates);
     }
     if (!states.contains(MaterialState.selected)) {
       return side;
@@ -764,7 +764,7 @@
 // Design token database by the script:
 //   dev/tools/gen_defaults/bin/gen_defaults.dart.
 
-// Token database version: v0_143
+// Token database version: v0_158
 
 class _CheckboxDefaultsM3 extends CheckboxThemeData {
   _CheckboxDefaultsM3(BuildContext context)
diff --git a/framework/lib/src/material/checkbox_list_tile.dart b/framework/lib/src/material/checkbox_list_tile.dart
index 8040099..97f25c7 100644
--- a/framework/lib/src/material/checkbox_list_tile.dart
+++ b/framework/lib/src/material/checkbox_list_tile.dart
@@ -50,7 +50,7 @@
 ///
 /// {@tool snippet}
 /// ```dart
-/// Container(
+/// ColoredBox(
 ///   color: Colors.green,
 ///   child: Material(
 ///     child: CheckboxListTile(
@@ -85,6 +85,13 @@
 /// ** See code in examples/api/lib/material/checkbox_list_tile/checkbox_list_tile.0.dart **
 /// {@end-tool}
 ///
+/// {@tool dartpad}
+/// This sample demonstrates how [CheckboxListTile] positions the checkbox widget
+/// relative to the text in different configurations.
+///
+/// ** See code in examples/api/lib/material/checkbox_list_tile/checkbox_list_tile.1.dart **
+/// {@end-tool}
+///
 /// ## Semantics in CheckboxListTile
 ///
 /// Since the entirety of the CheckboxListTile is interactive, it should represent
@@ -109,7 +116,7 @@
 /// LinkedLabelCheckbox, that includes an interactive [RichText] widget that
 /// handles tap gestures.
 ///
-/// ** See code in examples/api/lib/material/checkbox_list_tile/checkbox_list_tile.1.dart **
+/// ** See code in examples/api/lib/material/checkbox_list_tile/custom_labeled_checkbox.0.dart **
 /// {@end-tool}
 ///
 /// ## CheckboxListTile isn't exactly what I want
@@ -125,7 +132,7 @@
 /// Here is an example of a custom LabeledCheckbox widget, but you can easily
 /// make your own configurable widget.
 ///
-/// ** See code in examples/api/lib/material/checkbox_list_tile/checkbox_list_tile.2.dart **
+/// ** See code in examples/api/lib/material/checkbox_list_tile/custom_labeled_checkbox.1.dart **
 /// {@end-tool}
 ///
 /// See also:
@@ -178,13 +185,8 @@
     this.focusNode,
     this.onFocusChange,
     this.enableFeedback,
-  }) : assert(tristate != null),
-       assert(tristate || value != null),
-       assert(isThreeLine != null),
-       assert(!isThreeLine || subtitle != null),
-       assert(selected != null),
-       assert(controlAffinity != null),
-       assert(autofocus != null);
+  }) : assert(tristate || value != null),
+       assert(!isThreeLine || subtitle != null);
 
   /// Whether this checkbox is checked.
   final bool? value;
@@ -219,7 +221,7 @@
 
   /// The color to use when this checkbox is checked.
   ///
-  /// Defaults to accent color of the current [Theme].
+  /// Defaults to [ColorScheme.secondary] of the current [Theme].
   final Color? activeColor;
 
   /// The color to use for the check icon when this checkbox is checked.
diff --git a/framework/lib/src/material/checkbox_theme.dart b/framework/lib/src/material/checkbox_theme.dart
index 807cd5f..7d99ecd 100644
--- a/framework/lib/src/material/checkbox_theme.dart
+++ b/framework/lib/src/material/checkbox_theme.dart
@@ -192,10 +192,10 @@
 
   // Special case because BorderSide.lerp() doesn't support null arguments
   static BorderSide? _lerpSides(BorderSide? a, BorderSide? b, double t) {
-    if (a == null && b == null) {
+    if (a == null || b == null) {
       return null;
     }
-    return BorderSide.lerp(a!, b!, t);
+    return BorderSide.lerp(a, b, t);
   }
 }
 
diff --git a/framework/lib/src/material/chip.dart b/framework/lib/src/material/chip.dart
index 2d0907e..e6c40cf 100644
--- a/framework/lib/src/material/chip.dart
+++ b/framework/lib/src/material/chip.dart
@@ -593,10 +593,7 @@
       'This feature was deprecated after v2.10.0-0.3.pre.'
     )
     this.useDeleteButtonTooltip = true,
-  }) : assert(label != null),
-       assert(autofocus != null),
-       assert(clipBehavior != null),
-       assert(elevation == null || elevation >= 0.0);
+  }) : assert(elevation == null || elevation >= 0.0);
 
   @override
   final Widget? avatar;
@@ -766,12 +763,7 @@
       'This feature was deprecated after v2.10.0-0.3.pre.'
     )
     this.useDeleteButtonTooltip = true,
-  }) : assert(label != null),
-       assert(isEnabled != null),
-       assert(selected != null),
-       assert(clipBehavior != null),
-       assert(autofocus != null),
-       assert(pressElevation == null || pressElevation >= 0.0),
+  }) : assert(pressElevation == null || pressElevation >= 0.0),
        assert(elevation == null || elevation >= 0.0),
        deleteIcon = deleteIcon ?? _kDefaultDeleteIcon;
 
@@ -1158,7 +1150,7 @@
     final EdgeInsetsGeometry defaultLabelPadding = EdgeInsets.lerp(
       const EdgeInsets.symmetric(horizontal: 8.0),
       const EdgeInsets.symmetric(horizontal: 4.0),
-      clampDouble(MediaQuery.of(context).textScaleFactor - 1.0, 0.0, 1.0),
+      clampDouble(MediaQuery.textScaleFactorOf(context) - 1.0, 0.0, 1.0),
     )!;
 
     final ThemeData theme = Theme.of(context);
@@ -1381,7 +1373,7 @@
     required this.deleteDrawerAnimation,
     required this.enableAnimation,
     this.avatarBorder,
-  }) : assert(theme != null);
+  });
 
   final _ChipRenderTheme theme;
   final bool? value;
@@ -1518,9 +1510,7 @@
     required this.deleteDrawerAnimation,
     required this.enableAnimation,
     this.avatarBorder,
-  }) : assert(theme != null),
-       assert(textDirection != null),
-       _theme = theme,
+  }) : _theme = theme,
        _textDirection = textDirection {
     checkmarkAnimation.addListener(markNeedsPaint);
     avatarDrawerAnimation.addListener(markNeedsLayout);
@@ -1917,7 +1907,7 @@
   );
 
   Color get _disabledColor {
-    if (enableAnimation == null || enableAnimation.isCompleted) {
+    if (enableAnimation.isCompleted) {
       return Colors.white;
     }
     final ColorTween enableTween;
@@ -2196,7 +2186,7 @@
 // Design token database by the script:
 //   dev/tools/gen_defaults/bin/gen_defaults.dart.
 
-// Token database version: v0_143
+// Token database version: v0_158
 
 class _ChipDefaultsM3 extends ChipThemeData {
   const _ChipDefaultsM3(this.context, this.isEnabled)
@@ -2257,7 +2247,7 @@
   EdgeInsetsGeometry? get labelPadding => EdgeInsets.lerp(
     const EdgeInsets.symmetric(horizontal: 8.0),
     const EdgeInsets.symmetric(horizontal: 4.0),
-    clampDouble(MediaQuery.of(context).textScaleFactor - 1.0, 0.0, 1.0),
+    clampDouble(MediaQuery.textScaleFactorOf(context) - 1.0, 0.0, 1.0),
   )!;
 }
 
diff --git a/framework/lib/src/material/chip_theme.dart b/framework/lib/src/material/chip_theme.dart
index 3583fc4..63e65bc 100644
--- a/framework/lib/src/material/chip_theme.dart
+++ b/framework/lib/src/material/chip_theme.dart
@@ -47,8 +47,7 @@
     super.key,
     required this.data,
     required super.child,
-  }) : assert(child != null),
-       assert(data != null);
+  });
 
   /// Specifies the color, shape, and text style values for descendant chip
   /// widgets.
@@ -225,8 +224,6 @@
   }) {
     assert(primaryColor != null || brightness != null, 'One of primaryColor or brightness must be specified');
     assert(primaryColor == null || brightness == null, 'Only one of primaryColor or brightness may be specified');
-    assert(secondaryColor != null);
-    assert(labelStyle != null);
 
     if (primaryColor != null) {
       brightness = ThemeData.estimateBrightnessForColor(primaryColor);
@@ -486,7 +483,6 @@
   ///
   /// {@macro dart.ui.shadow.lerp}
   static ChipThemeData? lerp(ChipThemeData? a, ChipThemeData? b, double t) {
-    assert(t != null);
     if (a == null && b == null) {
       return null;
     }
diff --git a/framework/lib/src/material/choice_chip.dart b/framework/lib/src/material/choice_chip.dart
index aaf6965..9bd44bd 100644
--- a/framework/lib/src/material/choice_chip.dart
+++ b/framework/lib/src/material/choice_chip.dart
@@ -83,11 +83,7 @@
     this.iconTheme,
     this.selectedShadowColor,
     this.avatarBorder = const CircleBorder(),
-  }) : assert(selected != null),
-       assert(label != null),
-       assert(clipBehavior != null),
-       assert(autofocus != null),
-       assert(pressElevation == null || pressElevation >= 0.0),
+  }) : assert(pressElevation == null || pressElevation >= 0.0),
        assert(elevation == null || elevation >= 0.0);
 
   @override
@@ -191,7 +187,7 @@
 // Design token database by the script:
 //   dev/tools/gen_defaults/bin/gen_defaults.dart.
 
-// Token database version: v0_143
+// Token database version: v0_158
 
 class _ChoiceChipDefaultsM3 extends ChipThemeData {
   const _ChoiceChipDefaultsM3(this.context, this.isEnabled, this.isSelected)
@@ -212,7 +208,7 @@
   Color? get backgroundColor => null;
 
   @override
-  Color? get shadowColor => Theme.of(context).colorScheme.shadow;
+  Color? get shadowColor => Colors.transparent;
 
   @override
   Color? get surfaceTintColor => Theme.of(context).colorScheme.surfaceTint;
@@ -259,7 +255,7 @@
   EdgeInsetsGeometry? get labelPadding => EdgeInsets.lerp(
     const EdgeInsets.symmetric(horizontal: 8.0),
     const EdgeInsets.symmetric(horizontal: 4.0),
-    clampDouble(MediaQuery.of(context).textScaleFactor - 1.0, 0.0, 1.0),
+    clampDouble(MediaQuery.textScaleFactorOf(context) - 1.0, 0.0, 1.0),
   )!;
 }
 
diff --git a/framework/lib/src/material/color_scheme.dart b/framework/lib/src/material/color_scheme.dart
index 68e8f2e..2e55a26 100644
--- a/framework/lib/src/material/color_scheme.dart
+++ b/framework/lib/src/material/color_scheme.dart
@@ -9,9 +9,11 @@
 import 'colors.dart';
 import 'theme_data.dart';
 
+/// {@template flutter.material.color_scheme.ColorScheme}
 /// A set of 30 colors based on the
 /// [Material spec](https://m3.material.io/styles/color/the-color-system/color-roles)
 /// that can be used to configure the color properties of most components.
+/// {@endtemplate}
 ///
 /// The main accent color groups in the scheme are [primary], [secondary],
 /// and [tertiary].
@@ -117,18 +119,7 @@
       'This feature was deprecated after v2.6.0-0.0.pre.'
     )
     Color? secondaryVariant,
-  }) : assert(brightness != null),
-       assert(primary != null),
-       assert(onPrimary != null),
-       assert(secondary != null),
-       assert(onSecondary != null),
-       assert(error != null),
-       assert(onError != null),
-       assert(background != null),
-       assert(onBackground != null),
-       assert(surface != null),
-       assert(onSurface != null),
-       _primaryContainer = primaryContainer,
+  }) : _primaryContainer = primaryContainer,
        _onPrimaryContainer = onPrimaryContainer,
        _secondaryContainer = secondaryContainer,
        _onSecondaryContainer = onSecondaryContainer,
@@ -295,18 +286,7 @@
       'This feature was deprecated after v2.6.0-0.0.pre.'
     )
     Color? secondaryVariant = const Color(0xff018786),
-  }) : assert(brightness != null),
-       assert(primary != null),
-       assert(onPrimary != null),
-       assert(secondary != null),
-       assert(onSecondary != null),
-       assert(error != null),
-       assert(onError != null),
-       assert(background != null),
-       assert(onBackground != null),
-       assert(surface != null),
-       assert(onSurface != null),
-       _primaryContainer = primaryContainer,
+  }) : _primaryContainer = primaryContainer,
        _onPrimaryContainer = onPrimaryContainer,
        _secondaryContainer = secondaryContainer,
        _onSecondaryContainer = onSecondaryContainer,
@@ -373,18 +353,7 @@
       'This feature was deprecated after v2.6.0-0.0.pre.'
     )
     Color? secondaryVariant = const Color(0xff03dac6),
-  }) : assert(brightness != null),
-       assert(primary != null),
-       assert(onPrimary != null),
-       assert(secondary != null),
-       assert(onSecondary != null),
-       assert(error != null),
-       assert(onError != null),
-       assert(background != null),
-       assert(onBackground != null),
-       assert(surface != null),
-       assert(onSurface != null),
-       _primaryContainer = primaryContainer,
+  }) : _primaryContainer = primaryContainer,
        _onPrimaryContainer = onPrimaryContainer,
        _secondaryContainer = secondaryContainer,
        _onSecondaryContainer = onSecondaryContainer,
@@ -451,18 +420,7 @@
       'This feature was deprecated after v2.6.0-0.0.pre.'
     )
     Color? secondaryVariant = const Color(0xff018786),
-  }) : assert(brightness != null),
-       assert(primary != null),
-       assert(onPrimary != null),
-       assert(secondary != null),
-       assert(onSecondary != null),
-       assert(error != null),
-       assert(onError != null),
-       assert(background != null),
-       assert(onBackground != null),
-       assert(surface != null),
-       assert(onSurface != null),
-       _primaryContainer = primaryContainer,
+  }) : _primaryContainer = primaryContainer,
        _onPrimaryContainer = onPrimaryContainer,
        _secondaryContainer = secondaryContainer,
        _onSecondaryContainer = onSecondaryContainer,
@@ -529,18 +487,7 @@
       'This feature was deprecated after v2.6.0-0.0.pre.'
     )
     Color? secondaryVariant = const Color(0xff66fff9),
-  }) : assert(brightness != null),
-       assert(primary != null),
-       assert(onPrimary != null),
-       assert(secondary != null),
-       assert(onSecondary != null),
-       assert(error != null),
-       assert(onError != null),
-       assert(background != null),
-       assert(onBackground != null),
-       assert(surface != null),
-       assert(onSurface != null),
-       _primaryContainer = primaryContainer,
+  }) : _primaryContainer = primaryContainer,
        _onPrimaryContainer = onPrimaryContainer,
        _secondaryContainer = secondaryContainer,
        _onSecondaryContainer = onSecondaryContainer,
@@ -576,8 +523,6 @@
     Color? errorColor,
     Brightness brightness = Brightness.light,
   }) {
-    assert(primarySwatch != null);
-    assert(brightness != null);
 
     final bool isDark = brightness == Brightness.dark;
     final bool primaryIsDark = _brightnessFor(primarySwatch) == Brightness.dark;
diff --git a/framework/lib/src/material/data_table.dart b/framework/lib/src/material/data_table.dart
index c790a70..ba4f434 100644
--- a/framework/lib/src/material/data_table.dart
+++ b/framework/lib/src/material/data_table.dart
@@ -44,7 +44,7 @@
     this.tooltip,
     this.numeric = false,
     this.onSort,
-  }) : assert(label != null);
+  });
 
   /// The column heading.
   ///
@@ -107,7 +107,7 @@
     this.onLongPress,
     this.color,
     required this.cells,
-  }) : assert(cells != null);
+  });
 
   /// Creates the configuration for a row of a [DataTable], deriving
   /// the key from a row index.
@@ -120,8 +120,7 @@
     this.onLongPress,
     this.color,
     required this.cells,
-  }) : assert(cells != null),
-       key = ValueKey<int?>(index);
+  }) : key = ValueKey<int?>(index);
 
   /// A [Key] that uniquely identifies this row. This is used to
   /// ensure that if a row is added or removed, any stateful widgets
@@ -234,7 +233,7 @@
     this.onTapDown,
     this.onDoubleTap,
     this.onTapCancel,
-  }) : assert(child != null);
+  });
 
   /// A cell that has no content and has zero width and height.
   static const DataCell empty = DataCell(SizedBox.shrink());
@@ -407,15 +406,10 @@
     this.checkboxHorizontalMargin,
     this.border,
     this.clipBehavior = Clip.none,
-  }) : assert(columns != null),
-       assert(columns.isNotEmpty),
+  }) : assert(columns.isNotEmpty),
        assert(sortColumnIndex == null || (sortColumnIndex >= 0 && sortColumnIndex < columns.length)),
-       assert(sortAscending != null),
-       assert(showCheckboxColumn != null),
-       assert(rows != null),
        assert(!rows.any((DataRow row) => row.cells.length != columns.length)),
        assert(dividerThickness == null || dividerThickness >= 0),
-       assert(clipBehavior != null),
        _onlyTextColumn = _initOnlyTextColumn(columns);
 
   /// The configuration and labels for the columns in the table.
@@ -973,7 +967,7 @@
     int displayColumnIndex = 0;
     if (displayCheckboxColumn) {
       tableColumns[0] = FixedColumnWidth(effectiveCheckboxHorizontalMarginStart + Checkbox.width + effectiveCheckboxHorizontalMarginEnd);
-      tableRows[0].children![0] = _buildCheckbox(
+      tableRows[0].children[0] = _buildCheckbox(
         context: context,
         checked: someChecked ? null : allChecked,
         onRowTap: null,
@@ -983,7 +977,7 @@
       );
       rowIndex = 1;
       for (final DataRow row in rows) {
-        tableRows[rowIndex].children![0] = _buildCheckbox(
+        tableRows[rowIndex].children[0] = _buildCheckbox(
           context: context,
           checked: row.selected,
           onRowTap: row.onSelectChanged == null ? null : () => row.onSelectChanged?.call(!row.selected),
@@ -1026,7 +1020,7 @@
       } else {
         tableColumns[displayColumnIndex] = const IntrinsicColumnWidth();
       }
-      tableRows[0].children![displayColumnIndex] = _buildHeadingCell(
+      tableRows[0].children[displayColumnIndex] = _buildHeadingCell(
         context: context,
         padding: padding,
         label: column.label,
@@ -1040,7 +1034,7 @@
       rowIndex = 1;
       for (final DataRow row in rows) {
         final DataCell cell = row.cells[dataColumnIndex];
-        tableRows[rowIndex].children![displayColumnIndex] = _buildDataCell(
+        tableRows[rowIndex].children[displayColumnIndex] = _buildDataCell(
           context: context,
           padding: padding,
           label: cell.child,
diff --git a/framework/lib/src/material/data_table_theme.dart b/framework/lib/src/material/data_table_theme.dart
index 5f8d741..1ff4b55 100644
--- a/framework/lib/src/material/data_table_theme.dart
+++ b/framework/lib/src/material/data_table_theme.dart
@@ -122,7 +122,6 @@
   ///
   /// {@macro dart.ui.shadow.lerp}
   static DataTableThemeData lerp(DataTableThemeData a, DataTableThemeData b, double t) {
-    assert(t != null);
     return DataTableThemeData(
       decoration: Decoration.lerp(a.decoration, b.decoration, t),
       dataRowColor: MaterialStateProperty.lerp<Color?>(a.dataRowColor, b.dataRowColor, t, Color.lerp),
@@ -214,7 +213,7 @@
     super.key,
     required this.data,
     required super.child,
-  }) : assert(data != null);
+  });
 
   /// The properties used for all descendant [DataTable] widgets.
   final DataTableThemeData data;
diff --git a/framework/lib/src/material/date.dart b/framework/lib/src/material/date.dart
index a06c921..5186c48 100644
--- a/framework/lib/src/material/date.dart
+++ b/framework/lib/src/material/date.dart
@@ -212,9 +212,7 @@
   DateTimeRange({
     required this.start,
     required this.end,
-  }) : assert(start != null),
-       assert(end != null),
-       assert(!start.isAfter(end));
+  }) : assert(!start.isAfter(end));
 
   /// The start of the range of dates.
   final DateTime start;
diff --git a/framework/lib/src/material/date_picker.dart b/framework/lib/src/material/date_picker.dart
index 553979f..e9e0c90 100644
--- a/framework/lib/src/material/date_picker.dart
+++ b/framework/lib/src/material/date_picker.dart
@@ -4,16 +4,18 @@
 
 import 'dart:math' as math;
 
-import 'package:flute/gestures.dart' show DragStartBehavior;
 import 'package:flute/rendering.dart';
 import 'package:flute/services.dart';
 import 'package:flute/widgets.dart';
 
 import 'app_bar.dart';
 import 'back_button.dart';
+import 'button_style.dart';
 import 'calendar_date_picker.dart';
 import 'color_scheme.dart';
+import 'colors.dart';
 import 'date.dart';
+import 'date_picker_theme.dart';
 import 'debug.dart';
 import 'dialog.dart';
 import 'dialog_theme.dart';
@@ -26,15 +28,20 @@
 import 'input_decorator.dart';
 import 'material.dart';
 import 'material_localizations.dart';
+import 'material_state.dart';
 import 'scaffold.dart';
 import 'text_button.dart';
 import 'text_field.dart';
 import 'text_theme.dart';
 import 'theme.dart';
 
-const Size _calendarPortraitDialogSize = Size(330.0, 518.0);
+// The M3 sizes are coming from the tokens, but are hand coded,
+// as the current token DB does not contain landscape versions.
+const Size _calendarPortraitDialogSizeM2 = Size(330.0, 518.0);
+const Size _calendarPortraitDialogSizeM3 = Size(328.0, 512.0);
 const Size _calendarLandscapeDialogSize = Size(496.0, 346.0);
-const Size _inputPortraitDialogSize = Size(330.0, 270.0);
+const Size _inputPortraitDialogSizeM2 = Size(330.0, 270.0);
+const Size _inputPortraitDialogSizeM3 = Size(328.0, 270.0);
 const Size _inputLandscapeDialogSize = Size(496, 160.0);
 const Size _inputRangeLandscapeDialogSize = Size(496, 164.0);
 const Duration _dialogSizeAnimationDuration = Duration(milliseconds: 200);
@@ -158,10 +165,6 @@
   TextInputType? keyboardType,
   Offset? anchorPoint,
 }) async {
-  assert(context != null);
-  assert(initialDate != null);
-  assert(firstDate != null);
-  assert(lastDate != null);
   initialDate = DateUtils.dateOnly(initialDate);
   firstDate = DateUtils.dateOnly(firstDate);
   lastDate = DateUtils.dateOnly(lastDate);
@@ -181,9 +184,6 @@
     selectableDayPredicate == null || selectableDayPredicate(initialDate),
     'Provided initialDate $initialDate must satisfy provided selectableDayPredicate.',
   );
-  assert(initialEntryMode != null);
-  assert(useRootNavigator != null);
-  assert(initialDatePickerMode != null);
   assert(debugCheckHasMaterialLocalizations(context));
 
   Widget dialog = DatePickerDialog(
@@ -259,15 +259,10 @@
     this.fieldLabelText,
     this.keyboardType,
     this.restorationId,
-  }) : assert(initialDate != null),
-       assert(firstDate != null),
-       assert(lastDate != null),
-       initialDate = DateUtils.dateOnly(initialDate),
+  }) : initialDate = DateUtils.dateOnly(initialDate),
        firstDate = DateUtils.dateOnly(firstDate),
        lastDate = DateUtils.dateOnly(lastDate),
-       currentDate = DateUtils.dateOnly(currentDate ?? DateTime.now()),
-       assert(initialEntryMode != null),
-       assert(initialCalendarMode != null) {
+       currentDate = DateUtils.dateOnly(currentDate ?? DateTime.now()) {
     assert(
       !this.lastDate.isBefore(this.firstDate),
       'lastDate ${this.lastDate} must be on or after firstDate ${this.firstDate}.',
@@ -425,13 +420,15 @@
   }
 
   Size _dialogSize(BuildContext context) {
-    final Orientation orientation = MediaQuery.of(context).orientation;
+    final bool useMaterial3 = Theme.of(context).useMaterial3;
+    final Orientation orientation = MediaQuery.orientationOf(context);
+
     switch (_entryMode.value) {
       case DatePickerEntryMode.calendar:
       case DatePickerEntryMode.calendarOnly:
         switch (orientation) {
           case Orientation.portrait:
-            return _calendarPortraitDialogSize;
+            return useMaterial3 ? _calendarPortraitDialogSizeM3 : _calendarPortraitDialogSizeM2;
           case Orientation.landscape:
             return _calendarLandscapeDialogSize;
         }
@@ -439,7 +436,7 @@
       case DatePickerEntryMode.inputOnly:
         switch (orientation) {
           case Orientation.portrait:
-            return _inputPortraitDialogSize;
+            return useMaterial3 ? _inputPortraitDialogSizeM3 : _inputPortraitDialogSizeM2;
           case Orientation.landscape:
             return _inputLandscapeDialogSize;
         }
@@ -454,21 +451,28 @@
   @override
   Widget build(BuildContext context) {
     final ThemeData theme = Theme.of(context);
-    final ColorScheme colorScheme = theme.colorScheme;
+    final bool useMaterial3 = theme.useMaterial3;
     final MaterialLocalizations localizations = MaterialLocalizations.of(context);
-    final Orientation orientation = MediaQuery.of(context).orientation;
+    final Orientation orientation = MediaQuery.orientationOf(context);
+    final DatePickerThemeData datePickerTheme = DatePickerTheme.of(context);
+    final DatePickerThemeData defaults = DatePickerTheme.defaults(context);
     final TextTheme textTheme = theme.textTheme;
+
     // Constrain the textScaleFactor to the largest supported value to prevent
     // layout issues.
-    final double textScaleFactor = math.min(MediaQuery.of(context).textScaleFactor, 1.3);
+    final double textScaleFactor = math.min(MediaQuery.textScaleFactorOf(context), 1.3);
+    final Color? headerForegroundColor = datePickerTheme.headerForegroundColor ?? defaults.headerForegroundColor;
+    final TextStyle? headlineStyle = useMaterial3
+      ? (datePickerTheme.headerHeadlineStyle ?? defaults.headerHeadlineStyle)?.copyWith(
+          color: headerForegroundColor,
+        )
+      // Material2 has support for landscape and the current M3 spec doesn't
+      // address this layout, so handling it seperately here.
+      : (orientation == Orientation.landscape
+        ? textTheme.headlineSmall?.copyWith(color: headerForegroundColor)
+        : textTheme.headlineMedium?.copyWith(color: headerForegroundColor));
 
     final String dateText = localizations.formatMediumDate(_selectedDate.value);
-    final Color onPrimarySurface = colorScheme.brightness == Brightness.light
-      ? colorScheme.onPrimary
-      : colorScheme.onSurface;
-    final TextStyle? dateStyle = orientation == Orientation.landscape
-      ? textTheme.headlineSmall?.copyWith(color: onPrimarySurface)
-      : textTheme.headlineMedium?.copyWith(color: onPrimarySurface);
 
     final Widget actions = Container(
       alignment: AlignmentDirectional.centerEnd,
@@ -480,7 +484,7 @@
           TextButton(
             onPressed: _handleCancel,
             child: Text(widget.cancelText ?? (
-              theme.useMaterial3
+              useMaterial3
                 ? localizations.cancelButtonLabel
                 : localizations.cancelButtonLabel.toUpperCase()
             )),
@@ -546,8 +550,8 @@
       case DatePickerEntryMode.calendar:
         picker = calendarDatePicker();
         entryModeButton = IconButton(
-          icon: const Icon(Icons.edit),
-          color: onPrimarySurface,
+          icon:  Icon(useMaterial3 ? Icons.edit_outlined : Icons.edit),
+          color: headerForegroundColor,
           tooltip: localizations.inputDateModeButtonLabel,
           onPressed: _handleEntryModeToggle,
         );
@@ -562,7 +566,7 @@
         picker = inputDatePicker();
         entryModeButton = IconButton(
           icon: const Icon(Icons.calendar_today),
-          color: onPrimarySurface,
+          color: headerForegroundColor,
           tooltip: localizations.calendarModeButtonLabel,
           onPressed: _handleEntryModeToggle,
         );
@@ -576,19 +580,29 @@
 
     final Widget header = _DatePickerHeader(
       helpText: widget.helpText ?? (
-        Theme.of(context).useMaterial3
+        useMaterial3
           ? localizations.datePickerHelpText
           : localizations.datePickerHelpText.toUpperCase()
       ),
       titleText: dateText,
-      titleStyle: dateStyle,
+      titleStyle: headlineStyle,
       orientation: orientation,
       isShort: orientation == Orientation.landscape,
       entryModeButton: entryModeButton,
     );
 
     final Size dialogSize = _dialogSize(context) * textScaleFactor;
+    final DialogTheme dialogTheme = theme.dialogTheme;
     return Dialog(
+      backgroundColor: datePickerTheme.backgroundColor ?? defaults.backgroundColor,
+      elevation: useMaterial3
+        ? datePickerTheme.elevation ?? defaults.elevation!
+        : datePickerTheme.elevation ?? dialogTheme.elevation ?? 24,
+      shadowColor: datePickerTheme.shadowColor ?? defaults.shadowColor,
+      surfaceTintColor: datePickerTheme.surfaceTintColor ?? defaults.surfaceTintColor,
+      shape: useMaterial3
+        ? datePickerTheme.shape ?? defaults.shape
+        : datePickerTheme.shape ?? dialogTheme.shape ?? defaults.shape,
       insetPadding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 24.0),
       clipBehavior: Clip.antiAlias,
       child: AnimatedContainer(
@@ -608,6 +622,7 @@
                   crossAxisAlignment: CrossAxisAlignment.stretch,
                   children: <Widget>[
                     header,
+                    if (useMaterial3) const Divider(),
                     Expanded(child: picker),
                     actions,
                   ],
@@ -618,6 +633,7 @@
                   crossAxisAlignment: CrossAxisAlignment.stretch,
                   children: <Widget>[
                     header,
+                    if (useMaterial3) const VerticalDivider(),
                     Flexible(
                       child: Column(
                         mainAxisSize: MainAxisSize.min,
@@ -712,9 +728,7 @@
     required this.orientation,
     this.isShort = false,
     this.entryModeButton,
-  }) : assert(helpText != null),
-       assert(orientation != null),
-       assert(isShort != null);
+  });
 
   static const double _datePickerHeaderLandscapeWidth = 152.0;
   static const double _datePickerHeaderPortraitHeight = 120.0;
@@ -751,17 +765,12 @@
 
   @override
   Widget build(BuildContext context) {
-    final ThemeData theme = Theme.of(context);
-    final ColorScheme colorScheme = theme.colorScheme;
-    final TextTheme textTheme = theme.textTheme;
-
-    // The header should use the primary color in light themes and surface color in dark
-    final bool isDark = colorScheme.brightness == Brightness.dark;
-    final Color primarySurfaceColor = isDark ? colorScheme.surface : colorScheme.primary;
-    final Color onPrimarySurfaceColor = isDark ? colorScheme.onSurface : colorScheme.onPrimary;
-
-    final TextStyle? helpStyle = textTheme.labelSmall?.copyWith(
-      color: onPrimarySurfaceColor,
+    final DatePickerThemeData themeData = DatePickerTheme.of(context);
+    final DatePickerThemeData defaults = DatePickerTheme.defaults(context);
+    final Color? backgroundColor = themeData.headerBackgroundColor ?? defaults.headerBackgroundColor;
+    final Color? foregroundColor = themeData.headerForegroundColor ?? defaults.headerForegroundColor;
+    final TextStyle? helpStyle = (themeData.headerHelpStyle ?? defaults.headerHelpStyle)?.copyWith(
+      color: foregroundColor,
     );
 
     final Text help = Text(
@@ -783,7 +792,7 @@
         return SizedBox(
           height: _datePickerHeaderPortraitHeight,
           child: Material(
-            color: primarySurfaceColor,
+            color: backgroundColor,
             child: Padding(
               padding: const EdgeInsetsDirectional.only(
                 start: 24,
@@ -811,7 +820,7 @@
         return SizedBox(
           width: _datePickerHeaderLandscapeWidth,
           child: Material(
-            color: primarySurfaceColor,
+            color: backgroundColor,
             child: Column(
               crossAxisAlignment: CrossAxisAlignment.start,
               children: <Widget>[
@@ -963,19 +972,12 @@
   TransitionBuilder? builder,
   Offset? anchorPoint,
 }) async {
-  assert(context != null);
-  assert(
-    initialDateRange == null || (initialDateRange.start != null && initialDateRange.end != null),
-    'initialDateRange must be null or have non-null start and end dates.',
-  );
   assert(
     initialDateRange == null || !initialDateRange.start.isAfter(initialDateRange.end),
     "initialDateRange's start date must not be after it's end date.",
   );
   initialDateRange = initialDateRange == null ? null : DateUtils.datesOnly(initialDateRange);
-  assert(firstDate != null);
   firstDate = DateUtils.dateOnly(firstDate);
-  assert(lastDate != null);
   lastDate = DateUtils.dateOnly(lastDate);
   assert(
     !lastDate.isBefore(firstDate),
@@ -998,8 +1000,6 @@
     "initialDateRange's end date must be on or before lastDate $lastDate.",
   );
   currentDate = DateUtils.dateOnly(currentDate ?? DateTime.now());
-  assert(initialEntryMode != null);
-  assert(useRootNavigator != null);
   assert(debugCheckHasMaterialLocalizations(context));
 
   Widget dialog = DateRangePickerDialog(
@@ -1316,19 +1316,20 @@
 
   @override
   Widget build(BuildContext context) {
-    final MediaQueryData mediaQuery = MediaQuery.of(context);
-    final Orientation orientation = mediaQuery.orientation;
-    final double textScaleFactor = math.min(mediaQuery.textScaleFactor, 1.3);
+    final ThemeData theme = Theme.of(context);
+    final bool useMaterial3 = theme.useMaterial3;
+    final Orientation orientation = MediaQuery.orientationOf(context);
+    final double textScaleFactor = math.min(MediaQuery.textScaleFactorOf(context), 1.3);
     final MaterialLocalizations localizations = MaterialLocalizations.of(context);
-    final ColorScheme colors = Theme.of(context).colorScheme;
-    final Color onPrimarySurface = colors.brightness == Brightness.light
-      ? colors.onPrimary
-      : colors.onSurface;
+    final DatePickerThemeData datePickerTheme = DatePickerTheme.of(context);
+    final DatePickerThemeData defaults =  DatePickerTheme.defaults(context);
 
     final Widget contents;
     final Size size;
-    ShapeBorder? shape;
-    final double elevation;
+    final double? elevation;
+    final Color? shadowColor;
+    final Color? surfaceTintColor;
+    final ShapeBorder? shape;
     final EdgeInsets insetPadding;
     final bool showEntryModeButton =
       _entryMode.value == DatePickerEntryMode.calendar ||
@@ -1349,28 +1350,29 @@
           onCancel: _handleCancel,
           entryModeButton: showEntryModeButton
             ? IconButton(
-                icon: const Icon(Icons.edit),
+                icon: Icon(useMaterial3 ? Icons.edit_outlined : Icons.edit),
                 padding: EdgeInsets.zero,
-                color: onPrimarySurface,
                 tooltip: localizations.inputDateModeButtonLabel,
                 onPressed: _handleEntryModeToggle,
               )
             : null,
           confirmText: widget.saveText ?? (
-            Theme.of(context).useMaterial3
+            useMaterial3
               ? localizations.saveButtonLabel
               : localizations.saveButtonLabel.toUpperCase()
           ),
           helpText: widget.helpText ?? (
-            Theme.of(context).useMaterial3
+            useMaterial3
               ? localizations.dateRangePickerHelpText
               : localizations.dateRangePickerHelpText.toUpperCase()
             ),
         );
-        size = mediaQuery.size;
+        size = MediaQuery.sizeOf(context);
         insetPadding = EdgeInsets.zero;
-        shape = const RoundedRectangleBorder();
-        elevation = 0;
+        elevation = datePickerTheme.rangePickerElevation ?? defaults.rangePickerElevation!;
+        shadowColor = datePickerTheme.rangePickerShadowColor ?? defaults.rangePickerShadowColor!;
+        surfaceTintColor = datePickerTheme.rangePickerSurfaceTintColor ?? defaults.rangePickerSurfaceTintColor!;
+        shape = datePickerTheme.rangePickerShape ?? defaults.rangePickerShape;
         break;
 
       case DatePickerEntryMode.input:
@@ -1416,35 +1418,46 @@
             ? IconButton(
                 icon: const Icon(Icons.calendar_today),
                 padding: EdgeInsets.zero,
-                color: onPrimarySurface,
                 tooltip: localizations.calendarModeButtonLabel,
                 onPressed: _handleEntryModeToggle,
               )
             : null,
           confirmText: widget.confirmText ?? localizations.okButtonLabel,
           cancelText: widget.cancelText ?? (
-            Theme.of(context).useMaterial3
+            useMaterial3
               ? localizations.cancelButtonLabel
               : localizations.cancelButtonLabel.toUpperCase()
           ),
           helpText: widget.helpText ?? (
-            Theme.of(context).useMaterial3
+            useMaterial3
               ? localizations.dateRangePickerHelpText
               : localizations.dateRangePickerHelpText.toUpperCase()
           ),
         );
-        final DialogTheme dialogTheme = Theme.of(context).dialogTheme;
-        size = orientation == Orientation.portrait ? _inputPortraitDialogSize : _inputRangeLandscapeDialogSize;
+        final DialogTheme dialogTheme = theme.dialogTheme;
+        size = orientation == Orientation.portrait
+          ? (useMaterial3 ? _inputPortraitDialogSizeM3 : _inputPortraitDialogSizeM2)
+          : _inputRangeLandscapeDialogSize;
+        elevation = useMaterial3
+          ? datePickerTheme.elevation ?? defaults.elevation!
+          : datePickerTheme.elevation ?? dialogTheme.elevation ?? 24;
+        shadowColor = datePickerTheme.shadowColor ?? defaults.shadowColor;
+        surfaceTintColor = datePickerTheme.surfaceTintColor ?? defaults.surfaceTintColor;
+        shape = useMaterial3
+          ? datePickerTheme.shape ?? defaults.shape
+          : datePickerTheme.shape ?? dialogTheme.shape ?? defaults.shape;
+
         insetPadding = const EdgeInsets.symmetric(horizontal: 16.0, vertical: 24.0);
-        shape = dialogTheme.shape;
-        elevation = dialogTheme.elevation ?? 24;
         break;
     }
 
     return Dialog(
       insetPadding: insetPadding,
-      shape: shape,
+      backgroundColor: datePickerTheme.backgroundColor ?? defaults.backgroundColor,
       elevation: elevation,
+      shadowColor: shadowColor,
+      surfaceTintColor: surfaceTintColor,
+      shape: shape,
       clipBehavior: Clip.antiAlias,
       child: AnimatedContainer(
         width: size.width,
@@ -1497,26 +1510,29 @@
   @override
   Widget build(BuildContext context) {
     final ThemeData theme = Theme.of(context);
-    final ColorScheme colorScheme = theme.colorScheme;
+    final bool useMaterial3 = theme.useMaterial3;
     final MaterialLocalizations localizations = MaterialLocalizations.of(context);
-    final Orientation orientation = MediaQuery.of(context).orientation;
-    final TextTheme textTheme = theme.textTheme;
-    final Color headerForeground = colorScheme.brightness == Brightness.light
-        ? colorScheme.onPrimary
-        : colorScheme.onSurface;
-    final Color headerDisabledForeground = headerForeground.withOpacity(0.38);
+    final Orientation orientation = MediaQuery.orientationOf(context);
+    final DatePickerThemeData themeData = DatePickerTheme.of(context);
+    final DatePickerThemeData defaults = DatePickerTheme.defaults(context);
+    final Color? dialogBackground = themeData.rangePickerBackgroundColor ?? defaults.rangePickerBackgroundColor;
+    final Color? headerForeground = themeData.rangePickerHeaderForegroundColor ?? defaults.rangePickerHeaderForegroundColor;
+    final Color? headerDisabledForeground = headerForeground?.withOpacity(0.38);
+    final TextStyle? headlineStyle = themeData.rangePickerHeaderHeadlineStyle ?? defaults.rangePickerHeaderHeadlineStyle;
+    final TextStyle? headlineHelpStyle = (themeData.rangePickerHeaderHelpStyle ?? defaults.rangePickerHeaderHelpStyle)?.apply(color: headerForeground);
     final String startDateText = _formatRangeStartDate(localizations, selectedStartDate, selectedEndDate);
     final String endDateText = _formatRangeEndDate(localizations, selectedStartDate, selectedEndDate, DateTime.now());
-    final TextStyle? headlineStyle = textTheme.headlineSmall;
     final TextStyle? startDateStyle = headlineStyle?.apply(
         color: selectedStartDate != null ? headerForeground : headerDisabledForeground,
     );
     final TextStyle? endDateStyle = headlineStyle?.apply(
         color: selectedEndDate != null ? headerForeground : headerDisabledForeground,
     );
-    final TextStyle saveButtonStyle = textTheme.labelLarge!.apply(
-        color: onConfirm != null ? headerForeground : headerDisabledForeground,
+    final ButtonStyle buttonStyle = TextButton.styleFrom(
+      foregroundColor: headerForeground,
+      disabledForegroundColor: headerDisabledForeground
     );
+    final IconThemeData iconTheme = IconThemeData(color: headerForeground);
 
     return SafeArea(
       top: false,
@@ -1524,6 +1540,11 @@
       right: false,
       child: Scaffold(
         appBar: AppBar(
+          iconTheme: iconTheme,
+          actionsIconTheme: iconTheme,
+          elevation: useMaterial3 ? 0 : null,
+          scrolledUnderElevation: useMaterial3 ? 0 : null,
+          backgroundColor: useMaterial3 ? Colors.transparent : null,
           leading: CloseButton(
             onPressed: onCancel,
           ),
@@ -1531,15 +1552,16 @@
             if (orientation == Orientation.landscape && entryModeButton != null)
               entryModeButton!,
             TextButton(
+              style: buttonStyle,
               onPressed: onConfirm,
-              child: Text(confirmText, style: saveButtonStyle),
+              child: Text(confirmText),
             ),
             const SizedBox(width: 8),
           ],
           bottom: PreferredSize(
             preferredSize: const Size(double.infinity, 64),
             child: Row(children: <Widget>[
-              SizedBox(width: MediaQuery.of(context).size.width < 360 ? 42 : 72),
+              SizedBox(width: MediaQuery.sizeOf(context).width < 360 ? 42 : 72),
               Expanded(
                 child: Semantics(
                   label: '$helpText $startDateText to $endDateText',
@@ -1547,12 +1569,7 @@
                   child: Column(
                     crossAxisAlignment: CrossAxisAlignment.start,
                     children: <Widget>[
-                      Text(
-                        helpText,
-                        style: textTheme.labelSmall!.apply(
-                          color: headerForeground,
-                        ),
-                      ),
+                      Text(helpText, style: headlineHelpStyle),
                       const SizedBox(height: 8),
                       Row(
                         children: <Widget>[
@@ -1582,11 +1599,15 @@
               if (orientation == Orientation.portrait && entryModeButton != null)
                 Padding(
                   padding: const EdgeInsets.symmetric(horizontal: 8.0),
-                  child: entryModeButton,
+                  child: IconTheme(
+                    data: iconTheme,
+                    child: entryModeButton!,
+                  ),
                 ),
             ]),
           ),
         ),
+        backgroundColor: dialogBackground,
         body: _CalendarDateRangePicker(
           initialStartDate: selectedStartDate,
           initialEndDate: selectedEndDate,
@@ -1625,8 +1646,6 @@
     required this.onEndDateChanged,
   }) : initialStartDate = initialStartDate != null ? DateUtils.dateOnly(initialStartDate) : null,
        initialEndDate = initialEndDate != null ? DateUtils.dateOnly(initialEndDate) : null,
-       assert(firstDate != null),
-       assert(lastDate != null),
        firstDate = DateUtils.dateOnly(firstDate),
        lastDate = DateUtils.dateOnly(lastDate),
        currentDate = DateUtils.dateOnly(currentDate ?? DateTime.now()) {
@@ -2017,7 +2036,7 @@
 
     return Container(
       constraints: BoxConstraints(
-        maxWidth: MediaQuery.of(context).orientation == Orientation.landscape
+        maxWidth: MediaQuery.orientationOf(context) == Orientation.landscape
           ? _maxCalendarWidthLandscape
           : _maxCalendarWidthPortrait,
         maxHeight: _monthItemRowHeight,
@@ -2066,10 +2085,9 @@
     required this.dayChildWidth,
     required this.edgeChildWidth,
     required this.reverseCrossAxis,
-  }) : assert(crossAxisCount != null && crossAxisCount > 0),
-       assert(dayChildWidth != null && dayChildWidth >= 0),
-       assert(edgeChildWidth != null && edgeChildWidth >= 0),
-       assert(reverseCrossAxis != null);
+  }) : assert(crossAxisCount > 0),
+       assert(dayChildWidth >= 0),
+       assert(edgeChildWidth >= 0);
 
   /// The number of children in the cross axis.
   final int crossAxisCount;
@@ -2160,19 +2178,12 @@
     required this.firstDate,
     required this.lastDate,
     required this.displayedMonth,
-    this.dragStartBehavior = DragStartBehavior.start,
-  }) : assert(firstDate != null),
-       assert(lastDate != null),
-       assert(!firstDate.isAfter(lastDate)),
+  }) : assert(!firstDate.isAfter(lastDate)),
        assert(selectedDateStart == null || !selectedDateStart.isBefore(firstDate)),
        assert(selectedDateEnd == null || !selectedDateEnd.isBefore(firstDate)),
        assert(selectedDateStart == null || !selectedDateStart.isAfter(lastDate)),
        assert(selectedDateEnd == null || !selectedDateEnd.isAfter(lastDate)),
-       assert(selectedDateStart == null || selectedDateEnd == null || !selectedDateStart.isAfter(selectedDateEnd)),
-       assert(currentDate != null),
-       assert(onChanged != null),
-       assert(displayedMonth != null),
-       assert(dragStartBehavior != null);
+       assert(selectedDateStart == null || selectedDateEnd == null || !selectedDateStart.isAfter(selectedDateEnd));
 
   /// The currently selected start date.
   ///
@@ -2199,25 +2210,6 @@
   /// The month whose days are displayed by this picker.
   final DateTime displayedMonth;
 
-  /// Determines the way that drag start behavior is handled.
-  ///
-  /// If set to [DragStartBehavior.start], the drag gesture used to scroll a
-  /// date picker wheel will begin at the position where the drag gesture won
-  /// the arena. If set to [DragStartBehavior.down] it will begin at the position
-  /// where a down event is first detected.
-  ///
-  /// In general, setting this to [DragStartBehavior.start] will make drag
-  /// animation smoother and setting it to [DragStartBehavior.down] will make
-  /// drag behavior feel slightly more reactive.
-  ///
-  /// By default, the drag start behavior is [DragStartBehavior.start].
-  ///
-  /// See also:
-  ///
-  ///  * [DragGestureRecognizer.dragStartBehavior], which gives an example for
-  ///    the different behaviors.
-  final DragStartBehavior dragStartBehavior;
-
   @override
   _MonthItemState createState() => _MonthItemState();
 }
@@ -2255,7 +2247,8 @@
   }
 
   Color _highlightColor(BuildContext context) {
-    return Theme.of(context).colorScheme.primary.withOpacity(0.12);
+    return DatePickerTheme.of(context).rangeSelectionBackgroundColor
+      ?? DatePickerTheme.defaults(context).rangeSelectionBackgroundColor!;
   }
 
   void _dayFocusChanged(bool focused) {
@@ -2286,6 +2279,8 @@
     final ColorScheme colorScheme = theme.colorScheme;
     final TextTheme textTheme = theme.textTheme;
     final MaterialLocalizations localizations = MaterialLocalizations.of(context);
+    final DatePickerThemeData datePickerTheme = DatePickerTheme.of(context);
+    final DatePickerThemeData defaults = DatePickerTheme.defaults(context);
     final TextDirection textDirection = Directionality.of(context);
     final Color highlightColor = _highlightColor(context);
     final int day = dayToBuild.day;
@@ -2302,14 +2297,42 @@
       dayToBuild.isAfter(widget.selectedDateStart!) &&
       dayToBuild.isBefore(widget.selectedDateEnd!);
 
+    T? effectiveValue<T>(T? Function(DatePickerThemeData? theme) getProperty) {
+      return getProperty(datePickerTheme) ?? getProperty(defaults);
+    }
+
+    T? resolve<T>(MaterialStateProperty<T>? Function(DatePickerThemeData? theme) getProperty, Set<MaterialState> states) {
+      return effectiveValue(
+        (DatePickerThemeData? theme) {
+          return getProperty(theme)?.resolve(states);
+        },
+      );
+    }
+
+    final Set<MaterialState> states = <MaterialState>{
+      if (isDisabled) MaterialState.disabled,
+      if (isSelectedDayStart || isSelectedDayEnd) MaterialState.selected,
+    };
+
+    final Color? dayForegroundColor = resolve<Color?>((DatePickerThemeData? theme) => theme?.dayForegroundColor, states);
+    final Color? dayBackgroundColor = resolve<Color?>((DatePickerThemeData? theme) => theme?.dayBackgroundColor, states);
+    final MaterialStateProperty<Color?> dayOverlayColor = MaterialStateProperty.resolveWith<Color?>(
+      (Set<MaterialState> states) => effectiveValue(
+        (DatePickerThemeData? theme) =>
+          isInRange
+            ? theme?.rangeSelectionOverlayColor?.resolve(states)
+             : theme?.dayOverlayColor?.resolve(states),
+      )
+    );
+
     _HighlightPainter? highlightPainter;
 
     if (isSelectedDayStart || isSelectedDayEnd) {
       // The selected start and end dates gets a circle background
       // highlight, and a contrasting text color.
-      itemStyle = textTheme.bodyMedium?.apply(color: colorScheme.onPrimary);
+      itemStyle = textTheme.bodyMedium?.apply(color: dayForegroundColor);
       decoration = BoxDecoration(
-        color: colorScheme.primary,
+        color: dayBackgroundColor,
         shape: BoxShape.circle,
       );
 
@@ -2348,7 +2371,8 @@
     // day of month before the rest of the date, as they are looking
     // for the day of month. To do that we prepend day of month to the
     // formatted full date.
-    String semanticLabel = '${localizations.formatDecimal(day)}, ${localizations.formatFullDate(dayToBuild)}';
+    final String semanticLabelSuffix = DateUtils.isSameDay(widget.currentDate, dayToBuild) ? ', ${localizations.currentDateLabel}' : '';
+    String semanticLabel = '${localizations.formatDecimal(day)}, ${localizations.formatFullDate(dayToBuild)}$semanticLabelSuffix';
     if (isSelectedDayStart) {
       semanticLabel = localizations.dateRangeStartDateSemanticLabel(semanticLabel);
     } else if (isSelectedDayEnd) {
@@ -2380,7 +2404,8 @@
         focusNode: _dayFocusNodes[day - 1],
         onTap: () => widget.onChanged(dayToBuild),
         radius: _monthItemRowHeight / 2 + 4,
-        splashColor: colorScheme.primary.withOpacity(0.38),
+        statesController: MaterialStatesController(states),
+        overlayColor: dayOverlayColor,
         onFocusChange: _dayFocusChanged,
         child: dayWidget,
       );
@@ -2403,8 +2428,7 @@
     final int daysInMonth = DateUtils.getDaysInMonth(year, month);
     final int dayOffset = DateUtils.firstDayOffset(year, month, localizations);
     final int weeks = ((daysInMonth + dayOffset) / DateTime.daysPerWeek).ceil();
-    final double gridHeight =
-        weeks * _monthItemRowHeight + (weeks - 1) * _monthItemSpaceBetweenRows;
+    final double gridHeight = weeks * _monthItemRowHeight + (weeks - 1) * _monthItemSpaceBetweenRows;
     final List<Widget> dayItems = <Widget>[];
 
     for (int i = 0; true; i += 1) {
@@ -2468,7 +2492,7 @@
       paddedDayItems.addAll(weekList);
     }
 
-    final double maxWidth = MediaQuery.of(context).orientation == Orientation.landscape
+    final double maxWidth = MediaQuery.orientationOf(context) == Orientation.landscape
       ? _maxCalendarWidthLandscape
       : _maxCalendarWidthPortrait;
     return Column(
@@ -2619,18 +2643,17 @@
 
   @override
   Widget build(BuildContext context) {
-    final ThemeData theme = Theme.of(context);
-    final ColorScheme colorScheme = theme.colorScheme;
+    final bool useMaterial3 = Theme.of(context).useMaterial3;
     final MaterialLocalizations localizations = MaterialLocalizations.of(context);
-    final Orientation orientation = MediaQuery.of(context).orientation;
-    final TextTheme textTheme = theme.textTheme;
+    final Orientation orientation = MediaQuery.orientationOf(context);
+    final DatePickerThemeData datePickerTheme = DatePickerTheme.of(context);
+    final DatePickerThemeData defaults = DatePickerTheme.defaults(context);
 
-    final Color onPrimarySurfaceColor = colorScheme.brightness == Brightness.light
-        ? colorScheme.onPrimary
-        : colorScheme.onSurface;
-    final TextStyle? dateStyle = orientation == Orientation.landscape
-        ? textTheme.headlineSmall?.apply(color: onPrimarySurfaceColor)
-        : textTheme.headlineMedium?.apply(color: onPrimarySurfaceColor);
+    final Color? headerForegroundColor = datePickerTheme.headerForegroundColor ?? defaults.headerForegroundColor;
+    final TextStyle? headlineStyle = (datePickerTheme.headerHeadlineStyle ?? defaults.headerHeadlineStyle)?.copyWith(
+      color: headerForegroundColor,
+    );
+
     final String dateText = _formatDateRange(context, selectedStartDate, selectedEndDate, currentDate!);
     final String semanticDateText = selectedStartDate != null && selectedEndDate != null
         ? '${localizations.formatMediumDate(selectedStartDate!)} – ${localizations.formatMediumDate(selectedEndDate!)}'
@@ -2638,13 +2661,13 @@
 
     final Widget header = _DatePickerHeader(
       helpText: helpText ?? (
-        Theme.of(context).useMaterial3
+        useMaterial3
           ? localizations.dateRangePickerHelpText
           : localizations.dateRangePickerHelpText.toUpperCase()
       ),
       titleText: dateText,
       titleSemanticsLabel: semanticDateText,
-      titleStyle: dateStyle,
+      titleStyle: headlineStyle,
       orientation: orientation,
       isShort: orientation == Orientation.landscape,
       entryModeButton: entryModeButton,
@@ -2660,7 +2683,7 @@
           TextButton(
             onPressed: onCancel,
             child: Text(cancelText ?? (
-              theme.useMaterial3
+              useMaterial3
                 ? localizations.cancelButtonLabel
                 : localizations.cancelButtonLabel.toUpperCase()
             )),
@@ -2732,14 +2755,8 @@
     this.autovalidate = false,
   }) : initialStartDate = initialStartDate == null ? null : DateUtils.dateOnly(initialStartDate),
        initialEndDate = initialEndDate == null ? null : DateUtils.dateOnly(initialEndDate),
-       assert(firstDate != null),
        firstDate = DateUtils.dateOnly(firstDate),
-       assert(lastDate != null),
-       lastDate = DateUtils.dateOnly(lastDate),
-       assert(firstDate != null),
-       assert(lastDate != null),
-       assert(autofocus != null),
-       assert(autovalidate != null);
+       lastDate = DateUtils.dateOnly(lastDate);
 
   /// The [DateTime] that represents the start of the initial date range selection.
   final DateTime? initialStartDate;
@@ -2915,8 +2932,13 @@
 
   @override
   Widget build(BuildContext context) {
+    final ThemeData theme = Theme.of(context);
+    final bool useMaterial3 = theme.useMaterial3;
     final MaterialLocalizations localizations = MaterialLocalizations.of(context);
-    final InputDecorationTheme inputTheme = Theme.of(context).inputDecorationTheme;
+    final InputDecorationTheme inputTheme = theme.inputDecorationTheme;
+    final InputBorder inputBorder = inputTheme.border
+      ?? (useMaterial3 ? const OutlineInputBorder() : const UnderlineInputBorder());
+
     return Row(
       crossAxisAlignment: CrossAxisAlignment.start,
       children: <Widget>[
@@ -2924,7 +2946,7 @@
           child: TextField(
             controller: _startController,
             decoration: InputDecoration(
-              border: inputTheme.border ?? const UnderlineInputBorder(),
+              border: inputBorder,
               filled: inputTheme.filled,
               hintText: widget.fieldStartHintText ?? localizations.dateHelpText,
               labelText: widget.fieldStartLabelText ?? localizations.dateRangeStartLabel,
@@ -2940,7 +2962,7 @@
           child: TextField(
             controller: _endController,
             decoration: InputDecoration(
-              border: inputTheme.border ?? const UnderlineInputBorder(),
+              border: inputBorder,
               filled: inputTheme.filled,
               hintText: widget.fieldEndHintText ?? localizations.dateHelpText,
               labelText: widget.fieldEndLabelText ?? localizations.dateRangeEndLabel,
diff --git a/framework/lib/src/material/date_picker_theme.dart b/framework/lib/src/material/date_picker_theme.dart
new file mode 100644
index 0000000..c9a394f
--- /dev/null
+++ b/framework/lib/src/material/date_picker_theme.dart
@@ -0,0 +1,975 @@
+// 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:engine/ui.dart' show lerpDouble;
+
+import 'package:flute/foundation.dart';
+import 'package:flute/widgets.dart';
+
+import 'color_scheme.dart';
+import 'colors.dart';
+import 'material_state.dart';
+import 'text_theme.dart';
+import 'theme.dart';
+
+// Examples can assume:
+// late BuildContext context;
+
+/// Overrides the default values of visual properties for descendant
+/// [DatePickerDialog] widgets.
+///
+/// Descendant widgets obtain the current [DatePickerThemeData] object with
+/// [DatePickerTheme.of]. Instances of [DatePickerTheme] can
+/// be customized with [DatePickerThemeData.copyWith].
+///
+/// Typically a [DatePickerTheme] is specified as part of the overall
+/// [Theme] with [ThemeData.datePickerTheme].
+///
+/// All [DatePickerThemeData] properties are null by default. When null,
+/// the [DatePickerDialog] computes its own default values, typically based on
+/// the overall theme's [ThemeData.colorScheme], [ThemeData.textTheme], and
+/// [ThemeData.iconTheme].
+@immutable
+class DatePickerThemeData with Diagnosticable {
+  /// Creates a [DatePickerThemeData] that can be used to override default properties
+  /// in a [DatePickerTheme] widget.
+  const DatePickerThemeData({
+    this.backgroundColor,
+    this.elevation,
+    this.shadowColor,
+    this.surfaceTintColor,
+    this.shape,
+    this.headerBackgroundColor,
+    this.headerForegroundColor,
+    this.headerHeadlineStyle,
+    this.headerHelpStyle,
+    this.weekdayStyle,
+    this.dayStyle,
+    this.dayForegroundColor,
+    this.dayBackgroundColor,
+    this.dayOverlayColor,
+    this.todayForegroundColor,
+    this.todayBackgroundColor,
+    this.todayBorder,
+    this.yearStyle,
+    this.yearForegroundColor,
+    this.yearBackgroundColor,
+    this.yearOverlayColor,
+    this.rangePickerBackgroundColor,
+    this.rangePickerElevation,
+    this.rangePickerShadowColor,
+    this.rangePickerSurfaceTintColor,
+    this.rangePickerShape,
+    this.rangePickerHeaderBackgroundColor,
+    this.rangePickerHeaderForegroundColor,
+    this.rangePickerHeaderHeadlineStyle,
+    this.rangePickerHeaderHelpStyle,
+    this.rangeSelectionBackgroundColor,
+    this.rangeSelectionOverlayColor,
+  });
+
+  /// Overrides the default value of [Dialog.backgroundColor].
+  final Color? backgroundColor;
+
+  /// Overrides the default value of [Dialog.elevation].
+  ///
+  /// See also:
+  ///   [Material.elevation], which explains how elevation is related to a component's shadow.
+  final double? elevation;
+
+  /// Overrides the default value of [Dialog.shadowColor].
+  ///
+  /// See also:
+  ///   [Material.shadowColor], which explains how the shadow is rendered.
+  final Color? shadowColor;
+
+  /// Overrides the default value of [Dialog.surfaceTintColor].
+  ///
+  /// See also:
+  ///   [Material.surfaceTintColor], which explains how this color is related to
+  ///   [elevation] and [backgroundColor].
+  final Color? surfaceTintColor;
+
+  /// Overrides the default value of [Dialog.shape].
+  ///
+  /// If [elevation] is greater than zero then a shadow is shown and the shadow's
+  /// shape mirrors the shape of the dialog.
+  final ShapeBorder? shape;
+
+  /// Overrides the header's default background fill color.
+  ///
+  /// The dialog's header displays the currently selected date.
+  final Color? headerBackgroundColor;
+
+  /// Overrides the header's default color used for text labels and icons.
+  ///
+  /// The dialog's header displays the currently selected date.
+  ///
+  /// This is used instead of the [TextStyle.color] property of [headerHeadlineStyle]
+  /// and [headerHelpStyle].
+  final Color? headerForegroundColor;
+
+  /// Overrides the header's default headline text style.
+  ///
+  /// The dialog's header displays the currently selected date.
+  ///
+  /// The [TextStyle.color] of the [headerHeadlineStyle] is not used,
+  /// [headerForegroundColor] is used instead.
+  final TextStyle? headerHeadlineStyle;
+
+  /// Overrides the header's default help text style.
+  ///
+  /// The help text (also referred to as "supporting text" in the Material
+  /// spec) is usually a prompt to the user at the top of the header
+  /// (i.e. 'Select date').
+  ///
+  /// The [TextStyle.color] of the [headerHelpStyle] is not used,
+  /// [headerForegroundColor] is used instead.
+  ///
+  /// See also:
+  ///   [DatePickerDialog.helpText], which specifies the help text.
+  final TextStyle? headerHelpStyle;
+
+  /// Overrides the default text style used for the row of weekday
+  /// labels at the top of the date picker grid.
+  final TextStyle? weekdayStyle;
+
+  /// Overrides the default text style used for each individual day
+  /// label in the grid of the date picker.
+  ///
+  /// The [TextStyle.color] of the [dayStyle] is not used,
+  /// [dayForegroundColor] is used instead.
+  final TextStyle? dayStyle;
+
+  /// Overrides the default color used to paint the day labels in the
+  /// grid of the date picker.
+  ///
+  /// This will be used instead of the color provided in [dayStyle].
+  final MaterialStateProperty<Color?>? dayForegroundColor;
+
+  /// Overrides the default color used to paint the background of the
+  /// day labels in the grid of the date picker.
+  final MaterialStateProperty<Color?>? dayBackgroundColor;
+
+  /// Overriddes the default highlight color that's typically used to
+  /// indicate that a day in the grid is focused, hovered, or pressed.
+  final MaterialStateProperty<Color?>? dayOverlayColor;
+
+  /// Overrides the default color used to paint the
+  /// [DatePickerDialog.currentDate] label in the grid of the dialog's
+  /// [CalendarDatePicker] and the corresponding year in the dialog's
+  /// [YearPicker].
+  ///
+  /// This will be used instead of the [TextStyle.color] provided in [dayStyle].
+  final MaterialStateProperty<Color?>? todayForegroundColor;
+
+  /// Overrides the default color used to paint the background of the
+  /// [DatePickerDialog.currentDate] label in the grid of the date picker.
+  final MaterialStateProperty<Color?>? todayBackgroundColor;
+
+  /// Overrides the border used to paint the
+  /// [DatePickerDialog.currentDate] label in the grid of the date
+  /// picker.
+  ///
+  /// The border side's [BorderSide.color] is not used,
+  /// [todayForegroundColor] is used instead.
+  final BorderSide? todayBorder;
+
+  /// Overrides the default text style used to paint each of the year
+  /// entries in the year selector of the date picker.
+  ///
+  /// The [TextStyle.color] of the [yearStyle] is not used,
+  /// [yearForegroundColor] is used instead.
+  final TextStyle? yearStyle;
+
+  /// Overrides the default color used to paint the year labels in the year
+  /// selector of the date picker.
+  ///
+  /// This will be used instead of the color provided in [yearStyle].
+  final MaterialStateProperty<Color?>? yearForegroundColor;
+
+  /// Overrides the default color used to paint the background of the
+  /// year labels in the year selector of the of the date picker.
+  final MaterialStateProperty<Color?>? yearBackgroundColor;
+
+  /// Overrides the default highlight color that's typically used to
+  /// indicate that a year in the year selector is focused, hovered,
+  /// or pressed.
+  final MaterialStateProperty<Color?>? yearOverlayColor;
+
+  /// Overrides the default [Scaffold.backgroundColor] for
+  /// [DateRangePickerDialog].
+  final Color? rangePickerBackgroundColor;
+
+  /// Overrides the default elevation of the full screen
+  /// [DateRangePickerDialog].
+  ///
+  /// See also:
+  ///   [Material.elevation], which explains how elevation is related to a component's shadow.
+  final double? rangePickerElevation;
+
+  /// Overrides the color of the shadow painted below a full screen
+  /// [DateRangePickerDialog].
+  ///
+  /// See also:
+  ///   [Material.shadowColor], which explains how the shadow is rendered.
+  final Color? rangePickerShadowColor;
+
+  /// Overrides the default color of the surface tint overlay applied
+  /// to the [backgroundColor] of a full screen
+  /// [DateRangePickerDialog]'s to indicate elevation.
+  ///
+  /// See also:
+  ///   [Material.surfaceTintColor], which explains how this color is related to
+  ///   [elevation].
+  final Color? rangePickerSurfaceTintColor;
+
+  /// Overrides the default overall shape of a full screen
+  /// [DateRangePickerDialog].
+  ///
+  /// If [elevation] is greater than zero then a shadow is shown and the shadow's
+  /// shape mirrors the shape of the dialog.
+  ///
+  ///   [Material.surfaceTintColor], which explains how this color is related to
+  ///   [elevation].
+  final ShapeBorder? rangePickerShape;
+
+  /// Overrides the default background fill color for [DateRangePickerDialog].
+  ///
+  /// The dialog's header displays the currently selected date range.
+  final Color? rangePickerHeaderBackgroundColor;
+
+  /// Overrides the default color used for text labels and icons in
+  /// the header of a full screen [DateRangePickerDialog]
+  ///
+  /// The dialog's header displays the currently selected date range.
+  ///
+  /// This is used instead of any colors provided by
+  /// [rangePickerHeaderHeadlineStyle] or [rangePickerHeaderHelpStyle].
+  final Color? rangePickerHeaderForegroundColor;
+
+  /// Overrides the default text style used for the headline text in
+  /// the header of a full screen [DateRangePickerDialog].
+  ///
+  /// The dialog's header displays the currently selected date range.
+  ///
+  /// The [TextStyle.color] of [rangePickerHeaderHeadlineStyle] is not used,
+  /// [rangePickerHeaderForegroundColor] is used instead.
+  final TextStyle? rangePickerHeaderHeadlineStyle;
+
+  /// Overrides the default text style used for the help text of the
+  /// header of a full screen [DateRangePickerDialog].
+  ///
+  /// The help text (also referred to as "supporting text" in the Material
+  /// spec) is usually a prompt to the user at the top of the header
+  /// (i.e. 'Select date').
+  ///
+  /// The [TextStyle.color] of the [rangePickerHeaderHelpStyle] is not used,
+  /// [rangePickerHeaderForegroundColor] is used instead.
+  ///
+  /// See also:
+  ///   [DateRangePickerDialog.helpText], which specifies the help text.
+  final TextStyle? rangePickerHeaderHelpStyle;
+
+  /// Overrides the default background color used to paint days
+  /// selected between the start and end dates in a
+  /// [DateRangePickerDialog].
+  final Color? rangeSelectionBackgroundColor;
+
+  /// Overrides the default highlight color that's typically used to
+  /// indicate that a date in the selected range of a
+  /// [DateRangePickerDialog] is focused, hovered, or pressed.
+  final MaterialStateProperty<Color?>? rangeSelectionOverlayColor;
+
+  /// Creates a copy of this object with the given fields replaced with the
+  /// new values.
+  DatePickerThemeData copyWith({
+    Color? backgroundColor,
+    double? elevation,
+    Color? shadowColor,
+    Color? surfaceTintColor,
+    ShapeBorder? shape,
+    Color? headerBackgroundColor,
+    Color? headerForegroundColor,
+    TextStyle? headerHeadlineStyle,
+    TextStyle? headerHelpStyle,
+    TextStyle? weekdayStyle,
+    TextStyle? dayStyle,
+    MaterialStateProperty<Color?>? dayForegroundColor,
+    MaterialStateProperty<Color?>? dayBackgroundColor,
+    MaterialStateProperty<Color?>? dayOverlayColor,
+    MaterialStateProperty<Color?>? todayForegroundColor,
+    MaterialStateProperty<Color?>? todayBackgroundColor,
+    BorderSide? todayBorder,
+    TextStyle? yearStyle,
+    MaterialStateProperty<Color?>? yearForegroundColor,
+    MaterialStateProperty<Color?>? yearBackgroundColor,
+    MaterialStateProperty<Color?>? yearOverlayColor,
+    Color? rangePickerBackgroundColor,
+    double? rangePickerElevation,
+    Color? rangePickerShadowColor,
+    Color? rangePickerSurfaceTintColor,
+    ShapeBorder? rangePickerShape,
+    Color? rangePickerHeaderBackgroundColor,
+    Color? rangePickerHeaderForegroundColor,
+    TextStyle? rangePickerHeaderHeadlineStyle,
+    TextStyle? rangePickerHeaderHelpStyle,
+    Color? rangeSelectionBackgroundColor,
+    MaterialStateProperty<Color?>? rangeSelectionOverlayColor,
+  }) {
+    return DatePickerThemeData(
+      backgroundColor: backgroundColor ?? this.backgroundColor,
+      elevation: elevation ?? this.elevation,
+      shadowColor: shadowColor ?? this.shadowColor,
+      surfaceTintColor: surfaceTintColor ?? this.surfaceTintColor,
+      shape: shape ?? this.shape,
+      headerBackgroundColor: headerBackgroundColor ?? this.headerBackgroundColor,
+      headerForegroundColor: headerForegroundColor ?? this.headerForegroundColor,
+      headerHeadlineStyle: headerHeadlineStyle ?? this.headerHeadlineStyle,
+      headerHelpStyle: headerHelpStyle ?? this.headerHelpStyle,
+      weekdayStyle: weekdayStyle ?? this.weekdayStyle,
+      dayStyle: dayStyle ?? this.dayStyle,
+      dayForegroundColor: dayForegroundColor ?? this.dayForegroundColor,
+      dayBackgroundColor: dayBackgroundColor ?? this.dayBackgroundColor,
+      dayOverlayColor: dayOverlayColor ?? this.dayOverlayColor,
+      todayForegroundColor: todayForegroundColor ?? this.todayForegroundColor,
+      todayBackgroundColor: todayBackgroundColor ?? this.todayBackgroundColor,
+      todayBorder: todayBorder ?? this.todayBorder,
+      yearStyle: yearStyle ?? this.yearStyle,
+      yearForegroundColor: yearForegroundColor ?? this.yearForegroundColor,
+      yearBackgroundColor: yearBackgroundColor ?? this.yearBackgroundColor,
+      yearOverlayColor: yearOverlayColor ?? this.yearOverlayColor,
+      rangePickerBackgroundColor: rangePickerBackgroundColor ?? this.rangePickerBackgroundColor,
+      rangePickerElevation: rangePickerElevation ?? this.rangePickerElevation,
+      rangePickerShadowColor: rangePickerShadowColor ?? this.rangePickerShadowColor,
+      rangePickerSurfaceTintColor: rangePickerSurfaceTintColor ?? this.rangePickerSurfaceTintColor,
+      rangePickerShape: rangePickerShape ?? this.rangePickerShape,
+      rangePickerHeaderBackgroundColor: rangePickerHeaderBackgroundColor ?? this.rangePickerHeaderBackgroundColor,
+      rangePickerHeaderForegroundColor: rangePickerHeaderForegroundColor ?? this.rangePickerHeaderForegroundColor,
+      rangePickerHeaderHeadlineStyle: rangePickerHeaderHeadlineStyle ?? this.rangePickerHeaderHeadlineStyle,
+      rangePickerHeaderHelpStyle: rangePickerHeaderHelpStyle ?? this.rangePickerHeaderHelpStyle,
+      rangeSelectionBackgroundColor: rangeSelectionBackgroundColor ?? this.rangeSelectionBackgroundColor,
+      rangeSelectionOverlayColor: rangeSelectionOverlayColor ?? this.rangeSelectionOverlayColor,
+    );
+  }
+
+  /// Linearly interpolates between two [DatePickerThemeData].
+  static DatePickerThemeData lerp(DatePickerThemeData? a, DatePickerThemeData? b, double t) {
+    return DatePickerThemeData(
+      backgroundColor: Color.lerp(a?.backgroundColor, b?.backgroundColor, t),
+      elevation: lerpDouble(a?.elevation, b?.elevation, t),
+      shadowColor: Color.lerp(a?.shadowColor, b?.shadowColor, t),
+      surfaceTintColor: Color.lerp(a?.surfaceTintColor, b?.surfaceTintColor, t),
+      shape: ShapeBorder.lerp(a?.shape, b?.shape, t),
+      headerBackgroundColor: Color.lerp(a?.headerBackgroundColor, b?.headerBackgroundColor, t),
+      headerForegroundColor: Color.lerp(a?.headerForegroundColor, b?.headerForegroundColor, t),
+      headerHeadlineStyle: TextStyle.lerp(a?.headerHeadlineStyle, b?.headerHeadlineStyle, t),
+      headerHelpStyle: TextStyle.lerp(a?.headerHelpStyle, b?.headerHelpStyle, t),
+      weekdayStyle: TextStyle.lerp(a?.weekdayStyle, b?.weekdayStyle, t),
+      dayStyle: TextStyle.lerp(a?.dayStyle, b?.dayStyle, t),
+      dayForegroundColor: MaterialStateProperty.lerp<Color?>(a?.dayForegroundColor, b?.dayForegroundColor, t, Color.lerp),
+      dayBackgroundColor: MaterialStateProperty.lerp<Color?>(a?.dayBackgroundColor, b?.dayBackgroundColor, t, Color.lerp),
+      dayOverlayColor: MaterialStateProperty.lerp<Color?>(a?.dayOverlayColor, b?.dayOverlayColor, t, Color.lerp),
+      todayForegroundColor: MaterialStateProperty.lerp<Color?>(a?.todayForegroundColor, b?.todayForegroundColor, t, Color.lerp),
+      todayBackgroundColor: MaterialStateProperty.lerp<Color?>(a?.todayBackgroundColor, b?.todayBackgroundColor, t, Color.lerp),
+      todayBorder: _lerpBorderSide(a?.todayBorder, b?.todayBorder, t),
+      yearStyle: TextStyle.lerp(a?.yearStyle, b?.yearStyle, t),
+      yearForegroundColor: MaterialStateProperty.lerp<Color?>(a?.yearForegroundColor, b?.yearForegroundColor, t, Color.lerp),
+      yearBackgroundColor: MaterialStateProperty.lerp<Color?>(a?.yearBackgroundColor, b?.yearBackgroundColor, t, Color.lerp),
+      yearOverlayColor: MaterialStateProperty.lerp<Color?>(a?.yearOverlayColor, b?.yearOverlayColor, t, Color.lerp),
+      rangePickerBackgroundColor: Color.lerp(a?.rangePickerBackgroundColor, b?.rangePickerBackgroundColor, t),
+      rangePickerElevation: lerpDouble(a?.rangePickerElevation, b?.rangePickerElevation, t),
+      rangePickerShadowColor: Color.lerp(a?.rangePickerShadowColor, b?.rangePickerShadowColor, t),
+      rangePickerSurfaceTintColor: Color.lerp(a?.rangePickerSurfaceTintColor, b?.rangePickerSurfaceTintColor, t),
+      rangePickerShape: ShapeBorder.lerp(a?.rangePickerShape, b?.rangePickerShape, t),
+      rangePickerHeaderBackgroundColor: Color.lerp(a?.rangePickerHeaderBackgroundColor, b?.rangePickerHeaderBackgroundColor, t),
+      rangePickerHeaderForegroundColor: Color.lerp(a?.rangePickerHeaderForegroundColor, b?.rangePickerHeaderForegroundColor, t),
+      rangePickerHeaderHeadlineStyle: TextStyle.lerp(a?.rangePickerHeaderHeadlineStyle, b?.rangePickerHeaderHeadlineStyle, t),
+      rangePickerHeaderHelpStyle: TextStyle.lerp(a?.rangePickerHeaderHelpStyle, b?.rangePickerHeaderHelpStyle, t),
+      rangeSelectionBackgroundColor: Color.lerp(a?.rangeSelectionBackgroundColor, b?.rangeSelectionBackgroundColor, t),
+      rangeSelectionOverlayColor: MaterialStateProperty.lerp<Color?>(a?.rangeSelectionOverlayColor, b?.rangeSelectionOverlayColor, t, Color.lerp),
+    );
+  }
+
+  static BorderSide? _lerpBorderSide(BorderSide? a, BorderSide? b, double t) {
+    if (a == null && b == null) {
+      return null;
+    }
+    if (a == null) {
+      return BorderSide.lerp(BorderSide(width: 0, color: b!.color.withAlpha(0)), b, t);
+    }
+    return BorderSide.lerp(a, BorderSide(width: 0, color: a.color.withAlpha(0)), t);
+  }
+
+  @override
+  int get hashCode => Object.hashAll(<Object?>[
+    backgroundColor,
+    elevation,
+    shadowColor,
+    surfaceTintColor,
+    shape,
+    headerBackgroundColor,
+    headerForegroundColor,
+    headerHeadlineStyle,
+    headerHelpStyle,
+    weekdayStyle,
+    dayStyle,
+    dayForegroundColor,
+    dayBackgroundColor,
+    dayOverlayColor,
+    todayForegroundColor,
+    todayBackgroundColor,
+    todayBorder,
+    yearStyle,
+    yearForegroundColor,
+    yearBackgroundColor,
+    yearOverlayColor,
+    rangePickerBackgroundColor,
+    rangePickerElevation,
+    rangePickerShadowColor,
+    rangePickerSurfaceTintColor,
+    rangePickerShape,
+    rangePickerHeaderBackgroundColor,
+    rangePickerHeaderForegroundColor,
+    rangePickerHeaderHeadlineStyle,
+    rangePickerHeaderHelpStyle,
+    rangeSelectionBackgroundColor,
+    rangeSelectionOverlayColor,
+  ]);
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other)) {
+      return true;
+    }
+    return other is DatePickerThemeData
+      && other.backgroundColor == backgroundColor
+      && other.elevation == elevation
+      && other.shadowColor == shadowColor
+      && other.surfaceTintColor == surfaceTintColor
+      && other.shape == shape
+      && other.headerBackgroundColor == headerBackgroundColor
+      && other.headerForegroundColor == headerForegroundColor
+      && other.headerHeadlineStyle == headerHeadlineStyle
+      && other.headerHelpStyle == headerHelpStyle
+      && other.weekdayStyle == weekdayStyle
+      && other.dayStyle == dayStyle
+      && other.dayForegroundColor == dayForegroundColor
+      && other.dayBackgroundColor == dayBackgroundColor
+      && other.dayOverlayColor == dayOverlayColor
+      && other.todayForegroundColor == todayForegroundColor
+      && other.todayBackgroundColor == todayBackgroundColor
+      && other.todayBorder == todayBorder
+      && other.yearStyle == yearStyle
+      && other.yearForegroundColor == yearForegroundColor
+      && other.yearBackgroundColor == yearBackgroundColor
+      && other.yearOverlayColor == yearOverlayColor
+      && other.rangePickerBackgroundColor == rangePickerBackgroundColor
+      && other.rangePickerElevation == rangePickerElevation
+      && other.rangePickerShadowColor == rangePickerShadowColor
+      && other.rangePickerSurfaceTintColor == rangePickerSurfaceTintColor
+      && other.rangePickerShape == rangePickerShape
+      && other.rangePickerHeaderBackgroundColor == rangePickerHeaderBackgroundColor
+      && other.rangePickerHeaderForegroundColor == rangePickerHeaderForegroundColor
+      && other.rangePickerHeaderHeadlineStyle == rangePickerHeaderHeadlineStyle
+      && other.rangePickerHeaderHelpStyle == rangePickerHeaderHelpStyle
+      && other.rangeSelectionBackgroundColor == rangeSelectionBackgroundColor
+      && other.rangeSelectionOverlayColor == rangeSelectionOverlayColor;
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(ColorProperty('backgroundColor', backgroundColor, defaultValue: null));
+    properties.add(DoubleProperty('elevation', elevation, defaultValue: null));
+    properties.add(ColorProperty('shadowColor', shadowColor, defaultValue: null));
+    properties.add(ColorProperty('surfaceTintColor', surfaceTintColor, defaultValue: null));
+    properties.add(DiagnosticsProperty<ShapeBorder>('shape', shape, defaultValue: null));
+    properties.add(ColorProperty('headerBackgroundColor', headerBackgroundColor, defaultValue: null));
+    properties.add(ColorProperty('headerForegroundColor', headerForegroundColor, defaultValue: null));
+    properties.add(DiagnosticsProperty<TextStyle>('headerHeadlineStyle', headerHeadlineStyle, defaultValue: null));
+    properties.add(DiagnosticsProperty<TextStyle>('headerHelpStyle', headerHelpStyle, defaultValue: null));
+    properties.add(DiagnosticsProperty<TextStyle>('weekDayStyle', weekdayStyle, defaultValue: null));
+    properties.add(DiagnosticsProperty<TextStyle>('dayStyle', dayStyle, defaultValue: null));
+    properties.add(DiagnosticsProperty<MaterialStateProperty<Color?>>('dayForegroundColor', dayForegroundColor, defaultValue: null));
+    properties.add(DiagnosticsProperty<MaterialStateProperty<Color?>>('dayBackgroundColor', dayBackgroundColor, defaultValue: null));
+    properties.add(DiagnosticsProperty<MaterialStateProperty<Color?>>('dayOverlayColor', dayOverlayColor, defaultValue: null));
+    properties.add(DiagnosticsProperty<MaterialStateProperty<Color?>>('todayForegroundColor', todayForegroundColor, defaultValue: null));
+    properties.add(DiagnosticsProperty<MaterialStateProperty<Color?>>('todayBackgroundColor', todayBackgroundColor, defaultValue: null));
+    properties.add(DiagnosticsProperty<BorderSide?>('todayBorder', todayBorder, defaultValue: null));
+    properties.add(DiagnosticsProperty<TextStyle>('yearStyle', yearStyle, defaultValue: null));
+    properties.add(DiagnosticsProperty<MaterialStateProperty<Color?>>('yearForegroundColor', yearForegroundColor, defaultValue: null));
+    properties.add(DiagnosticsProperty<MaterialStateProperty<Color?>>('yearBackgroundColor', yearBackgroundColor, defaultValue: null));
+    properties.add(DiagnosticsProperty<MaterialStateProperty<Color?>>('yearOverlayColor', yearOverlayColor, defaultValue: null));
+    properties.add(ColorProperty('rangePickerBackgroundColor', rangePickerBackgroundColor, defaultValue: null));
+    properties.add(DoubleProperty('rangePickerElevation', rangePickerElevation, defaultValue: null));
+    properties.add(ColorProperty('rangePickerShadowColor', rangePickerShadowColor, defaultValue: null));
+    properties.add(ColorProperty('rangePickerSurfaceTintColor', rangePickerSurfaceTintColor, defaultValue: null));
+    properties.add(DiagnosticsProperty<ShapeBorder>('rangePickerShape', rangePickerShape, defaultValue: null));
+    properties.add(ColorProperty('rangePickerHeaderBackgroundColor', rangePickerHeaderBackgroundColor, defaultValue: null));
+    properties.add(ColorProperty('rangePickerHeaderForegroundColor', rangePickerHeaderForegroundColor, defaultValue: null));
+    properties.add(DiagnosticsProperty<TextStyle>('rangePickerHeaderHeadlineStyle', rangePickerHeaderHeadlineStyle, defaultValue: null));
+    properties.add(DiagnosticsProperty<TextStyle>('rangePickerHeaderHelpStyle', rangePickerHeaderHelpStyle, defaultValue: null));
+    properties.add(ColorProperty('rangeSelectionBackgroundColor', rangeSelectionBackgroundColor, defaultValue: null));
+    properties.add(DiagnosticsProperty<MaterialStateProperty<Color?>>('rangeSelectionOverlayColor', rangeSelectionOverlayColor, defaultValue: null));
+  }
+}
+
+/// An inherited widget that defines the visual properties for
+/// [DatePickerDialog]s in this widget's subtree.
+///
+/// Values specified here are used for [DatePickerDialog] properties that are not
+/// given an explicit non-null value.
+class DatePickerTheme extends InheritedTheme {
+  /// Creates a [DatePickerTheme] that controls visual parameters for
+  /// descendent [DatePickerDialog]s.
+  const DatePickerTheme({
+    super.key,
+    required this.data,
+    required super.child,
+  });
+
+  /// Specifies the visual properties used by descendant [DatePickerDialog]
+  /// widgets.
+  final DatePickerThemeData data;
+
+  /// The [data] from the closest instance of this class that encloses the given
+  /// context.
+  ///
+  /// If there is no [DatePickerTheme] in scope, this will return
+  /// [ThemeData.datePickerTheme] from the ambient [Theme].
+  ///
+  /// Typical usage is as follows:
+  ///
+  /// ```dart
+  /// DatePickerThemeData theme = DatePickerTheme.of(context);
+  /// ```
+  ///
+  /// See also:
+  ///
+  ///  * [maybeOf], which returns null if it doesn't find a
+  ///    [DatePickerTheme] ancestor.
+  ///  * [defaults], which will return the default properties used when no
+  ///    other [DatePickerTheme] has been provided.
+  static DatePickerThemeData of(BuildContext context) {
+    return maybeOf(context) ?? Theme.of(context).datePickerTheme;
+  }
+
+  /// The data from the closest instance of this class that encloses the given
+  /// context, if any.
+  ///
+  /// Use this function if you want to allow situations where no
+  /// [DatePickerTheme] is in scope. Prefer using [DatePickerTheme.of]
+  /// in situations where a [DatePickerThemeData] is expected to be
+  /// non-null.
+  ///
+  /// If there is no [DatePickerTheme] in scope, then this function will
+  /// return null.
+  ///
+  /// Typical usage is as follows:
+  ///
+  /// ```dart
+  /// DatePickerThemeData? theme = DatePickerTheme.maybeOf(context);
+  /// if (theme == null) {
+  ///   // Do something else instead.
+  /// }
+  /// ```
+  ///
+  /// See also:
+  ///
+  ///  * [of], which will return [ThemeData.datePickerTheme] if it doesn't
+  ///    find a [DatePickerTheme] ancestor, instead of returning null.
+  ///  * [defaults], which will return the default properties used when no
+  ///    other [DatePickerTheme] has been provided.
+  static DatePickerThemeData? maybeOf(BuildContext context) {
+    return context.dependOnInheritedWidgetOfExactType<DatePickerTheme>()?.data;
+  }
+
+  /// A DatePickerThemeData used as the default properties for date pickers.
+  ///
+  /// This is only used for properties not already specified in the ambient
+  /// [DatePickerTheme.of].
+  ///
+  /// See also:
+  ///
+  ///  * [of], which will return [ThemeData.datePickerTheme] if it doesn't
+  ///    find a [DatePickerTheme] ancestor, instead of returning null.
+  ///  * [maybeOf], which returns null if it doesn't find a
+  ///    [DatePickerTheme] ancestor.
+  static DatePickerThemeData defaults(BuildContext context) {
+    return Theme.of(context).useMaterial3
+      ? _DatePickerDefaultsM3(context)
+      : _DatePickerDefaultsM2(context);
+  }
+
+  @override
+  Widget wrap(BuildContext context, Widget child) {
+    return DatePickerTheme(data: data, child: child);
+  }
+
+  @override
+  bool updateShouldNotify(DatePickerTheme oldWidget) => data != oldWidget.data;
+}
+
+// Hand coded defaults based on Material Design 2.
+class _DatePickerDefaultsM2 extends DatePickerThemeData {
+  _DatePickerDefaultsM2(this.context)
+    : super(
+        elevation: 24.0,
+        shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(4.0))),
+        rangePickerElevation: 0.0,
+        rangePickerShape: const RoundedRectangleBorder(),
+      );
+
+  final BuildContext context;
+  late final ThemeData _theme = Theme.of(context);
+  late final ColorScheme _colors = _theme.colorScheme;
+  late final TextTheme _textTheme = _theme.textTheme;
+  late final bool _isDark = _colors.brightness == Brightness.dark;
+
+  @override
+  Color? get headerBackgroundColor => _isDark ? _colors.surface : _colors.primary;
+
+  @override
+  Color? get headerForegroundColor => _isDark ? _colors.onSurface : _colors.onPrimary;
+
+  @override
+  TextStyle? get headerHeadlineStyle => _textTheme.headlineSmall;
+
+  @override
+  TextStyle? get headerHelpStyle => _textTheme.labelSmall;
+
+  @override
+  TextStyle? get weekdayStyle => _textTheme.bodySmall?.apply(
+    color: _colors.onSurface.withOpacity(0.60),
+  );
+
+  @override
+  TextStyle? get dayStyle => _textTheme.bodySmall;
+
+  @override
+  MaterialStateProperty<Color?>? get dayForegroundColor =>
+    MaterialStateProperty.resolveWith((Set<MaterialState> states) {
+      if (states.contains(MaterialState.selected)) {
+        return _colors.onPrimary;
+      } else if (states.contains(MaterialState.disabled)) {
+        return _colors.onSurface.withOpacity(0.38);
+      }
+      return _colors.onSurface;
+    });
+
+  @override
+  MaterialStateProperty<Color?>? get dayBackgroundColor =>
+    MaterialStateProperty.resolveWith((Set<MaterialState> states) {
+      if (states.contains(MaterialState.selected)) {
+        return _colors.primary;
+      }
+      return null;
+    });
+
+  @override
+  MaterialStateProperty<Color?>? get dayOverlayColor =>
+    MaterialStateProperty.resolveWith((Set<MaterialState> states) {
+      if (states.contains(MaterialState.selected)) {
+        if (states.contains(MaterialState.hovered)) {
+          return _colors.onPrimary.withOpacity(0.08);
+        }
+        if (states.contains(MaterialState.focused)) {
+          return _colors.onPrimary.withOpacity(0.12);
+        }
+        if (states.contains(MaterialState.pressed)) {
+          return _colors.onPrimary.withOpacity(0.38);
+        }
+      } else {
+        if (states.contains(MaterialState.hovered)) {
+          return _colors.onSurfaceVariant.withOpacity(0.08);
+        }
+        if (states.contains(MaterialState.focused)) {
+          return _colors.onSurfaceVariant.withOpacity(0.12);
+        }
+        if (states.contains(MaterialState.pressed)) {
+          return _colors.onSurfaceVariant.withOpacity(0.12);
+        }
+      }
+      return null;
+    });
+
+  @override
+  MaterialStateProperty<Color?>? get todayForegroundColor =>
+    MaterialStateProperty.resolveWith((Set<MaterialState> states) {
+      if (states.contains(MaterialState.selected)) {
+        return _colors.onPrimary;
+      } else if (states.contains(MaterialState.disabled)) {
+        return _colors.onSurface.withOpacity(0.38);
+      }
+      return _colors.primary;
+    });
+
+  @override
+  MaterialStateProperty<Color?>? get todayBackgroundColor => dayBackgroundColor;
+
+  @override
+  BorderSide? get todayBorder => BorderSide(color: _colors.primary);
+
+  @override
+  TextStyle? get yearStyle => _textTheme.bodyLarge;
+
+  @override
+  Color? get rangePickerBackgroundColor => _colors.surface;
+
+  @override
+  Color? get rangePickerShadowColor => Colors.transparent;
+
+  @override
+  Color? get rangePickerSurfaceTintColor => Colors.transparent;
+
+  @override
+  Color? get rangePickerHeaderBackgroundColor => _isDark ? _colors.surface : _colors.primary;
+
+  @override
+  Color? get rangePickerHeaderForegroundColor => _isDark ? _colors.onSurface : _colors.onPrimary;
+
+  @override
+  TextStyle? get rangePickerHeaderHeadlineStyle => _textTheme.headlineSmall;
+
+  @override
+  TextStyle? get rangePickerHeaderHelpStyle => _textTheme.labelSmall;
+
+  @override
+  Color? get rangeSelectionBackgroundColor => _colors.primary.withOpacity(0.12);
+
+  @override
+  MaterialStateProperty<Color?>? get rangeSelectionOverlayColor =>
+    MaterialStateProperty.resolveWith((Set<MaterialState> states) {
+      if (states.contains(MaterialState.selected)) {
+        if (states.contains(MaterialState.hovered)) {
+          return _colors.onPrimary.withOpacity(0.08);
+        }
+        if (states.contains(MaterialState.focused)) {
+          return _colors.onPrimary.withOpacity(0.12);
+        }
+        if (states.contains(MaterialState.pressed)) {
+          return _colors.onPrimary.withOpacity(0.38);
+        }
+      } else {
+        if (states.contains(MaterialState.hovered)) {
+          return _colors.onSurfaceVariant.withOpacity(0.08);
+        }
+        if (states.contains(MaterialState.focused)) {
+          return _colors.onSurfaceVariant.withOpacity(0.12);
+        }
+        if (states.contains(MaterialState.pressed)) {
+          return _colors.onSurfaceVariant.withOpacity(0.12);
+        }
+      }
+      return null;
+    });
+}
+
+// BEGIN GENERATED TOKEN PROPERTIES - DatePicker
+
+// Do not edit by hand. The code between the "BEGIN GENERATED" and
+// "END GENERATED" comments are generated from data in the Material
+// Design token database by the script:
+//   dev/tools/gen_defaults/bin/gen_defaults.dart.
+
+// Token database version: v0_158
+
+class _DatePickerDefaultsM3 extends DatePickerThemeData {
+  _DatePickerDefaultsM3(this.context)
+    : super(
+        elevation: 6.0,
+        shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(28.0))),
+        rangePickerElevation: 0.0,
+        rangePickerShape: const RoundedRectangleBorder(),
+      );
+
+  final BuildContext context;
+  late final ThemeData _theme = Theme.of(context);
+  late final ColorScheme _colors = _theme.colorScheme;
+  late final TextTheme _textTheme = _theme.textTheme;
+
+  @override
+  Color? get backgroundColor => _colors.surface;
+
+  @override
+  Color? get shadowColor => Colors.transparent;
+
+  @override
+  Color? get surfaceTintColor => _colors.surfaceTint;
+
+  @override
+  Color? get headerBackgroundColor => Colors.transparent;
+
+  @override
+  Color? get headerForegroundColor => _colors.onSurfaceVariant;
+
+  @override
+  TextStyle? get headerHeadlineStyle => _textTheme.headlineLarge;
+
+  @override
+  TextStyle? get headerHelpStyle => _textTheme.labelLarge;
+
+  @override
+  TextStyle? get weekdayStyle => _textTheme.bodyLarge?.apply(
+    color: _colors.onSurface,
+  );
+
+  @override
+  TextStyle? get dayStyle => _textTheme.bodyLarge;
+
+  @override
+  MaterialStateProperty<Color?>? get dayForegroundColor =>
+    MaterialStateProperty.resolveWith((Set<MaterialState> states) {
+      if (states.contains(MaterialState.selected)) {
+        return _colors.onPrimary;
+      } else if (states.contains(MaterialState.disabled)) {
+        return _colors.onSurface.withOpacity(0.38);
+      }
+      return _colors.onSurface;
+    });
+
+  @override
+  MaterialStateProperty<Color?>? get dayBackgroundColor =>
+    MaterialStateProperty.resolveWith((Set<MaterialState> states) {
+      if (states.contains(MaterialState.selected)) {
+        return _colors.primary;
+      }
+      return null;
+    });
+
+  @override
+  MaterialStateProperty<Color?>? get dayOverlayColor =>
+    MaterialStateProperty.resolveWith((Set<MaterialState> states) {
+      if (states.contains(MaterialState.selected)) {
+        if (states.contains(MaterialState.hovered)) {
+          return _colors.onPrimary.withOpacity(0.08);
+        }
+        if (states.contains(MaterialState.focused)) {
+          return _colors.onPrimary.withOpacity(0.12);
+        }
+        if (states.contains(MaterialState.pressed)) {
+          return _colors.onPrimary.withOpacity(0.12);
+        }
+      } else {
+        if (states.contains(MaterialState.hovered)) {
+          return _colors.onSurfaceVariant.withOpacity(0.08);
+        }
+        if (states.contains(MaterialState.focused)) {
+          return _colors.onSurfaceVariant.withOpacity(0.12);
+        }
+        if (states.contains(MaterialState.pressed)) {
+          return _colors.onSurfaceVariant.withOpacity(0.12);
+        }
+      }
+      return null;
+    });
+
+  @override
+  MaterialStateProperty<Color?>? get todayForegroundColor =>
+    MaterialStateProperty.resolveWith((Set<MaterialState> states) {
+      if (states.contains(MaterialState.selected)) {
+        return _colors.onPrimary;
+      } else if (states.contains(MaterialState.disabled)) {
+        return _colors.primary.withOpacity(0.38);
+      }
+      return _colors.primary;
+    });
+
+  @override
+  MaterialStateProperty<Color?>? get todayBackgroundColor => dayBackgroundColor;
+
+  @override
+  BorderSide? get todayBorder => BorderSide(color: _colors.primary);
+
+  @override
+  TextStyle? get yearStyle => _textTheme.bodyLarge;
+
+  @override
+  MaterialStateProperty<Color?>? get yearForegroundColor =>
+    MaterialStateProperty.resolveWith((Set<MaterialState> states) {
+      if (states.contains(MaterialState.selected)) {
+        return _colors.onPrimary;
+      } else if (states.contains(MaterialState.disabled)) {
+        return _colors.onSurfaceVariant.withOpacity(0.38);
+      }
+      return _colors.onSurfaceVariant;
+    });
+
+  @override
+  MaterialStateProperty<Color?>? get yearBackgroundColor =>
+    MaterialStateProperty.resolveWith((Set<MaterialState> states) {
+      if (states.contains(MaterialState.selected)) {
+        return _colors.primary;
+      }
+      return null;
+    });
+
+  @override
+  MaterialStateProperty<Color?>? get yearOverlayColor =>
+    MaterialStateProperty.resolveWith((Set<MaterialState> states) {
+      if (states.contains(MaterialState.selected)) {
+        if (states.contains(MaterialState.hovered)) {
+          return _colors.onPrimary.withOpacity(0.08);
+        }
+        if (states.contains(MaterialState.focused)) {
+          return _colors.onPrimary.withOpacity(0.12);
+        }
+        if (states.contains(MaterialState.pressed)) {
+          return _colors.onPrimary.withOpacity(0.12);
+        }
+      } else {
+        if (states.contains(MaterialState.hovered)) {
+          return _colors.onSurfaceVariant.withOpacity(0.08);
+        }
+        if (states.contains(MaterialState.focused)) {
+          return _colors.onSurfaceVariant.withOpacity(0.12);
+        }
+        if (states.contains(MaterialState.pressed)) {
+          return _colors.onSurfaceVariant.withOpacity(0.12);
+        }
+      }
+      return null;
+    });
+
+    @override
+    Color? get rangePickerShadowColor => Colors.transparent;
+
+    @override
+    Color? get rangePickerSurfaceTintColor => Colors.transparent;
+
+    @override
+    Color? get rangeSelectionBackgroundColor => _colors.primaryContainer;
+
+  @override
+  MaterialStateProperty<Color?>? get rangeSelectionOverlayColor =>
+    MaterialStateProperty.resolveWith((Set<MaterialState> states) {
+      if (states.contains(MaterialState.hovered)) {
+        return null;
+      }
+      if (states.contains(MaterialState.focused)) {
+        return null;
+      }
+      if (states.contains(MaterialState.pressed)) {
+        return null;
+      }
+      return null;
+    });
+
+  @override
+  Color? get rangePickerHeaderBackgroundColor => Colors.transparent;
+
+  @override
+  Color? get rangePickerHeaderForegroundColor => _colors.onSurfaceVariant;
+
+  @override
+  TextStyle? get rangePickerHeaderHeadlineStyle => _textTheme.titleLarge;
+
+  @override
+  TextStyle? get rangePickerHeaderHelpStyle => _textTheme.titleSmall;
+
+
+}
+
+// END GENERATED TOKEN PROPERTIES - DatePicker
diff --git a/framework/lib/src/material/debug.dart b/framework/lib/src/material/debug.dart
index 260ab95..7f212ef 100644
--- a/framework/lib/src/material/debug.dart
+++ b/framework/lib/src/material/debug.dart
@@ -11,7 +11,8 @@
 // Examples can assume:
 // late BuildContext context;
 
-/// Asserts that the given context has a [Material] ancestor.
+/// Asserts that the given context has a [Material] ancestor within the closest
+/// [LookupBoundary].
 ///
 /// Used by many Material Design widgets to make sure that they are
 /// only used in contexts where they can print ink onto some material.
@@ -32,12 +33,17 @@
 /// Does nothing if asserts are disabled. Always returns true.
 bool debugCheckHasMaterial(BuildContext context) {
   assert(() {
-    if (context.widget is! Material && context.findAncestorWidgetOfExactType<Material>() == null) {
+    if (LookupBoundary.findAncestorWidgetOfExactType<Material>(context) == null) {
+      final bool hiddenByBoundary = LookupBoundary.debugIsHidingAncestorWidgetOfExactType<Material>(context);
       throw FlutterError.fromParts(<DiagnosticsNode>[
-        ErrorSummary('No Material widget found.'),
+        ErrorSummary('No Material widget found${hiddenByBoundary ? ' within the closest LookupBoundary' : ''}.'),
+        if (hiddenByBoundary)
+          ErrorDescription(
+            'There is an ancestor Material widget, but it is hidden by a LookupBoundary.'
+          ),
         ErrorDescription(
           '${context.widget.runtimeType} widgets require a Material '
-          'widget ancestor.\n'
+          'widget ancestor within the closest LookupBoundary.\n'
           'In Material Design, most widgets are conceptually "printed" on '
           "a sheet of material. In Flutter's material library, that "
           'material is represented by the Material widget. It is the '
diff --git a/framework/lib/src/material/desktop_text_selection.dart b/framework/lib/src/material/desktop_text_selection.dart
index 3b59c24..5de5c8e 100644
--- a/framework/lib/src/material/desktop_text_selection.dart
+++ b/framework/lib/src/material/desktop_text_selection.dart
@@ -177,12 +177,11 @@
       return const SizedBox.shrink();
     }
 
-    final MediaQueryData mediaQuery = MediaQuery.of(context);
-
+    final EdgeInsets mediaQueryPadding = MediaQuery.paddingOf(context);
     final Offset midpointAnchor = Offset(
       clampDouble(widget.selectionMidpoint.dx - widget.globalEditableRegion.left,
-        mediaQuery.padding.left,
-        mediaQuery.size.width - mediaQuery.padding.right,
+        mediaQueryPadding.left,
+        MediaQuery.sizeOf(context).width - mediaQueryPadding.right,
       ),
       widget.selectionMidpoint.dy - widget.globalEditableRegion.top,
     );
diff --git a/framework/lib/src/material/desktop_text_selection_toolbar.dart b/framework/lib/src/material/desktop_text_selection_toolbar.dart
index 8bdf670..adb41ea 100644
--- a/framework/lib/src/material/desktop_text_selection_toolbar.dart
+++ b/framework/lib/src/material/desktop_text_selection_toolbar.dart
@@ -64,9 +64,8 @@
   @override
   Widget build(BuildContext context) {
     assert(debugCheckHasMediaQuery(context));
-    final MediaQueryData mediaQuery = MediaQuery.of(context);
 
-    final double paddingAbove = mediaQuery.padding.top + _kToolbarScreenPadding;
+    final double paddingAbove = MediaQuery.paddingOf(context).top + _kToolbarScreenPadding;
     final Offset localAdjustment = Offset(_kToolbarScreenPadding, paddingAbove);
 
     return Padding(
diff --git a/framework/lib/src/material/dialog.dart b/framework/lib/src/material/dialog.dart
index ce4a864..414e8d7 100644
--- a/framework/lib/src/material/dialog.dart
+++ b/framework/lib/src/material/dialog.dart
@@ -60,8 +60,7 @@
     this.shape,
     this.alignment,
     this.child,
-  }) : assert(clipBehavior != null),
-       assert(elevation == null || elevation >= 0.0),
+  }) : assert(elevation == null || elevation >= 0.0),
        _fullscreen = false;
 
   /// Creates a fullscreen dialog.
@@ -217,7 +216,7 @@
   Widget build(BuildContext context) {
     final ThemeData theme = Theme.of(context);
     final DialogTheme dialogTheme = DialogTheme.of(context);
-    final EdgeInsets effectivePadding = MediaQuery.of(context).viewInsets + (insetPadding ?? EdgeInsets.zero);
+    final EdgeInsets effectivePadding = MediaQuery.viewInsetsOf(context) + (insetPadding ?? EdgeInsets.zero);
     final DialogTheme defaults = theme.useMaterial3
       ? (_fullscreen ? _DialogFullscreenDefaultsM3(context) : _DialogDefaultsM3(context))
       : _DialogDefaultsM2(context);
@@ -303,9 +302,9 @@
 ///     builder: (BuildContext context) {
 ///       return AlertDialog(
 ///         title: const Text('AlertDialog Title'),
-///         content: SingleChildScrollView(
+///         content: const SingleChildScrollView(
 ///           child: ListBody(
-///             children: const <Widget>[
+///             children: <Widget>[
 ///               Text('This is a demo alert dialog.'),
 ///               Text('Would you like to approve of this message?'),
 ///             ],
@@ -385,7 +384,7 @@
     this.shape,
     this.alignment,
     this.scrollable = false,
-  }) : assert(clipBehavior != null);
+  });
 
   /// An optional icon to display at the top of the dialog.
   ///
@@ -642,7 +641,7 @@
 
     // The paddingScaleFactor is used to adjust the padding of Dialog's
     // children.
-    final double paddingScaleFactor = _paddingScaleFactor(MediaQuery.of(context).textScaleFactor);
+    final double paddingScaleFactor = _paddingScaleFactor(MediaQuery.textScaleFactorOf(context));
     final TextDirection? textDirection = Directionality.maybeOf(context);
 
     Widget? iconWidget;
@@ -973,8 +972,7 @@
     this.clipBehavior = Clip.none,
     this.shape,
     this.alignment,
-  }) : assert(titlePadding != null),
-       assert(contentPadding != null);
+  });
 
   /// The (optional) title of the dialog is displayed in a large font at the top
   /// of the dialog.
@@ -1075,7 +1073,7 @@
 
     // The paddingScaleFactor is used to adjust the padding of Dialog
     // children.
-    final double paddingScaleFactor = _paddingScaleFactor(MediaQuery.of(context).textScaleFactor);
+    final double paddingScaleFactor = _paddingScaleFactor(MediaQuery.textScaleFactorOf(context));
     final TextDirection? textDirection = Directionality.maybeOf(context);
 
     Widget? titleWidget;
@@ -1238,8 +1236,11 @@
 ///
 /// {@macro flutter.widgets.RestorationManager}
 ///
-/// The `traversalEdgeBehavior` argument controls the transfer of focus
-/// beyond the first and the last items of the dialog route.
+/// If not null, `traversalEdgeBehavior` argument specifies the transfer of
+/// focus beyond the first and the last items of the dialog route. By default,
+/// uses [TraversalEdgeBehavior.closedLoop], because it's typical for dialogs
+/// to allow users to cycle through widgets inside it without leaving the
+/// dialog.
 ///
 /// ** See code in examples/api/lib/material/dialog/show_dialog.2.dart **
 /// {@end-tool}
@@ -1268,10 +1269,6 @@
   Offset? anchorPoint,
   TraversalEdgeBehavior? traversalEdgeBehavior,
 }) {
-  assert(builder != null);
-  assert(barrierDismissible != null);
-  assert(useSafeArea != null);
-  assert(useRootNavigator != null);
   assert(_debugIsActive(context));
   assert(debugCheckHasMaterialLocalizations(context));
 
@@ -1293,7 +1290,7 @@
     settings: routeSettings,
     themes: themes,
     anchorPoint: anchorPoint,
-    traversalEdgeBehavior: traversalEdgeBehavior,
+    traversalEdgeBehavior: traversalEdgeBehavior ?? TraversalEdgeBehavior.closedLoop,
   ));
 }
 
@@ -1373,8 +1370,7 @@
     super.settings,
     super.anchorPoint,
     super.traversalEdgeBehavior,
-  }) : assert(barrierDismissible != null),
-       super(
+  }) : super(
          pageBuilder: (BuildContext buildContext, Animation<double> animation, Animation<double> secondaryAnimation) {
            final Widget pageChild = Builder(builder: builder);
            Widget dialog = themes?.wrap(pageChild) ?? pageChild;
@@ -1437,7 +1433,7 @@
 // Design token database by the script:
 //   dev/tools/gen_defaults/bin/gen_defaults.dart.
 
-// Token database version: v0_143
+// Token database version: v0_158
 
 class _DialogDefaultsM3 extends DialogTheme {
   _DialogDefaultsM3(this.context)
@@ -1482,7 +1478,7 @@
 // Design token database by the script:
 //   dev/tools/gen_defaults/bin/gen_defaults.dart.
 
-// Token database version: v0_143
+// Token database version: v0_158
 
 class _DialogFullscreenDefaultsM3 extends DialogTheme {
   const _DialogFullscreenDefaultsM3(this.context);
diff --git a/framework/lib/src/material/dialog_theme.dart b/framework/lib/src/material/dialog_theme.dart
index 5896d87..c39da59 100644
--- a/framework/lib/src/material/dialog_theme.dart
+++ b/framework/lib/src/material/dialog_theme.dart
@@ -111,7 +111,6 @@
   ///
   /// {@macro dart.ui.shadow.lerp}
   static DialogTheme lerp(DialogTheme? a, DialogTheme? b, double t) {
-    assert(t != null);
     return DialogTheme(
       backgroundColor: Color.lerp(a?.backgroundColor, b?.backgroundColor, t),
       elevation: lerpDouble(a?.elevation, b?.elevation, t),
diff --git a/framework/lib/src/material/divider.dart b/framework/lib/src/material/divider.dart
index d40d117..5ddf599 100644
--- a/framework/lib/src/material/divider.dart
+++ b/framework/lib/src/material/divider.dart
@@ -328,7 +328,7 @@
 // Design token database by the script:
 //   dev/tools/gen_defaults/bin/gen_defaults.dart.
 
-// Token database version: v0_143
+// Token database version: v0_158
 
 class _DividerDefaultsM3 extends DividerThemeData {
   const _DividerDefaultsM3(this.context) : super(
diff --git a/framework/lib/src/material/divider_theme.dart b/framework/lib/src/material/divider_theme.dart
index c7926ed..79d631d 100644
--- a/framework/lib/src/material/divider_theme.dart
+++ b/framework/lib/src/material/divider_theme.dart
@@ -87,7 +87,6 @@
   ///
   /// {@macro dart.ui.shadow.lerp}
   static DividerThemeData lerp(DividerThemeData? a, DividerThemeData? b, double t) {
-    assert(t != null);
     return DividerThemeData(
       color: Color.lerp(a?.color, b?.color, t),
       space: lerpDouble(a?.space, b?.space, t),
@@ -144,7 +143,7 @@
     super.key,
     required this.data,
     required super.child,
-  }) : assert(data != null);
+  });
 
   /// The properties for descendant [Divider]s, [VerticalDivider]s, dividers
   /// between [ListTile]s, and dividers between rows in [DataTable]s.
diff --git a/framework/lib/src/material/drawer.dart b/framework/lib/src/material/drawer.dart
index c0943f1..8bca807 100644
--- a/framework/lib/src/material/drawer.dart
+++ b/framework/lib/src/material/drawer.dart
@@ -115,7 +115,7 @@
 /// {@end-tool}
 ///
 /// An open drawer may be closed with a swipe to close gesture, pressing the
-/// the escape key, by tapping the scrim, or by calling pop route function such as
+/// escape key, by tapping the scrim, or by calling pop route function such as
 /// [Navigator.pop]. For example a drawer item might close the drawer when tapped:
 ///
 /// ```dart
@@ -325,10 +325,7 @@
     this.scrimColor,
     this.edgeDragWidth,
     this.enableOpenDragGesture = true,
-  }) : assert(child != null),
-       assert(dragStartBehavior != null),
-       assert(alignment != null),
-       super(key: key);
+  }) : super(key: key);
 
   /// The widget below this widget in the tree.
   ///
@@ -382,11 +379,11 @@
   /// drawer.
   ///
   /// By default, the value used is 20.0 added to the padding edge of
-  /// `MediaQuery.of(context).padding` that corresponds to [alignment].
+  /// `MediaQuery.paddingOf(context)` that corresponds to [alignment].
   /// This ensures that the drag area for notched devices is not obscured. For
   /// example, if [alignment] is set to [DrawerAlignment.start] and
   /// `TextDirection.of(context)` is set to [TextDirection.ltr],
-  /// 20.0 will be added to `MediaQuery.of(context).padding.left`.
+  /// 20.0 will be added to `MediaQuery.paddingOf(context).left`.
   final double? edgeDragWidth;
 
   /// Whether or not the drawer is opened or closed.
@@ -659,7 +656,6 @@
   }
 
   AlignmentDirectional get _drawerOuterAlignment {
-    assert(widget.alignment != null);
     switch (widget.alignment) {
       case DrawerAlignment.start:
         return AlignmentDirectional.centerStart;
@@ -669,7 +665,6 @@
   }
 
   AlignmentDirectional get _drawerInnerAlignment {
-    assert(widget.alignment != null);
     switch (widget.alignment) {
       case DrawerAlignment.start:
         return AlignmentDirectional.centerEnd;
@@ -680,7 +675,6 @@
 
   Widget _buildDrawer(BuildContext context) {
     final bool drawerIsStart = widget.alignment == DrawerAlignment.start;
-    final EdgeInsets padding = MediaQuery.of(context).padding;
     final TextDirection textDirection = Directionality.of(context);
     final bool isDesktop;
     switch (Theme.of(context).platform) {
@@ -698,6 +692,7 @@
 
     double? dragAreaWidth = widget.edgeDragWidth;
     if (widget.edgeDragWidth == null) {
+      final EdgeInsets padding = MediaQuery.paddingOf(context);
       switch (textDirection) {
         case TextDirection.ltr:
           dragAreaWidth = _kEdgeDragWidth +
@@ -741,7 +736,6 @@
           platformHasBackButton = false;
           break;
       }
-      assert(platformHasBackButton != null);
 
       final Widget child = _DrawerControllerScope(
         controller: widget,
@@ -827,13 +821,14 @@
 // Design token database by the script:
 //   dev/tools/gen_defaults/bin/gen_defaults.dart.
 
-// Token database version: v0_143
+// Token database version: v0_158
 
 class _DrawerDefaultsM3 extends DrawerThemeData {
-  const _DrawerDefaultsM3(this.context)
+  _DrawerDefaultsM3(this.context)
       : super(elevation: 1.0);
 
   final BuildContext context;
+  late final TextDirection direction = Directionality.of(context);
 
   @override
   Color? get backgroundColor => Theme.of(context).colorScheme.surface;
@@ -844,18 +839,22 @@
   @override
   Color? get shadowColor => Colors.transparent;
 
-  // This don't appear to be tokens for this value, but it is
-  // shown in the spec.
+  // There isn't currently a token for this value, but it is shown in the spec,
+  // so hard coding here for now.
   @override
-  ShapeBorder? get shape => const RoundedRectangleBorder(
-    borderRadius: BorderRadius.horizontal(right: Radius.circular(16.0)),
+  ShapeBorder? get shape => RoundedRectangleBorder(
+    borderRadius: const BorderRadiusDirectional.horizontal(
+      end: Radius.circular(16.0),
+    ).resolve(direction),
   );
 
-  // This don't appear to be tokens for this value, but it is
-  // shown in the spec.
+  // There isn't currently a token for this value, but it is shown in the spec,
+  // so hard coding here for now.
   @override
-  ShapeBorder? get endShape => const RoundedRectangleBorder(
-    borderRadius: BorderRadius.horizontal(left: Radius.circular(16.0)),
+  ShapeBorder? get endShape => RoundedRectangleBorder(
+    borderRadius: const BorderRadiusDirectional.horizontal(
+      start: Radius.circular(16.0),
+    ).resolve(direction),
   );
 }
 
diff --git a/framework/lib/src/material/drawer_header.dart b/framework/lib/src/material/drawer_header.dart
index 639a36f..287ae5e 100644
--- a/framework/lib/src/material/drawer_header.dart
+++ b/framework/lib/src/material/drawer_header.dart
@@ -76,7 +76,7 @@
     assert(debugCheckHasMaterial(context));
     assert(debugCheckHasMediaQuery(context));
     final ThemeData theme = Theme.of(context);
-    final double statusBarHeight = MediaQuery.of(context).padding.top;
+    final double statusBarHeight = MediaQuery.paddingOf(context).top;
     return Container(
       height: statusBarHeight + _kDrawerHeaderHeight,
       margin: margin,
diff --git a/framework/lib/src/material/drawer_theme.dart b/framework/lib/src/material/drawer_theme.dart
index 26e2048..6986893 100644
--- a/framework/lib/src/material/drawer_theme.dart
+++ b/framework/lib/src/material/drawer_theme.dart
@@ -99,7 +99,6 @@
   ///
   /// {@macro dart.ui.shadow.lerp}
   static DrawerThemeData? lerp(DrawerThemeData? a, DrawerThemeData? b, double t) {
-    assert(t != null);
     if (a == null && b == null) {
       return null;
     }
@@ -174,7 +173,7 @@
     super.key,
     required this.data,
     required super.child,
-  }) : assert(data != null);
+  });
 
   /// Specifies the background color, scrim color, elevation, and shape for
   /// descendant [Drawer] widgets.
diff --git a/framework/lib/src/material/dropdown.dart b/framework/lib/src/material/dropdown.dart
index f357fc9..4286f51 100644
--- a/framework/lib/src/material/dropdown.dart
+++ b/framework/lib/src/material/dropdown.dart
@@ -434,8 +434,7 @@
     this.menuMaxHeight,
     required this.enableFeedback,
     this.borderRadius,
-  }) : assert(style != null),
-       itemHeights = List<double>.filled(items.length, itemHeight ?? kMinInteractiveDimension);
+  }) : itemHeights = List<double>.filled(items.length, itemHeight ?? kMinInteractiveDimension);
 
   final List<_MenuItem<T>> items;
   final EdgeInsetsGeometry padding;
@@ -664,7 +663,7 @@
     super.key,
     required this.onLayout,
     required this.item,
-  }) : assert(onLayout != null), super(child: item);
+  }) : super(child: item);
 
   final ValueChanged<Size> onLayout;
   final DropdownMenuItem<T>? item;
@@ -681,7 +680,7 @@
 }
 
 class _RenderMenuItem extends RenderProxyBox {
-  _RenderMenuItem(this.onLayout, [RenderBox? child]) : assert(onLayout != null), super(child);
+  _RenderMenuItem(this.onLayout, [RenderBox? child]) : super(child);
 
   ValueChanged<Size> onLayout;
 
@@ -703,7 +702,7 @@
     super.key,
     this.alignment = AlignmentDirectional.centerStart,
     required this.child,
-  }) : assert(child != null);
+  });
 
   /// The widget below this widget in the tree.
   ///
@@ -747,7 +746,7 @@
     this.enabled = true,
     super.alignment,
     required super.child,
-  }) : assert(child != null);
+  });
 
   /// Called when the dropdown menu item is tapped.
   final VoidCallback? onTap;
@@ -775,7 +774,7 @@
   const DropdownButtonHideUnderline({
     super.key,
     required super.child,
-  }) : assert(child != null);
+  });
 
   /// Returns whether the underline of [DropdownButton] widgets should
   /// be hidden.
@@ -898,11 +897,6 @@
                 'Either zero or 2 or more [DropdownMenuItem]s were detected '
                 'with the same value',
               ),
-       assert(elevation != null),
-       assert(iconSize != null),
-       assert(isDense != null),
-       assert(isExpanded != null),
-       assert(autofocus != null),
        assert(itemHeight == null || itemHeight >=  kMinInteractiveDimension),
        _inputDecoration = null,
        _isEmpty = false,
@@ -947,14 +941,7 @@
                 'Either zero or 2 or more [DropdownMenuItem]s were detected '
                 'with the same value',
               ),
-       assert(elevation != null),
-       assert(iconSize != null),
-       assert(isDense != null),
-       assert(isExpanded != null),
-       assert(autofocus != null),
        assert(itemHeight == null || itemHeight >=  kMinInteractiveDimension),
-       assert(isEmpty != null),
-       assert(isFocused != null),
        _inputDecoration = inputDecoration,
        _isEmpty = isEmpty,
        _isFocused = isFocused;
@@ -1333,7 +1320,7 @@
   // Similarly, we don't reduce the height of the button so much that its icon
   // would be clipped.
   double get _denseButtonHeight {
-    final double textScaleFactor = MediaQuery.of(context).textScaleFactor;
+    final double textScaleFactor = MediaQuery.textScaleFactorOf(context);
     final double fontSize = _textStyle!.fontSize ?? Theme.of(context).textTheme.titleMedium!.fontSize!;
     final double scaledFontSize = textScaleFactor * fontSize;
     return math.max(scaledFontSize, math.max(widget.iconSize, _kDenseButtonHeight));
@@ -1369,11 +1356,11 @@
   bool get _enabled => widget.items != null && widget.items!.isNotEmpty && widget.onChanged != null;
 
   Orientation _getOrientation(BuildContext context) {
-    Orientation? result = MediaQuery.maybeOf(context)?.orientation;
+    Orientation? result = MediaQuery.maybeOrientationOf(context);
     if (result == null) {
-      // If there's no MediaQuery, then use the window aspect to determine
+      // If there's no MediaQuery, then use the view aspect to determine
       // orientation.
-      final Size size = WidgetsBinding.instance.window.physicalSize;
+      final Size size = View.of(context).physicalSize;
       result = size.width > size.height ? Orientation.landscape : Orientation.portrait;
     }
     return result;
@@ -1530,7 +1517,7 @@
 /// This is a convenience widget that wraps a [DropdownButton] widget in a
 /// [FormField].
 ///
-/// A [Form] ancestor is not required. The [Form] simply makes it easier to
+/// A [Form] ancestor is not required. The [Form] allows one to
 /// save, reset, or validate multiple fields at once. To use without a [Form],
 /// pass a [GlobalKey] to the constructor and use [GlobalKey.currentState] to
 /// save or reset the form field.
@@ -1590,12 +1577,7 @@
                 'Either zero or 2 or more [DropdownMenuItem]s were detected '
                 'with the same value',
               ),
-       assert(elevation != null),
-       assert(iconSize != null),
-       assert(isDense != null),
-       assert(isExpanded != null),
        assert(itemHeight == null || itemHeight >= kMinInteractiveDimension),
-       assert(autofocus != null),
        decoration = decoration ?? InputDecoration(focusColor: focusColor),
        super(
          initialValue: value,
diff --git a/framework/lib/src/material/dropdown_menu.dart b/framework/lib/src/material/dropdown_menu.dart
index c301b79..a4d5ab9 100644
--- a/framework/lib/src/material/dropdown_menu.dart
+++ b/framework/lib/src/material/dropdown_menu.dart
@@ -135,6 +135,7 @@
     this.controller,
     this.initialSelection,
     this.onSelected,
+    this.requestFocusOnTap,
     required this.dropdownMenuEntries,
   });
 
@@ -228,6 +229,19 @@
   /// Defaults to null. If null, only the text field is updated.
   final ValueChanged<T?>? onSelected;
 
+  /// Determine if the dropdown button requests focus and the on-screen virtual
+  /// keyboard is shown in response to a touch event.
+  ///
+  /// By default, on mobile platforms, tapping on the text field and opening
+  /// the menu will not cause a focus request and the virtual keyboard will not
+  /// appear. The default behavior for desktop platforms is for the dropdown to
+  /// take the focus.
+  ///
+  /// Defaults to null. Setting this field to true or false, rather than allowing
+  /// the implementation to choose based on the platform, can be useful for
+  /// applications that want to override the default behavior.
+  final bool? requestFocusOnTap;
+
   /// Descriptions of the menu items in the [DropdownMenu].
   ///
   /// This is a required parameter. It is recommended that at least one [DropdownMenuEntry]
@@ -242,7 +256,6 @@
 class _DropdownMenuState<T> extends State<DropdownMenu<T>> {
   final GlobalKey _anchorKey = GlobalKey();
   final GlobalKey _leadingKey = GlobalKey();
-  final FocusNode _textFocusNode = FocusNode();
   final MenuController _controller = MenuController();
   late final TextEditingController _textEditingController;
   late bool _enableFilter;
@@ -288,6 +301,23 @@
     }
   }
 
+  bool canRequestFocus() {
+    if (widget.requestFocusOnTap != null) {
+      return widget.requestFocusOnTap!;
+    }
+
+    switch (Theme.of(context).platform) {
+      case TargetPlatform.iOS:
+      case TargetPlatform.android:
+      case TargetPlatform.fuchsia:
+        return false;
+      case TargetPlatform.macOS:
+      case TargetPlatform.linux:
+      case TargetPlatform.windows:
+        return true;
+    }
+  }
+
   void refreshLeadingPadding() {
     WidgetsBinding.instance.addPostFrameCallback((_) {
       setState(() {
@@ -428,7 +458,6 @@
 
   @override
   void dispose() {
-    _textEditingController.dispose();
     super.dispose();
   }
 
@@ -489,13 +518,12 @@
           builder: (BuildContext context, MenuController controller, Widget? child) {
             assert(_initialMenu != null);
             final Widget trailingButton = Padding(
-              padding: const EdgeInsets.symmetric(horizontal: 4.0),
+              padding: const EdgeInsets.all(4.0),
               child: IconButton(
                 isSelected: controller.isOpen,
                 icon: widget.trailingIcon ?? const Icon(Icons.arrow_drop_down),
                 selectedIcon: widget.selectedTrailingIcon ?? const Icon(Icons.arrow_drop_up),
                 onPressed: () {
-                  _textFocusNode.requestFocus();
                   handlePressed(controller);
                 },
               ),
@@ -511,7 +539,9 @@
               width: widget.width,
               children: <Widget>[
                 TextField(
-                  focusNode: _textFocusNode,
+                  canRequestFocus: canRequestFocus(),
+                  enableInteractiveSelection: canRequestFocus(),
+                  textAlignVertical: TextAlignVertical.center,
                   style: effectiveTextStyle,
                   controller: _textEditingController,
                   onEditingComplete: () {
@@ -575,7 +605,7 @@
 }
 
 class _DropdownMenuBody extends MultiChildRenderObjectWidget {
-  _DropdownMenuBody({
+  const _DropdownMenuBody({
     super.key,
     super.children,
     this.width,
diff --git a/framework/lib/src/material/dropdown_menu_theme.dart b/framework/lib/src/material/dropdown_menu_theme.dart
index 3027be3..7d891c8 100644
--- a/framework/lib/src/material/dropdown_menu_theme.dart
+++ b/framework/lib/src/material/dropdown_menu_theme.dart
@@ -112,7 +112,7 @@
     super.key,
     required this.data,
     required super.child,
-  }) : assert(data != null);
+  });
 
   /// Specifies the visual properties used by descendant [DropdownMenu]
   /// widgets.
@@ -162,7 +162,6 @@
   ///  * [of], which will return [ThemeData.dropdownMenuTheme] if it doesn't
   ///    find a [DropdownMenuTheme] ancestor, instead of returning null.
   static DropdownMenuThemeData? maybeOf(BuildContext context) {
-    assert(context != null);
     return context.dependOnInheritedWidgetOfExactType<DropdownMenuTheme>()?.data;
   }
 
diff --git a/framework/lib/src/material/elevated_button.dart b/framework/lib/src/material/elevated_button.dart
index 4fdd820..2ba8c92 100644
--- a/framework/lib/src/material/elevated_button.dart
+++ b/framework/lib/src/material/elevated_button.dart
@@ -252,7 +252,7 @@
   /// each state, and "others" means all other states.
   ///
   /// The `textScaleFactor` is the value of
-  /// `MediaQuery.of(context).textScaleFactor` and the names of the
+  /// `MediaQuery.textScaleFactorOf(context)` and the names of the
   /// EdgeInsets constructors and `EdgeInsetsGeometry.lerp` have been
   /// abbreviated for readability.
   ///
@@ -394,7 +394,7 @@
     const EdgeInsets.symmetric(horizontal: 16),
     const EdgeInsets.symmetric(horizontal: 8),
     const EdgeInsets.symmetric(horizontal: 4),
-    MediaQuery.maybeOf(context)?.textScaleFactor ?? 1,
+    MediaQuery.textScaleFactorOf(context),
   );
 }
 
@@ -486,9 +486,7 @@
     super.statesController,
     required Widget icon,
     required Widget label,
-  }) : assert(icon != null),
-       assert(label != null),
-       super(
+  }) : super(
          autofocus: autofocus ?? false,
          clipBehavior: clipBehavior ?? Clip.none,
          child: _ElevatedButtonWithIconChild(icon: icon, label: label),
@@ -500,7 +498,7 @@
       const EdgeInsetsDirectional.fromSTEB(12, 0, 16, 0),
       const EdgeInsets.symmetric(horizontal: 8),
       const EdgeInsetsDirectional.fromSTEB(8, 0, 4, 0),
-      MediaQuery.maybeOf(context)?.textScaleFactor ?? 1,
+      MediaQuery.textScaleFactorOf(context),
     );
     return super.defaultStyleOf(context).copyWith(
       padding: MaterialStatePropertyAll<EdgeInsetsGeometry>(scaledPadding),
@@ -516,7 +514,7 @@
 
   @override
   Widget build(BuildContext context) {
-    final double scale = MediaQuery.maybeOf(context)?.textScaleFactor ?? 1;
+    final double scale = MediaQuery.textScaleFactorOf(context);
     final double gap = scale <= 1 ? 8 : lerpDouble(8, 4, math.min(scale - 1, 1))!;
     return Row(
       mainAxisSize: MainAxisSize.min,
@@ -532,7 +530,7 @@
 // Design token database by the script:
 //   dev/tools/gen_defaults/bin/gen_defaults.dart.
 
-// Token database version: v0_143
+// Token database version: v0_158
 
 class _ElevatedButtonDefaultsM3 extends ButtonStyle {
   _ElevatedButtonDefaultsM3(this.context)
diff --git a/framework/lib/src/material/elevated_button_theme.dart b/framework/lib/src/material/elevated_button_theme.dart
index 629ae7e..bc59beb 100644
--- a/framework/lib/src/material/elevated_button_theme.dart
+++ b/framework/lib/src/material/elevated_button_theme.dart
@@ -49,7 +49,6 @@
 
   /// Linearly interpolate between two elevated button themes.
   static ElevatedButtonThemeData? lerp(ElevatedButtonThemeData? a, ElevatedButtonThemeData? b, double t) {
-    assert (t != null);
     if (a == null && b == null) {
       return null;
     }
@@ -98,7 +97,7 @@
     super.key,
     required this.data,
     required super.child,
-  }) : assert(data != null);
+  });
 
   /// The configuration of this theme.
   final ElevatedButtonThemeData data;
diff --git a/framework/lib/src/material/elevation_overlay.dart b/framework/lib/src/material/elevation_overlay.dart
index f989e60..21312dd 100644
--- a/framework/lib/src/material/elevation_overlay.dart
+++ b/framework/lib/src/material/elevation_overlay.dart
@@ -160,7 +160,7 @@
 // Design token database by the script:
 //   dev/tools/gen_defaults/bin/gen_defaults.dart.
 
-// Token database version: v0_143
+// Token database version: v0_158
 
 // Surface tint opacities based on elevations according to the
 // Material Design 3 specification:
diff --git a/framework/lib/src/material/expand_icon.dart b/framework/lib/src/material/expand_icon.dart
index 48b3ee5..3581d75 100644
--- a/framework/lib/src/material/expand_icon.dart
+++ b/framework/lib/src/material/expand_icon.dart
@@ -39,9 +39,7 @@
     this.color,
     this.disabledColor,
     this.expandedColor,
-  }) : assert(isExpanded != null),
-       assert(size != null),
-       assert(padding != null);
+  });
 
   /// Whether the icon is in an expanded state.
   ///
@@ -66,7 +64,7 @@
   /// This property must not be null. It defaults to 8.0 padding on all sides.
   final EdgeInsetsGeometry padding;
 
-
+  /// {@template flutter.material.ExpandIcon.color}
   /// The color of the icon.
   ///
   /// Defaults to [Colors.black54] when the theme's
@@ -74,6 +72,7 @@
   /// [Colors.white60] when it is [Brightness.dark]. This adheres to the
   /// Material Design specifications for [icons](https://material.io/design/iconography/system-icons.html#color)
   /// and for [dark theme](https://material.io/design/color/dark-theme.html#ui-application)
+  /// {@endtemplate}
   final Color? color;
 
   /// The color of the icon when it is disabled,
diff --git a/framework/lib/src/material/expansion_panel.dart b/framework/lib/src/material/expansion_panel.dart
index 051f395..f318c6b 100644
--- a/framework/lib/src/material/expansion_panel.dart
+++ b/framework/lib/src/material/expansion_panel.dart
@@ -16,6 +16,7 @@
 const EdgeInsets _kPanelHeaderExpandedDefaultPadding = EdgeInsets.symmetric(
     vertical: 64.0 - _kPanelHeaderCollapsedHeight,
 );
+const EdgeInsets _kExpandIconPadding = EdgeInsets.all(12.0);
 
 class _SaltedKey<S, V> extends LocalKey {
   const _SaltedKey(this.salt, this.value);
@@ -81,10 +82,7 @@
     this.isExpanded = false,
     this.canTapOnHeader = false,
     this.backgroundColor,
-  }) : assert(headerBuilder != null),
-       assert(body != null),
-       assert(isExpanded != null),
-       assert(canTapOnHeader != null);
+  });
 
   /// The widget builder that builds the expansion panels' header.
   final ExpansionPanelHeaderBuilder headerBuilder;
@@ -131,7 +129,7 @@
     required super.body,
     super.canTapOnHeader,
     super.backgroundColor,
-  }) : assert(value != null);
+  });
 
   /// The value that uniquely identifies a radio panel so that the currently
   /// selected radio panel can be identified.
@@ -168,9 +166,8 @@
     this.expandedHeaderPadding = _kPanelHeaderExpandedDefaultPadding,
     this.dividerColor,
     this.elevation = 2,
-  }) : assert(children != null),
-       assert(animationDuration != null),
-       _allowOnlyOnePanelOpen = false,
+    this.expandIconColor,
+  }) : _allowOnlyOnePanelOpen = false,
        initialOpenPanelValue = null;
 
   /// Creates a radio expansion panel list widget.
@@ -195,9 +192,8 @@
     this.expandedHeaderPadding = _kPanelHeaderExpandedDefaultPadding,
     this.dividerColor,
     this.elevation = 2,
-  }) : assert(children != null),
-       assert(animationDuration != null),
-       _allowOnlyOnePanelOpen = true;
+    this.expandIconColor,
+  }) : _allowOnlyOnePanelOpen = true;
 
   /// The children of the expansion panel list. They are laid out in a similar
   /// fashion to [ListBody].
@@ -207,14 +203,14 @@
   /// is pressed. The arguments passed to the callback are the index of the
   /// pressed panel and whether the panel is currently expanded or not.
   ///
-  /// If ExpansionPanelList.radio is used, the callback may be called a
+  /// If [ExpansionPanelList.radio] is used, the callback may be called a
   /// second time if a different panel was previously open. The arguments
   /// passed to the second callback are the index of the panel that will close
   /// and false, marking that it will be closed.
   ///
-  /// For ExpansionPanelList, the callback needs to setState when it's notified
+  /// For [ExpansionPanelList], the callback needs to setState when it's notified
   /// about the closing/opening panel. On the other hand, the callback for
-  /// ExpansionPanelList.radio is simply meant to inform the parent widget of
+  /// [ExpansionPanelList.radio] is intended to inform the parent widget of
   /// changes, as the radio panels' open/close states are managed internally.
   ///
   /// This callback is useful in order to keep track of the expanded/collapsed
@@ -249,6 +245,9 @@
   /// By default, the value of elevation is 2.
   final double elevation;
 
+  /// {@macro flutter.material.ExpandIcon.color}
+  final Color? expandIconColor;
+
   @override
   State<StatefulWidget> createState() => _ExpansionPanelListState();
 }
@@ -356,8 +355,9 @@
       Widget expandIconContainer = Container(
         margin: const EdgeInsetsDirectional.only(end: 8.0),
         child: ExpandIcon(
+          color: widget.expandIconColor,
           isExpanded: _isChildExpanded(index),
-          padding: const EdgeInsets.all(16.0),
+          padding: _kExpandIconPadding,
           onPressed: !child.canTapOnHeader
               ? (bool isExpanded) => _handlePressed(isExpanded, index)
               : null,
diff --git a/framework/lib/src/material/expansion_tile.dart b/framework/lib/src/material/expansion_tile.dart
index fefc032..855b9ce 100644
--- a/framework/lib/src/material/expansion_tile.dart
+++ b/framework/lib/src/material/expansion_tile.dart
@@ -34,7 +34,8 @@
 /// to the [leading] and [trailing] properties of [ExpansionTile].
 ///
 /// {@tool dartpad}
-/// This example demonstrates different configurations of ExpansionTile.
+/// This example demonstrates how the [ExpansionTile] icon's location and appearance
+/// can be customized.
 ///
 /// ** See code in examples/api/lib/material/expansion_tile/expansion_tile.0.dart **
 /// {@end-tool}
@@ -73,9 +74,7 @@
     this.collapsedShape,
     this.clipBehavior,
     this.controlAffinity,
-  }) : assert(initiallyExpanded != null),
-       assert(maintainState != null),
-       assert(
+  }) : assert(
        expandedCrossAxisAlignment != CrossAxisAlignment.baseline,
        'CrossAxisAlignment.baseline is not supported since the expanded children '
            'are aligned in a column, not a row. Try to use another constant.',
@@ -218,7 +217,7 @@
   /// Used to override to the [ListTileThemeData.iconColor].
   ///
   /// If this property is null then [ExpansionTileThemeData.iconColor] is used. If that
-  /// is also null then the value of [ListTileThemeData.iconColor] is used.
+  /// is also null then the value of [ColorScheme.primary] is used.
   ///
   /// See also:
   ///
@@ -229,6 +228,15 @@
   /// The icon color of tile's expansion arrow icon when the sublist is collapsed.
   ///
   /// Used to override to the [ListTileThemeData.iconColor].
+  ///
+  /// If this property is null then [ExpansionTileThemeData.collapsedIconColor] is used. If that
+  /// is also null and [ThemeData.useMaterial3] is true, [ColorScheme.onSurface] is used. Otherwise,
+  /// defaults to [ThemeData.unselectedWidgetColor] color.
+  ///
+  /// See also:
+  ///
+  /// * [ExpansionTileTheme.of], which returns the nearest [ExpansionTileTheme]'s
+  ///   [ExpansionTileThemeData].
   final Color? collapsedIconColor;
 
 
@@ -237,7 +245,8 @@
   /// Used to override to the [ListTileThemeData.textColor].
   ///
   /// If this property is null then [ExpansionTileThemeData.textColor] is used. If that
-  /// is also null then the value of [ListTileThemeData.textColor] is used.
+  /// is also null then and [ThemeData.useMaterial3] is true, color of the [TextTheme.bodyLarge]
+  /// will be used for the [title] and [subtitle]. Otherwise, defaults to [ColorScheme.primary] color.
   ///
   /// See also:
   ///
@@ -249,8 +258,10 @@
   ///
   /// Used to override to the [ListTileThemeData.textColor].
   ///
-  /// If this property is null then [ExpansionTileThemeData.collapsedTextColor] is used. If that
-  /// is also null then the value of [ListTileThemeData.textColor] is used.
+  /// If this property is null then [ExpansionTileThemeData.collapsedTextColor] is used.
+  /// If that is also null and [ThemeData.useMaterial3] is true, color of the
+  /// [TextTheme.bodyLarge] will be used for the [title] and [subtitle]. Otherwise,
+  /// defaults to color of the [TextTheme.titleMedium].
   ///
   /// See also:
   ///
@@ -443,7 +454,9 @@
   void didChangeDependencies() {
     final ThemeData theme = Theme.of(context);
     final ExpansionTileThemeData expansionTileTheme = ExpansionTileTheme.of(context);
-    final ColorScheme colorScheme = theme.colorScheme;
+    final ExpansionTileThemeData defaults = theme.useMaterial3
+      ? _ExpansionTileDefaultsM3(context)
+      : _ExpansionTileDefaultsM2(context);
     _borderTween
       ..begin = widget.collapsedShape
         ?? expansionTileTheme.collapsedShape
@@ -460,13 +473,13 @@
     _headerColorTween
       ..begin = widget.collapsedTextColor
         ?? expansionTileTheme.collapsedTextColor
-        ?? theme.textTheme.titleMedium!.color
-      ..end = widget.textColor ?? expansionTileTheme.textColor ?? colorScheme.primary;
+        ?? defaults.collapsedTextColor
+      ..end = widget.textColor ?? expansionTileTheme.textColor ?? defaults.textColor;
     _iconColorTween
       ..begin = widget.collapsedIconColor
         ?? expansionTileTheme.collapsedIconColor
-        ?? theme.unselectedWidgetColor
-      ..end = widget.iconColor ?? expansionTileTheme.iconColor ?? colorScheme.primary;
+        ?? defaults.collapsedIconColor
+      ..end = widget.iconColor ?? expansionTileTheme.iconColor ?? defaults.iconColor;
     _backgroundColorTween
       ..begin = widget.collapsedBackgroundColor ?? expansionTileTheme.collapsedBackgroundColor
       ..end = widget.backgroundColor ?? expansionTileTheme.backgroundColor;
@@ -500,3 +513,54 @@
     );
   }
 }
+
+class _ExpansionTileDefaultsM2 extends ExpansionTileThemeData {
+  _ExpansionTileDefaultsM2(this.context);
+
+  final BuildContext context;
+  late final ThemeData _theme = Theme.of(context);
+  late final ColorScheme _colorScheme = _theme.colorScheme;
+
+  @override
+  Color? get textColor => _colorScheme.primary;
+
+  @override
+  Color? get iconColor => _colorScheme.primary;
+
+  @override
+  Color? get collapsedTextColor => _theme.textTheme.titleMedium!.color;
+
+  @override
+  Color? get collapsedIconColor => _theme.unselectedWidgetColor;
+}
+
+// BEGIN GENERATED TOKEN PROPERTIES - ExpansionTile
+
+// Do not edit by hand. The code between the "BEGIN GENERATED" and
+// "END GENERATED" comments are generated from data in the Material
+// Design token database by the script:
+//   dev/tools/gen_defaults/bin/gen_defaults.dart.
+
+// Token database version: v0_152
+
+class _ExpansionTileDefaultsM3 extends ExpansionTileThemeData {
+  _ExpansionTileDefaultsM3(this.context);
+
+  final BuildContext context;
+  late final ThemeData _theme = Theme.of(context);
+  late final ColorScheme _colors = _theme.colorScheme;
+
+  @override
+  Color? get textColor => Theme.of(context).textTheme.bodyLarge!.color;
+
+  @override
+  Color? get iconColor => _colors.primary;
+
+  @override
+  Color? get collapsedTextColor => Theme.of(context).textTheme.bodyLarge!.color;
+
+  @override
+  Color? get collapsedIconColor => _colors.onSurface;
+}
+
+// END GENERATED TOKEN PROPERTIES - ExpansionTile
diff --git a/framework/lib/src/material/expansion_tile_theme.dart b/framework/lib/src/material/expansion_tile_theme.dart
index 08dec3a..8d62465 100644
--- a/framework/lib/src/material/expansion_tile_theme.dart
+++ b/framework/lib/src/material/expansion_tile_theme.dart
@@ -124,7 +124,6 @@
 
   /// Linearly interpolate between ExpansionTileThemeData objects.
   static ExpansionTileThemeData? lerp(ExpansionTileThemeData? a, ExpansionTileThemeData? b, double t) {
-    assert (t != null);
     if (a == null && b == null) {
       return null;
     }
@@ -217,8 +216,7 @@
     super.key,
     required this.data,
     required super.child,
-  }) : assert(child != null),
-       assert(data != null);
+  });
 
   /// Specifies color, alignment, and text style values for
   /// descendant [ExpansionTile] widgets.
diff --git a/framework/lib/src/material/filled_button.dart b/framework/lib/src/material/filled_button.dart
index 15b0854..a7f52b6 100644
--- a/framework/lib/src/material/filled_button.dart
+++ b/framework/lib/src/material/filled_button.dart
@@ -288,7 +288,7 @@
   /// each state, and "others" means all other states.
   ///
   /// The `textScaleFactor` is the value of
-  /// `MediaQuery.of(context).textScaleFactor` and the names of the
+  /// `MediaQuery.textScaleFactorOf(context)` and the names of the
   /// EdgeInsets constructors and `EdgeInsetsGeometry.lerp` have been
   /// abbreviated for readability.
   ///
@@ -368,7 +368,7 @@
     const EdgeInsets.symmetric(horizontal: 16),
     const EdgeInsets.symmetric(horizontal: 8),
     const EdgeInsets.symmetric(horizontal: 4),
-    MediaQuery.maybeOf(context)?.textScaleFactor ?? 1,
+    MediaQuery.textScaleFactorOf(context),
   );
 }
 
@@ -436,9 +436,7 @@
     super.statesController,
     required Widget icon,
     required Widget label,
-  }) : assert(icon != null),
-       assert(label != null),
-       super(
+  }) : super(
          autofocus: autofocus ?? false,
          clipBehavior: clipBehavior ?? Clip.none,
          child: _FilledButtonWithIconChild(icon: icon, label: label)
@@ -457,9 +455,7 @@
     super.statesController,
     required Widget icon,
     required Widget label,
-  }) : assert(icon != null),
-       assert(label != null),
-       super.tonal(
+  }) : super.tonal(
          autofocus: autofocus ?? false,
          clipBehavior: clipBehavior ?? Clip.none,
          child: _FilledButtonWithIconChild(icon: icon, label: label)
@@ -471,7 +467,7 @@
       const EdgeInsetsDirectional.fromSTEB(12, 0, 16, 0),
       const EdgeInsets.symmetric(horizontal: 8),
       const EdgeInsetsDirectional.fromSTEB(8, 0, 4, 0),
-      MediaQuery.maybeOf(context)?.textScaleFactor ?? 1,
+      MediaQuery.textScaleFactorOf(context),
     );
     return super.defaultStyleOf(context).copyWith(
       padding: MaterialStatePropertyAll<EdgeInsetsGeometry>(scaledPadding),
@@ -487,7 +483,7 @@
 
   @override
   Widget build(BuildContext context) {
-    final double scale = MediaQuery.maybeOf(context)?.textScaleFactor ?? 1;
+    final double scale = MediaQuery.textScaleFactorOf(context);
     // Adjust the gap based on the text scale factor. Start at 8, and lerp
     // to 4 based on how large the text is.
     final double gap = scale <= 1 ? 8 : lerpDouble(8, 4, math.min(scale - 1, 1))!;
@@ -505,7 +501,7 @@
 // Design token database by the script:
 //   dev/tools/gen_defaults/bin/gen_defaults.dart.
 
-// Token database version: v0_143
+// Token database version: v0_158
 
 class _FilledButtonDefaultsM3 extends ButtonStyle {
   _FilledButtonDefaultsM3(this.context)
@@ -629,7 +625,7 @@
 // Design token database by the script:
 //   dev/tools/gen_defaults/bin/gen_defaults.dart.
 
-// Token database version: v0_143
+// Token database version: v0_158
 
 class _FilledTonalButtonDefaultsM3 extends ButtonStyle {
   _FilledTonalButtonDefaultsM3(this.context)
diff --git a/framework/lib/src/material/filled_button_theme.dart b/framework/lib/src/material/filled_button_theme.dart
index 1a2187b..8e3a4d3 100644
--- a/framework/lib/src/material/filled_button_theme.dart
+++ b/framework/lib/src/material/filled_button_theme.dart
@@ -49,7 +49,6 @@
 
   /// Linearly interpolate between two filled button themes.
   static FilledButtonThemeData? lerp(FilledButtonThemeData? a, FilledButtonThemeData? b, double t) {
-    assert (t != null);
     if (a == null && b == null) {
       return null;
     }
@@ -98,7 +97,7 @@
     super.key,
     required this.data,
     required super.child,
-  }) : assert(data != null);
+  });
 
   /// The configuration of this theme.
   final FilledButtonThemeData data;
diff --git a/framework/lib/src/material/filter_chip.dart b/framework/lib/src/material/filter_chip.dart
index 61da51c..cf8c62f 100644
--- a/framework/lib/src/material/filter_chip.dart
+++ b/framework/lib/src/material/filter_chip.dart
@@ -88,11 +88,7 @@
     this.showCheckmark,
     this.checkmarkColor,
     this.avatarBorder = const CircleBorder(),
-  }) : assert(selected != null),
-       assert(label != null),
-       assert(clipBehavior != null),
-       assert(autofocus != null),
-       assert(pressElevation == null || pressElevation >= 0.0),
+  }) : assert(pressElevation == null || pressElevation >= 0.0),
        assert(elevation == null || elevation >= 0.0);
 
   @override
@@ -199,7 +195,7 @@
 // Design token database by the script:
 //   dev/tools/gen_defaults/bin/gen_defaults.dart.
 
-// Token database version: v0_143
+// Token database version: v0_158
 
 class _FilterChipDefaultsM3 extends ChipThemeData {
   const _FilterChipDefaultsM3(this.context, this.isEnabled, this.isSelected)
@@ -220,7 +216,7 @@
   Color? get backgroundColor => null;
 
   @override
-  Color? get shadowColor => Theme.of(context).colorScheme.shadow;
+  Color? get shadowColor => Colors.transparent;
 
   @override
   Color? get surfaceTintColor => Theme.of(context).colorScheme.surfaceTint;
@@ -267,7 +263,7 @@
   EdgeInsetsGeometry? get labelPadding => EdgeInsets.lerp(
     const EdgeInsets.symmetric(horizontal: 8.0),
     const EdgeInsets.symmetric(horizontal: 4.0),
-    clampDouble(MediaQuery.of(context).textScaleFactor - 1.0, 0.0, 1.0),
+    clampDouble(MediaQuery.textScaleFactorOf(context) - 1.0, 0.0, 1.0),
   )!;
 }
 
diff --git a/framework/lib/src/material/flexible_space_bar.dart b/framework/lib/src/material/flexible_space_bar.dart
index e2aa07d..c1f6890 100644
--- a/framework/lib/src/material/flexible_space_bar.dart
+++ b/framework/lib/src/material/flexible_space_bar.dart
@@ -84,8 +84,7 @@
     this.collapseMode = CollapseMode.parallax,
     this.stretchModes = const <StretchMode>[StretchMode.zoomBackground],
     this.expandedTitleScale = 1.5,
-  }) : assert(collapseMode != null),
-       assert(expandedTitleScale >= 1);
+  }) : assert(expandedTitleScale >= 1);
 
   /// The primary contents of the flexible space bar when expanded.
   ///
@@ -117,7 +116,7 @@
   /// bottom-left or its center.
   ///
   /// Typically this property is used to adjust how far the title is
-  /// is inset from the bottom-left and it is specified along with
+  /// inset from the bottom-left and it is specified along with
   /// [centerTitle] false.
   ///
   /// By default the value of this property is
@@ -159,7 +158,6 @@
     required double currentExtent,
     required Widget child,
   }) {
-    assert(currentExtent != null);
     return FlexibleSpaceBarSettings(
       toolbarOpacity: toolbarOpacity ?? 1.0,
       minExtent: minExtent ?? currentExtent,
@@ -179,7 +177,6 @@
     if (widget.centerTitle != null) {
       return widget.centerTitle!;
     }
-    assert(theme.platform != null);
     switch (theme.platform) {
       case TargetPlatform.android:
       case TargetPlatform.fuchsia:
@@ -197,7 +194,6 @@
       return Alignment.bottomCenter;
     }
     final TextDirection textDirection = Directionality.of(context);
-    assert(textDirection != null);
     switch (textDirection) {
       case TextDirection.rtl:
         return Alignment.bottomRight;
@@ -223,10 +219,6 @@
     return LayoutBuilder(
       builder: (BuildContext context, BoxConstraints constraints) {
         final FlexibleSpaceBarSettings settings = context.dependOnInheritedWidgetOfExactType<FlexibleSpaceBarSettings>()!;
-        assert(
-          settings != null,
-          'A FlexibleSpaceBar must be wrapped in the widget returned by FlexibleSpaceBar.createSettings().',
-        );
 
         final List<Widget> children = <Widget>[];
 
@@ -388,10 +380,9 @@
     required this.currentExtent,
     required super.child,
     this.isScrolledUnder,
-  }) : assert(toolbarOpacity != null),
-       assert(minExtent != null && minExtent >= 0),
-       assert(maxExtent != null && maxExtent >= 0),
-       assert(currentExtent != null && currentExtent >= 0),
+  }) : assert(minExtent >= 0),
+       assert(maxExtent >= 0),
+       assert(currentExtent >= 0),
        assert(toolbarOpacity >= 0.0),
        assert(minExtent <= maxExtent),
        assert(minExtent <= currentExtent),
diff --git a/framework/lib/src/material/floating_action_button.dart b/framework/lib/src/material/floating_action_button.dart
index cea314f..a8d8823 100644
--- a/framework/lib/src/material/floating_action_button.dart
+++ b/framework/lib/src/material/floating_action_button.dart
@@ -127,10 +127,6 @@
        assert(hoverElevation == null || hoverElevation >= 0.0),
        assert(highlightElevation == null || highlightElevation >= 0.0),
        assert(disabledElevation == null || disabledElevation >= 0.0),
-       assert(mini != null),
-       assert(clipBehavior != null),
-       assert(isExtended != null),
-       assert(autofocus != null),
        _floatingActionButtonType = mini ? _FloatingActionButtonType.small : _FloatingActionButtonType.regular,
        _extendedLabel = null,
        extendedIconLabelSpacing = null,
@@ -174,8 +170,6 @@
        assert(hoverElevation == null || hoverElevation >= 0.0),
        assert(highlightElevation == null || highlightElevation >= 0.0),
        assert(disabledElevation == null || disabledElevation >= 0.0),
-       assert(clipBehavior != null),
-       assert(autofocus != null),
        _floatingActionButtonType = _FloatingActionButtonType.small,
        mini = true,
        isExtended = false,
@@ -221,8 +215,6 @@
        assert(hoverElevation == null || hoverElevation >= 0.0),
        assert(highlightElevation == null || highlightElevation >= 0.0),
        assert(disabledElevation == null || disabledElevation >= 0.0),
-       assert(clipBehavior != null),
-       assert(autofocus != null),
        _floatingActionButtonType = _FloatingActionButtonType.large,
        mini = false,
        isExtended = false,
@@ -273,9 +265,6 @@
        assert(hoverElevation == null || hoverElevation >= 0.0),
        assert(highlightElevation == null || highlightElevation >= 0.0),
        assert(disabledElevation == null || disabledElevation >= 0.0),
-       assert(isExtended != null),
-       assert(clipBehavior != null),
-       assert(autofocus != null),
        mini = false,
        _floatingActionButtonType = _FloatingActionButtonType.extended,
        child = icon,
@@ -804,7 +793,7 @@
 // Design token database by the script:
 //   dev/tools/gen_defaults/bin/gen_defaults.dart.
 
-// Token database version: v0_143
+// Token database version: v0_158
 
 class _FABDefaultsM3 extends FloatingActionButtonThemeData {
   _FABDefaultsM3(this.context, this.type, this.hasChild)
diff --git a/framework/lib/src/material/floating_action_button_location.dart b/framework/lib/src/material/floating_action_button_location.dart
index 1f6f034..29fea42 100644
--- a/framework/lib/src/material/floating_action_button_location.dart
+++ b/framework/lib/src/material/floating_action_button_location.dart
@@ -652,7 +652,6 @@
   /// Calculates x-offset for start-aligned [FloatingActionButtonLocation]s.
   @override
   double getOffsetX(ScaffoldPrelayoutGeometry scaffoldGeometry, double adjustment) {
-    assert(scaffoldGeometry.textDirection != null);
     switch (scaffoldGeometry.textDirection) {
       case TextDirection.rtl:
         return StandardFabLocation._rightOffsetX(scaffoldGeometry, adjustment);
@@ -676,7 +675,6 @@
   /// Calculates x-offset for end-aligned [FloatingActionButtonLocation]s.
   @override
   double getOffsetX(ScaffoldPrelayoutGeometry scaffoldGeometry, double adjustment) {
-    assert(scaffoldGeometry.textDirection != null);
     switch (scaffoldGeometry.textDirection) {
       case TextDirection.rtl:
         return StandardFabLocation._leftOffsetX(scaffoldGeometry, adjustment);
diff --git a/framework/lib/src/material/floating_action_button_theme.dart b/framework/lib/src/material/floating_action_button_theme.dart
index d8495b8..1c744d5 100644
--- a/framework/lib/src/material/floating_action_button_theme.dart
+++ b/framework/lib/src/material/floating_action_button_theme.dart
@@ -193,7 +193,6 @@
   ///
   /// {@macro dart.ui.shadow.lerp}
   static FloatingActionButtonThemeData? lerp(FloatingActionButtonThemeData? a, FloatingActionButtonThemeData? b, double t) {
-    assert(t != null);
     if (a == null && b == null) {
       return null;
     }
diff --git a/framework/lib/src/material/flutter_logo.dart b/framework/lib/src/material/flutter_logo.dart
index 6abc740..2def362 100644
--- a/framework/lib/src/material/flutter_logo.dart
+++ b/framework/lib/src/material/flutter_logo.dart
@@ -28,10 +28,7 @@
     this.style = FlutterLogoStyle.markOnly,
     this.duration = const Duration(milliseconds: 750),
     this.curve = Curves.fastOutSlowIn,
-  }) : assert(textColor != null),
-       assert(style != null),
-       assert(duration != null),
-       assert(curve != null);
+  });
 
   /// The size of the logo in logical pixels.
   ///
diff --git a/framework/lib/src/material/grid_tile.dart b/framework/lib/src/material/grid_tile.dart
index e2560b7..01d5169 100644
--- a/framework/lib/src/material/grid_tile.dart
+++ b/framework/lib/src/material/grid_tile.dart
@@ -25,7 +25,7 @@
     this.header,
     this.footer,
     required this.child,
-  }) : assert(child != null);
+  });
 
   /// The widget to show over the top of this grid tile.
   ///
diff --git a/framework/lib/src/material/icon_button.dart b/framework/lib/src/material/icon_button.dart
index 6020d4e..21f541b 100644
--- a/framework/lib/src/material/icon_button.dart
+++ b/framework/lib/src/material/icon_button.dart
@@ -198,9 +198,7 @@
     this.isSelected,
     this.selectedIcon,
     required this.icon,
-  }) : assert(splashRadius == null || splashRadius > 0),
-       assert(autofocus != null),
-       assert(icon != null);
+  }) : assert(splashRadius == null || splashRadius > 0);
 
   /// The size of the icon inside the button.
   ///
@@ -962,7 +960,7 @@
 // Design token database by the script:
 //   dev/tools/gen_defaults/bin/gen_defaults.dart.
 
-// Token database version: v0_143
+// Token database version: v0_158
 
 class _IconButtonDefaultsM3 extends ButtonStyle {
   _IconButtonDefaultsM3(this.context)
@@ -1011,7 +1009,7 @@
         return _colors.onSurfaceVariant.withOpacity(0.08);
       }
       if (states.contains(MaterialState.focused)) {
-        return _colors.onSurfaceVariant.withOpacity(0.08);
+        return _colors.onSurfaceVariant.withOpacity(0.12);
       }
       if (states.contains(MaterialState.pressed)) {
         return _colors.onSurfaceVariant.withOpacity(0.12);
diff --git a/framework/lib/src/material/icon_button_theme.dart b/framework/lib/src/material/icon_button_theme.dart
index 71010c2..1f5261d 100644
--- a/framework/lib/src/material/icon_button_theme.dart
+++ b/framework/lib/src/material/icon_button_theme.dart
@@ -49,7 +49,6 @@
 
   /// Linearly interpolate between two icon button themes.
   static IconButtonThemeData? lerp(IconButtonThemeData? a, IconButtonThemeData? b, double t) {
-    assert (t != null);
     if (a == null && b == null) {
       return null;
     }
@@ -96,7 +95,7 @@
     super.key,
     required this.data,
     required super.child,
-  }) : assert(data != null);
+  });
 
   /// The configuration of this theme.
   final IconButtonThemeData data;
diff --git a/framework/lib/src/material/icons.dart b/framework/lib/src/material/icons.dart
index 2da17ac..70e4f81 100644
--- a/framework/lib/src/material/icons.dart
+++ b/framework/lib/src/material/icons.dart
@@ -119,9 +119,9 @@
 /// ![The following code snippet would generate a row of icons consisting of a pink heart, a green musical note, and a blue umbrella, each progressively bigger than the last.](https://flutter.github.io/assets-for-api-docs/assets/widgets/icon.png)
 ///
 /// ```dart
-/// Row(
+/// const Row(
 ///   mainAxisAlignment: MainAxisAlignment.spaceAround,
-///   children: const <Widget>[
+///   children: <Widget>[
 ///     Icon(
 ///       Icons.favorite,
 ///       color: Colors.pink,
@@ -149,6 +149,7 @@
 ///  * [IconButton]
 ///  * <https://material.io/resources/icons>
 ///  * [AnimatedIcons], for the list of available animated Material Icons.
+@staticIconProvider
 class Icons {
   // This class is not meant to be instantiated or extended; this constructor
   // prevents instantiation and extension.
diff --git a/framework/lib/src/material/ink_decoration.dart b/framework/lib/src/material/ink_decoration.dart
index 8a95a83..3d5dd9d 100644
--- a/framework/lib/src/material/ink_decoration.dart
+++ b/framework/lib/src/material/ink_decoration.dart
@@ -193,10 +193,6 @@
     this.height,
     this.child,
   }) : assert(padding == null || padding.isNonNegative),
-       assert(image != null),
-       assert(alignment != null),
-       assert(repeat != null),
-       assert(matchTextDirection != null),
        decoration = BoxDecoration(
          image: DecorationImage(
            image: image,
@@ -241,10 +237,10 @@
   final double? height;
 
   EdgeInsetsGeometry get _paddingIncludingDecoration {
-    if (decoration == null || decoration!.padding == null) {
+    if (decoration == null) {
       return padding ?? EdgeInsets.zero;
     }
-    final EdgeInsetsGeometry decorationPadding = decoration!.padding!;
+    final EdgeInsetsGeometry decorationPadding = decoration!.padding;
     if (padding == null) {
       return decorationPadding;
     }
@@ -337,8 +333,7 @@
     required super.controller,
     required super.referenceBox,
     super.onRemoved,
-  }) : assert(configuration != null),
-       _configuration = configuration {
+  }) : _configuration = configuration {
     this.decoration = decoration;
     controller.addInkFeature(this);
   }
@@ -369,7 +364,6 @@
   ImageConfiguration get configuration => _configuration;
   ImageConfiguration _configuration;
   set configuration(ImageConfiguration value) {
-    assert(value != null);
     if (value == _configuration) {
       return;
     }
diff --git a/framework/lib/src/material/ink_highlight.dart b/framework/lib/src/material/ink_highlight.dart
index 10d6b31..a2f14ac 100644
--- a/framework/lib/src/material/ink_highlight.dart
+++ b/framework/lib/src/material/ink_highlight.dart
@@ -48,11 +48,7 @@
     RectCallback? rectCallback,
     super.onRemoved,
     Duration fadeDuration = _kDefaultHighlightFadeDuration,
-  }) : assert(color != null),
-       assert(shape != null),
-       assert(textDirection != null),
-       assert(fadeDuration != null),
-       _shape = shape,
+  }) : _shape = shape,
        _radius = radius,
        _borderRadius = borderRadius ?? BorderRadius.zero,
        _customBorder = customBorder,
@@ -109,7 +105,6 @@
   }
 
   void _paintHighlight(Canvas canvas, Rect rect, Paint paint) {
-    assert(_shape != null);
     canvas.save();
     if (_customBorder != null) {
       canvas.clipPath(_customBorder!.getOuterPath(rect, textDirection: _textDirection));
diff --git a/framework/lib/src/material/ink_ripple.dart b/framework/lib/src/material/ink_ripple.dart
index 009df09..8a2fe57 100644
--- a/framework/lib/src/material/ink_ripple.dart
+++ b/framework/lib/src/material/ink_ripple.dart
@@ -119,17 +119,13 @@
     ShapeBorder? customBorder,
     double? radius,
     super.onRemoved,
-  }) : assert(color != null),
-       assert(position != null),
-       assert(textDirection != null),
-       _position = position,
+  }) : _position = position,
        _borderRadius = borderRadius ?? BorderRadius.zero,
        _customBorder = customBorder,
        _textDirection = textDirection,
        _targetRadius = radius ?? _getTargetRadius(referenceBox, containedInkWell, rectCallback, position),
        _clipCallback = _getClipCallback(referenceBox, containedInkWell, rectCallback),
        super(controller: controller, color: color) {
-    assert(_borderRadius != null);
 
     // Immediately begin fading-in the initial splash.
     _fadeInController = AnimationController(duration: _kFadeInDuration, vsync: controller.vsync)
@@ -232,10 +228,14 @@
   void paintFeature(Canvas canvas, Matrix4 transform) {
     final int alpha = _fadeInController.isAnimating ? _fadeIn.value : _fadeOut.value;
     final Paint paint = Paint()..color = color.withAlpha(alpha);
+    Rect? rect;
+    if (_clipCallback != null) {
+       rect = _clipCallback!();
+    }
     // Splash moves to the center of the reference box.
     final Offset center = Offset.lerp(
       _position,
-      referenceBox.size.center(Offset.zero),
+      rect != null ? rect.center : referenceBox.size.center(Offset.zero),
       Curves.ease.transform(_radiusController.value),
     )!;
     paintInkCircle(
diff --git a/framework/lib/src/material/ink_splash.dart b/framework/lib/src/material/ink_splash.dart
index 66e98c5..805e519 100644
--- a/framework/lib/src/material/ink_splash.dart
+++ b/framework/lib/src/material/ink_splash.dart
@@ -125,8 +125,7 @@
     ShapeBorder? customBorder,
     double? radius,
     super.onRemoved,
-  }) : assert(textDirection != null),
-       _position = position,
+  }) : _position = position,
        _borderRadius = borderRadius ?? BorderRadius.zero,
        _customBorder = customBorder,
        _targetRadius = radius ?? _getTargetRadius(referenceBox, containedInkWell, rectCallback, position!),
@@ -134,7 +133,6 @@
        _repositionToReferenceBox = !containedInkWell,
        _textDirection = textDirection,
        super(controller: controller, color: color) {
-    assert(_borderRadius != null);
     _radiusController = AnimationController(duration: _kUnconfirmedSplashDuration, vsync: controller.vsync)
       ..addListener(controller.markNeedsPaint)
       ..forward();
diff --git a/framework/lib/src/material/ink_well.dart b/framework/lib/src/material/ink_well.dart
index 3e264af..018de53 100644
--- a/framework/lib/src/material/ink_well.dart
+++ b/framework/lib/src/material/ink_well.dart
@@ -42,9 +42,7 @@
     required super.referenceBox,
     required Color color,
     super.onRemoved,
-  }) : assert(controller != null),
-       assert(referenceBox != null),
-       _color = color;
+  }) : _color = color;
 
   /// Called when the user input that triggered this feature's appearance was confirmed.
   ///
@@ -111,12 +109,6 @@
     BorderRadius borderRadius = BorderRadius.zero,
     RectCallback? clipCallback,
   }) {
-    assert(canvas != null);
-    assert(transform != null);
-    assert(paint != null);
-    assert(center != null);
-    assert(radius != null);
-    assert(borderRadius != null);
 
     final Offset? originOffset = MatrixUtils.getAsTranslation(transform);
     canvas.save();
@@ -323,12 +315,7 @@
     this.onFocusChange,
     this.autofocus = false,
     this.statesController,
-  }) : assert(containedInkWell != null),
-       assert(highlightShape != null),
-       assert(enableFeedback != null),
-       assert(excludeFromSemantics != null),
-       assert(autofocus != null),
-       assert(canRequestFocus != null);
+  });
 
   /// The widget below this widget in the tree.
   ///
@@ -688,12 +675,7 @@
     this.getRectCallback,
     required this.debugCheckContext,
     this.statesController,
-  }) : assert(containedInkWell != null),
-       assert(highlightShape != null),
-       assert(enableFeedback != null),
-       assert(excludeFromSemantics != null),
-       assert(autofocus != null),
-       assert(canRequestFocus != null);
+  });
 
   final Widget? child;
   final GestureTapCallback? onTap;
@@ -781,7 +763,6 @@
 
   @override
   void markChildInkResponsePressed(_ParentInkResponseState childState, bool value) {
-    assert(childState != null);
     final bool lastAnyPressed = _anyChildInkResponsePressed;
     if (value) {
       _activeChildren.add(childState);
@@ -842,14 +823,14 @@
         widget.radius != oldWidget.radius ||
         widget.borderRadius != oldWidget.borderRadius ||
         widget.highlightShape != oldWidget.highlightShape) {
-      final InkHighlight? hoverHighLight = _highlights[_HighlightType.hover];
-      if (hoverHighLight != null) {
-        hoverHighLight.dispose();
+      final InkHighlight? hoverHighlight = _highlights[_HighlightType.hover];
+      if (hoverHighlight != null) {
+        hoverHighlight.dispose();
         updateHighlight(_HighlightType.hover, value: _hovering, callOnHover: false);
       }
-      final InkHighlight? focusHighLight = _highlights[_HighlightType.focus];
-      if (focusHighLight != null) {
-        focusHighLight.dispose();
+      final InkHighlight? focusHighlight = _highlights[_HighlightType.focus];
+      if (focusHighlight != null) {
+        focusHighlight.dispose();
         // Do not call updateFocusHighlights() here because it is called below
       }
     }
@@ -857,6 +838,13 @@
       statesController.update(MaterialState.disabled, !enabled);
       if (!enabled) {
         statesController.update(MaterialState.pressed, false);
+        // Remove the existing hover highlight immediately when enabled is false.
+        // Do not rely on updateHighlight or InkHighlight.deactivate to not break
+        // the expected lifecycle which is updating _hovering when the mouse exit.
+        // Manually updating _hovering here or calling InkHighlight.deactivate
+        // will lead to onHover not being called or call when it is not allowed.
+        final InkHighlight? hoverHighlight = _highlights[_HighlightType.hover];
+        hoverHighlight?.dispose();
       }
       // Don't call widget.onHover because many widgets, including the button
       // widgets, apply setState to an ancestor context from onHover.
@@ -937,7 +925,7 @@
         _highlights[type] = InkHighlight(
           controller: Material.of(context),
           referenceBox: referenceBox,
-          color: resolvedOverlayColor,
+          color: enabled ? resolvedOverlayColor : resolvedOverlayColor.withAlpha(0),
           shape: widget.highlightShape,
           radius: widget.radius,
           borderRadius: widget.borderRadius,
@@ -1018,7 +1006,7 @@
   }
 
   bool get _shouldShowFocus {
-    final NavigationMode mode = MediaQuery.maybeOf(context)?.navigationMode ?? NavigationMode.traditional;
+    final NavigationMode mode = MediaQuery.maybeNavigationModeOf(context) ?? NavigationMode.traditional;
     switch (mode) {
       case NavigationMode.traditional:
         return enabled && _hasFocus;
@@ -1166,7 +1154,7 @@
   }
 
   bool get _canRequestFocus {
-    final NavigationMode mode = MediaQuery.maybeOf(context)?.navigationMode ?? NavigationMode.traditional;
+    final NavigationMode mode = MediaQuery.maybeNavigationModeOf(context) ?? NavigationMode.traditional;
     switch (mode) {
       case NavigationMode.traditional:
         return enabled && widget.canRequestFocus;
diff --git a/framework/lib/src/material/input_border.dart b/framework/lib/src/material/input_border.dart
index a18cfd2..b1952eb 100644
--- a/framework/lib/src/material/input_border.dart
+++ b/framework/lib/src/material/input_border.dart
@@ -37,7 +37,7 @@
   /// [InputDecorator.isFocused].
   const InputBorder({
     this.borderSide = BorderSide.none,
-  }) : assert(borderSide != null);
+  });
 
   /// No input border.
   ///
@@ -157,7 +157,7 @@
       topLeft: Radius.circular(4.0),
       topRight: Radius.circular(4.0),
     ),
-  }) : assert(borderRadius != null);
+  });
 
   /// The radii of the border's rounded rectangle corners.
   ///
@@ -259,12 +259,13 @@
     if (other.runtimeType != runtimeType) {
       return false;
     }
-    return other is InputBorder
-        && other.borderSide == borderSide;
+    return other is UnderlineInputBorder
+        && other.borderSide == borderSide
+        && other.borderRadius == borderRadius;
   }
 
   @override
-  int get hashCode => borderSide.hashCode;
+  int get hashCode => Object.hash(borderSide, borderRadius);
 }
 
 /// Draws a rounded rectangle around an [InputDecorator]'s container.
@@ -307,8 +308,7 @@
     super.borderSide = const BorderSide(),
     this.borderRadius = const BorderRadius.all(Radius.circular(4.0)),
     this.gapPadding = 4.0,
-  }) : assert(borderRadius != null),
-       assert(gapPadding != null && gapPadding >= 0.0);
+  }) : assert(gapPadding >= 0.0);
 
   // The label text's gap can extend into the corners (even both the top left
   // and the top right corner). To avoid the more complicated problem of finding
@@ -515,7 +515,6 @@
     double gapPercentage = 0.0,
     TextDirection? textDirection,
   }) {
-    assert(gapExtent != null);
     assert(gapPercentage >= 0.0 && gapPercentage <= 1.0);
     assert(_cornersAreCircular(borderRadius));
 
diff --git a/framework/lib/src/material/input_chip.dart b/framework/lib/src/material/input_chip.dart
index c2d483c..ac5c40d 100644
--- a/framework/lib/src/material/input_chip.dart
+++ b/framework/lib/src/material/input_chip.dart
@@ -114,12 +114,7 @@
       'This feature was deprecated after v2.10.0-0.3.pre.'
     )
     this.useDeleteButtonTooltip = true,
-  }) : assert(selected != null),
-       assert(isEnabled != null),
-       assert(label != null),
-       assert(clipBehavior != null),
-       assert(autofocus != null),
-       assert(pressElevation == null || pressElevation >= 0.0),
+  }) : assert(pressElevation == null || pressElevation >= 0.0),
        assert(elevation == null || elevation >= 0.0);
 
   @override
@@ -249,7 +244,7 @@
 // Design token database by the script:
 //   dev/tools/gen_defaults/bin/gen_defaults.dart.
 
-// Token database version: v0_143
+// Token database version: v0_158
 
 class _InputChipDefaultsM3 extends ChipThemeData {
   const _InputChipDefaultsM3(this.context, this.isEnabled)
@@ -310,7 +305,7 @@
   EdgeInsetsGeometry? get labelPadding => EdgeInsets.lerp(
     const EdgeInsets.symmetric(horizontal: 8.0),
     const EdgeInsets.symmetric(horizontal: 4.0),
-    clampDouble(MediaQuery.of(context).textScaleFactor - 1.0, 0.0, 1.0),
+    clampDouble(MediaQuery.textScaleFactorOf(context) - 1.0, 0.0, 1.0),
   )!;
 }
 
diff --git a/framework/lib/src/material/input_date_picker_form_field.dart b/framework/lib/src/material/input_date_picker_form_field.dart
index 7b6ac92..63acaff 100644
--- a/framework/lib/src/material/input_date_picker_form_field.dart
+++ b/framework/lib/src/material/input_date_picker_form_field.dart
@@ -58,10 +58,7 @@
     this.fieldLabelText,
     this.keyboardType,
     this.autofocus = false,
-  }) : assert(firstDate != null),
-       assert(lastDate != null),
-       assert(autofocus != null),
-       initialDate = initialDate != null ? DateUtils.dateOnly(initialDate) : null,
+  }) : initialDate = initialDate != null ? DateUtils.dateOnly(initialDate) : null,
        firstDate = DateUtils.dateOnly(firstDate),
        lastDate = DateUtils.dateOnly(lastDate) {
     assert(
@@ -237,11 +234,16 @@
 
   @override
   Widget build(BuildContext context) {
+    final ThemeData theme = Theme.of(context);
+    final bool useMaterial3 = theme.useMaterial3;
     final MaterialLocalizations localizations = MaterialLocalizations.of(context);
-    final InputDecorationTheme inputTheme = Theme.of(context).inputDecorationTheme;
+    final InputDecorationTheme inputTheme = theme.inputDecorationTheme;
+    final InputBorder inputBorder = inputTheme.border
+      ?? (useMaterial3 ? const OutlineInputBorder() : const UnderlineInputBorder());
+
     return TextFormField(
       decoration: InputDecoration(
-        border: inputTheme.border ?? const UnderlineInputBorder(),
+        border: inputBorder,
         filled: inputTheme.filled,
         hintText: widget.fieldHintText ?? localizations.dateHelpText,
         labelText: widget.fieldLabelText ?? localizations.dateInputLabel,
diff --git a/framework/lib/src/material/input_decorator.dart b/framework/lib/src/material/input_decorator.dart
index 708283d..7b2b408 100644
--- a/framework/lib/src/material/input_decorator.dart
+++ b/framework/lib/src/material/input_decorator.dart
@@ -12,6 +12,8 @@
 import 'color_scheme.dart';
 import 'colors.dart';
 import 'constants.dart';
+import 'icon_button.dart';
+import 'icon_button_theme.dart';
 import 'input_border.dart';
 import 'material.dart';
 import 'material_state.dart';
@@ -22,7 +24,9 @@
 // Examples can assume:
 // late Widget _myIcon;
 
-const Duration _kTransitionDuration = Duration(milliseconds: 200);
+// The duration value extracted from:
+// https://github.com/material-components/material-components-android/blob/master/lib/java/com/google/android/material/textfield/TextInputLayout.java
+const Duration _kTransitionDuration = Duration(milliseconds: 167);
 const Curve _kTransitionCurve = Curves.fastOutSlowIn;
 const double _kFinalLabelScale = 0.75;
 
@@ -152,9 +156,7 @@
     required this.fillColor,
     required this.hoverColor,
     required this.isHovering,
-  }) : assert(border != null),
-       assert(gap != null),
-       assert(fillColor != null);
+  });
 
   final InputBorder border;
   final _InputBorderGap gap;
@@ -192,6 +194,7 @@
     _borderAnimation = CurvedAnimation(
       parent: _controller,
       curve: _kTransitionCurve,
+      reverseCurve: _kTransitionCurve.flipped,
     );
     _border = _InputBorderTween(
       begin: widget.border,
@@ -505,8 +508,7 @@
 ///    behave.
 @immutable
 class FloatingLabelAlignment {
-  const FloatingLabelAlignment._(this._x) : assert(_x != null),
-       assert(_x >= -1.0 && _x <= 1.0);
+  const FloatingLabelAlignment._(this._x) : assert(_x >= -1.0 && _x <= 1.0);
 
   // -1 denotes start, 0 denotes center, and 1 denotes end.
   final double _x;
@@ -589,11 +591,7 @@
     this.helperError,
     this.counter,
     this.container,
-  }) : assert(contentPadding != null),
-       assert(isCollapsed != null),
-       assert(floatingLabelHeight != null),
-       assert(floatingLabelProgress != null),
-       assert(floatingLabelAlignment != null);
+  });
 
   final EdgeInsetsGeometry contentPadding;
   final bool isCollapsed;
@@ -705,11 +703,7 @@
     required bool expands,
     required bool material3,
     TextAlignVertical? textAlignVertical,
-  }) : assert(decoration != null),
-       assert(textDirection != null),
-       assert(textBaseline != null),
-       assert(expands != null),
-       _decoration = decoration,
+  }) : _decoration = decoration,
        _textDirection = textDirection,
        _textBaseline = textBaseline,
        _textAlignVertical = textAlignVertical,
@@ -763,7 +757,6 @@
   _Decoration get decoration => _decoration;
   _Decoration _decoration;
   set decoration(_Decoration value) {
-    assert(value != null);
     if (_decoration == value) {
       return;
     }
@@ -774,7 +767,6 @@
   TextDirection get textDirection => _textDirection;
   TextDirection _textDirection;
   set textDirection(TextDirection value) {
-    assert(value != null);
     if (_textDirection == value) {
       return;
     }
@@ -785,7 +777,6 @@
   TextBaseline get textBaseline => _textBaseline;
   TextBaseline _textBaseline;
   set textBaseline(TextBaseline value) {
-    assert(value != null);
     if (_textBaseline == value) {
       return;
     }
@@ -814,7 +805,6 @@
   bool get isFocused => _isFocused;
   bool _isFocused;
   set isFocused(bool value) {
-    assert(value != null);
     if (_isFocused == value) {
       return;
     }
@@ -825,7 +815,6 @@
   bool get expands => _expands;
   bool _expands = false;
   set expands(bool value) {
-    assert(value != null);
     if (_expands == value) {
       return;
     }
@@ -978,12 +967,12 @@
       0.0,
       constraints.maxWidth - (
         _boxSize(icon).width
-        + contentPadding.left
+        + (prefixIcon != null ? 0 : (textDirection == TextDirection.ltr ? contentPadding.left : contentPadding.right))
         + _boxSize(prefixIcon).width
         + _boxSize(prefix).width
         + _boxSize(suffix).width
         + _boxSize(suffixIcon).width
-        + contentPadding.right),
+        + (suffixIcon != null ? 0 : (textDirection == TextDirection.ltr ? contentPadding.right : contentPadding.left))),
     );
     // Increase the available width for the label when it is scaled down.
     final double invertedLabelScale = lerpDouble(1.00, 1 / _kFinalLabelScale, decoration.floatingLabelProgress)!;
@@ -1326,6 +1315,35 @@
     return Size.zero;
   }
 
+  ChildSemanticsConfigurationsResult _childSemanticsConfigurationDelegate(List<SemanticsConfiguration> childConfigs) {
+    final ChildSemanticsConfigurationsResultBuilder builder = ChildSemanticsConfigurationsResultBuilder();
+    List<SemanticsConfiguration>? prefixMergeGroup;
+    List<SemanticsConfiguration>? suffixMergeGroup;
+    for (final SemanticsConfiguration childConfig in childConfigs) {
+      if (childConfig.tagsChildrenWith(_InputDecoratorState._kPrefixSemanticsTag)) {
+        prefixMergeGroup ??= <SemanticsConfiguration>[];
+        prefixMergeGroup.add(childConfig);
+      } else if (childConfig.tagsChildrenWith(_InputDecoratorState._kSuffixSemanticsTag)) {
+        suffixMergeGroup ??= <SemanticsConfiguration>[];
+        suffixMergeGroup.add(childConfig);
+      } else {
+        builder.markAsMergeUp(childConfig);
+      }
+    }
+    if (prefixMergeGroup != null) {
+      builder.markAsSiblingMergeGroup(prefixMergeGroup);
+    }
+    if (suffixMergeGroup != null) {
+      builder.markAsSiblingMergeGroup(suffixMergeGroup);
+    }
+    return builder.build();
+  }
+
+  @override
+  void describeSemanticsConfiguration(SemanticsConfiguration config) {
+    config.childConfigurationsDelegate = _childSemanticsConfigurationDelegate;
+  }
+
   @override
   void performLayout() {
     final BoxConstraints constraints = this.constraints;
@@ -1542,7 +1560,7 @@
       final double t = decoration.floatingLabelProgress;
       // The center of the outline border label ends up a little below the
       // center of the top border line.
-      final bool isOutlineBorder = decoration.border != null && decoration.border.isOutline;
+      final bool isOutlineBorder = decoration.border.isOutline;
       // Temporary opt-in fix for https://github.com/flutter/flutter/issues/54028
       // Center the scaled label relative to the border.
       final double floatingY = isOutlineBorder ? (-labelHeight * _kFinalLabelScale) / 2.0 + borderWeight / 2.0 : contentPadding.top;
@@ -1600,7 +1618,6 @@
 
   @override
   bool hitTestChildren(BoxHitTestResult result, { required Offset position }) {
-    assert(position != null);
     for (final RenderBox child in children) {
       // The label must be handled specially since we've transformed it.
       final Offset offset = _boxParentData(child).offset;
@@ -1639,10 +1656,7 @@
     required this.textBaseline,
     required this.isFocused,
     required this.expands,
-  }) : assert(decoration != null),
-       assert(textDirection != null),
-       assert(textBaseline != null),
-       assert(expands != null);
+  });
 
   final _Decoration decoration;
   final TextDirection textDirection;
@@ -1713,12 +1727,16 @@
     this.text,
     this.style,
     this.child,
+    this.semanticsSortKey,
+    required this.semanticsTag,
   });
 
   final bool labelIsFloating;
   final String? text;
   final TextStyle? style;
   final Widget? child;
+  final SemanticsSortKey? semanticsSortKey;
+  final SemanticsTag semanticsTag;
 
   @override
   Widget build(BuildContext context) {
@@ -1728,7 +1746,11 @@
         duration: _kTransitionDuration,
         curve: _kTransitionCurve,
         opacity: labelIsFloating ? 1.0 : 0.0,
-        child: child ?? (text == null ? null : Text(text!, style: style)),
+        child: Semantics(
+          sortKey: semanticsSortKey,
+          tagForChildren: semanticsTag,
+          child: child ?? (text == null ? null : Text(text!, style: style)),
+        ),
       ),
     );
   }
@@ -1773,11 +1795,7 @@
     this.expands = false,
     this.isEmpty = false,
     this.child,
-  }) : assert(decoration != null),
-       assert(isFocused != null),
-       assert(isHovering != null),
-       assert(expands != null),
-       assert(isEmpty != null);
+  });
 
   /// The text and styles to use when decorating the child.
   ///
@@ -1896,9 +1914,15 @@
 }
 
 class _InputDecoratorState extends State<InputDecorator> with TickerProviderStateMixin {
-  late AnimationController _floatingLabelController;
-  late AnimationController _shakingLabelController;
+  late final AnimationController _floatingLabelController;
+  late final Animation<double> _floatingLabelAnimation;
+  late final AnimationController _shakingLabelController;
   final _InputBorderGap _borderGap = _InputBorderGap();
+  static const OrdinalSortKey _kPrefixSemanticsSortOrder = OrdinalSortKey(0);
+  static const OrdinalSortKey _kInputSemanticsSortOrder = OrdinalSortKey(1);
+  static const OrdinalSortKey _kSuffixSemanticsSortOrder = OrdinalSortKey(2);
+  static const SemanticsTag _kPrefixSemanticsTag = SemanticsTag('_InputDecoratorState.prefix');
+  static const SemanticsTag _kSuffixSemanticsTag = SemanticsTag('_InputDecoratorState.suffix');
 
   @override
   void initState() {
@@ -1914,6 +1938,11 @@
       value: labelIsInitiallyFloating ? 1.0 : 0.0,
     );
     _floatingLabelController.addListener(_handleChange);
+    _floatingLabelAnimation = CurvedAnimation(
+      parent: _floatingLabelController,
+      curve: _kTransitionCurve,
+      reverseCurve: _kTransitionCurve.flipped,
+    );
 
     _shakingLabelController = AnimationController(
       duration: _kTransitionDuration,
@@ -2166,7 +2195,6 @@
       opacity: (isEmpty && !_hasInlineLabel) ? 1.0 : 0.0,
       duration: _kTransitionDuration,
       curve: _kTransitionCurve,
-      alwaysIncludeSemantics: isEmpty || (decoration.labelText == null && decoration.label == null),
       child: Text(
         hintText,
         style: hintStyle,
@@ -2191,7 +2219,7 @@
     final Widget container = _BorderContainer(
       border: border,
       gap: _borderGap,
-      gapAnimation: _floatingLabelController.view,
+      gapAnimation: _floatingLabelAnimation,
       fillColor: _getFillColor(themeData, defaults),
       hoverColor: _getHoverColor(themeData),
       isHovering: isHovering,
@@ -2218,22 +2246,42 @@
       ),
     );
 
-    final Widget? prefix = decoration.prefix == null && decoration.prefixText == null ? null :
-      _AffixText(
-        labelIsFloating: widget._labelShouldWithdraw,
-        text: decoration.prefixText,
-        style: MaterialStateProperty.resolveAs(decoration.prefixStyle, materialState) ?? hintStyle,
-        child: decoration.prefix,
-      );
+    final bool hasPrefix = decoration.prefix != null || decoration.prefixText != null;
+    final bool hasSuffix = decoration.suffix != null || decoration.suffixText != null;
 
-    final Widget? suffix = decoration.suffix == null && decoration.suffixText == null ? null :
-      _AffixText(
-        labelIsFloating: widget._labelShouldWithdraw,
-        text: decoration.suffixText,
-        style: MaterialStateProperty.resolveAs(decoration.suffixStyle, materialState) ?? hintStyle,
-        child: decoration.suffix,
-      );
+    Widget? input = widget.child;
+    // If at least two out of the three are visible, it needs semantics sort
+    // order.
+    final bool needsSemanticsSortOrder = widget._labelShouldWithdraw && (input != null ? (hasPrefix || hasSuffix) : (hasPrefix && hasSuffix));
 
+    final Widget? prefix = hasPrefix
+      ? _AffixText(
+          labelIsFloating: widget._labelShouldWithdraw,
+          text: decoration.prefixText,
+          style: MaterialStateProperty.resolveAs(decoration.prefixStyle, materialState) ?? hintStyle,
+          semanticsSortKey: needsSemanticsSortOrder ? _kPrefixSemanticsSortOrder : null,
+          semanticsTag: _kPrefixSemanticsTag,
+          child: decoration.prefix,
+        )
+      : null;
+
+    final Widget? suffix = hasSuffix
+      ? _AffixText(
+          labelIsFloating: widget._labelShouldWithdraw,
+          text: decoration.suffixText,
+          style: MaterialStateProperty.resolveAs(decoration.suffixStyle, materialState) ?? hintStyle,
+          semanticsSortKey: needsSemanticsSortOrder ? _kSuffixSemanticsSortOrder : null,
+          semanticsTag: _kSuffixSemanticsTag,
+          child: decoration.suffix,
+        )
+      : null;
+
+    if (input != null && needsSemanticsSortOrder) {
+      input = Semantics(
+        sortKey: _kInputSemanticsSortOrder,
+        child: input,
+      );
+    }
 
     final bool decorationIsDense = decoration.isDense ?? false;
     final double iconSize = decorationIsDense ? 18.0 : 24.0;
@@ -2261,18 +2309,28 @@
           cursor: SystemMouseCursors.basic,
           child: ConstrainedBox(
             constraints: decoration.prefixIconConstraints ??
-                themeData.visualDensity.effectiveConstraints(
-                  const BoxConstraints(
-                    minWidth: kMinInteractiveDimension,
-                    minHeight: kMinInteractiveDimension,
-                  ),
+              themeData.visualDensity.effectiveConstraints(
+                const BoxConstraints(
+                  minWidth: kMinInteractiveDimension,
+                  minHeight: kMinInteractiveDimension,
                 ),
+              ),
             child: IconTheme.merge(
               data: IconThemeData(
                 color: _getPrefixIconColor(themeData, defaults),
                 size: iconSize,
               ),
-              child: decoration.prefixIcon!,
+              child: IconButtonTheme(
+                data: IconButtonThemeData(
+                style: IconButton.styleFrom(
+                  foregroundColor: _getPrefixIconColor(themeData, defaults),
+                  iconSize: iconSize,
+                  ),
+                ),
+                child: Semantics(
+                  child: decoration.prefixIcon,
+                ),
+              ),
             ),
           ),
         ),
@@ -2286,22 +2344,32 @@
           cursor: SystemMouseCursors.basic,
           child: ConstrainedBox(
             constraints: decoration.suffixIconConstraints ??
-                themeData.visualDensity.effectiveConstraints(
-                  const BoxConstraints(
-                    minWidth: kMinInteractiveDimension,
-                    minHeight: kMinInteractiveDimension,
+              themeData.visualDensity.effectiveConstraints(
+                const BoxConstraints(
+                  minWidth: kMinInteractiveDimension,
+                  minHeight: kMinInteractiveDimension,
+                ),
+              ),
+              child: IconTheme.merge(
+                data: IconThemeData(
+                  color: _getSuffixIconColor(themeData, defaults),
+                  size: iconSize,
+                ),
+                child: IconButtonTheme(
+                  data: IconButtonThemeData(
+                  style: IconButton.styleFrom(
+                    foregroundColor: _getSuffixIconColor(themeData, defaults),
+                    iconSize: iconSize,
+                    ),
+                  ),
+                  child: Semantics(
+                    child: decoration.suffixIcon,
                   ),
                 ),
-            child: IconTheme.merge(
-              data: IconThemeData(
-                color: _getSuffixIconColor(themeData, defaults),
-                size: iconSize,
               ),
-              child: decoration.suffixIcon!,
             ),
           ),
-        ),
-      );
+        );
 
     final Widget helperError = _HelperError(
       textAlign: textAlign,
@@ -2367,14 +2435,14 @@
         isCollapsed: decoration.isCollapsed,
         floatingLabelHeight: floatingLabelHeight,
         floatingLabelAlignment: decoration.floatingLabelAlignment!,
-        floatingLabelProgress: _floatingLabelController.value,
+        floatingLabelProgress: _floatingLabelAnimation.value,
         border: border,
         borderGap: _borderGap,
         alignLabelWithHint: decoration.alignLabelWithHint ?? false,
         isDense: decoration.isDense,
         visualDensity: themeData.visualDensity,
         icon: icon,
-        input: widget.child,
+        input: input,
         label: label,
         hint: hint,
         prefix: prefix,
@@ -2542,8 +2610,7 @@
     this.semanticCounterText,
     this.alignLabelWithHint,
     this.constraints,
-  }) : assert(enabled != null),
-       assert(!(label != null && labelText != null), 'Declaring both label and labelText is not supported.'),
+  }) : assert(!(label != null && labelText != null), 'Declaring both label and labelText is not supported.'),
        assert(!(prefix != null && prefixText != null), 'Declaring both prefix and prefixText is not supported.'),
        assert(!(suffix != null && suffixText != null), 'Declaring both suffix and suffixText is not supported.');
 
@@ -2564,8 +2631,7 @@
     this.hoverColor,
     this.border = InputBorder.none,
     this.enabled = true,
-  }) : assert(enabled != null),
-       icon = null,
+  }) : icon = null,
        iconColor = null,
        label = null,
        labelText = null,
@@ -3780,11 +3846,7 @@
     this.border,
     this.alignLabelWithHint = false,
     this.constraints,
-  }) : assert(isDense != null),
-       assert(isCollapsed != null),
-       assert(floatingLabelAlignment != null),
-       assert(filled != null),
-       assert(alignLabelWithHint != null);
+  });
 
   /// {@macro flutter.material.inputDecoration.labelStyle}
   final TextStyle? labelStyle;
@@ -4480,7 +4542,7 @@
 // Design token database by the script:
 //   dev/tools/gen_defaults/bin/gen_defaults.dart.
 
-// Token database version: v0_143
+// Token database version: v0_158
 
 class _InputDecoratorDefaultsM3 extends InputDecorationTheme {
    _InputDecoratorDefaultsM3(this.context)
@@ -4580,7 +4642,7 @@
 
   @override
   TextStyle? get labelStyle => MaterialStateTextStyle.resolveWith((Set<MaterialState> states) {
-    final TextStyle textStyle= _textTheme.bodyLarge ?? const TextStyle();
+    final TextStyle textStyle = _textTheme.bodyLarge ?? const TextStyle();
     if(states.contains(MaterialState.error)) {
       if (states.contains(MaterialState.focused)) {
         return textStyle.copyWith(color:_colors.error);
@@ -4604,7 +4666,7 @@
 
   @override
   TextStyle? get floatingLabelStyle => MaterialStateTextStyle.resolveWith((Set<MaterialState> states) {
-    final TextStyle textStyle= _textTheme.bodyLarge ?? const TextStyle();
+    final TextStyle textStyle = _textTheme.bodyLarge ?? const TextStyle();
     if(states.contains(MaterialState.error)) {
       if (states.contains(MaterialState.focused)) {
         return textStyle.copyWith(color:_colors.error);
@@ -4628,7 +4690,7 @@
 
   @override
   TextStyle? get helperStyle => MaterialStateTextStyle.resolveWith((Set<MaterialState> states) {
-    final TextStyle textStyle= _textTheme.bodySmall ?? const TextStyle();
+    final TextStyle textStyle = _textTheme.bodySmall ?? const TextStyle();
     if (states.contains(MaterialState.disabled)) {
       return textStyle.copyWith(color:_colors.onSurface.withOpacity(0.38));
     }
@@ -4637,7 +4699,7 @@
 
   @override
   TextStyle? get errorStyle => MaterialStateTextStyle.resolveWith((Set<MaterialState> states) {
-    final TextStyle textStyle= _textTheme.bodySmall ?? const TextStyle();
+    final TextStyle textStyle = _textTheme.bodySmall ?? const TextStyle();
     return textStyle.copyWith(color:_colors.error);
   });
 }
diff --git a/framework/lib/src/material/list_tile.dart b/framework/lib/src/material/list_tile.dart
index d485f5f..c8af8a0 100644
--- a/framework/lib/src/material/list_tile.dart
+++ b/framework/lib/src/material/list_tile.dart
@@ -7,14 +7,18 @@
 import 'package:flute/rendering.dart';
 import 'package:flute/widgets.dart';
 
+import 'color_scheme.dart';
 import 'colors.dart';
 import 'constants.dart';
 import 'debug.dart';
 import 'divider.dart';
+import 'icon_button.dart';
+import 'icon_button_theme.dart';
 import 'ink_decoration.dart';
 import 'ink_well.dart';
 import 'list_tile_theme.dart';
 import 'material_state.dart';
+import 'text_theme.dart';
 import 'theme.dart';
 import 'theme_data.dart';
 
@@ -99,9 +103,9 @@
 ///
 /// {@tool snippet}
 /// ```dart
-/// Container(
+/// const ColoredBox(
 ///   color: Colors.green,
-///   child: const Material(
+///   child: Material(
 ///     child: ListTile(
 ///       title: Text('ListTile with red background'),
 ///       tileColor: Colors.red,
@@ -137,15 +141,29 @@
 /// ** See code in examples/api/lib/material/list_tile/list_tile.1.dart **
 /// {@end-tool}
 ///
-/// {@tool snippet}
+/// {@tool dartpad}
+/// This sample shows the creation of a [ListTile] using [ThemeData.useMaterial3] flag,
+/// as described in: https://m3.material.io/components/lists/overview.
 ///
+/// ** See code in examples/api/lib/material/list_tile/list_tile.2.dart **
+/// {@end-tool}
+///
+/// {@tool dartpad}
+/// This sample shows [ListTile]'s [textColor] and [iconColor] can use
+/// [MaterialStateColor] color to change the color of the text and icon
+/// when the [ListTile] is enabled, selected, or disabled.
+///
+/// ** See code in examples/api/lib/material/list_tile/list_tile.3.dart **
+/// {@end-tool}
+///
+/// {@tool snippet}
 /// To use a [ListTile] within a [Row], it needs to be wrapped in an
 /// [Expanded] widget. [ListTile] requires fixed width constraints,
 /// whereas a [Row] does not constrain its children.
 ///
 /// ```dart
-/// Row(
-///   children: const <Widget>[
+/// const Row(
+///   children: <Widget>[
 ///     Expanded(
 ///       child: ListTile(
 ///         leading: FlutterLogo(),
@@ -229,7 +247,7 @@
 ///
 /// ![Custom list item a](https://flutter.github.io/assets-for-api-docs/assets/widgets/custom_list_item_a.png)
 ///
-/// ** See code in examples/api/lib/material/list_tile/list_tile.4.dart **
+/// ** See code in examples/api/lib/material/list_tile/custom_list_item.0.dart **
 /// {@end-tool}
 ///
 /// {@tool dartpad}
@@ -239,7 +257,7 @@
 ///
 /// ![Custom list item b](https://flutter.github.io/assets-for-api-docs/assets/widgets/custom_list_item_b.png)
 ///
-/// ** See code in examples/api/lib/material/list_tile/list_tile.5.dart **
+/// ** See code in examples/api/lib/material/list_tile/custom_list_item.1.dart **
 /// {@end-tool}
 ///
 /// See also:
@@ -254,10 +272,10 @@
 ///  * [ListTile.divideTiles], a utility for inserting [Divider]s in between [ListTile]s.
 ///  * [CheckboxListTile], [RadioListTile], and [SwitchListTile], widgets
 ///    that combine [ListTile] with other controls.
-///  * <https://material.io/design/components/lists.html>
+///  * Material 3 [ListTile] specifications are referenced from <https://m3.material.io/components/lists/specs>
+///    and Material 2 [ListTile] specifications are referenced from <https://material.io/design/components/lists.html>
 ///  * Cookbook: [Use lists](https://flutter.dev/docs/cookbook/lists/basic-list)
 ///  * Cookbook: [Implement swipe to dismiss](https://flutter.dev/docs/cookbook/gestures/dismissible)
-// TODO(plg): Add link to m3 spec below m2 spec link when available
 class ListTile extends StatelessWidget {
   /// Creates a list tile.
   ///
@@ -278,6 +296,9 @@
     this.selectedColor,
     this.iconColor,
     this.textColor,
+    this.titleTextStyle,
+    this.subtitleTextStyle,
+    this.leadingAndTrailingTextStyle,
     this.contentPadding,
     this.enabled = true,
     this.onTap,
@@ -296,11 +317,7 @@
     this.horizontalTitleGap,
     this.minVerticalPadding,
     this.minLeadingWidth,
-  }) : assert(isThreeLine != null),
-       assert(enabled != null),
-       assert(selected != null),
-       assert(autofocus != null),
-       assert(!isThreeLine || subtitle != null);
+  }) : assert(!isThreeLine || subtitle != null);
 
   /// A widget to display before the title.
   ///
@@ -364,6 +381,8 @@
   /// If this property is null then its value is based on [ListTileTheme.dense].
   ///
   /// Dense list tiles default to a smaller height.
+  ///
+  /// It is not recommended to set [dense] to true when [ThemeData.useMaterial3] is true.
   final bool? dense;
 
   /// Defines how compact the list tile's layout will be.
@@ -402,7 +421,16 @@
 
   /// Defines the default color for [leading] and [trailing] icons.
   ///
-  /// If this property is null then [ListTileThemeData.iconColor] is used.
+  /// If this property is null and [selected] is false then [ListTileThemeData.iconColor]
+  /// is used. If that is also null and [ThemeData.useMaterial3] is true, [ColorScheme.onSurface]
+  /// is used, otherwise if [ThemeData.brightness] is [Brightness.light], [Colors.black54] is used,
+  /// and if [ThemeData.brightness] is [Brightness.dark], the value is null.
+  ///
+  /// If this property is null and [selected] is true then [ListTileThemeData.selectedColor]
+  /// is used. If that is also null then [ColorScheme.primary] is used.
+  ///
+  /// If this color is a [MaterialStateColor] it will be resolved against
+  /// [MaterialState.selected] and [MaterialState.disabled] states.
   ///
   /// See also:
   ///
@@ -410,10 +438,18 @@
   ///   [ListTileThemeData].
   final Color? iconColor;
 
-  /// Defines the default color for the [title] and [subtitle].
+  /// Defines the text color for the [title], [subtitle], [leading], and [trailing].
   ///
-  /// If this property is null then [ListTileThemeData.textColor] is used. If that
-  /// is also null then [ColorScheme.primary] is used.
+  /// If this property is null and [selected] is false then [ListTileThemeData.textColor]
+  /// is used. If that is also null then default text color is used for the [title], [subtitle]
+  /// [leading], and [trailing]. Except for [subtitle], if [ThemeData.useMaterial3] is false,
+  /// [TextTheme.bodySmall] is used.
+  ///
+  /// If this property is null and [selected] is true then [ListTileThemeData.selectedColor]
+  /// is used. If that is also null then [ColorScheme.primary] is used.
+  ///
+  /// If this color is a [MaterialStateColor] it will be resolved against
+  /// [MaterialState.selected] and [MaterialState.disabled] states.
   ///
   /// See also:
   ///
@@ -421,6 +457,28 @@
   ///   [ListTileThemeData].
   final Color? textColor;
 
+  /// The text style for ListTile's [title].
+  ///
+  /// If this property is null, then [ListTileThemeData.titleTextStyle] is used.
+  /// If that is also null and [ThemeData.useMaterial3] is true, [TextTheme.bodyLarge]
+  /// will be used. Otherwise, If ListTile style is [ListTileStyle.list],
+  /// [TextTheme.titleMedium] will be used and if ListTile style is [ListTileStyle.drawer],
+  /// [TextTheme.bodyLarge] will be used.
+  final TextStyle? titleTextStyle;
+
+  /// The text style for ListTile's [subtitle].
+  ///
+  /// If this property is null, then [ListTileThemeData.subtitleTextStyle] is used.
+  /// If that is also null, [TextTheme.bodyMedium] will be used.
+  final TextStyle? subtitleTextStyle;
+
+  /// The text style for ListTile's [leading] and [trailing].
+  ///
+  /// If this property is null, then [ListTileThemeData.leadingAndTrailingTextStyle] is used.
+  /// If that is also null and [ThemeData.useMaterial3] is true, [TextTheme.labelSmall]
+  /// will be used, otherwise [TextTheme.bodyMedium] will be used.
+  final TextStyle? leadingAndTrailingTextStyle;
+
   /// Defines the font used for the [title].
   ///
   /// If this property is null then [ListTileThemeData.style] is used. If that
@@ -512,8 +570,11 @@
   /// {@template flutter.material.ListTile.tileColor}
   /// Defines the background color of `ListTile` when [selected] is false.
   ///
-  /// When the value is null, the [tileColor] is set to [ListTileTheme.tileColor]
-  /// if it's not null and to [Colors.transparent] if it's null.
+  /// If this property is null and [selected] is false then [ListTileThemeData.tileColor]
+  /// is used. If that is also null and [selected] is true, [selectedTileColor] is used.
+  /// When that is also null, the [ListTileTheme.selectedTileColor] is used, otherwise
+  /// [Colors.transparent] is used.
+  ///
   /// {@endtemplate}
   final Color? tileColor;
 
@@ -562,7 +623,6 @@
   ///
   ///  * [Divider], which you can use to obtain this effect manually.
   static Iterable<Widget> divideTiles({ BuildContext? context, required Iterable<Widget> tiles, Color? color }) {
-    assert(tiles != null);
     assert(color != null || context != null);
     tiles = tiles.toList();
 
@@ -588,91 +648,15 @@
     ];
   }
 
-  Color? _iconColor(ThemeData theme, ListTileThemeData tileTheme) {
-    if (!enabled) {
-      return theme.disabledColor;
-    }
-
-    if (selected) {
-      return selectedColor ?? tileTheme.selectedColor ?? theme.listTileTheme.selectedColor ?? theme.colorScheme.primary;
-    }
-
-    final Color? color = iconColor
-      ?? tileTheme.iconColor
-      ?? theme.listTileTheme.iconColor
-      // If [ThemeData.useMaterial3] is set to true the disabled icon color
-      // will be set to Theme.colorScheme.onSurface(0.38), if false, defaults to null,
-      // as described in: https://m3.material.io/components/icon-buttons/specs.
-      ?? (theme.useMaterial3 ? theme.colorScheme.onSurface.withOpacity(0.38) : null);
-    if (color != null) {
-      return color;
-    }
-
-    switch (theme.brightness) {
-      case Brightness.light:
-        // For the sake of backwards compatibility, the default for unselected
-        // tiles is Colors.black45 rather than colorScheme.onSurface.withAlpha(0x73).
-        return Colors.black45;
-      case Brightness.dark:
-        return null; // null - use current icon theme color
-    }
-  }
-
-  Color? _textColor(ThemeData theme, ListTileThemeData tileTheme, Color? defaultColor) {
-    if (!enabled) {
-      return theme.disabledColor;
-    }
-
-    if (selected) {
-      return selectedColor ?? tileTheme.selectedColor ?? theme.listTileTheme.selectedColor ?? theme.colorScheme.primary;
-    }
-
-    return textColor ?? tileTheme.textColor ?? theme.listTileTheme.textColor ?? defaultColor;
-  }
-
   bool _isDenseLayout(ThemeData theme, ListTileThemeData tileTheme) {
     return dense ?? tileTheme.dense ?? theme.listTileTheme.dense ?? false;
   }
 
-  TextStyle _titleTextStyle(ThemeData theme, ListTileThemeData tileTheme) {
-    final TextStyle textStyle;
-    switch(style ?? tileTheme.style ?? theme.listTileTheme.style ?? ListTileStyle.list) {
-      case ListTileStyle.drawer:
-        textStyle = theme.useMaterial3 ? theme.textTheme.bodyMedium! : theme.textTheme.bodyLarge!;
-        break;
-      case ListTileStyle.list:
-        textStyle = theme.useMaterial3 ? theme.textTheme.titleMedium! : theme.textTheme.titleMedium!;
-        break;
-    }
-    final Color? color = _textColor(theme, tileTheme, textStyle.color);
-    return _isDenseLayout(theme, tileTheme)
-      ? textStyle.copyWith(fontSize: 13.0, color: color)
-      : textStyle.copyWith(color: color);
-  }
-
-  TextStyle _subtitleTextStyle(ThemeData theme, ListTileThemeData tileTheme) {
-    final TextStyle textStyle = theme.useMaterial3 ? theme.textTheme.bodyMedium! : theme.textTheme.bodyMedium!;
-    final Color? color = _textColor(
-      theme,
-      tileTheme,
-      theme.useMaterial3 ? theme.textTheme.bodySmall!.color : theme.textTheme.bodySmall!.color,
-    );
-    return _isDenseLayout(theme, tileTheme)
-      ? textStyle.copyWith(color: color, fontSize: 12.0)
-      : textStyle.copyWith(color: color);
-  }
-
-  TextStyle _trailingAndLeadingTextStyle(ThemeData theme, ListTileThemeData tileTheme) {
-    final TextStyle textStyle = theme.useMaterial3 ? theme.textTheme.bodyMedium! : theme.textTheme.bodyMedium!;
-    final Color? color = _textColor(theme, tileTheme, textStyle.color);
-    return textStyle.copyWith(color: color);
-  }
-
-  Color _tileBackgroundColor(ThemeData theme, ListTileThemeData tileTheme) {
+  Color _tileBackgroundColor(ThemeData theme, ListTileThemeData tileTheme, ListTileThemeData defaults) {
     final Color? color = selected
       ? selectedTileColor ?? tileTheme.selectedTileColor ?? theme.listTileTheme.selectedTileColor
       : tileColor ?? tileTheme.tileColor ?? theme.listTileTheme.tileColor;
-    return color ?? Colors.transparent;
+    return color ?? defaults.tileColor!;
   }
 
   @override
@@ -680,23 +664,66 @@
     assert(debugCheckHasMaterial(context));
     final ThemeData theme = Theme.of(context);
     final ListTileThemeData tileTheme = ListTileTheme.of(context);
-    final IconThemeData iconThemeData = IconThemeData(color: _iconColor(theme, tileTheme));
+    final ListTileStyle listTileStyle = style
+      ?? tileTheme.style
+      ?? theme.listTileTheme.style
+      ?? ListTileStyle.list;
+    final ListTileThemeData defaults = theme.useMaterial3
+        ? _LisTileDefaultsM3(context)
+        : _LisTileDefaultsM2(context, listTileStyle);
+    final Set<MaterialState> states = <MaterialState>{
+      if (!enabled) MaterialState.disabled,
+      if (selected) MaterialState.selected,
+    };
 
-    TextStyle? leadingAndTrailingTextStyle;
+    Color? resolveColor(Color? explicitColor, Color? selectedColor, Color? enabledColor, [Color? disabledColor]) {
+      return _IndividualOverrides(
+        explicitColor: explicitColor,
+        selectedColor: selectedColor,
+        enabledColor: enabledColor,
+        disabledColor: disabledColor,
+      ).resolve(states);
+    }
+
+    final Color? effectiveIconColor = resolveColor(iconColor, selectedColor, iconColor)
+      ?? resolveColor(tileTheme.iconColor, tileTheme.selectedColor, tileTheme.iconColor)
+      ?? resolveColor(theme.listTileTheme.iconColor, theme.listTileTheme.selectedColor, theme.listTileTheme.iconColor)
+      ?? resolveColor(defaults.iconColor, defaults.selectedColor, defaults.iconColor, theme.disabledColor);
+    final Color? effectiveColor = resolveColor(textColor, selectedColor, textColor)
+      ?? resolveColor(tileTheme.textColor, tileTheme.selectedColor, tileTheme.textColor)
+      ?? resolveColor(theme.listTileTheme.textColor, theme.listTileTheme.selectedColor, theme.listTileTheme.textColor)
+      ?? resolveColor(defaults.textColor, defaults.selectedColor, defaults.textColor, theme.disabledColor);
+    final IconThemeData iconThemeData = IconThemeData(color: effectiveIconColor);
+    final IconButtonThemeData iconButtonThemeData = IconButtonThemeData(
+      style: IconButton.styleFrom(foregroundColor: effectiveIconColor),
+    );
+
+    TextStyle? leadingAndTrailingStyle;
     if (leading != null || trailing != null) {
-      leadingAndTrailingTextStyle = _trailingAndLeadingTextStyle(theme, tileTheme);
+      leadingAndTrailingStyle = leadingAndTrailingTextStyle
+        ?? tileTheme.leadingAndTrailingTextStyle
+        ?? defaults.leadingAndTrailingTextStyle!;
+      final Color? leadingAndTrailingTextColor = effectiveColor;
+      leadingAndTrailingStyle = leadingAndTrailingStyle.copyWith(color: leadingAndTrailingTextColor);
     }
 
     Widget? leadingIcon;
     if (leading != null) {
       leadingIcon = AnimatedDefaultTextStyle(
-        style: leadingAndTrailingTextStyle!,
+        style: leadingAndTrailingStyle!,
         duration: kThemeChangeDuration,
         child: leading!,
       );
     }
 
-    final TextStyle titleStyle = _titleTextStyle(theme, tileTheme);
+    TextStyle titleStyle = titleTextStyle
+      ?? tileTheme.titleTextStyle
+      ?? defaults.titleTextStyle!;
+    final Color? titleColor = effectiveColor;
+    titleStyle = titleStyle.copyWith(
+      color: titleColor,
+      fontSize: _isDenseLayout(theme, tileTheme) ? 13.0 : null,
+    );
     final Widget titleText = AnimatedDefaultTextStyle(
       style: titleStyle,
       duration: kThemeChangeDuration,
@@ -706,7 +733,14 @@
     Widget? subtitleText;
     TextStyle? subtitleStyle;
     if (subtitle != null) {
-      subtitleStyle = _subtitleTextStyle(theme, tileTheme);
+      subtitleStyle = subtitleTextStyle
+        ?? tileTheme.subtitleTextStyle
+        ?? defaults.subtitleTextStyle!;
+      final Color? subtitleColor = effectiveColor ?? theme.textTheme.bodySmall!.color;
+      subtitleStyle = subtitleStyle.copyWith(
+        color: subtitleColor,
+        fontSize: _isDenseLayout(theme, tileTheme) ? 12.0 : null,
+      );
       subtitleText = AnimatedDefaultTextStyle(
         style: subtitleStyle,
         duration: kThemeChangeDuration,
@@ -717,26 +751,23 @@
     Widget? trailingIcon;
     if (trailing != null) {
       trailingIcon = AnimatedDefaultTextStyle(
-        style: leadingAndTrailingTextStyle!,
+        style: leadingAndTrailingStyle!,
         duration: kThemeChangeDuration,
         child: trailing!,
       );
     }
 
-    const EdgeInsets defaultContentPadding = EdgeInsets.symmetric(horizontal: 16.0);
     final TextDirection textDirection = Directionality.of(context);
     final EdgeInsets resolvedContentPadding = contentPadding?.resolve(textDirection)
       ?? tileTheme.contentPadding?.resolve(textDirection)
-      ?? defaultContentPadding;
-
-    final Set<MaterialState> states = <MaterialState>{
+      ?? defaults.contentPadding!.resolve(textDirection);
+    // Show basic cursor when ListTile isn't enabled or gesture callbacks are null.
+    final Set<MaterialState> mouseStates = <MaterialState>{
       if (!enabled || (onTap == null && onLongPress == null)) MaterialState.disabled,
-      if (selected) MaterialState.selected,
     };
-
-    final MouseCursor effectiveMouseCursor = MaterialStateProperty.resolveAs<MouseCursor?>(mouseCursor, states)
-      ?? tileTheme.mouseCursor?.resolve(states)
-      ?? MaterialStateMouseCursor.clickable.resolve(states);
+    final MouseCursor effectiveMouseCursor = MaterialStateProperty.resolveAs<MouseCursor?>(mouseCursor, mouseStates)
+      ?? tileTheme.mouseCursor?.resolve(mouseStates)
+      ?? MaterialStateMouseCursor.clickable.resolve(mouseStates);
 
     return InkWell(
       customBorder: shape ?? tileTheme.shape,
@@ -757,7 +788,7 @@
         child: Ink(
           decoration: ShapeDecoration(
             shape: shape ?? tileTheme.shape ?? const Border(),
-            color: _tileBackgroundColor(theme, tileTheme),
+            color: _tileBackgroundColor(theme, tileTheme, defaults),
           ),
           child: SafeArea(
             top: false,
@@ -765,24 +796,28 @@
             minimum: resolvedContentPadding,
             child: IconTheme.merge(
               data: iconThemeData,
-              child: _ListTile(
-                leading: leadingIcon,
-                title: titleText,
-                subtitle: subtitleText,
-                trailing: trailingIcon,
-                isDense: _isDenseLayout(theme, tileTheme),
-                visualDensity: visualDensity ?? tileTheme.visualDensity ?? theme.visualDensity,
-                isThreeLine: isThreeLine,
-                textDirection: textDirection,
-                titleBaselineType: titleStyle.textBaseline!,
-                subtitleBaselineType: subtitleStyle?.textBaseline,
-                horizontalTitleGap: horizontalTitleGap ?? tileTheme.horizontalTitleGap ?? 16,
-                minVerticalPadding: minVerticalPadding ?? tileTheme.minVerticalPadding ?? 4,
-                minLeadingWidth: minLeadingWidth ?? tileTheme.minLeadingWidth ?? 40,
+              child: IconButtonTheme(
+                data: iconButtonThemeData,
+                child: _ListTile(
+                  leading: leadingIcon,
+                  title: titleText,
+                  subtitle: subtitleText,
+                  trailing: trailingIcon,
+                  isDense: _isDenseLayout(theme, tileTheme),
+                  visualDensity: visualDensity ?? tileTheme.visualDensity ?? theme.visualDensity,
+                  isThreeLine: isThreeLine,
+                  textDirection: textDirection,
+                  titleBaselineType: titleStyle.textBaseline ?? defaults.titleTextStyle!.textBaseline!,
+                  subtitleBaselineType: subtitleStyle?.textBaseline ?? defaults.subtitleTextStyle!.textBaseline!,
+                  horizontalTitleGap: horizontalTitleGap ?? tileTheme.horizontalTitleGap ?? 16,
+                  minVerticalPadding: minVerticalPadding ?? tileTheme.minVerticalPadding ?? defaults.minVerticalPadding!,
+                  minLeadingWidth: minLeadingWidth ?? tileTheme.minLeadingWidth ?? defaults.minLeadingWidth!,
+                  material3: theme.useMaterial3,
+                ),
               ),
             ),
           ),
-        ),
+       ),
       ),
     );
   }
@@ -802,6 +837,9 @@
     properties.add(ColorProperty('selectedColor', selectedColor, defaultValue: null));
     properties.add(ColorProperty('iconColor', iconColor, defaultValue: null));
     properties.add(ColorProperty('textColor', textColor, defaultValue: null));
+    properties.add(DiagnosticsProperty<TextStyle>('titleTextStyle', titleTextStyle, defaultValue: null));
+    properties.add(DiagnosticsProperty<TextStyle>('subtitleTextStyle', subtitleTextStyle, defaultValue: null));
+    properties.add(DiagnosticsProperty<TextStyle>('leadingAndTrailingTextStyle', leadingAndTrailingTextStyle, defaultValue: null));
     properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('contentPadding', contentPadding, defaultValue: null));
     properties.add(FlagProperty('enabled', value: enabled, ifTrue: 'true', ifFalse: 'false', showName: true, defaultValue: true));
     properties.add(DiagnosticsProperty<Function>('onTap', onTap, defaultValue: null));
@@ -821,6 +859,34 @@
   }
 }
 
+class _IndividualOverrides extends MaterialStateProperty<Color?> {
+  _IndividualOverrides({
+    this.explicitColor,
+    this.enabledColor,
+    this.selectedColor,
+    this.disabledColor,
+  });
+
+  final Color? explicitColor;
+  final Color? enabledColor;
+  final Color? selectedColor;
+  final Color? disabledColor;
+
+  @override
+  Color? resolve(Set<MaterialState> states) {
+    if (explicitColor is MaterialStateColor) {
+      return MaterialStateProperty.resolveAs<Color?>(explicitColor, states);
+    }
+    if (states.contains(MaterialState.disabled)) {
+      return disabledColor;
+    }
+    if (states.contains(MaterialState.selected)) {
+      return selectedColor;
+    }
+    return enabledColor;
+  }
+}
+
 // Identifies the children of a _ListTileElement.
 enum _ListTileSlot {
   leading,
@@ -844,14 +910,8 @@
     required this.minVerticalPadding,
     required this.minLeadingWidth,
     this.subtitleBaselineType,
-  }) : assert(isThreeLine != null),
-       assert(isDense != null),
-       assert(visualDensity != null),
-       assert(textDirection != null),
-       assert(titleBaselineType != null),
-       assert(horizontalTitleGap != null),
-       assert(minVerticalPadding != null),
-       assert(minLeadingWidth != null);
+    required this.material3,
+  });
 
   final Widget? leading;
   final Widget title;
@@ -866,6 +926,7 @@
   final double horizontalTitleGap;
   final double minVerticalPadding;
   final double minLeadingWidth;
+  final bool material3;
 
   @override
   Iterable<_ListTileSlot> get slots => _ListTileSlot.values;
@@ -896,6 +957,7 @@
       horizontalTitleGap: horizontalTitleGap,
       minVerticalPadding: minVerticalPadding,
       minLeadingWidth: minLeadingWidth,
+      material3: material3,
     );
   }
 
@@ -910,7 +972,8 @@
       ..subtitleBaselineType = subtitleBaselineType
       ..horizontalTitleGap = horizontalTitleGap
       ..minLeadingWidth = minLeadingWidth
-      ..minVerticalPadding = minVerticalPadding;
+      ..minVerticalPadding = minVerticalPadding
+      ..material3 = material3;
   }
 }
 
@@ -925,15 +988,8 @@
     required double horizontalTitleGap,
     required double minVerticalPadding,
     required double minLeadingWidth,
-  }) : assert(isDense != null),
-       assert(visualDensity != null),
-       assert(isThreeLine != null),
-       assert(textDirection != null),
-       assert(titleBaselineType != null),
-       assert(horizontalTitleGap != null),
-       assert(minVerticalPadding != null),
-       assert(minLeadingWidth != null),
-       _isDense = isDense,
+    required bool material3,
+  }) : _isDense = isDense,
        _visualDensity = visualDensity,
        _isThreeLine = isThreeLine,
        _textDirection = textDirection,
@@ -941,7 +997,8 @@
        _subtitleBaselineType = subtitleBaselineType,
        _horizontalTitleGap = horizontalTitleGap,
        _minVerticalPadding = minVerticalPadding,
-       _minLeadingWidth = minLeadingWidth;
+       _minLeadingWidth = minLeadingWidth,
+       _material3 = material3;
 
   RenderBox? get leading => childForSlot(_ListTileSlot.leading);
   RenderBox? get title => childForSlot(_ListTileSlot.title);
@@ -966,7 +1023,6 @@
   bool get isDense => _isDense;
   bool _isDense;
   set isDense(bool value) {
-    assert(value != null);
     if (_isDense == value) {
       return;
     }
@@ -977,7 +1033,6 @@
   VisualDensity get visualDensity => _visualDensity;
   VisualDensity _visualDensity;
   set visualDensity(VisualDensity value) {
-    assert(value != null);
     if (_visualDensity == value) {
       return;
     }
@@ -988,7 +1043,6 @@
   bool get isThreeLine => _isThreeLine;
   bool _isThreeLine;
   set isThreeLine(bool value) {
-    assert(value != null);
     if (_isThreeLine == value) {
       return;
     }
@@ -999,7 +1053,6 @@
   TextDirection get textDirection => _textDirection;
   TextDirection _textDirection;
   set textDirection(TextDirection value) {
-    assert(value != null);
     if (_textDirection == value) {
       return;
     }
@@ -1010,7 +1063,6 @@
   TextBaseline get titleBaselineType => _titleBaselineType;
   TextBaseline _titleBaselineType;
   set titleBaselineType(TextBaseline value) {
-    assert(value != null);
     if (_titleBaselineType == value) {
       return;
     }
@@ -1033,7 +1085,6 @@
   double get _effectiveHorizontalTitleGap => _horizontalTitleGap + visualDensity.horizontal * 2.0;
 
   set horizontalTitleGap(double value) {
-    assert(value != null);
     if (_horizontalTitleGap == value) {
       return;
     }
@@ -1045,7 +1096,6 @@
   double _minVerticalPadding;
 
   set minVerticalPadding(double value) {
-    assert(value != null);
     if (_minVerticalPadding == value) {
       return;
     }
@@ -1057,7 +1107,6 @@
   double _minLeadingWidth;
 
   set minLeadingWidth(double value) {
-    assert(value != null);
     if (_minLeadingWidth == value) {
       return;
     }
@@ -1065,6 +1114,16 @@
     markNeedsLayout();
   }
 
+  bool get material3 => _material3;
+  bool _material3;
+  set material3(bool value) {
+    if (_material3 == value) {
+      return;
+    }
+    _material3 = value;
+    markNeedsLayout();
+  }
+
   @override
   bool get sizedByParent => false;
 
@@ -1253,23 +1312,33 @@
       }
     }
 
-    // This attempts to implement the redlines for the vertical position of the
-    // leading and trailing icons on the spec page:
-    //   https://material.io/design/components/lists.html#specs
-    // The interpretation for these redlines is as follows:
-    //  - For large tiles (> 72dp), both leading and trailing controls should be
-    //    a fixed distance from top. As per guidelines this is set to 16dp.
-    //  - For smaller tiles, trailing should always be centered. Leading can be
-    //    centered or closer to the top. It should never be further than 16dp
-    //    to the top.
     final double leadingY;
     final double trailingY;
-    if (tileHeight > 72.0) {
-      leadingY = 16.0;
-      trailingY = 16.0;
+    if (material3) {
+      if (isThreeLine) {
+        leadingY = _minVerticalPadding;
+        trailingY = _minVerticalPadding;
+      } else {
+        leadingY = (tileHeight - leadingSize.height) / 2.0;
+        trailingY = (tileHeight - trailingSize.height) / 2.0;
+      }
     } else {
-      leadingY = math.min((tileHeight - leadingSize.height) / 2.0, 16.0);
-      trailingY = (tileHeight - trailingSize.height) / 2.0;
+      // This attempts to implement the redlines for the vertical position of the
+      // leading and trailing icons on the spec page:
+      //   https://material.io/design/components/lists.html#specs
+      // The interpretation for these redlines is as follows:
+      //  - For large tiles (> 72dp), both leading and trailing controls should be
+      //    a fixed distance from top. As per guidelines this is set to 16dp.
+      //  - For smaller tiles, trailing should always be centered. Leading can be
+      //    centered or closer to the top. It should never be further than 16dp
+      //    to the top.
+      if (tileHeight > 72.0) {
+        leadingY = 16.0;
+        trailingY = 16.0;
+      } else {
+        leadingY = math.min((tileHeight - leadingSize.height) / 2.0, 16.0);
+        trailingY = (tileHeight - trailingSize.height) / 2.0;
+      }
     }
 
     switch (textDirection) {
@@ -1325,7 +1394,6 @@
 
   @override
   bool hitTestChildren(BoxHitTestResult result, { required Offset position }) {
-    assert(position != null);
     for (final RenderBox child in children) {
       final BoxParentData parentData = child.parentData! as BoxParentData;
       final bool isHit = result.addWithPaintOffset(
@@ -1343,3 +1411,96 @@
     return false;
   }
 }
+
+class _LisTileDefaultsM2 extends ListTileThemeData {
+  _LisTileDefaultsM2(this.context, ListTileStyle style)
+    : super(
+        contentPadding: const EdgeInsets.symmetric(horizontal: 16.0),
+        minLeadingWidth: 40,
+        minVerticalPadding: 4,
+        shape: const Border(),
+        style: style,
+      );
+
+  final BuildContext context;
+  late final ThemeData _theme = Theme.of(context);
+  late final TextTheme _textTheme = _theme.textTheme;
+
+  @override
+  Color? get tileColor =>  Colors.transparent;
+
+  @override
+  TextStyle? get titleTextStyle {
+    switch (style!) {
+      case ListTileStyle.drawer:
+        return _textTheme.bodyLarge;
+      case ListTileStyle.list:
+        return _textTheme.titleMedium;
+    }
+  }
+
+  @override
+  TextStyle? get subtitleTextStyle => _textTheme.bodyMedium;
+
+  @override
+  TextStyle? get leadingAndTrailingTextStyle => _textTheme.bodyMedium;
+
+  @override
+  Color? get selectedColor => _theme.colorScheme.primary;
+
+  @override
+  Color? get iconColor {
+    switch (_theme.brightness) {
+      case Brightness.light:
+        // For the sake of backwards compatibility, the default for unselected
+        // tiles is Colors.black45 rather than colorScheme.onSurface.withAlpha(0x73).
+        return Colors.black45;
+      case Brightness.dark:
+        return null; // null, Use current icon theme color
+    }
+  }
+}
+
+// BEGIN GENERATED TOKEN PROPERTIES - LisTile
+
+// Do not edit by hand. The code between the "BEGIN GENERATED" and
+// "END GENERATED" comments are generated from data in the Material
+// Design token database by the script:
+//   dev/tools/gen_defaults/bin/gen_defaults.dart.
+
+// Token database version: v0_158
+
+class _LisTileDefaultsM3 extends ListTileThemeData {
+  _LisTileDefaultsM3(this.context)
+    : super(
+        contentPadding: const EdgeInsetsDirectional.only(start: 16.0, end: 24.0),
+        minLeadingWidth: 24,
+        minVerticalPadding: 8,
+        shape: const RoundedRectangleBorder(),
+      );
+
+  final BuildContext context;
+  late final ThemeData _theme = Theme.of(context);
+  late final ColorScheme _colors = _theme.colorScheme;
+  late final TextTheme _textTheme = _theme.textTheme;
+
+  @override
+  Color? get tileColor =>  Colors.transparent;
+
+  @override
+  TextStyle? get titleTextStyle => _textTheme.bodyLarge;
+
+  @override
+  TextStyle? get subtitleTextStyle => _textTheme.bodyMedium;
+
+  @override
+  TextStyle? get leadingAndTrailingTextStyle => _textTheme.labelSmall;
+
+  @override
+  Color? get selectedColor => _colors.primary;
+
+  @override
+  Color? get iconColor => _colors.onSurface;
+}
+
+// END GENERATED TOKEN PROPERTIES - LisTile
diff --git a/framework/lib/src/material/list_tile_theme.dart b/framework/lib/src/material/list_tile_theme.dart
index 0b4983d..9f3aee6 100644
--- a/framework/lib/src/material/list_tile_theme.dart
+++ b/framework/lib/src/material/list_tile_theme.dart
@@ -51,6 +51,9 @@
     this.selectedColor,
     this.iconColor,
     this.textColor,
+    this.titleTextStyle,
+    this.subtitleTextStyle,
+    this.leadingAndTrailingTextStyle,
     this.contentPadding,
     this.tileColor,
     this.selectedTileColor,
@@ -80,6 +83,15 @@
   /// Overrides the default value of [ListTile.textColor].
   final Color? textColor;
 
+  /// Overrides the default value of [ListTile.titleTextStyle].
+  final TextStyle? titleTextStyle;
+
+  /// Overrides the default value of [ListTile.subtitleTextStyle].
+  final TextStyle? subtitleTextStyle;
+
+  /// Overrides the default value of [ListTile.leadingAndTrailingTextStyle].
+  final TextStyle? leadingAndTrailingTextStyle;
+
   /// Overrides the default value of [ListTile.contentPadding].
   final EdgeInsetsGeometry? contentPadding;
 
@@ -116,6 +128,9 @@
     Color? selectedColor,
     Color? iconColor,
     Color? textColor,
+    TextStyle? titleTextStyle,
+    TextStyle? subtitleTextStyle,
+    TextStyle? leadingAndTrailingTextStyle,
     EdgeInsetsGeometry? contentPadding,
     Color? tileColor,
     Color? selectedTileColor,
@@ -134,6 +149,9 @@
       selectedColor: selectedColor ?? this.selectedColor,
       iconColor: iconColor ?? this.iconColor,
       textColor: textColor ?? this.textColor,
+      titleTextStyle: titleTextStyle ?? this.titleTextStyle,
+      subtitleTextStyle: subtitleTextStyle ?? this.subtitleTextStyle,
+      leadingAndTrailingTextStyle: leadingAndTrailingTextStyle ?? this.leadingAndTrailingTextStyle,
       contentPadding: contentPadding ?? this.contentPadding,
       tileColor: tileColor ?? this.tileColor,
       selectedTileColor: selectedTileColor ?? this.selectedTileColor,
@@ -148,7 +166,6 @@
 
   /// Linearly interpolate between ListTileThemeData objects.
   static ListTileThemeData? lerp(ListTileThemeData? a, ListTileThemeData? b, double t) {
-    assert (t != null);
     if (a == null && b == null) {
       return null;
     }
@@ -159,6 +176,9 @@
       selectedColor: Color.lerp(a?.selectedColor, b?.selectedColor, t),
       iconColor: Color.lerp(a?.iconColor, b?.iconColor, t),
       textColor: Color.lerp(a?.textColor, b?.textColor, t),
+      titleTextStyle: TextStyle.lerp(a?.titleTextStyle, b?.titleTextStyle, t),
+      subtitleTextStyle: TextStyle.lerp(a?.subtitleTextStyle, b?.subtitleTextStyle, t),
+      leadingAndTrailingTextStyle: TextStyle.lerp(a?.leadingAndTrailingTextStyle, b?.leadingAndTrailingTextStyle, t),
       contentPadding: EdgeInsetsGeometry.lerp(a?.contentPadding, b?.contentPadding, t),
       tileColor: Color.lerp(a?.tileColor, b?.tileColor, t),
       selectedTileColor: Color.lerp(a?.selectedTileColor, b?.selectedTileColor, t),
@@ -179,6 +199,9 @@
     selectedColor,
     iconColor,
     textColor,
+    titleTextStyle,
+    subtitleTextStyle,
+    leadingAndTrailingTextStyle,
     contentPadding,
     tileColor,
     selectedTileColor,
@@ -204,6 +227,9 @@
       && other.style == style
       && other.selectedColor == selectedColor
       && other.iconColor == iconColor
+      && other.titleTextStyle == titleTextStyle
+      && other.subtitleTextStyle == subtitleTextStyle
+      && other.leadingAndTrailingTextStyle == leadingAndTrailingTextStyle
       && other.textColor == textColor
       && other.contentPadding == contentPadding
       && other.tileColor == tileColor
@@ -225,6 +251,9 @@
     properties.add(ColorProperty('selectedColor', selectedColor, defaultValue: null));
     properties.add(ColorProperty('iconColor', iconColor, defaultValue: null));
     properties.add(ColorProperty('textColor', textColor, defaultValue: null));
+    properties.add(DiagnosticsProperty<TextStyle>('titleTextStyle', titleTextStyle, defaultValue: null));
+    properties.add(DiagnosticsProperty<TextStyle>('subtitleTextStyle', subtitleTextStyle, defaultValue: null));
+    properties.add(DiagnosticsProperty<TextStyle>('leadingAndTrailingTextStyle', leadingAndTrailingTextStyle, defaultValue: null));
     properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('contentPadding', contentPadding, defaultValue: null));
     properties.add(ColorProperty('tileColor', tileColor, defaultValue: null));
     properties.add(ColorProperty('selectedTileColor', selectedTileColor, defaultValue: null));
@@ -450,7 +479,6 @@
     double? minLeadingWidth,
     required Widget child,
   }) {
-    assert(child != null);
     return Builder(
       builder: (BuildContext context) {
         final ListTileThemeData parent = ListTileTheme.of(context);
diff --git a/framework/lib/src/material/magnifier.dart b/framework/lib/src/material/magnifier.dart
index 9880c94..075d40e 100644
--- a/framework/lib/src/material/magnifier.dart
+++ b/framework/lib/src/material/magnifier.dart
@@ -134,7 +134,7 @@
   void _determineMagnifierPositionAndFocalPoint() {
     final MagnifierInfo selectionInfo =
         widget.magnifierInfo.value;
-    final Rect screenRect = Offset.zero & MediaQuery.of(context).size;
+    final Rect screenRect = Offset.zero & MediaQuery.sizeOf(context);
 
     // Since by default we draw at the top left corner, this offset
     // shifts the magnifier so we draw at the center, and then also includes
diff --git a/framework/lib/src/material/material.dart b/framework/lib/src/material/material.dart
index 54fd5a0..74e7cdc 100644
--- a/framework/lib/src/material/material.dart
+++ b/framework/lib/src/material/material.dart
@@ -194,13 +194,9 @@
     this.clipBehavior = Clip.none,
     this.animationDuration = kThemeChangeDuration,
     this.child,
-  }) : assert(type != null),
-       assert(elevation != null && elevation >= 0.0),
+  }) : assert(elevation >= 0.0),
        assert(!(shape != null && borderRadius != null)),
-       assert(animationDuration != null),
-       assert(!(identical(type, MaterialType.circle) && (borderRadius != null || shape != null))),
-       assert(borderOnForeground != null),
-       assert(clipBehavior != null);
+       assert(!(identical(type, MaterialType.circle) && (borderRadius != null || shape != null)));
 
   /// The widget below this widget in the tree.
   ///
@@ -258,6 +254,7 @@
 
   /// The color to paint the shadow below the material.
   ///
+  /// {@template flutter.material.material.shadowColor}
   /// If null and [ThemeData.useMaterial3] is true then [ThemeData]'s
   /// [ColorScheme.shadow] will be used. If [ThemeData.useMaterial3] is false
   /// then [ThemeData.shadowColor] will be used.
@@ -270,11 +267,13 @@
   ///    property if it is null.
   ///  * [ThemeData.applyElevationOverlayColor], which turns elevation overlay
   /// on or off for dark themes.
+  /// {@endtemplate}
   final Color? shadowColor;
 
   /// The color of the surface tint overlay applied to the material color
   /// to indicate elevation.
   ///
+  /// {@template flutter.material.material.surfaceTintColor}
   /// Material Design 3 introduced a new way for some components to indicate
   /// their elevation by using a surface tint color overlay on top of the
   /// base material [color]. This overlay is painted with an opacity that is
@@ -295,6 +294,7 @@
   ///     tint.
   ///   * https://m3.material.io/styles/color/the-color-system/color-roles
   ///     which specifies how the overlay is applied.
+  /// {@endtemplate}
   final Color? surfaceTintColor;
 
   /// The typographical style to use for text within this material.
@@ -302,11 +302,13 @@
 
   /// Defines the material's shape as well its shadow.
   ///
+  /// {@template flutter.material.material.shape}
   /// If shape is non null, the [borderRadius] is ignored and the material's
   /// clip boundary and shadow are defined by the shape.
   ///
   /// A shadow is only displayed if the [elevation] is greater than
   /// zero.
+  /// {@endtemplate}
   final ShapeBorder? shape;
 
   /// Whether to paint the [shape] border in front of the [child].
@@ -343,7 +345,7 @@
   final BorderRadiusGeometry? borderRadius;
 
   /// The ink controller from the closest instance of this class that
-  /// encloses the given context.
+  /// encloses the given context within the closest [LookupBoundary].
   ///
   /// Typical usage is as follows:
   ///
@@ -358,11 +360,11 @@
   /// * [Material.of], which is similar to this method, but asserts if
   ///   no [Material] ancestor is found.
   static MaterialInkController? maybeOf(BuildContext context) {
-    return context.findAncestorRenderObjectOfType<_RenderInkFeatures>();
+    return LookupBoundary.findAncestorRenderObjectOfType<_RenderInkFeatures>(context);
   }
 
   /// The ink controller from the closest instance of [Material] that encloses
-  /// the given context.
+  /// the given context within the closest [LookupBoundary].
   ///
   /// If no [Material] widget ancestor can be found then this method will assert
   /// in debug mode, and throw an exception in release mode.
@@ -383,6 +385,16 @@
     final MaterialInkController? controller = maybeOf(context);
     assert(() {
       if (controller == null) {
+        if (LookupBoundary.debugIsHidingAncestorRenderObjectOfType<_RenderInkFeatures>(context)) {
+          throw FlutterError(
+            'Material.of() was called with a context that does not have access to a Material widget.\n'
+            'The context provided to Material.of() does have a Material widget ancestor, but it is '
+            'hidden by a LookupBoundary. This can happen because you are using a widget that looks '
+            'for a Material ancestor, but no such ancestor exists within the closest LookupBoundary.\n'
+            'The context used was:\n'
+            '  $context',
+          );
+        }
         throw FlutterError(
           'Material.of() was called with a context that does not contain a Material widget.\n'
           'No Material widget ancestor could be found starting from the context that was passed to '
@@ -447,7 +459,7 @@
     final Color? backgroundColor = _getBackgroundColor(context);
     final Color modelShadowColor = widget.shadowColor ?? (theme.useMaterial3 ? theme.colorScheme.shadow : theme.shadowColor);
     // If no shadow color is specified, use 0 for elevation in the model so a drop shadow won't be painted.
-    final double modelElevation = modelShadowColor != null ? widget.elevation : 0;
+    final double modelElevation = widget.elevation;
     assert(
       backgroundColor != null || widget.type == MaterialType.transparency,
       'If Material type is not MaterialType.transparency, a color must '
@@ -587,8 +599,7 @@
     required this.vsync,
     required this.absorbHitTest,
     this.color,
-  }) : assert(vsync != null),
-       super(child);
+  }) : super(child);
 
   // This class should exist in a 1:1 relationship with a MaterialState object,
   // since there's no current support for dynamically changing the ticker
@@ -700,9 +711,7 @@
     required MaterialInkController controller,
     required this.referenceBox,
     this.onRemoved,
-  }) : assert(controller != null),
-       assert(referenceBox != null),
-       _controller = controller as _RenderInkFeatures;
+  }) : _controller = controller as _RenderInkFeatures;
 
   /// The [MaterialInkController] associated with this [InkFeature].
   ///
@@ -807,11 +816,7 @@
     required this.surfaceTintColor,
     super.curve,
     required super.duration,
-  }) : assert(child != null),
-       assert(shape != null),
-       assert(clipBehavior != null),
-       assert(elevation != null && elevation >= 0.0),
-       assert(color != null);
+  }) : assert(elevation >= 0.0);
 
   /// The widget below this widget in the tree.
   ///
diff --git a/framework/lib/src/material/material_button.dart b/framework/lib/src/material/material_button.dart
index c0f6580..2e4bc15 100644
--- a/framework/lib/src/material/material_button.dart
+++ b/framework/lib/src/material/material_button.dart
@@ -82,9 +82,7 @@
     this.height,
     this.enableFeedback = true,
     this.child,
-  }) : assert(clipBehavior != null),
-       assert(autofocus != null),
-       assert(elevation == null || elevation >= 0.0),
+  }) : assert(elevation == null || elevation >= 0.0),
        assert(focusElevation == null || focusElevation >= 0.0),
        assert(hoverElevation == null || hoverElevation >= 0.0),
        assert(highlightElevation == null || highlightElevation >= 0.0),
diff --git a/framework/lib/src/material/material_localizations.dart b/framework/lib/src/material/material_localizations.dart
index 98f07e4..d6f2b4d 100644
--- a/framework/lib/src/material/material_localizations.dart
+++ b/framework/lib/src/material/material_localizations.dart
@@ -160,6 +160,19 @@
   /// as a hint text in the text field.
   String get searchFieldLabel;
 
+  /// Label indicating that a given date is the current date.
+  String get currentDateLabel;
+
+  /// Label for the scrim rendered underneath the content of a modal route.
+  String get scrimLabel;
+
+  /// Label for a BottomSheet.
+  String get bottomSheetLabel;
+
+  /// Hint text announced when tapping on the scrim underneath the content of
+  /// a modal route.
+  String scrimOnTapHint(String modalRouteContentName);
+
   /// The format used to lay out the time picker.
   ///
   /// The documentation for [TimeOfDayFormat] enum values provides details on
@@ -580,6 +593,9 @@
   /// The shortcut label for the keyboard key [LogicalKeyboardKey.select].
   String get keyboardKeySelect;
 
+  /// The shortcut label for the keyboard key [LogicalKeyboardKey.shift].
+  String get keyboardKeyShift;
+
   /// The shortcut label for the keyboard key [LogicalKeyboardKey.space].
   String get keyboardKeySpace;
 
@@ -1016,6 +1032,18 @@
   String get searchFieldLabel => 'Search';
 
   @override
+  String get currentDateLabel => 'Today';
+
+  @override
+  String get scrimLabel => 'Scrim';
+
+  @override
+  String get bottomSheetLabel => 'Bottom Sheet';
+
+  @override
+  String scrimOnTapHint(String modalRouteContentName) => 'Close $modalRouteContentName';
+
+  @override
   String aboutListTileTitle(String applicationName) => 'About $applicationName';
 
   @override
@@ -1316,5 +1344,8 @@
   String get keyboardKeySelect => 'Select';
 
   @override
+  String get keyboardKeyShift => 'Shift';
+
+  @override
   String get keyboardKeySpace => 'Space';
 }
diff --git a/framework/lib/src/material/material_state.dart b/framework/lib/src/material/material_state.dart
index e33b063..16357a0 100644
--- a/framework/lib/src/material/material_state.dart
+++ b/framework/lib/src/material/material_state.dart
@@ -409,7 +409,6 @@
   OutlinedBorder? resolve(Set<MaterialState> states);
 }
 
-
 /// Defines a [TextStyle] that is also a [MaterialStateProperty].
 ///
 /// This class exists to enable widgets with [TextStyle] valued properties
@@ -603,6 +602,8 @@
 /// on a widget's interactive "state", which is defined as a set
 /// of [MaterialState]s.
 ///
+/// {@youtube 560 315 https://www.youtube.com/watch?v=CylXr3AF3uU}
+///
 /// Material state properties represent values that depend on a widget's material
 /// "state". The state is encoded as a set of [MaterialState] values, like
 /// [MaterialState.focused], [MaterialState.hovered], [MaterialState.pressed]. For
diff --git a/framework/lib/src/material/material_state_mixin.dart b/framework/lib/src/material/material_state_mixin.dart
index 6363a0b..740f333 100644
--- a/framework/lib/src/material/material_state_mixin.dart
+++ b/framework/lib/src/material/material_state_mixin.dart
@@ -38,7 +38,7 @@
 ///   Widget build(BuildContext context) {
 ///     return InkWell(
 ///       onFocusChange: updateMaterialState(MaterialState.focused),
-///       child: Container(
+///       child: ColoredBox(
 ///         color: widget.color.resolve(materialStates),
 ///         child: widget.child,
 ///       ),
@@ -88,7 +88,7 @@
   /// class MyWidgetState extends State<MyWidget> with MaterialStateMixin<MyWidget> {
   ///   @override
   ///   Widget build(BuildContext context) {
-  ///     return Container(
+  ///     return ColoredBox(
   ///       color: isPressed ? Colors.black : Colors.white,
   ///       child: InkWell(
   ///         onHighlightChanged: updateMaterialState(
diff --git a/framework/lib/src/material/menu_anchor.dart b/framework/lib/src/material/menu_anchor.dart
index faced68..d47d624 100644
--- a/framework/lib/src/material/menu_anchor.dart
+++ b/framework/lib/src/material/menu_anchor.dart
@@ -44,11 +44,11 @@
 // has a submenu.
 const double _kDefaultSubmenuIconSize = 24;
 
-// The default spacing between the the leading icon, label, trailing icon, and
+// The default spacing between the leading icon, label, trailing icon, and
 // shortcut label in a _MenuItemLabel.
 const double _kLabelItemDefaultSpacing = 12;
 
-// The minimum spacing between the the leading icon, label, trailing icon, and
+// The minimum spacing between the leading icon, label, trailing icon, and
 // shortcut label in a _MenuItemLabel.
 const double _kLabelItemMinSpacing = 4;
 
@@ -315,7 +315,7 @@
     _position?.isScrollingNotifier.removeListener(_handleScroll);
     _position = Scrollable.maybeOf(context)?.position;
     _position?.isScrollingNotifier.addListener(_handleScroll);
-    final Size newSize = MediaQuery.of(context).size;
+    final Size newSize = MediaQuery.sizeOf(context);
     if (_viewSize != null && newSize != _viewSize) {
       // Close the menus if the view changes size.
       _root._close();
@@ -362,7 +362,7 @@
       );
     }
 
-    return _MenuAnchorMarker(
+    return _MenuAnchorScope(
       anchorKey: _anchorKey,
       anchor: this,
       isOpen: _isOpen,
@@ -511,7 +511,7 @@
                 // Copy all the themes from the supplied outer context to the
                 // overlay.
                 outerContext,
-                _MenuAnchorMarker(
+                _MenuAnchorScope(
                   // Re-advertize the anchor here in the overlay, since
                   // otherwise a search for the anchor by descendants won't find
                   // it.
@@ -571,7 +571,7 @@
   // dependency relationship that will rebuild the context when the node
   // changes.
   static _MenuAnchorState? _maybeOf(BuildContext context) {
-    return context.dependOnInheritedWidgetOfExactType<_MenuAnchorMarker>()?.anchor;
+    return context.dependOnInheritedWidgetOfExactType<_MenuAnchorScope>()?.anchor;
   }
 }
 
@@ -700,10 +700,10 @@
 ///   platform instead of by Flutter (on macOS, for example).
 /// * [ShortcutRegistry], a registry of shortcuts that apply for the entire
 ///   application.
-/// * [VoidCallbackIntent] to define intents that will call a [VoidCallback] and
+/// * [VoidCallbackIntent], to define intents that will call a [VoidCallback] and
 ///   work with the [Actions] and [Shortcuts] system.
-/// * [CallbackShortcuts] to define shortcuts that simply call a callback and
-///   don't involve using [Actions].
+/// * [CallbackShortcuts], to define shortcuts that call a callback without
+///   involving [Actions].
 class MenuBar extends StatelessWidget {
   /// Creates a const [MenuBar].
   ///
@@ -789,10 +789,10 @@
 ///   platform instead of by Flutter (on macOS, for example).
 /// * [ShortcutRegistry], a registry of shortcuts that apply for the entire
 ///   application.
-/// * [VoidCallbackIntent] to define intents that will call a [VoidCallback] and
+/// * [VoidCallbackIntent], to define intents that will call a [VoidCallback] and
 ///   work with the [Actions] and [Shortcuts] system.
-/// * [CallbackShortcuts] to define shortcuts that simply call a callback and
-///   don't involve using [Actions].
+/// * [CallbackShortcuts] to define shortcuts that call a callback without
+///   involving [Actions].
 class MenuItemButton extends StatefulWidget {
   /// Creates a const [MenuItemButton].
   ///
@@ -1796,21 +1796,23 @@
 
   @override
   Widget build(BuildContext context) {
-    final Offset menuPaddingOffset;
+    Offset menuPaddingOffset = widget.alignmentOffset ?? Offset.zero;
     final EdgeInsets menuPadding = _computeMenuPadding(context);
-    switch (_anchor?._root._orientation ?? Axis.vertical) {
+    // Move the submenu over by the size of the menu padding, so that
+    // the first menu item aligns with the submenu button that opens it.
+    switch (_anchor?._orientation ?? Axis.vertical) {
       case Axis.horizontal:
         switch (Directionality.of(context)) {
           case TextDirection.rtl:
-            menuPaddingOffset = widget.alignmentOffset ?? Offset(-menuPadding.right, 0);
+            menuPaddingOffset += Offset(menuPadding.right, 0);
             break;
           case TextDirection.ltr:
-            menuPaddingOffset = widget.alignmentOffset ?? Offset(-menuPadding.left, 0);
+            menuPaddingOffset += Offset(-menuPadding.left, 0);
             break;
         }
         break;
       case Axis.vertical:
-        menuPaddingOffset = widget.alignmentOffset ?? Offset(0, -menuPadding.top);
+        menuPaddingOffset += Offset(0, -menuPadding.top);
         break;
     }
 
@@ -1900,24 +1902,13 @@
   }
 
   EdgeInsets _computeMenuPadding(BuildContext context) {
-    final MenuStyle? themeStyle = MenuTheme.of(context).style;
-    final MenuStyle defaultStyle = _MenuDefaultsM3(context);
-
-    T? effectiveValue<T>(T? Function(MenuStyle? style) getProperty) {
-      return getProperty(widget.menuStyle) ?? getProperty(themeStyle) ?? getProperty(defaultStyle);
-    }
-
-    T? resolve<T>(MaterialStateProperty<T>? Function(MenuStyle? style) getProperty) {
-      return effectiveValue(
-        (MenuStyle? style) {
-          return getProperty(style)?.resolve(widget.statesController?.value ?? const <MaterialState>{});
-        },
-      );
-    }
-
-    return resolve<EdgeInsetsGeometry?>(
-          (MenuStyle? style) => style?.padding,
-        )?.resolve(Directionality.of(context)) ?? EdgeInsets.zero;
+    final MaterialStateProperty<EdgeInsetsGeometry?> insets =
+      widget.menuStyle?.padding ??
+      MenuTheme.of(context).style?.padding ??
+      _MenuDefaultsM3(context).padding!;
+    return insets
+      .resolve(widget.statesController?.value ?? const <MaterialState>{})!
+      .resolve(Directionality.of(context));
   }
 
   void _handleFocusChange() {
@@ -1983,9 +1974,6 @@
     LogicalKeyboardKey.arrowUp: '↑',
     LogicalKeyboardKey.arrowDown: '↓',
     LogicalKeyboardKey.enter: '↵',
-    LogicalKeyboardKey.shift: '⇧',
-    LogicalKeyboardKey.shiftLeft: '⇧',
-    LogicalKeyboardKey.shiftRight: '⇧',
   };
 
   static final Set<LogicalKeyboardKey> _modifiers = <LogicalKeyboardKey>{
@@ -2016,15 +2004,32 @@
   /// Returns the label to be shown to the user in the UI when a
   /// [MenuSerializableShortcut] is used as a keyboard shortcut.
   ///
-  /// To keep the representation short, this will return graphical key
-  /// representations when it can. For instance, the default
-  /// [LogicalKeyboardKey.shift] will return '⇧', and the arrow keys will return
-  /// arrows. When [defaultTargetPlatform] is [TargetPlatform.macOS] or
-  /// [TargetPlatform.iOS], the key [LogicalKeyboardKey.meta] will show as '⌘',
-  /// [LogicalKeyboardKey.control] will show as 'Ë„', and
-  /// [LogicalKeyboardKey.alt] will show as '⌥'.
+  /// When [defaultTargetPlatform] is [TargetPlatform.macOS] or
+  /// [TargetPlatform.iOS], this will return graphical key representations when
+  /// it can. For instance, the default [LogicalKeyboardKey.shift] will return
+  /// '⇧', and the arrow keys will return arrows. The key
+  /// [LogicalKeyboardKey.meta] will show as '⌘', [LogicalKeyboardKey.control]
+  /// will show as '˄', and [LogicalKeyboardKey.alt] will show as '⌥'.
+  ///
+  /// The keys are joined by spaces on macOS and iOS, and by "+" on other
+  /// platforms.
   String getShortcutLabel(MenuSerializableShortcut shortcut, MaterialLocalizations localizations) {
     final ShortcutSerialization serialized = shortcut.serializeForMenu();
+    final String keySeparator;
+    switch (defaultTargetPlatform) {
+      case TargetPlatform.iOS:
+      case TargetPlatform.macOS:
+        // Use "⌃ ⇧ A" style on macOS and iOS.
+        keySeparator = ' ';
+        break;
+      case TargetPlatform.android:
+      case TargetPlatform.fuchsia:
+      case TargetPlatform.linux:
+      case TargetPlatform.windows:
+        // Use "Ctrl+Shift+A" style.
+        keySeparator = '+';
+        break;
+    }
     if (serialized.trigger != null) {
       final List<String> modifiers = <String>[];
       final LogicalKeyboardKey trigger = serialized.trigger!;
@@ -2060,7 +2065,7 @@
       return <String>[
         ...modifiers,
         if (shortcutTrigger != null && shortcutTrigger.isNotEmpty) shortcutTrigger,
-      ].join(' ');
+      ].join(keySeparator);
     } else if (serialized.character != null) {
       return serialized.character!;
     }
@@ -2175,14 +2180,23 @@
     if (modifier == LogicalKeyboardKey.shift ||
         modifier == LogicalKeyboardKey.shiftLeft ||
         modifier == LogicalKeyboardKey.shiftRight) {
-      return _shortcutGraphicEquivalents[LogicalKeyboardKey.shift]!;
+      switch (defaultTargetPlatform) {
+        case TargetPlatform.android:
+        case TargetPlatform.fuchsia:
+        case TargetPlatform.linux:
+        case TargetPlatform.windows:
+          return localizations.keyboardKeyShift;
+        case TargetPlatform.iOS:
+        case TargetPlatform.macOS:
+          return '⇧';
+      }
     }
     throw ArgumentError('Keyboard key ${modifier.keyLabel} is not a modifier.');
   }
 }
 
-class _MenuAnchorMarker extends InheritedWidget {
-  const _MenuAnchorMarker({
+class _MenuAnchorScope extends InheritedWidget {
+  const _MenuAnchorScope({
     required super.child,
     required this.anchorKey,
     required this.anchor,
@@ -2194,7 +2208,7 @@
   final bool isOpen;
 
   @override
-  bool updateShouldNotify(_MenuAnchorMarker oldWidget) {
+  bool updateShouldNotify(_MenuAnchorScope oldWidget) {
     return anchorKey != oldWidget.anchorKey
         || anchor != oldWidget.anchor
         || isOpen != oldWidget.isOpen;
@@ -2905,7 +2919,6 @@
     // 4) Is part of an anchor that either doesn't have a submenu, or doesn't
     //    have any submenus currently open (only the "deepest" open menu should
     //    have accelerator shortcuts registered).
-    assert(_displayLabel != null);
     if (_showAccelerators && _acceleratorIndex != -1 && _binding?.onInvoke != null && !(_binding!.hasSubmenu && (_anchor?._isOpen ?? false))) {
       final String acceleratorCharacter = _displayLabel[_acceleratorIndex].toLowerCase();
       _shortcutRegistryEntry = _shortcutRegistry?.addAll(
@@ -3190,14 +3203,15 @@
 
   @override
   bool shouldRelayout(_MenuLayout oldDelegate) {
-    return anchorRect != oldDelegate.anchorRect ||
-        textDirection != oldDelegate.textDirection ||
-        alignment != oldDelegate.alignment ||
-        alignmentOffset != oldDelegate.alignmentOffset ||
-        menuPosition != oldDelegate.menuPosition ||
-        orientation != oldDelegate.orientation ||
-        parentOrientation != oldDelegate.parentOrientation ||
-        !setEquals(avoidBounds, oldDelegate.avoidBounds);
+    return anchorRect != oldDelegate.anchorRect
+        || textDirection != oldDelegate.textDirection
+        || alignment != oldDelegate.alignment
+        || alignmentOffset != oldDelegate.alignmentOffset
+        || menuPosition != oldDelegate.menuPosition
+        || menuPadding != oldDelegate.menuPadding
+        || orientation != oldDelegate.orientation
+        || parentOrientation != oldDelegate.parentOrientation
+        || !setEquals(avoidBounds, oldDelegate.avoidBounds);
   }
 
   Rect _closestScreen(Iterable<Rect> screens, Offset point) {
@@ -3301,7 +3315,7 @@
     final double dy = densityAdjustment.dy;
     final double dx = math.max(0, densityAdjustment.dx);
     final EdgeInsetsGeometry resolvedPadding = padding
-        .add(EdgeInsets.fromLTRB(dx, dy, dx, dy))
+        .add(EdgeInsets.symmetric(horizontal: dx, vertical: dy))
         .clamp(EdgeInsets.zero, EdgeInsetsGeometry.infinity); // ignore_clamp_double_lint
 
     BoxConstraints effectiveConstraints = visualDensity.effectiveConstraints(
@@ -3430,7 +3444,7 @@
     );
 
     final VisualDensity visualDensity =
-        effectiveValue((MenuStyle? style) => style?.visualDensity) ?? VisualDensity.standard;
+        effectiveValue((MenuStyle? style) => style?.visualDensity) ?? Theme.of(context).visualDensity;
     final AlignmentGeometry alignment = effectiveValue((MenuStyle? style) => style?.alignment)!;
     final BuildContext anchorContext = anchor._anchorKey.currentContext!;
     final RenderBox overlay = Overlay.of(anchorContext).context.findRenderObject()! as RenderBox;
@@ -3571,17 +3585,18 @@
 // Design token database by the script:
 //   dev/tools/gen_defaults/bin/gen_defaults.dart.
 
-// Token database version: v0_143
+// Token database version: v0_158
 
 class _MenuBarDefaultsM3 extends MenuStyle {
   _MenuBarDefaultsM3(this.context)
-      : super(
-          elevation: const MaterialStatePropertyAll<double?>(3.0),
-          shape: const MaterialStatePropertyAll<OutlinedBorder>(_defaultMenuBorder),
-          alignment: AlignmentDirectional.bottomStart,
-        );
+    : super(
+      elevation: const MaterialStatePropertyAll<double?>(3.0),
+      shape: const MaterialStatePropertyAll<OutlinedBorder>(_defaultMenuBorder),
+      alignment: AlignmentDirectional.bottomStart,
+    );
+
   static const RoundedRectangleBorder _defaultMenuBorder =
-      RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(4.0)));
+    RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(4.0)));
 
   final BuildContext context;
 
@@ -3617,11 +3632,12 @@
 
 class _MenuButtonDefaultsM3 extends ButtonStyle {
   _MenuButtonDefaultsM3(this.context)
-      : super(
-          animationDuration: kThemeChangeDuration,
-          enableFeedback: true,
-          alignment: AlignmentDirectional.centerStart,
-        );
+    : super(
+      animationDuration: kThemeChangeDuration,
+      enableFeedback: true,
+      alignment: AlignmentDirectional.centerStart,
+    );
+
   final BuildContext context;
 
   late final ColorScheme _colors = Theme.of(context).colorScheme;
@@ -3752,20 +3768,21 @@
       const EdgeInsets.symmetric(horizontal: 12),
       const EdgeInsets.symmetric(horizontal: 8),
       const EdgeInsets.symmetric(horizontal: 4),
-      MediaQuery.maybeOf(context)?.textScaleFactor ?? 1,
+      MediaQuery.maybeTextScaleFactorOf(context) ?? 1,
     );
   }
 }
 
 class _MenuDefaultsM3 extends MenuStyle {
   _MenuDefaultsM3(this.context)
-      : super(
-          elevation: const MaterialStatePropertyAll<double?>(3.0),
-          shape: const MaterialStatePropertyAll<OutlinedBorder>(_defaultMenuBorder),
-          alignment: AlignmentDirectional.topEnd,
-        );
+    : super(
+      elevation: const MaterialStatePropertyAll<double?>(3.0),
+      shape: const MaterialStatePropertyAll<OutlinedBorder>(_defaultMenuBorder),
+      alignment: AlignmentDirectional.topEnd,
+    );
+
   static const RoundedRectangleBorder _defaultMenuBorder =
-      RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(4.0)));
+    RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(4.0)));
 
   final BuildContext context;
 
diff --git a/framework/lib/src/material/menu_bar_theme.dart b/framework/lib/src/material/menu_bar_theme.dart
index c4b266a..ffda944 100644
--- a/framework/lib/src/material/menu_bar_theme.dart
+++ b/framework/lib/src/material/menu_bar_theme.dart
@@ -74,7 +74,7 @@
     super.key,
     required this.data,
     required super.child,
-  }) : assert(data != null);
+  });
 
   /// The properties to set for [MenuBar] in this widget's descendants.
   final MenuBarThemeData data;
diff --git a/framework/lib/src/material/menu_button_theme.dart b/framework/lib/src/material/menu_button_theme.dart
index 9af32d0..6eef7a4 100644
--- a/framework/lib/src/material/menu_button_theme.dart
+++ b/framework/lib/src/material/menu_button_theme.dart
@@ -106,7 +106,7 @@
     super.key,
     required this.data,
     required super.child,
-  }) : assert(data != null);
+  });
 
   /// The configuration of this theme.
   final MenuButtonThemeData data;
diff --git a/framework/lib/src/material/menu_style.dart b/framework/lib/src/material/menu_style.dart
index 62c30f7..fa0dc89 100644
--- a/framework/lib/src/material/menu_style.dart
+++ b/framework/lib/src/material/menu_style.dart
@@ -304,7 +304,6 @@
 
   /// Linearly interpolate between two [MenuStyle]s.
   static MenuStyle? lerp(MenuStyle? a, MenuStyle? b, double t) {
-    assert (t != null);
     if (a == null && b == null) {
       return null;
     }
diff --git a/framework/lib/src/material/menu_theme.dart b/framework/lib/src/material/menu_theme.dart
index bb2213c..685ce48 100644
--- a/framework/lib/src/material/menu_theme.dart
+++ b/framework/lib/src/material/menu_theme.dart
@@ -91,7 +91,7 @@
     super.key,
     required this.data,
     required super.child,
-  }) : assert(data != null);
+  });
 
   /// The properties for [MenuBar] and [MenuItemButton] in this widget's
   /// descendants.
diff --git a/framework/lib/src/material/mergeable_material.dart b/framework/lib/src/material/mergeable_material.dart
index f69988e..87b71d8 100644
--- a/framework/lib/src/material/mergeable_material.dart
+++ b/framework/lib/src/material/mergeable_material.dart
@@ -21,7 +21,7 @@
   /// const constructors so that they can be used in const expressions.
   ///
   /// The argument is the [key], which must not be null.
-  const MergeableMaterialItem(this.key) : assert(key != null);
+  const MergeableMaterialItem(this.key);
 
   /// The key for this item of the list.
   ///
@@ -42,8 +42,7 @@
     required LocalKey key,
     required this.child,
     this.color,
-  }) : assert(key != null),
-       super(key);
+  }) : super(key);
 
   /// The contents of this slice.
   ///
@@ -69,8 +68,7 @@
   const MaterialGap({
     required LocalKey key,
     this.size = 16.0,
-  }) : assert(key != null),
-       super(key);
+  }) : super(key);
 
   /// The main axis extent of this gap. For example, if the [MergeableMaterial]
   /// is vertical, then this is the height of the gap.
@@ -657,7 +655,7 @@
 }
 
 class _MergeableMaterialListBody extends ListBody {
-  _MergeableMaterialListBody({
+  const _MergeableMaterialListBody({
     required super.children,
     super.mainAxis,
     required this.items,
diff --git a/framework/lib/src/material/navigation_bar.dart b/framework/lib/src/material/navigation_bar.dart
index bede640..c9bfa99 100644
--- a/framework/lib/src/material/navigation_bar.dart
+++ b/framework/lib/src/material/navigation_bar.dart
@@ -17,8 +17,8 @@
 import 'theme.dart';
 import 'tooltip.dart';
 
-const double _kIndicatorHeight = 64;
-const double _kIndicatorWidth = 32;
+const double _kIndicatorHeight = 32;
+const double _kIndicatorWidth = 64;
 
 // Examples can assume:
 // late BuildContext context;
@@ -26,6 +26,8 @@
 
 /// Material 3 Navigation Bar component.
 ///
+/// {@youtube 560 315 https://www.youtube.com/watch?v=DVGYddFaLv0}
+///
 /// Navigation bars offer a persistent and convenient way to switch between
 /// primary destinations in an app.
 ///
@@ -53,6 +55,14 @@
 /// {@end-tool}
 ///
 /// {@tool dartpad}
+/// This example showcases [NavigationBar] label behaviors. When tapping on one
+/// of the label behavior options, the [labelBehavior] of the [NavigationBar]
+/// will be updated.
+///
+/// ** See code in examples/api/lib/material/navigation_bar/navigation_bar.1.dart **
+/// {@end-tool}
+///
+/// {@tool dartpad}
 /// This example shows a [NavigationBar] as it is used within a [Scaffold]
 /// widget when there are nested navigators that provide local navigation. The
 /// [NavigationBar] has four [NavigationDestination] widgets with different
@@ -60,7 +70,7 @@
 /// item's index and displays a corresponding page with its own local navigator
 /// in the body of a [Scaffold].
 ///
-/// ** See code in examples/api/lib/material/navigation_bar/navigation_bar.1.dart **
+/// ** See code in examples/api/lib/material/navigation_bar/navigation_bar.2.dart **
 /// {@end-tool}
 /// See also:
 ///
@@ -85,9 +95,11 @@
     this.elevation,
     this.shadowColor,
     this.surfaceTintColor,
+    this.indicatorColor,
+    this.indicatorShape,
     this.height,
     this.labelBehavior,
-  }) :  assert(destinations != null && destinations.length >= 2),
+  }) :  assert(destinations.length >= 2),
         assert(0 <= selectedIndex && selectedIndex < destinations.length);
 
   /// Determines the transition time for each destination as it goes between
@@ -150,6 +162,20 @@
   /// overlay is applied.
   final Color? surfaceTintColor;
 
+  /// The color of the [indicatorShape] when this destination is selected.
+  ///
+  /// If null, [NavigationBarThemeData.indicatorColor] is used. If that
+  /// is also null and [ThemeData.useMaterial3] is true, [ColorScheme.secondaryContainer]
+  /// is used. Otherwise, [ColorScheme.secondary] with an opacity of 0.24 is used.
+  final Color? indicatorColor;
+
+  /// The shape of the selected inidicator.
+  ///
+  /// If null, [NavigationBarThemeData.indicatorShape] is used. If that
+  /// is also null and [ThemeData.useMaterial3] is true, [StadiumBorder] is used.
+  /// Otherwise, [RoundedRectangleBorder] with a circular border radius of 16 is used.
+  final ShapeBorder? indicatorShape;
+
   /// The height of the [NavigationBar] itself.
   ///
   /// If this is used in [Scaffold.bottomNavigationBar] and the scaffold is
@@ -212,9 +238,12 @@
                     builder: (BuildContext context, Animation<double> animation) {
                       return _NavigationDestinationInfo(
                         index: i,
+                        selectedIndex: selectedIndex,
                         totalNumberOfDestinations: destinations.length,
                         selectedAnimation: animation,
                         labelBehavior: effectiveLabelBehavior,
+                        indicatorColor: indicatorColor,
+                        indicatorShape: indicatorShape,
                         onTap: _handleTap(i),
                         child: destinations[i],
                       );
@@ -307,12 +336,13 @@
 
   @override
   Widget build(BuildContext context) {
+    final _NavigationDestinationInfo info = _NavigationDestinationInfo.of(context);
     const Set<MaterialState> selectedState = <MaterialState>{MaterialState.selected};
     const Set<MaterialState> unselectedState = <MaterialState>{};
 
     final NavigationBarThemeData navigationBarTheme = NavigationBarTheme.of(context);
     final NavigationBarThemeData defaults = _defaultsFor(context);
-    final Animation<double> animation = _NavigationDestinationInfo.of(context).selectedAnimation;
+    final Animation<double> animation = info.selectedAnimation;
 
     return _NavigationDestinationBuilder(
       label: label,
@@ -334,8 +364,8 @@
           children: <Widget>[
             NavigationIndicator(
               animation: animation,
-              color: navigationBarTheme.indicatorColor ?? defaults.indicatorColor!,
-              shape: navigationBarTheme.indicatorShape ?? defaults.indicatorShape!
+              color: info.indicatorColor ?? navigationBarTheme.indicatorColor ?? defaults.indicatorColor!,
+              shape: info.indicatorShape ?? navigationBarTheme.indicatorShape ?? defaults.indicatorShape!
             ),
             _StatusTransitionWidgetBuilder(
               animation: animation,
@@ -434,11 +464,17 @@
     final _NavigationDestinationInfo info = _NavigationDestinationInfo.of(context);
     final NavigationBarThemeData navigationBarTheme = NavigationBarTheme.of(context);
     final NavigationBarThemeData defaults = _defaultsFor(context);
+    final GlobalKey labelKey = GlobalKey();
 
+    final bool selected = info.selectedIndex == info.index;
     return _NavigationBarDestinationSemantics(
       child: _NavigationBarDestinationTooltip(
         message: tooltip ?? label,
         child: _IndicatorInkWell(
+          key: UniqueKey(),
+          labelKey: labelKey,
+          labelBehavior: info.labelBehavior,
+          selected: selected,
           customBorder: navigationBarTheme.indicatorShape ?? defaults.indicatorShape,
           onTap: info.onTap,
           child: Row(
@@ -446,6 +482,7 @@
               Expanded(
                 child: _NavigationBarDestinationLayout(
                   icon: buildIcon(context),
+                  labelKey: labelKey,
                   label: buildLabel(context),
                 ),
               ),
@@ -459,24 +496,46 @@
 
 class _IndicatorInkWell extends InkResponse {
   const _IndicatorInkWell({
-    super.child,
-    super.onTap,
+    super.key,
+    required this.labelKey,
+    required this.labelBehavior,
+    required this.selected,
     super.customBorder,
+    super.onTap,
+    super.child,
   }) : super(
     containedInkWell: true,
     highlightColor: Colors.transparent,
   );
 
+  final GlobalKey labelKey;
+  final NavigationDestinationLabelBehavior labelBehavior;
+  final bool selected;
+
   @override
   RectCallback? getRectCallback(RenderBox referenceBox) {
+    final RenderBox labelBox = labelKey.currentContext!.findRenderObject()! as RenderBox;
+    final Rect labelRect = labelBox.localToGlobal(Offset.zero) & labelBox.size;
+    final double labelPadding;
+    switch (labelBehavior) {
+      case NavigationDestinationLabelBehavior.alwaysShow:
+        labelPadding = labelRect.height / 2;
+        break;
+      case NavigationDestinationLabelBehavior.onlyShowSelected:
+        labelPadding = selected ? labelRect.height / 2 : 0;
+        break;
+      case NavigationDestinationLabelBehavior.alwaysHide:
+        labelPadding = 0;
+        break;
+    }
     final double indicatorOffsetX = referenceBox.size.width / 2;
-    const double indicatorOffsetY = 30.0;
+    final double indicatorOffsetY = referenceBox.size.height / 2 - labelPadding;
 
     return () {
       return Rect.fromCenter(
         center: Offset(indicatorOffsetX, indicatorOffsetY),
-        width: _kIndicatorHeight,
-        height: _kIndicatorWidth,
+        width: _kIndicatorWidth,
+        height: _kIndicatorHeight,
       );
     };
   }
@@ -492,9 +551,12 @@
   /// [child] and descendants.
   const _NavigationDestinationInfo({
     required this.index,
+    required this.selectedIndex,
     required this.totalNumberOfDestinations,
     required this.selectedAnimation,
     required this.labelBehavior,
+    required this.indicatorColor,
+    required this.indicatorShape,
     required this.onTap,
     required super.child,
   });
@@ -529,7 +591,13 @@
   /// "Tab 1 of 3", for example.
   final int index;
 
-  /// How many total destinations are are in this navigation bar.
+  /// This is the index of the currently selected destination.
+  ///
+  /// This is required for `_IndicatorInkWell` to apply label padding to ripple animations
+  /// when label behavior is [NavigationDestinationLabelBehavior.onlyShowSelected].
+  final int selectedIndex;
+
+  /// How many total destinations are in this navigation bar.
   ///
   /// This is required for semantics, so that each destination can have a label
   /// "Tab 1 of 4", for example.
@@ -545,6 +613,16 @@
   /// label, or hide all labels.
   final NavigationDestinationLabelBehavior labelBehavior;
 
+  /// The color of the selection indicator.
+  ///
+  /// This is used by destinations to override the indicator color.
+  final Color? indicatorColor;
+
+  /// The shape of the selection indicator.
+  ///
+  /// This is used by destinations to override the indicator shape.
+  final ShapeBorder? indicatorShape;
+
   /// The callback that should be called when this destination is tapped.
   ///
   /// This is computed by calling [NavigationBar.onDestinationSelected]
@@ -593,8 +671,8 @@
     super.key,
     required this.animation,
     this.color,
-    this.width = _kIndicatorHeight,
-    this.height = _kIndicatorWidth,
+    this.width = _kIndicatorWidth,
+    this.height = _kIndicatorHeight,
     this.borderRadius = const BorderRadius.all(Radius.circular(16)),
     this.shape,
   });
@@ -699,6 +777,7 @@
   /// 3 [NavigationBar].
   const _NavigationBarDestinationLayout({
     required this.icon,
+    required this.labelKey,
     required this.label,
   });
 
@@ -707,6 +786,11 @@
   /// See [NavigationDestination.icon].
   final Widget icon;
 
+  /// The global key for the label of this destination.
+  ///
+  /// This is used to determine the position of the label relative to the icon.
+  final GlobalKey labelKey;
+
   /// The label widget that sits below the icon.
   ///
   /// This widget will sometimes be faded out, depending on
@@ -716,7 +800,6 @@
   final Widget label;
 
   static final Key _iconKey = UniqueKey();
-  static final Key _labelKey = UniqueKey();
 
   @override
   Widget build(BuildContext context) {
@@ -740,7 +823,7 @@
                 alwaysIncludeSemantics: true,
                 opacity: animation,
                 child: RepaintBoundary(
-                  key: _labelKey,
+                  key: labelKey,
                   child: label,
                 ),
               ),
@@ -862,9 +945,6 @@
 
   @override
   Widget build(BuildContext context) {
-    if (message == null) {
-      return child;
-    }
     return Tooltip(
       message: message,
       // TODO(johnsonmh): Make this value configurable/themable.
@@ -994,7 +1074,7 @@
   Widget build(BuildContext context) {
     return MediaQuery(
       data: MediaQuery.of(context).copyWith(
-        textScaleFactor: clampDouble(MediaQuery.of(context).textScaleFactor,
+        textScaleFactor: clampDouble(MediaQuery.textScaleFactorOf(context),
           0.0,
           upperLimit,
         ),
@@ -1288,7 +1368,7 @@
 // Design token database by the script:
 //   dev/tools/gen_defaults/bin/gen_defaults.dart.
 
-// Token database version: v0_143
+// Token database version: v0_158
 
 class _NavigationBarDefaultsM3 extends NavigationBarThemeData {
   _NavigationBarDefaultsM3(this.context)
diff --git a/framework/lib/src/material/navigation_bar_theme.dart b/framework/lib/src/material/navigation_bar_theme.dart
index dbf0593..9fc4b8b 100644
--- a/framework/lib/src/material/navigation_bar_theme.dart
+++ b/framework/lib/src/material/navigation_bar_theme.dart
@@ -125,7 +125,6 @@
   ///
   /// {@macro dart.ui.shadow.lerp}
   static NavigationBarThemeData? lerp(NavigationBarThemeData? a, NavigationBarThemeData? b, double t) {
-    assert(t != null);
     if (a == null && b == null) {
       return null;
     }
@@ -213,7 +212,7 @@
     super.key,
     required this.data,
     required super.child,
-  }) : assert(data != null);
+  });
 
   /// Specifies the background color, label text style, icon theme, and label
   /// type values for descendant [NavigationBar] widgets.
diff --git a/framework/lib/src/material/navigation_drawer.dart b/framework/lib/src/material/navigation_drawer.dart
index d21d39d..39c06fb 100644
--- a/framework/lib/src/material/navigation_drawer.dart
+++ b/framework/lib/src/material/navigation_drawer.dart
@@ -57,6 +57,8 @@
     this.shadowColor,
     this.surfaceTintColor,
     this.elevation,
+    this.indicatorColor,
+    this.indicatorShape,
     this.onDestinationSelected,
     this.selectedIndex = 0,
   });
@@ -90,6 +92,18 @@
   /// is also null, it will be 1.0.
   final double? elevation;
 
+  /// The color of the [indicatorShape] when this destination is selected.
+  ///
+  /// If this is null, [NavigationDrawerThemeData.indicatorColor] is used.
+  /// If that is also null, defaults to [ColorScheme.secondaryContainer].
+  final Color? indicatorColor;
+
+  /// The shape of the selected inidicator.
+  ///
+  /// If this is null, [NavigationDrawerThemeData.indicatorShape] is used.
+  /// If that is also null, defaults to [StadiumBorder].
+  final ShapeBorder? indicatorShape;
+
   /// Defines the appearance of the items within the navigation drawer.
   ///
   /// The list contains [NavigationDrawerDestination] widgets and/or customized
@@ -125,6 +139,8 @@
             index: index,
             totalNumberOfDestinations: totalNumberOfDestinations,
             selectedAnimation: animation,
+            indicatorColor: indicatorColor,
+            indicatorShape: indicatorShape,
             onTap: () {
               if (onDestinationSelected != null) {
                 onDestinationSelected!(index);
@@ -148,9 +164,11 @@
       shadowColor: shadowColor,
       surfaceTintColor: surfaceTintColor,
       elevation: elevation,
-      child: Column(
-        crossAxisAlignment: CrossAxisAlignment.start,
-        children: wrappedChildren,
+      child: SafeArea(
+        bottom: false,
+        child: ListView(
+          children: wrappedChildren,
+        ),
       ),
     );
   }
@@ -315,9 +333,9 @@
               alignment: Alignment.center,
               children: <Widget>[
                 NavigationIndicator(
-                  animation: _NavigationDrawerDestinationInfo.of(context).selectedAnimation,
-                  color: navigationDrawerTheme.indicatorColor ?? defaults.indicatorColor!,
-                  shape: navigationDrawerTheme.indicatorShape ?? defaults.indicatorShape!,
+                  animation: info.selectedAnimation,
+                  color: info.indicatorColor ?? navigationDrawerTheme.indicatorColor ?? defaults.indicatorColor!,
+                  shape: info.indicatorShape ?? navigationDrawerTheme.indicatorShape ?? defaults.indicatorShape!,
                   width: (navigationDrawerTheme.indicatorSize ?? defaults.indicatorSize!).width,
                   height: (navigationDrawerTheme.indicatorSize ?? defaults.indicatorSize!).height,
                 ),
@@ -433,6 +451,8 @@
     required this.index,
     required this.totalNumberOfDestinations,
     required this.selectedAnimation,
+    required this.indicatorColor,
+    required this.indicatorShape,
     required this.onTap,
     required super.child,
   });
@@ -468,7 +488,7 @@
   /// "Tab 1 of 3", for example.
   final int index;
 
-  /// How many total destinations are are in this navigation drawer.
+  /// How many total destinations are in this navigation drawer.
   ///
   /// This is required for semantics, so that each destination can have a label
   /// "Tab 1 of 4", for example.
@@ -478,6 +498,16 @@
   /// to 1 (selected).
   final Animation<double> selectedAnimation;
 
+  /// The color of the indicator.
+  ///
+  /// This is used by destinations to override the indicator color.
+  final Color? indicatorColor;
+
+  /// The shape of the indicator.
+  ///
+  /// This is used by destinations to override the indicator shape.
+  final ShapeBorder? indicatorShape;
+
   /// The callback that should be called when this destination is tapped.
   ///
   /// This is computed by calling [NavigationDrawer.onDestinationSelected]
@@ -628,7 +658,7 @@
 // Design token database by the script:
 //   dev/tools/gen_defaults/bin/gen_defaults.dart.
 
-// Token database version: v0_143
+// Token database version: v0_158
 
 class _NavigationDrawerDefaultsM3 extends NavigationDrawerThemeData {
   const _NavigationDrawerDefaultsM3(this.context)
diff --git a/framework/lib/src/material/navigation_drawer_theme.dart b/framework/lib/src/material/navigation_drawer_theme.dart
index e132a4c..1153a47 100644
--- a/framework/lib/src/material/navigation_drawer_theme.dart
+++ b/framework/lib/src/material/navigation_drawer_theme.dart
@@ -126,7 +126,6 @@
   /// {@macro dart.ui.shadow.lerp}
   static NavigationDrawerThemeData? lerp(
       NavigationDrawerThemeData? a, NavigationDrawerThemeData? b, double t) {
-    assert(t != null);
     if (a == null && b == null) {
       return null;
     }
@@ -227,7 +226,7 @@
     super.key,
     required this.data,
     required super.child,
-  }) : assert(data != null);
+  });
 
   /// Specifies the background color, label text style, icon theme, and label
   /// type values for descendant [NavigationDrawer] widgets.
diff --git a/framework/lib/src/material/navigation_rail.dart b/framework/lib/src/material/navigation_rail.dart
index 73b84a7..27d244c 100644
--- a/framework/lib/src/material/navigation_rail.dart
+++ b/framework/lib/src/material/navigation_rail.dart
@@ -16,6 +16,7 @@
 import 'theme.dart';
 
 const double _kCircularIndicatorDiameter = 56;
+const double _kIndicatorHeight = 32;
 
 /// A Material Design widget that is meant to be displayed at the left or right of an
 /// app to navigate between a small number of views, typically between three and
@@ -110,13 +111,13 @@
     this.minExtendedWidth,
     this.useIndicator,
     this.indicatorColor,
-  }) :  assert(destinations != null && destinations.length >= 2),
+    this.indicatorShape,
+  }) :  assert(destinations.length >= 2),
         assert(selectedIndex == null || (0 <= selectedIndex && selectedIndex < destinations.length)),
         assert(elevation == null || elevation > 0),
         assert(minWidth == null || minWidth > 0),
         assert(minExtendedWidth == null || minExtendedWidth > 0),
         assert((minWidth == null || minExtendedWidth == null) || minExtendedWidth >= minWidth),
-        assert(extended != null),
         assert(!extended || (labelType == null || labelType == NavigationRailLabelType.none));
 
   /// Sets the color of the Container that holds all of the [NavigationRail]'s
@@ -306,8 +307,18 @@
 
   /// Overrides the default value of [NavigationRail]'s selection indicator color,
   /// when [useIndicator] is true.
+  ///
+  /// If this is null, [NavigationRailThemeData.indicatorColor] is used. If
+  /// that is null, defaults to [ColorScheme.secondaryContainer].
   final Color? indicatorColor;
 
+  /// Overrides the default value of [NavigationRail]'s selection indicator shape,
+  /// when [useIndicator] is true.
+  ///
+  /// If this is null, [NavigationRailThemeData.indicatorShape] is used. If
+  /// that is null, defaults to [StadiumBorder].
+  final ShapeBorder? indicatorShape;
+
   /// Returns the animation that controls the [NavigationRail.extended] state.
   ///
   /// This can be used to synchronize animations in the [leading] or [trailing]
@@ -396,7 +407,7 @@
     final NavigationRailLabelType labelType = widget.labelType ?? navigationRailTheme.labelType ?? defaults.labelType!;
     final bool useIndicator = widget.useIndicator ?? navigationRailTheme.useIndicator ?? defaults.useIndicator!;
     final Color? indicatorColor = widget.indicatorColor ?? navigationRailTheme.indicatorColor ?? defaults.indicatorColor;
-    final ShapeBorder? indicatorShape = navigationRailTheme.indicatorShape ?? defaults.indicatorShape;
+    final ShapeBorder? indicatorShape = widget.indicatorShape ?? navigationRailTheme.indicatorShape ?? defaults.indicatorShape;
 
     // For backwards compatibility, in M2 the opacity of the unselected icons needs
     // to be set to the default if it isn't in the given theme. This can be removed
@@ -534,19 +545,7 @@
     required this.useIndicator,
     this.indicatorColor,
     this.indicatorShape,
-  }) : assert(minWidth != null),
-       assert(minExtendedWidth != null),
-       assert(icon != null),
-       assert(label != null),
-       assert(destinationAnimation != null),
-       assert(extendedTransitionAnimation != null),
-       assert(labelType != null),
-       assert(selected != null),
-       assert(iconTheme != null),
-       assert(labelTextStyle != null),
-       assert(onTap != null),
-       assert(indexLabel != null),
-       _positionAnimation = CurvedAnimation(
+  }) : _positionAnimation = CurvedAnimation(
           parent: ReverseAnimation(destinationAnimation),
           curve: Curves.easeInOut,
           reverseCurve: Curves.easeInOut.flipped,
@@ -579,7 +578,8 @@
     );
 
     final bool material3 = Theme.of(context).useMaterial3;
-    final double indicatorInkOffsetY;
+    final EdgeInsets destionationPadding = (padding ?? EdgeInsets.zero).resolve(Directionality.of(context));
+    Offset indicatorOffset;
 
     final Widget themedIcon = IconTheme(
       data: iconTheme,
@@ -596,8 +596,10 @@
       case NavigationRailLabelType.none:
         // Split the destination spacing across the top and bottom to keep the icon centered.
         final Widget? spacing = material3 ? const SizedBox(height: _verticalDestinationSpacingM3 / 2) : null;
-        indicatorInkOffsetY = _verticalDestinationPaddingNoLabel - (_verticalIconLabelSpacingM3 / 2);
-
+        indicatorOffset = Offset(
+          minWidth / 2 + destionationPadding.left,
+          _verticalDestinationSpacingM3 / 2 + destionationPadding.top,
+        );
         final Widget iconPart = Column(
           children: <Widget>[
             if (spacing != null) spacing,
@@ -625,9 +627,7 @@
               children: <Widget>[
                 iconPart,
                 // For semantics when label is not showing,
-                SizedBox(
-                  width: 0,
-                  height: 0,
+                SizedBox.shrink(
                   child: Visibility.maintain(
                     visible: false,
                     child: label,
@@ -675,8 +675,12 @@
         final Widget topSpacing = SizedBox(height: material3 ? 0 : verticalPadding);
         final Widget labelSpacing = SizedBox(height: material3 ? lerpDouble(0, _verticalIconLabelSpacingM3, appearingAnimationValue)! : 0);
         final Widget bottomSpacing = SizedBox(height: material3 ? _verticalDestinationSpacingM3 : verticalPadding);
-        indicatorInkOffsetY = _verticalDestinationPaddingWithLabel;
-
+        final double indicatorHorizontalPadding = (destionationPadding.left / 2) - (destionationPadding.right / 2);
+        final double indicatorVerticalPadding = destionationPadding.top;
+        indicatorOffset = Offset(minWidth / 2 + indicatorHorizontalPadding, indicatorVerticalPadding);
+        if (minWidth < _NavigationRailDefaultsM2(context).minWidth!) {
+          indicatorOffset = Offset(minWidth / 2 + _horizontalDestinationSpacingM3, indicatorVerticalPadding);
+        }
         content = Container(
           constraints: BoxConstraints(
             minWidth: minWidth,
@@ -719,7 +723,12 @@
         final Widget topSpacing = SizedBox(height: material3 ? 0 : _verticalDestinationPaddingWithLabel);
         final Widget labelSpacing = SizedBox(height: material3 ? _verticalIconLabelSpacingM3 : 0);
         final Widget bottomSpacing = SizedBox(height: material3 ? _verticalDestinationSpacingM3 : _verticalDestinationPaddingWithLabel);
-        indicatorInkOffsetY = _verticalDestinationPaddingWithLabel;
+        final double indicatorHorizontalPadding = (destionationPadding.left / 2) - (destionationPadding.right / 2);
+        final double indicatorVerticalPadding = destionationPadding.top;
+        indicatorOffset = Offset(minWidth / 2 + indicatorHorizontalPadding, indicatorVerticalPadding);
+        if (minWidth < _NavigationRailDefaultsM2(context).minWidth!) {
+          indicatorOffset = Offset(minWidth / 2 + _horizontalDestinationSpacingM3, indicatorVerticalPadding);
+        }
         content = Container(
           constraints: BoxConstraints(
             minWidth: minWidth,
@@ -761,7 +770,7 @@
               splashColor: colors.primary.withOpacity(0.12),
               hoverColor: colors.primary.withOpacity(0.04),
               useMaterial3: material3,
-              indicatorOffsetY: indicatorInkOffsetY,
+              indicatorOffset: indicatorOffset,
               child: content,
             ),
           ),
@@ -783,7 +792,7 @@
     super.splashColor,
     super.hoverColor,
     required this.useMaterial3,
-    required this.indicatorOffsetY,
+    required this.indicatorOffset,
   }) : super(
     containedInkWell: true,
     highlightShape: BoxShape.rectangle,
@@ -792,18 +801,17 @@
   );
 
   final bool useMaterial3;
-  final double indicatorOffsetY;
+  final Offset indicatorOffset;
 
   @override
   RectCallback? getRectCallback(RenderBox referenceBox) {
-    final double indicatorOffsetX = referenceBox.size.width / 2;
-
     if (useMaterial3) {
       return () {
-        return Rect.fromCenter(
-          center: Offset(indicatorOffsetX, indicatorOffsetY),
-          width: _kCircularIndicatorDiameter,
-          height: 32,
+        return Rect.fromLTWH(
+          indicatorOffset.dx - (_kCircularIndicatorDiameter / 2),
+          indicatorOffset.dy,
+          _kCircularIndicatorDiameter,
+          _kIndicatorHeight,
         );
       };
     }
@@ -900,11 +908,11 @@
   const NavigationRailDestination({
     required this.icon,
     Widget? selectedIcon,
+    this.indicatorColor,
+    this.indicatorShape,
     required this.label,
     this.padding,
-  }) : selectedIcon = selectedIcon ?? icon,
-       assert(icon != null),
-       assert(label != null);
+  }) : selectedIcon = selectedIcon ?? icon;
 
   /// The icon of the destination.
   ///
@@ -933,6 +941,12 @@
   ///    icons.
   final Widget selectedIcon;
 
+  /// The color of the [indicatorShape] when this destination is selected.
+  final Color? indicatorColor;
+
+  /// The shape of the selection inidicator.
+  final ShapeBorder? indicatorShape;
+
   /// The label for the destination.
   ///
   /// The label must be provided when used with the [NavigationRail]. When the
@@ -949,7 +963,7 @@
   const _ExtendedNavigationRailAnimation({
     required this.animation,
     required super.child,
-  }) : assert(child != null);
+  });
 
   final Animation<double> animation;
 
@@ -965,6 +979,7 @@
 const Widget _verticalSpacer = SizedBox(height: 8.0);
 const double _verticalIconLabelSpacingM3 = 4.0;
 const double _verticalDestinationSpacingM3 = 12.0;
+const double _horizontalDestinationSpacingM3 = 12.0;
 
 // Hand coded defaults based on Material Design 2.
 class _NavigationRailDefaultsM2 extends NavigationRailThemeData {
@@ -1017,7 +1032,7 @@
 // Design token database by the script:
 //   dev/tools/gen_defaults/bin/gen_defaults.dart.
 
-// Token database version: v0_143
+// Token database version: v0_158
 
 class _NavigationRailDefaultsM3 extends NavigationRailThemeData {
   _NavigationRailDefaultsM3(this.context)
diff --git a/framework/lib/src/material/navigation_rail_theme.dart b/framework/lib/src/material/navigation_rail_theme.dart
index 6056e94..72780de 100644
--- a/framework/lib/src/material/navigation_rail_theme.dart
+++ b/framework/lib/src/material/navigation_rail_theme.dart
@@ -143,7 +143,6 @@
   ///
   /// {@macro dart.ui.shadow.lerp}
   static NavigationRailThemeData? lerp(NavigationRailThemeData? a, NavigationRailThemeData? b, double t) {
-    assert(t != null);
     if (a == null && b == null) {
       return null;
     }
@@ -241,7 +240,7 @@
     super.key,
     required this.data,
     required super.child,
-  }) : assert(data != null);
+  });
 
   /// Specifies the background color, elevation, label text style, icon theme,
   /// group alignment, and label type and border values for descendant
diff --git a/framework/lib/src/material/outlined_button.dart b/framework/lib/src/material/outlined_button.dart
index bd02a20..0f26afe 100644
--- a/framework/lib/src/material/outlined_button.dart
+++ b/framework/lib/src/material/outlined_button.dart
@@ -351,7 +351,7 @@
     const EdgeInsets.symmetric(horizontal: 16),
     const EdgeInsets.symmetric(horizontal: 8),
     const EdgeInsets.symmetric(horizontal: 4),
-    MediaQuery.maybeOf(context)?.textScaleFactor ?? 1,
+    MediaQuery.textScaleFactorOf(context),
   );
 }
 
@@ -417,9 +417,7 @@
     super.statesController,
     required Widget icon,
     required Widget label,
-  }) : assert(icon != null),
-       assert(label != null),
-       super(
+  }) : super(
          autofocus: autofocus ?? false,
          clipBehavior: clipBehavior ?? Clip.none,
          child: _OutlinedButtonWithIconChild(icon: icon, label: label),
@@ -437,7 +435,7 @@
 
   @override
   Widget build(BuildContext context) {
-    final double scale = MediaQuery.maybeOf(context)?.textScaleFactor ?? 1;
+    final double scale = MediaQuery.textScaleFactorOf(context);
     final double gap = scale <= 1 ? 8 : lerpDouble(8, 4, math.min(scale - 1, 1))!;
     return Row(
       mainAxisSize: MainAxisSize.min,
@@ -453,7 +451,7 @@
 // Design token database by the script:
 //   dev/tools/gen_defaults/bin/gen_defaults.dart.
 
-// Token database version: v0_143
+// Token database version: v0_158
 
 class _OutlinedButtonDefaultsM3 extends ButtonStyle {
   _OutlinedButtonDefaultsM3(this.context)
diff --git a/framework/lib/src/material/outlined_button_theme.dart b/framework/lib/src/material/outlined_button_theme.dart
index 96948f2..bd12404 100644
--- a/framework/lib/src/material/outlined_button_theme.dart
+++ b/framework/lib/src/material/outlined_button_theme.dart
@@ -49,7 +49,6 @@
 
   /// Linearly interpolate between two outlined button themes.
   static OutlinedButtonThemeData? lerp(OutlinedButtonThemeData? a, OutlinedButtonThemeData? b, double t) {
-    assert (t != null);
     if (a == null && b == null) {
       return null;
     }
@@ -98,7 +97,7 @@
     super.key,
     required this.data,
     required super.child,
-  }) : assert(data != null);
+  });
 
   /// The configuration of this theme.
   final OutlinedButtonThemeData data;
diff --git a/framework/lib/src/material/page.dart b/framework/lib/src/material/page.dart
index 021ec49..be482a4 100644
--- a/framework/lib/src/material/page.dart
+++ b/framework/lib/src/material/page.dart
@@ -40,9 +40,7 @@
     this.maintainState = true,
     super.fullscreenDialog,
     super.allowSnapshotting = true,
-  }) : assert(builder != null),
-       assert(maintainState != null),
-       assert(fullscreenDialog != null) {
+  }) {
     assert(opaque);
   }
 
@@ -109,15 +107,6 @@
     Animation<double> secondaryAnimation,
   ) {
     final Widget result = buildContent(context);
-    assert(() {
-      if (result == null) {
-        throw FlutterError(
-          'The builder for route "${settings.name}" returned null.\n'
-          'Route builders must never return null.',
-        );
-      }
-      return true;
-    }());
     return Semantics(
       scopesRoute: true,
       explicitChildNodes: true,
@@ -163,9 +152,7 @@
     super.name,
     super.arguments,
     super.restorationId,
-  }) : assert(child != null),
-       assert(maintainState != null),
-       assert(fullscreenDialog != null);
+  });
 
   /// The content to be shown in the [Route] created by this page.
   final Widget child;
@@ -193,8 +180,7 @@
   _PageBasedMaterialPageRoute({
     required MaterialPage<T> page,
     super.allowSnapshotting,
-  }) : assert(page != null),
-       super(settings: page) {
+  }) : super(settings: page) {
     assert(opaque);
   }
 
diff --git a/framework/lib/src/material/page_transitions_theme.dart b/framework/lib/src/material/page_transitions_theme.dart
index c65f24f..f41fbc7 100644
--- a/framework/lib/src/material/page_transitions_theme.dart
+++ b/framework/lib/src/material/page_transitions_theme.dart
@@ -157,9 +157,9 @@
     required this.animation,
     required this.secondaryAnimation,
     required this.allowSnapshotting,
+    required this.allowEnterRouteSnapshotting,
     this.child,
-  }) : assert(animation != null),
-       assert(secondaryAnimation != null);
+  });
 
   // A curve sequence that is similar to the 'fastOutExtraSlowIn' curve used in
   // the native transition.
@@ -207,6 +207,15 @@
   /// [secondaryAnimation].
   final Widget? child;
 
+  /// Whether to enable snapshotting on the entering route during the
+  /// transition animation.
+  ///
+  /// If not specified, defaults to true.
+  /// If false, the route snapshotting will not be applied to the route being
+  /// animating into, e.g. when transitioning from route A to route B, B will
+  /// not be snapshotted.
+  final bool allowEnterRouteSnapshotting;
+
   @override
   Widget build(BuildContext context) {
     return DualTransitionBuilder(
@@ -218,7 +227,7 @@
       ) {
         return _ZoomEnterTransition(
           animation: animation,
-          allowSnapshotting: allowSnapshotting,
+          allowSnapshotting: allowSnapshotting && allowEnterRouteSnapshotting,
           child: child,
         );
       },
@@ -243,7 +252,7 @@
         ) {
           return _ZoomEnterTransition(
             animation: animation,
-            allowSnapshotting: allowSnapshotting,
+            allowSnapshotting: allowSnapshotting && allowEnterRouteSnapshotting ,
             reverse: true,
             child: child,
           );
@@ -271,8 +280,7 @@
     this.reverse = false,
     required this.allowSnapshotting,
     this.child,
-  }) : assert(animation != null),
-       assert(reverse != null);
+  });
 
   final Animation<double> animation;
   final Widget? child;
@@ -381,8 +389,7 @@
     this.reverse = false,
     required this.allowSnapshotting,
     this.child,
-  }) : assert(animation != null),
-       assert(reverse != null);
+  });
 
   final Animation<double> animation;
   final bool allowSnapshotting;
@@ -596,7 +603,18 @@
 class ZoomPageTransitionsBuilder extends PageTransitionsBuilder {
   /// Constructs a page transition animation that matches the transition used on
   /// Android Q.
-  const ZoomPageTransitionsBuilder();
+  const ZoomPageTransitionsBuilder({
+    this.allowEnterRouteSnapshotting = true,
+  });
+
+  /// Whether to enable snapshotting on the entering route during the
+  /// transition animation.
+  ///
+  /// If not specified, defaults to true.
+  /// If false, the route snapshotting will not be applied to the route being
+  /// animating into, e.g. when transitioning from route A to route B, B will
+  /// not be snapshotted.
+  final bool allowEnterRouteSnapshotting;
 
   @override
   Widget buildTransitions<T>(
@@ -610,6 +628,7 @@
       animation: animation,
       secondaryAnimation: secondaryAnimation,
       allowSnapshotting: route?.allowSnapshotting ?? true,
+      allowEnterRouteSnapshotting: allowEnterRouteSnapshotting,
       child: child,
     );
   }
diff --git a/framework/lib/src/material/paginated_data_table.dart b/framework/lib/src/material/paginated_data_table.dart
index 1677546..56ed3b4 100644
--- a/framework/lib/src/material/paginated_data_table.dart
+++ b/framework/lib/src/material/paginated_data_table.dart
@@ -88,27 +88,16 @@
     this.checkboxHorizontalMargin,
     this.controller,
     this.primary,
-  }) : assert(actions == null || (actions != null && header != null)),
-       assert(columns != null),
-       assert(dragStartBehavior != null),
+  }) : assert(actions == null || (header != null)),
        assert(columns.isNotEmpty),
        assert(sortColumnIndex == null || (sortColumnIndex >= 0 && sortColumnIndex < columns.length)),
-       assert(sortAscending != null),
-       assert(dataRowHeight != null),
-       assert(headingRowHeight != null),
-       assert(horizontalMargin != null),
-       assert(columnSpacing != null),
-       assert(showCheckboxColumn != null),
-       assert(showFirstLastButtons != null),
-       assert(rowsPerPage != null),
        assert(rowsPerPage > 0),
        assert(() {
          if (onRowsPerPageChanged != null) {
-           assert(availableRowsPerPage != null && availableRowsPerPage.contains(rowsPerPage));
+           assert(availableRowsPerPage.contains(rowsPerPage));
          }
          return true;
        }()),
-       assert(source != null),
        assert(!(controller != null && (primary ?? false)),
           'Primary ScrollViews obtain their ScrollController via inheritance from a PrimaryScrollController widget. '
           'You cannot both set primary to true and pass an explicit controller.',
diff --git a/framework/lib/src/material/popup_menu.dart b/framework/lib/src/material/popup_menu.dart
index c6861fd..44991de 100644
--- a/framework/lib/src/material/popup_menu.dart
+++ b/framework/lib/src/material/popup_menu.dart
@@ -131,7 +131,7 @@
   const _MenuItem({
     required this.onLayout,
     required super.child,
-  }) : assert(onLayout != null);
+  });
 
   final ValueChanged<Size> onLayout;
 
@@ -147,7 +147,7 @@
 }
 
 class _RenderMenuItem extends RenderShiftedBox {
-  _RenderMenuItem(this.onLayout, [RenderBox? child]) : assert(onLayout != null), super(child);
+  _RenderMenuItem(this.onLayout, [RenderBox? child]) : super(child);
 
   ValueChanged<Size> onLayout;
 
@@ -229,8 +229,7 @@
     this.labelTextStyle,
     this.mouseCursor,
     required this.child,
-  }) : assert(enabled != null),
-       assert(height != null);
+  });
 
   /// The value that will be returned by [showMenu] if this entry is selected.
   final T? value;
@@ -478,7 +477,7 @@
     super.height,
     super.mouseCursor,
     super.child,
-  }) : assert(checked != null);
+  });
 
   /// Whether to display a checkmark next to the menu item.
   ///
@@ -574,7 +573,7 @@
       );
       Widget item = route.items[i];
       if (route.initialValue != null && route.items[i].represents(route.initialValue)) {
-        item = Container(
+        item = ColoredBox(
           color: Theme.of(context).highlightColor,
           child: item,
         );
@@ -698,7 +697,7 @@
     final double buttonHeight = size.height - position.top - position.bottom;
     // Find the ideal vertical position.
     double y = position.top;
-    if (selectedItemIndex != null && itemSizes != null) {
+    if (selectedItemIndex != null) {
       double selectedItemOffset = _kMenuVerticalPadding;
       for (int index = 0; index < selectedItemIndex!; index += 1) {
         selectedItemOffset += itemSizes[index]!.height;
@@ -717,7 +716,6 @@
       x = position.left;
     } else {
       // Menu button is equidistant from both edges, so grow in reading direction.
-      assert(textDirection != null);
       switch (textDirection) {
         case TextDirection.rtl:
           x = size.width - position.right - childSize.width;
@@ -795,8 +793,8 @@
     this.constraints,
     required this.clipBehavior,
   }) : itemSizes = List<Size?>.filled(items.length, null),
-       // Menus always cycle focus through its items irrespective of the
-       // focus traversal edge behavior set in the MediaQuery.
+       // Menus always cycle focus through their items irrespective of the
+       // focus traversal edge behavior set in the Navigator.
        super(traversalEdgeBehavior: TraversalEdgeBehavior.closedLoop);
 
   final RelativeRect position;
@@ -955,10 +953,7 @@
   BoxConstraints? constraints,
   Clip clipBehavior = Clip.none,
 }) {
-  assert(context != null);
-  assert(position != null);
-  assert(useRootNavigator != null);
-  assert(items != null && items.isNotEmpty);
+  assert(items.isNotEmpty);
   assert(debugCheckHasMaterialLocalizations(context));
 
   switch (Theme.of(context).platform) {
@@ -1067,9 +1062,7 @@
     this.constraints,
     this.position,
     this.clipBehavior = Clip.none,
-  }) : assert(itemBuilder != null),
-       assert(enabled != null),
-       assert(
+  }) : assert(
          !(child != null && icon != null),
          'You can only pass [child] or [icon], not both.',
        );
@@ -1295,7 +1288,7 @@
   }
 
   bool get _canRequestFocus {
-    final NavigationMode mode = MediaQuery.maybeOf(context)?.navigationMode ?? NavigationMode.traditional;
+    final NavigationMode mode = MediaQuery.maybeNavigationModeOf(context) ?? NavigationMode.traditional;
     switch (mode) {
       case NavigationMode.traditional:
         return widget.enabled;
@@ -1378,7 +1371,7 @@
 // Design token database by the script:
 //   dev/tools/gen_defaults/bin/gen_defaults.dart.
 
-// Token database version: v0_143
+// Token database version: v0_158
 
 class _PopupMenuDefaultsM3 extends PopupMenuThemeData {
   _PopupMenuDefaultsM3(this.context)
diff --git a/framework/lib/src/material/popup_menu_theme.dart b/framework/lib/src/material/popup_menu_theme.dart
index df7aa57..9b3d4c8 100644
--- a/framework/lib/src/material/popup_menu_theme.dart
+++ b/framework/lib/src/material/popup_menu_theme.dart
@@ -129,7 +129,6 @@
   ///
   /// {@macro dart.ui.shadow.lerp}
   static PopupMenuThemeData? lerp(PopupMenuThemeData? a, PopupMenuThemeData? b, double t) {
-    assert(t != null);
     if (a == null && b == null) {
       return null;
     }
@@ -212,7 +211,7 @@
     super.key,
     required this.data,
     required super.child,
-  }) : assert(data != null);
+  });
 
   /// The properties for descendant popup menu widgets.
   final PopupMenuThemeData data;
diff --git a/framework/lib/src/material/progress_indicator.dart b/framework/lib/src/material/progress_indicator.dart
index e2a65fe..85b7adf 100644
--- a/framework/lib/src/material/progress_indicator.dart
+++ b/framework/lib/src/material/progress_indicator.dart
@@ -145,7 +145,7 @@
     this.value,
     required this.animationValue,
     required this.textDirection,
-  }) : assert(textDirection != null);
+  });
 
   final Color backgroundColor;
   final Color valueColor;
@@ -515,8 +515,8 @@
   /// [CupertinoActivityIndicator] in iOS and [CircularProgressIndicator] in
   /// material theme/non-iOS.
   ///
-  /// The [value], [backgroundColor], [valueColor], [strokeWidth],
-  /// [semanticsLabel], and [semanticsValue] will be ignored in iOS.
+  /// The [value], [valueColor], [strokeWidth], [semanticsLabel], and
+  /// [semanticsValue] will be ignored in iOS.
   ///
   /// {@macro flutter.material.ProgressIndicator.ProgressIndicator}
   const CircularProgressIndicator.adaptive({
@@ -654,7 +654,6 @@
         return _buildAnimation();
       case _ActivityIndicatorType.adaptive:
         final ThemeData theme = Theme.of(context);
-        assert(theme.platform != null);
         switch (theme.platform) {
           case TargetPlatform.iOS:
           case TargetPlatform.macOS:
@@ -925,7 +924,7 @@
 // Design token database by the script:
 //   dev/tools/gen_defaults/bin/gen_defaults.dart.
 
-// Token database version: v0_143
+// Token database version: v0_158
 
 class _CircularProgressIndicatorDefaultsM3 extends ProgressIndicatorThemeData {
   _CircularProgressIndicatorDefaultsM3(this.context);
diff --git a/framework/lib/src/material/progress_indicator_theme.dart b/framework/lib/src/material/progress_indicator_theme.dart
index 7cef172..b2b5521 100644
--- a/framework/lib/src/material/progress_indicator_theme.dart
+++ b/framework/lib/src/material/progress_indicator_theme.dart
@@ -87,7 +87,6 @@
     if (a == null && b == null) {
       return null;
     }
-    assert(t != null);
     return ProgressIndicatorThemeData(
       color: Color.lerp(a?.color, b?.color, t),
       linearTrackColor : Color.lerp(a?.linearTrackColor, b?.linearTrackColor, t),
@@ -160,7 +159,7 @@
     super.key,
     required this.data,
     required super.child,
-  }) : assert(data != null);
+  });
 
   /// The properties for descendant [ProgressIndicator] widgets.
   final ProgressIndicatorThemeData data;
diff --git a/framework/lib/src/material/radio.dart b/framework/lib/src/material/radio.dart
index a6dcdb7..8fc68d5 100644
--- a/framework/lib/src/material/radio.dart
+++ b/framework/lib/src/material/radio.dart
@@ -93,8 +93,7 @@
     this.visualDensity,
     this.focusNode,
     this.autofocus = false,
-  }) : assert(autofocus != null),
-       assert(toggleable != null);
+  });
 
   /// The value represented by this radio button.
   final T value;
@@ -537,7 +536,7 @@
 // Design token database by the script:
 //   dev/tools/gen_defaults/bin/gen_defaults.dart.
 
-// Token database version: v0_143
+// Token database version: v0_158
 
 class _RadioDefaultsM3 extends RadioThemeData {
   _RadioDefaultsM3(this.context);
diff --git a/framework/lib/src/material/radio_list_tile.dart b/framework/lib/src/material/radio_list_tile.dart
index b3a4237..754220c 100644
--- a/framework/lib/src/material/radio_list_tile.dart
+++ b/framework/lib/src/material/radio_list_tile.dart
@@ -53,7 +53,7 @@
 ///
 /// {@tool snippet}
 /// ```dart
-/// Container(
+/// ColoredBox(
 ///   color: Colors.green,
 ///   child: Material(
 ///     child: RadioListTile<Meridiem>(
@@ -86,6 +86,13 @@
 /// ** See code in examples/api/lib/material/radio_list_tile/radio_list_tile.0.dart **
 /// {@end-tool}
 ///
+/// {@tool dartpad}
+/// This sample demonstrates how [RadioListTile] positions the radio widget
+/// relative to the text in different configurations.
+///
+/// ** See code in examples/api/lib/material/radio_list_tile/radio_list_tile.1.dart **
+/// {@end-tool}
+///
 /// ## Semantics in RadioListTile
 ///
 /// Since the entirety of the RadioListTile is interactive, it should represent
@@ -110,7 +117,7 @@
 /// LinkedLabelRadio, that includes an interactive [RichText] widget that
 /// handles tap gestures.
 ///
-/// ** See code in examples/api/lib/material/radio_list_tile/radio_list_tile.1.dart **
+/// ** See code in examples/api/lib/material/radio_list_tile/custom_labeled_radio.0.dart **
 /// {@end-tool}
 ///
 /// ## RadioListTile isn't exactly what I want
@@ -126,7 +133,7 @@
 /// Here is an example of a custom LabeledRadio widget, but you can easily
 /// make your own configurable widget.
 ///
-/// ** See code in examples/api/lib/material/radio_list_tile/radio_list_tile.2.dart **
+/// ** See code in examples/api/lib/material/radio_list_tile/custom_labeled_radio.1.dart **
 /// {@end-tool}
 ///
 /// See also:
@@ -173,12 +180,7 @@
     this.focusNode,
     this.onFocusChange,
     this.enableFeedback,
-  }) : assert(toggleable != null),
-       assert(isThreeLine != null),
-       assert(!isThreeLine || subtitle != null),
-       assert(selected != null),
-       assert(controlAffinity != null),
-       assert(autofocus != null);
+  }) : assert(!isThreeLine || subtitle != null);
 
   /// The value represented by this radio button.
   final T value;
@@ -245,7 +247,7 @@
 
   /// The color to use when this radio button is selected.
   ///
-  /// Defaults to accent color of the current [Theme].
+  /// Defaults to [ColorScheme.secondary] of the current [Theme].
   final Color? activeColor;
 
   /// The primary content of the list tile.
diff --git a/framework/lib/src/material/range_slider.dart b/framework/lib/src/material/range_slider.dart
index 7396c96..bdbb5ff 100644
--- a/framework/lib/src/material/range_slider.dart
+++ b/framework/lib/src/material/range_slider.dart
@@ -14,6 +14,7 @@
 
 import 'constants.dart';
 import 'debug.dart';
+import 'material_state.dart';
 import 'slider_theme.dart';
 import 'theme.dart';
 
@@ -144,11 +145,10 @@
     this.labels,
     this.activeColor,
     this.inactiveColor,
+    this.overlayColor,
+    this.mouseCursor,
     this.semanticFormatterCallback,
-  }) : assert(values != null),
-       assert(min != null),
-       assert(max != null),
-       assert(min <= max),
+  }) : assert(min <= max),
        assert(values.start <= values.end),
        assert(values.start >= min && values.start <= max),
        assert(values.end >= min && values.end <= max),
@@ -322,6 +322,26 @@
   /// appearance of various components of the slider.
   final Color? inactiveColor;
 
+  /// The highlight color that's typically used to indicate that
+  /// the range slider thumb is hovered or dragged.
+  ///
+  /// If this property is null, [RangeSlider] will use [activeColor] with
+  /// an opacity of 0.12. If null, [SliderThemeData.overlayColor]
+  /// will be used, otherwise defaults to [ColorScheme.primary] with
+  /// an opacity of 0.12.
+  final MaterialStateProperty<Color?>? overlayColor;
+
+  /// The cursor for a mouse pointer when it enters or is hovering over the
+  /// widget.
+  ///
+  /// If null, then the value of [SliderThemeData.mouseCursor] is used. If that
+  /// is also null, then [MaterialStateMouseCursor.clickable] is used.
+  ///
+  /// See also:
+  ///
+  ///  * [MaterialStateMouseCursor], which can be used to create a [MouseCursor].
+  final MaterialStateProperty<MouseCursor?>? mouseCursor;
+
   /// The callback used to create a semantic value from the slider's values.
   ///
   /// Defaults to formatting values as a percentage.
@@ -400,6 +420,16 @@
   PaintRangeValueIndicator? paintTopValueIndicator;
   PaintRangeValueIndicator? paintBottomValueIndicator;
 
+  bool get _enabled => widget.onChanged != null;
+
+  bool _dragging = false;
+
+  bool _hovering = false;
+  void _handleHoverChanged(bool hovering) {
+    if (hovering != _hovering) {
+      setState(() { _hovering = hovering; });
+    }
+  }
 
   @override
   void initState() {
@@ -415,7 +445,7 @@
     enableController = AnimationController(
       duration: enableAnimationDuration,
       vsync: this,
-      value: widget.onChanged != null ? 1.0 : 0.0,
+      value: _enabled ? 1.0 : 0.0,
     );
     startPositionController = AnimationController(
       duration: Duration.zero,
@@ -436,7 +466,7 @@
       return;
     }
     final bool wasEnabled = oldWidget.onChanged != null;
-    final bool isEnabled = widget.onChanged != null;
+    final bool isEnabled = _enabled;
     if (wasEnabled != isEnabled) {
       if (isEnabled) {
         enableController.forward();
@@ -462,7 +492,7 @@
   }
 
   void _handleChanged(RangeValues values) {
-    assert(widget.onChanged != null);
+    assert(_enabled);
     final RangeValues lerpValues = _lerpRangeValues(values);
     if (lerpValues != widget.values) {
       widget.onChanged!(lerpValues);
@@ -471,11 +501,13 @@
 
   void _handleDragStart(RangeValues values) {
     assert(widget.onChangeStart != null);
+    _dragging = true;
     widget.onChangeStart!(_lerpRangeValues(values));
   }
 
   void _handleDragEnd(RangeValues values) {
     assert(widget.onChangeEnd != null);
+    _dragging = false;
     widget.onChangeEnd!(_lerpRangeValues(values));
   }
 
@@ -576,6 +608,12 @@
     const ShowValueIndicator defaultShowValueIndicator = ShowValueIndicator.onlyForDiscrete;
     const double defaultMinThumbSeparation = 8;
 
+    final Set<MaterialState> states = <MaterialState>{
+      if (!_enabled) MaterialState.disabled,
+      if (_hovering) MaterialState.hovered,
+      if (_dragging) MaterialState.dragged,
+    };
+
     // The value indicator's color is not the same as the thumb and active track
     // (which can be defined by activeColor) if the
     // RectangularSliderValueIndicatorShape is used. In all other cases, the
@@ -588,6 +626,13 @@
       valueIndicatorColor = widget.activeColor ?? sliderTheme.valueIndicatorColor ?? theme.colorScheme.primary;
     }
 
+    Color? effectiveOverlayColor() {
+      return widget.overlayColor?.resolve(states)
+        ?? widget.activeColor?.withOpacity(0.12)
+        ?? MaterialStateProperty.resolveAs<Color?>(sliderTheme.overlayColor, states)
+        ?? theme.colorScheme.primary.withOpacity(0.12);
+    }
+
     sliderTheme = sliderTheme.copyWith(
       trackHeight: sliderTheme.trackHeight ?? defaultTrackHeight,
       activeTrackColor: widget.activeColor ?? sliderTheme.activeTrackColor ?? theme.colorScheme.primary,
@@ -601,7 +646,7 @@
       thumbColor: widget.activeColor ?? sliderTheme.thumbColor ?? theme.colorScheme.primary,
       overlappingShapeStrokeColor: sliderTheme.overlappingShapeStrokeColor ?? theme.colorScheme.surface,
       disabledThumbColor: sliderTheme.disabledThumbColor ?? Color.alphaBlend(theme.colorScheme.onSurface.withOpacity(.38), theme.colorScheme.surface),
-      overlayColor: widget.activeColor?.withOpacity(0.12) ?? sliderTheme.overlayColor ?? theme.colorScheme.primary.withOpacity(0.12),
+      overlayColor: effectiveOverlayColor(),
       valueIndicatorColor: valueIndicatorColor,
       rangeTrackShape: sliderTheme.rangeTrackShape ?? defaultTrackShape,
       rangeTickMarkShape: sliderTheme.rangeTickMarkShape ?? defaultTickMarkShape,
@@ -615,26 +660,36 @@
       minThumbSeparation: sliderTheme.minThumbSeparation ?? defaultMinThumbSeparation,
       thumbSelector: sliderTheme.thumbSelector ?? _defaultRangeThumbSelector,
     );
+    final MouseCursor effectiveMouseCursor = widget.mouseCursor?.resolve(states)
+      ?? sliderTheme.mouseCursor?.resolve(states)
+      ?? MaterialStateMouseCursor.clickable.resolve(states);
 
     // This size is used as the max bounds for the painting of the value
     // indicators. It must be kept in sync with the function with the same name
     // in slider.dart.
-    Size screenSize() => MediaQuery.of(context).size;
+    Size screenSize() => MediaQuery.sizeOf(context);
 
-    return CompositedTransformTarget(
-      link: _layerLink,
-      child: _RangeSliderRenderObjectWidget(
-        values: _unlerpRangeValues(widget.values),
-        divisions: widget.divisions,
-        labels: widget.labels,
-        sliderTheme: sliderTheme,
-        textScaleFactor: MediaQuery.of(context).textScaleFactor,
-        screenSize: screenSize(),
-        onChanged: (widget.onChanged != null) && (widget.max > widget.min) ? _handleChanged : null,
-        onChangeStart: widget.onChangeStart != null ? _handleDragStart : null,
-        onChangeEnd: widget.onChangeEnd != null ? _handleDragEnd : null,
-        state: this,
-        semanticFormatterCallback: widget.semanticFormatterCallback,
+    return FocusableActionDetector(
+      enabled: _enabled,
+      onShowHoverHighlight: _handleHoverChanged,
+      includeFocusSemantics: false,
+      mouseCursor: effectiveMouseCursor,
+      child: CompositedTransformTarget(
+        link: _layerLink,
+        child: _RangeSliderRenderObjectWidget(
+          values: _unlerpRangeValues(widget.values),
+          divisions: widget.divisions,
+          labels: widget.labels,
+          sliderTheme: sliderTheme,
+          textScaleFactor: MediaQuery.of(context).textScaleFactor,
+          screenSize: screenSize(),
+          onChanged: _enabled && (widget.max > widget.min) ? _handleChanged : null,
+          onChangeStart: widget.onChangeStart != null ? _handleDragStart : null,
+          onChangeEnd: widget.onChangeEnd != null ? _handleDragEnd : null,
+          state: this,
+          semanticFormatterCallback: widget.semanticFormatterCallback,
+          hovering: _hovering,
+        ),
       ),
     );
   }
@@ -673,6 +728,7 @@
     required this.onChangeEnd,
     required this.state,
     required this.semanticFormatterCallback,
+    required this.hovering,
   });
 
   final RangeValues values;
@@ -686,6 +742,7 @@
   final ValueChanged<RangeValues>? onChangeEnd;
   final SemanticFormatterCallback? semanticFormatterCallback;
   final _RangeSliderState state;
+  final bool hovering;
 
   @override
   _RenderRangeSlider createRenderObject(BuildContext context) {
@@ -704,7 +761,8 @@
       textDirection: Directionality.of(context),
       semanticFormatterCallback: semanticFormatterCallback,
       platform: Theme.of(context).platform,
-      gestureSettings: MediaQuery.of(context).gestureSettings,
+      hovering: hovering,
+      gestureSettings: MediaQuery.gestureSettingsOf(context),
     );
   }
 
@@ -726,7 +784,8 @@
       ..textDirection = Directionality.of(context)
       ..semanticFormatterCallback = semanticFormatterCallback
       ..platform = Theme.of(context).platform
-      ..gestureSettings = MediaQuery.of(context).gestureSettings;
+      ..hovering = hovering
+      ..gestureSettings = MediaQuery.gestureSettingsOf(context);
   }
 }
 
@@ -746,12 +805,10 @@
     required this.onChangeEnd,
     required _RangeSliderState state,
     required TextDirection textDirection,
+    required bool hovering,
     required DeviceGestureSettings gestureSettings,
-  })  : assert(values != null),
-        assert(values.start >= 0.0 && values.start <= 1.0),
+  })  : assert(values.start >= 0.0 && values.start <= 1.0),
         assert(values.end >= 0.0 && values.end <= 1.0),
-        assert(state != null),
-        assert(textDirection != null),
         _platform = platform,
         _semanticFormatterCallback = semanticFormatterCallback,
         _labels = labels,
@@ -763,7 +820,8 @@
         _screenSize = screenSize,
         _onChanged = onChanged,
         _state = state,
-        _textDirection = textDirection {
+        _textDirection = textDirection,
+        _hovering = hovering {
     _updateLabelPainters();
     final GestureArenaTeam team = GestureArenaTeam();
     _drag = HorizontalDragGestureRecognizer()
@@ -842,6 +900,8 @@
   late RangeValues _newValues;
   late Offset _startThumbCenter;
   late Offset _endThumbCenter;
+  Rect? overlayStartRect;
+  Rect? overlayEndRect;
 
   bool get isEnabled => onChanged != null;
 
@@ -852,9 +912,8 @@
   RangeValues get values => _values;
   RangeValues _values;
   set values(RangeValues newValues) {
-    assert(newValues != null);
-    assert(newValues.start != null && newValues.start >= 0.0 && newValues.start <= 1.0);
-    assert(newValues.end != null && newValues.end >= 0.0 && newValues.end <= 1.0);
+    assert(newValues.start >= 0.0 && newValues.start <= 1.0);
+    assert(newValues.end >= 0.0 && newValues.end <= 1.0);
     assert(newValues.start <= newValues.end);
     final RangeValues convertedValues = isDiscrete ? _discretizeRangeValues(newValues) : newValues;
     if (convertedValues == _values) {
@@ -985,7 +1044,6 @@
   TextDirection get textDirection => _textDirection;
   TextDirection _textDirection;
   set textDirection(TextDirection value) {
-    assert(value != null);
     if (value == _textDirection) {
       return;
     }
@@ -993,6 +1051,50 @@
     _updateLabelPainters();
   }
 
+  /// True if this slider is being hovered over by a pointer.
+  bool get hovering => _hovering;
+  bool _hovering;
+  set hovering(bool value) {
+    if (value == _hovering) {
+      return;
+    }
+    _hovering = value;
+    _updateForHover(_hovering);
+  }
+
+  /// True if the slider is interactive and the start thumb is being
+  /// hovered over by a pointer.
+  bool _hoveringStartThumb = false;
+  bool get hoveringStartThumb => _hoveringStartThumb;
+  set hoveringStartThumb(bool value) {
+    if (value == _hoveringStartThumb) {
+      return;
+    }
+    _hoveringStartThumb = value;
+    _updateForHover(_hovering);
+  }
+
+  /// True if the slider is interactive and the end thumb is being
+  /// hovered over by a pointer.
+  bool _hoveringEndThumb = false;
+  bool get hoveringEndThumb => _hoveringEndThumb;
+  set hoveringEndThumb(bool value) {
+    if (value == _hoveringEndThumb) {
+      return;
+    }
+    _hoveringEndThumb = value;
+    _updateForHover(_hovering);
+  }
+
+  void _updateForHover(bool hovered) {
+    // Only show overlay when pointer is hovering the thumb.
+    if (hovered && (hoveringStartThumb || hoveringEndThumb)) {
+      _state.overlayController.forward();
+    } else {
+      _state.overlayController.reverse();
+    }
+  }
+
   bool get showValueIndicator {
     switch (_sliderTheme.showValueIndicator!) {
       case ShowValueIndicator.onlyForDiscrete:
@@ -1253,6 +1355,14 @@
       _drag.addPointer(event);
       _tap.addPointer(event);
     }
+    if (isEnabled) {
+      if (overlayStartRect != null) {
+        hoveringStartThumb = overlayStartRect!.contains(event.localPosition);
+      }
+      if (overlayEndRect != null) {
+        hoveringEndThumb = overlayEndRect!.contains(event.localPosition);
+      }
+    }
   }
 
   @override
@@ -1307,6 +1417,11 @@
     );
     _startThumbCenter = Offset(trackRect.left + startVisualPosition * trackRect.width, trackRect.center.dy);
     _endThumbCenter = Offset(trackRect.left + endVisualPosition * trackRect.width, trackRect.center.dy);
+    if (isEnabled) {
+      final Size overlaySize = sliderTheme.overlayShape!.getPreferredSize(isEnabled, false);
+      overlayStartRect = Rect.fromCircle(center: _startThumbCenter, radius: overlaySize.width / 2.0);
+      overlayEndRect = Rect.fromCircle(center: _endThumbCenter, radius: overlaySize.width / 2.0);
+    }
 
     _sliderTheme.rangeTrackShape!.paint(
         context,
@@ -1326,7 +1441,7 @@
     final Size resolvedscreenSize = screenSize.isEmpty ? size : screenSize;
 
     if (!_overlayAnimation.isDismissed) {
-      if (startThumbSelected) {
+      if (startThumbSelected || hoveringStartThumb) {
         _sliderTheme.overlayShape!.paint(
           context,
           _startThumbCenter,
@@ -1342,7 +1457,7 @@
           sizeWithOverflow: resolvedscreenSize,
         );
       }
-      if (endThumbSelected) {
+      if (endThumbSelected || hoveringEndThumb) {
         _sliderTheme.overlayShape!.paint(
           context,
           _endThumbCenter,
diff --git a/framework/lib/src/material/refresh_indicator.dart b/framework/lib/src/material/refresh_indicator.dart
index a0d44c8..c04a70f 100644
--- a/framework/lib/src/material/refresh_indicator.dart
+++ b/framework/lib/src/material/refresh_indicator.dart
@@ -138,11 +138,7 @@
     this.semanticsValue,
     this.strokeWidth = RefreshProgressIndicator.defaultStrokeWidth,
     this.triggerMode = RefreshIndicatorTriggerMode.onEdge,
-  }) : assert(child != null),
-       assert(onRefresh != null),
-       assert(notificationPredicate != null),
-       assert(strokeWidth != null),
-       assert(triggerMode != null);
+  });
 
   /// The widget below this widget in the tree.
   ///
@@ -469,29 +465,12 @@
       .animateTo(1.0 / _kDragSizeFactorLimit, duration: _kIndicatorSnapDuration)
       .then<void>((void value) {
         if (mounted && _mode == _RefreshIndicatorMode.snap) {
-          assert(widget.onRefresh != null);
           setState(() {
             // Show the indeterminate progress indicator.
             _mode = _RefreshIndicatorMode.refresh;
           });
 
           final Future<void> refreshResult = widget.onRefresh();
-          assert(() {
-            if (refreshResult == null) {
-              FlutterError.reportError(FlutterErrorDetails(
-                exception: FlutterError(
-                  'The onRefresh callback returned null.\n'
-                  'The RefreshIndicator onRefresh callback must return a Future.',
-                ),
-                context: ErrorDescription('when calling onRefresh'),
-                library: 'material library',
-              ));
-            }
-            return true;
-          }());
-          if (refreshResult == null) {
-            return;
-          }
           refreshResult.whenComplete(() {
             if (mounted && _mode == _RefreshIndicatorMode.refresh) {
               completer.complete();
diff --git a/framework/lib/src/material/reorderable_list.dart b/framework/lib/src/material/reorderable_list.dart
index 8cf0d4a..ec985bd 100644
--- a/framework/lib/src/material/reorderable_list.dart
+++ b/framework/lib/src/material/reorderable_list.dart
@@ -85,10 +85,7 @@
     this.keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
     this.restorationId,
     this.clipBehavior = Clip.hardEdge,
-  }) : assert(scrollDirection != null),
-       assert(onReorder != null),
-       assert(children != null),
-       assert(
+  }) : assert(
          itemExtent == null || prototypeItem == null,
          'You can only pass itemExtent or prototypeItem, not both',
        ),
@@ -96,7 +93,6 @@
          children.every((Widget w) => w.key != null),
          'All children of this widget must have a key.',
        ),
-       assert(buildDefaultDragHandles != null),
        itemBuilder = ((BuildContext context, int index) => children[index]),
        itemCount = children.length;
 
@@ -154,14 +150,11 @@
     this.keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
     this.restorationId,
     this.clipBehavior = Clip.hardEdge,
-  }) : assert(scrollDirection != null),
-       assert(itemCount >= 0),
-       assert(onReorder != null),
+  }) : assert(itemCount >= 0),
        assert(
          itemExtent == null || prototypeItem == null,
          'You can only pass itemExtent or prototypeItem, not both',
-       ),
-       assert(buildDefaultDragHandles != null);
+       );
 
   /// {@macro flutter.widgets.reorderable_list.itemBuilder}
   final IndexedWidgetBuilder itemBuilder;
diff --git a/framework/lib/src/material/scaffold.dart b/framework/lib/src/material/scaffold.dart
index 6a82925..a5a0885 100644
--- a/framework/lib/src/material/scaffold.dart
+++ b/framework/lib/src/material/scaffold.dart
@@ -100,7 +100,7 @@
   const ScaffoldMessenger({
     super.key,
     required this.child,
-  }) : assert(child != null);
+  });
 
   /// The widget below this widget in the tree.
   ///
@@ -144,7 +144,6 @@
   ///  * [debugCheckHasScaffoldMessenger], which asserts that the given context
   ///    has a [ScaffoldMessenger] ancestor.
   static ScaffoldMessengerState of(BuildContext context) {
-    assert(context != null);
     assert(debugCheckHasScaffoldMessenger(context));
 
     final _ScaffoldMessengerScope scope = context.dependOnInheritedWidgetOfExactType<_ScaffoldMessengerScope>()!;
@@ -161,7 +160,6 @@
   ///  * [of], which is a similar function, except that it will throw an
   ///    exception if a [ScaffoldMessenger] is not found in the given context.
   static ScaffoldMessengerState? maybeOf(BuildContext context) {
-    assert(context != null);
 
     final _ScaffoldMessengerScope? scope = context.dependOnInheritedWidgetOfExactType<_ScaffoldMessengerScope>();
     return scope?._scaffoldMessengerState;
@@ -191,18 +189,18 @@
 
   @override
   void didChangeDependencies() {
-    final MediaQueryData mediaQuery = MediaQuery.of(context);
+    final bool accessibleNavigation = MediaQuery.accessibleNavigationOf(context);
     // If we transition from accessible navigation to non-accessible navigation
     // and there is a SnackBar that would have timed out that has already
     // completed its timer, dismiss that SnackBar. If the timer hasn't finished
     // yet, let it timeout as normal.
     if ((_accessibleNavigation ?? false)
-        && !mediaQuery.accessibleNavigation
+        && !accessibleNavigation
         && _snackBarTimer != null
         && !_snackBarTimer!.isActive) {
       hideCurrentSnackBar(reason: SnackBarClosedReason.timeout);
     }
-    _accessibleNavigation = mediaQuery.accessibleNavigation;
+    _accessibleNavigation = accessibleNavigation;
     super.didChangeDependencies();
   }
 
@@ -383,7 +381,6 @@
   /// The removed snack bar does not run its normal exit animation. If there are
   /// any queued snack bars, they begin their entrance animation immediately.
   void removeCurrentSnackBar({ SnackBarClosedReason reason = SnackBarClosedReason.remove }) {
-    assert(reason != null);
     if (_snackBars.isEmpty) {
       return;
     }
@@ -401,7 +398,6 @@
   ///
   /// The closed completer is called after the animation is complete.
   void hideCurrentSnackBar({ SnackBarClosedReason reason = SnackBarClosedReason.hide }) {
-    assert(reason != null);
     if (_snackBars.isEmpty || _snackBarController!.status == AnimationStatus.dismissed) {
       return;
     }
@@ -518,7 +514,6 @@
   /// The removed material banner does not run its normal exit animation. If there are
   /// any queued material banners, they begin their entrance animation immediately.
   void removeCurrentMaterialBanner({ MaterialBannerClosedReason reason = MaterialBannerClosedReason.remove }) {
-    assert(reason != null);
     if (_materialBanners.isEmpty) {
       return;
     }
@@ -535,7 +530,6 @@
   ///
   /// The closed completer is called after the animation is complete.
   void hideCurrentMaterialBanner({ MaterialBannerClosedReason reason = MaterialBannerClosedReason.hide }) {
-    assert(reason != null);
     if (_materialBanners.isEmpty || _materialBannerController!.status == AnimationStatus.dismissed) {
       return;
     }
@@ -568,8 +562,7 @@
   @override
   Widget build(BuildContext context) {
     assert(debugCheckHasMediaQuery(context));
-    final MediaQueryData mediaQuery = MediaQuery.of(context);
-    _accessibleNavigation = mediaQuery.accessibleNavigation;
+    _accessibleNavigation = MediaQuery.accessibleNavigationOf(context);
 
     if (_snackBars.isNotEmpty) {
       final ModalRoute<dynamic>? route = ModalRoute.of(context);
@@ -582,8 +575,7 @@
                 _snackBarController!.status == AnimationStatus.completed,
             );
             // Look up MediaQuery again in case the setting changed.
-            final MediaQueryData mediaQuery = MediaQuery.of(context);
-            if (mediaQuery.accessibleNavigation && snackBar.action != null) {
+            if (snackBar.action != null && MediaQuery.accessibleNavigationOf(context)) {
               return;
             }
             hideCurrentSnackBar(reason: SnackBarClosedReason.timeout);
@@ -829,8 +821,7 @@
 }
 
 class _ScaffoldGeometryNotifier extends ChangeNotifier implements ValueListenable<ScaffoldGeometry> {
-  _ScaffoldGeometryNotifier(this.geometry, this.context)
-    : assert (context != null);
+  _ScaffoldGeometryNotifier(this.geometry, this.context);
 
   final BuildContext context;
   double? floatingActionButtonScale;
@@ -882,11 +873,8 @@
     required this.bottomWidgetsHeight,
     required this.appBarHeight,
     required this.materialBannerHeight,
-  }) : assert(bottomWidgetsHeight != null),
-       assert(bottomWidgetsHeight >= 0),
-       assert(appBarHeight != null),
+  }) : assert(bottomWidgetsHeight >= 0),
        assert(appBarHeight >= 0),
-       assert(materialBannerHeight != null),
        assert(materialBannerHeight >= 0);
 
   final double bottomWidgetsHeight;
@@ -923,9 +911,7 @@
     required this.extendBody,
     required this.extendBodyBehindAppBar,
     required this.body,
-  }) : assert(extendBody != null),
-       assert(extendBodyBehindAppBar != null),
-       assert(body != null);
+  });
 
   final Widget body;
   final bool extendBody;
@@ -981,13 +967,7 @@
     required this.extendBody,
     required this.extendBodyBehindAppBar,
     required this.extendBodyBehindMaterialBanner,
-  }) : assert(minInsets != null),
-       assert(textDirection != null),
-       assert(geometryNotifier != null),
-       assert(previousFloatingActionButtonLocation != null),
-       assert(currentFloatingActionButtonLocation != null),
-       assert(extendBody != null),
-       assert(extendBodyBehindAppBar != null);
+  });
 
   final bool extendBody;
   final bool extendBodyBehindAppBar;
@@ -1253,9 +1233,7 @@
     required this.fabMotionAnimator,
     required this.geometryNotifier,
     required this.currentController,
-  }) : assert(fabMoveAnimation != null),
-       assert(fabMotionAnimator != null),
-       assert(currentController != null);
+  });
 
   final Widget? child;
   final Animation<double> fabMoveAnimation;
@@ -1602,10 +1580,7 @@
     this.drawerEnableOpenDragGesture = true,
     this.endDrawerEnableOpenDragGesture = true,
     this.restorationId,
-  }) : assert(primary != null),
-       assert(extendBody != null),
-       assert(extendBodyBehindAppBar != null),
-       assert(drawerDragStartBehavior != null);
+  });
 
   /// If true, and [bottomNavigationBar] or [persistentFooterButtons]
   /// is specified, then the [body] extends to the bottom of the Scaffold,
@@ -1824,11 +1799,11 @@
   /// drawer.
   ///
   /// By default, the value used is 20.0 added to the padding edge of
-  /// `MediaQuery.of(context).padding` that corresponds to the surrounding
+  /// `MediaQuery.paddingOf(context)` that corresponds to the surrounding
   /// [TextDirection]. This ensures that the drag area for notched devices is
   /// not obscured. For example, if `TextDirection.of(context)` is set to
   /// [TextDirection.ltr], 20.0 will be added to
-  /// `MediaQuery.of(context).padding.left`.
+  /// `MediaQuery.paddingOf(context).left`.
   final double? drawerEdgeDragWidth;
 
   /// Determines if the [Scaffold.drawer] can be opened with a drag
@@ -1900,7 +1875,6 @@
   /// If there is no [Scaffold] in scope, then this will throw an exception.
   /// To return null if there is no [Scaffold], use [maybeOf] instead.
   static ScaffoldState of(BuildContext context) {
-    assert(context != null);
     final ScaffoldState? result = context.findAncestorStateOfType<ScaffoldState>();
     if (result != null) {
       return result;
@@ -1947,7 +1921,6 @@
   ///    encloses the given context. Also includes some sample code in its
   ///    documentation.
   static ScaffoldState? maybeOf(BuildContext context) {
-    assert(context != null);
     return context.findAncestorStateOfType<ScaffoldState>();
   }
 
@@ -2016,8 +1989,6 @@
   ///  * [Scaffold.of], which provides access to the [ScaffoldState] object as a
   ///    whole, from which you can show bottom sheets, and so forth.
   static bool hasDrawer(BuildContext context, { bool registerForUpdates = true }) {
-    assert(registerForUpdates != null);
-    assert(context != null);
     if (registerForUpdates) {
       final _ScaffoldScope? scaffold = context.dependOnInheritedWidgetOfExactType<_ScaffoldScope>();
       return scaffold?.hasDrawer ?? false;
@@ -2232,7 +2203,9 @@
             child: DraggableScrollableActuator(
               child: StatefulBuilder(
                 key: _currentBottomSheetKey,
-                builder: (BuildContext context, StateSetter setState) => widget.bottomSheet!,
+                builder: (BuildContext context, StateSetter setState) {
+                  return widget.bottomSheet ?? const SizedBox.shrink();
+                },
               ),
             ),
           );
@@ -2509,7 +2482,6 @@
   /// Sets the current value of the visibility animation for the
   /// [Scaffold.floatingActionButton]. This value must not be null.
   set _floatingActionButtonVisibilityValue(double newValue) {
-    assert(newValue != null);
     _floatingActionButtonVisibilityController.value = clampDouble(newValue,
       _floatingActionButtonVisibilityController.lowerBound,
       _floatingActionButtonVisibilityController.upperBound,
@@ -2747,7 +2719,6 @@
   ///
   /// The `value` parameter must not be null.
   void showBodyScrim(bool value, double opacity) {
-    assert(value != null);
     if (_showBodyScrim == value && _bodyScrimColor.opacity == opacity) {
       return;
     }
@@ -2761,7 +2732,6 @@
   Widget build(BuildContext context) {
     assert(debugCheckHasMediaQuery(context));
     assert(debugCheckHasDirectionality(context));
-    final MediaQueryData mediaQuery = MediaQuery.of(context);
     final ThemeData themeData = Theme.of(context);
     final TextDirection textDirection = Directionality.of(context);
 
@@ -2796,7 +2766,7 @@
     }
 
     if (widget.appBar != null) {
-      final double topPadding = widget.primary ? mediaQuery.padding.top : 0.0;
+      final double topPadding = widget.primary ? MediaQuery.paddingOf(context).top : 0.0;
       _appBarMaxHeight = AppBar.preferredHeightFor(context, widget.appBar!.preferredSize) + topPadding;
       assert(_appBarMaxHeight! >= 0.0 && _appBarMaxHeight!.isFinite);
       _addIfNonNull(
@@ -2973,14 +2943,14 @@
     }
 
     // The minimum insets for contents of the Scaffold to keep visible.
-    final EdgeInsets minInsets = mediaQuery.padding.copyWith(
-      bottom: _resizeToAvoidBottomInset ? mediaQuery.viewInsets.bottom : 0.0,
+    final EdgeInsets minInsets = MediaQuery.paddingOf(context).copyWith(
+      bottom: _resizeToAvoidBottomInset ? MediaQuery.viewInsetsOf(context).bottom : 0.0,
     );
 
     // The minimum viewPadding for interactive elements positioned by the
     // Scaffold to keep within safe interactive areas.
-    final EdgeInsets minViewPadding = mediaQuery.viewPadding.copyWith(
-      bottom: _resizeToAvoidBottomInset && mediaQuery.viewInsets.bottom != 0.0 ? 0.0 : null,
+    final EdgeInsets minViewPadding = MediaQuery.viewPaddingOf(context).copyWith(
+      bottom: _resizeToAvoidBottomInset && MediaQuery.viewInsetsOf(context).bottom != 0.0 ? 0.0 : null,
     );
 
     // extendBody locked when keyboard is open
@@ -3082,8 +3052,7 @@
   const _BottomSheetSuspendedCurve(
       this.startingPoint, {
         this.curve = Curves.easeOutCubic,
-      }) : assert(startingPoint != null),
-        assert(curve != null);
+      });
 
   /// The progress value at which [curve] should begin.
   ///
@@ -3157,7 +3126,6 @@
   @override
   void initState() {
     super.initState();
-    assert(widget.animationController != null);
     assert(
       widget.animationController.status == AnimationStatus.forward
         || widget.animationController.status == AnimationStatus.completed,
@@ -3178,7 +3146,6 @@
   }
 
   void close() {
-    assert(widget.animationController != null);
     widget.animationController.reverse();
     widget.onClosing?.call();
   }
@@ -3282,7 +3249,7 @@
     required this.hasDrawer,
     required this.geometryNotifier,
     required super.child,
-  }) : assert(hasDrawer != null);
+  });
 
   final bool hasDrawer;
   final _ScaffoldGeometryNotifier geometryNotifier;
diff --git a/framework/lib/src/material/scrollbar.dart b/framework/lib/src/material/scrollbar.dart
index a2f0d1f..6342470 100644
--- a/framework/lib/src/material/scrollbar.dart
+++ b/framework/lib/src/material/scrollbar.dart
@@ -427,7 +427,7 @@
       ..crossAxisMargin = _scrollbarTheme.crossAxisMargin ?? (_useAndroidScrollbar ? 0.0 : _kScrollbarMargin)
       ..mainAxisMargin = _scrollbarTheme.mainAxisMargin ?? 0.0
       ..minLength = _scrollbarTheme.minThumbLength ?? _kScrollbarMinLength
-      ..padding = MediaQuery.of(context).padding
+      ..padding = MediaQuery.paddingOf(context)
       ..scrollbarOrientation = widget.scrollbarOrientation
       ..ignorePointer = !enableGestures;
   }
diff --git a/framework/lib/src/material/scrollbar_theme.dart b/framework/lib/src/material/scrollbar_theme.dart
index ca32843..2257055 100644
--- a/framework/lib/src/material/scrollbar_theme.dart
+++ b/framework/lib/src/material/scrollbar_theme.dart
@@ -204,7 +204,6 @@
   ///
   /// {@macro dart.ui.shadow.lerp}
   static ScrollbarThemeData lerp(ScrollbarThemeData? a, ScrollbarThemeData? b, double t) {
-    assert(t != null);
     return ScrollbarThemeData(
       thumbVisibility: MaterialStateProperty.lerp<bool?>(a?.thumbVisibility, b?.thumbVisibility, t, _lerpBool),
       thickness: MaterialStateProperty.lerp<double?>(a?.thickness, b?.thickness, t, lerpDouble),
diff --git a/framework/lib/src/material/search.dart b/framework/lib/src/material/search.dart
index 6537007..e5f656b 100644
--- a/framework/lib/src/material/search.dart
+++ b/framework/lib/src/material/search.dart
@@ -62,9 +62,6 @@
   String? query = '',
   bool useRootNavigator = false,
 }) {
-  assert(delegate != null);
-  assert(context != null);
-  assert(useRootNavigator != null);
   delegate.query = query ?? delegate.query;
   delegate._currentBody = _SearchBody.suggestions;
   return Navigator.of(context, rootNavigator: useRootNavigator).push(_SearchPageRoute<T>(
@@ -230,16 +227,15 @@
   ///  * [InputDecorationTheme], which configures the appearance of the search
   ///    text field.
   ThemeData appBarTheme(BuildContext context) {
-    assert(context != null);
     final ThemeData theme = Theme.of(context);
     final ColorScheme colorScheme = theme.colorScheme;
-    assert(theme != null);
     return theme.copyWith(
       appBarTheme: AppBarTheme(
         brightness: colorScheme.brightness,
         backgroundColor: colorScheme.brightness == Brightness.dark ? Colors.grey[900] : Colors.white,
         iconTheme: theme.primaryIconTheme.copyWith(color: Colors.grey),
-        textTheme: theme.textTheme,
+        titleTextStyle: theme.textTheme.titleLarge,
+        toolbarTextStyle: theme.textTheme.bodyMedium,
       ),
       inputDecorationTheme: searchFieldDecorationTheme ??
           InputDecorationTheme(
@@ -261,7 +257,6 @@
   ///
   /// Setting the query string programmatically moves the cursor to the end of the text field.
   set query(String value) {
-    assert(query != null);
     _queryTextController.text = value;
     if (_queryTextController.text.isNotEmpty) {
       _queryTextController.selection = TextSelection.fromPosition(TextPosition(offset: _queryTextController.text.length));
@@ -389,7 +384,7 @@
 class _SearchPageRoute<T> extends PageRoute<T> {
   _SearchPageRoute({
     required this.delegate,
-  }) : assert(delegate != null) {
+  }) {
     assert(
       delegate._route == null,
       'The ${delegate.runtimeType} instance is currently used by another active '
diff --git a/framework/lib/src/material/segmented_button.dart b/framework/lib/src/material/segmented_button.dart
index 86b05c6..23e9706 100644
--- a/framework/lib/src/material/segmented_button.dart
+++ b/framework/lib/src/material/segmented_button.dart
@@ -116,9 +116,7 @@
     this.style,
     this.showSelectedIcon = true,
     this.selectedIcon,
-  })  : assert(segments != null),
-        assert(segments.length > 0),
-        assert(selected != null),
+  })  : assert(segments.length > 0),
         assert(selected.length > 0 || emptySelectionAllowed),
         assert(selected.length < 2 || multiSelectionEnabled);
 
@@ -374,7 +372,7 @@
   }
 }
 class _SegmentedButtonRenderWidget<T> extends MultiChildRenderObjectWidget {
-  _SegmentedButtonRenderWidget({
+  const _SegmentedButtonRenderWidget({
     super.key,
     required this.segments,
     required this.enabledBorder,
@@ -693,7 +691,6 @@
 
   @override
   bool hitTestChildren(BoxHitTestResult result, { required Offset position }) {
-    assert(position != null);
     RenderBox? child = lastChild;
     while (child != null) {
       final _SegmentedButtonContainerBoxParentData childParentData = child.parentData! as _SegmentedButtonContainerBoxParentData;
@@ -720,15 +717,13 @@
 // Design token database by the script:
 //   dev/tools/gen_defaults/bin/gen_defaults.dart.
 
-// Token database version: v0_143
+// Token database version: v0_158
 
 class _SegmentedButtonDefaultsM3 extends SegmentedButtonThemeData {
   _SegmentedButtonDefaultsM3(this.context);
-
   final BuildContext context;
   late final ThemeData _theme = Theme.of(context);
   late final ColorScheme _colors = _theme.colorScheme;
-
   @override ButtonStyle? get style {
     return ButtonStyle(
       textStyle: MaterialStatePropertyAll<TextStyle?>(Theme.of(context).textTheme.labelLarge),
@@ -766,7 +761,7 @@
           if (states.contains(MaterialState.focused)) {
             return _colors.onSurface;
           }
-          return null;
+          return _colors.onSurface;
         }
       }),
       overlayColor: MaterialStateProperty.resolveWith((Set<MaterialState> states) {
@@ -803,9 +798,9 @@
         return BorderSide(color: _colors.outline);
       }),
       shape: const MaterialStatePropertyAll<OutlinedBorder>(StadiumBorder()),
+      minimumSize: const MaterialStatePropertyAll<Size?>(Size.fromHeight(40.0)),
     );
   }
-
   @override
   Widget? get selectedIcon => const Icon(Icons.check);
 }
diff --git a/framework/lib/src/material/segmented_button_theme.dart b/framework/lib/src/material/segmented_button_theme.dart
index 10cc6c6..686455b 100644
--- a/framework/lib/src/material/segmented_button_theme.dart
+++ b/framework/lib/src/material/segmented_button_theme.dart
@@ -107,7 +107,7 @@
     super.key,
     required this.data,
     required super.child,
-  }) : assert(data != null);
+  });
 
   /// Specifies the visual properties used by descendant [SegmentedButton]
   /// widgets.
@@ -158,7 +158,6 @@
   ///  * [of], which will return [ThemeData.segmentedButtonTheme] if it doesn't
   ///    find a [SegmentedButtonTheme] ancestor, instead of returning null.
   static SegmentedButtonThemeData? maybeOf(BuildContext context) {
-    assert(context != null);
     return context.dependOnInheritedWidgetOfExactType<SegmentedButtonTheme>()?.data;
   }
 
diff --git a/framework/lib/src/material/selectable_text.dart b/framework/lib/src/material/selectable_text.dart
index 844ddf3..e4d7071 100644
--- a/framework/lib/src/material/selectable_text.dart
+++ b/framework/lib/src/material/selectable_text.dart
@@ -30,7 +30,6 @@
 
 class _TextSpanEditingController extends TextEditingController {
   _TextSpanEditingController({required TextSpan textSpan}):
-    assert(textSpan != null),
     _textSpan = textSpan,
     super(text: textSpan.toPlainText(includeSemanticsLabels: false));
 
@@ -85,7 +84,7 @@
   }
 
   @override
-  void onSingleTapUp(TapUpDetails details) {
+  void onSingleTapUp(TapDragUpDetails details) {
     editableText.hideToolbar();
     if (delegate.selectionEnabled) {
       switch (Theme.of(_state.context).platform) {
@@ -215,21 +214,12 @@
     this.onSelectionChanged,
     this.contextMenuBuilder = _defaultContextMenuBuilder,
     this.magnifierConfiguration,
-  }) :  assert(showCursor != null),
-        assert(autofocus != null),
-        assert(dragStartBehavior != null),
-        assert(selectionHeightStyle != null),
-        assert(selectionWidthStyle != null),
-        assert(maxLines == null || maxLines > 0),
+  }) :  assert(maxLines == null || maxLines > 0),
         assert(minLines == null || minLines > 0),
         assert(
           (maxLines == null) || (minLines == null) || (maxLines >= minLines),
           "minLines can't be greater than maxLines",
         ),
-        assert(
-          data != null,
-          'A non-null String must be provided to a SelectableText widget.',
-        ),
         textSpan = null;
 
   /// Creates a selectable text widget with a [TextSpan].
@@ -273,19 +263,12 @@
     this.onSelectionChanged,
     this.contextMenuBuilder = _defaultContextMenuBuilder,
     this.magnifierConfiguration,
-  }) :  assert(showCursor != null),
-    assert(autofocus != null),
-    assert(dragStartBehavior != null),
-    assert(maxLines == null || maxLines > 0),
+  }) :  assert(maxLines == null || maxLines > 0),
     assert(minLines == null || minLines > 0),
     assert(
       (maxLines == null) || (minLines == null) || (maxLines >= minLines),
       "minLines can't be greater than maxLines",
     ),
-    assert(
-      textSpan != null,
-      'A non-null TextSpan must be provided to a SelectableText.rich widget.',
-    ),
     data = null;
 
   /// The text to display.
@@ -558,8 +541,6 @@
     });
   }
 
-  TextSelection? _lastSeenTextSelection;
-
   void _handleSelectionChanged(TextSelection selection, SelectionChangedCause? cause) {
     final bool willShowSelectionHandles = _shouldShowSelectionHandles(cause);
     if (willShowSelectionHandles != _showSelectionHandles) {
@@ -567,12 +548,8 @@
         _showSelectionHandles = willShowSelectionHandles;
       });
     }
-    // TODO(chunhtai): The selection may be the same. We should remove this
-    // check once this is fixed https://github.com/flutter/flutter/issues/76349.
-    if (widget.onSelectionChanged != null && _lastSeenTextSelection != selection) {
-      widget.onSelectionChanged!(selection, cause);
-    }
-    _lastSeenTextSelection = selection;
+
+    widget.onSelectionChanged?.call(selection, cause);
 
     switch (Theme.of(context).platform) {
       case TargetPlatform.iOS:
@@ -659,7 +636,7 @@
         cursorColor = widget.cursorColor ?? selectionStyle.cursorColor ?? cupertinoTheme.primaryColor;
         selectionColor = selectionStyle.selectionColor ?? cupertinoTheme.primaryColor.withOpacity(0.40);
         cursorRadius ??= const Radius.circular(2.0);
-        cursorOffset = Offset(iOSHorizontalOffset / MediaQuery.of(context).devicePixelRatio, 0);
+        cursorOffset = Offset(iOSHorizontalOffset / MediaQuery.devicePixelRatioOf(context), 0);
         break;
 
       case TargetPlatform.macOS:
@@ -671,7 +648,7 @@
         cursorColor = widget.cursorColor ?? selectionStyle.cursorColor ?? cupertinoTheme.primaryColor;
         selectionColor = selectionStyle.selectionColor ?? cupertinoTheme.primaryColor.withOpacity(0.40);
         cursorRadius ??= const Radius.circular(2.0);
-        cursorOffset = Offset(iOSHorizontalOffset / MediaQuery.of(context).devicePixelRatio, 0);
+        cursorOffset = Offset(iOSHorizontalOffset / MediaQuery.devicePixelRatioOf(context), 0);
         break;
 
       case TargetPlatform.android:
@@ -700,7 +677,7 @@
     if (effectiveTextStyle == null || effectiveTextStyle.inherit) {
       effectiveTextStyle = defaultTextStyle.style.merge(widget.style ?? _controller._textSpan.style);
     }
-    if (MediaQuery.boldTextOverride(context)) {
+    if (MediaQuery.boldTextOf(context)) {
       effectiveTextStyle = effectiveTextStyle.merge(const TextStyle(fontWeight: FontWeight.bold));
     }
     final Widget child = RepaintBoundary(
diff --git a/framework/lib/src/material/slider.dart b/framework/lib/src/material/slider.dart
index 72b0335..d64d736 100644
--- a/framework/lib/src/material/slider.dart
+++ b/framework/lib/src/material/slider.dart
@@ -159,9 +159,6 @@
     this.focusNode,
     this.autofocus = false,
   }) : _sliderType = _SliderType.material,
-       assert(value != null),
-       assert(min != null),
-       assert(max != null),
        assert(min <= max),
        assert(value >= min && value <= max,
          'Value $value is not between minimum $min and maximum $max'),
@@ -202,9 +199,6 @@
     this.focusNode,
     this.autofocus = false,
   }) : _sliderType = _SliderType.adaptive,
-       assert(value != null),
-       assert(min != null),
-       assert(max != null),
        assert(min <= max),
        assert(value >= min && value <= max,
          'Value $value is not between minimum $min and maximum $max'),
@@ -437,7 +431,7 @@
   /// the slider thumb is focused, hovered, or dragged.
   ///
   /// If this property is null, [Slider] will use [activeColor] with
-  /// with an opacity of 0.12, If null, [SliderThemeData.overlayColor]
+  /// an opacity of 0.12, If null, [SliderThemeData.overlayColor]
   /// will be used.
   ///
   /// If that is also null, If [ThemeData.useMaterial3] is true,
@@ -735,7 +729,6 @@
 
       case _SliderType.adaptive: {
         final ThemeData theme = Theme.of(context);
-        assert(theme.platform != null);
         switch (theme.platform) {
           case TargetPlatform.android:
           case TargetPlatform.fuchsia:
@@ -826,7 +819,7 @@
     // This size is used as the max bounds for the painting of the value
     // indicators It must be kept in sync with the function with the same name
     // in range_slider.dart.
-    Size screenSize() => MediaQuery.of(context).size;
+    Size screenSize() => MediaQuery.sizeOf(context);
 
     VoidCallback? handleDidGainAccessibilityFocus;
     switch (theme.platform) {
@@ -847,7 +840,7 @@
     }
 
     final Map<ShortcutActivator, Intent> shortcutMap;
-    switch (MediaQuery.of(context).navigationMode) {
+    switch (MediaQuery.navigationModeOf(context)) {
       case NavigationMode.directional:
         shortcutMap = _directionalNavShortcutMap;
         break;
@@ -861,8 +854,8 @@
       // This needs to be updated when accessibility
       // guidelines are available on the material specs page
       // https://m3.material.io/components/sliders/accessibility.
-      ? math.min(MediaQuery.of(context).textScaleFactor, 1.3)
-      : MediaQuery.of(context).textScaleFactor;
+      ? math.min(MediaQuery.textScaleFactorOf(context), 1.3)
+      : MediaQuery.textScaleFactorOf(context);
 
     return Semantics(
       container: true,
@@ -994,7 +987,7 @@
       platform: Theme.of(context).platform,
       hasFocus: hasFocus,
       hovering: hovering,
-      gestureSettings: MediaQuery.of(context).gestureSettings,
+      gestureSettings: MediaQuery.gestureSettingsOf(context),
     );
   }
 
@@ -1018,7 +1011,7 @@
       ..platform = Theme.of(context).platform
       ..hasFocus = hasFocus
       ..hovering = hovering
-      ..gestureSettings = MediaQuery.of(context).gestureSettings;
+      ..gestureSettings = MediaQuery.gestureSettingsOf(context);
     // Ticker provider cannot change since there's a 1:1 relationship between
     // the _SliderRenderObjectWidget object and the _SliderState object.
   }
@@ -1043,10 +1036,8 @@
     required bool hasFocus,
     required bool hovering,
     required DeviceGestureSettings gestureSettings,
-  }) : assert(value != null && value >= 0.0 && value <= 1.0),
+  }) : assert(value >= 0.0 && value <= 1.0),
         assert(secondaryTrackValue == null || (secondaryTrackValue >= 0.0 && secondaryTrackValue <= 1.0)),
-        assert(state != null),
-        assert(textDirection != null),
         _platform = platform,
         _semanticFormatterCallback = semanticFormatterCallback,
         _label = label,
@@ -1138,7 +1129,7 @@
   double get value => _value;
   double _value;
   set value(double newValue) {
-    assert(newValue != null && newValue >= 0.0 && newValue <= 1.0);
+    assert(newValue >= 0.0 && newValue <= 1.0);
     final double convertedValue = isDiscrete ? _discretize(newValue) : newValue;
     if (convertedValue == _value) {
       return;
@@ -1272,7 +1263,6 @@
   TextDirection get textDirection => _textDirection;
   TextDirection _textDirection;
   set textDirection(TextDirection value) {
-    assert(value != null);
     if (value == _textDirection) {
       return;
     }
@@ -1284,7 +1274,6 @@
   bool get hasFocus => _hasFocus;
   bool _hasFocus;
   set hasFocus(bool value) {
-    assert(value != null);
     if (value == _hasFocus) {
       return;
     }
@@ -1297,7 +1286,6 @@
   bool get hovering => _hovering;
   bool _hovering;
   set hovering(bool value) {
-    assert(value != null);
     if (value == _hovering) {
       return;
     }
@@ -1310,7 +1298,6 @@
   bool _hoveringThumb = false;
   bool get hoveringThumb => _hoveringThumb;
   set hoveringThumb(bool value) {
-    assert(value != null);
     if (value == _hoveringThumb) {
       return;
     }
@@ -1528,6 +1515,9 @@
 
   @override
   void handleEvent(PointerEvent event, BoxHitTestEntry entry) {
+    if (!_state.mounted) {
+      return;
+    }
     assert(debugHandleEvent(event, entry));
     if (event is PointerDownEvent && isInteractive) {
       // We need to add the drag first so that it has priority.
@@ -1888,7 +1878,7 @@
 // Design token database by the script:
 //   dev/tools/gen_defaults/bin/gen_defaults.dart.
 
-// Token database version: v0_143
+// Token database version: v0_158
 
 class _SliderDefaultsM3 extends SliderThemeData {
   _SliderDefaultsM3(this.context)
diff --git a/framework/lib/src/material/slider_theme.dart b/framework/lib/src/material/slider_theme.dart
index 025f665..c01c918 100644
--- a/framework/lib/src/material/slider_theme.dart
+++ b/framework/lib/src/material/slider_theme.dart
@@ -54,8 +54,7 @@
     super.key,
     required this.data,
     required super.child,
-  }) : assert(child != null),
-       assert(data != null);
+  });
 
   /// Specifies the color and shape values for descendant slider widgets.
   final SliderThemeData data;
@@ -310,10 +309,6 @@
     required Color primaryColorLight,
     required TextStyle valueIndicatorTextStyle,
   }) {
-    assert(primaryColor != null);
-    assert(primaryColorDark != null);
-    assert(primaryColorLight != null);
-    assert(valueIndicatorTextStyle != null);
 
     // These are Material Design defaults, and are used to derive
     // component Colors (with opacity) from base colors.
@@ -655,9 +650,6 @@
   ///
   /// {@macro dart.ui.shadow.lerp}
   static SliderThemeData lerp(SliderThemeData a, SliderThemeData b, double t) {
-    assert(a != null);
-    assert(b != null);
-    assert(t != null);
     return SliderThemeData(
       trackHeight: lerpDouble(a.trackHeight, b.trackHeight, t),
       activeTrackColor: Color.lerp(a.activeTrackColor, b.activeTrackColor, t),
@@ -1508,10 +1500,6 @@
     bool isEnabled = false,
     bool isDiscrete = false,
   }) {
-    assert(isEnabled != null);
-    assert(isDiscrete != null);
-    assert(parentBox != null);
-    assert(sliderTheme != null);
     final double thumbWidth = sliderTheme.thumbShape!.getPreferredSize(isEnabled, isDiscrete).width;
     final double overlayWidth = sliderTheme.overlayShape!.getPreferredSize(isEnabled, isDiscrete).width;
     final double trackHeight = sliderTheme.trackHeight!;
@@ -1570,20 +1558,11 @@
     bool isDiscrete = false,
     bool isEnabled = false,
   }) {
-    assert(context != null);
-    assert(offset != null);
-    assert(parentBox != null);
-    assert(sliderTheme != null);
     assert(sliderTheme.disabledActiveTrackColor != null);
     assert(sliderTheme.disabledInactiveTrackColor != null);
     assert(sliderTheme.activeTrackColor != null);
     assert(sliderTheme.inactiveTrackColor != null);
     assert(sliderTheme.thumbShape != null);
-    assert(enableAnimation != null);
-    assert(textDirection != null);
-    assert(thumbCenter != null);
-    assert(isEnabled != null);
-    assert(isDiscrete != null);
     // If the slider [SliderThemeData.trackHeight] is less than or equal to 0,
     // then it makes no difference whether the track is painted or not,
     // therefore the painting can be a no-op.
@@ -1692,18 +1671,11 @@
     bool isEnabled = false,
     double additionalActiveTrackHeight = 2,
   }) {
-    assert(context != null);
-    assert(offset != null);
-    assert(parentBox != null);
-    assert(sliderTheme != null);
     assert(sliderTheme.disabledActiveTrackColor != null);
     assert(sliderTheme.disabledInactiveTrackColor != null);
     assert(sliderTheme.activeTrackColor != null);
     assert(sliderTheme.inactiveTrackColor != null);
     assert(sliderTheme.thumbShape != null);
-    assert(enableAnimation != null);
-    assert(textDirection != null);
-    assert(thumbCenter != null);
     // If the slider [SliderThemeData.trackHeight] is less than or equal to 0,
     // then it makes no difference whether the track is painted or not,
     // therefore the painting can be a no-op.
@@ -1842,12 +1814,7 @@
     bool isEnabled = false,
     bool isDiscrete = false,
   }) {
-    assert(parentBox != null);
-    assert(offset != null);
-    assert(sliderTheme != null);
     assert(sliderTheme.overlayShape != null);
-    assert(isEnabled != null);
-    assert(isDiscrete != null);
     final double overlayWidth = sliderTheme.overlayShape!.getPreferredSize(isEnabled, isDiscrete).width;
     final double trackHeight = sliderTheme.trackHeight!;
     assert(overlayWidth >= 0);
@@ -1874,21 +1841,12 @@
     bool isDiscrete = false,
     required TextDirection textDirection,
   }) {
-    assert(context != null);
-    assert(offset != null);
-    assert(parentBox != null);
-    assert(sliderTheme != null);
     assert(sliderTheme.disabledActiveTrackColor != null);
     assert(sliderTheme.disabledInactiveTrackColor != null);
     assert(sliderTheme.activeTrackColor != null);
     assert(sliderTheme.inactiveTrackColor != null);
     assert(sliderTheme.rangeThumbShape != null);
     assert(enableAnimation != null);
-    assert(startThumbCenter != null);
-    assert(endThumbCenter != null);
-    assert(isEnabled != null);
-    assert(isDiscrete != null);
-    assert(textDirection != null);
     // Assign the track segment paints, which are left: active, right: inactive,
     // but reversed for right to left text.
     final ColorTween activeTrackColorTween = ColorTween(begin: sliderTheme.disabledActiveTrackColor, end: sliderTheme.activeTrackColor);
@@ -1973,13 +1931,8 @@
     bool isEnabled = false,
     bool isDiscrete = false,
   }) {
-    assert(parentBox != null);
-    assert(offset != null);
-    assert(sliderTheme != null);
     assert(sliderTheme.overlayShape != null);
     assert(sliderTheme.trackHeight != null);
-    assert(isEnabled != null);
-    assert(isDiscrete != null);
     final double overlayWidth = sliderTheme.overlayShape!.getPreferredSize(isEnabled, isDiscrete).width;
     final double trackHeight = sliderTheme.trackHeight!;
     assert(overlayWidth >= 0);
@@ -2007,21 +1960,11 @@
     required TextDirection textDirection,
     double additionalActiveTrackHeight = 2,
   }) {
-    assert(context != null);
-    assert(offset != null);
-    assert(parentBox != null);
-    assert(sliderTheme != null);
     assert(sliderTheme.disabledActiveTrackColor != null);
     assert(sliderTheme.disabledInactiveTrackColor != null);
     assert(sliderTheme.activeTrackColor != null);
     assert(sliderTheme.inactiveTrackColor != null);
     assert(sliderTheme.rangeThumbShape != null);
-    assert(enableAnimation != null);
-    assert(startThumbCenter != null);
-    assert(endThumbCenter != null);
-    assert(isEnabled != null);
-    assert(isDiscrete != null);
-    assert(textDirection != null);
 
     if (sliderTheme.trackHeight == null || sliderTheme.trackHeight! <= 0) {
       return;
@@ -2139,9 +2082,7 @@
     required SliderThemeData sliderTheme,
     required bool isEnabled,
   }) {
-    assert(sliderTheme != null);
     assert(sliderTheme.trackHeight != null);
-    assert(isEnabled != null);
     // The tick marks are tiny circles. If no radius is provided, then the
     // radius is defaulted to be a fraction of the
     // [SliderThemeData.trackHeight]. The fraction is 1/4.
@@ -2159,18 +2100,10 @@
     required Offset thumbCenter,
     required bool isEnabled,
   }) {
-    assert(context != null);
-    assert(center != null);
-    assert(parentBox != null);
-    assert(sliderTheme != null);
     assert(sliderTheme.disabledActiveTickMarkColor != null);
     assert(sliderTheme.disabledInactiveTickMarkColor != null);
     assert(sliderTheme.activeTickMarkColor != null);
     assert(sliderTheme.inactiveTickMarkColor != null);
-    assert(enableAnimation != null);
-    assert(textDirection != null);
-    assert(thumbCenter != null);
-    assert(isEnabled != null);
     // The paint color of the tick mark depends on its position relative
     // to the thumb and the text direction.
     Color? begin;
@@ -2237,9 +2170,7 @@
     required SliderThemeData sliderTheme,
     bool isEnabled = false,
   }) {
-    assert(sliderTheme != null);
     assert(sliderTheme.trackHeight != null);
-    assert(isEnabled != null);
     return Size.fromRadius(tickMarkRadius ?? sliderTheme.trackHeight! / 4);
   }
 
@@ -2255,19 +2186,10 @@
     bool isEnabled = false,
     required TextDirection textDirection,
   }) {
-    assert(context != null);
-    assert(center != null);
-    assert(parentBox != null);
-    assert(sliderTheme != null);
     assert(sliderTheme.disabledActiveTickMarkColor != null);
     assert(sliderTheme.disabledInactiveTickMarkColor != null);
     assert(sliderTheme.activeTickMarkColor != null);
     assert(sliderTheme.inactiveTickMarkColor != null);
-    assert(enableAnimation != null);
-    assert(startThumbCenter != null);
-    assert(endThumbCenter != null);
-    assert(isEnabled != null);
-    assert(textDirection != null);
 
     final bool isBetweenThumbs;
     switch (textDirection) {
@@ -2426,10 +2348,6 @@
     required double textScaleFactor,
     required Size sizeWithOverflow,
   }) {
-    assert(context != null);
-    assert(center != null);
-    assert(enableAnimation != null);
-    assert(sliderTheme != null);
     assert(sliderTheme.disabledThumbColor != null);
     assert(sliderTheme.thumbColor != null);
 
@@ -2495,7 +2413,7 @@
     this.disabledThumbRadius,
     this.elevation = 1.0,
     this.pressedElevation = 6.0,
-  }) : assert(enabledThumbRadius != null);
+  });
 
   /// The preferred radius of the round thumb shape when the slider is enabled.
   ///
@@ -2538,13 +2456,8 @@
     Thumb? thumb,
     bool? isPressed,
   }) {
-    assert(context != null);
-    assert(center != null);
-    assert(activationAnimation != null);
-    assert(sliderTheme != null);
     assert(sliderTheme.showValueIndicator != null);
     assert(sliderTheme.overlappingShapeStrokeColor != null);
-    assert(enableAnimation != null);
     final Canvas canvas = context.canvas;
     final Tween<double> radiusTween = Tween<double>(
       begin: _disabledThumbRadius,
@@ -2642,15 +2555,6 @@
     required double textScaleFactor,
     required Size sizeWithOverflow,
   }) {
-    assert(context != null);
-    assert(center != null);
-    assert(activationAnimation != null);
-    assert(enableAnimation != null);
-    assert(labelPainter != null);
-    assert(parentBox != null);
-    assert(sliderTheme != null);
-    assert(textDirection != null);
-    assert(value != null);
 
     final Canvas canvas = context.canvas;
     final Tween<double> radiusTween = Tween<double>(
@@ -2748,8 +2652,7 @@
     required TextPainter labelPainter,
     required double textScaleFactor,
   }) {
-    assert(labelPainter != null);
-    assert(textScaleFactor != null && textScaleFactor >= 0);
+    assert(textScaleFactor >= 0);
     return _pathPainter.getPreferredSize(labelPainter, textScaleFactor);
   }
 
@@ -2820,7 +2723,6 @@
     TextPainter labelPainter,
     double textScaleFactor,
   ) {
-    assert(labelPainter != null);
     return Size(
       _upperRectangleWidth(labelPainter, 1, textScaleFactor),
       labelPainter.height + _labelPadding,
@@ -2976,13 +2878,6 @@
     required double textScaleFactor,
     required Size sizeWithOverflow,
   }) {
-    assert(context != null);
-    assert(center != null);
-    assert(activationAnimation != null);
-    assert(enableAnimation != null);
-    assert(labelPainter != null);
-    assert(parentBox != null);
-    assert(sliderTheme != null);
     assert(!sizeWithOverflow.isEmpty);
     final ColorTween enableColor = ColorTween(
       begin: sliderTheme.disabledThumbColor,
@@ -3025,8 +2920,7 @@
     required TextPainter labelPainter,
     required double textScaleFactor,
   }) {
-    assert(labelPainter != null);
-    assert(textScaleFactor != null && textScaleFactor >= 0);
+    assert(textScaleFactor >= 0);
     return _pathPainter.getPreferredSize(labelPainter, textScaleFactor);
   }
 
@@ -3065,13 +2959,6 @@
     double? textScaleFactor,
     Size? sizeWithOverflow,
   }) {
-    assert(context != null);
-    assert(center != null);
-    assert(activationAnimation != null);
-    assert(enableAnimation != null);
-    assert(labelPainter != null);
-    assert(parentBox != null);
-    assert(sliderTheme != null);
     assert(!sizeWithOverflow!.isEmpty);
     final ColorTween enableColor = ColorTween(
       begin: sliderTheme.disabledThumbColor,
@@ -3135,8 +3022,7 @@
     TextPainter labelPainter,
     double textScaleFactor,
   ) {
-    assert(labelPainter != null);
-    assert(textScaleFactor != null && textScaleFactor >= 0);
+    assert(textScaleFactor >= 0);
     final double width = math.max(_minLabelWidth * textScaleFactor, labelPainter.width) + _labelPadding * 2 * textScaleFactor;
     return Size(width, _preferredHeight * textScaleFactor);
   }
@@ -3556,7 +3442,6 @@
     TextPainter labelPainter,
     double textScaleFactor,
   ) {
-    assert(labelPainter != null);
     final double width = math.max(_minLabelWidth, labelPainter.width) + _labelPadding * 2 * textScaleFactor;
     return Size(width, _preferredHeight * textScaleFactor);
   }
diff --git a/framework/lib/src/material/snack_bar.dart b/framework/lib/src/material/snack_bar.dart
index 0587d1b..5d45c47 100644
--- a/framework/lib/src/material/snack_bar.dart
+++ b/framework/lib/src/material/snack_bar.dart
@@ -71,8 +71,8 @@
 
 /// A button for a [SnackBar], known as an "action".
 ///
-/// Snack bar actions are always enabled. If you want to disable a snack bar
-/// action, simply don't include it in the snack bar.
+/// Snack bar actions are always enabled. Instead of disabling a snack bar
+/// action, avoid including it in the snack bar in the first place.
 ///
 /// Snack bar actions can only be pressed once. Subsequent presses are ignored.
 ///
@@ -90,14 +90,13 @@
     this.disabledTextColor,
     required this.label,
     required this.onPressed,
-  }) : assert(label != null),
-       assert(onPressed != null);
+  });
 
   /// The button label color. If not provided, defaults to
   /// [SnackBarThemeData.actionTextColor].
   ///
   /// If [textColor] is a [MaterialStateColor], then the text color will be
-  /// be resolved against the set of [MaterialState]s that the action text
+  /// resolved against the set of [MaterialState]s that the action text
   /// is in, thus allowing for different colors for states such as pressed,
   /// hovered and others.
   final Color? textColor;
@@ -243,13 +242,10 @@
     this.dismissDirection = DismissDirection.down,
     this.clipBehavior = Clip.hardEdge,
   }) : assert(elevation == null || elevation >= 0.0),
-       assert(content != null),
        assert(
          width == null || margin == null,
          'Width and margin can not be used together',
-       ),
-       assert(duration != null),
-       assert(clipBehavior != null);
+       );
 
   /// The primary content of the snack bar.
   ///
@@ -370,7 +366,7 @@
   /// inverse surface.
   ///
   /// If [closeIconColor] is a [MaterialStateColor], then the icon color will be
-  /// be resolved against the set of [MaterialState]s that the action text
+  /// resolved against the set of [MaterialState]s that the action text
   /// is in, thus allowing for different colors for states such as pressed,
   /// hovered and others.
   final Color? closeIconColor;
@@ -485,7 +481,7 @@
   @override
   Widget build(BuildContext context) {
     assert(debugCheckHasMediaQuery(context));
-    final MediaQueryData mediaQueryData = MediaQuery.of(context);
+    final bool accessibleNavigation = MediaQuery.accessibleNavigationOf(context);
     assert(widget.animation != null);
     final ThemeData theme = Theme.of(context);
     final ColorScheme colorScheme = theme.colorScheme;
@@ -599,7 +595,7 @@
 
     final EdgeInsets margin = widget.margin?.resolve(TextDirection.ltr) ?? snackBarTheme.insetPadding ?? defaults.insetPadding!;
 
-    final double snackBarWidth = widget.width ?? mediaQueryData.size.width - (margin.left + margin.right);
+    final double snackBarWidth = widget.width ?? MediaQuery.sizeOf(context).width - (margin.left + margin.right);
     // Action and Icon will overflow to a new line if their width is greater
     // than one quarter of the total Snack Bar width.
     final bool actionLineOverflow =
@@ -675,7 +671,7 @@
       color: backgroundColor,
       child: Theme(
         data: effectiveTheme,
-        child: mediaQueryData.accessibleNavigation || theme.useMaterial3
+        child: accessibleNavigation || theme.useMaterial3
             ? snackBar
             : FadeTransition(
                 opacity: fadeOutAnimation,
@@ -723,7 +719,7 @@
     );
 
     final Widget snackBarTransition;
-    if (mediaQueryData.accessibleNavigation) {
+    if (accessibleNavigation) {
       snackBarTransition = snackBar;
     } else if (isFloatingSnackBar && !theme.useMaterial3) {
       snackBarTransition = FadeTransition(
@@ -828,7 +824,7 @@
 // Design token database by the script:
 //   dev/tools/gen_defaults/bin/gen_defaults.dart.
 
-// Token database version: v0_143
+// Token database version: v0_158
 
 class _SnackbarDefaultsM3 extends SnackBarThemeData {
     _SnackbarDefaultsM3(this.context);
@@ -883,10 +879,6 @@
 
   @override
   bool get showCloseIcon => false;
-
-  @override
-  Color get closeIconColor => _colors.onInverseSurface;
-  }
-
+}
 
 // END GENERATED TOKEN PROPERTIES - Snackbar
diff --git a/framework/lib/src/material/snack_bar_theme.dart b/framework/lib/src/material/snack_bar_theme.dart
index c35c8d6..a4b4c88 100644
--- a/framework/lib/src/material/snack_bar_theme.dart
+++ b/framework/lib/src/material/snack_bar_theme.dart
@@ -67,7 +67,7 @@
   })  : assert(elevation == null || elevation >= 0.0),
         assert(
             width == null ||
-                (width != null && identical(behavior, SnackBarBehavior.floating)),
+                (identical(behavior, SnackBarBehavior.floating)),
             'Width can only be set if behaviour is SnackBarBehavior.floating');
   /// Overrides the default value for [SnackBar.backgroundColor].
   ///
@@ -169,7 +169,6 @@
   ///
   /// {@macro dart.ui.shadow.lerp}
   static SnackBarThemeData lerp(SnackBarThemeData? a, SnackBarThemeData? b, double t) {
-    assert(t != null);
     return SnackBarThemeData(
       backgroundColor: Color.lerp(a?.backgroundColor, b?.backgroundColor, t),
       actionTextColor: Color.lerp(a?.actionTextColor, b?.actionTextColor, t),
diff --git a/framework/lib/src/material/spell_check_suggestions_toolbar.dart b/framework/lib/src/material/spell_check_suggestions_toolbar.dart
new file mode 100644
index 0000000..44a8a78
--- /dev/null
+++ b/framework/lib/src/material/spell_check_suggestions_toolbar.dart
@@ -0,0 +1,221 @@
+// 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:flute/cupertino.dart';
+import 'package:flute/services.dart' show SuggestionSpan;
+
+import 'adaptive_text_selection_toolbar.dart';
+import 'colors.dart';
+import 'material.dart';
+import 'spell_check_suggestions_toolbar_layout_delegate.dart';
+import 'text_selection_toolbar.dart';
+import 'text_selection_toolbar_text_button.dart';
+
+// The default height of the SpellCheckSuggestionsToolbar, which
+// assumes there are the maximum number of spell check suggestions available, 3.
+// Size eyeballed on Pixel 4 emulator running Android API 31.
+const double _kDefaultToolbarHeight = 193.0;
+
+/// The default spell check suggestions toolbar for Android.
+///
+/// Tries to position itself below the [anchor], but if it doesn't fit, then it
+/// readjusts to fit above bottom view insets.
+class SpellCheckSuggestionsToolbar extends StatelessWidget {
+  /// Constructs a [SpellCheckSuggestionsToolbar].
+  const SpellCheckSuggestionsToolbar({
+    super.key,
+    required this.anchor,
+    required this.buttonItems,
+  });
+
+  /// {@template flutter.material.SpellCheckSuggestionsToolbar.anchor}
+  /// The focal point below which the toolbar attempts to position itself.
+  /// {@endtemplate}
+  final Offset anchor;
+
+  /// The [ContextMenuButtonItem]s that will be turned into the correct button
+  /// widgets and displayed in the spell check suggestions toolbar.
+  ///
+  /// See also:
+  ///
+  ///  * [AdaptiveTextSelectionToolbar.buttonItems], the list of
+  ///    [ContextMenuButtonItem]s that are used to build the buttons of the
+  ///    text selection toolbar.
+  final List<ContextMenuButtonItem> buttonItems;
+
+  /// Padding between the toolbar and the anchor. Eyeballed on Pixel 4 emulator
+  /// running Android API 31.
+  static const double kToolbarContentDistanceBelow = TextSelectionToolbar.kHandleSize - 3.0;
+
+  /// Builds the default Android Material spell check suggestions toolbar.
+  static Widget _spellCheckSuggestionsToolbarBuilder(BuildContext context, Widget child) {
+    return _SpellCheckSuggestionsToolbarContainer(
+      child: child,
+    );
+  }
+
+  /// Builds the button items for the toolbar based on the available
+  /// spell check suggestions.
+  static List<ContextMenuButtonItem>? buildButtonItems(
+    BuildContext context,
+    EditableTextState editableTextState,
+  ) {
+    // Determine if composing region is misspelled.
+    final SuggestionSpan? spanAtCursorIndex =
+      editableTextState.findSuggestionSpanAtCursorIndex(
+        editableTextState.currentTextEditingValue.selection.baseOffset,
+    );
+
+    if (spanAtCursorIndex == null) {
+      return null;
+    }
+
+    final List<ContextMenuButtonItem> buttonItems = <ContextMenuButtonItem>[];
+
+    // Build suggestion buttons.
+    for (final String suggestion in spanAtCursorIndex.suggestions) {
+      buttonItems.add(ContextMenuButtonItem(
+        onPressed: () {
+          editableTextState
+            .replaceComposingRegion(
+              SelectionChangedCause.toolbar,
+              suggestion,
+          );
+        },
+        label: suggestion,
+      ));
+    }
+
+    // Build delete button.
+    final ContextMenuButtonItem deleteButton =
+      ContextMenuButtonItem(
+        onPressed: () {
+          editableTextState.replaceComposingRegion(
+            SelectionChangedCause.toolbar,
+            '',
+          );
+        },
+        type: ContextMenuButtonType.delete,
+    );
+    buttonItems.add(deleteButton);
+
+    return buttonItems;
+  }
+
+  /// Determines the Offset that the toolbar will be anchored to.
+  static Offset getToolbarAnchor(TextSelectionToolbarAnchors anchors) {
+    return anchors.secondaryAnchor == null ? anchors.primaryAnchor : anchors.secondaryAnchor!;
+  }
+
+  /// Builds the toolbar buttons based on the [buttonItems].
+  List<Widget> _buildToolbarButtons(BuildContext context) {
+    return buttonItems.map((ContextMenuButtonItem buttonItem) {
+      final TextSelectionToolbarTextButton button =
+        TextSelectionToolbarTextButton(
+          padding: const EdgeInsets.fromLTRB(20, 0, 0, 0),
+          onPressed: buttonItem.onPressed,
+          alignment: Alignment.centerLeft,
+          child: Text(
+            AdaptiveTextSelectionToolbar.getButtonLabel(context, buttonItem),
+            style: buttonItem.type == ContextMenuButtonType.delete ? const TextStyle(color: Colors.blue) : null,
+          ),
+      );
+
+      if (buttonItem.type != ContextMenuButtonType.delete) {
+        return button;
+      }
+      return DecoratedBox(
+        decoration: const BoxDecoration(border: Border(top: BorderSide(color: Colors.grey))),
+        child: button,
+      );
+    }).toList();
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    // Adjust toolbar height if needed.
+    final double spellCheckSuggestionsToolbarHeight =
+        _kDefaultToolbarHeight - (48.0 * (4 - buttonItems.length));
+    // Incorporate the padding distance between the content and toolbar.
+    final Offset anchorPadded =
+        anchor + const Offset(0.0, kToolbarContentDistanceBelow);
+    final MediaQueryData mediaQueryData = MediaQuery.of(context);
+    final double softKeyboardViewInsetsBottom = mediaQueryData.viewInsets.bottom;
+    final double paddingAbove = mediaQueryData.padding.top + CupertinoTextSelectionToolbar.kToolbarScreenPadding;
+    // Makes up for the Padding.
+    final Offset localAdjustment = Offset(CupertinoTextSelectionToolbar.kToolbarScreenPadding, paddingAbove);
+
+    return Padding(
+      padding: EdgeInsets.fromLTRB(
+        CupertinoTextSelectionToolbar.kToolbarScreenPadding,
+        kToolbarContentDistanceBelow,
+        CupertinoTextSelectionToolbar.kToolbarScreenPadding,
+        CupertinoTextSelectionToolbar.kToolbarScreenPadding + softKeyboardViewInsetsBottom,
+      ),
+      child: CustomSingleChildLayout(
+        delegate: SpellCheckSuggestionsToolbarLayoutDelegate(
+          anchor: anchorPadded - localAdjustment,
+        ),
+        child: AnimatedSize(
+          // This duration was eyeballed on a Pixel 2 emulator running Android
+          // API 28 for the Material TextSelectionToolbar.
+          duration: const Duration(milliseconds: 140),
+          child: _spellCheckSuggestionsToolbarBuilder(context, _SpellCheckSuggestsionsToolbarItemsLayout(
+            height: spellCheckSuggestionsToolbarHeight,
+            children: <Widget>[..._buildToolbarButtons(context)],
+          )),
+        ),
+      ),
+    );
+  }
+}
+
+/// The Material-styled toolbar outline for the spell check suggestions
+/// toolbar.
+class _SpellCheckSuggestionsToolbarContainer extends StatelessWidget {
+  const _SpellCheckSuggestionsToolbarContainer({
+    required this.child,
+  });
+
+  final Widget child;
+
+  @override
+  Widget build(BuildContext context) {
+    return Material(
+      // This elevation was eyeballed on a Pixel 4 emulator running Android
+      // API 31 for the SpellCheckSuggestionsToolbar.
+      elevation: 2.0,
+      type: MaterialType.card,
+      child: child,
+    );
+  }
+}
+
+/// Renders the spell check suggestions toolbar items in the correct positions
+/// in the menu.
+class _SpellCheckSuggestsionsToolbarItemsLayout extends StatelessWidget {
+  const _SpellCheckSuggestsionsToolbarItemsLayout({
+    required this.height,
+    required this.children,
+  });
+
+  final double height;
+
+  final List<Widget> children;
+
+  @override
+  Widget build(BuildContext context) {
+    return SizedBox(
+      // This width was eyeballed on a Pixel 4 emulator running Android
+      // API 31 for the SpellCheckSuggestionsToolbar.
+      width: 165,
+      height: height,
+      child: Column(
+        mainAxisSize: MainAxisSize.min,
+        crossAxisAlignment: CrossAxisAlignment.stretch,
+        children: children,
+      ),
+    );
+  }
+}
diff --git a/framework/lib/src/material/spell_check_suggestions_toolbar_layout_delegate.dart b/framework/lib/src/material/spell_check_suggestions_toolbar_layout_delegate.dart
new file mode 100644
index 0000000..f3074e9
--- /dev/null
+++ b/framework/lib/src/material/spell_check_suggestions_toolbar_layout_delegate.dart
@@ -0,0 +1,50 @@
+// 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:flute/rendering.dart';
+import 'package:flute/widgets.dart' show TextSelectionToolbarLayoutDelegate;
+
+/// Positions the toolbar below [anchor] or adjusts it higher to fit above
+/// the bottom view insets, if applicable.
+///
+/// See also:
+///
+///   * [SpellCheckSuggestionsToolbar], which uses this to position itself.
+class SpellCheckSuggestionsToolbarLayoutDelegate extends SingleChildLayoutDelegate {
+  /// Creates an instance of [SpellCheckSuggestionsToolbarLayoutDelegate].
+  SpellCheckSuggestionsToolbarLayoutDelegate({
+    required this.anchor,
+  });
+
+  /// {@macro flutter.material.SpellCheckSuggestionsToolbar.anchor}
+  ///
+  /// Should be provided in local coordinates.
+  final Offset anchor;
+
+  @override
+  BoxConstraints getConstraintsForChild(BoxConstraints constraints) {
+    return constraints.loosen();
+  }
+
+  @override
+  Offset getPositionForChild(Size size, Size childSize) {
+    return Offset(
+      TextSelectionToolbarLayoutDelegate.centerOn(
+        anchor.dx,
+        childSize.width,
+        size.width,
+      ),
+      // Positions child (of childSize) just enough upwards to fit within size
+      // if it otherwise does not fit below the anchor.
+      anchor.dy + childSize.height > size.height
+          ? size.height - childSize.height
+          : anchor.dy,
+    );
+  }
+
+  @override
+  bool shouldRelayout(SpellCheckSuggestionsToolbarLayoutDelegate oldDelegate) {
+    return anchor != oldDelegate.anchor;
+  }
+}
diff --git a/framework/lib/src/material/stepper.dart b/framework/lib/src/material/stepper.dart
old mode 100755
new mode 100644
index e9aa608..4a7f5a2
--- a/framework/lib/src/material/stepper.dart
+++ b/framework/lib/src/material/stepper.dart
@@ -139,9 +139,7 @@
     this.state = StepState.indexed,
     this.isActive = false,
     this.label,
-  }) : assert(title != null),
-       assert(content != null),
-       assert(state != null);
+  });
 
   /// The title of the step that typically describes it.
   final Widget title;
@@ -209,10 +207,7 @@
     this.controlsBuilder,
     this.elevation,
     this.margin,
-  }) : assert(steps != null),
-       assert(type != null),
-       assert(currentStep != null),
-       assert(0 <= currentStep && currentStep < steps.length);
+  }) : assert(0 <= currentStep && currentStep < steps.length);
 
   /// The steps of the stepper whose titles, subtitles, icons always get shown.
   ///
@@ -378,7 +373,6 @@
   Widget _buildCircleChild(int index, bool oldState) {
     final StepState state = oldState ? _oldStates[index]! : widget.steps[index].state;
     final bool isDarkActive = _isDark() && widget.steps[index].isActive;
-    assert(state != null);
     switch (state) {
       case StepState.indexed:
       case StepState.disabled:
@@ -557,7 +551,6 @@
     final ThemeData themeData = Theme.of(context);
     final TextTheme textTheme = themeData.textTheme;
 
-    assert(widget.steps[index].state != null);
     switch (widget.steps[index].state) {
       case StepState.indexed:
       case StepState.editing:
@@ -578,7 +571,6 @@
     final ThemeData themeData = Theme.of(context);
     final TextTheme textTheme = themeData.textTheme;
 
-    assert(widget.steps[index].state != null);
     switch (widget.steps[index].state) {
       case StepState.indexed:
       case StepState.editing:
@@ -599,7 +591,6 @@
     final ThemeData themeData = Theme.of(context);
     final TextTheme textTheme = themeData.textTheme;
 
-    assert(widget.steps[index].state != null);
     switch (widget.steps[index].state) {
       case StepState.indexed:
       case StepState.editing:
@@ -846,7 +837,6 @@
       }
       return true;
     }());
-    assert(widget.type != null);
     switch (widget.type) {
       case StepperType.vertical:
         return _buildVertical();
diff --git a/framework/lib/src/material/switch.dart b/framework/lib/src/material/switch.dart
index 4511dac..0170814 100644
--- a/framework/lib/src/material/switch.dart
+++ b/framework/lib/src/material/switch.dart
@@ -117,7 +117,7 @@
     this.onFocusChange,
     this.autofocus = false,
   })  : _switchType = _SwitchType.material,
-        assert(dragStartBehavior != null),
+        applyCupertinoTheme = false,
         assert(activeThumbImage != null || onActiveThumbImageError == null),
         assert(inactiveThumbImage != null || onInactiveThumbImageError == null);
 
@@ -161,8 +161,8 @@
     this.focusNode,
     this.onFocusChange,
     this.autofocus = false,
-  })  : assert(autofocus != null),
-        assert(activeThumbImage != null || onActiveThumbImageError == null),
+    this.applyCupertinoTheme,
+  })  : assert(activeThumbImage != null || onActiveThumbImageError == null),
         assert(inactiveThumbImage != null || onInactiveThumbImageError == null),
         _switchType = _SwitchType.adaptive;
 
@@ -381,6 +381,9 @@
 
   final _SwitchType _switchType;
 
+  /// {@macro flutter.cupertino.CupertinoSwitch.applyTheme}
+  final bool? applyCupertinoTheme;
+
   /// {@macro flutter.cupertino.CupertinoSwitch.dragStartBehavior}
   final DragStartBehavior dragStartBehavior;
 
@@ -495,6 +498,7 @@
           onChanged: onChanged,
           activeColor: activeColor,
           trackColor: inactiveTrackColor,
+          applyTheme: applyCupertinoTheme,
         ),
       ),
     );
@@ -537,7 +541,6 @@
 
       case _SwitchType.adaptive: {
         final ThemeData theme = Theme.of(context);
-        assert(theme.platform != null);
         switch (theme.platform) {
           case TargetPlatform.android:
           case TargetPlatform.fuchsia:
@@ -586,8 +589,7 @@
     this.focusNode,
     this.onFocusChange,
     this.autofocus = false,
-  })  : assert(dragStartBehavior != null),
-        assert(activeThumbImage != null || onActiveThumbImageError == null),
+  })  : assert(activeThumbImage != null || onActiveThumbImageError == null),
         assert(inactiveThumbImage != null || onInactiveThumbImageError == null);
 
   final bool value;
@@ -928,7 +930,6 @@
   Color get activeIconColor => _activeIconColor!;
   Color? _activeIconColor;
   set activeIconColor(Color value) {
-    assert(value != null);
     if (value == _activeIconColor) {
       return;
     }
@@ -939,7 +940,6 @@
   Color get inactiveIconColor => _inactiveIconColor!;
   Color? _inactiveIconColor;
   set inactiveIconColor(Color value) {
-    assert(value != null);
     if (value == _inactiveIconColor) {
       return;
     }
@@ -972,7 +972,6 @@
   double get activeThumbRadius => _activeThumbRadius!;
   double? _activeThumbRadius;
   set activeThumbRadius(double value) {
-    assert(value != null);
     if (value == _activeThumbRadius) {
       return;
     }
@@ -983,7 +982,6 @@
   double get inactiveThumbRadius => _inactiveThumbRadius!;
   double? _inactiveThumbRadius;
   set inactiveThumbRadius(double value) {
-    assert(value != null);
     if (value == _inactiveThumbRadius) {
       return;
     }
@@ -994,7 +992,6 @@
   double get pressedThumbRadius => _pressedThumbRadius!;
   double? _pressedThumbRadius;
   set pressedThumbRadius(double value) {
-    assert(value != null);
     if (value == _pressedThumbRadius) {
       return;
     }
@@ -1015,7 +1012,6 @@
   Size get transitionalThumbSize => _transitionalThumbSize!;
   Size? _transitionalThumbSize;
   set transitionalThumbSize(Size value) {
-    assert(value != null);
     if (value == _transitionalThumbSize) {
       return;
     }
@@ -1026,7 +1022,6 @@
   double get trackHeight => _trackHeight!;
   double? _trackHeight;
   set trackHeight(double value) {
-    assert(value != null);
     if (value == _trackHeight) {
       return;
     }
@@ -1037,7 +1032,6 @@
   double get trackWidth => _trackWidth!;
   double? _trackWidth;
   set trackWidth(double value) {
-    assert(value != null);
     if (value == _trackWidth) {
       return;
     }
@@ -1088,7 +1082,6 @@
   Color get activeTrackColor => _activeTrackColor!;
   Color? _activeTrackColor;
   set activeTrackColor(Color value) {
-    assert(value != null);
     if (value == _activeTrackColor) {
       return;
     }
@@ -1109,7 +1102,6 @@
   Color get inactiveTrackColor => _inactiveTrackColor!;
   Color? _inactiveTrackColor;
   set inactiveTrackColor(Color value) {
-    assert(value != null);
     if (value == _inactiveTrackColor) {
       return;
     }
@@ -1120,7 +1112,6 @@
   ImageConfiguration get configuration => _configuration!;
   ImageConfiguration? _configuration;
   set configuration(ImageConfiguration value) {
-    assert(value != null);
     if (value == _configuration) {
       return;
     }
@@ -1131,7 +1122,6 @@
   TextDirection get textDirection => _textDirection!;
   TextDirection? _textDirection;
   set textDirection(TextDirection value) {
-    assert(value != null);
     if (_textDirection == value) {
       return;
     }
@@ -1142,7 +1132,6 @@
   Color get surfaceColor => _surfaceColor!;
   Color? _surfaceColor;
   set surfaceColor(Color value) {
-    assert(value != null);
     if (value == _surfaceColor) {
       return;
     }
@@ -1635,7 +1624,7 @@
 // Design token database by the script:
 //   dev/tools/gen_defaults/bin/gen_defaults.dart.
 
-// Token database version: v0_143
+// Token database version: v0_158
 
 class _SwitchDefaultsM3 extends SwitchThemeData {
   _SwitchDefaultsM3(BuildContext context)
diff --git a/framework/lib/src/material/switch_list_tile.dart b/framework/lib/src/material/switch_list_tile.dart
index 43070ab..e017c2d 100644
--- a/framework/lib/src/material/switch_list_tile.dart
+++ b/framework/lib/src/material/switch_list_tile.dart
@@ -57,7 +57,7 @@
 ///
 /// {@tool snippet}
 /// ```dart
-/// Container(
+/// ColoredBox(
 ///   color: Colors.green,
 ///   child: Material(
 ///     child: SwitchListTile(
@@ -89,6 +89,13 @@
 /// ** See code in examples/api/lib/material/switch_list_tile/switch_list_tile.0.dart **
 /// {@end-tool}
 ///
+/// {@tool dartpad}
+/// This sample demonstrates how [SwitchListTile] positions the switch widget
+/// relative to the text in different configurations.
+///
+/// ** See code in examples/api/lib/material/switch_list_tile/switch_list_tile.1.dart **
+/// {@end-tool}
+///
 /// ## Semantics in SwitchListTile
 ///
 /// Since the entirety of the SwitchListTile is interactive, it should represent
@@ -113,7 +120,7 @@
 /// LinkedLabelRadio, that includes an interactive [RichText] widget that
 /// handles tap gestures.
 ///
-/// ** See code in examples/api/lib/material/switch_list_tile/switch_list_tile.1.dart **
+/// ** See code in examples/api/lib/material/switch_list_tile/custom_labeled_switch.0.dart **
 /// {@end-tool}
 ///
 /// ## SwitchListTile isn't exactly what I want
@@ -129,7 +136,7 @@
 /// Here is an example of a custom LabeledSwitch widget, but you can easily
 /// make your own configurable widget.
 ///
-/// ** See code in examples/api/lib/material/switch_list_tile/switch_list_tile.2.dart **
+/// ** See code in examples/api/lib/material/switch_list_tile/custom_labeled_switch.1.dart **
 /// {@end-tool}
 ///
 /// See also:
@@ -180,11 +187,7 @@
     this.enableFeedback,
     this.hoverColor,
   }) : _switchListTileType = _SwitchListTileType.material,
-       assert(value != null),
-       assert(isThreeLine != null),
-       assert(!isThreeLine || subtitle != null),
-       assert(selected != null),
-       assert(autofocus != null);
+       assert(!isThreeLine || subtitle != null);
 
   /// Creates a Material [ListTile] with an adaptive [Switch], following
   /// Material design's
@@ -226,11 +229,7 @@
     this.enableFeedback,
     this.hoverColor,
   }) : _switchListTileType = _SwitchListTileType.adaptive,
-       assert(value != null),
-       assert(isThreeLine != null),
-       assert(!isThreeLine || subtitle != null),
-       assert(selected != null),
-       assert(autofocus != null);
+       assert(!isThreeLine || subtitle != null);
 
   /// Whether this switch is checked.
   ///
@@ -266,7 +265,7 @@
 
   /// The color to use when this switch is on.
   ///
-  /// Defaults to accent color of the current [Theme].
+  /// Defaults to [ColorScheme.secondary] of the current [Theme].
   final Color? activeColor;
 
   /// The color to use on the track when this switch is on.
diff --git a/framework/lib/src/material/tab_bar_theme.dart b/framework/lib/src/material/tab_bar_theme.dart
index bfbc033..5e9f249 100644
--- a/framework/lib/src/material/tab_bar_theme.dart
+++ b/framework/lib/src/material/tab_bar_theme.dart
@@ -127,9 +127,6 @@
   ///
   /// {@macro dart.ui.shadow.lerp}
   static TabBarTheme lerp(TabBarTheme a, TabBarTheme b, double t) {
-    assert(a != null);
-    assert(b != null);
-    assert(t != null);
     return TabBarTheme(
       indicator: Decoration.lerp(a.indicator, b.indicator, t),
       indicatorColor: Color.lerp(a.indicatorColor, b.indicatorColor, t),
diff --git a/framework/lib/src/material/tab_controller.dart b/framework/lib/src/material/tab_controller.dart
index 885f1fc..390b5cb 100644
--- a/framework/lib/src/material/tab_controller.dart
+++ b/framework/lib/src/material/tab_controller.dart
@@ -102,8 +102,8 @@
   /// The `initialIndex` must be valid given [length] and must not be null. If
   /// [length] is zero, then `initialIndex` must be 0 (the default).
   TabController({ int initialIndex = 0, Duration? animationDuration, required this.length, required TickerProvider vsync})
-    : assert(length != null && length >= 0),
-      assert(initialIndex != null && initialIndex >= 0 && (length == 0 || initialIndex < length)),
+    : assert(length >= 0),
+      assert(initialIndex >= 0 && (length == 0 || initialIndex < length)),
       _index = initialIndex,
       _previousIndex = initialIndex,
       _animationDuration = animationDuration ?? kTabScrollDuration,
@@ -177,7 +177,6 @@
   final int length;
 
   void _changeIndex(int value, { Duration? duration, Curve? curve }) {
-    assert(value != null);
     assert(value >= 0 && (value < length || length == 0));
     assert(duration != null || curve == null);
     assert(_indexIsChangingCount >= 0);
@@ -254,7 +253,6 @@
   /// 0.0 and 1.0 implies that the TabBarView has been dragged to the right.
   double get offset => _animationController!.value - _index.toDouble();
   set offset(double value) {
-    assert(value != null);
     assert(value >= -1.0 && value <= 1.0);
     assert(!indexIsChanging);
     if (value == offset) {
@@ -348,8 +346,7 @@
     this.initialIndex = 0,
     required this.child,
     this.animationDuration,
-  }) : assert(initialIndex != null),
-       assert(length >= 0),
+  }) : assert(length >= 0),
        assert(length == 0 || (initialIndex >= 0 && initialIndex < length));
 
   /// The total number of tabs.
diff --git a/framework/lib/src/material/tab_indicator.dart b/framework/lib/src/material/tab_indicator.dart
index d00c474..f9249e2 100644
--- a/framework/lib/src/material/tab_indicator.dart
+++ b/framework/lib/src/material/tab_indicator.dart
@@ -23,8 +23,7 @@
     this.borderRadius,
     this.borderSide = const BorderSide(width: 2.0, color: Colors.white),
     this.insets = EdgeInsets.zero,
-  }) : assert(borderSide != null),
-       assert(insets != null);
+  });
 
   /// The radius of the indicator's corners.
   ///
@@ -71,8 +70,6 @@
   }
 
   Rect _indicatorRectFor(Rect rect, TextDirection textDirection) {
-    assert(rect != null);
-    assert(textDirection != null);
     final Rect indicator = insets.resolve(textDirection).deflateRect(rect);
     return Rect.fromLTWH(
       indicator.left,
@@ -98,15 +95,13 @@
     this.decoration,
     this.borderRadius,
     super.onChanged,
-  )
-    : assert(decoration != null);
+  );
 
   final UnderlineTabIndicator decoration;
   final BorderRadius? borderRadius;
 
   @override
   void paint(Canvas canvas, Offset offset, ImageConfiguration configuration) {
-    assert(configuration != null);
     assert(configuration.size != null);
     final Rect rect = offset & configuration.size!;
     final TextDirection textDirection = configuration.textDirection!;
diff --git a/framework/lib/src/material/tabs.dart b/framework/lib/src/material/tabs.dart
index 6541452..732009f 100644
--- a/framework/lib/src/material/tabs.dart
+++ b/framework/lib/src/material/tabs.dart
@@ -239,8 +239,7 @@
     required TextDirection super.textDirection,
     required super.verticalDirection,
     required this.onPerformLayout,
-  }) : assert(onPerformLayout != null),
-       assert(textDirection != null);
+  });
 
   _LayoutCallback onPerformLayout;
 
@@ -276,7 +275,7 @@
 // upon layout. The tab widths are only used at paint time (see _IndicatorPainter)
 // or in response to input.
 class _TabLabelBar extends Flex {
-  _TabLabelBar({
+  const _TabLabelBar({
     super.children,
     required this.onPerformLayout,
   }) : super(
@@ -332,10 +331,9 @@
     required this.tabKeys,
     required _IndicatorPainter? old,
     required this.indicatorPadding,
+    required this.labelPaddings,
     this.dividerColor,
-  }) : assert(controller != null),
-       assert(indicator != null),
-       super(repaint: controller.animation) {
+  }) : super(repaint: controller.animation) {
     if (old != null) {
       saveTabOffsets(old._currentTabOffsets, old._currentTextDirection);
     }
@@ -347,6 +345,7 @@
   final EdgeInsetsGeometry indicatorPadding;
   final List<GlobalKey> tabKeys;
   final Color? dividerColor;
+  final List<EdgeInsetsGeometry> labelPaddings;
 
   // _currentTabOffsets and _currentTextDirection are set each time TabBar
   // layout is completed. These values can be null when TabBar contains no
@@ -402,9 +401,11 @@
 
     if (indicatorSize == TabBarIndicatorSize.label) {
       final double tabWidth = tabKeys[tabIndex].currentContext!.size!.width;
-      final double delta = ((tabRight - tabLeft) - tabWidth) / 2.0;
-      tabLeft += delta;
-      tabRight -= delta;
+      final EdgeInsetsGeometry labelPadding = labelPaddings[tabIndex];
+      final EdgeInsets insets = labelPadding.resolve(_currentTextDirection);
+      final double delta = ((tabRight - tabLeft) - (tabWidth + insets.horizontal)) / 2.0;
+      tabLeft += delta + insets.left;
+      tabRight = tabLeft + tabWidth;
     }
 
     final EdgeInsets insets = indicatorPadding.resolve(_currentTextDirection);
@@ -655,11 +656,7 @@
     this.physics,
     this.splashFactory,
     this.splashBorderRadius,
-  }) : assert(tabs != null),
-       assert(isScrollable != null),
-       assert(dragStartBehavior != null),
-       assert(indicator != null || (indicatorWeight != null && indicatorWeight > 0.0)),
-       assert(indicator != null || (indicatorPadding != null));
+  }) : assert(indicator != null || (indicatorWeight > 0.0));
 
   /// Typically a list of two or more [Tab] widgets.
   ///
@@ -952,6 +949,7 @@
   int? _currentIndex;
   late double _tabStripWidth;
   late List<GlobalKey> _tabKeys;
+  late List<EdgeInsetsGeometry> _labelPaddings;
   bool _debugHasScheduledValidTabsCountCheck = false;
 
   @override
@@ -960,6 +958,7 @@
     // If indicatorSize is TabIndicatorSize.label, _tabKeys[i] is used to find
     // the width of tab widget i. See _IndicatorPainter.indicatorRect().
     _tabKeys = widget.tabs.map((Widget tab) => GlobalKey()).toList();
+    _labelPaddings = List<EdgeInsetsGeometry>.filled(widget.tabs.length, EdgeInsets.zero, growable: true);
   }
 
   Decoration _getIndicator() {
@@ -1063,6 +1062,7 @@
       tabKeys: _tabKeys,
       old: _indicatorPainter,
       dividerColor: theme.useMaterial3 ? widget.dividerColor ?? defaults.dividerColor : null,
+      labelPaddings: _labelPaddings,
     );
   }
 
@@ -1098,8 +1098,10 @@
     if (widget.tabs.length > _tabKeys.length) {
       final int delta = widget.tabs.length - _tabKeys.length;
       _tabKeys.addAll(List<GlobalKey>.generate(delta, (int n) => GlobalKey()));
+      _labelPaddings.addAll(List<EdgeInsetsGeometry>.filled(delta, EdgeInsets.zero));
     } else if (widget.tabs.length < _tabKeys.length) {
       _tabKeys.removeRange(widget.tabs.length, _tabKeys.length);
+      _labelPaddings.removeRange(widget.tabs.length, _tabKeys.length);
     }
   }
 
@@ -1274,10 +1276,12 @@
         }
       }
 
+      _labelPaddings[index] = adjustedPadding ?? widget.labelPadding ?? tabBarTheme.labelPadding ?? kTabLabelPadding;
+
       return Center(
         heightFactor: 1.0,
         child: Padding(
-          padding: adjustedPadding ?? widget.labelPadding ?? tabBarTheme.labelPadding ?? kTabLabelPadding,
+          padding: _labelPaddings[index],
           child: KeyedSubtree(
             key: _tabKeys[index],
             child: widget.tabs[index],
@@ -1423,8 +1427,7 @@
     this.dragStartBehavior = DragStartBehavior.start,
     this.viewportFraction = 1.0,
     this.clipBehavior = Clip.hardEdge,
-  }) : assert(children != null),
-       assert(dragStartBehavior != null);
+  });
 
   /// This widget's selection and animation state.
   ///
@@ -1711,9 +1714,7 @@
     required this.borderColor,
     required this.size,
     this.borderStyle = BorderStyle.solid,
-  }) : assert(backgroundColor != null),
-       assert(borderColor != null),
-       assert(size != null);
+  });
 
   /// The indicator circle's background color.
   final Color backgroundColor;
@@ -1763,7 +1764,7 @@
     this.color,
     this.selectedColor,
     this.borderStyle,
-  }) : assert(indicatorSize != null && indicatorSize > 0.0);
+  }) : assert(indicatorSize > 0.0);
 
   /// This widget's selection and animation state.
   ///
@@ -1901,7 +1902,7 @@
 // Design token database by the script:
 //   dev/tools/gen_defaults/bin/gen_defaults.dart.
 
-// Token database version: v0_143
+// Token database version: v0_158
 
 class _TabsDefaultsM3 extends TabBarTheme {
   _TabsDefaultsM3(this.context)
diff --git a/framework/lib/src/material/text_button.dart b/framework/lib/src/material/text_button.dart
index ceac94a..cd9966a 100644
--- a/framework/lib/src/material/text_button.dart
+++ b/framework/lib/src/material/text_button.dart
@@ -249,7 +249,7 @@
   /// each state and "others" means all other states.
   ///
   /// The `textScaleFactor` is the value of
-  /// `MediaQuery.of(context).textScaleFactor` and the names of the
+  /// `MediaQuery.textScaleFactorOf(context)` and the names of the
   /// EdgeInsets constructors and `EdgeInsetsGeometry.lerp` have been
   /// abbreviated for readability.
   ///
@@ -382,7 +382,7 @@
     const EdgeInsets.all(8),
     const EdgeInsets.symmetric(horizontal: 8),
     const EdgeInsets.symmetric(horizontal: 4),
-    MediaQuery.maybeOf(context)?.textScaleFactor ?? 1,
+    MediaQuery.textScaleFactorOf(context),
   );
 }
 
@@ -481,9 +481,7 @@
     super.statesController,
     required Widget icon,
     required Widget label,
-  }) : assert(icon != null),
-       assert(label != null),
-       super(
+  }) : super(
          autofocus: autofocus ?? false,
          clipBehavior: clipBehavior ?? Clip.none,
          child: _TextButtonWithIconChild(icon: icon, label: label),
@@ -495,7 +493,7 @@
       const EdgeInsets.all(8),
       const EdgeInsets.symmetric(horizontal: 4),
       const EdgeInsets.symmetric(horizontal: 4),
-      MediaQuery.maybeOf(context)?.textScaleFactor ?? 1,
+      MediaQuery.textScaleFactorOf(context),
     );
     return super.defaultStyleOf(context).copyWith(
       padding: MaterialStatePropertyAll<EdgeInsetsGeometry>(scaledPadding),
@@ -514,7 +512,7 @@
 
   @override
   Widget build(BuildContext context) {
-    final double scale = MediaQuery.maybeOf(context)?.textScaleFactor ?? 1;
+    final double scale = MediaQuery.textScaleFactorOf(context);
     final double gap = scale <= 1 ? 8 : lerpDouble(8, 4, math.min(scale - 1, 1))!;
     return Row(
       mainAxisSize: MainAxisSize.min,
@@ -530,7 +528,7 @@
 // Design token database by the script:
 //   dev/tools/gen_defaults/bin/gen_defaults.dart.
 
-// Token database version: v0_143
+// Token database version: v0_158
 
 class _TextButtonDefaultsM3 extends ButtonStyle {
   _TextButtonDefaultsM3(this.context)
diff --git a/framework/lib/src/material/text_button_theme.dart b/framework/lib/src/material/text_button_theme.dart
index fb47f8d..130817e 100644
--- a/framework/lib/src/material/text_button_theme.dart
+++ b/framework/lib/src/material/text_button_theme.dart
@@ -49,7 +49,6 @@
 
   /// Linearly interpolate between two text button themes.
   static TextButtonThemeData? lerp(TextButtonThemeData? a, TextButtonThemeData? b, double t) {
-    assert (t != null);
     if (a == null && b == null) {
       return null;
     }
@@ -98,7 +97,7 @@
     super.key,
     required this.data,
     required super.child,
-  }) : assert(data != null);
+  });
 
   /// The configuration of this theme.
   final TextButtonThemeData data;
diff --git a/framework/lib/src/material/text_field.dart b/framework/lib/src/material/text_field.dart
index 5f09142..eeffe03 100644
--- a/framework/lib/src/material/text_field.dart
+++ b/framework/lib/src/material/text_field.dart
@@ -20,6 +20,7 @@
 import 'material_localizations.dart';
 import 'material_state.dart';
 import 'selectable_text.dart' show iOSHorizontalOffset;
+import 'spell_check_suggestions_toolbar.dart';
 import 'text_selection.dart';
 import 'theme.dart';
 
@@ -64,7 +65,7 @@
   }
 
   @override
-  void onSingleTapUp(TapUpDetails details) {
+  void onSingleTapUp(TapDragUpDetails details) {
     super.onSingleTapUp(details);
     _state._requestKeyboard();
     _state.widget.onTap?.call();
@@ -306,33 +307,24 @@
     this.scrollController,
     this.scrollPhysics,
     this.autofillHints = const <String>[],
+    this.contentInsertionConfiguration,
     this.clipBehavior = Clip.hardEdge,
     this.restorationId,
     this.scribbleEnabled = true,
     this.enableIMEPersonalizedLearning = true,
     this.contextMenuBuilder = _defaultContextMenuBuilder,
+    this.canRequestFocus = true,
     this.spellCheckConfiguration,
     this.magnifierConfiguration,
-  }) : assert(textAlign != null),
-       assert(readOnly != null),
-       assert(autofocus != null),
-       assert(obscuringCharacter != null && obscuringCharacter.length == 1),
-       assert(obscureText != null),
-       assert(autocorrect != null),
+  }) : assert(obscuringCharacter.length == 1),
        smartDashesType = smartDashesType ?? (obscureText ? SmartDashesType.disabled : SmartDashesType.enabled),
        smartQuotesType = smartQuotesType ?? (obscureText ? SmartQuotesType.disabled : SmartQuotesType.enabled),
-       assert(enableSuggestions != null),
-       assert(scrollPadding != null),
-       assert(dragStartBehavior != null),
-       assert(selectionHeightStyle != null),
-       assert(selectionWidthStyle != null),
        assert(maxLines == null || maxLines > 0),
        assert(minLines == null || minLines > 0),
        assert(
          (maxLines == null) || (minLines == null) || (maxLines >= minLines),
          "minLines can't be greater than maxLines",
        ),
-       assert(expands != null),
        assert(
          !expands || (maxLines == null && minLines == null),
          'minLines and maxLines must be null when expands is true.',
@@ -346,8 +338,6 @@
          !identical(keyboardType, TextInputType.text),
          'Use keyboardType TextInputType.multiline when using TextInputAction.newline on a multiline TextField.',
        ),
-       assert(clipBehavior != null),
-       assert(enableIMEPersonalizedLearning != null),
        keyboardType = keyboardType ?? (maxLines == 1 ? TextInputType.text : TextInputType.multiline),
        enableInteractiveSelection = enableInteractiveSelection ?? (!readOnly || !obscureText);
 
@@ -765,6 +755,9 @@
   /// {@macro flutter.services.TextInputConfiguration.enableIMEPersonalizedLearning}
   final bool enableIMEPersonalizedLearning;
 
+  /// {@macro flutter.widgets.editableText.contentInsertionConfiguration}
+  final ContentInsertionConfiguration? contentInsertionConfiguration;
+
   /// {@macro flutter.widgets.EditableText.contextMenuBuilder}
   ///
   /// If not provided, will build a default menu based on the platform.
@@ -774,6 +767,13 @@
   ///  * [AdaptiveTextSelectionToolbar], which is built by default.
   final EditableTextContextMenuBuilder? contextMenuBuilder;
 
+  /// Determine whether this text field can request the primary focus.
+  ///
+  /// Defaults to true. If false, the text field will not request focus
+  /// when tapped, or when its context menu is displayed. If false it will not
+  /// be possible to move the focus to the text field with tab key.
+  final bool canRequestFocus;
+
   static Widget _defaultContextMenuBuilder(BuildContext context, EditableTextState editableTextState) {
     return AdaptiveTextSelectionToolbar.editableText(
       editableTextState: editableTextState,
@@ -800,6 +800,32 @@
       decorationStyle: TextDecorationStyle.wavy,
   );
 
+  /// Default builder for the spell check suggestions toolbar in the Material
+  /// style.
+  ///
+  /// See also:
+  ///  * [SpellCheckConfiguration.spellCheckSuggestionsToolbarBuilder], the
+  //     builder configured to show a spell check suggestions toolbar.
+  @visibleForTesting
+  static Widget defaultSpellCheckSuggestionsToolbarBuilder(
+    BuildContext context,
+    EditableTextState editableTextState,
+  ) {
+    final Offset anchor =
+      SpellCheckSuggestionsToolbar.getToolbarAnchor(editableTextState.contextMenuAnchors);
+    final List<ContextMenuButtonItem>? buttonItems =
+      SpellCheckSuggestionsToolbar.buildButtonItems(context, editableTextState);
+
+    if (buttonItems == null){
+      return const SizedBox.shrink();
+    }
+
+    return SpellCheckSuggestionsToolbar(
+      anchor: anchor,
+      buttonItems: buttonItems,
+    );
+  }
+
   @override
   State<TextField> createState() => _TextFieldState();
 
@@ -843,6 +869,7 @@
     properties.add(DiagnosticsProperty<bool>('scribbleEnabled', scribbleEnabled, defaultValue: true));
     properties.add(DiagnosticsProperty<bool>('enableIMEPersonalizedLearning', enableIMEPersonalizedLearning, defaultValue: true));
     properties.add(DiagnosticsProperty<SpellCheckConfiguration>('spellCheckConfiguration', spellCheckConfiguration, defaultValue: null));
+    properties.add(DiagnosticsProperty<List<String>>('contentCommitMimeTypes', contentInsertionConfiguration?.allowedMimeTypes ?? const <String>[], defaultValue: contentInsertionConfiguration == null ? const <String>[] : kDefaultContentInsertionMimeTypes));
   }
 }
 
@@ -962,15 +989,15 @@
     if (widget.controller == null) {
       _createLocalController();
     }
-    _effectiveFocusNode.canRequestFocus = _isEnabled;
+    _effectiveFocusNode.canRequestFocus = widget.canRequestFocus && _isEnabled;
     _effectiveFocusNode.addListener(_handleFocusChanged);
   }
 
   bool get _canRequestFocus {
-    final NavigationMode mode = MediaQuery.maybeOf(context)?.navigationMode ?? NavigationMode.traditional;
+    final NavigationMode mode = MediaQuery.maybeNavigationModeOf(context) ?? NavigationMode.traditional;
     switch (mode) {
       case NavigationMode.traditional:
-        return _isEnabled;
+        return widget.canRequestFocus && _isEnabled;
       case NavigationMode.directional:
         return true;
     }
@@ -1192,7 +1219,11 @@
       widget.spellCheckConfiguration != const SpellCheckConfiguration.disabled()
         ? widget.spellCheckConfiguration!.copyWith(
             misspelledTextStyle: widget.spellCheckConfiguration!.misspelledTextStyle
-              ?? TextField.materialMisspelledTextStyle)
+              ?? TextField.materialMisspelledTextStyle,
+            spellCheckSuggestionsToolbarBuilder:
+              widget.spellCheckConfiguration!.spellCheckSuggestionsToolbarBuilder
+                ?? TextField.defaultSpellCheckSuggestionsToolbarBuilder
+          )
         : const SpellCheckConfiguration.disabled();
 
     TextSelectionControls? textSelectionControls = widget.selectionControls;
@@ -1215,7 +1246,7 @@
         cursorColor = widget.cursorColor ?? selectionStyle.cursorColor ?? cupertinoTheme.primaryColor;
         selectionColor = selectionStyle.selectionColor ?? cupertinoTheme.primaryColor.withOpacity(0.40);
         cursorRadius ??= const Radius.circular(2.0);
-        cursorOffset = Offset(iOSHorizontalOffset / MediaQuery.of(context).devicePixelRatio, 0);
+        cursorOffset = Offset(iOSHorizontalOffset / MediaQuery.devicePixelRatioOf(context), 0);
         autocorrectionTextRectColor = selectionColor;
         break;
 
@@ -1228,7 +1259,7 @@
         cursorColor = widget.cursorColor ?? selectionStyle.cursorColor ?? cupertinoTheme.primaryColor;
         selectionColor = selectionStyle.selectionColor ?? cupertinoTheme.primaryColor.withOpacity(0.40);
         cursorRadius ??= const Radius.circular(2.0);
-        cursorOffset = Offset(iOSHorizontalOffset / MediaQuery.of(context).devicePixelRatio, 0);
+        cursorOffset = Offset(iOSHorizontalOffset / MediaQuery.devicePixelRatioOf(context), 0);
         handleDidGainAccessibilityFocus = () {
           // Automatically activate the TextField when it receives accessibility focus.
           if (!_effectiveFocusNode.hasFocus && _effectiveFocusNode.canRequestFocus) {
@@ -1335,6 +1366,7 @@
           restorationId: 'editable',
           scribbleEnabled: widget.scribbleEnabled,
           enableIMEPersonalizedLearning: widget.enableIMEPersonalizedLearning,
+          contentInsertionConfiguration: widget.contentInsertionConfiguration,
           contextMenuBuilder: widget.contextMenuBuilder,
           spellCheckConfiguration: spellCheckConfiguration,
           magnifierConfiguration: widget.magnifierConfiguration ?? TextMagnifier.adaptiveMagnifierConfiguration,
@@ -1424,11 +1456,11 @@
 // Design token database by the script:
 //   dev/tools/gen_defaults/bin/gen_defaults.dart.
 
-// Token database version: v0_143
+// Token database version: v0_158
 
 TextStyle _m3InputStyle(BuildContext context) => Theme.of(context).textTheme.bodyLarge!;
 
 TextStyle _m3CounterErrorStyle(BuildContext context) =>
-  Theme.of(context).textTheme.bodySmall!.copyWith(color:Theme.of(context).colorScheme.error);
+  Theme.of(context).textTheme.bodySmall!.copyWith(color: Theme.of(context).colorScheme.error);
 
 // END GENERATED TOKEN PROPERTIES - TextField
diff --git a/framework/lib/src/material/text_form_field.dart b/framework/lib/src/material/text_form_field.dart
index fc057c9..f3f5deb 100644
--- a/framework/lib/src/material/text_form_field.dart
+++ b/framework/lib/src/material/text_form_field.dart
@@ -17,7 +17,7 @@
 /// This is a convenience widget that wraps a [TextField] widget in a
 /// [FormField].
 ///
-/// A [Form] ancestor is not required. The [Form] simply makes it easier to
+/// A [Form] ancestor is not required. The [Form] allows one to
 /// save, reset, or validate multiple fields at once. To use without a [Form],
 /// pass a `GlobalKey<FormFieldState>` (see [GlobalKey]) to the constructor and use
 /// [GlobalKey.currentState] to save or reset the form field.
@@ -154,29 +154,21 @@
     bool enableIMEPersonalizedLearning = true,
     MouseCursor? mouseCursor,
     EditableTextContextMenuBuilder? contextMenuBuilder = _defaultContextMenuBuilder,
+    TextMagnifierConfiguration? magnifierConfiguration,
   }) : assert(initialValue == null || controller == null),
-       assert(textAlign != null),
-       assert(autofocus != null),
-       assert(readOnly != null),
-       assert(obscuringCharacter != null && obscuringCharacter.length == 1),
-       assert(obscureText != null),
-       assert(autocorrect != null),
-       assert(enableSuggestions != null),
-       assert(scrollPadding != null),
+       assert(obscuringCharacter.length == 1),
        assert(maxLines == null || maxLines > 0),
        assert(minLines == null || minLines > 0),
        assert(
          (maxLines == null) || (minLines == null) || (maxLines >= minLines),
          "minLines can't be greater than maxLines",
        ),
-       assert(expands != null),
        assert(
          !expands || (maxLines == null && minLines == null),
          'minLines and maxLines must be null when expands is true.',
        ),
        assert(!obscureText || maxLines == 1, 'Obscured fields cannot be multiline.'),
        assert(maxLength == null || maxLength == TextField.noMaxLength || maxLength > 0),
-       assert(enableIMEPersonalizedLearning != null),
        super(
          initialValue: controller != null ? controller.text : (initialValue ?? ''),
          enabled: enabled ?? decoration?.enabled ?? true,
@@ -243,6 +235,7 @@
                enableIMEPersonalizedLearning: enableIMEPersonalizedLearning,
                mouseCursor: mouseCursor,
                contextMenuBuilder: contextMenuBuilder,
+               magnifierConfiguration: magnifierConfiguration,
              ),
            );
          },
diff --git a/framework/lib/src/material/text_selection.dart b/framework/lib/src/material/text_selection.dart
index 9bd2f71..44f797d 100644
--- a/framework/lib/src/material/text_selection.dart
+++ b/framework/lib/src/material/text_selection.dart
@@ -55,7 +55,7 @@
     ClipboardStatusNotifier? clipboardStatus,
     Offset? lastSecondaryTapDownPosition,
   ) {
-   return _TextSelectionControlsToolbar(
+    return _TextSelectionControlsToolbar(
       globalEditableRegion: globalEditableRegion,
       textLineHeight: textLineHeight,
       selectionMidpoint: selectionMidpoint,
diff --git a/framework/lib/src/material/text_selection_theme.dart b/framework/lib/src/material/text_selection_theme.dart
index 134a9d6..592019b 100644
--- a/framework/lib/src/material/text_selection_theme.dart
+++ b/framework/lib/src/material/text_selection_theme.dart
@@ -76,7 +76,6 @@
     if (a == null && b == null) {
       return null;
     }
-    assert(t != null);
     return TextSelectionThemeData(
       cursorColor: Color.lerp(a?.cursorColor, b?.cursorColor, t),
       selectionColor: Color.lerp(a?.selectionColor, b?.selectionColor, t),
@@ -147,8 +146,7 @@
     super.key,
     required this.data,
     required Widget child,
-  }) : assert(data != null),
-       _child = child,
+  }) : _child = child,
        // See `get child` override below.
        super(child: const _NullWidget());
 
diff --git a/framework/lib/src/material/text_selection_toolbar.dart b/framework/lib/src/material/text_selection_toolbar.dart
index cc3701c..5e607f3 100644
--- a/framework/lib/src/material/text_selection_toolbar.dart
+++ b/framework/lib/src/material/text_selection_toolbar.dart
@@ -4,9 +4,9 @@
 
 import 'dart:math' as math;
 
+import 'package:flute/cupertino.dart';
 import 'package:flute/foundation.dart' show listEquals;
 import 'package:flute/rendering.dart';
-import 'package:flute/widgets.dart';
 
 import 'debug.dart';
 import 'icon_button.dart';
@@ -14,15 +14,7 @@
 import 'material.dart';
 import 'material_localizations.dart';
 
-// Minimal padding from all edges of the selection toolbar to all edges of the
-// viewport.
-const double _kToolbarScreenPadding = 8.0;
 const double _kToolbarHeight = 44.0;
-
-const double _kHandleSize = 22.0;
-
-// Padding between the toolbar and the anchor.
-const double _kToolbarContentDistanceBelow = _kHandleSize - 2.0;
 const double _kToolbarContentDistance = 8.0;
 
 /// A fully-functional Material-style text selection toolbar.
@@ -84,6 +76,17 @@
   /// {@endtemplate}
   final ToolbarBuilder toolbarBuilder;
 
+  /// The size of the text selection handles.
+  ///
+  /// See also:
+  ///
+  ///  * [SpellCheckSuggestionsToolbar], which references this value to calculate
+  ///    the padding between the toolbar and anchor.
+  static const double kHandleSize = 22.0;
+
+  /// Padding between the toolbar and the anchor.
+  static const double kToolbarContentDistanceBelow = kHandleSize - 2.0;
+
   // Build the default Android Material text selection menu toolbar.
   static Widget _defaultToolbarBuilder(BuildContext context, Widget child) {
     return _TextSelectionToolbarContainer(
@@ -97,21 +100,22 @@
     final Offset anchorAbovePadded =
         anchorAbove - const Offset(0.0, _kToolbarContentDistance);
     final Offset anchorBelowPadded =
-        anchorBelow + const Offset(0.0, _kToolbarContentDistanceBelow);
+        anchorBelow + const Offset(0.0, kToolbarContentDistanceBelow);
 
-    final double paddingAbove = MediaQuery.of(context).padding.top
-        + _kToolbarScreenPadding;
+    const double screenPadding = CupertinoTextSelectionToolbar.kToolbarScreenPadding;
+    final double paddingAbove = MediaQuery.paddingOf(context).top
+        + screenPadding;
     final double availableHeight = anchorAbovePadded.dy - _kToolbarContentDistance - paddingAbove;
     final bool fitsAbove = _kToolbarHeight <= availableHeight;
     // Makes up for the Padding above the Stack.
-    final Offset localAdjustment = Offset(_kToolbarScreenPadding, paddingAbove);
+    final Offset localAdjustment = Offset(screenPadding, paddingAbove);
 
     return Padding(
       padding: EdgeInsets.fromLTRB(
-        _kToolbarScreenPadding,
+        screenPadding,
         paddingAbove,
-        _kToolbarScreenPadding,
-        _kToolbarScreenPadding,
+        screenPadding,
+        screenPadding,
       ),
       child: CustomSingleChildLayout(
         delegate: TextSelectionToolbarLayoutDelegate(
@@ -237,8 +241,7 @@
     required Widget super.child,
     required this.overflowOpen,
     required this.textDirection,
-  }) : assert(child != null),
-       assert(overflowOpen != null);
+  });
 
   final bool overflowOpen;
   final TextDirection textDirection;
@@ -362,13 +365,11 @@
 // Renders the menu items in the correct positions in the menu and its overflow
 // submenu based on calculating which item would first overflow.
 class _TextSelectionToolbarItemsLayout extends MultiChildRenderObjectWidget {
-  _TextSelectionToolbarItemsLayout({
+  const _TextSelectionToolbarItemsLayout({
     required this.isAbove,
     required this.overflowOpen,
     required super.children,
-  }) : assert(children != null),
-       assert(isAbove != null),
-       assert(overflowOpen != null);
+  });
 
   final bool isAbove;
   final bool overflowOpen;
@@ -411,9 +412,7 @@
   _RenderTextSelectionToolbarItemsLayout({
     required bool isAbove,
     required bool overflowOpen,
-  }) : assert(overflowOpen != null),
-       assert(isAbove != null),
-       _isAbove = isAbove,
+  }) : _isAbove = isAbove,
        _overflowOpen = overflowOpen,
        super();
 
diff --git a/framework/lib/src/material/text_selection_toolbar_text_button.dart b/framework/lib/src/material/text_selection_toolbar_text_button.dart
index ca6fae2..959ca8e 100644
--- a/framework/lib/src/material/text_selection_toolbar_text_button.dart
+++ b/framework/lib/src/material/text_selection_toolbar_text_button.dart
@@ -31,6 +31,7 @@
     required this.child,
     required this.padding,
     this.onPressed,
+    this.alignment,
   });
 
   // These values were eyeballed to match the native text selection menu on a
@@ -62,6 +63,15 @@
   ///  * [ButtonStyle.padding], which is where this padding is applied.
   final EdgeInsets padding;
 
+  /// The alignment of the button's child.
+  ///
+  /// By default, this will be [Alignment.center].
+  ///
+  /// See also:
+  ///
+  ///  * [ButtonStyle.alignment], which is where this alignment is applied.
+  final AlignmentGeometry? alignment;
+
   /// Returns the standard padding for a button at index out of a total number
   /// of buttons.
   ///
@@ -104,6 +114,22 @@
     return _TextSelectionToolbarItemPosition.middle;
   }
 
+  /// Returns a copy of the current [TextSelectionToolbarTextButton] instance
+  /// with specific overrides.
+  TextSelectionToolbarTextButton copyWith({
+    Widget? child,
+    VoidCallback? onPressed,
+    EdgeInsets? padding,
+    AlignmentGeometry? alignment,
+  }) {
+    return TextSelectionToolbarTextButton(
+      onPressed: onPressed ?? this.onPressed,
+      padding: padding ?? this.padding,
+      alignment: alignment ?? this.alignment,
+      child: child ?? this.child,
+    );
+  }
+
   @override
   Widget build(BuildContext context) {
     // TODO(hansmuller): Should be colorScheme.onSurface
@@ -117,6 +143,7 @@
         shape: const RoundedRectangleBorder(),
         minimumSize: const Size(kMinInteractiveDimension, kMinInteractiveDimension),
         padding: padding,
+        alignment: alignment,
       ),
       onPressed: onPressed,
       child: child,
diff --git a/framework/lib/src/material/text_theme.dart b/framework/lib/src/material/text_theme.dart
index 351fdf8..42aefb8 100644
--- a/framework/lib/src/material/text_theme.dart
+++ b/framework/lib/src/material/text_theme.dart
@@ -799,7 +799,6 @@
   ///
   /// {@macro dart.ui.shadow.lerp}
   static TextTheme lerp(TextTheme? a, TextTheme? b, double t) {
-    assert(t != null);
     return TextTheme(
       displayLarge: TextStyle.lerp(a?.displayLarge, b?.displayLarge, t),
       displayMedium: TextStyle.lerp(a?.displayMedium, b?.displayMedium, t),
diff --git a/framework/lib/src/material/theme.dart b/framework/lib/src/material/theme.dart
index b00e085..bb736d5 100644
--- a/framework/lib/src/material/theme.dart
+++ b/framework/lib/src/material/theme.dart
@@ -42,8 +42,7 @@
     super.key,
     required this.data,
     required this.child,
-  }) : assert(child != null),
-       assert(data != null);
+  });
 
   /// Specifies the color and typography values for descendant widgets.
   final ThemeData data;
@@ -154,7 +153,7 @@
   const _InheritedTheme({
     required this.theme,
     required super.child,
-  }) : assert(theme != null);
+  });
 
   final Theme theme;
 
@@ -211,8 +210,7 @@
     super.duration = kThemeAnimationDuration,
     super.onEnd,
     required this.child,
-  }) : assert(child != null),
-       assert(data != null);
+  });
 
   /// Specifies the color and typography values for descendant widgets.
   final ThemeData data;
diff --git a/framework/lib/src/material/theme_data.dart b/framework/lib/src/material/theme_data.dart
index 4019219..cdaf4c0 100644
--- a/framework/lib/src/material/theme_data.dart
+++ b/framework/lib/src/material/theme_data.dart
@@ -22,6 +22,7 @@
 import 'colors.dart';
 import 'constants.dart';
 import 'data_table_theme.dart';
+import 'date_picker_theme.dart';
 import 'dialog_theme.dart';
 import 'divider_theme.dart';
 import 'drawer_theme.dart';
@@ -70,6 +71,8 @@
 
 /// An interface that defines custom additions to a [ThemeData] object.
 ///
+/// {@youtube 560 315 https://www.youtube.com/watch?v=8-szcYzFVao}
+///
 /// Typically used for custom colors. To use, subclass [ThemeExtension],
 /// define a number of fields (e.g. [Color]s), and implement the [copyWith] and
 /// [lerp] methods. The latter will ensure smooth transitions of properties when
@@ -346,6 +349,7 @@
     CheckboxThemeData? checkboxTheme,
     ChipThemeData? chipTheme,
     DataTableThemeData? dataTableTheme,
+    DatePickerThemeData? datePickerTheme,
     DialogTheme? dialogTheme,
     DividerThemeData? dividerTheme,
     DrawerThemeData? drawerTheme,
@@ -392,25 +396,6 @@
     )
     Brightness? accentColorBrightness,
     @Deprecated(
-      'No longer used by the framework, please remove any reference to it. '
-      'For more information, consult the migration guide at '
-      'https://flutter.dev/docs/release/breaking-changes/theme-data-accent-properties#migration-guide. '
-      'This feature was deprecated after v2.3.0-0.1.pre.',
-    )
-    TextTheme? accentTextTheme,
-    @Deprecated(
-      'No longer used by the framework, please remove any reference to it. '
-      'For more information, consult the migration guide at '
-      'https://flutter.dev/docs/release/breaking-changes/theme-data-accent-properties#migration-guide. '
-      'This feature was deprecated after v2.3.0-0.1.pre.',
-    )
-    IconThemeData? accentIconTheme,
-    @Deprecated(
-      'No longer used by the framework, please remove any reference to it. '
-      'This feature was deprecated after v2.3.0-0.2.pre.',
-    )
-    Color? buttonColor,
-    @Deprecated(
       'This "fix" is now enabled by default. '
       'This feature was deprecated after v2.5.0-1.0.pre.',
     )
@@ -522,7 +507,6 @@
     toggleableActiveColor ??= isDark ? Colors.tealAccent[200]! : (accentColor ?? primarySwatch[600]!);
     accentColor ??= isDark ? Colors.tealAccent[200]! : primarySwatch[500]!;
     accentColorBrightness ??= estimateBrightnessForColor(accentColor);
-    final bool accentIsDark = accentColorBrightness == Brightness.dark;
     focusColor ??= isDark ? Colors.white.withOpacity(0.12) : Colors.black.withOpacity(0.12);
     hoverColor ??= isDark ? Colors.white.withOpacity(0.04) : Colors.black.withOpacity(0.04);
     shadowColor ??= Colors.black;
@@ -552,7 +536,7 @@
     // [disabledColor], [highlightColor], and [splashColor].
     buttonTheme ??= ButtonThemeData(
       colorScheme: colorScheme,
-      buttonColor: buttonColor ?? (isDark ? primarySwatch[600]! : Colors.grey[300]!),
+      buttonColor: isDark ? primarySwatch[600]! : Colors.grey[300]!,
       disabledColor: disabledColor,
       focusColor: focusColor,
       hoverColor: hoverColor,
@@ -570,21 +554,17 @@
       : Typography.material2014(platform: platform);
     TextTheme defaultTextTheme = isDark ? typography.white : typography.black;
     TextTheme defaultPrimaryTextTheme = primaryIsDark ? typography.white : typography.black;
-    TextTheme defaultAccentTextTheme = accentIsDark ? typography.white : typography.black;
     if (fontFamily != null) {
       defaultTextTheme = defaultTextTheme.apply(fontFamily: fontFamily);
       defaultPrimaryTextTheme = defaultPrimaryTextTheme.apply(fontFamily: fontFamily);
-      defaultAccentTextTheme = defaultAccentTextTheme.apply(fontFamily: fontFamily);
     }
     if (fontFamilyFallback != null) {
       defaultTextTheme = defaultTextTheme.apply(fontFamilyFallback: fontFamilyFallback);
       defaultPrimaryTextTheme = defaultPrimaryTextTheme.apply(fontFamilyFallback: fontFamilyFallback);
-      defaultAccentTextTheme = defaultAccentTextTheme.apply(fontFamilyFallback: fontFamilyFallback);
     }
     if (package != null) {
       defaultTextTheme = defaultTextTheme.apply(package: package);
       defaultPrimaryTextTheme = defaultPrimaryTextTheme.apply(package: package);
-      defaultAccentTextTheme = defaultAccentTextTheme.apply(package: package);
     }
     textTheme = defaultTextTheme.merge(textTheme);
     primaryTextTheme = defaultPrimaryTextTheme.merge(primaryTextTheme);
@@ -603,6 +583,7 @@
     checkboxTheme ??= const CheckboxThemeData();
     chipTheme ??= const ChipThemeData();
     dataTableTheme ??= const DataTableThemeData();
+    datePickerTheme ??= const DatePickerThemeData();
     dialogTheme ??= const DialogTheme();
     dividerTheme ??= const DividerThemeData();
     drawerTheme ??= const DrawerThemeData();
@@ -635,9 +616,6 @@
     tooltipTheme ??= const TooltipThemeData();
 
     // DEPRECATED (newest deprecations at the bottom)
-    accentTextTheme = defaultAccentTextTheme.merge(accentTextTheme);
-    accentIconTheme ??= accentIsDark ? const IconThemeData(color: Colors.white) : const IconThemeData(color: Colors.black);
-    buttonColor ??= isDark ? primarySwatch[600]! : Colors.grey[300]!;
     fixTextFieldOutlineLabel ??= true;
     primaryColorBrightness = estimatedPrimaryColorBrightness;
     errorColor ??= Colors.red[700]!;
@@ -701,6 +679,7 @@
       checkboxTheme: checkboxTheme,
       chipTheme: chipTheme,
       dataTableTheme: dataTableTheme,
+      datePickerTheme: datePickerTheme,
       dialogTheme: dialogTheme,
       dividerTheme: dividerTheme,
       drawerTheme: drawerTheme,
@@ -734,9 +713,6 @@
       // DEPRECATED (newest deprecations at the bottom)
       accentColor: accentColor,
       accentColorBrightness: accentColorBrightness,
-      accentTextTheme: accentTextTheme,
-      accentIconTheme: accentIconTheme,
-      buttonColor: buttonColor,
       fixTextFieldOutlineLabel: fixTextFieldOutlineLabel,
       primaryColorBrightness: primaryColorBrightness,
       androidOverscrollIndicator: androidOverscrollIndicator,
@@ -815,6 +791,7 @@
     required this.checkboxTheme,
     required this.chipTheme,
     required this.dataTableTheme,
+    required this.datePickerTheme,
     required this.dialogTheme,
     required this.dividerTheme,
     required this.drawerTheme,
@@ -861,25 +838,6 @@
     )
     Brightness? accentColorBrightness,
     @Deprecated(
-      'No longer used by the framework, please remove any reference to it. '
-      'For more information, consult the migration guide at '
-      'https://flutter.dev/docs/release/breaking-changes/theme-data-accent-properties#migration-guide. '
-      'This feature was deprecated after v2.3.0-0.1.pre.',
-    )
-    TextTheme? accentTextTheme,
-    @Deprecated(
-      'No longer used by the framework, please remove any reference to it. '
-      'For more information, consult the migration guide at '
-      'https://flutter.dev/docs/release/breaking-changes/theme-data-accent-properties#migration-guide. '
-      'This feature was deprecated after v2.3.0-0.1.pre.',
-    )
-    IconThemeData? accentIconTheme,
-    @Deprecated(
-      'No longer used by the framework, please remove any reference to it. '
-      'This feature was deprecated after v2.3.0-0.2.pre.',
-    )
-    Color? buttonColor,
-    @Deprecated(
       'This "fix" is now enabled by default. '
       'This feature was deprecated after v2.5.0-1.0.pre.',
     )
@@ -926,9 +884,6 @@
        // should not be `required`, use getter pattern to avoid breakages.
        _accentColor = accentColor,
        _accentColorBrightness = accentColorBrightness,
-       _accentTextTheme = accentTextTheme,
-       _accentIconTheme = accentIconTheme,
-       _buttonColor = buttonColor,
        _fixTextFieldOutlineLabel = fixTextFieldOutlineLabel,
        _primaryColorBrightness = primaryColorBrightness,
        _toggleableActiveColor = toggleableActiveColor,
@@ -936,93 +891,10 @@
        _errorColor = errorColor,
        _backgroundColor = backgroundColor,
        _bottomAppBarColor = bottomAppBarColor,
-       // GENERAL CONFIGURATION
-       assert(applyElevationOverlayColor != null),
-       assert(extensions != null),
-       assert(inputDecorationTheme != null),
-       assert(materialTapTargetSize != null),
-       assert(pageTransitionsTheme != null),
-       assert(platform != null),
-       assert(scrollbarTheme != null),
-       assert(splashFactory != null),
-       assert(useMaterial3 != null),
-       assert(visualDensity != null),
-        // COLOR
-       assert(canvasColor != null),
-       assert(cardColor != null),
-       assert(colorScheme != null),
-       assert(dialogBackgroundColor != null),
-       assert(disabledColor != null),
-       assert(dividerColor != null),
-       assert(focusColor != null),
-       assert(highlightColor != null),
-       assert(hintColor != null),
-       assert(hoverColor != null),
-       assert(indicatorColor != null),
-       assert(primaryColor != null),
-       assert(primaryColorDark != null),
-       assert(primaryColorLight != null),
-       assert(scaffoldBackgroundColor != null),
-       assert(secondaryHeaderColor != null),
-       assert(shadowColor != null),
-       assert(splashColor != null),
        assert(toggleableActiveColor != null),
-       assert(unselectedWidgetColor != null),
-        // TYPOGRAPHY & ICONOGRAPHY
-       assert(iconTheme != null),
-       assert(primaryIconTheme != null),
-       assert(primaryTextTheme != null),
-       assert(textTheme != null),
-       assert(typography != null),
-        // COMPONENT THEMES
-       assert(appBarTheme != null),
-       assert(badgeTheme != null),
-       assert(bannerTheme != null),
-       assert(bottomAppBarTheme != null),
-       assert(bottomNavigationBarTheme != null),
-       assert(bottomSheetTheme != null),
-       assert(buttonBarTheme != null),
-       assert(buttonTheme != null),
-       assert(cardTheme != null),
-       assert(checkboxTheme != null),
-       assert(chipTheme != null),
-       assert(dataTableTheme != null),
-       assert(dialogTheme != null),
-       assert(dividerTheme != null),
-       assert(drawerTheme != null),
-       assert(dropdownMenuTheme != null),
-       assert(elevatedButtonTheme != null),
-       assert(expansionTileTheme != null),
-       assert(filledButtonTheme != null),
-       assert(floatingActionButtonTheme != null),
-       assert(iconButtonTheme != null),
-       assert(listTileTheme != null),
-       assert(menuBarTheme != null),
-       assert(menuButtonTheme != null),
-       assert(menuTheme != null),
-       assert(navigationBarTheme != null),
-       assert(navigationDrawerTheme != null),
-       assert(navigationRailTheme != null),
-       assert(outlinedButtonTheme != null),
-       assert(popupMenuTheme != null),
-       assert(progressIndicatorTheme != null),
-       assert(radioTheme != null),
-       assert(segmentedButtonTheme != null),
-       assert(sliderTheme != null),
-       assert(snackBarTheme != null),
-       assert(switchTheme != null),
-       assert(tabBarTheme != null),
-       assert(textButtonTheme != null),
-       assert(textSelectionTheme != null),
-       assert(timePickerTheme != null),
-       assert(toggleButtonsTheme != null),
-       assert(tooltipTheme != null),
         // DEPRECATED (newest deprecations at the bottom)
        assert(accentColor != null),
        assert(accentColorBrightness != null),
-       assert(accentTextTheme != null),
-       assert(accentIconTheme != null),
-       assert(buttonColor != null),
        assert(fixTextFieldOutlineLabel != null),
        assert(primaryColorBrightness != null),
        assert(errorColor != null),
@@ -1237,7 +1109,7 @@
   /// to determine the current platform for the purpose of emulating the
   /// platform behavior (e.g. scrolling or haptic effects). Widgets and render
   /// objects at lower layers that try to emulate the underlying platform
-  /// platform can depend on [defaultTargetPlatform] directly, or may require
+  /// can depend on [defaultTargetPlatform] directly, or may require
   /// that the target platform be provided as an argument. The
   /// [dart:io.Platform] object should only be used directly when it's critical
   /// to actually know the current platform, without any overrides possible (for
@@ -1316,35 +1188,37 @@
   ///   * Typography: [Typography] (see table above)
   ///
   /// ### Components
-  ///   * Badges: [Badge]
+  ///   * Badges: [Badge] (*new*)
   ///   * Bottom app bar: [BottomAppBar]
   ///   * Bottom sheets: [BottomSheet]
   ///   * Buttons
-  ///     - Common buttons: [ElevatedButton], [FilledButton], [OutlinedButton], [TextButton]
+  ///     - Common buttons: [ElevatedButton], [FilledButton] (*new*), FilledButton.tonal] (*new*), [OutlinedButton], [TextButton]
   ///     - FAB: [FloatingActionButton], [FloatingActionButton.extended]
   ///     - Icon buttons: [IconButton]
-  ///     - Segmented buttons: [SegmentedButton]
+  ///     - Segmented buttons: [SegmentedButton] (*new*, replacing [ToggleButtons])
   ///   * Cards: [Card]
-  ///   * Checkbox: [Checkbox]
+  ///   * Checkbox: [Checkbox], [CheckboxListTile]
   ///   * Chips:
   ///     - [ActionChip] (used for Assist and Suggestion chips),
   ///     - [FilterChip], [ChoiceChip] (used for single selection filter chips),
   ///     - [InputChip]
-  ///   * Dialogs: [Dialog], [AlertDialog]
-  ///   * Divider: [Divider]
+  ///   * Date pickers: [showDatePicker], [showDateRangePicker], [DatePickerDialog], [DateRangePickerDialog], [InputDatePickerFormField]
+  ///   * Dialogs: [AlertDialog], [Dialog.fullscreen] (*new*)
+  ///   * Divider: [Divider], [VerticalDivider]
   ///   * Lists: [ListTile]
-  ///   * Menus: [MenuBar], [DropdownMenu]
-  ///   * Navigation bar: [NavigationBar] (new, replacing [BottomNavigationBar])
-  ///   * Navigation drawer: [NavigationDrawer]
+  ///   * Menus: [MenuAnchor] (*new*), [DropdownMenu] (*new*), [MenuBar] (*new*)
+  ///   * Navigation bar: [NavigationBar] (*new*, replacing [BottomNavigationBar])
+  ///   * Navigation drawer: [NavigationDrawer] (*new*, replacing [Drawer])
   ///   * Navigation rail: [NavigationRail]
   ///   * Progress indicators: [CircularProgressIndicator], [LinearProgressIndicator]
-  ///   * Radio button: [Radio]
+  ///   * Radio button: [Radio], [RadioListTile]
   ///   * Snack bar: [SnackBar]
-  ///   * Slider: [Slider]
-  ///   * Switch: [Switch]
+  ///   * Slider: [Slider], [RangeSlider]
+  ///   * Switch: [Switch], [SwitchListTile]
   ///   * Tabs: [TabBar]
   ///   * TextFields: [TextField] together with its [InputDecoration]
-  ///   * Top app bar: [AppBar]
+  ///   * Time pickers: [showTimePicker], [TimePickerDialog]
+  ///   * Top app bar: [AppBar], [SliverAppBar], [SliverAppBar.medium] (*new*), [SliverAppBar.large] (*new*)
   ///
   /// In addition, this flag enables features introduced in Android 12.
   ///   * Stretch overscroll: [MaterialScrollBehavior]
@@ -1405,14 +1279,13 @@
   /// The color of [Material] when it is used as a [Card].
   final Color cardColor;
 
-  /// A set of twelve colors that can be used to configure the
-  /// color properties of most components.
+  /// {@macro flutter.material.color_scheme.ColorScheme}
   ///
-  /// This property was added much later than the theme's set of highly
-  /// specific colors, like [cardColor], [buttonColor], [canvasColor] etc.
-  /// New components can be defined exclusively in terms of [colorScheme].
-  /// Existing components will gradually migrate to it, to the extent
-  /// that is possible without significant backwards compatibility breaks.
+  /// This property was added much later than the theme's set of highly specific
+  /// colors, like [cardColor], [canvasColor] etc. New components can be defined
+  /// exclusively in terms of [colorScheme]. Existing components will gradually
+  /// migrate to it, to the extent that is possible without significant
+  /// backwards compatibility breaks.
   final ColorScheme colorScheme;
 
   /// The background color of [Dialog] elements.
@@ -1566,6 +1439,10 @@
   /// widgets.
   final DataTableThemeData dataTableTheme;
 
+  /// A theme for customizing the appearance and layout of [DatePickerDialog]
+  /// widgets.
+  final DatePickerThemeData datePickerTheme;
+
   /// A theme for customizing the shape of a dialog.
   final DialogTheme dialogTheme;
 
@@ -1684,7 +1561,7 @@
   ///
   /// Apps should migrate uses of this property to the theme's [colorScheme]
   /// [ColorScheme.secondary] color. In cases where a color is needed that
-  /// that contrasts well with the secondary color [ColorScheme.onSecondary]
+  /// contrasts well with the secondary color [ColorScheme.onSecondary]
   /// can be used.
   @Deprecated(
     'Use colorScheme.secondary instead. '
@@ -1712,54 +1589,6 @@
   Brightness get accentColorBrightness => _accentColorBrightness!;
   final Brightness? _accentColorBrightness;
 
-  /// Obsolete property that was originally used when a [TextTheme]
-  /// that contrasted well with the [accentColor] was needed.
-  ///
-  /// The material library no longer uses this property and most uses
-  /// of [accentColor] have been replaced with
-  /// the theme's [colorScheme] [ColorScheme.secondary].
-  /// You can configure the color of a [textTheme] [TextStyle] so that it
-  /// contrasts well with the [ColorScheme.secondary] like this:
-  ///
-  /// ```dart
-  /// final ThemeData theme = Theme.of(context);
-  /// final TextStyle style = theme.textTheme.displayLarge!.copyWith(
-  ///   color: theme.colorScheme.onSecondary,
-  /// );
-  /// // ...use style...
-  /// ```
-  @Deprecated(
-    'No longer used by the framework, please remove any reference to it. '
-    'For more information, consult the migration guide at '
-    'https://flutter.dev/docs/release/breaking-changes/theme-data-accent-properties#migration-guide. '
-    'This feature was deprecated after v2.3.0-0.1.pre.',
-  )
-  TextTheme get accentTextTheme => _accentTextTheme!;
-  final TextTheme? _accentTextTheme;
-
-  /// Obsolete property that was originally used when an [IconTheme]
-  /// that contrasted well with the [accentColor] was needed.
-  ///
-  /// The material library no longer uses this property and most uses
-  /// of [accentColor] have been replaced with
-  /// the theme's [colorScheme] [ColorScheme.secondary].
-  @Deprecated(
-    'No longer used by the framework, please remove any reference to it. '
-    'For more information, consult the migration guide at '
-    'https://flutter.dev/docs/release/breaking-changes/theme-data-accent-properties#migration-guide. '
-    'This feature was deprecated after v2.3.0-0.1.pre.',
-  )
-  IconThemeData get accentIconTheme => _accentIconTheme!;
-  final IconThemeData? _accentIconTheme;
-
-  /// The default fill color of the [Material].
-  @Deprecated(
-    'No longer used by the framework, please remove any reference to it. '
-    'This feature was deprecated after v2.3.0-0.2.pre.',
-  )
-  Color get buttonColor => _buttonColor!;
-  final Color? _buttonColor;
-
   /// An obsolete flag to allow apps to opt-out of a
   /// [small fix](https://github.com/flutter/flutter/issues/54028) for the Y
   /// coordinate of the floating label in a [TextField] [OutlineInputBorder].
@@ -1912,6 +1741,7 @@
     CheckboxThemeData? checkboxTheme,
     ChipThemeData? chipTheme,
     DataTableThemeData? dataTableTheme,
+    DatePickerThemeData? datePickerTheme,
     DialogTheme? dialogTheme,
     DividerThemeData? dividerTheme,
     DrawerThemeData? drawerTheme,
@@ -1958,25 +1788,6 @@
     )
     Brightness? accentColorBrightness,
     @Deprecated(
-      'No longer used by the framework, please remove any reference to it. '
-      'For more information, consult the migration guide at '
-      'https://flutter.dev/docs/release/breaking-changes/theme-data-accent-properties#migration-guide. '
-      'This feature was deprecated after v2.3.0-0.1.pre.',
-    )
-    TextTheme? accentTextTheme,
-    @Deprecated(
-      'No longer used by the framework, please remove any reference to it. '
-      'For more information, consult the migration guide at '
-      'https://flutter.dev/docs/release/breaking-changes/theme-data-accent-properties#migration-guide. '
-      'This feature was deprecated after v2.3.0-0.1.pre.',
-    )
-    IconThemeData? accentIconTheme,
-    @Deprecated(
-      'No longer used by the framework, please remove any reference to it. '
-      'This feature was deprecated after v2.3.0-0.2.pre.',
-    )
-    Color? buttonColor,
-    @Deprecated(
       'This "fix" is now enabled by default. '
       'This feature was deprecated after v2.5.0-1.0.pre.',
     )
@@ -2077,6 +1888,7 @@
       checkboxTheme: checkboxTheme ?? this.checkboxTheme,
       chipTheme: chipTheme ?? this.chipTheme,
       dataTableTheme: dataTableTheme ?? this.dataTableTheme,
+      datePickerTheme: datePickerTheme ?? this.datePickerTheme,
       dialogTheme: dialogTheme ?? this.dialogTheme,
       dividerTheme: dividerTheme ?? this.dividerTheme,
       drawerTheme: drawerTheme ?? this.drawerTheme,
@@ -2110,9 +1922,6 @@
       // DEPRECATED (newest deprecations at the bottom)
       accentColor: accentColor ?? _accentColor,
       accentColorBrightness: accentColorBrightness ?? _accentColorBrightness,
-      accentTextTheme: accentTextTheme ?? _accentTextTheme,
-      accentIconTheme: accentIconTheme ?? _accentIconTheme,
-      buttonColor: buttonColor ?? _buttonColor,
       fixTextFieldOutlineLabel: fixTextFieldOutlineLabel ?? _fixTextFieldOutlineLabel,
       primaryColorBrightness: primaryColorBrightness ?? _primaryColorBrightness,
       androidOverscrollIndicator: androidOverscrollIndicator ?? this.androidOverscrollIndicator,
@@ -2147,15 +1956,12 @@
     //
     // There are only two hard things in Computer Science: cache invalidation
     // and naming things. -- Phil Karlton
-    assert(baseTheme != null);
-    assert(localTextGeometry != null);
 
     return _localizedThemeDataCache.putIfAbsent(
       _IdentityThemeDataCacheKey(baseTheme, localTextGeometry),
       () {
         return baseTheme.copyWith(
           primaryTextTheme: localTextGeometry.merge(baseTheme.primaryTextTheme),
-          accentTextTheme: localTextGeometry.merge(baseTheme.accentTextTheme),
           textTheme: localTextGeometry.merge(baseTheme.textTheme),
         );
       },
@@ -2217,9 +2023,6 @@
   ///
   /// {@macro dart.ui.shadow.lerp}
   static ThemeData lerp(ThemeData a, ThemeData b, double t) {
-    assert(a != null);
-    assert(b != null);
-    assert(t != null);
     return ThemeData.raw(
       // For the sanity of the reader, make sure these properties are in the same
       // order in every place that they are separated by section comments (e.g.
@@ -2277,6 +2080,7 @@
       checkboxTheme: CheckboxThemeData.lerp(a.checkboxTheme, b.checkboxTheme, t),
       chipTheme: ChipThemeData.lerp(a.chipTheme, b.chipTheme, t)!,
       dataTableTheme: DataTableThemeData.lerp(a.dataTableTheme, b.dataTableTheme, t),
+      datePickerTheme: DatePickerThemeData.lerp(a.datePickerTheme, b.datePickerTheme, t),
       dialogTheme: DialogTheme.lerp(a.dialogTheme, b.dialogTheme, t),
       dividerTheme: DividerThemeData.lerp(a.dividerTheme, b.dividerTheme, t),
       drawerTheme: DrawerThemeData.lerp(a.drawerTheme, b.drawerTheme, t)!,
@@ -2310,9 +2114,6 @@
       // DEPRECATED (newest deprecations at the bottom)
       accentColor: Color.lerp(a.accentColor, b.accentColor, t),
       accentColorBrightness: t < 0.5 ? a.accentColorBrightness : b.accentColorBrightness,
-      accentTextTheme: TextTheme.lerp(a.accentTextTheme, b.accentTextTheme, t),
-      accentIconTheme: IconThemeData.lerp(a.accentIconTheme, b.accentIconTheme, t),
-      buttonColor: Color.lerp(a.buttonColor, b.buttonColor, t),
       fixTextFieldOutlineLabel: t < 0.5 ? a.fixTextFieldOutlineLabel : b.fixTextFieldOutlineLabel,
       primaryColorBrightness: t < 0.5 ? a.primaryColorBrightness : b.primaryColorBrightness,
       androidOverscrollIndicator:t < 0.5 ? a.androidOverscrollIndicator : b.androidOverscrollIndicator,
@@ -2386,6 +2187,7 @@
         other.checkboxTheme == checkboxTheme &&
         other.chipTheme == chipTheme &&
         other.dataTableTheme == dataTableTheme &&
+        other.datePickerTheme == datePickerTheme &&
         other.dialogTheme == dialogTheme &&
         other.dividerTheme == dividerTheme &&
         other.drawerTheme == drawerTheme &&
@@ -2419,9 +2221,6 @@
         // DEPRECATED (newest deprecations at the bottom)
         other.accentColor == accentColor &&
         other.accentColorBrightness == accentColorBrightness &&
-        other.accentTextTheme == accentTextTheme &&
-        other.accentIconTheme == accentIconTheme &&
-        other.buttonColor == buttonColor &&
         other.fixTextFieldOutlineLabel == fixTextFieldOutlineLabel &&
         other.primaryColorBrightness == primaryColorBrightness &&
         other.androidOverscrollIndicator == androidOverscrollIndicator &&
@@ -2492,6 +2291,7 @@
       checkboxTheme,
       chipTheme,
       dataTableTheme,
+      datePickerTheme,
       dialogTheme,
       dividerTheme,
       drawerTheme,
@@ -2525,9 +2325,6 @@
       // DEPRECATED (newest deprecations at the bottom)
       accentColor,
       accentColorBrightness,
-      accentTextTheme,
-      accentIconTheme,
-      buttonColor,
       fixTextFieldOutlineLabel,
       primaryColorBrightness,
       androidOverscrollIndicator,
@@ -2600,6 +2397,7 @@
     properties.add(DiagnosticsProperty<CheckboxThemeData>('checkboxTheme', checkboxTheme, defaultValue: defaultData.checkboxTheme, level: DiagnosticLevel.debug));
     properties.add(DiagnosticsProperty<ChipThemeData>('chipTheme', chipTheme, level: DiagnosticLevel.debug));
     properties.add(DiagnosticsProperty<DataTableThemeData>('dataTableTheme', dataTableTheme, defaultValue: defaultData.dataTableTheme, level: DiagnosticLevel.debug));
+    properties.add(DiagnosticsProperty<DatePickerThemeData>('datePickerTheme', datePickerTheme, defaultValue: defaultData.datePickerTheme, level: DiagnosticLevel.debug));
     properties.add(DiagnosticsProperty<DialogTheme>('dialogTheme', dialogTheme, defaultValue: defaultData.dialogTheme, level: DiagnosticLevel.debug));
     properties.add(DiagnosticsProperty<DividerThemeData>('dividerTheme', dividerTheme, defaultValue: defaultData.dividerTheme, level: DiagnosticLevel.debug));
     properties.add(DiagnosticsProperty<DrawerThemeData>('drawerTheme', drawerTheme, defaultValue: defaultData.drawerTheme, level: DiagnosticLevel.debug));
@@ -2633,9 +2431,6 @@
     // DEPRECATED (newest deprecations at the bottom)
     properties.add(ColorProperty('accentColor', accentColor, defaultValue: defaultData.accentColor, level: DiagnosticLevel.debug));
     properties.add(EnumProperty<Brightness>('accentColorBrightness', accentColorBrightness, defaultValue: defaultData.accentColorBrightness, level: DiagnosticLevel.debug));
-    properties.add(DiagnosticsProperty<TextTheme>('accentTextTheme', accentTextTheme, level: DiagnosticLevel.debug));
-    properties.add(DiagnosticsProperty<IconThemeData>('accentIconTheme', accentIconTheme, level: DiagnosticLevel.debug));
-    properties.add(ColorProperty('buttonColor', buttonColor, defaultValue: defaultData.buttonColor, level: DiagnosticLevel.debug));
     properties.add(DiagnosticsProperty<bool>('fixTextFieldOutlineLabel', fixTextFieldOutlineLabel, level: DiagnosticLevel.debug));
     properties.add(EnumProperty<Brightness>('primaryColorBrightness', primaryColorBrightness, defaultValue: defaultData.primaryColorBrightness, level: DiagnosticLevel.debug));
     properties.add(EnumProperty<AndroidOverscrollIndicator>('androidOverscrollIndicator', androidOverscrollIndicator, defaultValue: null, level: DiagnosticLevel.debug));
@@ -2655,7 +2450,7 @@
 /// styles.
 ///
 /// In the most basic case, [ThemeData]'s `cupertinoOverrideTheme` is null and
-/// and descendant Cupertino widgets' styling is derived from the Material theme.
+/// descendant Cupertino widgets' styling is derived from the Material theme.
 ///
 /// To override individual parts of the Material-derived Cupertino styling,
 /// `cupertinoOverrideTheme`'s construction parameters can be used.
@@ -2691,9 +2486,7 @@
   MaterialBasedCupertinoThemeData._(
     this._materialTheme,
     this._cupertinoOverrideTheme,
-  ) : assert(_materialTheme != null),
-      assert(_cupertinoOverrideTheme != null),
-      // Pass all values to the superclass so Material-agnostic properties
+  ) : // Pass all values to the superclass so Material-agnostic properties
       // like barBackgroundColor can still behave like a normal
       // CupertinoThemeData.
       super.raw(
@@ -2703,6 +2496,7 @@
         _cupertinoOverrideTheme.textTheme,
         _cupertinoOverrideTheme.barBackgroundColor,
         _cupertinoOverrideTheme.scaffoldBackgroundColor,
+        _cupertinoOverrideTheme.applyThemeToAll,
       );
 
   final ThemeData _materialTheme;
@@ -2738,6 +2532,7 @@
     CupertinoTextThemeData? textTheme,
     Color? barBackgroundColor,
     Color? scaffoldBackgroundColor,
+    bool? applyThemeToAll,
   }) {
     return MaterialBasedCupertinoThemeData._(
       _materialTheme,
@@ -2748,6 +2543,7 @@
         textTheme: textTheme,
         barBackgroundColor: barBackgroundColor,
         scaffoldBackgroundColor: scaffoldBackgroundColor,
+        applyThemeToAll: applyThemeToAll,
       ),
     );
   }
@@ -2791,7 +2587,7 @@
 /// The key that was inserted before all other keys is evicted first, i.e. the
 /// one inserted least recently.
 class _FifoCache<K, V> {
-  _FifoCache(this._maximumSize) : assert(_maximumSize != null && _maximumSize > 0);
+  _FifoCache(this._maximumSize) : assert(_maximumSize > 0);
 
   /// In Dart the map literal uses a linked hash-map implementation, whose keys
   /// are stored such that [Map.keys] returns them in the order they were
@@ -2810,7 +2606,6 @@
   /// The arguments must not be null.
   V putIfAbsent(K key, V Function() loader) {
     assert(key != null);
-    assert(loader != null);
     final V? result = _cache[key];
     if (result != null) {
       return result;
@@ -2867,9 +2662,7 @@
   const VisualDensity({
     this.horizontal = 0.0,
     this.vertical = 0.0,
-  }) : assert(horizontal != null),
-       assert(vertical != null),
-       assert(vertical <= maximumDensity),
+  }) : assert(vertical <= maximumDensity),
        assert(vertical <= maximumDensity),
        assert(vertical >= minimumDensity),
        assert(horizontal <= maximumDensity),
@@ -3000,7 +2793,7 @@
   /// The resulting minWidth and minHeight values are clamped to not exceed the
   /// maxWidth and maxHeight values, respectively.
   BoxConstraints effectiveConstraints(BoxConstraints constraints) {
-    assert(constraints != null && constraints.debugAssertIsValid());
+    assert(constraints.debugAssertIsValid());
     return constraints.copyWith(
       minWidth: clampDouble(constraints.minWidth + baseSizeAdjustment.dx, 0.0, constraints.maxWidth),
       minHeight: clampDouble(constraints.minHeight + baseSizeAdjustment.dy, 0.0, constraints.maxHeight),
@@ -3040,7 +2833,7 @@
 // Design token database by the script:
 //   dev/tools/gen_defaults/bin/gen_defaults.dart.
 
-// Token database version: v0_143
+// Token database version: v0_158
 
 const ColorScheme _colorSchemeLightM3 = ColorScheme(
   brightness: Brightness.light,
diff --git a/framework/lib/src/material/time.dart b/framework/lib/src/material/time.dart
index 2e6351c..02e64ac 100644
--- a/framework/lib/src/material/time.dart
+++ b/framework/lib/src/material/time.dart
@@ -108,7 +108,7 @@
     final MaterialLocalizations localizations = MaterialLocalizations.of(context);
     return localizations.formatTimeOfDay(
       this,
-      alwaysUse24HourFormat: MediaQuery.of(context).alwaysUse24HourFormat,
+      alwaysUse24HourFormat: MediaQuery.alwaysUse24HourFormatOf(context),
     );
   }
 
diff --git a/framework/lib/src/material/time_picker.dart b/framework/lib/src/material/time_picker.dart
index 6651366..e7f8c90 100644
--- a/framework/lib/src/material/time_picker.dart
+++ b/framework/lib/src/material/time_picker.dart
@@ -4,14 +4,15 @@
 
 import 'dart:async';
 import 'dart:math' as math;
+import 'package:engine/ui.dart' hide TextStyle;
 
 import 'package:flute/rendering.dart';
 import 'package:flute/services.dart';
 import 'package:flute/widgets.dart';
 
+import 'button_style.dart';
 import 'color_scheme.dart';
 import 'colors.dart';
-import 'constants.dart';
 import 'curves.dart';
 import 'debug.dart';
 import 'dialog.dart';
@@ -40,30 +41,22 @@
 const double _kTwoPi = 2 * math.pi;
 const Duration _kVibrateCommitDelay = Duration(milliseconds: 100);
 
-enum _TimePickerMode { hour, minute }
-
-const double _kTimePickerHeaderLandscapeWidth = 264.0;
-const double _kTimePickerHeaderControlHeight = 80.0;
-
-const double _kTimePickerWidthPortrait = 328.0;
-const double _kTimePickerWidthLandscape = 528.0;
-
-const double _kTimePickerHeightInput = 226.0;
-const double _kTimePickerHeightPortrait = 496.0;
-const double _kTimePickerHeightLandscape = 316.0;
-
-const double _kTimePickerHeightPortraitCollapsed = 484.0;
-const double _kTimePickerHeightLandscapeCollapsed = 304.0;
-
-const BorderRadius _kDefaultBorderRadius = BorderRadius.all(Radius.circular(4.0));
-const ShapeBorder _kDefaultShape = RoundedRectangleBorder(borderRadius: _kDefaultBorderRadius);
+const double _kTimePickerHeaderLandscapeWidth = 216;
+const double _kTimePickerInnerDialOffset = 28;
+const double _kTimePickerDialMinRadius = 50;
+const double _kTimePickerDialPadding = 28;
 
 /// Interactive input mode of the time picker dialog.
 ///
-/// In [TimePickerEntryMode.dial] mode, a clock dial is displayed and
-/// the user taps or drags the time they wish to select. In
-/// TimePickerEntryMode.input] mode, [TextField]s are displayed and the user
-/// types in the time they wish to select.
+/// In [TimePickerEntryMode.dial] mode, a clock dial is displayed and the user
+/// taps or drags the time they wish to select. In TimePickerEntryMode.input]
+/// mode, [TextField]s are displayed and the user types in the time they wish to
+/// select.
+///
+/// See also:
+///
+/// * [showTimePicker], a function that shows a [TimePickerDialog] and returns
+///   the selected time as a [Future].
 enum TimePickerEntryMode {
   /// User picks time from a clock dial.
   ///
@@ -86,186 +79,217 @@
   inputOnly
 }
 
-/// Provides properties for rendering time picker header fragments.
-@immutable
-class _TimePickerFragmentContext {
-  const _TimePickerFragmentContext({
-    required this.selectedTime,
-    required this.mode,
-    required this.onTimeChange,
-    required this.onModeChange,
-    required this.onHourDoubleTapped,
-    required this.onMinuteDoubleTapped,
-    required this.use24HourDials,
-  }) : assert(selectedTime != null),
-       assert(mode != null),
-       assert(onTimeChange != null),
-       assert(onModeChange != null),
-       assert(use24HourDials != null);
+// Whether the dial-mode time picker is currently selecting the hour or the
+// minute.
+enum _HourMinuteMode { hour, minute }
 
-  final TimeOfDay selectedTime;
-  final _TimePickerMode mode;
-  final ValueChanged<TimeOfDay> onTimeChange;
-  final ValueChanged<_TimePickerMode> onModeChange;
-  final GestureTapCallback onHourDoubleTapped;
-  final GestureTapCallback onMinuteDoubleTapped;
-  final bool use24HourDials;
+// Aspects of _TimePickerModel that can be depended upon.
+enum _TimePickerAspect {
+  use24HourFormat,
+  useMaterial3,
+  entryMode,
+  hourMinuteMode,
+  onHourMinuteModeChanged,
+  onHourDoubleTapped,
+  onMinuteDoubleTapped,
+  hourDialType,
+  selectedTime,
+  onSelectedTimeChanged,
+  orientation,
+  theme,
+  defaultTheme,
 }
 
-class _TimePickerHeader extends StatelessWidget {
-  const _TimePickerHeader({
-    required this.selectedTime,
-    required this.mode,
-    required this.orientation,
-    required this.onModeChanged,
-    required this.onChanged,
+class _TimePickerModel extends InheritedModel<_TimePickerAspect> {
+  const _TimePickerModel({
+    required this.entryMode,
+    required this.hourMinuteMode,
+    required this.onHourMinuteModeChanged,
     required this.onHourDoubleTapped,
     required this.onMinuteDoubleTapped,
-    required this.use24HourDials,
-    required this.helpText,
-  }) : assert(selectedTime != null),
-       assert(mode != null),
-       assert(orientation != null),
-       assert(use24HourDials != null);
+    required this.selectedTime,
+    required this.onSelectedTimeChanged,
+    required this.use24HourFormat,
+    required this.useMaterial3,
+    required this.hourDialType,
+    required this.orientation,
+    required this.theme,
+    required this.defaultTheme,
+    required super.child,
+  });
 
-  final TimeOfDay selectedTime;
-  final _TimePickerMode mode;
-  final Orientation orientation;
-  final ValueChanged<_TimePickerMode> onModeChanged;
-  final ValueChanged<TimeOfDay> onChanged;
+  final TimePickerEntryMode entryMode;
+  final _HourMinuteMode hourMinuteMode;
+  final ValueChanged<_HourMinuteMode> onHourMinuteModeChanged;
   final GestureTapCallback onHourDoubleTapped;
   final GestureTapCallback onMinuteDoubleTapped;
-  final bool use24HourDials;
-  final String? helpText;
+  final TimeOfDay selectedTime;
+  final ValueChanged<TimeOfDay> onSelectedTimeChanged;
+  final bool use24HourFormat;
+  final bool useMaterial3;
+  final _HourDialType hourDialType;
+  final Orientation orientation;
+  final TimePickerThemeData theme;
+  final _TimePickerDefaults defaultTheme;
 
-  void _handleChangeMode(_TimePickerMode value) {
-    if (value != mode) {
-      onModeChanged(value);
+  static _TimePickerModel of(BuildContext context, [_TimePickerAspect? aspect]) => InheritedModel.inheritFrom<_TimePickerModel>(context, aspect: aspect)!;
+  static TimePickerEntryMode entryModeOf(BuildContext context) => of(context, _TimePickerAspect.entryMode).entryMode;
+  static _HourMinuteMode hourMinuteModeOf(BuildContext context) => of(context, _TimePickerAspect.hourMinuteMode).hourMinuteMode;
+  static TimeOfDay selectedTimeOf(BuildContext context) => of(context, _TimePickerAspect.selectedTime).selectedTime;
+  static bool use24HourFormatOf(BuildContext context) => of(context, _TimePickerAspect.use24HourFormat).use24HourFormat;
+  static bool useMaterial3Of(BuildContext context) => of(context, _TimePickerAspect.useMaterial3).useMaterial3;
+  static _HourDialType hourDialTypeOf(BuildContext context) => of(context, _TimePickerAspect.hourDialType).hourDialType;
+  static Orientation orientationOf(BuildContext context) => of(context, _TimePickerAspect.orientation).orientation;
+  static TimePickerThemeData themeOf(BuildContext context) => of(context, _TimePickerAspect.theme).theme;
+  static _TimePickerDefaults defaultThemeOf(BuildContext context) => of(context, _TimePickerAspect.defaultTheme).defaultTheme;
+
+  static void setSelectedTime(BuildContext context, TimeOfDay value) => of(context, _TimePickerAspect.onSelectedTimeChanged).onSelectedTimeChanged(value);
+  static void setHourMinuteMode(BuildContext context, _HourMinuteMode value) => of(context, _TimePickerAspect.onHourMinuteModeChanged).onHourMinuteModeChanged(value);
+
+  @override
+  bool updateShouldNotifyDependent(_TimePickerModel oldWidget, Set<_TimePickerAspect> dependencies) {
+    if (use24HourFormat != oldWidget.use24HourFormat && dependencies.contains(_TimePickerAspect.use24HourFormat)) {
+      return true;
     }
+    if (useMaterial3 != oldWidget.useMaterial3 && dependencies.contains(_TimePickerAspect.useMaterial3)) {
+      return true;
+    }
+    if (entryMode != oldWidget.entryMode && dependencies.contains(_TimePickerAspect.entryMode)) {
+      return true;
+    }
+    if (hourMinuteMode != oldWidget.hourMinuteMode && dependencies.contains(_TimePickerAspect.hourMinuteMode)) {
+      return true;
+    }
+    if (onHourMinuteModeChanged != oldWidget.onHourMinuteModeChanged && dependencies.contains(_TimePickerAspect.onHourMinuteModeChanged)) {
+      return true;
+    }
+    if (onHourMinuteModeChanged != oldWidget.onHourDoubleTapped && dependencies.contains(_TimePickerAspect.onHourDoubleTapped)) {
+      return true;
+    }
+    if (onHourMinuteModeChanged != oldWidget.onMinuteDoubleTapped && dependencies.contains(_TimePickerAspect.onMinuteDoubleTapped)) {
+      return true;
+    }
+    if (hourDialType != oldWidget.hourDialType && dependencies.contains(_TimePickerAspect.hourDialType)) {
+      return true;
+    }
+    if (selectedTime != oldWidget.selectedTime && dependencies.contains(_TimePickerAspect.selectedTime)) {
+      return true;
+    }
+    if (onSelectedTimeChanged != oldWidget.onSelectedTimeChanged && dependencies.contains(_TimePickerAspect.onSelectedTimeChanged)) {
+      return true;
+    }
+    if (orientation != oldWidget.orientation && dependencies.contains(_TimePickerAspect.orientation)) {
+      return true;
+    }
+    if (theme != oldWidget.theme && dependencies.contains(_TimePickerAspect.theme)) {
+      return true;
+    }
+    if (defaultTheme != oldWidget.defaultTheme && dependencies.contains(_TimePickerAspect.defaultTheme)) {
+      return true;
+    }
+    return false;
   }
 
   @override
+  bool updateShouldNotify(_TimePickerModel oldWidget) {
+    return use24HourFormat != oldWidget.use24HourFormat
+        || useMaterial3 != oldWidget.useMaterial3
+        || entryMode != oldWidget.entryMode
+        || hourMinuteMode != oldWidget.hourMinuteMode
+        || onHourMinuteModeChanged != oldWidget.onHourMinuteModeChanged
+        || onHourDoubleTapped != oldWidget.onHourDoubleTapped
+        || onMinuteDoubleTapped != oldWidget.onMinuteDoubleTapped
+        || hourDialType != oldWidget.hourDialType
+        || selectedTime != oldWidget.selectedTime
+        || onSelectedTimeChanged != oldWidget.onSelectedTimeChanged
+        || orientation != oldWidget.orientation
+        || theme != oldWidget.theme
+        || defaultTheme != oldWidget.defaultTheme;
+  }
+}
+
+class _TimePickerHeader extends StatelessWidget {
+  const _TimePickerHeader({ required this.helpText });
+
+  final String helpText;
+
+  @override
   Widget build(BuildContext context) {
-    assert(debugCheckHasMediaQuery(context));
-    final ThemeData themeData = Theme.of(context);
     final TimeOfDayFormat timeOfDayFormat = MaterialLocalizations.of(context).timeOfDayFormat(
-      alwaysUse24HourFormat: MediaQuery.of(context).alwaysUse24HourFormat,
-    );
-    final MaterialLocalizations localizations = MaterialLocalizations.of(context);
-    final String timePickerDialHelpText = themeData.useMaterial3
-      ? localizations.timePickerDialHelpText
-      : localizations.timePickerDialHelpText.toUpperCase();
-
-    final _TimePickerFragmentContext fragmentContext = _TimePickerFragmentContext(
-      selectedTime: selectedTime,
-      mode: mode,
-      onTimeChange: onChanged,
-      onModeChange: _handleChangeMode,
-      onHourDoubleTapped: onHourDoubleTapped,
-      onMinuteDoubleTapped: onMinuteDoubleTapped,
-      use24HourDials: use24HourDials,
+      alwaysUse24HourFormat: _TimePickerModel.use24HourFormatOf(context),
     );
 
-    final EdgeInsets padding;
-    double? width;
-    final Widget controls;
-
-    switch (orientation) {
+    final _HourDialType hourDialType = _TimePickerModel.hourDialTypeOf(context);
+    switch (_TimePickerModel.orientationOf(context)) {
       case Orientation.portrait:
-        // Keep width null because in portrait we don't cap the width.
-        padding = const EdgeInsets.symmetric(horizontal: 24.0);
-        controls = Column(
+        return Column(
+          crossAxisAlignment: CrossAxisAlignment.start,
           children: <Widget>[
-            const SizedBox(height: 16.0),
-            SizedBox(
-              height: kMinInteractiveDimension * 2,
-              child: Row(
-                children: <Widget>[
-                  if (!use24HourDials && timeOfDayFormat == TimeOfDayFormat.a_space_h_colon_mm) ...<Widget>[
-                    _DayPeriodControl(
-                      selectedTime: selectedTime,
-                      orientation: orientation,
-                      onChanged: onChanged,
-                    ),
-                    const SizedBox(width: 12.0),
+            Padding(padding: EdgeInsetsDirectional.only(bottom: _TimePickerModel.useMaterial3Of(context) ? 20 : 24),
+              child: Text(
+                helpText,
+                style: _TimePickerModel.themeOf(context).helpTextStyle ?? _TimePickerModel.defaultThemeOf(context).helpTextStyle,
+              ),
+            ),
+            Row(
+              children: <Widget>[
+                if (hourDialType == _HourDialType.twelveHour && timeOfDayFormat == TimeOfDayFormat.a_space_h_colon_mm)
+                  const _DayPeriodControl(),
+                Expanded(
+                  child: Row(
+                    // Hour/minutes should not change positions in RTL locales.
+                    textDirection: TextDirection.ltr,
+                    children: <Widget>[
+                      const Expanded(child: _HourControl()),
+                      _StringFragment(timeOfDayFormat: timeOfDayFormat),
+                      const Expanded(child: _MinuteControl()),
+                    ],
+                  ),
+                ),
+                if (hourDialType == _HourDialType.twelveHour && timeOfDayFormat != TimeOfDayFormat.a_space_h_colon_mm)
+                  ...<Widget>[
+                    const SizedBox(width: 12),
+                    const _DayPeriodControl(),
                   ],
-                  Expanded(
+              ],
+            ),
+          ],
+        );
+      case Orientation.landscape:
+        return SizedBox(
+          width: _kTimePickerHeaderLandscapeWidth,
+          child: Stack(
+            children: <Widget>[
+              Text(
+                helpText,
+                style: _TimePickerModel.themeOf(context).helpTextStyle ?? _TimePickerModel.defaultThemeOf(context).helpTextStyle,
+              ),
+              Column(
+                mainAxisAlignment: MainAxisAlignment.center,
+                crossAxisAlignment: CrossAxisAlignment.start,
+                children: <Widget>[
+                  if (hourDialType == _HourDialType.twelveHour && timeOfDayFormat == TimeOfDayFormat.a_space_h_colon_mm)
+                    const _DayPeriodControl(),
+                  Padding(
+                    padding: EdgeInsets.only(bottom: hourDialType == _HourDialType.twelveHour ? 12 : 0),
                     child: Row(
                       // Hour/minutes should not change positions in RTL locales.
                       textDirection: TextDirection.ltr,
                       children: <Widget>[
-                        Expanded(child: _HourControl(fragmentContext: fragmentContext)),
+                        const Expanded(child: _HourControl()),
                         _StringFragment(timeOfDayFormat: timeOfDayFormat),
-                        Expanded(child: _MinuteControl(fragmentContext: fragmentContext)),
+                        const Expanded(child: _MinuteControl()),
                       ],
                     ),
                   ),
-                  if (!use24HourDials && timeOfDayFormat != TimeOfDayFormat.a_space_h_colon_mm) ...<Widget>[
-                    const SizedBox(width: 12.0),
-                    _DayPeriodControl(
-                      selectedTime: selectedTime,
-                      orientation: orientation,
-                      onChanged: onChanged,
-                    ),
-                  ],
+                  if (hourDialType == _HourDialType.twelveHour && timeOfDayFormat != TimeOfDayFormat.a_space_h_colon_mm)
+                    const _DayPeriodControl(),
                 ],
               ),
-            ),
-          ],
-        );
-        break;
-      case Orientation.landscape:
-        width = _kTimePickerHeaderLandscapeWidth;
-        padding = const EdgeInsets.symmetric(horizontal: 24.0);
-        controls = Expanded(
-          child: Column(
-            mainAxisAlignment: MainAxisAlignment.center,
-            children: <Widget>[
-              if (!use24HourDials && timeOfDayFormat == TimeOfDayFormat.a_space_h_colon_mm)
-                _DayPeriodControl(
-                  selectedTime: selectedTime,
-                  orientation: orientation,
-                  onChanged: onChanged,
-                ),
-              SizedBox(
-                height: kMinInteractiveDimension * 2,
-                child: Row(
-                  // Hour/minutes should not change positions in RTL locales.
-                  textDirection: TextDirection.ltr,
-                  children: <Widget>[
-                    Expanded(child: _HourControl(fragmentContext: fragmentContext)),
-                    _StringFragment(timeOfDayFormat: timeOfDayFormat),
-                    Expanded(child: _MinuteControl(fragmentContext: fragmentContext)),
-                  ],
-                ),
-              ),
-              if (!use24HourDials && timeOfDayFormat != TimeOfDayFormat.a_space_h_colon_mm)
-                _DayPeriodControl(
-                  selectedTime: selectedTime,
-                  orientation: orientation,
-                  onChanged: onChanged,
-                ),
             ],
           ),
         );
-        break;
     }
-
-    return Container(
-      width: width,
-      padding: padding,
-      child: Column(
-        crossAxisAlignment: CrossAxisAlignment.start,
-        children: <Widget>[
-          const SizedBox(height: 16.0),
-          Text(
-            helpText ?? timePickerDialHelpText,
-            style: TimePickerTheme.of(context).helpTextStyle ?? themeData.textTheme.labelSmall,
-          ),
-          controls,
-        ],
-      ),
-    );
   }
 }
 
@@ -275,9 +299,7 @@
     required this.onTap,
     required this.onDoubleTap,
     required this.isSelected,
-  }) : assert(text != null),
-       assert(onTap != null),
-       assert(isSelected != null);
+  });
 
   final String text;
   final GestureTapCallback onTap;
@@ -286,27 +308,37 @@
 
   @override
   Widget build(BuildContext context) {
-    final ThemeData themeData = Theme.of(context);
-    final TimePickerThemeData timePickerTheme = TimePickerTheme.of(context);
-    final bool isDark = themeData.colorScheme.brightness == Brightness.dark;
-    final Color textColor = timePickerTheme.hourMinuteTextColor
-        ?? MaterialStateColor.resolveWith((Set<MaterialState> states) {
-          return states.contains(MaterialState.selected)
-              ? themeData.colorScheme.primary
-              : themeData.colorScheme.onSurface;
-        });
-    final Color backgroundColor = timePickerTheme.hourMinuteColor
-        ?? MaterialStateColor.resolveWith((Set<MaterialState> states) {
-          return states.contains(MaterialState.selected)
-              ? themeData.colorScheme.primary.withOpacity(isDark ? 0.24 : 0.12)
-              : themeData.colorScheme.onSurface.withOpacity(0.12);
-        });
-    final TextStyle style = timePickerTheme.hourMinuteTextStyle ?? themeData.textTheme.displayMedium!;
-    final ShapeBorder shape = timePickerTheme.hourMinuteShape ?? _kDefaultShape;
+    final TimePickerThemeData timePickerTheme = _TimePickerModel.themeOf(context);
+    final _TimePickerDefaults defaultTheme = _TimePickerModel.defaultThemeOf(context);
+    final Color backgroundColor = timePickerTheme.hourMinuteColor ?? defaultTheme.hourMinuteColor;
+    final ShapeBorder shape = timePickerTheme.hourMinuteShape ?? defaultTheme.hourMinuteShape;
 
-    final Set<MaterialState> states = isSelected ? <MaterialState>{MaterialState.selected} : <MaterialState>{};
+    final Set<MaterialState> states = <MaterialState>{
+      if (isSelected) MaterialState.selected,
+    };
+    final Color effectiveTextColor = MaterialStateProperty.resolveAs<Color>(
+      _TimePickerModel.themeOf(context).hourMinuteTextColor ?? _TimePickerModel.defaultThemeOf(context).hourMinuteTextColor,
+      states,
+    );
+    final TextStyle effectiveStyle = MaterialStateProperty.resolveAs<TextStyle>(
+      timePickerTheme.hourMinuteTextStyle ?? defaultTheme.hourMinuteTextStyle,
+      states,
+    ).copyWith(color: effectiveTextColor);
+
+    final double height;
+    switch (_TimePickerModel.entryModeOf(context)) {
+      case TimePickerEntryMode.dial:
+      case TimePickerEntryMode.dialOnly:
+        height = defaultTheme.hourMinuteSize.height;
+        break;
+      case TimePickerEntryMode.input:
+      case TimePickerEntryMode.inputOnly:
+        height = defaultTheme.hourMinuteInputSize.height;
+        break;
+    }
+
     return SizedBox(
-      height: _kTimePickerHeaderControlHeight,
+      height: height,
       child: Material(
         color: MaterialStateProperty.resolveAs(backgroundColor, states),
         clipBehavior: Clip.antiAlias,
@@ -317,8 +349,8 @@
           child: Center(
             child: Text(
               text,
-              style: style.copyWith(color: MaterialStateProperty.resolveAs(textColor, states)),
-              textScaleFactor: 1.0,
+              style: effectiveStyle,
+              textScaleFactor: 1,
             ),
           ),
         ),
@@ -326,39 +358,39 @@
     );
   }
 }
+
 /// Displays the hour fragment.
 ///
-/// When tapped changes time picker dial mode to [_TimePickerMode.hour].
+/// When tapped changes time picker dial mode to [_HourMinuteMode.hour].
 class _HourControl extends StatelessWidget {
-  const _HourControl({
-    required this.fragmentContext,
-  });
-
-  final _TimePickerFragmentContext fragmentContext;
+  const _HourControl();
 
   @override
   Widget build(BuildContext context) {
     assert(debugCheckHasMediaQuery(context));
-    final bool alwaysUse24HourFormat = MediaQuery.of(context).alwaysUse24HourFormat;
+    final bool alwaysUse24HourFormat = MediaQuery.alwaysUse24HourFormatOf(context);
+    final TimeOfDay selectedTime = _TimePickerModel.selectedTimeOf(context);
     final MaterialLocalizations localizations = MaterialLocalizations.of(context);
     final String formattedHour = localizations.formatHour(
-      fragmentContext.selectedTime,
-      alwaysUse24HourFormat: alwaysUse24HourFormat,
+      selectedTime,
+      alwaysUse24HourFormat: _TimePickerModel.use24HourFormatOf(context),
     );
 
     TimeOfDay hoursFromSelected(int hoursToAdd) {
-      if (fragmentContext.use24HourDials) {
-        final int selectedHour = fragmentContext.selectedTime.hour;
-        return fragmentContext.selectedTime.replacing(
-          hour: (selectedHour + hoursToAdd) % TimeOfDay.hoursPerDay,
-        );
-      } else {
-        // Cycle 1 through 12 without changing day period.
-        final int periodOffset = fragmentContext.selectedTime.periodOffset;
-        final int hours = fragmentContext.selectedTime.hourOfPeriod;
-        return fragmentContext.selectedTime.replacing(
-          hour: periodOffset + (hours + hoursToAdd) % TimeOfDay.hoursPerPeriod,
-        );
+      switch (_TimePickerModel.hourDialTypeOf(context)) {
+        case _HourDialType.twentyFourHour:
+        case _HourDialType.twentyFourHourDoubleRing:
+          final int selectedHour = selectedTime.hour;
+          return selectedTime.replacing(
+            hour: (selectedHour + hoursToAdd) % TimeOfDay.hoursPerDay,
+          );
+        case _HourDialType.twelveHour:
+          // Cycle 1 through 12 without changing day period.
+          final int periodOffset = selectedTime.periodOffset;
+          final int hours = selectedTime.hourOfPeriod;
+          return selectedTime.replacing(
+            hour: periodOffset + (hours + hoursToAdd) % TimeOfDay.hoursPerPeriod,
+          );
       }
     }
 
@@ -378,27 +410,27 @@
       excludeSemantics: true,
       increasedValue: formattedNextHour,
       onIncrease: () {
-        fragmentContext.onTimeChange(nextHour);
+        _TimePickerModel.setSelectedTime(context, nextHour);
       },
       decreasedValue: formattedPreviousHour,
       onDecrease: () {
-        fragmentContext.onTimeChange(previousHour);
+        _TimePickerModel.setSelectedTime(context, previousHour);
       },
       child: _HourMinuteControl(
-        isSelected: fragmentContext.mode == _TimePickerMode.hour,
+        isSelected: _TimePickerModel.hourMinuteModeOf(context) == _HourMinuteMode.hour,
         text: formattedHour,
-        onTap: Feedback.wrapForTap(() => fragmentContext.onModeChange(_TimePickerMode.hour), context)!,
-        onDoubleTap: fragmentContext.onHourDoubleTapped,
+        onTap: Feedback.wrapForTap(() => _TimePickerModel.setHourMinuteMode(context, _HourMinuteMode.hour), context)!,
+        onDoubleTap: _TimePickerModel.of(context, _TimePickerAspect.onHourDoubleTapped).onHourDoubleTapped,
       ),
     );
   }
 }
 
 /// A passive fragment showing a string value.
+///
+/// Used to display the appropriate separator between the input fields.
 class _StringFragment extends StatelessWidget {
-  const _StringFragment({
-    required this.timeOfDayFormat,
-  });
+  const _StringFragment({ required this.timeOfDayFormat });
 
   final TimeOfDayFormat timeOfDayFormat;
 
@@ -420,18 +452,39 @@
   Widget build(BuildContext context) {
     final ThemeData theme = Theme.of(context);
     final TimePickerThemeData timePickerTheme = TimePickerTheme.of(context);
-    final TextStyle hourMinuteStyle = timePickerTheme.hourMinuteTextStyle ?? theme.textTheme.displayMedium!;
-    final Color textColor = timePickerTheme.hourMinuteTextColor ?? theme.colorScheme.onSurface;
+    final _TimePickerDefaults defaultTheme = theme.useMaterial3 ? _TimePickerDefaultsM3(context) : _TimePickerDefaultsM2(context);
+    final Set<MaterialState> states = <MaterialState>{};
+
+    final Color effectiveTextColor = MaterialStateProperty.resolveAs<Color>(
+      timePickerTheme.hourMinuteTextColor ?? defaultTheme.hourMinuteTextColor,
+      states,
+    );
+    final TextStyle effectiveStyle = MaterialStateProperty.resolveAs<TextStyle>(
+      timePickerTheme.hourMinuteTextStyle ?? defaultTheme.hourMinuteTextStyle,
+      states,
+    ).copyWith(color: effectiveTextColor);
+
+    final double height;
+    switch (_TimePickerModel.entryModeOf(context)) {
+      case TimePickerEntryMode.dial:
+      case TimePickerEntryMode.dialOnly:
+        height = defaultTheme.hourMinuteSize.height;
+        break;
+      case TimePickerEntryMode.input:
+      case TimePickerEntryMode.inputOnly:
+        height = defaultTheme.hourMinuteInputSize.height;
+        break;
+    }
 
     return ExcludeSemantics(
-      child: Padding(
-        padding: const EdgeInsets.symmetric(horizontal: 6.0),
-        child: Center(
-          child: Text(
-            _stringFragmentValue(timeOfDayFormat),
-            style: hourMinuteStyle.apply(color: MaterialStateProperty.resolveAs(textColor, <MaterialState>{})),
-            textScaleFactor: 1.0,
-          ),
+      child: SizedBox(
+        width: timeOfDayFormat == TimeOfDayFormat.frenchCanadian ? 36 : 24,
+        height: height,
+        child: Text(
+          _stringFragmentValue(timeOfDayFormat),
+          style: effectiveStyle,
+          textScaleFactor: 1,
+          textAlign: TextAlign.center,
         ),
       ),
     );
@@ -440,24 +493,21 @@
 
 /// Displays the minute fragment.
 ///
-/// When tapped changes time picker dial mode to [_TimePickerMode.minute].
+/// When tapped changes time picker dial mode to [_HourMinuteMode.minute].
 class _MinuteControl extends StatelessWidget {
-  const _MinuteControl({
-    required this.fragmentContext,
-  });
-
-  final _TimePickerFragmentContext fragmentContext;
+  const _MinuteControl();
 
   @override
   Widget build(BuildContext context) {
     final MaterialLocalizations localizations = MaterialLocalizations.of(context);
-    final String formattedMinute = localizations.formatMinute(fragmentContext.selectedTime);
-    final TimeOfDay nextMinute = fragmentContext.selectedTime.replacing(
-      minute: (fragmentContext.selectedTime.minute + 1) % TimeOfDay.minutesPerHour,
+    final TimeOfDay selectedTime = _TimePickerModel.selectedTimeOf(context);
+    final String formattedMinute = localizations.formatMinute(selectedTime);
+    final TimeOfDay nextMinute = selectedTime.replacing(
+      minute: (selectedTime.minute + 1) % TimeOfDay.minutesPerHour,
     );
     final String formattedNextMinute = localizations.formatMinute(nextMinute);
-    final TimeOfDay previousMinute = fragmentContext.selectedTime.replacing(
-      minute: (fragmentContext.selectedTime.minute - 1) % TimeOfDay.minutesPerHour,
+    final TimeOfDay previousMinute = selectedTime.replacing(
+      minute: (selectedTime.minute - 1) % TimeOfDay.minutesPerHour,
     );
     final String formattedPreviousMinute = localizations.formatMinute(previousMinute);
 
@@ -466,43 +516,42 @@
       value: '${localizations.timePickerMinuteModeAnnouncement} $formattedMinute',
       increasedValue: formattedNextMinute,
       onIncrease: () {
-        fragmentContext.onTimeChange(nextMinute);
+        _TimePickerModel.setSelectedTime(context, nextMinute);
       },
       decreasedValue: formattedPreviousMinute,
       onDecrease: () {
-        fragmentContext.onTimeChange(previousMinute);
+        _TimePickerModel.setSelectedTime(context, previousMinute);
       },
       child: _HourMinuteControl(
-        isSelected: fragmentContext.mode == _TimePickerMode.minute,
+        isSelected: _TimePickerModel.hourMinuteModeOf(context) == _HourMinuteMode.minute,
         text: formattedMinute,
-        onTap: Feedback.wrapForTap(() => fragmentContext.onModeChange(_TimePickerMode.minute), context)!,
-        onDoubleTap: fragmentContext.onMinuteDoubleTapped,
+        onTap: Feedback.wrapForTap(() => _TimePickerModel.setHourMinuteMode(context, _HourMinuteMode.minute), context)!,
+        onDoubleTap: _TimePickerModel.of(context, _TimePickerAspect.onMinuteDoubleTapped).onMinuteDoubleTapped,
       ),
     );
   }
 }
 
-
 /// Displays the am/pm fragment and provides controls for switching between am
 /// and pm.
 class _DayPeriodControl extends StatelessWidget {
-  const _DayPeriodControl({
-    required this.selectedTime,
-    required this.onChanged,
-    required this.orientation,
-  });
+  const _DayPeriodControl({ this.onPeriodChanged });
 
-  final TimeOfDay selectedTime;
-  final Orientation orientation;
-  final ValueChanged<TimeOfDay> onChanged;
+  final ValueChanged<TimeOfDay>? onPeriodChanged;
 
-  void _togglePeriod() {
+  void _togglePeriod(BuildContext context) {
+    final TimeOfDay selectedTime = _TimePickerModel.selectedTimeOf(context);
     final int newHour = (selectedTime.hour + TimeOfDay.hoursPerPeriod) % TimeOfDay.hoursPerDay;
     final TimeOfDay newTime = selectedTime.replacing(hour: newHour);
-    onChanged(newTime);
+    if (onPeriodChanged != null) {
+      onPeriodChanged!.call(newTime);
+    } else {
+      _TimePickerModel.setSelectedTime(context, newTime);
+    }
   }
 
   void _setAm(BuildContext context) {
+    final TimeOfDay selectedTime = _TimePickerModel.selectedTimeOf(context);
     if (selectedTime.period == DayPeriod.am) {
       return;
     }
@@ -517,10 +566,11 @@
       case TargetPlatform.macOS:
         break;
     }
-    _togglePeriod();
+    _togglePeriod(context);
   }
 
   void _setPm(BuildContext context) {
+    final TimeOfDay selectedTime = _TimePickerModel.selectedTimeOf(context);
     if (selectedTime.period == DayPeriod.pm) {
       return;
     }
@@ -535,113 +585,72 @@
       case TargetPlatform.macOS:
         break;
     }
-    _togglePeriod();
+    _togglePeriod(context);
   }
 
   @override
   Widget build(BuildContext context) {
     final MaterialLocalizations materialLocalizations = MaterialLocalizations.of(context);
-    final ColorScheme colorScheme = Theme.of(context).colorScheme;
-    final TimePickerThemeData timePickerTheme = TimePickerTheme.of(context);
-    final bool isDark = colorScheme.brightness == Brightness.dark;
-    final Color textColor = timePickerTheme.dayPeriodTextColor
-        ?? MaterialStateColor.resolveWith((Set<MaterialState> states) {
-          return states.contains(MaterialState.selected)
-              ? colorScheme.primary
-              : colorScheme.onSurface.withOpacity(0.60);
-        });
-    final Color backgroundColor = timePickerTheme.dayPeriodColor
-        ?? MaterialStateColor.resolveWith((Set<MaterialState> states) {
-          // The unselected day period should match the overall picker dialog
-          // color. Making it transparent enables that without being redundant
-          // and allows the optional elevation overlay for dark mode to be
-          // visible.
-          return states.contains(MaterialState.selected)
-              ? colorScheme.primary.withOpacity(isDark ? 0.24 : 0.12)
-              : Colors.transparent;
-        });
+    final TimePickerThemeData timePickerTheme = _TimePickerModel.themeOf(context);
+    final _TimePickerDefaults defaultTheme = _TimePickerModel.defaultThemeOf(context);
+    final TimeOfDay selectedTime = _TimePickerModel.selectedTimeOf(context);
     final bool amSelected = selectedTime.period == DayPeriod.am;
-    final Set<MaterialState> amStates = amSelected ? <MaterialState>{MaterialState.selected} : <MaterialState>{};
     final bool pmSelected = !amSelected;
-    final Set<MaterialState> pmStates = pmSelected ? <MaterialState>{MaterialState.selected} : <MaterialState>{};
-    final TextStyle textStyle = timePickerTheme.dayPeriodTextStyle ?? Theme.of(context).textTheme.titleMedium!;
-    final TextStyle amStyle = textStyle.copyWith(
-      color: MaterialStateProperty.resolveAs(textColor, amStates),
-    );
-    final TextStyle pmStyle = textStyle.copyWith(
-      color: MaterialStateProperty.resolveAs(textColor, pmStates),
-    );
-    OutlinedBorder shape = timePickerTheme.dayPeriodShape ??
-        const RoundedRectangleBorder(borderRadius: _kDefaultBorderRadius);
-    final BorderSide borderSide = timePickerTheme.dayPeriodBorderSide ?? BorderSide(
-      color: Color.alphaBlend(colorScheme.onBackground.withOpacity(0.38), colorScheme.surface),
-    );
-    // Apply the custom borderSide.
-    shape = shape.copyWith(
-      side: borderSide,
+    final BorderSide resolvedSide = timePickerTheme.dayPeriodBorderSide ?? defaultTheme.dayPeriodBorderSide;
+    final OutlinedBorder resolvedShape = (timePickerTheme.dayPeriodShape ?? defaultTheme.dayPeriodShape)
+      .copyWith(side: resolvedSide);
+
+    final Widget amButton = _AmPmButton(
+      selected: amSelected,
+      onPressed: () => _setAm(context),
+      label: materialLocalizations.anteMeridiemAbbreviation,
     );
 
-    final double buttonTextScaleFactor = math.min(MediaQuery.of(context).textScaleFactor, 2.0);
-
-    final Widget amButton = Material(
-      color: MaterialStateProperty.resolveAs(backgroundColor, amStates),
-      child: InkWell(
-        onTap: Feedback.wrapForTap(() => _setAm(context), context),
-        child: Semantics(
-          checked: amSelected,
-          inMutuallyExclusiveGroup: true,
-          button: true,
-          child: Center(
-            child: Text(
-              materialLocalizations.anteMeridiemAbbreviation,
-              style: amStyle,
-              textScaleFactor: buttonTextScaleFactor,
-            ),
-          ),
-        ),
-      ),
+    final Widget pmButton = _AmPmButton(
+      selected: pmSelected,
+      onPressed: () => _setPm(context),
+      label: materialLocalizations.postMeridiemAbbreviation,
     );
 
-    final Widget pmButton = Material(
-      color: MaterialStateProperty.resolveAs(backgroundColor, pmStates),
-      child: InkWell(
-        onTap: Feedback.wrapForTap(() => _setPm(context), context),
-        child: Semantics(
-          checked: pmSelected,
-          inMutuallyExclusiveGroup: true,
-          button: true,
-          child: Center(
-            child: Text(
-              materialLocalizations.postMeridiemAbbreviation,
-              style: pmStyle,
-              textScaleFactor: buttonTextScaleFactor,
-            ),
-          ),
-        ),
-      ),
-    );
+    Size dayPeriodSize;
+    final Orientation orientation;
+    switch (_TimePickerModel.entryModeOf(context)) {
+      case TimePickerEntryMode.dial:
+      case TimePickerEntryMode.dialOnly:
+        orientation = _TimePickerModel.orientationOf(context);
+        switch (orientation) {
+          case Orientation.portrait:
+            dayPeriodSize = defaultTheme.dayPeriodPortraitSize;
+            break;
+          case Orientation.landscape:
+            dayPeriodSize = defaultTheme.dayPeriodLandscapeSize;
+            break;
+        }
+        break;
+      case TimePickerEntryMode.input:
+      case TimePickerEntryMode.inputOnly:
+        orientation = Orientation.portrait;
+        dayPeriodSize = defaultTheme.dayPeriodInputSize;
+        break;
+    }
 
     final Widget result;
     switch (orientation) {
       case Orientation.portrait:
-        const double width = 52.0;
         result = _DayPeriodInputPadding(
-          minSize: const Size(width, kMinInteractiveDimension * 2),
+          minSize: dayPeriodSize,
           orientation: orientation,
-          child: SizedBox(
-            width: width,
-            height: _kTimePickerHeaderControlHeight,
+          child: SizedBox.fromSize(
+            size: dayPeriodSize,
             child: Material(
               clipBehavior: Clip.antiAlias,
               color: Colors.transparent,
-              shape: shape,
+              shape: resolvedShape,
               child: Column(
                 children: <Widget>[
                   Expanded(child: amButton),
                   Container(
-                    decoration: BoxDecoration(
-                      border: Border(top: borderSide),
-                    ),
+                    decoration: BoxDecoration(border: Border(top: resolvedSide)),
                     height: 1,
                   ),
                   Expanded(child: pmButton),
@@ -653,21 +662,19 @@
         break;
       case Orientation.landscape:
         result = _DayPeriodInputPadding(
-          minSize: const Size(0.0, kMinInteractiveDimension),
+          minSize: dayPeriodSize,
           orientation: orientation,
           child: SizedBox(
-            height: 40.0,
+            height: dayPeriodSize.height,
             child: Material(
               clipBehavior: Clip.antiAlias,
               color: Colors.transparent,
-              shape: shape,
+              shape: resolvedShape,
               child: Row(
                 children: <Widget>[
                   Expanded(child: amButton),
                   Container(
-                    decoration: BoxDecoration(
-                      border: Border(left: borderSide),
-                    ),
+                    decoration: BoxDecoration(border: Border(left: resolvedSide)),
                     width: 1,
                   ),
                   Expanded(child: pmButton),
@@ -682,6 +689,48 @@
   }
 }
 
+class _AmPmButton extends StatelessWidget {
+  const _AmPmButton({
+    required this.onPressed,
+    required this.selected,
+    required this.label,
+  });
+
+  final bool selected;
+  final VoidCallback onPressed;
+  final String label;
+
+  @override
+  Widget build(BuildContext context) {
+    final Set<MaterialState> states = <MaterialState>{ if (selected) MaterialState.selected };
+    final TimePickerThemeData timePickerTheme = _TimePickerModel.themeOf(context);
+    final _TimePickerDefaults defaultTheme = _TimePickerModel.defaultThemeOf(context);
+    final Color resolvedBackgroundColor = MaterialStateProperty.resolveAs<Color>(timePickerTheme.dayPeriodColor ?? defaultTheme.dayPeriodColor, states);
+    final Color resolvedTextColor = MaterialStateProperty.resolveAs<Color>(timePickerTheme.dayPeriodTextColor ?? defaultTheme.dayPeriodTextColor, states);
+    final TextStyle? resolvedTextStyle = MaterialStateProperty.resolveAs<TextStyle?>(timePickerTheme.dayPeriodTextStyle ?? defaultTheme.dayPeriodTextStyle, states)?.copyWith(color: resolvedTextColor);
+    final double buttonTextScaleFactor = math.min(MediaQuery.textScaleFactorOf(context), 2);
+
+    return Material(
+      color: resolvedBackgroundColor,
+      child: InkWell(
+        onTap: Feedback.wrapForTap(onPressed, context),
+        child: Semantics(
+          checked: selected,
+          inMutuallyExclusiveGroup: true,
+          button: true,
+          child: Center(
+            child: Text(
+              label,
+              style: resolvedTextStyle,
+              textScaleFactor: buttonTextScaleFactor,
+            ),
+          ),
+        ),
+      ),
+    );
+  }
+}
+
 /// A widget to pad the area around the [_DayPeriodControl]'s inner [Material].
 class _DayPeriodInputPadding extends SingleChildRenderObjectWidget {
   const _DayPeriodInputPadding({
@@ -734,7 +783,7 @@
     if (child != null) {
       return math.max(child!.getMinIntrinsicWidth(height), minSize.width);
     }
-    return 0.0;
+    return 0;
   }
 
   @override
@@ -742,7 +791,7 @@
     if (child != null) {
       return math.max(child!.getMinIntrinsicHeight(width), minSize.height);
     }
-    return 0.0;
+    return 0;
   }
 
   @override
@@ -750,7 +799,7 @@
     if (child != null) {
       return math.max(child!.getMaxIntrinsicWidth(height), minSize.width);
     }
-    return 0.0;
+    return 0;
   }
 
   @override
@@ -758,7 +807,7 @@
     if (child != null) {
       return math.max(child!.getMaxIntrinsicHeight(width), minSize.height);
     }
-    return 0.0;
+    return 0;
   }
 
   Size _computeSize({required BoxConstraints constraints, required ChildLayouter layoutChild}) {
@@ -797,9 +846,9 @@
       return true;
     }
 
-    if (position.dx < 0.0 ||
+    if (position.dx < 0 ||
         position.dx > math.max(child!.size.width, minSize.width) ||
-        position.dy < 0.0 ||
+        position.dy < 0 ||
         position.dy > math.max(child!.size.height, minSize.height)) {
       return false;
     }
@@ -808,21 +857,20 @@
     switch (orientation) {
       case Orientation.portrait:
         if (position.dy > newPosition.dy) {
-          newPosition += const Offset(0.0, 1.0);
+          newPosition += const Offset(0, 1);
         } else {
-          newPosition += const Offset(0.0, -1.0);
+          newPosition += const Offset(0, -1);
         }
         break;
       case Orientation.landscape:
         if (position.dx > newPosition.dx) {
-          newPosition += const Offset(1.0, 0.0);
+          newPosition += const Offset(1, 0);
         } else {
-          newPosition += const Offset(-1.0, 0.0);
+          newPosition += const Offset(-1, 0);
         }
         break;
     }
 
-
     return result.addWithRawTransform(
       transform: MatrixUtils.forceToPoint(newPosition),
       position: newPosition,
@@ -837,6 +885,7 @@
 class _TappableLabel {
   _TappableLabel({
     required this.value,
+    required this.inner,
     required this.painter,
     required this.onTap,
   });
@@ -844,6 +893,10 @@
   /// The value this label is displaying.
   final int value;
 
+  /// This value is part of the "inner" ring of values on the dial, used for 24
+  /// hour input.
+  final bool inner;
+
   /// Paints the text of the label.
   final TextPainter painter;
 
@@ -854,73 +907,88 @@
 class _DialPainter extends CustomPainter {
   _DialPainter({
     required this.primaryLabels,
-    required this.secondaryLabels,
+    required this.selectedLabels,
     required this.backgroundColor,
-    required this.accentColor,
+    required this.handColor,
+    required this.handWidth,
     required this.dotColor,
+    required this.dotRadius,
+    required this.centerRadius,
     required this.theta,
+    required this.radius,
     required this.textDirection,
     required this.selectedValue,
   }) : super(repaint: PaintingBinding.instance.systemFonts);
 
   final List<_TappableLabel> primaryLabels;
-  final List<_TappableLabel> secondaryLabels;
+  final List<_TappableLabel> selectedLabels;
   final Color backgroundColor;
-  final Color accentColor;
+  final Color handColor;
+  final double handWidth;
   final Color dotColor;
+  final double dotRadius;
+  final double centerRadius;
   final double theta;
+  final double radius;
   final TextDirection textDirection;
   final int selectedValue;
 
-  static const double _labelPadding = 28.0;
-
   void dispose() {
     for (final _TappableLabel label in primaryLabels) {
       label.painter.dispose();
     }
-    for (final _TappableLabel label in secondaryLabels) {
+    for (final _TappableLabel label in selectedLabels) {
       label.painter.dispose();
     }
     primaryLabels.clear();
-    secondaryLabels.clear();
+    selectedLabels.clear();
   }
 
   @override
   void paint(Canvas canvas, Size size) {
-    final double radius = size.shortestSide / 2.0;
-    final Offset center = Offset(size.width / 2.0, size.height / 2.0);
+    final double dialRadius = clampDouble(size.shortestSide / 2, _kTimePickerDialMinRadius + dotRadius, double.infinity);
+    final double labelRadius = clampDouble(dialRadius - _kTimePickerDialPadding, _kTimePickerDialMinRadius, double.infinity);
+    final double innerLabelRadius = clampDouble(labelRadius - _kTimePickerInnerDialOffset, 0, double.infinity);
+    final double handleRadius = clampDouble(labelRadius - (radius < 0.5 ? 1 : 0) * (labelRadius - innerLabelRadius), _kTimePickerDialMinRadius, double.infinity);
+    final Offset center = Offset(size.width / 2, size.height / 2);
     final Offset centerPoint = center;
-    canvas.drawCircle(centerPoint, radius, Paint()..color = backgroundColor);
+    canvas.drawCircle(centerPoint, dialRadius, Paint()..color = backgroundColor);
 
-    final double labelRadius = radius - _labelPadding;
-    Offset getOffsetForTheta(double theta) {
-      return center + Offset(labelRadius * math.cos(theta), -labelRadius * math.sin(theta));
+    Offset getOffsetForTheta(double theta, double radius) {
+      return center + Offset(radius * math.cos(theta), -radius * math.sin(theta));
     }
 
-    void paintLabels(List<_TappableLabel>? labels) {
-      if (labels == null) {
+    void paintLabels(List<_TappableLabel> labels, double radius) {
+      if (labels.isEmpty) {
         return;
       }
       final double labelThetaIncrement = -_kTwoPi / labels.length;
-      double labelTheta = math.pi / 2.0;
+      double labelTheta = math.pi / 2;
 
       for (final _TappableLabel label in labels) {
         final TextPainter labelPainter = label.painter;
-        final Offset labelOffset = Offset(-labelPainter.width / 2.0, -labelPainter.height / 2.0);
-        labelPainter.paint(canvas, getOffsetForTheta(labelTheta) + labelOffset);
+        final Offset labelOffset = Offset(-labelPainter.width / 2, -labelPainter.height / 2);
+        labelPainter.paint(canvas, getOffsetForTheta(labelTheta, radius) + labelOffset);
         labelTheta += labelThetaIncrement;
       }
     }
 
-    paintLabels(primaryLabels);
+    void paintInnerOuterLabels(List<_TappableLabel>? labels) {
+      if (labels == null) {
+        return;
+      }
 
-    final Paint selectorPaint = Paint()
-      ..color = accentColor;
-    final Offset focusedPoint = getOffsetForTheta(theta);
-    const double focusedRadius = _labelPadding - 4.0;
-    canvas.drawCircle(centerPoint, 4.0, selectorPaint);
-    canvas.drawCircle(focusedPoint, focusedRadius, selectorPaint);
-    selectorPaint.strokeWidth = 2.0;
+      paintLabels(labels.where((_TappableLabel label) => !label.inner).toList(), labelRadius);
+      paintLabels(labels.where((_TappableLabel label) => label.inner).toList(), innerLabelRadius);
+    }
+
+    paintInnerOuterLabels(primaryLabels);
+
+    final Paint selectorPaint = Paint()..color = handColor;
+    final Offset focusedPoint = getOffsetForTheta(theta, handleRadius);
+    canvas.drawCircle(centerPoint, centerRadius, selectorPaint);
+    canvas.drawCircle(focusedPoint, dotRadius, selectorPaint);
+    selectorPaint.strokeWidth = handWidth;
     canvas.drawLine(centerPoint, focusedPoint, selectorPaint);
 
     // Add a dot inside the selector but only when it isn't over the labels.
@@ -929,43 +997,49 @@
     // labels. The values were derived by manually testing the dial.
     final double labelThetaIncrement = -_kTwoPi / primaryLabels.length;
     if (theta % labelThetaIncrement > 0.1 && theta % labelThetaIncrement < 0.45) {
-      canvas.drawCircle(focusedPoint, 2.0, selectorPaint..color = dotColor);
+      canvas.drawCircle(focusedPoint, 2, selectorPaint..color = dotColor);
     }
 
     final Rect focusedRect = Rect.fromCircle(
-      center: focusedPoint, radius: focusedRadius,
+      center: focusedPoint,
+      radius: dotRadius,
     );
     canvas
       ..save()
       ..clipPath(Path()..addOval(focusedRect));
-    paintLabels(secondaryLabels);
+    paintInnerOuterLabels(selectedLabels);
     canvas.restore();
   }
 
   @override
   bool shouldRepaint(_DialPainter oldPainter) {
     return oldPainter.primaryLabels != primaryLabels
-        || oldPainter.secondaryLabels != secondaryLabels
+        || oldPainter.selectedLabels != selectedLabels
         || oldPainter.backgroundColor != backgroundColor
-        || oldPainter.accentColor != accentColor
+        || oldPainter.handColor != handColor
         || oldPainter.theta != theta;
   }
 }
 
+// Which kind of hour dial being presented.
+enum _HourDialType {
+  twentyFourHour,
+  twentyFourHourDoubleRing,
+  twelveHour,
+}
+
 class _Dial extends StatefulWidget {
   const _Dial({
     required this.selectedTime,
-    required this.mode,
-    required this.use24HourDials,
+    required this.hourMinuteMode,
+    required this.hourDialType,
     required this.onChanged,
     required this.onHourSelected,
-  }) : assert(selectedTime != null),
-       assert(mode != null),
-       assert(use24HourDials != null);
+  });
 
   final TimeOfDay selectedTime;
-  final _TimePickerMode mode;
-  final bool use24HourDials;
+  final _HourMinuteMode hourMinuteMode;
+  final _HourDialType hourDialType;
   final ValueChanged<TimeOfDay>? onChanged;
   final VoidCallback? onHourSelected;
 
@@ -974,103 +1048,174 @@
 }
 
 class _DialState extends State<_Dial> with SingleTickerProviderStateMixin {
+  late ThemeData themeData;
+  late MaterialLocalizations localizations;
+  _DialPainter? painter;
+  late AnimationController _animationController;
+  late Tween<double> _thetaTween;
+  late Animation<double> _theta;
+  late Tween<double> _radiusTween;
+  late Animation<double> _radius;
+  bool _dragging = false;
+
   @override
   void initState() {
     super.initState();
-    _thetaController = AnimationController(
+    _animationController = AnimationController(
       duration: _kDialAnimateDuration,
       vsync: this,
     );
     _thetaTween = Tween<double>(begin: _getThetaForTime(widget.selectedTime));
-    _theta = _thetaController
+    _radiusTween = Tween<double>(begin: _getRadiusForTime(widget.selectedTime));
+    _theta = _animationController
       .drive(CurveTween(curve: standardEasing))
       .drive(_thetaTween)
       ..addListener(() => setState(() { /* _theta.value has changed */ }));
+    _radius = _animationController
+      .drive(CurveTween(curve: standardEasing))
+      .drive(_radiusTween)
+      ..addListener(() => setState(() { /* _radius.value has changed */ }));
   }
 
-  late ThemeData themeData;
-  late MaterialLocalizations localizations;
-  late MediaQueryData media;
-  _DialPainter? painter;
-
   @override
   void didChangeDependencies() {
     super.didChangeDependencies();
     assert(debugCheckHasMediaQuery(context));
     themeData = Theme.of(context);
     localizations = MaterialLocalizations.of(context);
-    media = MediaQuery.of(context);
   }
 
   @override
   void didUpdateWidget(_Dial oldWidget) {
     super.didUpdateWidget(oldWidget);
-    if (widget.mode != oldWidget.mode || widget.selectedTime != oldWidget.selectedTime) {
+    if (widget.hourMinuteMode != oldWidget.hourMinuteMode || widget.selectedTime != oldWidget.selectedTime) {
       if (!_dragging) {
-        _animateTo(_getThetaForTime(widget.selectedTime));
+        _animateTo(_getThetaForTime(widget.selectedTime), _getRadiusForTime(widget.selectedTime));
       }
     }
   }
 
   @override
   void dispose() {
-    _thetaController.dispose();
+    _animationController.dispose();
     painter?.dispose();
     super.dispose();
   }
 
-  late Tween<double> _thetaTween;
-  late Animation<double> _theta;
-  late AnimationController _thetaController;
-  bool _dragging = false;
-
   static double _nearest(double target, double a, double b) {
     return ((target - a).abs() < (target - b).abs()) ? a : b;
   }
 
-  void _animateTo(double targetTheta) {
-    final double currentTheta = _theta.value;
-    double beginTheta = _nearest(targetTheta, currentTheta, currentTheta + _kTwoPi);
-    beginTheta = _nearest(targetTheta, beginTheta, currentTheta - _kTwoPi);
-    _thetaTween
-      ..begin = beginTheta
-      ..end = targetTheta;
-    _thetaController
-      ..value = 0.0
-      ..forward();
+  void _animateTo(double targetTheta, double targetRadius) {
+    void animateToValue({
+      required double target,
+      required Animation<double> animation,
+      required Tween<double> tween,
+      required AnimationController controller,
+      required double min,
+      required double max,
+    }) {
+      double beginValue = _nearest(target, animation.value, max);
+      beginValue = _nearest(target, beginValue, min);
+      tween
+        ..begin = beginValue
+        ..end = target;
+      controller
+        ..value = 0
+        ..forward();
+    }
+
+    animateToValue(
+      target: targetTheta,
+      animation: _theta,
+      tween: _thetaTween,
+      controller: _animationController,
+      min: _theta.value - _kTwoPi,
+      max: _theta.value + _kTwoPi,
+    );
+    animateToValue(
+      target: targetRadius,
+      animation: _radius,
+      tween: _radiusTween,
+      controller: _animationController,
+      min: 0,
+      max: 1,
+    );
+  }
+
+  double _getRadiusForTime(TimeOfDay time) {
+    switch (widget.hourMinuteMode) {
+      case _HourMinuteMode.hour:
+        switch (widget.hourDialType) {
+          case _HourDialType.twentyFourHourDoubleRing:
+            return time.hour >= 12 ? 0 : 1;
+          case _HourDialType.twentyFourHour:
+          case _HourDialType.twelveHour:
+            return 1;
+        }
+      case _HourMinuteMode.minute:
+        return 1;
+    }
   }
 
   double _getThetaForTime(TimeOfDay time) {
-    final int hoursFactor = widget.use24HourDials ? TimeOfDay.hoursPerDay : TimeOfDay.hoursPerPeriod;
-    final double fraction = widget.mode == _TimePickerMode.hour
-      ? (time.hour / hoursFactor) % hoursFactor
-      : (time.minute / TimeOfDay.minutesPerHour) % TimeOfDay.minutesPerHour;
-    return (math.pi / 2.0 - fraction * _kTwoPi) % _kTwoPi;
+    final int hoursFactor;
+    switch (widget.hourDialType) {
+      case _HourDialType.twentyFourHour:
+        hoursFactor = TimeOfDay.hoursPerDay;
+        break;
+      case _HourDialType.twentyFourHourDoubleRing:
+        hoursFactor = TimeOfDay.hoursPerPeriod;
+        break;
+      case _HourDialType.twelveHour:
+        hoursFactor = TimeOfDay.hoursPerPeriod;
+        break;
+    }
+    final double fraction;
+    switch (widget.hourMinuteMode) {
+      case _HourMinuteMode.hour:
+        fraction = (time.hour / hoursFactor) % hoursFactor;
+        break;
+      case _HourMinuteMode.minute:
+        fraction = (time.minute / TimeOfDay.minutesPerHour) % TimeOfDay.minutesPerHour;
+        break;
+    }
+    return (math.pi / 2 - fraction * _kTwoPi) % _kTwoPi;
   }
 
-  TimeOfDay _getTimeForTheta(double theta, {bool roundMinutes = false}) {
-    final double fraction = (0.25 - (theta % _kTwoPi) / _kTwoPi) % 1.0;
-    if (widget.mode == _TimePickerMode.hour) {
-      int newHour;
-      if (widget.use24HourDials) {
-        newHour = (fraction * TimeOfDay.hoursPerDay).round() % TimeOfDay.hoursPerDay;
-      } else {
-        newHour = (fraction * TimeOfDay.hoursPerPeriod).round() % TimeOfDay.hoursPerPeriod;
-        newHour = newHour + widget.selectedTime.periodOffset;
-      }
-      return widget.selectedTime.replacing(hour: newHour);
-    } else {
-      int minute = (fraction * TimeOfDay.minutesPerHour).round() % TimeOfDay.minutesPerHour;
-      if (roundMinutes) {
-        // Round the minutes to nearest 5 minute interval.
-        minute = ((minute + 2) ~/ 5) * 5 % TimeOfDay.minutesPerHour;
-      }
-      return widget.selectedTime.replacing(minute: minute);
+  TimeOfDay _getTimeForTheta(double theta, {bool roundMinutes = false, required double radius}) {
+    final double fraction = (0.25 - (theta % _kTwoPi) / _kTwoPi) % 1;
+    switch (widget.hourMinuteMode) {
+      case _HourMinuteMode.hour:
+        int newHour;
+        switch (widget.hourDialType) {
+          case _HourDialType.twentyFourHour:
+            newHour = (fraction * TimeOfDay.hoursPerDay).round() % TimeOfDay.hoursPerDay;
+            break;
+          case _HourDialType.twentyFourHourDoubleRing:
+            newHour = (fraction * TimeOfDay.hoursPerPeriod).round() % TimeOfDay.hoursPerPeriod;
+            if (radius < 0.5) {
+              newHour = newHour + TimeOfDay.hoursPerPeriod;
+            }
+            break;
+          case _HourDialType.twelveHour:
+            newHour = (fraction * TimeOfDay.hoursPerPeriod).round() % TimeOfDay.hoursPerPeriod;
+            newHour = newHour + widget.selectedTime.periodOffset;
+            break;
+        }
+        return widget.selectedTime.replacing(hour: newHour);
+      case _HourMinuteMode.minute:
+        int minute = (fraction * TimeOfDay.minutesPerHour).round() % TimeOfDay.minutesPerHour;
+        if (roundMinutes) {
+          // Round the minutes to nearest 5 minute interval.
+          minute = ((minute + 2) ~/ 5) * 5 % TimeOfDay.minutesPerHour;
+        }
+        return widget.selectedTime.replacing(minute: minute);
     }
   }
 
   TimeOfDay _notifyOnChangedIfNeeded({ bool roundMinutes = false }) {
-    final TimeOfDay current = _getTimeForTheta(_theta.value, roundMinutes: roundMinutes);
+    final TimeOfDay current = _getTimeForTheta(_theta.value, roundMinutes: roundMinutes, radius: _radius.value);
     if (widget.onChanged == null) {
       return current;
     }
@@ -1083,25 +1228,34 @@
   void _updateThetaForPan({ bool roundMinutes = false }) {
     setState(() {
       final Offset offset = _position! - _center!;
-      double angle = (math.atan2(offset.dx, offset.dy) - math.pi / 2.0) % _kTwoPi;
+      final double labelRadius = _dialSize!.shortestSide / 2 - _kTimePickerDialPadding;
+      final double innerRadius = labelRadius - _kTimePickerInnerDialOffset;
+      double angle = (math.atan2(offset.dx, offset.dy) - math.pi / 2) % _kTwoPi;
+      final double radius = clampDouble((offset.distance - innerRadius) / _kTimePickerInnerDialOffset, 0, 1);
       if (roundMinutes) {
-        angle = _getThetaForTime(_getTimeForTheta(angle, roundMinutes: roundMinutes));
+        angle = _getThetaForTime(_getTimeForTheta(angle, roundMinutes: roundMinutes, radius: radius));
       }
+      // The controller doesn't animate during the pan gesture.
       _thetaTween
         ..begin = angle
-        ..end = angle; // The controller doesn't animate during the pan gesture.
+        ..end = angle;
+      _radiusTween
+        ..begin = radius
+        ..end = radius;
     });
   }
 
   Offset? _position;
   Offset? _center;
+  Size? _dialSize;
 
   void _handlePanStart(DragStartDetails details) {
     assert(!_dragging);
     _dragging = true;
     final RenderBox box = context.findRenderObject()! as RenderBox;
     _position = box.globalToLocal(details.globalPosition);
-    _center = box.size.center(Offset.zero);
+    _dialSize = box.size;
+    _center = _dialSize!.center(Offset.zero);
     _updateThetaForPan();
     _notifyOnChangedIfNeeded();
   }
@@ -1117,8 +1271,9 @@
     _dragging = false;
     _position = null;
     _center = null;
-    _animateTo(_getThetaForTime(widget.selectedTime));
-    if (widget.mode == _TimePickerMode.hour) {
+    _dialSize = null;
+    _animateTo(_getThetaForTime(widget.selectedTime), _getRadiusForTime(widget.selectedTime));
+    if (widget.hourMinuteMode == _HourMinuteMode.hour) {
       widget.onHourSelected?.call();
     }
   }
@@ -1127,36 +1282,60 @@
     final RenderBox box = context.findRenderObject()! as RenderBox;
     _position = box.globalToLocal(details.globalPosition);
     _center = box.size.center(Offset.zero);
+    _dialSize = box.size;
     _updateThetaForPan(roundMinutes: true);
     final TimeOfDay newTime = _notifyOnChangedIfNeeded(roundMinutes: true);
-    if (widget.mode == _TimePickerMode.hour) {
-      if (widget.use24HourDials) {
-        _announceToAccessibility(context, localizations.formatDecimal(newTime.hour));
-      } else {
-        _announceToAccessibility(context, localizations.formatDecimal(newTime.hourOfPeriod));
+    if (widget.hourMinuteMode == _HourMinuteMode.hour) {
+      switch (widget.hourDialType) {
+        case _HourDialType.twentyFourHour:
+        case _HourDialType.twentyFourHourDoubleRing:
+          _announceToAccessibility(context, localizations.formatDecimal(newTime.hour));
+          break;
+        case _HourDialType.twelveHour:
+          _announceToAccessibility(context, localizations.formatDecimal(newTime.hourOfPeriod));
+          break;
       }
       widget.onHourSelected?.call();
     } else {
       _announceToAccessibility(context, localizations.formatDecimal(newTime.minute));
     }
-    _animateTo(_getThetaForTime(_getTimeForTheta(_theta.value, roundMinutes: true)));
+    final TimeOfDay time = _getTimeForTheta(_theta.value, roundMinutes: true, radius: _radius.value);
+    _animateTo(_getThetaForTime(time), _getRadiusForTime(time));
     _dragging = false;
     _position = null;
     _center = null;
+    _dialSize = null;
   }
 
   void _selectHour(int hour) {
     _announceToAccessibility(context, localizations.formatDecimal(hour));
     final TimeOfDay time;
-    if (widget.mode == _TimePickerMode.hour && widget.use24HourDials) {
-      time = TimeOfDay(hour: hour, minute: widget.selectedTime.minute);
-    } else {
-      if (widget.selectedTime.period == DayPeriod.am) {
-        time = TimeOfDay(hour: hour, minute: widget.selectedTime.minute);
-      } else {
-        time = TimeOfDay(hour: hour + TimeOfDay.hoursPerPeriod, minute: widget.selectedTime.minute);
+
+    TimeOfDay getAmPmTime() {
+      switch (widget.selectedTime.period) {
+        case DayPeriod.am:
+          return TimeOfDay(hour: hour, minute: widget.selectedTime.minute);
+        case DayPeriod.pm:
+          return TimeOfDay(hour: hour + TimeOfDay.hoursPerPeriod, minute: widget.selectedTime.minute);
       }
     }
+
+    switch (widget.hourMinuteMode) {
+      case _HourMinuteMode.hour:
+        switch (widget.hourDialType) {
+          case _HourDialType.twentyFourHour:
+          case _HourDialType.twentyFourHourDoubleRing:
+            time = TimeOfDay(hour: hour, minute: widget.selectedTime.minute);
+            break;
+          case _HourDialType.twelveHour:
+            time = getAmPmTime();
+            break;
+        }
+        break;
+      case _HourMinuteMode.minute:
+        time = getAmPmTime();
+        break;
+    }
     final double angle = _getThetaForTime(time);
     _thetaTween
       ..begin = angle
@@ -1192,7 +1371,8 @@
     TimeOfDay(hour: 11, minute: 0),
   ];
 
-  static const List<TimeOfDay> _twentyFourHours = <TimeOfDay>[
+  // On M2, there's no inner ring of numbers.
+  static const List<TimeOfDay> _twentyFourHoursM2 = <TimeOfDay>[
     TimeOfDay(hour: 0, minute: 0),
     TimeOfDay(hour: 2, minute: 0),
     TimeOfDay(hour: 4, minute: 0),
@@ -1207,13 +1387,47 @@
     TimeOfDay(hour: 22, minute: 0),
   ];
 
-  _TappableLabel _buildTappableLabel(TextTheme textTheme, Color color, int value, String label, VoidCallback onTap) {
-    final TextStyle style = textTheme.bodyLarge!.copyWith(color: color);
-    final double labelScaleFactor = math.min(MediaQuery.of(context).textScaleFactor, 2.0);
+  static const List<TimeOfDay> _twentyFourHours = <TimeOfDay>[
+    TimeOfDay(hour: 0, minute: 0),
+    TimeOfDay(hour: 1, minute: 0),
+    TimeOfDay(hour: 2, minute: 0),
+    TimeOfDay(hour: 3, minute: 0),
+    TimeOfDay(hour: 4, minute: 0),
+    TimeOfDay(hour: 5, minute: 0),
+    TimeOfDay(hour: 6, minute: 0),
+    TimeOfDay(hour: 7, minute: 0),
+    TimeOfDay(hour: 8, minute: 0),
+    TimeOfDay(hour: 9, minute: 0),
+    TimeOfDay(hour: 10, minute: 0),
+    TimeOfDay(hour: 11, minute: 0),
+    TimeOfDay(hour: 12, minute: 0),
+    TimeOfDay(hour: 13, minute: 0),
+    TimeOfDay(hour: 14, minute: 0),
+    TimeOfDay(hour: 15, minute: 0),
+    TimeOfDay(hour: 16, minute: 0),
+    TimeOfDay(hour: 17, minute: 0),
+    TimeOfDay(hour: 18, minute: 0),
+    TimeOfDay(hour: 19, minute: 0),
+    TimeOfDay(hour: 20, minute: 0),
+    TimeOfDay(hour: 21, minute: 0),
+    TimeOfDay(hour: 22, minute: 0),
+    TimeOfDay(hour: 23, minute: 0),
+  ];
+
+  _TappableLabel _buildTappableLabel({
+    required TextStyle? textStyle,
+    required int selectedValue,
+    required int value,
+    required bool inner,
+    required String label,
+    required VoidCallback onTap,
+  }) {
+    final double labelScaleFactor = math.min(MediaQuery.textScaleFactorOf(context), 2);
     return _TappableLabel(
       value: value,
+      inner: inner,
       painter: TextPainter(
-        text: TextSpan(style: style, text: label),
+        text: TextSpan(style: textStyle, text: label),
         textDirection: TextDirection.ltr,
         textScaleFactor: labelScaleFactor,
       )..layout(),
@@ -1221,33 +1435,63 @@
     );
   }
 
-  List<_TappableLabel> _build24HourRing(TextTheme textTheme, Color color) => <_TappableLabel>[
-    for (final TimeOfDay timeOfDay in _twentyFourHours)
-      _buildTappableLabel(
-        textTheme,
-        color,
-        timeOfDay.hour,
-        localizations.formatHour(timeOfDay, alwaysUse24HourFormat: media.alwaysUse24HourFormat),
-        () {
-          _selectHour(timeOfDay.hour);
-        },
-      ),
-  ];
+  List<_TappableLabel> _build24HourRing({
+    required TextStyle? textStyle,
+    required int selectedValue,
+  }) {
+    return <_TappableLabel>[
+      if (themeData.useMaterial3)
+        for (final TimeOfDay timeOfDay in _twentyFourHours)
+          _buildTappableLabel(
+            textStyle: textStyle,
+            selectedValue: selectedValue,
+            inner: timeOfDay.hour >= 12,
+            value: timeOfDay.hour,
+            label: timeOfDay.hour != 0
+                ? '${timeOfDay.hour}'
+                : localizations.formatHour(timeOfDay, alwaysUse24HourFormat: true),
+            onTap: () {
+              _selectHour(timeOfDay.hour);
+            },
+          ),
+      if (!themeData.useMaterial3)
+        for (final TimeOfDay timeOfDay in _twentyFourHoursM2)
+          _buildTappableLabel(
+            textStyle: textStyle,
+            selectedValue: selectedValue,
+            inner: false,
+            value: timeOfDay.hour,
+            label: localizations.formatHour(timeOfDay, alwaysUse24HourFormat: true),
+            onTap: () {
+              _selectHour(timeOfDay.hour);
+            },
+          ),
+    ];
+  }
 
-  List<_TappableLabel> _build12HourRing(TextTheme textTheme, Color color) => <_TappableLabel>[
-    for (final TimeOfDay timeOfDay in _amHours)
-      _buildTappableLabel(
-        textTheme,
-        color,
-        timeOfDay.hour,
-        localizations.formatHour(timeOfDay, alwaysUse24HourFormat: media.alwaysUse24HourFormat),
-        () {
-          _selectHour(timeOfDay.hour);
-        },
-      ),
-  ];
+  List<_TappableLabel> _build12HourRing({
+    required TextStyle? textStyle,
+    required int selectedValue,
+  }) {
+    return <_TappableLabel>[
+      for (final TimeOfDay timeOfDay in _amHours)
+        _buildTappableLabel(
+          textStyle: textStyle,
+          selectedValue: selectedValue,
+          inner: false,
+          value: timeOfDay.hour,
+          label: localizations.formatHour(timeOfDay, alwaysUse24HourFormat: MediaQuery.alwaysUse24HourFormatOf(context)),
+          onTap: () {
+            _selectHour(timeOfDay.hour);
+          },
+        ),
+    ];
+  }
 
-  List<_TappableLabel> _buildMinutes(TextTheme textTheme, Color color) {
+  List<_TappableLabel> _buildMinutes({
+    required TextStyle? textStyle,
+    required int selectedValue,
+  }) {
     const List<TimeOfDay> minuteMarkerValues = <TimeOfDay>[
       TimeOfDay(hour: 0, minute: 0),
       TimeOfDay(hour: 0, minute: 5),
@@ -1266,11 +1510,12 @@
     return <_TappableLabel>[
       for (final TimeOfDay timeOfDay in minuteMarkerValues)
         _buildTappableLabel(
-          textTheme,
-          color,
-          timeOfDay.minute,
-          localizations.formatMinute(timeOfDay),
-          () {
+          textStyle: textStyle,
+          selectedValue: selectedValue,
+          inner: false,
+          value: timeOfDay.minute,
+          label: localizations.formatMinute(timeOfDay),
+          onTap: () {
             _selectMinute(timeOfDay.minute);
           },
         ),
@@ -1280,42 +1525,79 @@
   @override
   Widget build(BuildContext context) {
     final ThemeData theme = Theme.of(context);
-    final TimePickerThemeData pickerTheme = TimePickerTheme.of(context);
-    final Color backgroundColor = pickerTheme.dialBackgroundColor ?? themeData.colorScheme.onBackground.withOpacity(0.12);
-    final Color accentColor = pickerTheme.dialHandColor ?? themeData.colorScheme.primary;
-    final Color primaryLabelColor = MaterialStateProperty.resolveAs(pickerTheme.dialTextColor, <MaterialState>{}) ?? themeData.colorScheme.onSurface;
-    final Color secondaryLabelColor = MaterialStateProperty.resolveAs(pickerTheme.dialTextColor, <MaterialState>{MaterialState.selected}) ?? themeData.colorScheme.onPrimary;
+    final TimePickerThemeData timePickerTheme = TimePickerTheme.of(context);
+    final _TimePickerDefaults defaultTheme = theme.useMaterial3 ? _TimePickerDefaultsM3(context) : _TimePickerDefaultsM2(context);
+    final Color backgroundColor = timePickerTheme.dialBackgroundColor ?? defaultTheme.dialBackgroundColor;
+    final Color dialHandColor = timePickerTheme.dialHandColor ?? defaultTheme.dialHandColor;
+    final TextStyle labelStyle = timePickerTheme.dialTextStyle ?? defaultTheme.dialTextStyle;
+    final Color dialTextUnselectedColor = MaterialStateProperty
+      .resolveAs<Color>(timePickerTheme.dialTextColor ?? defaultTheme.dialTextColor, <MaterialState>{ });
+    final Color dialTextSelectedColor = MaterialStateProperty
+      .resolveAs<Color>(timePickerTheme.dialTextColor ?? defaultTheme.dialTextColor, <MaterialState>{  MaterialState.selected });
+    final TextStyle resolvedUnselectedLabelStyle = labelStyle.copyWith(color: dialTextUnselectedColor);
+    final TextStyle resolvedSelectedLabelStyle = labelStyle.copyWith(color: dialTextSelectedColor);
+    final Color dotColor = dialTextSelectedColor;
+
     List<_TappableLabel> primaryLabels;
-    List<_TappableLabel> secondaryLabels;
+    List<_TappableLabel> selectedLabels;
     final int selectedDialValue;
-    switch (widget.mode) {
-      case _TimePickerMode.hour:
-        if (widget.use24HourDials) {
-          selectedDialValue = widget.selectedTime.hour;
-          primaryLabels = _build24HourRing(theme.textTheme, primaryLabelColor);
-          secondaryLabels = _build24HourRing(theme.textTheme, secondaryLabelColor);
-        } else {
-          selectedDialValue = widget.selectedTime.hourOfPeriod;
-          primaryLabels = _build12HourRing(theme.textTheme, primaryLabelColor);
-          secondaryLabels = _build12HourRing(theme.textTheme, secondaryLabelColor);
+    final double radiusValue;
+    switch (widget.hourMinuteMode) {
+      case _HourMinuteMode.hour:
+        switch (widget.hourDialType) {
+          case _HourDialType.twentyFourHour:
+          case _HourDialType.twentyFourHourDoubleRing:
+            selectedDialValue = widget.selectedTime.hour;
+            primaryLabels = _build24HourRing(
+              textStyle: resolvedUnselectedLabelStyle,
+              selectedValue: selectedDialValue,
+            );
+            selectedLabels = _build24HourRing(
+              textStyle: resolvedSelectedLabelStyle,
+              selectedValue: selectedDialValue,
+            );
+            radiusValue = theme.useMaterial3 ? _radius.value : 1;
+            break;
+          case _HourDialType.twelveHour:
+            selectedDialValue = widget.selectedTime.hourOfPeriod;
+            primaryLabels = _build12HourRing(
+              textStyle: resolvedUnselectedLabelStyle,
+              selectedValue: selectedDialValue,
+            );
+            selectedLabels = _build12HourRing(
+              textStyle: resolvedSelectedLabelStyle,
+              selectedValue: selectedDialValue,
+            );
+            radiusValue = 1;
+            break;
         }
         break;
-      case _TimePickerMode.minute:
+      case _HourMinuteMode.minute:
         selectedDialValue = widget.selectedTime.minute;
-        primaryLabels = _buildMinutes(theme.textTheme, primaryLabelColor);
-        secondaryLabels = _buildMinutes(theme.textTheme, secondaryLabelColor);
+        primaryLabels = _buildMinutes(
+          textStyle: resolvedUnselectedLabelStyle,
+          selectedValue: selectedDialValue,
+        );
+        selectedLabels = _buildMinutes(
+          textStyle: resolvedSelectedLabelStyle,
+          selectedValue: selectedDialValue,
+        );
+        radiusValue = 1;
         break;
     }
-
     painter?.dispose();
     painter = _DialPainter(
       selectedValue: selectedDialValue,
       primaryLabels: primaryLabels,
-      secondaryLabels: secondaryLabels,
+      selectedLabels: selectedLabels,
       backgroundColor: backgroundColor,
-      accentColor: accentColor,
-      dotColor: theme.colorScheme.surface,
+      handColor: dialHandColor,
+      handWidth: defaultTheme.handWidth,
+      dotColor: dotColor,
+      dotRadius: defaultTheme.dotRadius,
+      centerRadius: defaultTheme.centerRadius,
       theta: _theta.value,
+      radius: radiusValue,
       textDirection: Directionality.of(context),
     );
 
@@ -1336,23 +1618,18 @@
 class _TimePickerInput extends StatefulWidget {
   const _TimePickerInput({
     required this.initialSelectedTime,
-    required this.helpText,
     required this.errorInvalidText,
     required this.hourLabelText,
     required this.minuteLabelText,
+    required this.helpText,
     required this.autofocusHour,
     required this.autofocusMinute,
-    required this.onChanged,
     this.restorationId,
-  }) : assert(initialSelectedTime != null),
-       assert(onChanged != null);
+  });
 
   /// The time initially selected when the dialog is shown.
   final TimeOfDay initialSelectedTime;
 
-  /// Optionally provide your own help text to the time picker.
-  final String? helpText;
-
   /// Optionally provide your own validation error text.
   final String? errorInvalidText;
 
@@ -1362,12 +1639,12 @@
   /// Optionally provide your own minute label text.
   final String? minuteLabelText;
 
+  final String helpText;
+
   final bool? autofocusHour;
 
   final bool? autofocusMinute;
 
-  final ValueChanged<TimeOfDay> onChanged;
-
   /// Restoration ID to save and restore the state of the time picker input
   /// widget.
   ///
@@ -1406,14 +1683,14 @@
       return null;
     }
 
-    if (MediaQuery.of(context).alwaysUse24HourFormat) {
+    if (MediaQuery.alwaysUse24HourFormatOf(context)) {
       if (newHour >= 0 && newHour < 24) {
         return newHour;
       }
     } else {
       if (newHour > 0 && newHour < 13) {
-        if ((_selectedTime.value.period == DayPeriod.pm && newHour != 12)
-            || (_selectedTime.value.period == DayPeriod.am && newHour == 12)) {
+        if ((_selectedTime.value.period == DayPeriod.pm && newHour != 12) ||
+            (_selectedTime.value.period == DayPeriod.am && newHour == 12)) {
           newHour = (newHour + TimeOfDay.hoursPerPeriod) % TimeOfDay.hoursPerDay;
         }
         return newHour;
@@ -1442,7 +1719,7 @@
     final int? newHour = _parseHour(value);
     if (newHour != null) {
       _selectedTime.value = TimeOfDay(hour: newHour, minute: _selectedTime.value.minute);
-      widget.onChanged(_selectedTime.value);
+      _TimePickerModel.setSelectedTime(context, _selectedTime.value);
       FocusScope.of(context).requestFocus();
     }
   }
@@ -1459,14 +1736,14 @@
     final int? newMinute = _parseMinute(value);
     if (newMinute != null) {
       _selectedTime.value = TimeOfDay(hour: _selectedTime.value.hour, minute: int.parse(value!));
-      widget.onChanged(_selectedTime.value);
+      _TimePickerModel.setSelectedTime(context, _selectedTime.value);
       FocusScope.of(context).unfocus();
     }
   }
 
   void _handleDayPeriodChanged(TimeOfDay value) {
     _selectedTime.value = value;
-    widget.onChanged(_selectedTime.value);
+    _TimePickerModel.setSelectedTime(context, _selectedTime.value);
   }
 
   String? _validateHour(String? value) {
@@ -1494,36 +1771,32 @@
   @override
   Widget build(BuildContext context) {
     assert(debugCheckHasMediaQuery(context));
-    final MediaQueryData media = MediaQuery.of(context);
-    final TimeOfDayFormat timeOfDayFormat = MaterialLocalizations.of(context).timeOfDayFormat(alwaysUse24HourFormat: media.alwaysUse24HourFormat);
+    final TimeOfDayFormat timeOfDayFormat = MaterialLocalizations.of(context).timeOfDayFormat(alwaysUse24HourFormat: _TimePickerModel.use24HourFormatOf(context));
     final bool use24HourDials = hourFormat(of: timeOfDayFormat) != HourFormat.h;
     final ThemeData theme = Theme.of(context);
-    final TextStyle hourMinuteStyle = TimePickerTheme.of(context).hourMinuteTextStyle ?? theme.textTheme.displayMedium!;
-    final MaterialLocalizations localizations = MaterialLocalizations.of(context);
-    final String timePickerInputHelpText = theme.useMaterial3
-      ? localizations.timePickerInputHelpText
-      : localizations.timePickerInputHelpText.toUpperCase();
+    final TimePickerThemeData timePickerTheme = _TimePickerModel.themeOf(context);
+    final _TimePickerDefaults defaultTheme = _TimePickerModel.defaultThemeOf(context);
+    final TextStyle hourMinuteStyle = timePickerTheme.hourMinuteTextStyle ?? defaultTheme.hourMinuteTextStyle;
 
     return Padding(
-      padding: const EdgeInsets.symmetric(horizontal: 24.0, vertical: 16.0),
+      padding: _TimePickerModel.useMaterial3Of(context) ? EdgeInsets.zero : const EdgeInsets.symmetric(horizontal: 16),
       child: Column(
         crossAxisAlignment: CrossAxisAlignment.start,
         children: <Widget>[
-          Text(
-            widget.helpText ?? timePickerInputHelpText,
-            style: TimePickerTheme.of(context).helpTextStyle ?? theme.textTheme.labelSmall,
+          Padding(padding: EdgeInsetsDirectional.only(bottom: _TimePickerModel.useMaterial3Of(context) ? 20 : 24),
+            child: Text(
+              widget.helpText,
+              style: _TimePickerModel.themeOf(context).helpTextStyle ?? _TimePickerModel.defaultThemeOf(context).helpTextStyle,
+            ),
           ),
-          const SizedBox(height: 16.0),
           Row(
             crossAxisAlignment: CrossAxisAlignment.start,
             children: <Widget>[
               if (!use24HourDials && timeOfDayFormat == TimeOfDayFormat.a_space_h_colon_mm) ...<Widget>[
-                _DayPeriodControl(
-                  selectedTime: _selectedTime.value,
-                  orientation: Orientation.portrait,
-                  onChanged: _handleDayPeriodChanged,
+                Padding(
+                  padding: const EdgeInsetsDirectional.only(end: 12),
+                  child: _DayPeriodControl(onPeriodChanged: _handleDayPeriodChanged),
                 ),
-                const SizedBox(width: 12.0),
               ],
               Expanded(
                 child: Row(
@@ -1535,19 +1808,20 @@
                       child: Column(
                         crossAxisAlignment: CrossAxisAlignment.start,
                         children: <Widget>[
-                          const SizedBox(height: 8.0),
-                          _HourTextField(
-                            restorationId: 'hour_text_field',
-                            selectedTime: _selectedTime.value,
-                            style: hourMinuteStyle,
-                            autofocus: widget.autofocusHour,
-                            inputAction: TextInputAction.next,
-                            validator: _validateHour,
-                            onSavedSubmitted: _handleHourSavedSubmitted,
-                            onChanged: _handleHourChanged,
-                            hourLabelText: widget.hourLabelText,
+                          Padding(
+                            padding: const EdgeInsets.only(bottom: 10),
+                            child: _HourTextField(
+                              restorationId: 'hour_text_field',
+                              selectedTime: _selectedTime.value,
+                              style: hourMinuteStyle,
+                              autofocus: widget.autofocusHour,
+                              inputAction: TextInputAction.next,
+                              validator: _validateHour,
+                              onSavedSubmitted: _handleHourSavedSubmitted,
+                              onChanged: _handleHourChanged,
+                              hourLabelText: widget.hourLabelText,
+                            ),
                           ),
-                          const SizedBox(height: 8.0),
                           if (!hourHasError.value && !minuteHasError.value)
                             ExcludeSemantics(
                               child: Text(
@@ -1560,27 +1834,24 @@
                         ],
                       ),
                     ),
-                    Container(
-                      margin: const EdgeInsets.only(top: 8.0),
-                      height: _kTimePickerHeaderControlHeight,
-                      child: _StringFragment(timeOfDayFormat: timeOfDayFormat),
-                    ),
+                    _StringFragment(timeOfDayFormat: timeOfDayFormat),
                     Expanded(
                       child: Column(
                         crossAxisAlignment: CrossAxisAlignment.start,
                         children: <Widget>[
-                          const SizedBox(height: 8.0),
-                          _MinuteTextField(
-                            restorationId: 'minute_text_field',
-                            selectedTime: _selectedTime.value,
-                            style: hourMinuteStyle,
-                            autofocus: widget.autofocusMinute,
-                            inputAction: TextInputAction.done,
-                            validator: _validateMinute,
-                            onSavedSubmitted: _handleMinuteSavedSubmitted,
-                            minuteLabelText: widget.minuteLabelText,
+                          Padding(
+                            padding: const EdgeInsets.only(bottom: 10),
+                            child: _MinuteTextField(
+                              restorationId: 'minute_text_field',
+                              selectedTime: _selectedTime.value,
+                              style: hourMinuteStyle,
+                              autofocus: widget.autofocusMinute,
+                              inputAction: TextInputAction.done,
+                              validator: _validateMinute,
+                              onSavedSubmitted: _handleMinuteSavedSubmitted,
+                              minuteLabelText: widget.minuteLabelText,
+                            ),
                           ),
-                          const SizedBox(height: 8.0),
                           if (!hourHasError.value && !minuteHasError.value)
                             ExcludeSemantics(
                               child: Text(
@@ -1597,11 +1868,9 @@
                 ),
               ),
               if (!use24HourDials && timeOfDayFormat != TimeOfDayFormat.a_space_h_colon_mm) ...<Widget>[
-                const SizedBox(width: 12.0),
-                _DayPeriodControl(
-                  selectedTime: _selectedTime.value,
-                  orientation: Orientation.portrait,
-                  onChanged: _handleDayPeriodChanged,
+                Padding(
+                  padding: const EdgeInsetsDirectional.only(start: 12),
+                  child: _DayPeriodControl(onPeriodChanged: _handleDayPeriodChanged),
                 ),
               ],
             ],
@@ -1612,7 +1881,7 @@
               style: theme.textTheme.bodyMedium!.copyWith(color: theme.colorScheme.error),
             )
           else
-            const SizedBox(height: 2.0),
+            const SizedBox(height: 2),
         ],
       ),
     );
@@ -1651,7 +1920,7 @@
       autofocus: autofocus,
       inputAction: inputAction,
       style: style,
-      semanticHintText: hourLabelText ??  MaterialLocalizations.of(context).timePickerHourLabel,
+      semanticHintText: hourLabelText ?? MaterialLocalizations.of(context).timePickerHourLabel,
       validator: validator,
       onSavedSubmitted: onSavedSubmitted,
       onChanged: onChanged,
@@ -1733,9 +2002,12 @@
   @override
   void initState() {
     super.initState();
-    focusNode = FocusNode()..addListener(() {
-      setState(() { }); // Rebuild.
-    });
+    focusNode = FocusNode()
+      ..addListener(() {
+        setState(() {
+          // Rebuild when focus changes.
+        });
+      });
   }
 
   @override
@@ -1759,86 +2031,97 @@
   }
 
   String get _formattedValue {
-    final bool alwaysUse24HourFormat = MediaQuery.of(context).alwaysUse24HourFormat;
+    final bool alwaysUse24HourFormat = MediaQuery.alwaysUse24HourFormatOf(context);
     final MaterialLocalizations localizations = MaterialLocalizations.of(context);
-    return !widget.isHour ? localizations.formatMinute(widget.selectedTime) : localizations.formatHour(
-      widget.selectedTime,
-      alwaysUse24HourFormat: alwaysUse24HourFormat,
-    );
+    return !widget.isHour
+        ? localizations.formatMinute(widget.selectedTime)
+        : localizations.formatHour(
+            widget.selectedTime,
+            alwaysUse24HourFormat: alwaysUse24HourFormat,
+          );
   }
 
   @override
   Widget build(BuildContext context) {
     final ThemeData theme = Theme.of(context);
     final TimePickerThemeData timePickerTheme = TimePickerTheme.of(context);
-    final ColorScheme colorScheme = theme.colorScheme;
+    final _TimePickerDefaults defaultTheme = theme.useMaterial3 ? _TimePickerDefaultsM3(context) : _TimePickerDefaultsM2(context);
+    final bool alwaysUse24HourFormat = MediaQuery.alwaysUse24HourFormatOf(context);
 
-    final InputDecorationTheme? inputDecorationTheme = timePickerTheme.inputDecorationTheme;
-    InputDecoration inputDecoration;
-    if (inputDecorationTheme != null) {
-      inputDecoration = const InputDecoration().applyDefaults(inputDecorationTheme);
-    } else {
-      inputDecoration = InputDecoration(
-        contentPadding: EdgeInsets.zero,
-        filled: true,
-        enabledBorder: const OutlineInputBorder(
-          borderSide: BorderSide(color: Colors.transparent),
-        ),
-        errorBorder: OutlineInputBorder(
-          borderSide: BorderSide(color: colorScheme.error, width: 2.0),
-        ),
-        focusedBorder: OutlineInputBorder(
-          borderSide: BorderSide(color: colorScheme.primary, width: 2.0),
-        ),
-        focusedErrorBorder: OutlineInputBorder(
-          borderSide: BorderSide(color: colorScheme.error, width: 2.0),
-        ),
-        hintStyle: widget.style.copyWith(color: colorScheme.onSurface.withOpacity(0.36)),
-        // TODO(rami-a): Remove this logic once https://github.com/flutter/flutter/issues/54104 is fixed.
-        errorStyle: const TextStyle(fontSize: 0.0, height: 0.0), // Prevent the error text from appearing.
-      );
-    }
-    final Color unfocusedFillColor = timePickerTheme.hourMinuteColor ?? colorScheme.onSurface.withOpacity(0.12);
-    // If screen reader is in use, make the hint text say hours/minutes.
-    // Otherwise, remove the hint text when focused because the centered cursor
+    final InputDecorationTheme inputDecorationTheme = timePickerTheme.inputDecorationTheme ?? defaultTheme.inputDecorationTheme;
+    InputDecoration inputDecoration = const InputDecoration().applyDefaults(inputDecorationTheme);
+    // Remove the hint text when focused because the centered cursor
     // appears odd above the hint text.
-    //
-    // TODO(rami-a): Once https://github.com/flutter/flutter/issues/67571 is
-    // resolved, remove the window check for semantics being enabled on web.
-    final String? hintText = MediaQuery.of(context).accessibleNavigation || WidgetsBinding.instance.window.semanticsEnabled
-        ? widget.semanticHintText
-        : (focusNode.hasFocus ? null : _formattedValue);
+    final String? hintText = focusNode.hasFocus ? null : _formattedValue;
+
+    // Because the fill color is specified in both the inputDecorationTheme and
+    // the TimePickerTheme, if there's one in the user's input decoration theme,
+    // use that. If not, but there's one in the user's
+    // timePickerTheme.hourMinuteColor, use that, and otherwise use the default.
+    // We ignore the value in the fillColor of the input decoration in the
+    // default theme here, but it's the same as the hourMinuteColor.
+    final Color startingFillColor =
+      timePickerTheme.inputDecorationTheme?.fillColor ??
+      timePickerTheme.hourMinuteColor ??
+      defaultTheme.hourMinuteColor;
+    final Color fillColor;
+    if (theme.useMaterial3) {
+      fillColor = MaterialStateProperty.resolveAs<Color>(
+        startingFillColor,
+        <MaterialState>{
+          if (focusNode.hasFocus) MaterialState.focused,
+          if (focusNode.hasFocus) MaterialState.selected,
+        },
+      );
+    } else {
+      fillColor = focusNode.hasFocus ? Colors.transparent : startingFillColor;
+    }
+
     inputDecoration = inputDecoration.copyWith(
       hintText: hintText,
-      fillColor: focusNode.hasFocus ? Colors.transparent : inputDecorationTheme?.fillColor ?? unfocusedFillColor,
+      fillColor: fillColor,
     );
 
-    return SizedBox(
-      height: _kTimePickerHeaderControlHeight,
+    final Set<MaterialState> states = <MaterialState>{
+      if (focusNode.hasFocus) MaterialState.focused,
+      if (focusNode.hasFocus) MaterialState.selected,
+    };
+    final Color effectiveTextColor = MaterialStateProperty.resolveAs<Color>(
+      timePickerTheme.hourMinuteTextColor ?? defaultTheme.hourMinuteTextColor,
+      states,
+    );
+    final TextStyle effectiveStyle = MaterialStateProperty.resolveAs<TextStyle>(widget.style, states)
+      .copyWith(color: effectiveTextColor);
+
+    return SizedBox.fromSize(
+      size: alwaysUse24HourFormat ? defaultTheme.hourMinuteInputSize24Hour : defaultTheme.hourMinuteInputSize,
       child: MediaQuery(
-        data: MediaQuery.of(context).copyWith(textScaleFactor: 1.0),
+        data: MediaQuery.of(context).copyWith(textScaleFactor: 1),
         child: UnmanagedRestorationScope(
           bucket: bucket,
-          child: TextFormField(
-            restorationId: 'hour_minute_text_form_field',
-            autofocus: widget.autofocus ?? false,
-            expands: true,
-            maxLines: null,
-            inputFormatters: <TextInputFormatter>[
-              LengthLimitingTextInputFormatter(2),
-            ],
-            focusNode: focusNode,
-            textAlign: TextAlign.center,
-            textInputAction: widget.inputAction,
-            keyboardType: TextInputType.number,
-            style: widget.style.copyWith(color: timePickerTheme.hourMinuteTextColor ?? colorScheme.onSurface),
-            controller: controller.value,
-            decoration: inputDecoration,
-            validator: widget.validator,
-            onEditingComplete: () => widget.onSavedSubmitted(controller.value.text),
-            onSaved: widget.onSavedSubmitted,
-            onFieldSubmitted: widget.onSavedSubmitted,
-            onChanged: widget.onChanged,
+          child: Semantics(
+            label: widget.semanticHintText,
+            child: TextFormField(
+              restorationId: 'hour_minute_text_form_field',
+              autofocus: widget.autofocus ?? false,
+              expands: true,
+              maxLines: null,
+              inputFormatters: <TextInputFormatter>[
+                LengthLimitingTextInputFormatter(2),
+              ],
+              focusNode: focusNode,
+              textAlign: TextAlign.center,
+              textInputAction: widget.inputAction,
+              keyboardType: TextInputType.number,
+              style: effectiveStyle,
+              controller: controller.value,
+              decoration: inputDecoration,
+              validator: widget.validator,
+              onEditingComplete: () => widget.onSavedSubmitted(controller.value.text),
+              onSaved: widget.onSavedSubmitted,
+              onFieldSubmitted: widget.onSavedSubmitted,
+              onChanged: widget.onChanged,
+            ),
           ),
         ),
       ),
@@ -1870,15 +2153,13 @@
     this.minuteLabelText,
     this.restorationId,
     this.initialEntryMode = TimePickerEntryMode.dial,
+    this.orientation,
     this.onEntryModeChanged,
-  }) : assert(initialTime != null);
+  });
 
   /// The time initially selected when the dialog is shown.
   final TimeOfDay initialTime;
 
-  /// The entry mode for the picker. Whether it's text input or a dial.
-  final TimePickerEntryMode initialEntryMode;
-
   /// Optionally provide your own text for the cancel button.
   ///
   /// If null, the button uses [MaterialLocalizations.cancelButtonLabel].
@@ -1915,6 +2196,22 @@
   ///    Flutter.
   final String? restorationId;
 
+  /// The entry mode for the picker. Whether it's text input or a dial.
+  final TimePickerEntryMode initialEntryMode;
+
+  /// The optional [orientation] parameter sets the [Orientation] to use when
+  /// displaying the dialog.
+  ///
+  /// By default, the orientation is derived from the [MediaQueryData.size] of
+  /// the ambient [MediaQuery]. If the aspect of the size is tall, then
+  /// [Orientation.portrait] is used, if the size is wide, then
+  /// [Orientation.landscape] is used.
+  ///
+  /// Use this parameter to override the default and force the dialog to appear
+  /// in either portrait or landscape mode regardless of the aspect of the
+  /// [MediaQueryData.size].
+  final Orientation? orientation;
+
   /// Callback called when the selected entry mode is changed.
   final EntryModeChangeCallback? onEntryModeChanged;
 
@@ -1922,124 +2219,421 @@
   State<TimePickerDialog> createState() => _TimePickerDialogState();
 }
 
-// A restorable [TimePickerEntryMode] value.
-//
-// This serializes each entry as a unique `int` value.
-class _RestorableTimePickerEntryMode extends RestorableValue<TimePickerEntryMode> {
-  _RestorableTimePickerEntryMode(
-    TimePickerEntryMode defaultValue,
-  ) : _defaultValue = defaultValue;
-
-  final TimePickerEntryMode _defaultValue;
-
-  @override
-  TimePickerEntryMode createDefaultValue() => _defaultValue;
-
-  @override
-  void didUpdateValue(TimePickerEntryMode? oldValue) {
-    assert(debugIsSerializableForRestoration(value.index));
-    notifyListeners();
-  }
-
-  @override
-  TimePickerEntryMode fromPrimitives(Object? data) => TimePickerEntryMode.values[data! as int];
-
-  @override
-  Object? toPrimitives() => value.index;
-}
-
-// A restorable [_RestorableTimePickerEntryMode] value.
-//
-// This serializes each entry as a unique `int` value.
-class _RestorableTimePickerMode extends RestorableValue<_TimePickerMode> {
-  _RestorableTimePickerMode(
-    _TimePickerMode defaultValue,
-  ) : _defaultValue = defaultValue;
-
-  final _TimePickerMode _defaultValue;
-
-  @override
-  _TimePickerMode createDefaultValue() => _defaultValue;
-
-  @override
-  void didUpdateValue(_TimePickerMode? oldValue) {
-    assert(debugIsSerializableForRestoration(value.index));
-    notifyListeners();
-  }
-
-  @override
-  _TimePickerMode fromPrimitives(Object? data) => _TimePickerMode.values[data! as int];
-
-  @override
-  Object? toPrimitives() => value.index;
-}
-
-// A restorable [AutovalidateMode] value.
-//
-// This serializes each entry as a unique `int` value.
-class _RestorableAutovalidateMode extends RestorableValue<AutovalidateMode> {
-  _RestorableAutovalidateMode(
-      AutovalidateMode defaultValue,
-      ) : _defaultValue = defaultValue;
-
-  final AutovalidateMode _defaultValue;
-
-  @override
-  AutovalidateMode createDefaultValue() => _defaultValue;
-
-  @override
-  void didUpdateValue(AutovalidateMode? oldValue) {
-    assert(debugIsSerializableForRestoration(value.index));
-    notifyListeners();
-  }
-
-  @override
-  AutovalidateMode fromPrimitives(Object? data) => AutovalidateMode.values[data! as int];
-
-  @override
-  Object? toPrimitives() => value.index;
-}
-
-// A restorable [_RestorableTimePickerEntryMode] value.
-//
-// This serializes each entry as a unique `int` value.
-//
-// This value can be null.
-class _RestorableTimePickerModeN extends RestorableValue<_TimePickerMode?> {
-  _RestorableTimePickerModeN(
-    _TimePickerMode? defaultValue,
-  ) : _defaultValue = defaultValue;
-
-  final _TimePickerMode? _defaultValue;
-
-  @override
-  _TimePickerMode? createDefaultValue() => _defaultValue;
-
-  @override
-  void didUpdateValue(_TimePickerMode? oldValue) {
-    assert(debugIsSerializableForRestoration(value?.index));
-    notifyListeners();
-  }
-
-  @override
-  _TimePickerMode fromPrimitives(Object? data) => _TimePickerMode.values[data! as int];
-
-  @override
-  Object? toPrimitives() => value?.index;
-}
-
 class _TimePickerDialogState extends State<TimePickerDialog> with RestorationMixin {
+  late final RestorableEnum<TimePickerEntryMode> _entryMode = RestorableEnum<TimePickerEntryMode>(widget.initialEntryMode, values: TimePickerEntryMode.values);
+  late final RestorableTimeOfDay _selectedTime = RestorableTimeOfDay(widget.initialTime);
   final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
+  final RestorableEnum<AutovalidateMode> _autovalidateMode = RestorableEnum<AutovalidateMode>(AutovalidateMode.disabled, values: AutovalidateMode.values);
+  late final RestorableEnumN<Orientation> _orientation = RestorableEnumN<Orientation>(widget.orientation, values: Orientation.values);
 
-  late final _RestorableTimePickerEntryMode _entryMode = _RestorableTimePickerEntryMode(widget.initialEntryMode);
-  final _RestorableTimePickerMode _mode = _RestorableTimePickerMode(_TimePickerMode.hour);
-  final _RestorableTimePickerModeN _lastModeAnnounced = _RestorableTimePickerModeN(null);
-  final _RestorableAutovalidateMode _autovalidateMode = _RestorableAutovalidateMode(AutovalidateMode.disabled);
+  // Base sizes
+  static const Size _kTimePickerPortraitSize = Size(310, 468);
+  static const Size _kTimePickerLandscapeSize = Size(524, 342);
+  static const Size _kTimePickerLandscapeSizeM2 = Size(508, 300);
+  static const Size _kTimePickerInputSize = Size(312, 216);
+
+  // Absolute minimum dialog sizes, which is the point at which it begins
+  // scrolling to fit everything in.
+  static const Size _kTimePickerMinPortraitSize = Size(238, 326);
+  static const Size _kTimePickerMinLandscapeSize = Size(416, 248);
+  static const Size _kTimePickerMinInputSize = Size(312, 196);
+
+  @override
+  String? get restorationId => widget.restorationId;
+
+  @override
+  void restoreState(RestorationBucket? oldBucket, bool initialRestore) {
+    registerForRestoration(_selectedTime, 'selected_time');
+    registerForRestoration(_entryMode, 'entry_mode');
+    registerForRestoration(_autovalidateMode, 'autovalidate_mode');
+    registerForRestoration(_orientation, 'orientation');
+  }
+
+  void _handleTimeChanged(TimeOfDay value) {
+    if (value != _selectedTime.value) {
+      setState(() {
+        _selectedTime.value = value;
+      });
+    }
+  }
+
+  void _handleEntryModeChanged(TimePickerEntryMode value) {
+    if (value != _entryMode.value) {
+      setState(() {
+        switch (_entryMode.value) {
+          case TimePickerEntryMode.dial:
+            _autovalidateMode.value = AutovalidateMode.disabled;
+            break;
+          case TimePickerEntryMode.input:
+            _formKey.currentState!.save();
+            break;
+          case TimePickerEntryMode.dialOnly:
+            break;
+          case TimePickerEntryMode.inputOnly:
+            break;
+        }
+        _entryMode.value = value;
+        widget.onEntryModeChanged?.call(value);
+      });
+    }
+  }
+
+  void _toggleEntryMode() {
+    switch (_entryMode.value) {
+      case TimePickerEntryMode.dial:
+        _handleEntryModeChanged(TimePickerEntryMode.input);
+        break;
+      case TimePickerEntryMode.input:
+        _handleEntryModeChanged(TimePickerEntryMode.dial);
+        break;
+      case TimePickerEntryMode.dialOnly:
+      case TimePickerEntryMode.inputOnly:
+        FlutterError('Can not change entry mode from $_entryMode');
+        break;
+    }
+  }
+
+  void _handleCancel() {
+    Navigator.pop(context);
+  }
+
+  void _handleOk() {
+    if (_entryMode.value == TimePickerEntryMode.input || _entryMode.value == TimePickerEntryMode.inputOnly) {
+      final FormState form = _formKey.currentState!;
+      if (!form.validate()) {
+        setState(() {
+          _autovalidateMode.value = AutovalidateMode.always;
+        });
+        return;
+      }
+      form.save();
+    }
+    Navigator.pop(context, _selectedTime.value);
+  }
+
+  Size _minDialogSize(BuildContext context, {required bool useMaterial3}) {
+    final Orientation orientation = _orientation.value ?? MediaQuery.orientationOf(context);
+
+    switch (_entryMode.value) {
+      case TimePickerEntryMode.dial:
+      case TimePickerEntryMode.dialOnly:
+        switch (orientation) {
+          case Orientation.portrait:
+            return _kTimePickerMinPortraitSize;
+          case Orientation.landscape:
+            return _kTimePickerMinLandscapeSize;
+        }
+      case TimePickerEntryMode.input:
+      case TimePickerEntryMode.inputOnly:
+        final MaterialLocalizations localizations = MaterialLocalizations.of(context);
+        final TimeOfDayFormat timeOfDayFormat = localizations.timeOfDayFormat(alwaysUse24HourFormat: MediaQuery.alwaysUse24HourFormatOf(context));
+        final double timePickerWidth;
+        switch(timeOfDayFormat) {
+          case TimeOfDayFormat.HH_colon_mm:
+          case TimeOfDayFormat.HH_dot_mm:
+          case TimeOfDayFormat.frenchCanadian:
+          case TimeOfDayFormat.H_colon_mm:
+            final _TimePickerDefaults defaultTheme = useMaterial3 ? _TimePickerDefaultsM3(context) : _TimePickerDefaultsM2(context);
+            timePickerWidth = _kTimePickerMinInputSize.width - defaultTheme.dayPeriodPortraitSize.width - 12;
+            break;
+          case TimeOfDayFormat.a_space_h_colon_mm:
+          case TimeOfDayFormat.h_colon_mm_space_a:
+            timePickerWidth = _kTimePickerMinInputSize.width;
+            break;
+        }
+        return Size(timePickerWidth, _kTimePickerMinInputSize.height);
+    }
+  }
+
+  Size _dialogSize(BuildContext context, {required bool useMaterial3}) {
+    final Orientation orientation = _orientation.value ?? MediaQuery.orientationOf(context);
+    // Constrain the textScaleFactor to prevent layout issues. Since only some
+    // parts of the time picker scale up with textScaleFactor, we cap the factor
+    // to 1.1 as that provides enough space to reasonably fit all the content.
+    final double textScaleFactor = math.min(MediaQuery.textScaleFactorOf(context), 1.1);
+
+    final Size timePickerSize;
+    switch (_entryMode.value) {
+      case TimePickerEntryMode.dial:
+      case TimePickerEntryMode.dialOnly:
+        switch (orientation) {
+          case Orientation.portrait:
+            timePickerSize = _kTimePickerPortraitSize;
+            break;
+          case Orientation.landscape:
+            timePickerSize = Size(
+              _kTimePickerLandscapeSize.width * textScaleFactor,
+              useMaterial3 ? _kTimePickerLandscapeSize.height : _kTimePickerLandscapeSizeM2.height
+            );
+            break;
+        }
+        break;
+      case TimePickerEntryMode.input:
+      case TimePickerEntryMode.inputOnly:
+        final MaterialLocalizations localizations = MaterialLocalizations.of(context);
+        final TimeOfDayFormat timeOfDayFormat = localizations.timeOfDayFormat(alwaysUse24HourFormat: MediaQuery.alwaysUse24HourFormatOf(context));
+        final double timePickerWidth;
+        switch(timeOfDayFormat) {
+          case TimeOfDayFormat.HH_colon_mm:
+          case TimeOfDayFormat.HH_dot_mm:
+          case TimeOfDayFormat.frenchCanadian:
+          case TimeOfDayFormat.H_colon_mm:
+            final _TimePickerDefaults defaultTheme = useMaterial3 ? _TimePickerDefaultsM3(context) : _TimePickerDefaultsM2(context);
+            timePickerWidth = _kTimePickerInputSize.width - defaultTheme.dayPeriodPortraitSize.width - 12;
+            break;
+          case TimeOfDayFormat.a_space_h_colon_mm:
+          case TimeOfDayFormat.h_colon_mm_space_a:
+            timePickerWidth = _kTimePickerInputSize.width;
+            break;
+        }
+        timePickerSize = Size(timePickerWidth, _kTimePickerInputSize.height);
+        break;
+    }
+    return Size(timePickerSize.width, timePickerSize.height * textScaleFactor);
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    assert(debugCheckHasMediaQuery(context));
+    final ThemeData theme = Theme.of(context);
+    final TimePickerThemeData pickerTheme = TimePickerTheme.of(context);
+    final _TimePickerDefaults defaultTheme = theme.useMaterial3 ? _TimePickerDefaultsM3(context) : _TimePickerDefaultsM2(context);
+    final ShapeBorder shape = pickerTheme.shape ?? defaultTheme.shape;
+    final Color entryModeIconColor = pickerTheme.entryModeIconColor ?? defaultTheme.entryModeIconColor;
+    final MaterialLocalizations localizations = MaterialLocalizations.of(context);
+
+    final Widget actions = Padding(
+      padding: EdgeInsetsDirectional.only(start: theme.useMaterial3 ? 0 : 4),
+      child: Row(
+        children: <Widget>[
+          if (_entryMode.value == TimePickerEntryMode.dial || _entryMode.value == TimePickerEntryMode.input)
+            IconButton(
+              // In material3 mode, we want to use the color as part of the
+              // button style which applies its own opacity. In material2 mode,
+              // we want to use the color as the color, which already includes
+              // the opacity.
+              color: theme.useMaterial3 ? null : entryModeIconColor,
+              style: theme.useMaterial3 ? IconButton.styleFrom(foregroundColor: entryModeIconColor) : null,
+              onPressed: _toggleEntryMode,
+              icon: Icon(_entryMode.value == TimePickerEntryMode.dial ? Icons.keyboard_outlined : Icons.access_time),
+              tooltip: _entryMode.value == TimePickerEntryMode.dial
+                  ? MaterialLocalizations.of(context).inputTimeModeButtonLabel
+                  : MaterialLocalizations.of(context).dialModeButtonLabel,
+            ),
+          Expanded(
+            child: Container(
+              alignment: AlignmentDirectional.centerEnd,
+              constraints: const BoxConstraints(minHeight: 36),
+              child: OverflowBar(
+                spacing: 8,
+                overflowAlignment: OverflowBarAlignment.end,
+                children: <Widget>[
+                  TextButton(
+                    onPressed: _handleCancel,
+                    child: Text(widget.cancelText ??
+                        (theme.useMaterial3
+                            ? localizations.cancelButtonLabel
+                            : localizations.cancelButtonLabel.toUpperCase())),
+                  ),
+                  TextButton(
+                    onPressed: _handleOk,
+                    child: Text(widget.confirmText ?? localizations.okButtonLabel),
+                  ),
+                ],
+              ),
+            ),
+          ),
+        ],
+      ),
+    );
+
+    final Offset tapTargetSizeOffset;
+    switch (theme.materialTapTargetSize) {
+      case MaterialTapTargetSize.padded:
+        tapTargetSizeOffset = Offset.zero;
+        break;
+      case MaterialTapTargetSize.shrinkWrap:
+        // _dialogSize returns "padded" sizes.
+        tapTargetSizeOffset = const Offset(0, -12);
+        break;
+    }
+
+    final Size dialogSize = _dialogSize(context, useMaterial3: theme.useMaterial3) + tapTargetSizeOffset;
+    final Size minDialogSize = _minDialogSize(context, useMaterial3: theme.useMaterial3) + tapTargetSizeOffset;
+    return Dialog(
+      shape: shape,
+      elevation: pickerTheme.elevation ?? defaultTheme.elevation,
+      backgroundColor: pickerTheme.backgroundColor ?? defaultTheme.backgroundColor,
+      insetPadding: EdgeInsets.symmetric(
+        horizontal: 16,
+        vertical: (_entryMode.value == TimePickerEntryMode.input || _entryMode.value == TimePickerEntryMode.inputOnly) ? 0 : 24,
+      ),
+      child: Padding(
+        padding: pickerTheme.padding ?? defaultTheme.padding,
+        child: LayoutBuilder(builder: (BuildContext context, BoxConstraints constraints) {
+          final Size constrainedSize = constraints.constrain(dialogSize);
+          final Size allowedSize = Size(
+            constrainedSize.width < minDialogSize.width ? minDialogSize.width : constrainedSize.width,
+            constrainedSize.height < minDialogSize.height ? minDialogSize.height : constrainedSize.height,
+          );
+          return SingleChildScrollView(
+            restorationId: 'time_picker_scroll_view_horizontal',
+            scrollDirection: Axis.horizontal,
+            child: SingleChildScrollView(
+              restorationId: 'time_picker_scroll_view_vertical',
+              child: AnimatedContainer(
+                width: allowedSize.width,
+                height: allowedSize.height,
+                duration: _kDialogSizeAnimationDuration,
+                curve: Curves.easeIn,
+                child: Column(
+                  crossAxisAlignment: CrossAxisAlignment.start,
+                  children: <Widget>[
+                    Expanded(
+                      child: Form(
+                        key: _formKey,
+                        autovalidateMode: _autovalidateMode.value,
+                        child: _TimePicker(
+                          time: widget.initialTime,
+                          onTimeChanged: _handleTimeChanged,
+                          helpText: widget.helpText,
+                          cancelText: widget.cancelText,
+                          confirmText: widget.confirmText,
+                          errorInvalidText: widget.errorInvalidText,
+                          hourLabelText: widget.hourLabelText,
+                          minuteLabelText: widget.minuteLabelText,
+                          restorationId: 'time_picker',
+                          entryMode: _entryMode.value,
+                          orientation: widget.orientation,
+                          onEntryModeChanged: _handleEntryModeChanged,
+                        ),
+                      ),
+                    ),
+                    actions,
+                  ],
+                ),
+              ),
+            ),
+          );
+        }),
+      ),
+    );
+  }
+}
+
+// The _TimePicker widget is constructed so that in the future we could expose
+// this as a public API for embedding time pickers into other non-dialog
+// widgets, once we're sure we want to support that.
+
+/// A Time Picker widget that can be embedded into another widget.
+class _TimePicker extends StatefulWidget {
+  /// Creates a const Material Design time picker.
+  const _TimePicker({
+    required this.time,
+    required this.onTimeChanged,
+    this.helpText,
+    this.cancelText,
+    this.confirmText,
+    this.errorInvalidText,
+    this.hourLabelText,
+    this.minuteLabelText,
+    this.restorationId,
+    this.entryMode = TimePickerEntryMode.dial,
+    this.orientation,
+    this.onEntryModeChanged,
+  });
+
+  /// Optionally provide your own text for the help text at the top of the
+  /// control.
+  ///
+  /// If null, the widget uses [MaterialLocalizations.timePickerDialHelpText]
+  /// when the [entryMode] is [TimePickerEntryMode.dial], and
+  /// [MaterialLocalizations.timePickerInputHelpText] when the [entryMode] is
+  /// [TimePickerEntryMode.input].
+  final String? helpText;
+
+  /// Optionally provide your own text for the cancel button.
+  ///
+  /// If null, the button uses [MaterialLocalizations.cancelButtonLabel].
+  final String? cancelText;
+
+  /// Optionally provide your own text for the confirm button.
+  ///
+  /// If null, the button uses [MaterialLocalizations.okButtonLabel].
+  final String? confirmText;
+
+  /// Optionally provide your own validation error text.
+  final String? errorInvalidText;
+
+  /// Optionally provide your own hour label text.
+  final String? hourLabelText;
+
+  /// Optionally provide your own minute label text.
+  final String? minuteLabelText;
+
+  /// Restoration ID to save and restore the state of the [TimePickerDialog].
+  ///
+  /// If it is non-null, the time picker will persist and restore the
+  /// dialog's state.
+  ///
+  /// The state of this widget is persisted in a [RestorationBucket] claimed
+  /// from the surrounding [RestorationScope] using the provided restoration ID.
+  ///
+  /// See also:
+  ///
+  ///  * [RestorationManager], which explains how state restoration works in
+  ///    Flutter.
+  final String? restorationId;
+
+  /// The initial entry mode for the picker. Whether it's text input or a dial.
+  final TimePickerEntryMode entryMode;
+
+  /// The currently selected time of day.
+  final TimeOfDay time;
+
+  final ValueChanged<TimeOfDay>? onTimeChanged;
+
+  /// The optional [orientation] parameter sets the [Orientation] to use when
+  /// displaying the dialog.
+  ///
+  /// By default, the orientation is derived from the [MediaQueryData.size] of
+  /// the ambient [MediaQuery]. If the aspect of the size is tall, then
+  /// [Orientation.portrait] is used, if the size is wide, then
+  /// [Orientation.landscape] is used.
+  ///
+  /// Use this parameter to override the default and force the dialog to appear
+  /// in either portrait or landscape mode regardless of the aspect of the
+  /// [MediaQueryData.size].
+  final Orientation? orientation;
+
+  /// Callback called when the selected entry mode is changed.
+  final EntryModeChangeCallback? onEntryModeChanged;
+
+  @override
+  State<_TimePicker> createState() => _TimePickerState();
+}
+
+class _TimePickerState extends State<_TimePicker> with RestorationMixin {
+  Timer? _vibrateTimer;
+  late MaterialLocalizations localizations;
+  final RestorableEnum<_HourMinuteMode> _hourMinuteMode =
+      RestorableEnum<_HourMinuteMode>(_HourMinuteMode.hour, values: _HourMinuteMode.values);
+  final RestorableEnumN<_HourMinuteMode> _lastModeAnnounced =
+      RestorableEnumN<_HourMinuteMode>(null, values: _HourMinuteMode.values);
   final RestorableBoolN _autofocusHour = RestorableBoolN(null);
   final RestorableBoolN _autofocusMinute = RestorableBoolN(null);
   final RestorableBool _announcedInitialTime = RestorableBool(false);
+  late final RestorableEnumN<Orientation> _orientation =
+      RestorableEnumN<Orientation>(widget.orientation, values: Orientation.values);
+  RestorableTimeOfDay get selectedTime => _selectedTime;
+  late final RestorableTimeOfDay _selectedTime = RestorableTimeOfDay(widget.time);
 
-  late final VoidCallback _entryModeListener;
+  @override
+  void dispose() {
+    _vibrateTimer?.cancel();
+    _vibrateTimer = null;
+    super.dispose();
+  }
 
   @override
   void didChangeDependencies() {
@@ -2050,10 +2644,18 @@
   }
 
   @override
-  void initState() {
-    super.initState();
-    _entryModeListener = () => widget.onEntryModeChanged?.call(_entryMode.value);
-    _entryMode.addListener(_entryModeListener);
+  void didUpdateWidget (_TimePicker oldWidget) {
+    super.didUpdateWidget(oldWidget);
+    if (oldWidget.orientation != widget.orientation) {
+      _orientation.value = widget.orientation;
+    }
+    if (oldWidget.time != widget.time) {
+      _selectedTime.value = widget.time;
+    }
+  }
+
+  void _setEntryMode(TimePickerEntryMode mode){
+    widget.onEntryModeChanged?.call(mode);
   }
 
   @override
@@ -2061,22 +2663,15 @@
 
   @override
   void restoreState(RestorationBucket? oldBucket, bool initialRestore) {
-    registerForRestoration(_entryMode, 'entry_mode');
-    registerForRestoration(_mode, 'mode');
+    registerForRestoration(_hourMinuteMode, 'hour_minute_mode');
     registerForRestoration(_lastModeAnnounced, 'last_mode_announced');
-    registerForRestoration(_autovalidateMode, 'autovalidateMode');
     registerForRestoration(_autofocusHour, 'autofocus_hour');
     registerForRestoration(_autofocusMinute, 'autofocus_minute');
     registerForRestoration(_announcedInitialTime, 'announced_initial_time');
     registerForRestoration(_selectedTime, 'selected_time');
+    registerForRestoration(_orientation, 'orientation');
   }
 
-  RestorableTimeOfDay get selectedTime => _selectedTime;
-  late final RestorableTimeOfDay _selectedTime = RestorableTimeOfDay(widget.initialTime);
-
-  Timer? _vibrateTimer;
-  late MaterialLocalizations localizations;
-
   void _vibrate() {
     switch (Theme.of(context).platform) {
       case TargetPlatform.android:
@@ -2095,50 +2690,50 @@
     }
   }
 
-  void _handleModeChanged(_TimePickerMode mode) {
+  void _handleHourMinuteModeChanged(_HourMinuteMode mode) {
     _vibrate();
     setState(() {
-      _mode.value = mode;
+      _hourMinuteMode.value = mode;
       _announceModeOnce();
     });
   }
 
   void _handleEntryModeToggle() {
     setState(() {
-      switch (_entryMode.value) {
+      TimePickerEntryMode newMode = widget.entryMode;
+      switch (widget.entryMode) {
         case TimePickerEntryMode.dial:
-          _autovalidateMode.value = AutovalidateMode.disabled;
-          _entryMode.value = TimePickerEntryMode.input;
+          newMode = TimePickerEntryMode.input;
           break;
         case TimePickerEntryMode.input:
-          _formKey.currentState!.save();
           _autofocusHour.value = false;
           _autofocusMinute.value = false;
-          _entryMode.value = TimePickerEntryMode.dial;
+          newMode = TimePickerEntryMode.dial;
           break;
         case TimePickerEntryMode.dialOnly:
         case TimePickerEntryMode.inputOnly:
-          FlutterError('Can not change entry mode from $_entryMode');
+          FlutterError('Can not change entry mode from ${widget.entryMode}');
           break;
       }
+      _setEntryMode(newMode);
     });
   }
 
   void _announceModeOnce() {
-    if (_lastModeAnnounced.value == _mode.value) {
+    if (_lastModeAnnounced.value == _hourMinuteMode.value) {
       // Already announced it.
       return;
     }
 
-    switch (_mode.value) {
-      case _TimePickerMode.hour:
+    switch (_hourMinuteMode.value) {
+      case _HourMinuteMode.hour:
         _announceToAccessibility(context, localizations.timePickerHourModeAnnouncement);
         break;
-      case _TimePickerMode.minute:
+      case _HourMinuteMode.minute:
         _announceToAccessibility(context, localizations.timePickerMinuteModeAnnouncement);
         break;
     }
-    _lastModeAnnounced.value = _mode.value;
+    _lastModeAnnounced.value = _hourMinuteMode.value;
   }
 
   void _announceInitialTimeOnce() {
@@ -2146,11 +2741,10 @@
       return;
     }
 
-    final MediaQueryData media = MediaQuery.of(context);
     final MaterialLocalizations localizations = MaterialLocalizations.of(context);
     _announceToAccessibility(
       context,
-      localizations.formatTimeOfDay(widget.initialTime, alwaysUse24HourFormat: media.alwaysUse24HourFormat),
+      localizations.formatTimeOfDay(_selectedTime.value, alwaysUse24HourFormat: MediaQuery.alwaysUse24HourFormatOf(context)),
     );
     _announcedInitialTime.value = true;
   }
@@ -2159,6 +2753,7 @@
     _vibrate();
     setState(() {
       _selectedTime.value = value;
+      widget.onTimeChanged?.call(value);
     });
   }
 
@@ -2174,161 +2769,94 @@
 
   void _handleHourSelected() {
     setState(() {
-      _mode.value = _TimePickerMode.minute;
+      _hourMinuteMode.value = _HourMinuteMode.minute;
     });
   }
 
-  void _handleCancel() {
-    Navigator.pop(context);
-  }
-
-  void _handleOk() {
-    if (_entryMode.value == TimePickerEntryMode.input || _entryMode.value == TimePickerEntryMode.inputOnly) {
-      final FormState form = _formKey.currentState!;
-      if (!form.validate()) {
-        setState(() { _autovalidateMode.value = AutovalidateMode.always; });
-        return;
-      }
-      form.save();
-    }
-    Navigator.pop(context, _selectedTime.value);
-  }
-
-  Size _dialogSize(BuildContext context) {
-    final Orientation orientation = MediaQuery.of(context).orientation;
-    final ThemeData theme = Theme.of(context);
-    // Constrain the textScaleFactor to prevent layout issues. Since only some
-    // parts of the time picker scale up with textScaleFactor, we cap the factor
-    // to 1.1 as that provides enough space to reasonably fit all the content.
-    final double textScaleFactor = math.min(MediaQuery.of(context).textScaleFactor, 1.1);
-
-    final double timePickerWidth;
-    final double timePickerHeight;
-    switch (_entryMode.value) {
-      case TimePickerEntryMode.dial:
-      case TimePickerEntryMode.dialOnly:
-        switch (orientation) {
-          case Orientation.portrait:
-            timePickerWidth = _kTimePickerWidthPortrait;
-            timePickerHeight = theme.materialTapTargetSize == MaterialTapTargetSize.padded
-                ? _kTimePickerHeightPortrait
-                : _kTimePickerHeightPortraitCollapsed;
-            break;
-          case Orientation.landscape:
-            timePickerWidth = _kTimePickerWidthLandscape * textScaleFactor;
-            timePickerHeight = theme.materialTapTargetSize == MaterialTapTargetSize.padded
-                ? _kTimePickerHeightLandscape
-                : _kTimePickerHeightLandscapeCollapsed;
-            break;
-        }
-        break;
-      case TimePickerEntryMode.input:
-      case TimePickerEntryMode.inputOnly:
-        timePickerWidth = _kTimePickerWidthPortrait;
-        timePickerHeight = _kTimePickerHeightInput;
-        break;
-    }
-    return Size(timePickerWidth, timePickerHeight * textScaleFactor);
-  }
-
   @override
   Widget build(BuildContext context) {
     assert(debugCheckHasMediaQuery(context));
-    final MediaQueryData media = MediaQuery.of(context);
-    final TimeOfDayFormat timeOfDayFormat = localizations.timeOfDayFormat(alwaysUse24HourFormat: media.alwaysUse24HourFormat);
-    final bool use24HourDials = hourFormat(of: timeOfDayFormat) != HourFormat.h;
+    final TimeOfDayFormat timeOfDayFormat = localizations.timeOfDayFormat(alwaysUse24HourFormat: MediaQuery.alwaysUse24HourFormatOf(context));
     final ThemeData theme = Theme.of(context);
-    final ShapeBorder shape = TimePickerTheme.of(context).shape ?? _kDefaultShape;
-    final Orientation orientation = media.orientation;
+    final _TimePickerDefaults defaultTheme = theme.useMaterial3 ? _TimePickerDefaultsM3(context) : _TimePickerDefaultsM2(context);
+    final Orientation orientation = _orientation.value ?? MediaQuery.orientationOf(context);
+    final HourFormat timeOfDayHour = hourFormat(of: timeOfDayFormat);
+    final _HourDialType hourMode;
+    switch (timeOfDayHour) {
+      case HourFormat.HH:
+      case HourFormat.H:
+        hourMode = theme.useMaterial3 ? _HourDialType.twentyFourHourDoubleRing : _HourDialType.twentyFourHour;
+        break;
+      case HourFormat.h:
+        hourMode = _HourDialType.twelveHour;
+        break;
+    }
 
-    final Widget actions = Row(
-      children: <Widget>[
-        const SizedBox(width: 10.0),
-        if (_entryMode.value == TimePickerEntryMode.dial || _entryMode.value == TimePickerEntryMode.input)
-          IconButton(
-            color: TimePickerTheme.of(context).entryModeIconColor ?? theme.colorScheme.onSurface.withOpacity(
-              theme.colorScheme.brightness == Brightness.dark ? 1.0 : 0.6,
-            ),
-            onPressed: _handleEntryModeToggle,
-            icon: Icon(_entryMode.value == TimePickerEntryMode.dial ? Icons.keyboard : Icons.access_time),
-            tooltip: _entryMode.value == TimePickerEntryMode.dial
-                ? MaterialLocalizations.of(context).inputTimeModeButtonLabel
-                : MaterialLocalizations.of(context).dialModeButtonLabel,
-          ),
-        Expanded(
-          child: Container(
-            alignment: AlignmentDirectional.centerEnd,
-            constraints: const BoxConstraints(minHeight: 52.0),
-            padding: const EdgeInsets.symmetric(horizontal: 8),
-            child: OverflowBar(
-              spacing: 8,
-              overflowAlignment: OverflowBarAlignment.end,
-              children: <Widget>[
-                TextButton(
-                  onPressed: _handleCancel,
-                  child: Text(widget.cancelText ?? (
-                    theme.useMaterial3
-                      ? localizations.cancelButtonLabel
-                      : localizations.cancelButtonLabel.toUpperCase()
-                  )),
-                ),
-                TextButton(
-                  onPressed: _handleOk,
-                  child: Text(widget.confirmText ?? localizations.okButtonLabel),
-                ),
-              ],
-            ),
-          ),
-        ),
-      ],
-    );
-
+    final String helpText;
     final Widget picker;
-    switch (_entryMode.value) {
+    switch (widget.entryMode) {
       case TimePickerEntryMode.dial:
       case TimePickerEntryMode.dialOnly:
+        helpText = widget.helpText ?? (theme.useMaterial3
+          ? localizations.timePickerDialHelpText
+          : localizations.timePickerDialHelpText.toUpperCase());
+
+        final EdgeInsetsGeometry dialPadding;
+        switch (orientation) {
+          case Orientation.portrait:
+            dialPadding = const EdgeInsets.only(left: 12, right: 12, top: 36);
+            break;
+          case Orientation.landscape:
+            switch (theme.materialTapTargetSize) {
+              case MaterialTapTargetSize.padded:
+                dialPadding = const EdgeInsetsDirectional.only(start: 64);
+                break;
+              case MaterialTapTargetSize.shrinkWrap:
+                dialPadding = const EdgeInsetsDirectional.only(start: 64);
+                break;
+            }
+            break;
+        }
         final Widget dial = Padding(
-          padding: orientation == Orientation.portrait ? const EdgeInsets.symmetric(horizontal: 36, vertical: 24) : const EdgeInsets.all(24),
+          padding: dialPadding,
           child: ExcludeSemantics(
-            child: AspectRatio(
-              aspectRatio: 1.0,
-              child: _Dial(
-                mode: _mode.value,
-                use24HourDials: use24HourDials,
-                selectedTime: _selectedTime.value,
-                onChanged: _handleTimeChanged,
-                onHourSelected: _handleHourSelected,
+            child: SizedBox.fromSize(
+              size: defaultTheme.dialSize,
+              child: AspectRatio(
+                aspectRatio: 1,
+                child: _Dial(
+                  hourMinuteMode: _hourMinuteMode.value,
+                  hourDialType: hourMode,
+                  selectedTime: _selectedTime.value,
+                  onChanged: _handleTimeChanged,
+                  onHourSelected: _handleHourSelected,
+                ),
               ),
             ),
           ),
         );
 
-        final Widget header = _TimePickerHeader(
-          selectedTime: _selectedTime.value,
-          mode: _mode.value,
-          orientation: orientation,
-          onModeChanged: _handleModeChanged,
-          onChanged: _handleTimeChanged,
-          onHourDoubleTapped: _handleHourDoubleTapped,
-          onMinuteDoubleTapped: _handleMinuteDoubleTapped,
-          use24HourDials: use24HourDials,
-          helpText: widget.helpText,
-        );
-
         switch (orientation) {
           case Orientation.portrait:
             picker = Column(
               mainAxisSize: MainAxisSize.min,
               crossAxisAlignment: CrossAxisAlignment.stretch,
               children: <Widget>[
-                header,
+                Padding(
+                  padding: EdgeInsets.symmetric(horizontal: theme.useMaterial3 ? 0 : 16),
+                  child: _TimePickerHeader(helpText: helpText),
+                ),
                 Expanded(
                   child: Column(
                     mainAxisSize: MainAxisSize.min,
                     children: <Widget>[
                       // Dial grows and shrinks with the available space.
-                      Expanded(child: dial),
-                      actions,
+                      Expanded(
+                        child: Padding(
+                          padding: EdgeInsets.symmetric(horizontal: theme.useMaterial3 ? 0 : 16),
+                          child: dial,
+                        ),
+                      ),
                     ],
                   ),
                 ),
@@ -2339,14 +2867,17 @@
             picker = Column(
               children: <Widget>[
                 Expanded(
-                  child: Row(
-                    children: <Widget>[
-                      header,
-                      Expanded(child: dial),
-                    ],
+                  child: Padding(
+                    padding: EdgeInsets.symmetric(horizontal: theme.useMaterial3 ? 0 : 16),
+                    child: Row(
+                      crossAxisAlignment: CrossAxisAlignment.stretch,
+                      children: <Widget>[
+                        _TimePickerHeader(helpText: helpText),
+                        Expanded(child: dial),
+                      ],
+                    ),
                   ),
                 ),
-                actions,
               ],
             );
             break;
@@ -2354,58 +2885,43 @@
         break;
       case TimePickerEntryMode.input:
       case TimePickerEntryMode.inputOnly:
-        picker = Form(
-          key: _formKey,
-          autovalidateMode: _autovalidateMode.value,
-          child: SingleChildScrollView(
-            restorationId: 'time_picker_scroll_view',
-            child: Column(
-              mainAxisSize: MainAxisSize.min,
-              children: <Widget>[
-                _TimePickerInput(
-                  initialSelectedTime: _selectedTime.value,
-                  helpText: widget.helpText,
-                  errorInvalidText: widget.errorInvalidText,
-                  hourLabelText: widget.hourLabelText,
-                  minuteLabelText: widget.minuteLabelText,
-                  autofocusHour: _autofocusHour.value,
-                  autofocusMinute: _autofocusMinute.value,
-                  onChanged: _handleTimeChanged,
-                  restorationId: 'time_picker_input',
-                ),
-                actions,
-              ],
+        final String helpText =  widget.helpText ?? (theme.useMaterial3
+          ? localizations.timePickerInputHelpText
+          : localizations.timePickerInputHelpText.toUpperCase());
+
+        picker = Column(
+          mainAxisSize: MainAxisSize.min,
+          children: <Widget>[
+            _TimePickerInput(
+              initialSelectedTime: _selectedTime.value,
+              errorInvalidText: widget.errorInvalidText,
+              hourLabelText: widget.hourLabelText,
+              minuteLabelText: widget.minuteLabelText,
+              helpText: helpText,
+              autofocusHour: _autofocusHour.value,
+              autofocusMinute: _autofocusMinute.value,
+              restorationId: 'time_picker_input',
             ),
-          ),
+          ],
         );
-        break;
     }
-
-    final Size dialogSize = _dialogSize(context);
-    return Dialog(
-      shape: shape,
-      backgroundColor: TimePickerTheme.of(context).backgroundColor ?? theme.colorScheme.surface,
-      insetPadding: EdgeInsets.symmetric(
-        horizontal: 16.0,
-        vertical: (_entryMode.value == TimePickerEntryMode.input || _entryMode.value == TimePickerEntryMode.inputOnly) ? 0.0 : 24.0,
-      ),
-      child: AnimatedContainer(
-        width: dialogSize.width,
-        height: dialogSize.height,
-        duration: _kDialogSizeAnimationDuration,
-        curve: Curves.easeIn,
-        child: picker,
-      ),
+    return _TimePickerModel(
+      entryMode: widget.entryMode,
+      selectedTime: _selectedTime.value,
+      hourMinuteMode: _hourMinuteMode.value,
+      orientation: orientation,
+      onHourMinuteModeChanged: _handleHourMinuteModeChanged,
+      onHourDoubleTapped: _handleHourDoubleTapped,
+      onMinuteDoubleTapped: _handleMinuteDoubleTapped,
+      hourDialType: hourMode,
+      onSelectedTimeChanged: _handleTimeChanged,
+      useMaterial3: theme.useMaterial3,
+      use24HourFormat: MediaQuery.alwaysUse24HourFormatOf(context),
+      theme: TimePickerTheme.of(context),
+      defaultTheme: defaultTheme,
+      child: picker,
     );
   }
-
-  @override
-  void dispose() {
-    _vibrateTimer?.cancel();
-    _vibrateTimer = null;
-    _entryMode.removeListener(_entryModeListener);
-    super.dispose();
-  }
 }
 
 /// Shows a dialog containing a Material Design time picker.
@@ -2413,8 +2929,7 @@
 /// The returned Future resolves to the time selected by the user when the user
 /// closes the dialog. If the user cancels the dialog, null is returned.
 ///
-/// {@tool snippet}
-/// Show a dialog with [initialTime] equal to the current time.
+/// {@tool snippet} Show a dialog with [initialTime] equal to the current time.
 ///
 /// ```dart
 /// Future<TimeOfDay?> selectedTime = showTimePicker(
@@ -2424,29 +2939,35 @@
 /// ```
 /// {@end-tool}
 ///
-/// The [context], [useRootNavigator] and [routeSettings] arguments are passed to
-/// [showDialog], the documentation for which discusses how it is used.
+/// The [context], [useRootNavigator] and [routeSettings] arguments are passed
+/// to [showDialog], the documentation for which discusses how it is used.
 ///
-/// The [builder] parameter can be used to wrap the dialog widget
-/// to add inherited widgets like [Localizations.override],
-/// [Directionality], or [MediaQuery].
+/// The [builder] parameter can be used to wrap the dialog widget to add
+/// inherited widgets like [Localizations.override], [Directionality], or
+/// [MediaQuery].
 ///
-/// The `initialEntryMode` parameter can be used to
-/// determine the initial time entry selection of the picker (either a clock
-/// dial or text input).
+/// The `initialEntryMode` parameter can be used to determine the initial time
+/// entry selection of the picker (either a clock dial or text input).
 ///
 /// Optional strings for the [helpText], [cancelText], [errorInvalidText],
 /// [hourLabelText], [minuteLabelText] and [confirmText] can be provided to
 /// override the default values.
 ///
+/// The optional [orientation] parameter sets the [Orientation] to use when
+/// displaying the dialog. By default, the orientation is derived from the
+/// [MediaQueryData.size] of the ambient [MediaQuery]: wide sizes use the
+/// landscape orientation, and tall sizes use the portrait orientation. Use this
+/// parameter to override the default and force the dialog to appear in either
+/// portrait or landscape mode.
+///
 /// {@macro flutter.widgets.RawDialogRoute}
 ///
 /// By default, the time picker gets its colors from the overall theme's
 /// [ColorScheme]. The time picker can be further customized by providing a
 /// [TimePickerThemeData] to the overall theme.
 ///
-/// {@tool snippet}
-/// Show a dialog with the text direction overridden to be [TextDirection.rtl].
+/// {@tool snippet} Show a dialog with the text direction overridden to be
+/// [TextDirection.rtl].
 ///
 /// ```dart
 /// Future<TimeOfDay?> selectedTimeRTL = showTimePicker(
@@ -2462,8 +2983,8 @@
 /// ```
 /// {@end-tool}
 ///
-/// {@tool snippet}
-/// Show a dialog with time unconditionally displayed in 24 hour format.
+/// {@tool snippet} Show a dialog with time unconditionally displayed in 24 hour
+/// format.
 ///
 /// ```dart
 /// Future<TimeOfDay?> selectedTime24Hour = showTimePicker(
@@ -2479,14 +3000,21 @@
 /// ```
 /// {@end-tool}
 ///
+/// {@tool dartpad}
+/// This example illustrates how to open a time picker, and allows exploring
+/// some of the variations in the types of time pickers that may be shown.
+///
+/// ** See code in examples/api/lib/material/time_picker/show_time_picker.0.dart **
+/// {@end-tool}
+///
 /// See also:
 ///
-///  * [showDatePicker], which shows a dialog that contains a Material Design
-///    date picker.
-///  * [TimePickerThemeData], which allows you to customize the colors,
-///    typography, and shape of the time picker.
-///  * [DisplayFeatureSubScreen], which documents the specifics of how
-///    [DisplayFeature]s can split the screen into sub-screens.
+/// * [showDatePicker], which shows a dialog that contains a Material Design
+///   date picker.
+/// * [TimePickerThemeData], which allows you to customize the colors,
+///   typography, and shape of the time picker.
+/// * [DisplayFeatureSubScreen], which documents the specifics of how
+///   [DisplayFeature]s can split the screen into sub-screens.
 Future<TimeOfDay?> showTimePicker({
   required BuildContext context,
   required TimeOfDay initialTime,
@@ -2502,11 +3030,8 @@
   RouteSettings? routeSettings,
   EntryModeChangeCallback? onEntryModeChanged,
   Offset? anchorPoint,
+  Orientation? orientation,
 }) async {
-  assert(context != null);
-  assert(initialTime != null);
-  assert(useRootNavigator != null);
-  assert(initialEntryMode != null);
   assert(debugCheckHasMaterialLocalizations(context));
 
   final Widget dialog = TimePickerDialog(
@@ -2518,6 +3043,7 @@
     errorInvalidText: errorInvalidText,
     hourLabelText: hourLabelText,
     minuteLabelText: minuteLabelText,
+    orientation: orientation,
     onEntryModeChanged: onEntryModeChanged,
   );
   return showDialog<TimeOfDay>(
@@ -2534,3 +3060,661 @@
 void _announceToAccessibility(BuildContext context, String message) {
   SemanticsService.announce(message, Directionality.of(context));
 }
+
+// An abstract base class for the M2 and M3 defaults below, so that their return
+// types can be non-nullable.
+abstract class _TimePickerDefaults extends TimePickerThemeData {
+  @override
+  Color get backgroundColor;
+
+  @override
+  ButtonStyle get cancelButtonStyle;
+
+  @override
+  ButtonStyle get confirmButtonStyle;
+
+  @override
+  BorderSide get dayPeriodBorderSide;
+
+  @override
+  Color get dayPeriodColor;
+
+  @override
+  OutlinedBorder get dayPeriodShape;
+
+  Size get dayPeriodInputSize;
+  Size get dayPeriodLandscapeSize;
+  Size get dayPeriodPortraitSize;
+
+  @override
+  Color get dayPeriodTextColor;
+
+  @override
+  TextStyle get dayPeriodTextStyle;
+
+  @override
+  Color get dialBackgroundColor;
+
+  @override
+  Color get dialHandColor;
+
+  // Sizes that are generated from the tokens, but these aren't ones we're ready
+  // to expose in the theme.
+  Size get dialSize;
+  double get handWidth;
+  double get dotRadius;
+  double get centerRadius;
+
+  @override
+  Color get dialTextColor;
+
+  @override
+  TextStyle get dialTextStyle;
+
+  @override
+  double get elevation;
+
+  @override
+  Color get entryModeIconColor;
+
+  @override
+  TextStyle get helpTextStyle;
+
+  @override
+  Color get hourMinuteColor;
+
+  @override
+  ShapeBorder get hourMinuteShape;
+
+  Size get hourMinuteSize;
+  Size get hourMinuteSize24Hour;
+  Size get hourMinuteInputSize;
+  Size get hourMinuteInputSize24Hour;
+
+  @override
+  Color get hourMinuteTextColor;
+
+  @override
+  TextStyle get hourMinuteTextStyle;
+
+  @override
+  InputDecorationTheme get inputDecorationTheme;
+
+  @override
+  EdgeInsetsGeometry get padding;
+
+  @override
+  ShapeBorder get shape;
+}
+
+// These theme defaults are not auto-generated: they match the values for the
+// Material 2 spec, which are not expected to change.
+class _TimePickerDefaultsM2 extends _TimePickerDefaults {
+  _TimePickerDefaultsM2(this.context) : super();
+
+  final BuildContext context;
+
+  late final ColorScheme _colors = Theme.of(context).colorScheme;
+  late final TextTheme _textTheme = Theme.of(context).textTheme;
+  static const OutlinedBorder _kDefaultShape = RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(4)));
+
+  @override
+  Color get backgroundColor {
+    return _colors.surface;
+  }
+
+  @override
+  ButtonStyle get cancelButtonStyle {
+    return TextButton.styleFrom();
+  }
+
+  @override
+  ButtonStyle get confirmButtonStyle {
+    return TextButton.styleFrom();
+  }
+
+  @override
+  BorderSide get dayPeriodBorderSide {
+    return BorderSide(
+      color: Color.alphaBlend(_colors.onSurface.withOpacity(0.38), _colors.surface),
+    );
+  }
+
+  @override
+  Color get dayPeriodColor {
+    return MaterialStateColor.resolveWith((Set<MaterialState> states) {
+      if (states.contains(MaterialState.selected)) {
+        return _colors.primary.withOpacity(_colors.brightness == Brightness.dark ? 0.24 : 0.12);
+      }
+      // The unselected day period should match the overall picker dialog color.
+      // Making it transparent enables that without being redundant and allows
+      // the optional elevation overlay for dark mode to be visible.
+      return Colors.transparent;
+    });
+  }
+
+  @override
+  OutlinedBorder get dayPeriodShape {
+    return _kDefaultShape;
+  }
+
+  @override
+  Size get dayPeriodPortraitSize {
+    return const Size(52, 80);
+  }
+
+  @override
+  Size get dayPeriodLandscapeSize {
+    return const Size(0, 40);
+  }
+
+  @override
+  Size get dayPeriodInputSize {
+    return const Size(52, 70);
+  }
+
+  @override
+  Color get dayPeriodTextColor {
+    return  MaterialStateColor.resolveWith((Set<MaterialState> states) {
+      return states.contains(MaterialState.selected) ? _colors.primary : _colors.onSurface.withOpacity(0.60);
+    });
+  }
+
+  @override
+  TextStyle get dayPeriodTextStyle {
+    return _textTheme.titleMedium!.copyWith(color: dayPeriodTextColor);
+  }
+
+  @override
+  Color get dialBackgroundColor {
+    return _colors.onSurface.withOpacity(_colors.brightness == Brightness.dark ? 0.12 : 0.08);
+  }
+
+  @override
+  Color get dialHandColor {
+    return _colors.primary;
+  }
+
+  @override
+  Size get dialSize {
+    return const Size.square(280);
+  }
+
+  @override
+  double get handWidth {
+    return 2;
+  }
+
+  @override
+  double get dotRadius {
+    return 22;
+  }
+
+  @override
+  double get centerRadius {
+    return 4;
+  }
+
+  @override
+  Color get dialTextColor {
+    return MaterialStateColor.resolveWith((Set<MaterialState> states) {
+      if (states.contains(MaterialState.selected)) {
+        return _colors.surface;
+      }
+      return _colors.onSurface;
+    });
+  }
+
+  @override
+  TextStyle get dialTextStyle {
+    return _textTheme.bodyLarge!;
+  }
+
+  @override
+  double get elevation {
+    return 6;
+  }
+
+  @override
+  Color get entryModeIconColor {
+    return _colors.onSurface.withOpacity(_colors.brightness == Brightness.dark ? 1.0 : 0.6);
+  }
+
+  @override
+  TextStyle get helpTextStyle {
+    return _textTheme.labelSmall!;
+  }
+
+  @override
+  Color get hourMinuteColor {
+    return MaterialStateColor.resolveWith((Set<MaterialState> states) {
+      return states.contains(MaterialState.selected)
+          ? _colors.primary.withOpacity(_colors.brightness == Brightness.dark ? 0.24 : 0.12)
+          : _colors.onSurface.withOpacity(0.12);
+    });
+  }
+
+  @override
+  ShapeBorder get hourMinuteShape {
+    return _kDefaultShape;
+  }
+
+  @override
+  Size get hourMinuteSize {
+    return const Size(96, 80);
+  }
+
+  @override
+  Size get hourMinuteSize24Hour {
+    return const Size(114, 80);
+  }
+
+  @override
+  Size get hourMinuteInputSize {
+    return const Size(96, 70);
+  }
+
+  @override
+  Size get hourMinuteInputSize24Hour {
+    return const Size(114, 70);
+  }
+
+  @override
+  Color get hourMinuteTextColor {
+    return MaterialStateColor.resolveWith((Set<MaterialState> states) {
+      return states.contains(MaterialState.selected) ? _colors.primary : _colors.onSurface;
+    });
+  }
+
+  @override
+  TextStyle get hourMinuteTextStyle {
+    return _textTheme.displayMedium!;
+  }
+
+  Color get _hourMinuteInputColor {
+    return MaterialStateColor.resolveWith((Set<MaterialState> states) {
+      return states.contains(MaterialState.selected)
+          ? Colors.transparent
+          : _colors.onSurface.withOpacity(0.12);
+    });
+  }
+
+  @override
+  InputDecorationTheme get inputDecorationTheme {
+    return InputDecorationTheme(
+      contentPadding: EdgeInsets.zero,
+      filled: true,
+      fillColor: _hourMinuteInputColor,
+      focusColor: Colors.transparent,
+      enabledBorder: const OutlineInputBorder(
+        borderSide: BorderSide(color: Colors.transparent),
+      ),
+      errorBorder: OutlineInputBorder(
+        borderSide: BorderSide(color: _colors.error, width: 2),
+      ),
+      focusedBorder: OutlineInputBorder(
+        borderSide: BorderSide(color: _colors.primary, width: 2),
+      ),
+      focusedErrorBorder: OutlineInputBorder(
+        borderSide: BorderSide(color: _colors.error, width: 2),
+      ),
+      hintStyle: hourMinuteTextStyle.copyWith(color: _colors.onSurface.withOpacity(0.36)),
+      // Prevent the error text from appearing.
+      // TODO(rami-a): Remove this workaround once
+      // https://github.com/flutter/flutter/issues/54104
+      // is fixed.
+      errorStyle: const TextStyle(fontSize: 0, height: 0),
+    );
+  }
+
+  @override
+  EdgeInsetsGeometry get padding {
+    return const EdgeInsets.fromLTRB(8, 18, 8, 8);
+  }
+
+  @override
+  ShapeBorder get shape {
+    return _kDefaultShape;
+  }
+}
+
+// BEGIN GENERATED TOKEN PROPERTIES - TimePicker
+
+// Do not edit by hand. The code between the "BEGIN GENERATED" and
+// "END GENERATED" comments are generated from data in the Material
+// Design token database by the script:
+//   dev/tools/gen_defaults/bin/gen_defaults.dart.
+
+// Token database version: v0_158
+
+class _TimePickerDefaultsM3 extends _TimePickerDefaults {
+  _TimePickerDefaultsM3(this.context);
+
+  final BuildContext context;
+
+  late final ColorScheme _colors = Theme.of(context).colorScheme;
+  late final TextTheme _textTheme = Theme.of(context).textTheme;
+
+  @override
+  Color get backgroundColor {
+    return _colors.surface;
+  }
+
+  @override
+  ButtonStyle get cancelButtonStyle {
+    return TextButton.styleFrom();
+  }
+
+  @override
+  ButtonStyle get confirmButtonStyle {
+    return TextButton.styleFrom();
+  }
+
+  @override
+  BorderSide get dayPeriodBorderSide {
+    return BorderSide(color: _colors.outline);
+  }
+
+  @override
+  Color get dayPeriodColor {
+    return MaterialStateColor.resolveWith((Set<MaterialState> states) {
+      if (states.contains(MaterialState.selected)) {
+        return _colors.tertiaryContainer;
+      }
+      // The unselected day period should match the overall picker dialog color.
+      // Making it transparent enables that without being redundant and allows
+      // the optional elevation overlay for dark mode to be visible.
+      return Colors.transparent;
+    });
+  }
+
+  @override
+  OutlinedBorder get dayPeriodShape {
+    return const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(8.0))).copyWith(side: dayPeriodBorderSide);
+  }
+
+  @override
+  Size get dayPeriodPortraitSize {
+    return const Size(52, 80);
+  }
+
+  @override
+  Size get dayPeriodLandscapeSize {
+    return const Size(216, 38);
+  }
+
+  @override
+  Size get dayPeriodInputSize {
+    // Input size is eight pixels smaller than the portrait size in the spec,
+    // but there's not token for it yet.
+    return Size(dayPeriodPortraitSize.width, dayPeriodPortraitSize.height - 8);
+  }
+
+  @override
+  Color get dayPeriodTextColor {
+    return MaterialStateColor.resolveWith((Set<MaterialState> states) {
+      return _dayPeriodForegroundColor.resolve(states);
+    });
+  }
+
+  MaterialStateProperty<Color> get _dayPeriodForegroundColor {
+    return MaterialStateProperty.resolveWith((Set<MaterialState> states) {
+      Color? textColor;
+      if (states.contains(MaterialState.selected)) {
+        if (states.contains(MaterialState.pressed)) {
+          textColor = _colors.onTertiaryContainer;
+        } else {
+          // not pressed
+          if (states.contains(MaterialState.focused)) {
+            textColor = _colors.onTertiaryContainer;
+          } else {
+            // not focused
+            if (states.contains(MaterialState.hovered)) {
+              textColor = _colors.onTertiaryContainer;
+            }
+          }
+        }
+      } else {
+        // unselected
+        if (states.contains(MaterialState.pressed)) {
+          textColor = _colors.onSurfaceVariant;
+        } else {
+          // not pressed
+          if (states.contains(MaterialState.focused)) {
+            textColor = _colors.onSurfaceVariant;
+          } else {
+            // not focused
+            if (states.contains(MaterialState.hovered)) {
+              textColor = _colors.onSurfaceVariant;
+            }
+          }
+        }
+      }
+      return textColor ?? _colors.onTertiaryContainer;
+    });
+  }
+
+  @override
+  TextStyle get dayPeriodTextStyle {
+    return _textTheme.titleMedium!.copyWith(color: dayPeriodTextColor);
+  }
+
+  @override
+  Color get dialBackgroundColor {
+    return _colors.surfaceVariant.withOpacity(_colors.brightness == Brightness.dark ? 0.12 : 0.08);
+  }
+
+  @override
+  Color get dialHandColor {
+    return _colors.primary;
+  }
+
+  @override
+  Size get dialSize {
+    return const Size.square(256.0);
+  }
+
+  @override
+  double get handWidth {
+    return const Size(2, double.infinity).width;
+  }
+
+  @override
+  double get dotRadius {
+    return const Size.square(48.0).width / 2;
+  }
+
+  @override
+  double get centerRadius {
+    return const Size.square(8.0).width / 2;
+  }
+
+  @override
+  Color get dialTextColor {
+    return MaterialStateColor.resolveWith((Set<MaterialState> states) {
+      if (states.contains(MaterialState.selected)) {
+        return _colors.onPrimary;
+      }
+      return _colors.onSurface;
+    });
+  }
+
+  @override
+  TextStyle get dialTextStyle {
+    return _textTheme.bodyLarge!;
+  }
+
+  @override
+  double get elevation {
+    return 6.0;
+  }
+
+  @override
+  Color get entryModeIconColor {
+    return _colors.onSurface;
+  }
+
+  @override
+  TextStyle get helpTextStyle {
+    return MaterialStateTextStyle.resolveWith((Set<MaterialState> states) {
+      final TextStyle textStyle = _textTheme.labelMedium!;
+      return textStyle.copyWith(color: _colors.onSurfaceVariant);
+    });
+  }
+
+  @override
+  EdgeInsetsGeometry get padding {
+    return const EdgeInsets.all(24);
+  }
+
+  @override
+  Color get hourMinuteColor {
+    return MaterialStateColor.resolveWith((Set<MaterialState> states) {
+      if (states.contains(MaterialState.selected)) {
+        Color overlayColor = _colors.primaryContainer;
+        if (states.contains(MaterialState.pressed)) {
+          overlayColor = _colors.onPrimaryContainer;
+        } else if (states.contains(MaterialState.focused)) {
+          const double focusOpacity = 0.12;
+          overlayColor = _colors.onPrimaryContainer.withOpacity(focusOpacity);
+        } else if (states.contains(MaterialState.hovered)) {
+          const double hoverOpacity = 0.08;
+          overlayColor = _colors.onPrimaryContainer.withOpacity(hoverOpacity);
+        }
+        return Color.alphaBlend(overlayColor, _colors.primaryContainer);
+      } else {
+        Color overlayColor = _colors.surfaceVariant;
+        if (states.contains(MaterialState.pressed)) {
+          overlayColor = _colors.onSurface;
+        } else if (states.contains(MaterialState.focused)) {
+          const double focusOpacity = 0.12;
+          overlayColor = _colors.onSurface.withOpacity(focusOpacity);
+        } else if (states.contains(MaterialState.hovered)) {
+          const double hoverOpacity = 0.08;
+          overlayColor = _colors.onSurface.withOpacity(hoverOpacity);
+        }
+        return Color.alphaBlend(overlayColor, _colors.surfaceVariant);
+      }
+    });
+  }
+
+  @override
+  ShapeBorder get hourMinuteShape {
+    return const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(8.0)));
+  }
+
+  @override
+  Size get hourMinuteSize {
+    return const Size(96, 80);
+  }
+
+  @override
+  Size get hourMinuteSize24Hour {
+    return Size(const Size(114, double.infinity).width, hourMinuteSize.height);
+  }
+
+  @override
+  Size get hourMinuteInputSize {
+    // Input size is eight pixels smaller than the regular size in the spec, but
+    // there's not token for it yet.
+    return Size(hourMinuteSize.width, hourMinuteSize.height - 8);
+  }
+
+  @override
+  Size get hourMinuteInputSize24Hour {
+    // Input size is eight pixels smaller than the regular size in the spec, but
+    // there's not token for it yet.
+    return Size(hourMinuteSize24Hour.width, hourMinuteSize24Hour.height - 8);
+  }
+
+  @override
+  Color get hourMinuteTextColor {
+    return MaterialStateColor.resolveWith((Set<MaterialState> states) {
+      return _hourMinuteTextColor.resolve(states);
+    });
+  }
+
+  MaterialStateProperty<Color> get _hourMinuteTextColor {
+    return MaterialStateProperty.resolveWith((Set<MaterialState> states) {
+      if (states.contains(MaterialState.selected)) {
+        if (states.contains(MaterialState.pressed)) {
+          return _colors.onPrimaryContainer;
+        }
+        if (states.contains(MaterialState.focused)) {
+          return _colors.onPrimaryContainer;
+        }
+        if (states.contains(MaterialState.hovered)) {
+          return _colors.onPrimaryContainer;
+        }
+        return _colors.onPrimaryContainer;
+      } else {
+        // unselected
+        if (states.contains(MaterialState.pressed)) {
+          return _colors.onSurface;
+        }
+        if (states.contains(MaterialState.focused)) {
+          return _colors.onSurface;
+        }
+        if (states.contains(MaterialState.hovered)) {
+          return _colors.onSurface;
+        }
+        return _colors.onSurface;
+      }
+    });
+  }
+
+  @override
+  TextStyle get hourMinuteTextStyle {
+    return MaterialStateTextStyle.resolveWith((Set<MaterialState> states) {
+      return _textTheme.displayLarge!.copyWith(color: _hourMinuteTextColor.resolve(states));
+    });
+  }
+
+  @override
+  InputDecorationTheme get inputDecorationTheme {
+    // This is NOT correct, but there's no token for
+    // 'time-input.container.shape', so this is using the radius from the shape
+    // for the hour/minute selector. It's a BorderRadiusGeometry, so we have to
+    // resolve it before we can use it.
+    final BorderRadius selectorRadius = const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(8.0)))
+      .borderRadius
+      .resolve(Directionality.of(context));
+    return InputDecorationTheme(
+      contentPadding: EdgeInsets.zero,
+      filled: true,
+      // This should be derived from a token, but there isn't one for 'time-input'.
+      fillColor: hourMinuteColor,
+      // This should be derived from a token, but there isn't one for 'time-input'.
+      focusColor: _colors.primaryContainer,
+      enabledBorder: OutlineInputBorder(
+        borderRadius: selectorRadius,
+        borderSide: const BorderSide(color: Colors.transparent),
+      ),
+      errorBorder: OutlineInputBorder(
+        borderRadius: selectorRadius,
+        borderSide: BorderSide(color: _colors.error, width: 2),
+      ),
+      focusedBorder: OutlineInputBorder(
+        borderRadius: selectorRadius,
+        borderSide: BorderSide(color: _colors.primary, width: 2),
+      ),
+      focusedErrorBorder: OutlineInputBorder(
+        borderRadius: selectorRadius,
+        borderSide: BorderSide(color: _colors.error, width: 2),
+      ),
+      hintStyle: hourMinuteTextStyle.copyWith(color: _colors.onSurface.withOpacity(0.36)),
+      // Prevent the error text from appearing.
+      // TODO(rami-a): Remove this workaround once
+      // https://github.com/flutter/flutter/issues/54104
+      // is fixed.
+      errorStyle: const TextStyle(fontSize: 0, height: 0),
+    );
+  }
+
+  @override
+  ShapeBorder get shape {
+    return const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(28.0)));
+  }
+}
+
+// END GENERATED TOKEN PROPERTIES - TimePicker
diff --git a/framework/lib/src/material/time_picker_theme.dart b/framework/lib/src/material/time_picker_theme.dart
index 743d68c..e2312b8 100644
--- a/framework/lib/src/material/time_picker_theme.dart
+++ b/framework/lib/src/material/time_picker_theme.dart
@@ -2,10 +2,14 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import 'package:engine/ui.dart' hide TextStyle;
+
 import 'package:flute/foundation.dart';
 import 'package:flute/widgets.dart';
 
+import 'button_style.dart';
 import 'input_decorator.dart';
+import 'material_state.dart';
 import 'theme.dart';
 
 // Examples can assume:
@@ -36,22 +40,27 @@
   /// [ThemeData.timePickerTheme].
   const TimePickerThemeData({
     this.backgroundColor,
-    this.hourMinuteTextColor,
-    this.hourMinuteColor,
-    this.dayPeriodTextColor,
-    this.dayPeriodColor,
-    this.dialHandColor,
-    this.dialBackgroundColor,
-    this.dialTextColor,
-    this.entryModeIconColor,
-    this.hourMinuteTextStyle,
-    this.dayPeriodTextStyle,
-    this.helpTextStyle,
-    this.shape,
-    this.hourMinuteShape,
-    this.dayPeriodShape,
+    this.cancelButtonStyle,
+    this.confirmButtonStyle,
     this.dayPeriodBorderSide,
+    this.dayPeriodColor,
+    this.dayPeriodShape,
+    this.dayPeriodTextColor,
+    this.dayPeriodTextStyle,
+    this.dialBackgroundColor,
+    this.dialHandColor,
+    this.dialTextColor,
+    this.dialTextStyle,
+    this.elevation,
+    this.entryModeIconColor,
+    this.helpTextStyle,
+    this.hourMinuteColor,
+    this.hourMinuteShape,
+    this.hourMinuteTextColor,
+    this.hourMinuteTextStyle,
     this.inputDecorationTheme,
+    this.padding,
+    this.shape,
   });
 
   /// The background color of a time picker.
@@ -60,41 +69,25 @@
   /// [ColorScheme.background].
   final Color? backgroundColor;
 
-  /// The color of the header text that represents hours and minutes.
-  ///
-  /// If [hourMinuteTextColor] is a [MaterialStateColor], then the effective
-  /// text color can depend on the [MaterialState.selected] state, i.e. if the
-  /// text is selected or not.
-  ///
-  /// By default the overall theme's [ColorScheme.primary] color is used when
-  /// the text is selected and [ColorScheme.onSurface] when it's not selected.
-  final Color? hourMinuteTextColor;
+  /// The style of the cancel button of a [TimePickerDialog].
+  final ButtonStyle? cancelButtonStyle;
 
-  /// The background color of the hour and minutes header segments.
-  ///
-  /// If [hourMinuteColor] is a [MaterialStateColor], then the effective
-  /// background color can depend on the [MaterialState.selected] state, i.e.
-  /// if the segment is selected or not.
-  ///
-  /// By default, if the segment is selected, the overall theme's
-  /// `ColorScheme.primary.withOpacity(0.12)` is used when the overall theme's
-  /// brightness is [Brightness.light] and
-  /// `ColorScheme.primary.withOpacity(0.24)` is used when the overall theme's
-  /// brightness is [Brightness.dark].
-  /// If the segment is not selected, the overall theme's
-  /// `ColorScheme.onSurface.withOpacity(0.12)` is used.
-  final Color? hourMinuteColor;
+  /// The style of the conform (OK) button of a [TimePickerDialog].
+  final ButtonStyle? confirmButtonStyle;
 
-  /// The color of the day period text that represents AM/PM.
+  /// The color and weight of the day period's outline.
   ///
-  /// If [dayPeriodTextColor] is a [MaterialStateColor], then the effective
-  /// text color can depend on the [MaterialState.selected] state, i.e. if the
-  /// text is selected or not.
+  /// If this is null, the time picker defaults to:
   ///
-  /// By default the overall theme's [ColorScheme.primary] color is used when
-  /// the text is selected and `ColorScheme.onSurface.withOpacity(0.60)` when
-  /// it's not selected.
-  final Color? dayPeriodTextColor;
+  /// ```dart
+  /// BorderSide(
+  ///   color: Color.alphaBlend(
+  ///     Theme.of(context).colorScheme.onBackground.withOpacity(0.38),
+  ///     Theme.of(context).colorScheme.surface,
+  ///   ),
+  /// ),
+  /// ```
+  final BorderSide? dayPeriodBorderSide;
 
   /// The background color of the AM/PM toggle.
   ///
@@ -111,69 +104,6 @@
   /// [Dialog]'s color to be used.
   final Color? dayPeriodColor;
 
-  /// The color of the time picker dial's hand.
-  ///
-  /// If this is null, the time picker defaults to the overall theme's
-  /// [ColorScheme.primary].
-  final Color? dialHandColor;
-
-  /// The background color of the time picker dial.
-  ///
-  /// If this is null, the time picker defaults to the overall theme's
-  /// [ColorScheme.primary].
-  final Color? dialBackgroundColor;
-
-  /// The color of the dial text that represents specific hours and minutes.
-  ///
-  /// If [dialTextColor] is a [MaterialStateColor], then the effective
-  /// text color can depend on the [MaterialState.selected] state, i.e. if the
-  /// text is selected or not.
-  ///
-  /// If this color is null then the dial's text colors are based on the
-  /// theme's [ThemeData.colorScheme].
-  final Color? dialTextColor;
-
-  /// The color of the entry mode [IconButton].
-  ///
-  /// If this is null, the time picker defaults to:
-  ///
-  /// ```dart
-  /// Theme.of(context).colorScheme.onSurface.withOpacity(
-  ///   Theme.of(context).colorScheme.brightness == Brightness.dark ? 1.0 : 0.6,
-  /// )
-  /// ```
-  final Color? entryModeIconColor;
-
-  /// Used to configure the [TextStyle]s for the hour/minute controls.
-  ///
-  /// If this is null, the time picker defaults to the overall theme's
-  /// [TextTheme.headline3].
-  final TextStyle? hourMinuteTextStyle;
-
-  /// Used to configure the [TextStyle]s for the day period control.
-  ///
-  /// If this is null, the time picker defaults to the overall theme's
-  /// [TextTheme.titleMedium].
-  final TextStyle? dayPeriodTextStyle;
-
-  /// Used to configure the [TextStyle]s for the helper text in the header.
-  ///
-  /// If this is null, the time picker defaults to the overall theme's
-  /// [TextTheme.labelSmall].
-  final TextStyle? helpTextStyle;
-
-  /// The shape of the [Dialog] that the time picker is presented in.
-  ///
-  /// If this is null, the time picker defaults to
-  /// `RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(4.0)))`.
-  final ShapeBorder? shape;
-
-  /// The shape of the hour and minute controls that the time picker uses.
-  ///
-  /// If this is null, the time picker defaults to
-  /// `RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(4.0)))`.
-  final ShapeBorder? hourMinuteShape;
-
   /// The shape of the day period that the time picker uses.
   ///
   /// If this is null, the time picker defaults to:
@@ -186,64 +116,180 @@
   /// ```
   final OutlinedBorder? dayPeriodShape;
 
-  /// The color and weight of the day period's outline.
+  /// The color of the day period text that represents AM/PM.
+  ///
+  /// If [dayPeriodTextColor] is a [MaterialStateColor], then the effective
+  /// text color can depend on the [MaterialState.selected] state, i.e. if the
+  /// text is selected or not.
+  ///
+  /// By default the overall theme's [ColorScheme.primary] color is used when
+  /// the text is selected and `ColorScheme.onSurface.withOpacity(0.60)` when
+  /// it's not selected.
+  final Color? dayPeriodTextColor;
+
+  /// Used to configure the [TextStyle]s for the day period control.
+  ///
+  /// If this is null, the time picker defaults to the overall theme's
+  /// [TextTheme.titleMedium].
+  final TextStyle? dayPeriodTextStyle;
+
+  /// The background color of the time picker dial when the entry mode is
+  /// [TimePickerEntryMode.dial] or [TimePickerEntryMode.dialOnly].
+  ///
+  /// If this is null, the time picker defaults to the overall theme's
+  /// [ColorScheme.primary].
+  final Color? dialBackgroundColor;
+
+  /// The color of the time picker dial's hand when the entry mode is
+  /// [TimePickerEntryMode.dial] or [TimePickerEntryMode.dialOnly].
+  ///
+  /// If this is null, the time picker defaults to the overall theme's
+  /// [ColorScheme.primary].
+  final Color? dialHandColor;
+
+  /// The color of the dial text that represents specific hours and minutes.
+  ///
+  /// If [dialTextColor] is a [MaterialStateColor], then the effective
+  /// text color can depend on the [MaterialState.selected] state, i.e. if the
+  /// text is selected or not.
+  ///
+  /// If this color is null then the dial's text colors are based on the
+  /// theme's [ThemeData.colorScheme].
+  final Color? dialTextColor;
+
+  /// The [TextStyle] for the numbers on the time selection dial.
+  ///
+  /// If [dialTextStyle]'s [TextStyle.color] is a [MaterialStateColor], then the
+  /// effective text color can depend on the [MaterialState.selected] state,
+  /// i.e. if the text is selected or not.
+  ///
+  /// If this style is null then the dial's text style is based on the theme's
+  /// [ThemeData.textTheme].
+  final TextStyle? dialTextStyle;
+
+  /// The Material elevation for the time picker dialog.
+  final double? elevation;
+
+  /// The color of the entry mode [IconButton].
   ///
   /// If this is null, the time picker defaults to:
   ///
+  ///
   /// ```dart
-  /// BorderSide(
-  ///   color: Color.alphaBlend(
-  ///     Theme.of(context).colorScheme.onBackground.withOpacity(0.38),
-  ///     Theme.of(context).colorScheme.surface,
-  ///   ),
-  /// ),
+  /// Theme.of(context).colorScheme.onSurface.withOpacity(
+  ///   Theme.of(context).colorScheme.brightness == Brightness.dark ? 1.0 : 0.6,
+  /// )
   /// ```
-  final BorderSide? dayPeriodBorderSide;
+  final Color? entryModeIconColor;
+
+  /// Used to configure the [TextStyle]s for the helper text in the header.
+  ///
+  /// If this is null, the time picker defaults to the overall theme's
+  /// [TextTheme.labelSmall].
+  final TextStyle? helpTextStyle;
+
+  /// The background color of the hour and minute header segments.
+  ///
+  /// If [hourMinuteColor] is a [MaterialStateColor], then the effective
+  /// background color can depend on the [MaterialState.selected] state, i.e.
+  /// if the segment is selected or not.
+  ///
+  /// By default, if the segment is selected, the overall theme's
+  /// `ColorScheme.primary.withOpacity(0.12)` is used when the overall theme's
+  /// brightness is [Brightness.light] and
+  /// `ColorScheme.primary.withOpacity(0.24)` is used when the overall theme's
+  /// brightness is [Brightness.dark].
+  /// If the segment is not selected, the overall theme's
+  /// `ColorScheme.onSurface.withOpacity(0.12)` is used.
+  final Color? hourMinuteColor;
+
+  /// The shape of the hour and minute controls that the time picker uses.
+  ///
+  /// If this is null, the time picker defaults to
+  /// `RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(4.0)))`.
+  final ShapeBorder? hourMinuteShape;
+
+  /// The color of the header text that represents hours and minutes.
+  ///
+  /// If [hourMinuteTextColor] is a [MaterialStateColor], then the effective
+  /// text color can depend on the [MaterialState.selected] state, i.e. if the
+  /// text is selected or not.
+  ///
+  /// By default the overall theme's [ColorScheme.primary] color is used when
+  /// the text is selected and [ColorScheme.onSurface] when it's not selected.
+  final Color? hourMinuteTextColor;
+
+  /// Used to configure the [TextStyle]s for the hour/minute controls.
+  ///
+  /// If this is null, the time picker defaults to the overall theme's
+  /// [TextTheme.headline3].
+  final TextStyle? hourMinuteTextStyle;
 
   /// The input decoration theme for the [TextField]s in the time picker.
   ///
   /// If this is null, the time picker provides its own defaults.
   final InputDecorationTheme? inputDecorationTheme;
 
+  /// The padding around the time picker dialog when the entry mode is
+  /// [TimePickerEntryMode.dial] or [TimePickerEntryMode.dialOnly].
+  final EdgeInsetsGeometry? padding;
+
+  /// The shape of the [Dialog] that the time picker is presented in.
+  ///
+  /// If this is null, the time picker defaults to
+  /// `RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(4.0)))`.
+  final ShapeBorder? shape;
+
   /// Creates a copy of this object with the given fields replaced with the
   /// new values.
   TimePickerThemeData copyWith({
     Color? backgroundColor,
-    Color? hourMinuteTextColor,
-    Color? hourMinuteColor,
-    Color? dayPeriodTextColor,
-    Color? dayPeriodColor,
-    Color? dialHandColor,
-    Color? dialBackgroundColor,
-    Color? dialTextColor,
-    Color? entryModeIconColor,
-    TextStyle? hourMinuteTextStyle,
-    TextStyle? dayPeriodTextStyle,
-    TextStyle? helpTextStyle,
-    ShapeBorder? shape,
-    ShapeBorder? hourMinuteShape,
-    OutlinedBorder? dayPeriodShape,
+    ButtonStyle? cancelButtonStyle,
+    ButtonStyle? confirmButtonStyle,
+    ButtonStyle? dayPeriodButtonStyle,
     BorderSide? dayPeriodBorderSide,
+    Color? dayPeriodColor,
+    OutlinedBorder? dayPeriodShape,
+    Color? dayPeriodTextColor,
+    TextStyle? dayPeriodTextStyle,
+    Color? dialBackgroundColor,
+    Color? dialHandColor,
+    Color? dialTextColor,
+    TextStyle? dialTextStyle,
+    double? elevation,
+    Color? entryModeIconColor,
+    TextStyle? helpTextStyle,
+    Color? hourMinuteColor,
+    ShapeBorder? hourMinuteShape,
+    Color? hourMinuteTextColor,
+    TextStyle? hourMinuteTextStyle,
     InputDecorationTheme? inputDecorationTheme,
+    EdgeInsetsGeometry? padding,
+    ShapeBorder? shape,
   }) {
     return TimePickerThemeData(
       backgroundColor: backgroundColor ?? this.backgroundColor,
-      hourMinuteTextColor: hourMinuteTextColor ?? this.hourMinuteTextColor,
-      hourMinuteColor: hourMinuteColor ?? this.hourMinuteColor,
-      dayPeriodTextColor: dayPeriodTextColor ?? this.dayPeriodTextColor,
-      dayPeriodColor: dayPeriodColor ?? this.dayPeriodColor,
-      dialHandColor: dialHandColor ?? this.dialHandColor,
-      dialBackgroundColor: dialBackgroundColor ?? this.dialBackgroundColor,
-      dialTextColor: dialTextColor ?? this.dialTextColor,
-      entryModeIconColor: entryModeIconColor ?? this.entryModeIconColor,
-      hourMinuteTextStyle: hourMinuteTextStyle ?? this.hourMinuteTextStyle,
-      dayPeriodTextStyle: dayPeriodTextStyle ?? this.dayPeriodTextStyle,
-      helpTextStyle: helpTextStyle ?? this.helpTextStyle,
-      shape: shape ?? this.shape,
-      hourMinuteShape: hourMinuteShape ?? this.hourMinuteShape,
-      dayPeriodShape: dayPeriodShape ?? this.dayPeriodShape,
+      cancelButtonStyle: cancelButtonStyle ?? this.cancelButtonStyle,
+      confirmButtonStyle: confirmButtonStyle ?? this.confirmButtonStyle,
       dayPeriodBorderSide: dayPeriodBorderSide ?? this.dayPeriodBorderSide,
+      dayPeriodColor: dayPeriodColor ?? this.dayPeriodColor,
+      dayPeriodShape: dayPeriodShape ?? this.dayPeriodShape,
+      dayPeriodTextColor: dayPeriodTextColor ?? this.dayPeriodTextColor,
+      dayPeriodTextStyle: dayPeriodTextStyle ?? this.dayPeriodTextStyle,
+      dialBackgroundColor: dialBackgroundColor ?? this.dialBackgroundColor,
+      dialHandColor: dialHandColor ?? this.dialHandColor,
+      dialTextColor: dialTextColor ?? this.dialTextColor,
+      dialTextStyle: dialTextStyle ?? this.dialTextStyle,
+      elevation: elevation ?? this.elevation,
+      entryModeIconColor: entryModeIconColor ?? this.entryModeIconColor,
+      helpTextStyle: helpTextStyle ?? this.helpTextStyle,
+      hourMinuteColor: hourMinuteColor ?? this.hourMinuteColor,
+      hourMinuteShape: hourMinuteShape ?? this.hourMinuteShape,
+      hourMinuteTextColor: hourMinuteTextColor ?? this.hourMinuteTextColor,
+      hourMinuteTextStyle: hourMinuteTextStyle ?? this.hourMinuteTextStyle,
       inputDecorationTheme: inputDecorationTheme ?? this.inputDecorationTheme,
+      padding: padding ?? this.padding,
+      shape: shape ?? this.shape,
     );
   }
 
@@ -253,7 +299,6 @@
   ///
   /// {@macro dart.ui.shadow.lerp}
   static TimePickerThemeData lerp(TimePickerThemeData? a, TimePickerThemeData? b, double t) {
-    assert(t != null);
 
     // Workaround since BorderSide's lerp does not allow for null arguments.
     BorderSide? lerpedBorderSide;
@@ -268,45 +313,55 @@
     }
     return TimePickerThemeData(
       backgroundColor: Color.lerp(a?.backgroundColor, b?.backgroundColor, t),
-      hourMinuteTextColor: Color.lerp(a?.hourMinuteTextColor, b?.hourMinuteTextColor, t),
-      hourMinuteColor: Color.lerp(a?.hourMinuteColor, b?.hourMinuteColor, t),
-      dayPeriodTextColor: Color.lerp(a?.dayPeriodTextColor, b?.dayPeriodTextColor, t),
-      dayPeriodColor: Color.lerp(a?.dayPeriodColor, b?.dayPeriodColor, t),
-      dialHandColor: Color.lerp(a?.dialHandColor, b?.dialHandColor, t),
-      dialBackgroundColor: Color.lerp(a?.dialBackgroundColor, b?.dialBackgroundColor, t),
-      dialTextColor: Color.lerp(a?.dialTextColor, b?.dialTextColor, t),
-      entryModeIconColor: Color.lerp(a?.entryModeIconColor, b?.entryModeIconColor, t),
-      hourMinuteTextStyle: TextStyle.lerp(a?.hourMinuteTextStyle, b?.hourMinuteTextStyle, t),
-      dayPeriodTextStyle: TextStyle.lerp(a?.dayPeriodTextStyle, b?.dayPeriodTextStyle, t),
-      helpTextStyle: TextStyle.lerp(a?.helpTextStyle, b?.helpTextStyle, t),
-      shape: ShapeBorder.lerp(a?.shape, b?.shape, t),
-      hourMinuteShape: ShapeBorder.lerp(a?.hourMinuteShape, b?.hourMinuteShape, t),
-      dayPeriodShape: ShapeBorder.lerp(a?.dayPeriodShape, b?.dayPeriodShape, t) as OutlinedBorder?,
+      cancelButtonStyle: ButtonStyle.lerp(a?.cancelButtonStyle, b?.cancelButtonStyle, t),
+      confirmButtonStyle: ButtonStyle.lerp(a?.confirmButtonStyle, b?.confirmButtonStyle, t),
       dayPeriodBorderSide: lerpedBorderSide,
+      dayPeriodColor: Color.lerp(a?.dayPeriodColor, b?.dayPeriodColor, t),
+      dayPeriodShape: ShapeBorder.lerp(a?.dayPeriodShape, b?.dayPeriodShape, t) as OutlinedBorder?,
+      dayPeriodTextColor: Color.lerp(a?.dayPeriodTextColor, b?.dayPeriodTextColor, t),
+      dayPeriodTextStyle: TextStyle.lerp(a?.dayPeriodTextStyle, b?.dayPeriodTextStyle, t),
+      dialBackgroundColor: Color.lerp(a?.dialBackgroundColor, b?.dialBackgroundColor, t),
+      dialHandColor: Color.lerp(a?.dialHandColor, b?.dialHandColor, t),
+      dialTextColor: Color.lerp(a?.dialTextColor, b?.dialTextColor, t),
+      dialTextStyle: TextStyle.lerp(a?.dialTextStyle, b?.dialTextStyle, t),
+      elevation: lerpDouble(a?.elevation, b?.elevation, t),
+      entryModeIconColor: Color.lerp(a?.entryModeIconColor, b?.entryModeIconColor, t),
+      helpTextStyle: TextStyle.lerp(a?.helpTextStyle, b?.helpTextStyle, t),
+      hourMinuteColor: Color.lerp(a?.hourMinuteColor, b?.hourMinuteColor, t),
+      hourMinuteShape: ShapeBorder.lerp(a?.hourMinuteShape, b?.hourMinuteShape, t),
+      hourMinuteTextColor: Color.lerp(a?.hourMinuteTextColor, b?.hourMinuteTextColor, t),
+      hourMinuteTextStyle: TextStyle.lerp(a?.hourMinuteTextStyle, b?.hourMinuteTextStyle, t),
       inputDecorationTheme: t < 0.5 ? a?.inputDecorationTheme : b?.inputDecorationTheme,
+      padding: EdgeInsetsGeometry.lerp(a?.padding, b?.padding, t),
+      shape: ShapeBorder.lerp(a?.shape, b?.shape, t),
     );
   }
 
   @override
-  int get hashCode => Object.hash(
+  int get hashCode => Object.hashAll(<Object?>[
     backgroundColor,
-    hourMinuteTextColor,
-    hourMinuteColor,
-    dayPeriodTextColor,
-    dayPeriodColor,
-    dialHandColor,
-    dialBackgroundColor,
-    dialTextColor,
-    entryModeIconColor,
-    hourMinuteTextStyle,
-    dayPeriodTextStyle,
-    helpTextStyle,
-    shape,
-    hourMinuteShape,
-    dayPeriodShape,
+    cancelButtonStyle,
+    confirmButtonStyle,
     dayPeriodBorderSide,
+    dayPeriodColor,
+    dayPeriodShape,
+    dayPeriodTextColor,
+    dayPeriodTextStyle,
+    dialBackgroundColor,
+    dialHandColor,
+    dialTextColor,
+    dialTextStyle,
+    elevation,
+    entryModeIconColor,
+    helpTextStyle,
+    hourMinuteColor,
+    hourMinuteShape,
+    hourMinuteTextColor,
+    hourMinuteTextStyle,
     inputDecorationTheme,
-  );
+    padding,
+    shape,
+  ]);
 
   @override
   bool operator ==(Object other) {
@@ -318,44 +373,54 @@
     }
     return other is TimePickerThemeData
         && other.backgroundColor == backgroundColor
-        && other.hourMinuteTextColor == hourMinuteTextColor
-        && other.hourMinuteColor == hourMinuteColor
-        && other.dayPeriodTextColor == dayPeriodTextColor
-        && other.dayPeriodColor == dayPeriodColor
-        && other.dialHandColor == dialHandColor
-        && other.dialBackgroundColor == dialBackgroundColor
-        && other.dialTextColor == dialTextColor
-        && other.entryModeIconColor == entryModeIconColor
-        && other.hourMinuteTextStyle == hourMinuteTextStyle
-        && other.dayPeriodTextStyle == dayPeriodTextStyle
-        && other.helpTextStyle == helpTextStyle
-        && other.shape == shape
-        && other.hourMinuteShape == hourMinuteShape
-        && other.dayPeriodShape == dayPeriodShape
+        && other.cancelButtonStyle == cancelButtonStyle
+        && other.confirmButtonStyle == confirmButtonStyle
         && other.dayPeriodBorderSide == dayPeriodBorderSide
-        && other.inputDecorationTheme == inputDecorationTheme;
+        && other.dayPeriodColor == dayPeriodColor
+        && other.dayPeriodShape == dayPeriodShape
+        && other.dayPeriodTextColor == dayPeriodTextColor
+        && other.dayPeriodTextStyle == dayPeriodTextStyle
+        && other.dialBackgroundColor == dialBackgroundColor
+        && other.dialHandColor == dialHandColor
+        && other.dialTextColor == dialTextColor
+        && other.dialTextStyle == dialTextStyle
+        && other.elevation == elevation
+        && other.entryModeIconColor == entryModeIconColor
+        && other.helpTextStyle == helpTextStyle
+        && other.hourMinuteColor == hourMinuteColor
+        && other.hourMinuteShape == hourMinuteShape
+        && other.hourMinuteTextColor == hourMinuteTextColor
+        && other.hourMinuteTextStyle == hourMinuteTextStyle
+        && other.inputDecorationTheme == inputDecorationTheme
+        && other.padding == padding
+        && other.shape == shape;
   }
 
   @override
   void debugFillProperties(DiagnosticPropertiesBuilder properties) {
     super.debugFillProperties(properties);
     properties.add(ColorProperty('backgroundColor', backgroundColor, defaultValue: null));
-    properties.add(ColorProperty('hourMinuteTextColor', hourMinuteTextColor, defaultValue: null));
-    properties.add(ColorProperty('hourMinuteColor', hourMinuteColor, defaultValue: null));
-    properties.add(ColorProperty('dayPeriodTextColor', dayPeriodTextColor, defaultValue: null));
-    properties.add(ColorProperty('dayPeriodColor', dayPeriodColor, defaultValue: null));
-    properties.add(ColorProperty('dialHandColor', dialHandColor, defaultValue: null));
-    properties.add(ColorProperty('dialBackgroundColor', dialBackgroundColor, defaultValue: null));
-    properties.add(ColorProperty('dialTextColor', dialTextColor, defaultValue: null));
-    properties.add(ColorProperty('entryModeIconColor', entryModeIconColor, defaultValue: null));
-    properties.add(DiagnosticsProperty<TextStyle>('hourMinuteTextStyle', hourMinuteTextStyle, defaultValue: null));
-    properties.add(DiagnosticsProperty<TextStyle>('dayPeriodTextStyle', dayPeriodTextStyle, defaultValue: null));
-    properties.add(DiagnosticsProperty<TextStyle>('helpTextStyle', helpTextStyle, defaultValue: null));
-    properties.add(DiagnosticsProperty<ShapeBorder>('shape', shape, defaultValue: null));
-    properties.add(DiagnosticsProperty<ShapeBorder>('hourMinuteShape', hourMinuteShape, defaultValue: null));
-    properties.add(DiagnosticsProperty<ShapeBorder>('dayPeriodShape', dayPeriodShape, defaultValue: null));
+    properties.add(DiagnosticsProperty<ButtonStyle>('cancelButtonStyle', cancelButtonStyle, defaultValue: null));
+    properties.add(DiagnosticsProperty<ButtonStyle>('confirmButtonStyle', confirmButtonStyle, defaultValue: null));
     properties.add(DiagnosticsProperty<BorderSide>('dayPeriodBorderSide', dayPeriodBorderSide, defaultValue: null));
+    properties.add(ColorProperty('dayPeriodColor', dayPeriodColor, defaultValue: null));
+    properties.add(DiagnosticsProperty<ShapeBorder>('dayPeriodShape', dayPeriodShape, defaultValue: null));
+    properties.add(ColorProperty('dayPeriodTextColor', dayPeriodTextColor, defaultValue: null));
+    properties.add(DiagnosticsProperty<TextStyle>('dayPeriodTextStyle', dayPeriodTextStyle, defaultValue: null));
+    properties.add(ColorProperty('dialBackgroundColor', dialBackgroundColor, defaultValue: null));
+    properties.add(ColorProperty('dialHandColor', dialHandColor, defaultValue: null));
+    properties.add(ColorProperty('dialTextColor', dialTextColor, defaultValue: null));
+    properties.add(DiagnosticsProperty<TextStyle?>('dialTextStyle', dialTextStyle, defaultValue: null));
+    properties.add(DoubleProperty('elevation', elevation, defaultValue: null));
+    properties.add(ColorProperty('entryModeIconColor', entryModeIconColor, defaultValue: null));
+    properties.add(DiagnosticsProperty<TextStyle>('helpTextStyle', helpTextStyle, defaultValue: null));
+    properties.add(ColorProperty('hourMinuteColor', hourMinuteColor, defaultValue: null));
+    properties.add(DiagnosticsProperty<ShapeBorder>('hourMinuteShape', hourMinuteShape, defaultValue: null));
+    properties.add(ColorProperty('hourMinuteTextColor', hourMinuteTextColor, defaultValue: null));
+    properties.add(DiagnosticsProperty<TextStyle>('hourMinuteTextStyle', hourMinuteTextStyle, defaultValue: null));
     properties.add(DiagnosticsProperty<InputDecorationTheme>('inputDecorationTheme', inputDecorationTheme, defaultValue: null));
+    properties.add(DiagnosticsProperty<EdgeInsetsGeometry>('padding', padding, defaultValue: null));
+    properties.add(DiagnosticsProperty<ShapeBorder>('shape', shape, defaultValue: null));
   }
 }
 
@@ -371,7 +436,7 @@
     super.key,
     required this.data,
     required super.child,
-  }) : assert(data != null);
+  });
 
   /// The properties for descendant time picker widgets.
   final TimePickerThemeData data;
diff --git a/framework/lib/src/material/toggle_buttons.dart b/framework/lib/src/material/toggle_buttons.dart
index 31ef55f..99692c9 100644
--- a/framework/lib/src/material/toggle_buttons.dart
+++ b/framework/lib/src/material/toggle_buttons.dart
@@ -207,12 +207,7 @@
     this.borderWidth,
     this.direction = Axis.horizontal,
     this.verticalDirection = VerticalDirection.down,
-  }) :
-    assert(children != null),
-    assert(isSelected != null),
-    assert(children.length == isSelected.length),
-    assert(direction != null),
-    assert(direction == Axis.horizontal || verticalDirection != null);
+  }) : assert(children.length == isSelected.length);
 
   static const double _defaultBorderWidth = 1.0;
 
@@ -653,18 +648,6 @@
   @override
   Widget build(BuildContext context) {
     assert(
-      !isSelected.any((bool val) => val == null),
-      'All elements of isSelected must be non-null.\n'
-      'The current list of isSelected values is as follows:\n'
-      '$isSelected',
-    );
-    assert(
-      focusNodes == null || !focusNodes!.any((FocusNode val) => val == null),
-      'All elements of focusNodes must be non-null.\n'
-      'The current list of focus node values is as follows:\n'
-      '$focusNodes',
-    );
-    assert(
       () {
         if (focusNodes != null) {
           return focusNodes!.length == children.length;
diff --git a/framework/lib/src/material/toggle_buttons_theme.dart b/framework/lib/src/material/toggle_buttons_theme.dart
index e2279f0..d4c9bd5 100644
--- a/framework/lib/src/material/toggle_buttons_theme.dart
+++ b/framework/lib/src/material/toggle_buttons_theme.dart
@@ -150,7 +150,6 @@
 
   /// Linearly interpolate between two toggle buttons themes.
   static ToggleButtonsThemeData? lerp(ToggleButtonsThemeData? a, ToggleButtonsThemeData? b, double t) {
-    assert (t != null);
     if (a == null && b == null) {
       return null;
     }
@@ -253,7 +252,7 @@
     super.key,
     required this.data,
     required super.child,
-  }) : assert(data != null);
+  });
 
   /// Specifies the color and border values for descendant [ToggleButtons] widgets.
   final ToggleButtonsThemeData data;
diff --git a/framework/lib/src/material/tooltip.dart b/framework/lib/src/material/tooltip.dart
index 28138c1..07517be 100644
--- a/framework/lib/src/material/tooltip.dart
+++ b/framework/lib/src/material/tooltip.dart
@@ -630,6 +630,7 @@
       _entry?.remove();
     }
     _isConcealed = false;
+    _entry?.dispose();
     _entry = null;
     if (_mouseIsConnected) {
       Tooltip._revealLastTooltip();
@@ -778,9 +779,7 @@
     required this.target,
     required this.verticalOffset,
     required this.preferBelow,
-  }) : assert(target != null),
-       assert(verticalOffset != null),
-       assert(preferBelow != null);
+  });
 
   /// The offset of the target the tooltip is positioned near in the global
   /// coordinate system.
@@ -884,7 +883,7 @@
       );
     }
     return Positioned.fill(
-      bottom: MediaQuery.maybeOf(context)?.viewInsets.bottom ?? 0.0,
+      bottom: MediaQuery.maybeViewInsetsOf(context)?.bottom ?? 0.0,
       child: CustomSingleChildLayout(
         delegate: _TooltipPositionDelegate(
           target: target,
diff --git a/framework/lib/src/material/tooltip_theme.dart b/framework/lib/src/material/tooltip_theme.dart
index 7a4c7a4..53cde10 100644
--- a/framework/lib/src/material/tooltip_theme.dart
+++ b/framework/lib/src/material/tooltip_theme.dart
@@ -152,7 +152,6 @@
     if (a == null && b == null) {
       return null;
     }
-    assert(t != null);
     return TooltipThemeData(
       height: lerpDouble(a?.height, b?.height, t),
       padding: EdgeInsetsGeometry.lerp(a?.padding, b?.padding, t),
@@ -269,7 +268,7 @@
     super.key,
     required this.data,
     required super.child,
-  }) : assert(data != null);
+  });
 
   /// The properties for descendant [Tooltip] widgets.
   final TooltipThemeData data;
diff --git a/framework/lib/src/material/typography.dart b/framework/lib/src/material/typography.dart
index 0d846ba..0a08ffc 100644
--- a/framework/lib/src/material/typography.dart
+++ b/framework/lib/src/material/typography.dart
@@ -211,9 +211,6 @@
     TextTheme tall,
   ) {
     assert(platform != null || (black != null && white != null));
-    assert(englishLike != null);
-    assert(dense != null);
-    assert(tall != null);
     switch (platform) {
       case TargetPlatform.iOS:
         black ??= blackCupertino;
@@ -242,12 +239,7 @@
     return Typography._(black!, white!, englishLike, dense, tall);
   }
 
-  const Typography._(this.black, this.white, this.englishLike, this.dense, this.tall)
-    : assert(black != null),
-      assert(white != null),
-      assert(englishLike != null),
-      assert(dense != null),
-      assert(tall != null);
+  const Typography._(this.black, this.white, this.englishLike, this.dense, this.tall);
 
   /// A Material Design text theme with dark glyphs.
   ///
@@ -305,7 +297,6 @@
 
   /// Returns one of [englishLike], [dense], or [tall].
   TextTheme geometryThemeFor(ScriptCategory category) {
-    assert(category != null);
     switch (category) {
       case ScriptCategory.englishLike:
         return englishLike;
@@ -759,7 +750,7 @@
 // Design token database by the script:
 //   dev/tools/gen_defaults/bin/gen_defaults.dart.
 
-// Token database version: v0_143
+// Token database version: v0_158
 
 class _M3Typography {
   _M3Typography._();
diff --git a/framework/lib/src/painting/_network_image_io.dart b/framework/lib/src/painting/_network_image_io.dart
index 13ab17e..e8f5e87 100644
--- a/framework/lib/src/painting/_network_image_io.dart
+++ b/framework/lib/src/painting/_network_image_io.dart
@@ -19,9 +19,7 @@
   /// Creates an object that fetches the image at the given URL.
   ///
   /// The arguments [url] and [scale] must not be null.
-  const NetworkImage(this.url, { this.scale = 1.0, this.headers })
-    : assert(url != null),
-      assert(scale != null);
+  const NetworkImage(this.url, { this.scale = 1.0, this.headers });
 
   @override
   final String url;
@@ -45,7 +43,7 @@
     final StreamController<ImageChunkEvent> chunkEvents = StreamController<ImageChunkEvent>();
 
     return MultiFrameImageStreamCompleter(
-      codec: _loadAsync(key as NetworkImage, chunkEvents, null, decode),
+      codec: _loadAsync(key as NetworkImage, chunkEvents, decodeDeprecated: decode),
       chunkEvents: chunkEvents.stream,
       scale: key.scale,
       debugLabel: key.url,
@@ -64,7 +62,26 @@
     final StreamController<ImageChunkEvent> chunkEvents = StreamController<ImageChunkEvent>();
 
     return MultiFrameImageStreamCompleter(
-      codec: _loadAsync(key as NetworkImage, chunkEvents, decode, null),
+      codec: _loadAsync(key as NetworkImage, chunkEvents, decodeBufferDeprecated: decode),
+      chunkEvents: chunkEvents.stream,
+      scale: key.scale,
+      debugLabel: key.url,
+      informationCollector: () => <DiagnosticsNode>[
+        DiagnosticsProperty<image_provider.ImageProvider>('Image provider', this),
+        DiagnosticsProperty<image_provider.NetworkImage>('Image key', key),
+      ],
+    );
+  }
+
+  @override
+  ImageStreamCompleter loadImage(image_provider.NetworkImage key, image_provider.ImageDecoderCallback decode) {
+    // Ownership of this controller is handed off to [_loadAsync]; it is that
+    // method's responsibility to close the controller's stream when the image
+    // has been loaded or an error is thrown.
+    final StreamController<ImageChunkEvent> chunkEvents = StreamController<ImageChunkEvent>();
+
+    return MultiFrameImageStreamCompleter(
+      codec: _loadAsync(key as NetworkImage, chunkEvents, decode: decode),
       chunkEvents: chunkEvents.stream,
       scale: key.scale,
       debugLabel: key.url,
@@ -94,10 +111,11 @@
 
   Future<ui.Codec> _loadAsync(
     NetworkImage key,
-    StreamController<ImageChunkEvent> chunkEvents,
-    image_provider.DecoderBufferCallback? decode,
-    image_provider.DecoderCallback? decodeDepreacted,
-  ) async {
+    StreamController<ImageChunkEvent> chunkEvents, {
+    image_provider.ImageDecoderCallback? decode,
+    image_provider.DecoderBufferCallback? decodeBufferDeprecated,
+    image_provider.DecoderCallback? decodeDeprecated,
+  }) async {
     try {
       assert(key == this);
 
@@ -133,9 +151,12 @@
       if (decode != null) {
         final ui.ImmutableBuffer buffer = await ui.ImmutableBuffer.fromUint8List(bytes);
         return decode(buffer);
+      } else if (decodeBufferDeprecated != null) {
+        final ui.ImmutableBuffer buffer = await ui.ImmutableBuffer.fromUint8List(bytes);
+        return decodeBufferDeprecated(buffer);
       } else {
-        assert(decodeDepreacted != null);
-        return decodeDepreacted!(bytes);
+        assert(decodeDeprecated != null);
+        return decodeDeprecated!(bytes);
       }
     } catch (e) {
       // Depending on where the exception was thrown, the image cache may not
diff --git a/framework/lib/src/painting/_network_image_web.dart b/framework/lib/src/painting/_network_image_web.dart
index b5fa5b8..4821c6d 100644
--- a/framework/lib/src/painting/_network_image_web.dart
+++ b/framework/lib/src/painting/_network_image_web.dart
@@ -39,9 +39,7 @@
   /// Creates an object that fetches the image at the given URL.
   ///
   /// The arguments [url] and [scale] must not be null.
-  const NetworkImage(this.url, {this.scale = 1.0, this.headers})
-      : assert(url != null),
-        assert(scale != null);
+  const NetworkImage(this.url, {this.scale = 1.0, this.headers});
 
   @override
   final String url;
@@ -67,7 +65,7 @@
 
     return MultiFrameImageStreamCompleter(
       chunkEvents: chunkEvents.stream,
-      codec: _loadAsync(key as NetworkImage, null, decode, chunkEvents),
+      codec: _loadAsync(key as NetworkImage, null, null, decode, chunkEvents),
       scale: key.scale,
       debugLabel: key.url,
       informationCollector: _imageStreamInformationCollector(key),
@@ -84,7 +82,23 @@
 
     return MultiFrameImageStreamCompleter(
       chunkEvents: chunkEvents.stream,
-      codec: _loadAsync(key as NetworkImage, decode, null, chunkEvents),
+      codec: _loadAsync(key as NetworkImage, null, decode, null, chunkEvents),
+      scale: key.scale,
+      debugLabel: key.url,
+      informationCollector: _imageStreamInformationCollector(key),
+    );
+  }
+
+  @override
+  ImageStreamCompleter loadImage(image_provider.NetworkImage key, image_provider.ImageDecoderCallback decode) {
+    // Ownership of this controller is handed off to [_loadAsync]; it is that
+    // method's responsibility to close the controller's stream when the image
+    // has been loaded or an error is thrown.
+    final StreamController<ImageChunkEvent> chunkEvents = StreamController<ImageChunkEvent>();
+
+    return MultiFrameImageStreamCompleter(
+      chunkEvents: chunkEvents.stream,
+      codec: _loadAsync(key as NetworkImage, decode, null, null, chunkEvents),
       scale: key.scale,
       debugLabel: key.url,
       informationCollector: _imageStreamInformationCollector(key),
@@ -103,34 +117,36 @@
     return collector;
   }
 
-  // TODO(garyq): We should eventually support custom decoding of network images on Web as
-  // well, see https://github.com/flutter/flutter/issues/42789.
-  //
-  // Web does not support decoding network images to a specified size. The decode parameter
+  // Html renderer does not support decoding network images to a specified size. The decode parameter
   // here is ignored and the web-only `ui.webOnlyInstantiateImageCodecFromUrl` will be used
   // directly in place of the typical `instantiateImageCodec` method.
   Future<ui.Codec> _loadAsync(
     NetworkImage key,
-    image_provider.DecoderBufferCallback? decode,
-    image_provider.DecoderCallback? decodeDepreacted,
+    image_provider.ImageDecoderCallback? decode,
+    image_provider.DecoderBufferCallback? decodeBufferDeprecated,
+    image_provider.DecoderCallback? decodeDeprecated,
     StreamController<ImageChunkEvent> chunkEvents,
   ) async {
     assert(key == this);
 
     final Uri resolved = Uri.base.resolve(key.url);
 
+    final bool containsNetworkImageHeaders = key.headers?.isNotEmpty ?? false;
+
     // We use a different method when headers are set because the
     // `ui.webOnlyInstantiateImageCodecFromUrl` method is not capable of handling headers.
-    if (key.headers?.isNotEmpty ?? false) {
+    if (isCanvasKit || containsNetworkImageHeaders) {
       final Completer<DomXMLHttpRequest> completer =
           Completer<DomXMLHttpRequest>();
       final DomXMLHttpRequest request = httpRequestFactory();
 
       request.open('GET', key.url, true);
       request.responseType = 'arraybuffer';
-      key.headers!.forEach((String header, String value) {
-        request.setRequestHeader(header, value);
-      });
+      if (containsNetworkImageHeaders) {
+        key.headers!.forEach((String header, String value) {
+          request.setRequestHeader(header, value);
+        });
+      }
 
       request.addEventListener('load', allowInterop((DomEvent e) {
         final int? status = request.status;
@@ -166,9 +182,12 @@
       if (decode != null) {
         final ui.ImmutableBuffer buffer = await ui.ImmutableBuffer.fromUint8List(bytes);
         return decode(buffer);
+      } else if (decodeBufferDeprecated != null) {
+        final ui.ImmutableBuffer buffer = await ui.ImmutableBuffer.fromUint8List(bytes);
+        return decodeBufferDeprecated(buffer);
       } else {
-        assert(decodeDepreacted != null);
-        return decodeDepreacted!(bytes);
+        assert(decodeDeprecated != null);
+        return decodeDeprecated!(bytes);
       }
     } else {
       // This API only exists in the web engine implementation and is not
diff --git a/framework/lib/src/painting/alignment.dart b/framework/lib/src/painting/alignment.dart
index cbb2cc1..4149485 100644
--- a/framework/lib/src/painting/alignment.dart
+++ b/framework/lib/src/painting/alignment.dart
@@ -87,7 +87,6 @@
   ///
   /// {@macro dart.ui.shadow.lerp}
   static AlignmentGeometry? lerp(AlignmentGeometry? a, AlignmentGeometry? b, double t) {
-    assert(t != null);
     if (a == null && b == null) {
       return null;
     }
@@ -188,9 +187,7 @@
   /// Creates an alignment.
   ///
   /// The [x] and [y] arguments must not be null.
-  const Alignment(this.x, this.y)
-    : assert(x != null),
-      assert(y != null);
+  const Alignment(this.x, this.y);
 
   /// The distance fraction in the horizontal direction.
   ///
@@ -340,7 +337,6 @@
   ///
   /// {@macro dart.ui.shadow.lerp}
   static Alignment? lerp(Alignment? a, Alignment? b, double t) {
-    assert(t != null);
     if (a == null && b == null) {
       return null;
     }
@@ -407,9 +403,7 @@
   /// Creates a directional alignment.
   ///
   /// The [start] and [y] arguments must not be null.
-  const AlignmentDirectional(this.start, this.y)
-    : assert(start != null),
-      assert(y != null);
+  const AlignmentDirectional(this.start, this.y);
 
   /// The distance fraction in the horizontal direction.
   ///
@@ -534,7 +528,6 @@
   ///
   /// {@macro dart.ui.shadow.lerp}
   static AlignmentDirectional? lerp(AlignmentDirectional? a, AlignmentDirectional? b, double t) {
-    assert(t != null);
     if (a == null && b == null) {
       return null;
     }
@@ -682,8 +675,7 @@
   /// Creates a TextAlignVertical from any y value between -1.0 and 1.0.
   const TextAlignVertical({
     required this.y,
-  }) : assert(y != null),
-       assert(y >= -1.0 && y <= 1.0);
+  }) : assert(y >= -1.0 && y <= 1.0);
 
   /// A value ranging from -1.0 to 1.0 that defines the topmost and bottommost
   /// locations of the top and bottom of the input box.
diff --git a/framework/lib/src/painting/basic_types.dart b/framework/lib/src/painting/basic_types.dart
index c48b20f..6675055 100644
--- a/framework/lib/src/painting/basic_types.dart
+++ b/framework/lib/src/painting/basic_types.dart
@@ -136,7 +136,6 @@
 ///
 ///  * [flipAxisDirection], which does the same thing for [AxisDirection] values.
 Axis flipAxis(Axis direction) {
-  assert(direction != null);
   switch (direction) {
     case Axis.horizontal:
       return Axis.vertical;
@@ -204,7 +203,6 @@
 /// [AxisDirection.down] and returns [Axis.horizontal] for [AxisDirection.left]
 /// and [AxisDirection.right].
 Axis axisDirectionToAxis(AxisDirection axisDirection) {
-  assert(axisDirection != null);
   switch (axisDirection) {
     case AxisDirection.up:
     case AxisDirection.down:
@@ -220,7 +218,6 @@
 /// Specifically, returns [AxisDirection.left] for [TextDirection.rtl] and
 /// [AxisDirection.right] for [TextDirection.ltr].
 AxisDirection textDirectionToAxisDirection(TextDirection textDirection) {
-  assert(textDirection != null);
   switch (textDirection) {
     case TextDirection.rtl:
       return AxisDirection.left;
@@ -239,7 +236,6 @@
 ///
 ///  * [flipAxis], which does the same thing for [Axis] values.
 AxisDirection flipAxisDirection(AxisDirection axisDirection) {
-  assert(axisDirection != null);
   switch (axisDirection) {
     case AxisDirection.up:
       return AxisDirection.down;
@@ -258,7 +254,6 @@
 /// Specifically, returns true for [AxisDirection.up] and [AxisDirection.left]
 /// and false for [AxisDirection.down] and [AxisDirection.right].
 bool axisDirectionIsReversed(AxisDirection axisDirection) {
-  assert(axisDirection != null);
   switch (axisDirection) {
     case AxisDirection.up:
     case AxisDirection.left:
diff --git a/framework/lib/src/painting/beveled_rectangle_border.dart b/framework/lib/src/painting/beveled_rectangle_border.dart
index 1480e5b..6205814 100644
--- a/framework/lib/src/painting/beveled_rectangle_border.dart
+++ b/framework/lib/src/painting/beveled_rectangle_border.dart
@@ -25,8 +25,7 @@
   const BeveledRectangleBorder({
     super.side,
     this.borderRadius = BorderRadius.zero,
-  }) : assert(side != null),
-       assert(borderRadius != null);
+  });
 
   /// The radii for each corner.
   ///
@@ -49,7 +48,6 @@
 
   @override
   ShapeBorder? lerpFrom(ShapeBorder? a, double t) {
-    assert(t != null);
     if (a is BeveledRectangleBorder) {
       return BeveledRectangleBorder(
         side: BorderSide.lerp(a.side, side, t),
@@ -61,7 +59,6 @@
 
   @override
   ShapeBorder? lerpTo(ShapeBorder? b, double t) {
-    assert(t != null);
     if (b is BeveledRectangleBorder) {
       return BeveledRectangleBorder(
         side: BorderSide.lerp(side, b.side, t),
diff --git a/framework/lib/src/painting/binding.dart b/framework/lib/src/painting/binding.dart
index aa81bcd..6eae7c3 100644
--- a/framework/lib/src/painting/binding.dart
+++ b/framework/lib/src/painting/binding.dart
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import 'package:engine/ui.dart' as ui show Codec, ImmutableBuffer, instantiateImageCodec, instantiateImageCodecFromBuffer;
+import 'package:engine/ui.dart' as ui;
 import 'package:flute/foundation.dart';
 import 'package:flute/services.dart' show ServicesBinding;
 
@@ -100,7 +100,7 @@
   /// above its native resolution should prefer scaling the canvas the image is
   /// drawn into.
   @Deprecated(
-    'Use instantiateImageCodecFromBuffer with an ImmutableBuffer instance instead. '
+    'Use instantiateImageCodecWithSize with an ImmutableBuffer instance instead. '
     'This feature was deprecated after v2.13.0-1.0.pre.',
   )
   Future<ui.Codec> instantiateImageCodec(
@@ -111,7 +111,6 @@
   }) {
     assert(cacheWidth == null || cacheWidth > 0);
     assert(cacheHeight == null || cacheHeight > 0);
-    assert(allowUpscaling != null);
     return ui.instantiateImageCodec(
       bytes,
       targetWidth: cacheWidth,
@@ -141,6 +140,10 @@
   /// unnecessary memory usage for images. Callers that wish to display an image
   /// above its native resolution should prefer scaling the canvas the image is
   /// drawn into.
+  @Deprecated(
+    'Use instantiateImageCodecWithSize instead. '
+    'This feature was deprecated after v3.7.0-1.4.pre.',
+  )
   Future<ui.Codec> instantiateImageCodecFromBuffer(
     ui.ImmutableBuffer buffer, {
     int? cacheWidth,
@@ -149,7 +152,6 @@
   }) {
     assert(cacheWidth == null || cacheWidth > 0);
     assert(cacheHeight == null || cacheHeight > 0);
-    assert(allowUpscaling != null);
     return ui.instantiateImageCodecFromBuffer(
       buffer,
       targetWidth: cacheWidth,
@@ -158,6 +160,28 @@
     );
   }
 
+  /// Calls through to [dart:ui.instantiateImageCodecWithSize] from [ImageCache].
+  ///
+  /// The [buffer] parameter should be an [ui.ImmutableBuffer] instance which can
+  /// be acquired from [ui.ImmutableBuffer.fromUint8List] or
+  /// [ui.ImmutableBuffer.fromAsset].
+  ///
+  /// The [getTargetSize] parameter, when specified, will be invoked and passed
+  /// the image's intrinsic size to determine the size to decode the image to.
+  /// The width and the height of the size it returns must be positive values
+  /// greater than or equal to 1, or null. It is valid to return a [TargetImageSize]
+  /// that specifies only one of `width` and `height` with the other remaining
+  /// null, in which case the omitted dimension will be scaled to maintain the
+  /// aspect ratio of the original dimensions. When both are null or omitted,
+  /// the image will be decoded at its native resolution (as will be the case if
+  /// the [getTargetSize] parameter is omitted).
+  Future<ui.Codec> instantiateImageCodecWithSize(
+    ui.ImmutableBuffer buffer, {
+    ui.TargetImageSizeCallback? getTargetSize,
+  }) {
+    return ui.instantiateImageCodecWithSize(buffer, getTargetSize: getTargetSize);
+  }
+
   @override
   void evict(String asset) {
     super.evict(asset);
diff --git a/framework/lib/src/painting/border_radius.dart b/framework/lib/src/painting/border_radius.dart
index 7b4761f..b1bdfaf 100644
--- a/framework/lib/src/painting/border_radius.dart
+++ b/framework/lib/src/painting/border_radius.dart
@@ -129,7 +129,6 @@
   ///
   /// {@macro dart.ui.shadow.lerp}
   static BorderRadiusGeometry? lerp(BorderRadiusGeometry? a, BorderRadiusGeometry? b, double t) {
-    assert(t != null);
     if (a == null && b == null) {
       return null;
     }
@@ -507,7 +506,6 @@
   ///
   /// {@macro dart.ui.shadow.lerp}
   static BorderRadius? lerp(BorderRadius? a, BorderRadius? b, double t) {
-    assert(t != null);
     if (a == null && b == null) {
       return null;
     }
@@ -590,7 +588,7 @@
 
   /// A border radius with all zero radii.
   ///
-  /// Consider using [EdgeInsets.zero] instead, since that object has the same
+  /// Consider using [BorderRadius.zero] instead, since that object has the same
   /// effect, but will be cheaper to [resolve].
   static const BorderRadiusDirectional zero = BorderRadiusDirectional.all(Radius.zero);
 
@@ -729,7 +727,6 @@
   ///
   /// {@macro dart.ui.shadow.lerp}
   static BorderRadiusDirectional? lerp(BorderRadiusDirectional? a, BorderRadiusDirectional? b, double t) {
-    assert(t != null);
     if (a == null && b == null) {
       return null;
     }
diff --git a/framework/lib/src/painting/borders.dart b/framework/lib/src/painting/borders.dart
index 01604ea..4316028 100644
--- a/framework/lib/src/painting/borders.dart
+++ b/framework/lib/src/painting/borders.dart
@@ -66,11 +66,7 @@
     this.width = 1.0,
     this.style = BorderStyle.solid,
     this.strokeAlign = strokeAlignInside,
-  }) : assert(color != null),
-       assert(width != null),
-       assert(width >= 0.0),
-       assert(style != null),
-       assert(strokeAlign != null);
+  }) : assert(width >= 0.0);
 
   /// Creates a [BorderSide] that represents the addition of the two given
   /// [BorderSide]s.
@@ -84,8 +80,6 @@
   ///
   /// The arguments must not be null.
   static BorderSide merge(BorderSide a, BorderSide b) {
-    assert(a != null);
-    assert(b != null);
     assert(canMerge(a, b));
     final bool aIsNone = a.style == BorderStyle.none && a.width == 0.0;
     final bool bIsNone = b.style == BorderStyle.none && b.width == 0.0;
@@ -136,7 +130,7 @@
   ///
   /// Values typically range from -1.0 ([strokeAlignInside], inside border,
   /// default) to 1.0 ([strokeAlignOutside], outside border), without any
-  /// bound constraints (e.g., a value of -2.0 is is not typical, but allowed).
+  /// bound constraints (e.g., a value of -2.0 is not typical, but allowed).
   /// A value of 0 ([strokeAlignCenter]) will center the border on the edge
   /// of the widget.
   ///
@@ -202,7 +196,6 @@
   /// Values for `t` are usually obtained from an [Animation<double>], such as
   /// an [AnimationController].
   BorderSide scale(double t) {
-    assert(t != null);
     return BorderSide(
       color: color,
       width: math.max(0.0, width * t),
@@ -239,8 +232,6 @@
   ///
   /// The arguments must not be null.
   static bool canMerge(BorderSide a, BorderSide b) {
-    assert(a != null);
-    assert(b != null);
     if ((a.style == BorderStyle.none && a.width == 0.0) ||
         (b.style == BorderStyle.none && b.width == 0.0)) {
       return true;
@@ -255,9 +246,6 @@
   ///
   /// {@macro dart.ui.shadow.lerp}
   static BorderSide lerp(BorderSide a, BorderSide b, double t) {
-    assert(a != null);
-    assert(b != null);
-    assert(t != null);
     if (t == 0.0) {
       return a;
     }
@@ -517,7 +505,6 @@
   ///
   /// {@macro dart.ui.shadow.lerp}
   static ShapeBorder? lerp(ShapeBorder? a, ShapeBorder? b, double t) {
-    assert(t != null);
     ShapeBorder? result;
     if (b != null) {
       result = b.lerpFrom(a, t);
@@ -664,7 +651,7 @@
   /// const constructors so that they can be used in const expressions.
   ///
   /// The value of [side] must not be null.
-  const OutlinedBorder({ this.side = BorderSide.none }) : assert(side != null);
+  const OutlinedBorder({ this.side = BorderSide.none });
 
   @override
   EdgeInsetsGeometry get dimensions => EdgeInsets.all(math.max(side.strokeInset, 0));
@@ -707,7 +694,6 @@
   ///
   /// {@macro dart.ui.shadow.lerp}
   static OutlinedBorder? lerp(OutlinedBorder? a, OutlinedBorder? b, double t) {
-    assert(t != null);
     ShapeBorder? result;
     if (b != null) {
       result = b.lerpFrom(a, t);
@@ -724,8 +710,7 @@
 /// The borders are listed from the outside to the inside.
 class _CompoundBorder extends ShapeBorder {
   _CompoundBorder(this.borders)
-    : assert(borders != null),
-      assert(borders.length >= 2),
+    : assert(borders.length >= 2),
       assert(!borders.any((ShapeBorder border) => border is _CompoundBorder));
 
   final List<ShapeBorder> borders;
@@ -788,7 +773,6 @@
   }
 
   static _CompoundBorder lerp(ShapeBorder? a, ShapeBorder? b, double t) {
-    assert(t != null);
     assert(a is _CompoundBorder || b is _CompoundBorder); // Not really necessary, but all call sites currently intend this.
     final List<ShapeBorder?> aList = a is _CompoundBorder ? a.borders : <ShapeBorder?>[a];
     final List<ShapeBorder?> bList = b is _CompoundBorder ? b.borders : <ShapeBorder?>[b];
@@ -897,12 +881,6 @@
   BorderSide bottom = BorderSide.none,
   BorderSide left = BorderSide.none,
 }) {
-  assert(canvas != null);
-  assert(rect != null);
-  assert(top != null);
-  assert(right != null);
-  assert(bottom != null);
-  assert(left != null);
 
   // We draw the borders as filled shapes, unless the borders are hairline
   // borders, in which case we use PaintingStyle.stroke, with the stroke width
diff --git a/framework/lib/src/painting/box_border.dart b/framework/lib/src/painting/box_border.dart
index faf1b7b..e5a4ffc 100644
--- a/framework/lib/src/painting/box_border.dart
+++ b/framework/lib/src/painting/box_border.dart
@@ -103,7 +103,6 @@
   ///
   /// {@macro dart.ui.shadow.lerp}
   static BoxBorder? lerp(BoxBorder? a, BoxBorder? b, double t) {
-    assert(t != null);
     if ((a is Border?) && (b is Border?)) {
       return Border.lerp(a, b, t);
     }
@@ -322,17 +321,13 @@
     this.right = BorderSide.none,
     this.bottom = BorderSide.none,
     this.left = BorderSide.none,
-  }) : assert(top != null),
-       assert(right != null),
-       assert(bottom != null),
-       assert(left != null);
+  });
 
   /// Creates a border whose sides are all the same.
   ///
   /// The `side` argument must not be null.
   const Border.fromBorderSide(BorderSide side)
-      : assert(side != null),
-        top = side,
+      : top = side,
         right = side,
         bottom = side,
         left = side;
@@ -346,9 +341,7 @@
   const Border.symmetric({
     BorderSide vertical = BorderSide.none,
     BorderSide horizontal = BorderSide.none,
-  }) : assert(vertical != null),
-       assert(horizontal != null),
-       left = vertical,
+  }) : left = vertical,
        top = horizontal,
        right = vertical,
        bottom = horizontal;
@@ -374,8 +367,6 @@
   ///
   /// The arguments must not be null.
   static Border merge(Border a, Border b) {
-    assert(a != null);
-    assert(b != null);
     assert(BorderSide.canMerge(a.top, b.top));
     assert(BorderSide.canMerge(a.right, b.right));
     assert(BorderSide.canMerge(a.bottom, b.bottom));
@@ -478,7 +469,6 @@
   ///
   /// {@macro dart.ui.shadow.lerp}
   static Border? lerp(Border? a, Border? b, double t) {
-    assert(t != null);
     if (a == null && b == null) {
       return null;
     }
@@ -650,10 +640,7 @@
     this.start = BorderSide.none,
     this.end = BorderSide.none,
     this.bottom = BorderSide.none,
-  }) : assert(top != null),
-       assert(start != null),
-       assert(end != null),
-       assert(bottom != null);
+  });
 
   /// Creates a [BorderDirectional] that represents the addition of the two
   /// given [BorderDirectional]s.
@@ -663,8 +650,6 @@
   ///
   /// The arguments must not be null.
   static BorderDirectional merge(BorderDirectional a, BorderDirectional b) {
-    assert(a != null);
-    assert(b != null);
     assert(BorderSide.canMerge(a.top, b.top));
     assert(BorderSide.canMerge(a.start, b.start));
     assert(BorderSide.canMerge(a.end, b.end));
@@ -826,7 +811,6 @@
   ///
   /// {@macro dart.ui.shadow.lerp}
   static BorderDirectional? lerp(BorderDirectional? a, BorderDirectional? b, double t) {
-    assert(t != null);
     if (a == null && b == null) {
       return null;
     }
diff --git a/framework/lib/src/painting/box_decoration.dart b/framework/lib/src/painting/box_decoration.dart
index 9c49d0c..a81eab4 100644
--- a/framework/lib/src/painting/box_decoration.dart
+++ b/framework/lib/src/painting/box_decoration.dart
@@ -93,8 +93,7 @@
     this.gradient,
     this.backgroundBlendMode,
     this.shape = BoxShape.rectangle,
-  }) : assert(shape != null),
-       assert(
+  }) : assert(
          backgroundBlendMode == null || color != null || gradient != null,
          "backgroundBlendMode applies to BoxDecoration's background color or "
          'gradient, but no color or gradient was provided.',
@@ -209,7 +208,7 @@
   final BoxShape shape;
 
   @override
-  EdgeInsetsGeometry? get padding => border?.dimensions;
+  EdgeInsetsGeometry get padding => border?.dimensions ?? EdgeInsets.zero;
 
   @override
   Path getClipPath(Rect rect, TextDirection textDirection) {
@@ -289,7 +288,6 @@
   ///    and which use [BoxDecoration.lerp] when interpolating two
   ///    [BoxDecoration]s or a [BoxDecoration] to or from null.
   static BoxDecoration? lerp(BoxDecoration? a, BoxDecoration? b, double t) {
-    assert(t != null);
     if (a == null && b == null) {
       return null;
     }
@@ -365,7 +363,6 @@
 
   @override
   bool hitTest(Size size, Offset position, { TextDirection? textDirection }) {
-    assert(shape != null);
     assert((Offset.zero & size).contains(position));
     switch (shape) {
       case BoxShape.rectangle:
@@ -391,15 +388,13 @@
 
 /// An object that paints a [BoxDecoration] into a canvas.
 class _BoxDecorationPainter extends BoxPainter {
-  _BoxDecorationPainter(this._decoration, super.onChanged)
-    : assert(_decoration != null);
+  _BoxDecorationPainter(this._decoration, super.onChanged);
 
   final BoxDecoration _decoration;
 
   Paint? _cachedBackgroundPaint;
   Rect? _rectForCachedBackgroundPaint;
   Paint _getBackgroundPaint(Rect rect, TextDirection? textDirection) {
-    assert(rect != null);
     assert(_decoration.gradient != null || _rectForCachedBackgroundPaint == null);
 
     if (_cachedBackgroundPaint == null ||
@@ -489,7 +484,6 @@
   /// Paint the box decoration into the given location on the given canvas.
   @override
   void paint(Canvas canvas, Offset offset, ImageConfiguration configuration) {
-    assert(configuration != null);
     assert(configuration.size != null);
     final Rect rect = offset & configuration.size!;
     final TextDirection? textDirection = configuration.textDirection;
diff --git a/framework/lib/src/painting/box_fit.dart b/framework/lib/src/painting/box_fit.dart
index 38d14e0..ccfdc81 100644
--- a/framework/lib/src/painting/box_fit.dart
+++ b/framework/lib/src/painting/box_fit.dart
@@ -166,12 +166,26 @@
       destinationSize = outputSize;
       break;
     case BoxFit.fitWidth:
-      sourceSize = Size(inputSize.width, inputSize.width * outputSize.height / outputSize.width);
-      destinationSize = Size(outputSize.width, sourceSize.height * outputSize.width / sourceSize.width);
+      if (outputSize.width / outputSize.height > inputSize.width / inputSize.height) {
+        // Like "cover"
+        sourceSize = Size(inputSize.width, inputSize.width * outputSize.height / outputSize.width);
+        destinationSize = outputSize;
+      } else {
+        // Like "contain"
+        sourceSize = inputSize;
+        destinationSize = Size(outputSize.width, sourceSize.height * outputSize.width / sourceSize.width);
+      }
       break;
     case BoxFit.fitHeight:
-      sourceSize = Size(inputSize.height * outputSize.width / outputSize.height, inputSize.height);
-      destinationSize = Size(sourceSize.width * outputSize.height / sourceSize.height, outputSize.height);
+      if (outputSize.width / outputSize.height > inputSize.width / inputSize.height) {
+        // Like "contain"
+        sourceSize = inputSize;
+        destinationSize = Size(sourceSize.width * outputSize.height / sourceSize.height, outputSize.height);
+      } else {
+        // Like "cover"
+        sourceSize = Size(inputSize.height * outputSize.width / outputSize.height, inputSize.height);
+        destinationSize = outputSize;
+      }
       break;
     case BoxFit.none:
       sourceSize = Size(math.min(inputSize.width, outputSize.width), math.min(inputSize.height, outputSize.height));
diff --git a/framework/lib/src/painting/box_shadow.dart b/framework/lib/src/painting/box_shadow.dart
index 513589c..f050709 100644
--- a/framework/lib/src/painting/box_shadow.dart
+++ b/framework/lib/src/painting/box_shadow.dart
@@ -80,13 +80,12 @@
 
   /// Linearly interpolate between two box shadows.
   ///
-  /// If either box shadow is null, this function linearly interpolates from a
+  /// If either box shadow is null, this function linearly interpolates from
   /// a box shadow that matches the other box shadow in color but has a zero
   /// offset and a zero blurRadius.
   ///
   /// {@macro dart.ui.shadow.lerp}
   static BoxShadow? lerp(BoxShadow? a, BoxShadow? b, double t) {
-    assert(t != null);
     if (a == null && b == null) {
       return null;
     }
@@ -111,7 +110,6 @@
   ///
   /// {@macro dart.ui.shadow.lerp}
   static List<BoxShadow>? lerpList(List<BoxShadow>? a, List<BoxShadow>? b, double t) {
-    assert(t != null);
     if (a == null && b == null) {
       return null;
     }
diff --git a/framework/lib/src/painting/circle_border.dart b/framework/lib/src/painting/circle_border.dart
index dd255a3..c39d06f 100644
--- a/framework/lib/src/painting/circle_border.dart
+++ b/framework/lib/src/painting/circle_border.dart
@@ -33,9 +33,7 @@
   ///
   /// The [side] argument must not be null.
   const CircleBorder({ super.side, this.eccentricity = 0.0 })
-      : assert(side != null),
-        assert(eccentricity != null),
-        assert(eccentricity >= 0.0, 'The eccentricity argument $eccentricity is not greater than or equal to zero.'),
+      : assert(eccentricity >= 0.0, 'The eccentricity argument $eccentricity is not greater than or equal to zero.'),
         assert(eccentricity <= 1.0, 'The eccentricity argument $eccentricity is not less than or equal to one.');
 
   /// Defines the ratio (0.0-1.0) from which the border will deform
diff --git a/framework/lib/src/painting/clip.dart b/framework/lib/src/painting/clip.dart
index 233f257..f6e46b5 100644
--- a/framework/lib/src/painting/clip.dart
+++ b/framework/lib/src/painting/clip.dart
@@ -10,7 +10,6 @@
   Canvas get canvas;
 
   void _clipAndPaint(void Function(bool doAntiAlias) canvasClipCall, Clip clipBehavior, Rect bounds, VoidCallback painter) {
-    assert(canvasClipCall != null);
     canvas.save();
     switch (clipBehavior) {
       case Clip.none:
diff --git a/framework/lib/src/painting/colors.dart b/framework/lib/src/painting/colors.dart
index b73e7d4..45a87fe 100644
--- a/framework/lib/src/painting/colors.dart
+++ b/framework/lib/src/painting/colors.dart
@@ -89,11 +89,7 @@
   /// All the arguments must not be null and be in their respective ranges. See
   /// the fields for each parameter for a description of their ranges.
   const HSVColor.fromAHSV(this.alpha, this.hue, this.saturation, this.value)
-    : assert(alpha != null),
-      assert(hue != null),
-      assert(saturation != null),
-      assert(value != null),
-      assert(alpha >= 0.0),
+    : assert(alpha >= 0.0),
       assert(alpha <= 1.0),
       assert(hue >= 0.0),
       assert(hue <= 360.0),
@@ -198,7 +194,6 @@
   ///
   /// Values outside of the valid range for each channel will be clamped.
   static HSVColor? lerp(HSVColor? a, HSVColor? b, double t) {
-    assert(t != null);
     if (a == null && b == null) {
       return null;
     }
@@ -262,11 +257,7 @@
   /// All the arguments must not be null and be in their respective ranges. See
   /// the fields for each parameter for a description of their ranges.
   const HSLColor.fromAHSL(this.alpha, this.hue, this.saturation, this.lightness)
-    : assert(alpha != null),
-      assert(hue != null),
-      assert(saturation != null),
-      assert(lightness != null),
-      assert(alpha >= 0.0),
+    : assert(alpha >= 0.0),
       assert(alpha <= 1.0),
       assert(hue >= 0.0),
       assert(hue <= 360.0),
@@ -386,7 +377,6 @@
   /// Values for `t` are usually obtained from an [Animation<double>], such as
   /// an [AnimationController].
   static HSLColor? lerp(HSLColor? a, HSLColor? b, double t) {
-    assert(t != null);
     if (a == null && b == null) {
       return null;
     }
@@ -519,9 +509,7 @@
     super.defaultValue,
     super.style,
     super.level,
-  }) : assert(showName != null),
-       assert(style != null),
-       assert(level != null);
+  });
 
   @override
   Map<String, Object?> toJsonMap(DiagnosticsSerializationDelegate delegate) {
diff --git a/framework/lib/src/painting/continuous_rectangle_border.dart b/framework/lib/src/painting/continuous_rectangle_border.dart
index ac6ad27..05302b1 100644
--- a/framework/lib/src/painting/continuous_rectangle_border.dart
+++ b/framework/lib/src/painting/continuous_rectangle_border.dart
@@ -37,8 +37,7 @@
   const ContinuousRectangleBorder({
     super.side,
     this.borderRadius = BorderRadius.zero,
-  }) : assert(side != null),
-       assert(borderRadius != null);
+  });
 
   /// The radius for each corner.
   ///
@@ -59,7 +58,6 @@
 
   @override
   ShapeBorder? lerpFrom(ShapeBorder? a, double t) {
-    assert(t != null);
     if (a is ContinuousRectangleBorder) {
       return ContinuousRectangleBorder(
         side: BorderSide.lerp(a.side, side, t),
@@ -71,7 +69,6 @@
 
   @override
   ShapeBorder? lerpTo(ShapeBorder? b, double t) {
-    assert(t != null);
     if (b is ContinuousRectangleBorder) {
       return ContinuousRectangleBorder(
         side: BorderSide.lerp(side, b.side, t),
diff --git a/framework/lib/src/painting/decoration.dart b/framework/lib/src/painting/decoration.dart
index 285e4d5..85ef326 100644
--- a/framework/lib/src/painting/decoration.dart
+++ b/framework/lib/src/painting/decoration.dart
@@ -59,7 +59,7 @@
   /// [EdgeInsetsGeometry.resolve] to obtain an absolute [EdgeInsets]. (For
   /// example, [BorderDirectional] will return an [EdgeInsetsDirectional] for
   /// its [padding].)
-  EdgeInsetsGeometry? get padding => EdgeInsets.zero;
+  EdgeInsetsGeometry get padding => EdgeInsets.zero;
 
   /// Whether this decoration is complex enough to benefit from caching its painting.
   bool get isComplex => false;
@@ -129,7 +129,6 @@
   ///
   /// {@macro dart.ui.shadow.lerp}
   static Decoration? lerp(Decoration? a, Decoration? b, double t) {
-    assert(t != null);
     if (a == null && b == null) {
       return null;
     }
diff --git a/framework/lib/src/painting/decoration_image.dart b/framework/lib/src/painting/decoration_image.dart
index 041fd8b..b6e8c7e 100644
--- a/framework/lib/src/painting/decoration_image.dart
+++ b/framework/lib/src/painting/decoration_image.dart
@@ -3,10 +3,12 @@
 // found in the LICENSE file.
 
 import 'dart:developer' as developer;
-import 'package:engine/ui.dart' as ui show Image;
+import 'dart:math' as math;
+import 'package:engine/ui.dart' as ui show FlutterView, Image;
 
 import 'package:flute/foundation.dart';
 import 'package:flute/scheduler.dart';
+import 'package:vector_math/vector_math_64.dart';
 
 import 'alignment.dart';
 import 'basic_types.dart';
@@ -56,11 +58,7 @@
     this.filterQuality = FilterQuality.low,
     this.invertColors = false,
     this.isAntiAlias = false,
-  }) : assert(image != null),
-       assert(alignment != null),
-       assert(repeat != null),
-       assert(matchTextDirection != null),
-       assert(scale != null);
+  });
 
   /// The image to be painted into the decoration.
   ///
@@ -180,7 +178,6 @@
   /// image needs to be repainted, e.g. because it is loading incrementally or
   /// because it is animated.
   DecorationImagePainter createPainter(VoidCallback onChanged) {
-    assert(onChanged != null);
     return DecorationImagePainter._(this, onChanged);
   }
 
@@ -264,7 +261,7 @@
 /// This object should be disposed using the [dispose] method when it is no
 /// longer needed.
 class DecorationImagePainter {
-  DecorationImagePainter._(this._details, this._onChanged) : assert(_details != null);
+  DecorationImagePainter._(this._details, this._onChanged);
 
   final DecorationImage _details;
   final VoidCallback _onChanged;
@@ -287,9 +284,6 @@
   /// then the `onChanged` callback passed to [DecorationImage.createPainter]
   /// will be called.
   void paint(Canvas canvas, Rect rect, Path? clipPath, ImageConfiguration configuration) {
-    assert(canvas != null);
-    assert(rect != null);
-    assert(configuration != null);
 
     bool flipHorizontally = false;
     if (_details.matchTextDirection) {
@@ -366,7 +360,6 @@
     }
     _image?.dispose();
     _image = value;
-    assert(_onChanged != null);
     if (!synchronousCall) {
       _onChanged();
     }
@@ -412,6 +405,61 @@
   }());
 }
 
+/// Information that describes how to tile an image for a given [ImageRepeat]
+/// enum.
+///
+/// Used with [createTilingInfo].
+@visibleForTesting
+@immutable
+class ImageTilingInfo {
+  /// Create a new [ImageTilingInfo] object.
+  const ImageTilingInfo({
+    required this.tmx,
+    required this.tmy,
+    required this.transform,
+  });
+
+  /// The tile mode for the x-axis.
+  final TileMode tmx;
+
+  /// The tile mode for the y-axis.
+  final TileMode tmy;
+
+  /// The transform to apply to the image shader.
+  final Matrix4 transform;
+
+  @override
+  String toString() {
+    if (!kDebugMode) {
+      return 'ImageTilingInfo';
+    }
+    return 'ImageTilingInfo($tmx, $tmy, $transform)';
+  }
+}
+
+/// Create the [ImageTilingInfo] for a given [ImageRepeat], canvas [rect],
+/// [destinationRect], and [sourceRect].
+@visibleForTesting
+ImageTilingInfo createTilingInfo(ImageRepeat repeat, Rect rect, Rect destinationRect, Rect sourceRect) {
+  assert(repeat != ImageRepeat.noRepeat);
+  final TileMode tmx = (repeat == ImageRepeat.repeatX || repeat == ImageRepeat.repeat)
+    ? TileMode.repeated
+    : TileMode.decal;
+  final TileMode tmy = (repeat == ImageRepeat.repeatY || repeat == ImageRepeat.repeat)
+    ? TileMode.repeated
+    : TileMode.decal;
+  final Rect data = _generateImageTileRects(rect, destinationRect, repeat).first;
+  final Matrix4 transform = Matrix4.identity()
+    ..scale(data.width / sourceRect.width, data.height / sourceRect.height)
+    ..setTranslationRaw(data.topLeft.dx, data.topLeft.dy, 0);
+
+  return ImageTilingInfo(
+    tmx: tmx,
+    tmy: tmy,
+    transform: transform,
+  );
+}
+
 /// Paints an image into the given rectangle on the canvas.
 ///
 /// The arguments have the following meanings:
@@ -499,12 +547,6 @@
   FilterQuality filterQuality = FilterQuality.low,
   bool isAntiAlias = false,
 }) {
-  assert(canvas != null);
-  assert(image != null);
-  assert(alignment != null);
-  assert(repeat != null);
-  assert(flipHorizontally != null);
-  assert(isAntiAlias != null);
   assert(
     image.debugGetOpenHandleStackTraces()?.isNotEmpty ?? true,
     'Cannot paint an image that is disposed.\n'
@@ -558,13 +600,22 @@
   bool invertedCanvas = false;
   // Output size and destination rect are fully calculated.
   if (!kReleaseMode) {
+    // We can use the devicePixelRatio of the views directly here (instead of
+    // going through a MediaQuery) because if it changes, whatever is aware of
+    // the MediaQuery will be repainting the image anyways.
+    // Furthermore, for the memory check below we just assume that all images
+    // are decoded for the view with the highest device pixel ratio and use that
+    // as an upper bound for the display size of the image.
+    final double maxDevicePixelRatio = PaintingBinding.instance.platformDispatcher.views.fold(
+      0.0,
+      (double previousValue, ui.FlutterView view) => math.max(previousValue, view.devicePixelRatio),
+    );
+
     final ImageSizeInfo sizeInfo = ImageSizeInfo(
       // Some ImageProvider implementations may not have given this.
       source: debugImageLabel ?? '<Unknown Image(${image.width}×${image.height})>',
       imageSize: Size(image.width.toDouble(), image.height.toDouble()),
-      // It's ok to use this instead of a MediaQuery because if this changes,
-      // whatever is aware of the MediaQuery will be repainting the image anyway.
-      displaySize: outputSize * PaintingBinding.instance.window.devicePixelRatio,
+      displaySize: outputSize * maxDevicePixelRatio,
     );
     assert(() {
       if (debugInvertOversizedImages &&
@@ -576,7 +627,8 @@
           exception: 'Image $debugImageLabel has a display size of '
             '$outputWidth×$outputHeight but a decode size of '
             '${image.width}×${image.height}, which uses an additional '
-            '${overheadInKilobytes}KB.\n\n'
+            '${overheadInKilobytes}KB (assuming a device pixel ratio of '
+            '$maxDevicePixelRatio).\n\n'
             'Consider resizing the asset ahead of time, supplying a cacheWidth '
             'parameter of $outputWidth, a cacheHeight parameter of '
             '$outputHeight, or using a ResizeImage.',
@@ -603,7 +655,7 @@
       return true;
     }());
     // Avoid emitting events that are the same as those emitted in the last frame.
-    if (!_lastFrameImageSizeInfo.contains(sizeInfo)) {
+    if (!kReleaseMode && !_lastFrameImageSizeInfo.contains(sizeInfo)) {
       final ImageSizeInfo? existingSizeInfo = _pendingImageSizeInfo[sizeInfo.source];
       if (existingSizeInfo == null || existingSizeInfo.displaySizeInBytes < sizeInfo.displaySizeInBytes) {
         _pendingImageSizeInfo[sizeInfo.source!] = sizeInfo;
@@ -630,7 +682,8 @@
   if (needSave) {
     canvas.save();
   }
-  if (repeat != ImageRepeat.noRepeat) {
+  if (repeat != ImageRepeat.noRepeat && centerSlice != null) {
+    // Don't clip if an image shader is used.
     canvas.clipRect(rect);
   }
   if (flipHorizontally) {
@@ -646,9 +699,12 @@
     if (repeat == ImageRepeat.noRepeat) {
       canvas.drawImageRect(image, sourceRect, destinationRect, paint);
     } else {
-      for (final Rect tileRect in _generateImageTileRects(rect, destinationRect, repeat)) {
-        canvas.drawImageRect(image, sourceRect, tileRect, paint);
-      }
+      final ImageTilingInfo info = createTilingInfo(repeat, rect, destinationRect, sourceRect);
+      final ImageShader shader = ImageShader(image, info.tmx, info.tmy, info.transform.storage, filterQuality: filterQuality);
+      canvas.drawRect(
+        rect,
+        paint..shader = shader
+      );
     }
   } else {
     canvas.scale(1 / scale);
@@ -669,7 +725,7 @@
   }
 }
 
-Iterable<Rect> _generateImageTileRects(Rect outputRect, Rect fundamentalRect, ImageRepeat repeat) {
+List<Rect> _generateImageTileRects(Rect outputRect, Rect fundamentalRect, ImageRepeat repeat) {
   int startX = 0;
   int startY = 0;
   int stopX = 0;
diff --git a/framework/lib/src/painting/edge_insets.dart b/framework/lib/src/painting/edge_insets.dart
index 4515508..26e9c2d 100644
--- a/framework/lib/src/painting/edge_insets.dart
+++ b/framework/lib/src/painting/edge_insets.dart
@@ -64,7 +64,6 @@
 
   /// The total offset in the given direction.
   double along(Axis axis) {
-    assert(axis != null);
     switch (axis) {
       case Axis.horizontal:
         return horizontal;
@@ -216,7 +215,6 @@
   ///
   /// {@macro dart.ui.shadow.lerp}
   static EdgeInsetsGeometry? lerp(EdgeInsetsGeometry? a, EdgeInsetsGeometry? b, double t) {
-    assert(t != null);
     if (a == null && b == null) {
       return null;
     }
@@ -604,7 +602,6 @@
   ///
   /// {@macro dart.ui.shadow.lerp}
   static EdgeInsets? lerp(EdgeInsets? a, EdgeInsets? b, double t) {
-    assert(t != null);
     if (a == null && b == null) {
       return null;
     }
@@ -871,7 +868,6 @@
   ///
   /// {@macro dart.ui.shadow.lerp}
   static EdgeInsetsDirectional? lerp(EdgeInsetsDirectional? a, EdgeInsetsDirectional? b, double t) {
-    assert(t != null);
     if (a == null && b == null) {
       return null;
     }
diff --git a/framework/lib/src/painting/flutter_logo.dart b/framework/lib/src/painting/flutter_logo.dart
index 42eb084..c3c31ee 100644
--- a/framework/lib/src/painting/flutter_logo.dart
+++ b/framework/lib/src/painting/flutter_logo.dart
@@ -44,10 +44,7 @@
     this.textColor = const Color(0xFF757575),
     this.style = FlutterLogoStyle.markOnly,
     this.margin = EdgeInsets.zero,
-  }) : assert(textColor != null),
-       assert(style != null),
-       assert(margin != null),
-       _position = identical(style, FlutterLogoStyle.markOnly) ? 0.0 : identical(style, FlutterLogoStyle.horizontal) ? 1.0 : -1.0,
+  }) : _position = identical(style, FlutterLogoStyle.markOnly) ? 0.0 : identical(style, FlutterLogoStyle.horizontal) ? 1.0 : -1.0,
        _opacity = 1.0;
 
   const FlutterLogoDecoration._(this.textColor, this.style, this.margin, this._position, this._opacity);
@@ -78,12 +75,7 @@
   @override
   bool debugAssertIsValid() {
     assert(
-      textColor != null
-        && style != null
-        && margin != null
-        && _position != null
-        && _position.isFinite
-        && _opacity != null
+      _position.isFinite
         && _opacity >= 0.0
         && _opacity <= 1.0,
     );
@@ -107,7 +99,6 @@
   ///
   ///  * [Decoration.lerp], which interpolates between arbitrary decorations.
   static FlutterLogoDecoration? lerp(FlutterLogoDecoration? a, FlutterLogoDecoration? b, double t) {
-    assert(t != null);
     assert(a == null || a.debugAssertIsValid());
     assert(b == null || b.debugAssertIsValid());
     if (a == null && b == null) {
@@ -218,8 +209,7 @@
 /// An object that paints a [BoxDecoration] into a canvas.
 class _FlutterLogoPainter extends BoxPainter {
   _FlutterLogoPainter(this._config)
-      : assert(_config != null),
-        assert(_config.debugAssertIsValid()),
+      : assert(_config.debugAssertIsValid()),
         super(null) {
     _prepareText();
   }
diff --git a/framework/lib/src/painting/fractional_offset.dart b/framework/lib/src/painting/fractional_offset.dart
index 130c9d1..d32957c 100644
--- a/framework/lib/src/painting/fractional_offset.dart
+++ b/framework/lib/src/painting/fractional_offset.dart
@@ -56,17 +56,13 @@
   ///
   /// The [dx] and [dy] arguments must not be null.
   const FractionalOffset(double dx, double dy)
-    : assert(dx != null),
-      assert(dy != null),
-      super(dx * 2.0 - 1.0, dy * 2.0 - 1.0);
+    : super(dx * 2.0 - 1.0, dy * 2.0 - 1.0);
 
   /// Creates a fractional offset from a specific offset and size.
   ///
   /// The returned [FractionalOffset] describes the position of the
   /// [Offset] in the [Size], as a fraction of the [Size].
   factory FractionalOffset.fromOffsetAndSize(Offset offset, Size size) {
-    assert(size != null);
-    assert(offset != null);
     return FractionalOffset(
       offset.dx / size.width,
       offset.dy / size.height,
@@ -180,7 +176,6 @@
   ///
   /// {@macro dart.ui.shadow.lerp}
   static FractionalOffset? lerp(FractionalOffset? a, FractionalOffset? b, double t) {
-    assert(t != null);
     if (a == null && b == null) {
       return null;
     }
diff --git a/framework/lib/src/painting/geometry.dart b/framework/lib/src/painting/geometry.dart
index a662de3..a6b7fc0 100644
--- a/framework/lib/src/painting/geometry.dart
+++ b/framework/lib/src/painting/geometry.dart
@@ -45,12 +45,6 @@
   double verticalOffset = 0.0,
   double margin = 10.0,
 }) {
-  assert(size != null);
-  assert(childSize != null);
-  assert(target != null);
-  assert(verticalOffset != null);
-  assert(preferBelow != null);
-  assert(margin != null);
   // VERTICAL DIRECTION
   final bool fitsBelow = target.dy + verticalOffset + childSize.height <= size.height - margin;
   final bool fitsAbove = target.dy - verticalOffset - childSize.height >= margin;
diff --git a/framework/lib/src/painting/gradient.dart b/framework/lib/src/painting/gradient.dart
index 7e9fb21..810eca6 100644
--- a/framework/lib/src/painting/gradient.dart
+++ b/framework/lib/src/painting/gradient.dart
@@ -20,11 +20,8 @@
 
 /// Calculate the color at position [t] of the gradient defined by [colors] and [stops].
 Color _sample(List<Color> colors, List<double> stops, double t) {
-  assert(colors != null);
   assert(colors.isNotEmpty);
-  assert(stops != null);
   assert(stops.isNotEmpty);
-  assert(t != null);
   if (t <= stops.first) {
     return colors.first;
   }
@@ -107,7 +104,6 @@
 
   @override
   Matrix4 transform(Rect bounds, {TextDirection? textDirection}) {
-    assert(bounds != null);
     final double sinRadians = math.sin(radians);
     final double oneMinusCosRadians = 1 - math.cos(radians);
     final Offset center = bounds.center;
@@ -170,7 +166,7 @@
     required this.colors,
     this.stops,
     this.transform,
-  }) : assert(colors != null);
+  });
 
   /// The colors the gradient should obtain at each of the stops.
   ///
@@ -311,7 +307,6 @@
   ///
   /// {@macro dart.ui.shadow.lerp}
   static Gradient? lerp(Gradient? a, Gradient? b, double t) {
-    assert(t != null);
     Gradient? result;
     if (b != null) {
       result = b.lerpFrom(a, t); // if a is null, this must return non-null
@@ -388,9 +383,7 @@
     super.stops,
     this.tileMode = TileMode.clamp,
     super.transform,
-  }) : assert(begin != null),
-       assert(end != null),
-       assert(tileMode != null);
+  });
 
   /// The offset at which stop 0.0 of the gradient is placed.
   ///
@@ -475,7 +468,7 @@
 
   /// Linearly interpolate between two [LinearGradient]s.
   ///
-  /// If either gradient is null, this function linearly interpolates from a
+  /// If either gradient is null, this function linearly interpolates from
   /// a gradient that matches the other gradient in [begin], [end], [stops] and
   /// [tileMode] and with the same [colors] but transparent (using [scale]).
   ///
@@ -493,7 +486,6 @@
   /// Values for `t` are usually obtained from an [Animation<double>], such as
   /// an [AnimationController].
   static LinearGradient? lerp(LinearGradient? a, LinearGradient? b, double t) {
-    assert(t != null);
     if (a == null && b == null) {
       return null;
     }
@@ -644,10 +636,7 @@
     this.focal,
     this.focalRadius = 0.0,
     super.transform,
-  }) : assert(center != null),
-       assert(radius != null),
-       assert(tileMode != null),
-       assert(focalRadius != null);
+  });
 
   /// The center of the gradient, as an offset into the (-1.0, -1.0) x (1.0, 1.0)
   /// square describing the gradient which will be mapped onto the paint box.
@@ -758,7 +747,7 @@
 
   /// Linearly interpolate between two [RadialGradient]s.
   ///
-  /// If either gradient is null, this function linearly interpolates from a
+  /// If either gradient is null, this function linearly interpolates from
   /// a gradient that matches the other gradient in [center], [radius], [stops] and
   /// [tileMode] and with the same [colors] but transparent (using [scale]).
   ///
@@ -776,7 +765,6 @@
   /// Values for `t` are usually obtained from an [Animation<double>], such as
   /// an [AnimationController].
   static RadialGradient? lerp(RadialGradient? a, RadialGradient? b, double t) {
-    assert(t != null);
     if (a == null && b == null) {
       return null;
     }
@@ -943,10 +931,7 @@
     super.stops,
     this.tileMode = TileMode.clamp,
     super.transform,
-  }) : assert(center != null),
-       assert(startAngle != null),
-       assert(endAngle != null),
-       assert(tileMode != null);
+  });
 
   /// The center of the gradient, as an offset into the (-1.0, -1.0) x (1.0, 1.0)
   /// square describing the gradient which will be mapped onto the paint box.
@@ -1047,7 +1032,6 @@
   /// Values for `t` are usually obtained from an [Animation<double>], such as
   /// an [AnimationController].
   static SweepGradient? lerp(SweepGradient? a, SweepGradient? b, double t) {
-    assert(t != null);
     if (a == null && b == null) {
       return null;
     }
diff --git a/framework/lib/src/painting/image_cache.dart b/framework/lib/src/painting/image_cache.dart
index e86eaa5..648ceea 100644
--- a/framework/lib/src/painting/image_cache.dart
+++ b/framework/lib/src/painting/image_cache.dart
@@ -99,7 +99,6 @@
   /// returning it to its original value will therefore immediately clear the
   /// cache.
   set maximumSize(int value) {
-    assert(value != null);
     assert(value >= 0);
     if (value == maximumSize) {
       return;
@@ -139,7 +138,6 @@
   /// returning it to its original value will therefore immediately clear the
   /// cache.
   set maximumSizeBytes(int value) {
-    assert(value != null);
     assert(value >= 0);
     if (value == _maximumSizeBytes) {
       return;
@@ -240,7 +238,6 @@
   ///
   ///  * [ImageProvider], for providing images to the [Image] widget.
   bool evict(Object key, { bool includeLive = true }) {
-    assert(includeLive != null);
     if (includeLive) {
       // Remove from live images - the cache will not be able to mark
       // it as complete, and it might be getting evicted because it
@@ -324,8 +321,6 @@
   /// no completers are cached and `null` is returned instead of a new
   /// completer.
   ImageStreamCompleter? putIfAbsent(Object key, ImageStreamCompleter Function() loader, { ImageErrorListener? onError }) {
-    assert(key != null);
-    assert(loader != null);
     TimelineTask? timelineTask;
     TimelineTask? listenerTask;
     if (!kReleaseMode) {
@@ -613,8 +608,7 @@
   _CachedImageBase(
     this.completer, {
     this.sizeBytes,
-  }) : assert(completer != null),
-       handle = completer.keepAlive();
+  }) : handle = completer.keepAlive();
 
   final ImageStreamCompleter completer;
   int? sizeBytes;
diff --git a/framework/lib/src/painting/image_provider.dart b/framework/lib/src/painting/image_provider.dart
index da46c3b..fbc9ccc 100644
--- a/framework/lib/src/painting/image_provider.dart
+++ b/framework/lib/src/painting/image_provider.dart
@@ -4,7 +4,7 @@
 
 import 'dart:async';
 import 'dart:io';
-import 'package:engine/ui.dart' as ui show Codec, ImmutableBuffer;
+import 'package:engine/ui.dart' as ui;
 import 'package:engine/ui.dart' show Locale, Size, TextDirection;
 
 import 'package:flute/foundation.dart';
@@ -175,12 +175,11 @@
 ///  * [ResizeImage], which uses this to override the `cacheWidth`,
 ///    `cacheHeight`, and `allowUpscaling` parameters.
 @Deprecated(
-  'Use DecoderBufferCallback with ImageProvider.loadBuffer instead. '
+  'Use ImageDecoderCallback with ImageProvider.loadImage instead. '
   'This feature was deprecated after v2.13.0-1.0.pre.',
 )
 typedef DecoderCallback = Future<ui.Codec> Function(Uint8List buffer, {int? cacheWidth, int? cacheHeight, bool allowUpscaling});
 
-
 /// Performs the decode process for use in [ImageProvider.loadBuffer].
 ///
 /// This callback allows decoupling of the `cacheWidth`, `cacheHeight`, and
@@ -191,8 +190,25 @@
 ///
 ///  * [ResizeImage], which uses this to override the `cacheWidth`,
 ///    `cacheHeight`, and `allowUpscaling` parameters.
+@Deprecated(
+  'Use ImageDecoderCallback with ImageProvider.loadImage instead. '
+  'This feature was deprecated after v3.7.0-1.4.pre.',
+)
 typedef DecoderBufferCallback = Future<ui.Codec> Function(ui.ImmutableBuffer buffer, {int? cacheWidth, int? cacheHeight, bool allowUpscaling});
 
+/// Performs the decode process for use in [ImageProvider.loadImage].
+///
+/// This callback allows decoupling of the `getTargetSize` parameter from
+/// implementations of [ImageProvider] that do not expose it.
+///
+/// See also:
+///
+///  * [ResizeImage], which uses this to load images at specific sizes.
+typedef ImageDecoderCallback = Future<ui.Codec> Function(
+  ui.ImmutableBuffer buffer, {
+  ui.TargetImageSizeCallback? getTargetSize,
+});
+
 /// Identifies an image without committing to the precise final asset. This
 /// allows a set of images to be identified and for the precise image to later
 /// be resolved based on the environment, e.g. the device pixel ratio.
@@ -348,7 +364,6 @@
   /// See the Lifecycle documentation on [ImageProvider] for more information.
   @nonVirtual
   ImageStream resolve(ImageConfiguration configuration) {
-    assert(configuration != null);
     final ImageStream stream = createStream(configuration);
     // Load the key (potentially asynchronously), set up an error handling zone,
     // and call resolveStreamForKey.
@@ -407,7 +422,6 @@
     required ImageConfiguration configuration,
     ImageErrorListener? handleError,
   }) {
-    assert(configuration != null);
     final Completer<ImageCacheStatus?> completer = Completer<ImageCacheStatus?>();
     _createErrorHandlerAndKey(
       configuration,
@@ -455,9 +469,9 @@
         return;
       }
       if (!didError) {
+        didError = true;
         errorCallback(obtainedKey, exception, stack);
       }
-      didError = true;
     }
 
     Future<T> key;
@@ -510,7 +524,22 @@
     }
     final ImageStreamCompleter? completer = PaintingBinding.instance.imageCache.putIfAbsent(
       key,
-      () => loadBuffer(key, PaintingBinding.instance.instantiateImageCodecFromBuffer),
+      () {
+        ImageStreamCompleter result = loadImage(key, PaintingBinding.instance.instantiateImageCodecWithSize);
+        // This check exists as a fallback for backwards compatibility until the
+        // deprecated `loadBuffer()` method is removed. Until then, ImageProvider
+        // subclasses may have only overridden `loadBuffer()`, in which case the
+        // base implementation of `loadWithSize()` will return a sentinel value
+        // of type `_AbstractImageStreamCompleter`.
+        if (result is _AbstractImageStreamCompleter) {
+          result = loadBuffer(key, PaintingBinding.instance.instantiateImageCodecFromBuffer);
+          if (result is _AbstractImageStreamCompleter) {
+            // Same fallback as above but for the deprecated `load()` method.
+            result = load(key, PaintingBinding.instance.instantiateImageCodec);
+          }
+        }
+        return result;
+      },
       onError: handleError,
     );
     if (completer != null) {
@@ -594,7 +623,7 @@
   ///  * [ResizeImage], for modifying the key to account for cache dimensions.
   @protected
   @Deprecated(
-    'Implement loadBuffer for faster image loading. '
+    'Implement loadImage for faster image loading. '
     'This feature was deprecated after v2.13.0-1.0.pre.',
   )
   ImageStreamCompleter load(T key, DecoderCallback decode) {
@@ -604,9 +633,10 @@
   /// Converts a key into an [ImageStreamCompleter], and begins fetching the
   /// image.
   ///
-  /// For backwards-compatibility the default implementation of this method calls
-  /// through to [ImageProvider.load]. However, implementors of this interface should
-  /// only override this method and not [ImageProvider.load], which is deprecated.
+  /// For backwards-compatibility the default implementation of this method returns
+  /// an object that will cause [resolveStreamForKey] to consult [load]. However,
+  /// implementors of this interface should only override this method and not
+  /// [load], which is deprecated.
   ///
   /// The [decode] callback provides the logic to obtain the codec for the
   /// image.
@@ -615,14 +645,42 @@
   ///
   ///  * [ResizeImage], for modifying the key to account for cache dimensions.
   @protected
+  @Deprecated(
+    'Implement loadImage for image loading. '
+    'This feature was deprecated after v3.7.0-1.4.pre.',
+  )
   ImageStreamCompleter loadBuffer(T key, DecoderBufferCallback decode) {
-    return load(key, PaintingBinding.instance.instantiateImageCodec);
+    return _AbstractImageStreamCompleter();
+  }
+
+  /// Converts a key into an [ImageStreamCompleter], and begins fetching the
+  /// image.
+  ///
+  /// For backwards-compatibility the default implementation of this method returns
+  /// an object that will cause [resolveStreamForKey] to consult [loadBuffer].
+  /// However, implementors of this interface should only override this method
+  /// and not [loadBuffer], which is deprecated.
+  ///
+  /// The [decode] callback provides the logic to obtain the codec for the
+  /// image.
+  ///
+  /// See also:
+  ///
+  ///  * [ResizeImage], for modifying the key to account for cache dimensions.
+  // TODO(tvolkert): make abstract (https://github.com/flutter/flutter/issues/119209)
+  @protected
+  ImageStreamCompleter loadImage(T key, ImageDecoderCallback decode) {
+    return _AbstractImageStreamCompleter();
   }
 
   @override
   String toString() => '${objectRuntimeType(this, 'ImageConfiguration')}()';
 }
 
+/// A class that exists to facilitate backwards compatibility in the transition
+/// from [ImageProvider.load] to [ImageProvider.loadBuffer] to [ImageProvider.loadImage]
+class _AbstractImageStreamCompleter extends ImageStreamCompleter {}
+
 /// Key for the image obtained by an [AssetImage] or [ExactAssetImage].
 ///
 /// This is used to identify the precise resource in the [imageCache].
@@ -635,9 +693,7 @@
     required this.bundle,
     required this.name,
     required this.scale,
-  }) : assert(bundle != null),
-       assert(name != null),
-       assert(scale != null);
+  });
 
   /// The bundle from which the image will be obtained.
   ///
@@ -679,6 +735,24 @@
   /// const constructors so that they can be used in const expressions.
   const AssetBundleImageProvider();
 
+  @override
+  ImageStreamCompleter loadImage(AssetBundleImageKey key, ImageDecoderCallback decode) {
+    InformationCollector? collector;
+    assert(() {
+      collector = () => <DiagnosticsNode>[
+            DiagnosticsProperty<ImageProvider>('Image provider', this),
+            DiagnosticsProperty<AssetBundleImageKey>('Image key', key),
+          ];
+      return true;
+    }());
+    return MultiFrameImageStreamCompleter(
+      codec: _loadAsync(key, decode: decode),
+      scale: key.scale,
+      debugLabel: key.name,
+      informationCollector: collector,
+    );
+  }
+
   /// Converts a key into an [ImageStreamCompleter], and begins fetching the
   /// image.
   @override
@@ -692,7 +766,7 @@
       return true;
     }());
     return MultiFrameImageStreamCompleter(
-      codec: _loadAsync(key, decode, null),
+      codec: _loadAsync(key, decodeBufferDeprecated: decode),
       scale: key.scale,
       debugLabel: key.name,
       informationCollector: collector,
@@ -710,7 +784,7 @@
       return true;
     }());
     return MultiFrameImageStreamCompleter(
-      codec: _loadAsync(key, null, decode),
+      codec: _loadAsync(key, decodeDeprecated: decode),
       scale: key.scale,
       debugLabel: key.name,
       informationCollector: collector,
@@ -722,9 +796,14 @@
   ///
   /// This function is used by [load].
   @protected
-  Future<ui.Codec> _loadAsync(AssetBundleImageKey key, DecoderBufferCallback? decode, DecoderCallback? decodeDepreacted) async {
+  Future<ui.Codec> _loadAsync(
+    AssetBundleImageKey key, {
+    ImageDecoderCallback? decode,
+    DecoderBufferCallback? decodeBufferDeprecated,
+    DecoderCallback? decodeDeprecated,
+  }) async {
     if (decode != null) {
-      ui.ImmutableBuffer? buffer;
+      ui.ImmutableBuffer buffer;
       // Hot reload/restart could change whether an asset bundle or key in a
       // bundle are available, or if it is a network backed bundle.
       try {
@@ -733,12 +812,20 @@
         PaintingBinding.instance.imageCache.evict(key);
         rethrow;
       }
-      if (buffer == null) {
-        PaintingBinding.instance.imageCache.evict(key);
-        throw StateError('Unable to read data');
-      }
       return decode(buffer);
     }
+    if (decodeBufferDeprecated != null) {
+      ui.ImmutableBuffer buffer;
+      // Hot reload/restart could change whether an asset bundle or key in a
+      // bundle are available, or if it is a network backed bundle.
+      try {
+        buffer = await key.bundle.loadBuffer(key.name);
+      } on FlutterError {
+        PaintingBinding.instance.imageCache.evict(key);
+        rethrow;
+      }
+      return decodeBufferDeprecated(buffer);
+    }
     ByteData data;
     // Hot reload/restart could change whether an asset bundle or key in a
     // bundle are available, or if it is a network backed bundle.
@@ -748,11 +835,7 @@
       PaintingBinding.instance.imageCache.evict(key);
       rethrow;
     }
-    if (data == null) {
-      PaintingBinding.instance.imageCache.evict(key);
-      throw StateError('Unable to read data');
-    }
-    return decodeDepreacted!(data.buffer.asUint8List());
+    return decodeDeprecated!(data.buffer.asUint8List());
   }
 }
 
@@ -803,8 +886,7 @@
     this.width,
     this.height,
     this.allowUpscaling = false,
-  }) : assert(width != null || height != null),
-       assert(allowUpscaling != null);
+  }) : assert(width != null || height != null);
 
   /// The [ImageProvider] that this class wraps.
   final ImageProvider imageProvider;
@@ -863,6 +945,7 @@
       );
       return decode(buffer, cacheWidth: width, cacheHeight: height, allowUpscaling: this.allowUpscaling);
     }
+
     final ImageStreamCompleter completer = imageProvider.loadBuffer(key._providerCacheKey, decodeResize);
     if (!kReleaseMode) {
       completer.debugLabel = '${completer.debugLabel} - Resized(${key._width}×${key._height})';
@@ -871,6 +954,36 @@
   }
 
   @override
+  ImageStreamCompleter loadImage(ResizeImageKey key, ImageDecoderCallback decode) {
+    Future<ui.Codec> decodeResize(ui.ImmutableBuffer buffer, {ui.TargetImageSizeCallback? getTargetSize}) {
+      assert(
+        getTargetSize == null,
+        'ResizeImage cannot be composed with another ImageProvider that applies '
+        'getTargetSize.',
+      );
+      return decode(buffer, getTargetSize: (int intrinsicWidth, int intrinsicHeight) {
+        int? targetWidth = width;
+        int? targetHeight = height;
+        if (!allowUpscaling) {
+          if (targetWidth != null && targetWidth > intrinsicWidth) {
+            targetWidth = intrinsicWidth;
+          }
+          if (targetHeight != null && targetHeight > intrinsicHeight) {
+            targetHeight = intrinsicHeight;
+          }
+        }
+        return ui.TargetImageSize(width: targetWidth, height: targetHeight);
+      });
+    }
+
+    final ImageStreamCompleter completer = imageProvider.loadImage(key._providerCacheKey, decodeResize);
+    if (!kReleaseMode) {
+      completer.debugLabel = '${completer.debugLabel} - Resized(${key._width}×${key._height})';
+    }
+    return completer;
+  }
+
+  @override
   Future<ResizeImageKey> obtainKey(ImageConfiguration configuration) {
     Completer<ResizeImageKey>? completer;
     // If the imageProvider.obtainKey future is synchronous, then we will be able to fill in result with
@@ -901,9 +1014,10 @@
 /// The image will be cached regardless of cache headers from the server.
 ///
 /// When a network image is used on the Web platform, the `cacheWidth` and
-/// `cacheHeight` parameters of the [DecoderCallback] are ignored as the Web
-/// engine delegates image decoding of network images to the Web, which does
-/// not support custom decode sizes.
+/// `cacheHeight` parameters of the [DecoderCallback] are only supported when the
+/// application is running with the CanvasKit renderer. When the application is using
+/// the HTML renderer, the web engine delegates image decoding of network images to the Web,
+/// which does not support custom decode sizes.
 ///
 /// See also:
 ///
@@ -933,6 +1047,9 @@
 
   @override
   ImageStreamCompleter loadBuffer(NetworkImage key, DecoderBufferCallback decode);
+
+  @override
+  ImageStreamCompleter loadImage(NetworkImage key, ImageDecoderCallback decode);
 }
 
 /// Decodes the given [File] object as an image, associating it with the given
@@ -949,9 +1066,7 @@
   /// Creates an object that decodes a [File] as an image.
   ///
   /// The arguments must not be null.
-  const FileImage(this.file, { this.scale = 1.0 })
-    : assert(file != null),
-      assert(scale != null);
+  const FileImage(this.file, { this.scale = 1.0 });
 
   /// The file to decode into an image.
   final File file;
@@ -967,7 +1082,7 @@
   @override
   ImageStreamCompleter load(FileImage key, DecoderCallback decode) {
     return MultiFrameImageStreamCompleter(
-      codec: _loadAsync(key, null, decode),
+      codec: _loadAsync(key, decodeDeprecated: decode),
       scale: key.scale,
       debugLabel: key.file.path,
       informationCollector: () => <DiagnosticsNode>[
@@ -979,7 +1094,7 @@
   @override
   ImageStreamCompleter loadBuffer(FileImage key, DecoderBufferCallback decode) {
     return MultiFrameImageStreamCompleter(
-      codec: _loadAsync(key, decode, null),
+      codec: _loadAsync(key, decodeBufferDeprecated: decode),
       scale: key.scale,
       debugLabel: key.file.path,
       informationCollector: () => <DiagnosticsNode>[
@@ -988,7 +1103,25 @@
     );
   }
 
-  Future<ui.Codec> _loadAsync(FileImage key, DecoderBufferCallback? decode, DecoderCallback? decodeDeprecated) async {
+  @override
+  @protected
+  ImageStreamCompleter loadImage(FileImage key, ImageDecoderCallback decode) {
+    return MultiFrameImageStreamCompleter(
+      codec: _loadAsync(key, decode: decode),
+      scale: key.scale,
+      debugLabel: key.file.path,
+      informationCollector: () => <DiagnosticsNode>[
+        ErrorDescription('Path: ${file.path}'),
+      ],
+    );
+  }
+
+  Future<ui.Codec> _loadAsync(
+    FileImage key, {
+    ImageDecoderCallback? decode,
+    DecoderBufferCallback? decodeBufferDeprecated,
+    DecoderCallback? decodeDeprecated,
+  }) async {
     assert(key == this);
 
     // TODO(jonahwilliams): making this sync caused test failures that seem to
@@ -1007,6 +1140,12 @@
       }
       return decode(await ui.ImmutableBuffer.fromUint8List(await file.readAsBytes()));
     }
+    if (decodeBufferDeprecated != null) {
+      if (file.runtimeType == File) {
+        return decodeBufferDeprecated(await ui.ImmutableBuffer.fromFilePath(file.path));
+      }
+      return decodeBufferDeprecated(await ui.ImmutableBuffer.fromUint8List(await file.readAsBytes()));
+    }
     return decodeDeprecated!(await file.readAsBytes());
   }
 
@@ -1044,9 +1183,7 @@
   /// Creates an object that decodes a [Uint8List] buffer as an image.
   ///
   /// The arguments must not be null.
-  const MemoryImage(this.bytes, { this.scale = 1.0 })
-    : assert(bytes != null),
-      assert(scale != null);
+  const MemoryImage(this.bytes, { this.scale = 1.0 });
 
   /// The bytes to decode into an image.
   ///
@@ -1074,7 +1211,7 @@
   @override
   ImageStreamCompleter load(MemoryImage key, DecoderCallback decode) {
     return MultiFrameImageStreamCompleter(
-      codec: _loadAsync(key, null, decode),
+      codec: _loadAsync(key, decodeDeprecated: decode),
       scale: key.scale,
       debugLabel: 'MemoryImage(${describeIdentity(key.bytes)})',
     );
@@ -1083,19 +1220,37 @@
   @override
   ImageStreamCompleter loadBuffer(MemoryImage key, DecoderBufferCallback decode) {
     return MultiFrameImageStreamCompleter(
-      codec: _loadAsync(key, decode, null),
+      codec: _loadAsync(key, decodeBufferDeprecated: decode),
       scale: key.scale,
       debugLabel: 'MemoryImage(${describeIdentity(key.bytes)})',
     );
   }
 
-  Future<ui.Codec> _loadAsync(MemoryImage key, DecoderBufferCallback? decode, DecoderCallback? decodeDepreacted) async {
+  @override
+  ImageStreamCompleter loadImage(MemoryImage key, ImageDecoderCallback decode) {
+    return MultiFrameImageStreamCompleter(
+      codec: _loadAsync(key, decode: decode),
+      scale: key.scale,
+      debugLabel: 'MemoryImage(${describeIdentity(key.bytes)})',
+    );
+  }
+
+  Future<ui.Codec> _loadAsync(
+    MemoryImage key, {
+    ImageDecoderCallback? decode,
+    DecoderBufferCallback? decodeBufferDeprecated,
+    DecoderCallback? decodeDeprecated,
+  }) async {
     assert(key == this);
     if (decode != null) {
       final ui.ImmutableBuffer buffer = await ui.ImmutableBuffer.fromUint8List(bytes);
       return decode(buffer);
     }
-    return decodeDepreacted!(bytes);
+    if (decodeBufferDeprecated != null) {
+      final ui.ImmutableBuffer buffer = await ui.ImmutableBuffer.fromUint8List(bytes);
+      return decodeBufferDeprecated(buffer);
+    }
+    return decodeDeprecated!(bytes);
   }
 
   @override
@@ -1203,8 +1358,7 @@
     this.scale = 1.0,
     this.bundle,
     this.package,
-  }) : assert(assetName != null),
-       assert(scale != null);
+  });
 
   /// The name of the asset.
   final String assetName;
@@ -1265,9 +1419,7 @@
   /// Creates a [NetworkImageLoadException] with the specified http [statusCode]
   /// and [uri].
   NetworkImageLoadException({required this.statusCode, required this.uri})
-      : assert(uri != null),
-        assert(statusCode != null),
-        _message = 'HTTP request failed, statusCode: $statusCode, $uri';
+      : _message = 'HTTP request failed, statusCode: $statusCode, $uri';
 
   /// The HTTP status code from the server.
   final int statusCode;
diff --git a/framework/lib/src/painting/image_resolution.dart b/framework/lib/src/painting/image_resolution.dart
index 4895c6b..b7e88ee 100644
--- a/framework/lib/src/painting/image_resolution.dart
+++ b/framework/lib/src/painting/image_resolution.dart
@@ -244,7 +244,7 @@
     this.assetName, {
     this.bundle,
     this.package,
-  }) : assert(assetName != null);
+  });
 
   /// The name of the main asset from the set of images to choose from. See the
   /// documentation for the [AssetImage] class itself for details.
diff --git a/framework/lib/src/painting/image_stream.dart b/framework/lib/src/painting/image_stream.dart
index d151696..272af2c 100644
--- a/framework/lib/src/painting/image_stream.dart
+++ b/framework/lib/src/painting/image_stream.dart
@@ -23,9 +23,7 @@
   /// Both the [image] and the [scale] must not be null.
   ///
   /// The [debugLabel] may be used to identify the source of this image.
-  const ImageInfo({ required this.image, this.scale = 1.0, this.debugLabel })
-    : assert(image != null),
-      assert(scale != null);
+  const ImageInfo({ required this.image, this.scale = 1.0, this.debugLabel });
 
   /// Creates an [ImageInfo] with a cloned [image].
   ///
@@ -167,7 +165,7 @@
     this.onImage, {
     this.onChunk,
     this.onError,
-  }) : assert(onImage != null);
+  });
 
   /// Callback for getting notified that an image is available.
   ///
@@ -623,7 +621,6 @@
   ///
   /// This callback will never fire if [removeListener] is never called.
   void addOnLastListenerRemovedCallback(VoidCallback callback) {
-    assert(callback != null);
     _checkDisposed();
     _onLastListenerRemovedCallbacks.add(callback);
   }
@@ -631,7 +628,6 @@
   /// Removes a callback previously supplied to
   /// [addOnLastListenerRemovedCallback].
   void removeOnLastListenerRemovedCallback(VoidCallback callback) {
-    assert(callback != null);
     _checkDisposed();
     _onLastListenerRemovedCallbacks.remove(callback);
   }
@@ -789,8 +785,7 @@
   /// argument on [FlutterErrorDetails] set to true, meaning that by default the
   /// message is only dumped to the console in debug mode (see [
   /// FlutterErrorDetails]).
-  OneFrameImageStreamCompleter(Future<ImageInfo> image, { InformationCollector? informationCollector })
-      : assert(image != null) {
+  OneFrameImageStreamCompleter(Future<ImageInfo> image, { InformationCollector? informationCollector }) {
     image.then<void>(setImage, onError: (Object error, StackTrace stack) {
       reportError(
         context: ErrorDescription('resolving a single-frame image stream'),
@@ -859,8 +854,7 @@
     String? debugLabel,
     Stream<ImageChunkEvent>? chunkEvents,
     InformationCollector? informationCollector,
-  }) : assert(codec != null),
-       _informationCollector = informationCollector,
+  }) : _informationCollector = informationCollector,
        _scale = scale {
     this.debugLabel = debugLabel;
     codec.then<void>(_handleCodecReady, onError: (Object error, StackTrace stack) {
diff --git a/framework/lib/src/painting/inline_span.dart b/framework/lib/src/painting/inline_span.dart
index e9e9cc1..1182338 100644
--- a/framework/lib/src/painting/inline_span.dart
+++ b/framework/lib/src/painting/inline_span.dart
@@ -60,9 +60,7 @@
     this.semanticsLabel,
     this.stringAttributes = const <ui.StringAttribute>[],
     this.recognizer,
-  }) : assert(text != null),
-       assert(isPlaceholder != null),
-       assert(isPlaceholder == false || (text == '\uFFFC' && semanticsLabel == null && recognizer == null)),
+  }) : assert(isPlaceholder == false || (text == '\uFFFC' && semanticsLabel == null && recognizer == null)),
        requiresOwnNode = isPlaceholder || recognizer != null;
 
   /// The text info for a [PlaceholderSpan].
diff --git a/framework/lib/src/painting/linear_border.dart b/framework/lib/src/painting/linear_border.dart
new file mode 100644
index 0000000..6fbd234
--- /dev/null
+++ b/framework/lib/src/painting/linear_border.dart
@@ -0,0 +1,389 @@
+// 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:engine/ui.dart' show lerpDouble;
+
+import 'package:flute/foundation.dart';
+
+import 'basic_types.dart';
+import 'borders.dart';
+import 'edge_insets.dart';
+
+/// Defines the relative size and alignment of one <LinearBorder> edge.
+///
+/// A [LinearBorder] defines a box outline as zero to four edges, each
+/// of which is rendered as a single line. The width and color of the
+/// lines is defined by [LinearBorder.side].
+///
+/// Each line's length is defined by [size], a value between 0.0 and 1.0
+/// (the default) which defines the length as a percentage of the
+/// length of a box edge.
+///
+/// When [size] is less than 1.0, the line is aligned within the
+/// available space according to [alignment], a value between -1.0 and
+/// 1.0.  The default is 0.0, which means centered, -1.0 means align on the
+/// "start" side, and 1.0 means align on the "end" side. The meaning of
+/// start and end depend on the current [TextDirection], see
+/// [Directionality].
+@immutable
+class LinearBorderEdge {
+  /// Defines one side of a [LinearBorder].
+  ///
+  /// The values of [size] and [alignment] must be between
+  /// 0.0 and 1.0, and -1.0 and 1.0 respectively.
+  const LinearBorderEdge({
+    this.size = 1.0,
+    this.alignment = 0.0,
+  }) : assert(size >= 0.0 && size <= 1.0);
+
+  /// A value between 0.0 and 1.0 that defines the length of the edge as a
+  /// percentage of the length of the corresponding box
+  /// edge. Default is 1.0.
+  final double size;
+
+  /// A value between -1.0 and 1.0 that defines how edges for which [size]
+  /// is less than 1.0 are aligned relative to the corresponding box edge.
+  ///
+  ///  * -1.0, aligned in the "start" direction. That's left
+  ///    for [TextDirection.ltr] and right for [TextDirection.rtl].
+  ///  * 0.0, centered.
+  ///  * 1.0, aligned in the "end" direction. That's right
+  ///    for [TextDirection.ltr] and left for [TextDirection.rtl].
+  final double alignment;
+
+  /// Linearly interpolates between two [LinearBorder]s.
+  ///
+  /// If both `a` and `b` are null then null is returned. If `a` is null
+  /// then we interpolate to `b` varying [size] from 0.0 to `b.size`. If `b`
+  /// is null then we interpolate from `a` varying size from `a.size` to zero.
+  /// Otherwise both values are interpolated.
+  static LinearBorderEdge? lerp(LinearBorderEdge? a, LinearBorderEdge? b, double t) {
+    if (a == null && b == null) {
+      return null;
+    }
+
+    a ??= LinearBorderEdge(alignment: b!.alignment, size: 0);
+    b ??= LinearBorderEdge(alignment: a.alignment, size: 0);
+
+    return LinearBorderEdge(
+      size: lerpDouble(a.size, b.size, t)!,
+      alignment: lerpDouble(a.alignment, b.alignment, t)!,
+    );
+  }
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other)) {
+      return true;
+    }
+    if (other.runtimeType != runtimeType) {
+      return false;
+    }
+    return other is LinearBorderEdge
+        && other.size == size
+        && other.alignment == alignment;
+  }
+
+  @override
+  int get hashCode => Object.hash(size, alignment);
+
+  @override
+  String toString() {
+    final StringBuffer s = StringBuffer('${objectRuntimeType(this, 'LinearBorderEdge')}(');
+    if (size != 1.0 ) {
+      s.write('size: $size');
+    }
+    if (alignment != 0) {
+      final String comma = size != 1.0 ? ', ' : '';
+      s.write('${comma}alignment: $alignment');
+    }
+    s.write(')');
+    return s.toString();
+  }
+}
+
+/// An [OutlinedBorder] like [BoxBorder] that allows one to define a rectangular (box) border
+/// in terms of zero to four [LinearBorderEdge]s, each of which is rendered as a single line.
+///
+/// The color and width of each line are defined by [side]. When [LinearBorder] is used
+/// with a class whose border sides and shape are defined by a [ButtonStyle], then a non-null
+/// [ButtonStyle.side] will override the one specified here. For example the [LinearBorder]
+/// in the [TextButton] example below adds a red underline to the button. This is because
+/// TextButton's `side` parameter overrides the `side` property of its [ButtonStyle.shape].
+///
+/// ```dart
+///  TextButton(
+///    style: TextButton.styleFrom(
+///      side: const BorderSide(color: Colors.red),
+///      shape: const LinearBorder(
+///        side: BorderSide(color: Colors.blue),
+///        bottom: LinearBorderEdge(),
+///      ),
+///    ),
+///    onPressed: () { },
+///    child: const Text('Red LinearBorder'),
+///  )
+///```
+///
+/// This class resolves itself against the current [TextDirection] (see [Directionality]).
+/// Start and end values resolve to left and right for [TextDirection.ltr] and to
+/// right and left for [TextDirection.rtl].
+///
+/// Convenience constructors are included for the common case where just one edge is specified:
+/// [LinearBorder.start], [LinearBorder.end], [LinearBorder.top], [LinearBorder.bottom].
+class LinearBorder extends OutlinedBorder {
+  /// Creates a rectangular box border that's rendered as zero to four lines.
+  const LinearBorder({
+    super.side,
+    this.start,
+    this.end,
+    this.top,
+    this.bottom,
+  });
+
+  /// Creates a rectangular box border with an edge on the left for [TextDirection.ltr]
+  /// or on the right for [TextDirection.rtl].
+  LinearBorder.start({
+    super.side,
+    double alignment = 0.0,
+    double size = 1.0
+  }) : start = LinearBorderEdge(alignment: alignment, size: size),
+       end = null,
+       top = null,
+       bottom = null;
+
+  /// Creates a rectangular box border with an edge on the right for [TextDirection.ltr]
+  /// or on the left for [TextDirection.rtl].
+  LinearBorder.end({
+    super.side,
+    double alignment = 0.0,
+    double size = 1.0
+  }) : start = null,
+       end = LinearBorderEdge(alignment: alignment, size: size),
+       top = null,
+       bottom = null;
+
+  /// Creates a rectangular box border with an edge on the top.
+  LinearBorder.top({
+    super.side,
+    double alignment = 0.0,
+    double size = 1.0
+  }) : start = null,
+       end = null,
+       top = LinearBorderEdge(alignment: alignment, size: size),
+       bottom = null;
+
+  /// Creates a rectangular box border with an edge on the bottom.
+  LinearBorder.bottom({
+    super.side,
+    double alignment = 0.0,
+    double size = 1.0
+  }) : start = null,
+       end = null,
+       top = null,
+       bottom = LinearBorderEdge(alignment: alignment, size: size);
+
+  /// No border.
+  static const LinearBorder none = LinearBorder();
+
+  /// Defines the left edge for [TextDirection.ltr] or the right
+  /// for [TextDirection.rtl].
+  final LinearBorderEdge? start;
+
+  /// Defines the right edge for [TextDirection.ltr] or the left
+  /// for [TextDirection.rtl].
+  final LinearBorderEdge? end;
+
+  /// Defines the top edge.
+  final LinearBorderEdge? top;
+
+  /// Defines the bottom edge.
+  final LinearBorderEdge? bottom;
+
+  @override
+  LinearBorder scale(double t) {
+    return LinearBorder(
+      side: side.scale(t),
+    );
+  }
+
+  @override
+  EdgeInsetsGeometry get dimensions {
+    final double width = side.width;
+    return EdgeInsetsDirectional.fromSTEB(
+      start == null ? 0.0 : width,
+      top == null ? 0.0 : width,
+      end == null ? 0.0 : width,
+      bottom == null ? 0.0 : width,
+    );
+  }
+
+  @override
+  ShapeBorder? lerpFrom(ShapeBorder? a, double t) {
+    if (a is LinearBorder) {
+      return LinearBorder(
+        side: BorderSide.lerp(a.side, side, t),
+        start: LinearBorderEdge.lerp(a.start, start, t),
+        end: LinearBorderEdge.lerp(a.end, end, t),
+        top: LinearBorderEdge.lerp(a.top, top, t),
+        bottom: LinearBorderEdge.lerp(a.bottom, bottom, t),
+      );
+    }
+    return super.lerpFrom(a, t);
+  }
+
+  @override
+  ShapeBorder? lerpTo(ShapeBorder? b, double t) {
+    if (b is LinearBorder) {
+      return LinearBorder(
+        side: BorderSide.lerp(side, b.side, t),
+        start: LinearBorderEdge.lerp(start, b.start, t),
+        end: LinearBorderEdge.lerp(end, b.end, t),
+        top: LinearBorderEdge.lerp(top, b.top, t),
+        bottom: LinearBorderEdge.lerp(bottom, b.bottom, t),
+      );
+    }
+    return super.lerpTo(b, t);
+  }
+
+  /// Returns a copy of this LinearBorder with the given fields replaced with
+  /// the new values.
+  @override
+  LinearBorder copyWith({
+    BorderSide? side,
+    LinearBorderEdge? start,
+    LinearBorderEdge? end,
+    LinearBorderEdge? top,
+    LinearBorderEdge? bottom,
+  }) {
+    return LinearBorder(
+      side: side ?? this.side,
+      start: start ?? this.start,
+      end: end ?? this.end,
+      top: top ?? this.top,
+      bottom: bottom ?? this.bottom,
+    );
+  }
+
+  @override
+  Path getInnerPath(Rect rect, { TextDirection? textDirection }) {
+    final Rect adjustedRect = dimensions.resolve(textDirection).deflateRect(rect);
+    return Path()
+      ..addRect(adjustedRect);
+  }
+
+  @override
+  Path getOuterPath(Rect rect, { TextDirection? textDirection }) {
+    return Path()
+      ..addRect(rect);
+  }
+
+  @override
+  void paint(Canvas canvas, Rect rect, { TextDirection? textDirection }) {
+    final EdgeInsets insets = dimensions.resolve(textDirection);
+    final bool rtl = textDirection == TextDirection.rtl;
+
+    final Path path = Path();
+    final Paint paint = Paint()
+      ..strokeWidth = 0.0;
+
+    void drawEdge(Rect rect, Color color) {
+      paint.color = color;
+      path.reset();
+      path.moveTo(rect.left, rect.top);
+      if (rect.width == 0.0) {
+        paint.style = PaintingStyle.stroke;
+        path.lineTo(rect.left, rect.bottom);
+      } else if (rect.height == 0.0) {
+        paint.style = PaintingStyle.stroke;
+        path.lineTo(rect.right, rect.top);
+      } else {
+        paint.style = PaintingStyle.fill;
+        path.lineTo(rect.right, rect.top);
+        path.lineTo(rect.right, rect.bottom);
+        path.lineTo(rect.left, rect.bottom);
+      }
+      canvas.drawPath(path, paint);
+    }
+
+    if (start != null && start!.size != 0.0 && side.style != BorderStyle.none) {
+      final Rect insetRect = Rect.fromLTWH(rect.left, rect.top + insets.top, rect.width, rect.height - insets.vertical);
+      final double x = rtl ? rect.right - insets.right : rect.left;
+      final double width = rtl ? insets.right : insets.left;
+      final double height = insetRect.height * start!.size;
+      final double y = (insetRect.height - height) * ((start!.alignment + 1.0) / 2.0);
+      final Rect r = Rect.fromLTWH(x, y, width, height);
+      drawEdge(r, side.color);
+    }
+
+    if (end != null && end!.size != 0.0 && side.style != BorderStyle.none) {
+      final Rect insetRect = Rect.fromLTWH(rect.left, rect.top + insets.top, rect.width, rect.height - insets.vertical);
+      final double x = rtl ? rect.left : rect.right - insets.right;
+      final double width = rtl ? insets.left : insets.right;
+      final double height = insetRect.height * end!.size;
+      final double y = (insetRect.height - height) * ((end!.alignment + 1.0) / 2.0);
+      final Rect r = Rect.fromLTWH(x, y, width, height);
+      drawEdge(r, side.color);
+    }
+
+    if (top != null && top!.size != 0.0 && side.style != BorderStyle.none) {
+      final double width = rect.width * top!.size;
+      final double startX = (rect.width - width) * ((top!.alignment + 1.0) / 2.0);
+      final double x = rtl ? rect.width - startX - width : startX;
+      final Rect r = Rect.fromLTWH(x, rect.top, width, insets.top);
+      drawEdge(r, side.color);
+    }
+
+    if (bottom != null && bottom!.size != 0.0 && side.style != BorderStyle.none) {
+      final double width = rect.width * bottom!.size;
+      final double startX = (rect.width - width) * ((bottom!.alignment + 1.0) / 2.0);
+      final double x = rtl ? rect.width - startX - width: startX;
+      final Rect r = Rect.fromLTWH(x, rect.bottom - insets.bottom, width, side.width);
+      drawEdge(r, side.color);
+    }
+  }
+
+  @override
+  bool operator ==(Object other) {
+    if (identical(this, other)) {
+      return true;
+    }
+    if (other.runtimeType != runtimeType) {
+      return false;
+    }
+    return other is LinearBorder
+      && other.side == side
+      && other.start == start
+      && other.end == end
+      && other.top == top
+      && other.bottom == bottom;
+  }
+
+  @override
+  int get hashCode => Object.hash(side, start, end, top, bottom);
+
+  @override
+  String toString() {
+    if (this == LinearBorder.none) {
+      return 'LinearBorder.none';
+    }
+
+    final StringBuffer s = StringBuffer('${objectRuntimeType(this, 'LinearBorder')}(side: $side');
+
+    if (start != null ) {
+      s.write(', start: $start');
+    }
+    if (end != null ) {
+      s.write(', end: $end');
+    }
+    if (top != null ) {
+      s.write(', top: $top');
+    }
+    if (bottom != null ) {
+      s.write(', bottom: $bottom');
+    }
+    s.write(')');
+    return s.toString();
+  }
+}
diff --git a/framework/lib/src/painting/matrix_utils.dart b/framework/lib/src/painting/matrix_utils.dart
index 04beff2..13b11e3 100644
--- a/framework/lib/src/painting/matrix_utils.dart
+++ b/framework/lib/src/painting/matrix_utils.dart
@@ -18,7 +18,6 @@
   ///
   /// Otherwise, returns null.
   static Offset? getAsTranslation(Matrix4 transform) {
-    assert(transform != null);
     final Float64List values = transform.storage;
     // Values are stored in column-major order.
     if (values[0] == 1.0 && // col 1
@@ -45,7 +44,6 @@
   ///
   /// Otherwise, returns null.
   static double? getAsScale(Matrix4 transform) {
-    assert(transform != null);
     final Float64List values = transform.storage;
     // Values are stored in column-major order.
     if (values[1] == 0.0 && // col 1 (value 0 is the scale)
@@ -81,7 +79,6 @@
     if (b == null) {
       return isIdentity(a);
     }
-    assert(a != null && b != null);
     return a.storage[0] == b.storage[0]
         && a.storage[1] == b.storage[1]
         && a.storage[2] == b.storage[2]
@@ -102,7 +99,6 @@
 
   /// Whether the given matrix is the identity matrix.
   static bool isIdentity(Matrix4 a) {
-    assert(a != null);
     return a.storage[0] == 1.0 // col 1
         && a.storage[1] == 0.0
         && a.storage[2] == 0.0
@@ -437,7 +433,6 @@
   /// The transformed rect is then projected back into the plane with z equals
   /// 0.0 before computing its bounding rect.
   static Rect inverseTransformRect(Matrix4 transform, Rect rect) {
-    assert(rect != null);
     // As exposed by `unrelated_type_equality_checks`, this assert was a no-op.
     // Fixing it introduces a bunch of runtime failures; for more context see:
     // https://github.com/flutter/flutter/pull/31568
@@ -455,8 +450,8 @@
   ///
   /// The `radius` simulates the radius of the cylinder the plane is being
   /// wrapped onto. If the transformation is applied to a 0-dimensional dot
-  /// instead of a plane, the dot would simply translate by +/- `radius` pixels
-  /// along the `orientation` [Axis] when rotating from 0 to +/- 90 degrees.
+  /// instead of a plane, the dot would translate by ± `radius` pixels
+  /// along the `orientation` [Axis] when rotating from 0 to ±90 degrees.
   ///
   /// A positive radius means the object is closest at 0 `angle` and a negative
   /// radius means the object is closest at π `angle` or 180 degrees.
@@ -478,7 +473,7 @@
   /// The `orientation` is the direction of the rotation axis.
   ///
   /// Because the viewing position is a point, it's never possible to see the
-  /// outer side of the cylinder at or past +/- π / 2 or 90 degrees and it's
+  /// outer side of the cylinder at or past ±π/2 or 90 degrees and it's
   /// almost always possible to end up seeing the inner side of the cylinder
   /// or the back side of the transformed plane before π / 2 when perspective > 0.
   static Matrix4 createCylindricalProjectionTransform({
@@ -487,10 +482,7 @@
     double perspective = 0.001,
     Axis orientation = Axis.vertical,
   }) {
-    assert(radius != null);
-    assert(angle != null);
     assert(perspective >= 0 && perspective <= 1.0);
-    assert(orientation != null);
 
     // Pre-multiplied matrix of a projection matrix and a view matrix.
     //
@@ -561,8 +553,7 @@
     super.showName,
     super.defaultValue,
     super.level,
-  }) : assert(showName != null),
-       assert(level != null);
+  });
 
   @override
   String valueToString({ TextTreeConfiguration? parentConfiguration }) {
diff --git a/framework/lib/src/painting/rounded_rectangle_border.dart b/framework/lib/src/painting/rounded_rectangle_border.dart
index baf1d41..36e4e5c 100644
--- a/framework/lib/src/painting/rounded_rectangle_border.dart
+++ b/framework/lib/src/painting/rounded_rectangle_border.dart
@@ -30,8 +30,7 @@
   const RoundedRectangleBorder({
     super.side,
     this.borderRadius = BorderRadius.zero,
-  }) : assert(side != null),
-       assert(borderRadius != null);
+  });
 
   /// The radii for each corner.
   final BorderRadiusGeometry borderRadius;
@@ -46,7 +45,6 @@
 
   @override
   ShapeBorder? lerpFrom(ShapeBorder? a, double t) {
-    assert(t != null);
     if (a is RoundedRectangleBorder) {
       return RoundedRectangleBorder(
         side: BorderSide.lerp(a.side, side, t),
@@ -66,7 +64,6 @@
 
   @override
   ShapeBorder? lerpTo(ShapeBorder? b, double t) {
-    assert(t != null);
     if (b is RoundedRectangleBorder) {
       return RoundedRectangleBorder(
         side: BorderSide.lerp(side, b.side, t),
@@ -165,9 +162,7 @@
     this.borderRadius = BorderRadius.zero,
     required this.circularity,
     required this.eccentricity,
-  }) : assert(side != null),
-       assert(borderRadius != null),
-       assert(circularity != null);
+  });
 
   final BorderRadiusGeometry borderRadius;
   final double circularity;
@@ -185,7 +180,6 @@
 
   @override
   ShapeBorder? lerpFrom(ShapeBorder? a, double t) {
-    assert(t != null);
     if (a is RoundedRectangleBorder) {
       return _RoundedRectangleToCircleBorder(
         side: BorderSide.lerp(a.side, side, t),
diff --git a/framework/lib/src/painting/shape_decoration.dart b/framework/lib/src/painting/shape_decoration.dart
index de32872..43fc655 100644
--- a/framework/lib/src/painting/shape_decoration.dart
+++ b/framework/lib/src/painting/shape_decoration.dart
@@ -76,8 +76,7 @@
     this.gradient,
     this.shadows,
     required this.shape,
-  }) : assert(!(color != null && gradient != null)),
-       assert(shape != null);
+  }) : assert(!(color != null && gradient != null));
 
   /// Creates a shape decoration configured to match a [BoxDecoration].
   ///
@@ -91,7 +90,6 @@
   /// transition from a [BoxShape.circle] to [BoxShape.rectangle]).
   factory ShapeDecoration.fromBoxDecoration(BoxDecoration source) {
     final ShapeBorder shape;
-    assert(source.shape != null);
     switch (source.shape) {
       case BoxShape.circle:
         if (source.border != null) {
@@ -227,7 +225,6 @@
   ///    and which use [ShapeDecoration.lerp] when interpolating two
   ///    [ShapeDecoration]s or a [ShapeDecoration] to or from null.
   static ShapeDecoration? lerp(ShapeDecoration? a, ShapeDecoration? b, double t) {
-    assert(t != null);
     if (a == null && b == null) {
       return null;
     }
@@ -299,8 +296,7 @@
 /// An object that paints a [ShapeDecoration] into a canvas.
 class _ShapeDecorationPainter extends BoxPainter {
   _ShapeDecorationPainter(this._decoration, VoidCallback onChanged)
-    : assert(_decoration != null),
-      super(onChanged);
+    : super(onChanged);
 
   final ShapeDecoration _decoration;
 
@@ -318,7 +314,6 @@
   VoidCallback get onChanged => super.onChanged!;
 
   void _precache(Rect rect, TextDirection? textDirection) {
-    assert(rect != null);
     if (rect == _lastRect && textDirection == _lastTextDirection) {
       return;
     }
@@ -409,7 +404,6 @@
 
   @override
   void paint(Canvas canvas, Offset offset, ImageConfiguration configuration) {
-    assert(configuration != null);
     assert(configuration.size != null);
     final Rect rect = offset & configuration.size!;
     final TextDirection? textDirection = configuration.textDirection;
diff --git a/framework/lib/src/painting/stadium_border.dart b/framework/lib/src/painting/stadium_border.dart
index 0866aa5..48a181f 100644
--- a/framework/lib/src/painting/stadium_border.dart
+++ b/framework/lib/src/painting/stadium_border.dart
@@ -27,14 +27,13 @@
   /// Create a stadium border.
   ///
   /// The [side] argument must not be null.
-  const StadiumBorder({ super.side }) : assert(side != null);
+  const StadiumBorder({ super.side });
 
   @override
   ShapeBorder scale(double t) => StadiumBorder(side: side.scale(t));
 
   @override
   ShapeBorder? lerpFrom(ShapeBorder? a, double t) {
-    assert(t != null);
     if (a is StadiumBorder) {
       return StadiumBorder(side: BorderSide.lerp(a.side, side, t));
     }
@@ -57,7 +56,6 @@
 
   @override
   ShapeBorder? lerpTo(ShapeBorder? b, double t) {
-    assert(t != null);
     if (b is StadiumBorder) {
       return StadiumBorder(side: BorderSide.lerp(side, b.side, t));
     }
@@ -144,8 +142,7 @@
     super.side,
     this.circularity = 0.0,
     required this.eccentricity,
-  }) : assert(side != null),
-       assert(circularity != null);
+  });
 
   final double circularity;
   final double eccentricity;
@@ -161,7 +158,6 @@
 
   @override
   ShapeBorder? lerpFrom(ShapeBorder? a, double t) {
-    assert(t != null);
     if (a is StadiumBorder) {
       return _StadiumToCircleBorder(
         side: BorderSide.lerp(a.side, side, t),
@@ -188,7 +184,6 @@
 
   @override
   ShapeBorder? lerpTo(ShapeBorder? b, double t) {
-    assert(t != null);
     if (b is StadiumBorder) {
       return _StadiumToCircleBorder(
         side: BorderSide.lerp(side, b.side, t),
@@ -326,9 +321,7 @@
     super.side,
     this.borderRadius = BorderRadius.zero,
     this.rectilinearity = 0.0,
-  }) : assert(side != null),
-       assert(borderRadius != null),
-       assert(rectilinearity != null);
+  });
 
   final BorderRadiusGeometry borderRadius;
 
@@ -345,7 +338,6 @@
 
   @override
   ShapeBorder? lerpFrom(ShapeBorder? a, double t) {
-    assert(t != null);
     if (a is StadiumBorder) {
       return _StadiumToRoundedRectangleBorder(
         side: BorderSide.lerp(a.side, side, t),
@@ -372,7 +364,6 @@
 
   @override
   ShapeBorder? lerpTo(ShapeBorder? b, double t) {
-    assert(t != null);
     if (b is StadiumBorder) {
       return _StadiumToRoundedRectangleBorder(
         side: BorderSide.lerp(side, b.side, t),
diff --git a/framework/lib/src/painting/star_border.dart b/framework/lib/src/painting/star_border.dart
index 18b9735..eba09ee 100644
--- a/framework/lib/src/painting/star_border.dart
+++ b/framework/lib/src/painting/star_border.dart
@@ -468,8 +468,8 @@
     required this.rotation,
     required this.squash,
   })  : assert(points > 1),
-        assert(innerRadiusRatio == null || innerRadiusRatio <= 1),
-        assert(innerRadiusRatio == null || innerRadiusRatio >= 0),
+        assert(innerRadiusRatio <= 1),
+        assert(innerRadiusRatio >= 0),
         assert(squash >= 0),
         assert(squash <= 1),
         assert(pointRounding >= 0),
@@ -484,7 +484,6 @@
   final double valleyRounding;
   final double rotation;
   final double squash;
-  bool get isStar => innerRadiusRatio != null;
 
   Path generate(Rect rect) {
     final double radius = rect.shortestSide / 2;
diff --git a/framework/lib/src/painting/strut_style.dart b/framework/lib/src/painting/strut_style.dart
index c9ec5c1..b90c49a 100644
--- a/framework/lib/src/painting/strut_style.dart
+++ b/framework/lib/src/painting/strut_style.dart
@@ -347,8 +347,7 @@
     this.forceStrutHeight,
     String? debugLabel,
     String? package,
-  }) : assert(textStyle != null),
-       assert(fontSize == null || fontSize > 0),
+  }) : assert(fontSize == null || fontSize > 0),
        assert(leading == null || leading >= 0),
        assert(package == null || fontFamily != null || fontFamilyFallback != null),
        fontFamily = fontFamily != null ? (package == null ? fontFamily : 'packages/$package/$fontFamily') : textStyle.fontFamily,
diff --git a/framework/lib/src/painting/text_painter.dart b/framework/lib/src/painting/text_painter.dart
index d418868..fdd064d 100644
--- a/framework/lib/src/painting/text_painter.dart
+++ b/framework/lib/src/painting/text_painter.dart
@@ -74,8 +74,7 @@
     required this.alignment,
     this.baseline,
     this.baselineOffset,
-  }) : assert(size != null),
-       assert(alignment != null);
+  });
 
   /// A constant representing an empty placeholder.
   static const PlaceholderDimensions empty = PlaceholderDimensions(size: Size.zero, alignment: ui.PlaceholderAlignment.bottom);
@@ -144,6 +143,133 @@
   longestLine,
 }
 
+/// A [TextBoundary] subclass for locating word breaks.
+///
+/// The underlying implementation uses [UAX #29](https://unicode.org/reports/tr29/)
+/// defined default word boundaries.
+///
+/// The default word break rules can be tailored to meet the requirements of
+/// different use cases. For instance, the default rule set keeps horizontal
+/// whitespaces together as a single word, which may not make sense in a
+/// word-counting context -- "hello    world" counts as 3 words instead of 2.
+/// An example is the [moveByWordBoundary] variant, which is a tailored
+/// word-break locator that more closely matches the default behavior of most
+/// platforms and editors when it comes to handling text editing keyboard
+/// shortcuts that move or delete word by word.
+class WordBoundary extends TextBoundary {
+  /// Creates a [WordBoundary] with the text and layout information.
+  WordBoundary._(this._text, this._paragraph);
+
+  final InlineSpan _text;
+  final ui.Paragraph _paragraph;
+
+  @override
+  TextRange getTextBoundaryAt(int position) => _paragraph.getWordBoundary(TextPosition(offset: max(position, 0)));
+
+  // Combines two UTF-16 code units (high surrogate + low surrogate) into a
+  // single code point that represents a supplementary character.
+  static int _codePointFromSurrogates(int highSurrogate, int lowSurrogate) {
+    assert(
+      TextPainter._isHighSurrogate(highSurrogate),
+      'U+${highSurrogate.toRadixString(16).toUpperCase().padLeft(4, "0")}) is not a high surrogate.',
+    );
+    assert(
+      TextPainter._isLowSurrogate(lowSurrogate),
+      'U+${lowSurrogate.toRadixString(16).toUpperCase().padLeft(4, "0")}) is not a low surrogate.',
+    );
+    const int base = 0x010000 - (0xD800 << 10) - 0xDC00;
+    return (highSurrogate << 10) + lowSurrogate + base;
+  }
+
+  // The Runes class does not provide random access with a code unit offset.
+  int? _codePointAt(int index) {
+    final int? codeUnitAtIndex = _text.codeUnitAt(index);
+    if (codeUnitAtIndex == null) {
+      return null;
+    }
+    switch (codeUnitAtIndex & 0xFC00) {
+      case 0xD800:
+        return _codePointFromSurrogates(codeUnitAtIndex, _text.codeUnitAt(index + 1)!);
+      case 0xDC00:
+        return _codePointFromSurrogates(_text.codeUnitAt(index - 1)!, codeUnitAtIndex);
+      default:
+        return codeUnitAtIndex;
+    }
+  }
+
+  static bool _isNewline(int codePoint) {
+    switch (codePoint) {
+      case 0x000A:
+      case 0x0085:
+      case 0x000B:
+      case 0x000C:
+      case 0x2028:
+      case 0x2029:
+        return true;
+      default:
+        return false;
+    }
+  }
+
+  bool _skipSpacesAndPunctuations(int offset, bool forward) {
+    // Use code point since some punctuations are supplementary characters.
+    // "inner" here refers to the code unit that's before the break in the
+    // search direction (`forward`).
+    final int? innerCodePoint = _codePointAt(forward ? offset - 1 : offset);
+    final int? outerCodeUnit = _text.codeUnitAt(forward ? offset : offset - 1);
+
+    // Make sure the hard break rules in UAX#29 take precedence over the ones we
+    // add below. Luckily there're only 4 hard break rules for word breaks, and
+    // dictionary based breaking does not introduce new hard breaks:
+    // https://unicode-org.github.io/icu/userguide/boundaryanalysis/break-rules.html#word-dictionaries
+    //
+    // WB1 & WB2: always break at the start or the end of the text.
+    final bool hardBreakRulesApply = innerCodePoint == null || outerCodeUnit == null
+    // WB3a & WB3b: always break before and after newlines.
+                                  || _isNewline(innerCodePoint) || _isNewline(outerCodeUnit);
+    return hardBreakRulesApply || !RegExp(r'[\p{Space_Separator}\p{Punctuation}]', unicode: true).hasMatch(String.fromCharCode(innerCodePoint));
+  }
+
+  /// Returns a [TextBoundary] suitable for handling keyboard navigation
+  /// commands that change the current selection word by word.
+  ///
+  /// This [TextBoundary] is used by text widgets in the flutter framework to
+  /// provide default implementation for text editing shortcuts, for example,
+  /// "delete to the previous word".
+  ///
+  /// The implementation applies the same set of rules [WordBoundary] uses,
+  /// except that word breaks end on a space separator or a punctuation will be
+  /// skipped, to match the behavior of most platforms. Additional rules may be
+  /// added in the future to better match platform behaviors.
+  late final TextBoundary moveByWordBoundary = _UntilTextBoundary(this, _skipSpacesAndPunctuations);
+}
+
+class _UntilTextBoundary extends TextBoundary {
+  const _UntilTextBoundary(this._textBoundary, this._predicate);
+
+  final UntilPredicate _predicate;
+  final TextBoundary _textBoundary;
+
+  @override
+  int? getLeadingTextBoundaryAt(int position) {
+    if (position < 0) {
+      return null;
+    }
+    final int? offset = _textBoundary.getLeadingTextBoundaryAt(position);
+    return offset == null || _predicate(offset, false)
+      ? offset
+      : getLeadingTextBoundaryAt(offset - 1);
+  }
+
+  @override
+  int? getTrailingTextBoundaryAt(int position) {
+    final int? offset = _textBoundary.getTrailingTextBoundaryAt(max(position, 0));
+    return offset == null || _predicate(offset, true)
+      ? offset
+      : getTrailingTextBoundaryAt(offset);
+  }
+}
+
 /// This is used to cache and pass the computed metrics regarding the
 /// caret's size and position. This is preferred due to the expensive
 /// nature of the calculation.
@@ -202,10 +328,7 @@
     TextWidthBasis textWidthBasis = TextWidthBasis.parent,
     ui.TextHeightBehavior? textHeightBehavior,
   }) : assert(text == null || text.debugAssertIsValid()),
-       assert(textAlign != null),
-       assert(textScaleFactor != null),
        assert(maxLines == null || maxLines > 0),
-       assert(textWidthBasis != null),
        _text = text,
        _textAlign = textAlign,
        _textDirection = textDirection,
@@ -400,7 +523,6 @@
   TextAlign get textAlign => _textAlign;
   TextAlign _textAlign;
   set textAlign(TextAlign value) {
-    assert(value != null);
     if (_textAlign == value) {
       return;
     }
@@ -444,7 +566,6 @@
   double get textScaleFactor => _textScaleFactor;
   double _textScaleFactor;
   set textScaleFactor(double value) {
-    assert(value != null);
     if (_textScaleFactor == value) {
       return;
     }
@@ -539,7 +660,6 @@
   TextWidthBasis get textWidthBasis => _textWidthBasis;
   TextWidthBasis _textWidthBasis;
   set textWidthBasis(TextWidthBasis value) {
-    assert(value != null);
     if (_textWidthBasis == value) {
       return;
     }
@@ -607,7 +727,6 @@
   ui.ParagraphStyle _createParagraphStyle([ TextDirection? defaultTextDirection ]) {
     // The defaultTextDirection argument is used for preferredLineHeight in case
     // textDirection hasn't yet been set.
-    assert(textAlign != null);
     assert(textDirection != null || defaultTextDirection != null, 'TextPainter.textDirection must be set to a non-null value before using the TextPainter.');
     return _text!.style?.getParagraphStyle(
       textAlign: textAlign,
@@ -720,7 +839,6 @@
   /// Valid only after [layout] has been called.
   double computeDistanceToActualBaseline(TextBaseline baseline) {
     assert(_debugAssertTextLayoutIsValid);
-    assert(baseline != null);
     switch (baseline) {
       case TextBaseline.alphabetic:
         return _paragraph!.alphabeticBaseline;
@@ -750,7 +868,7 @@
 
   // Creates a ui.Paragraph using the current configurations in this class and
   // assign it to _paragraph.
-  void _createParagraph() {
+  ui.Paragraph _createParagraph() {
     assert(_paragraph == null || _rebuildParagraphForPaint);
     final InlineSpan? text = this.text;
     if (text == null) {
@@ -763,8 +881,9 @@
       _debugMarkNeedsLayoutCallStack = null;
       return true;
     }());
-    _paragraph = builder.build();
+    final ui.Paragraph paragraph = _paragraph = builder.build();
     _rebuildParagraphForPaint = false;
+    return paragraph;
   }
 
   void _layoutParagraph(double minWidth, double maxWidth) {
@@ -861,13 +980,18 @@
     canvas.drawParagraph(_paragraph!, offset);
   }
 
-  // Returns true iff the given value is a valid UTF-16 surrogate. The value
+  // Returns true iff the given value is a valid UTF-16 high surrogate. The value
   // must be a UTF-16 code unit, meaning it must be in the range 0x0000-0xFFFF.
   //
   // See also:
   //   * https://en.wikipedia.org/wiki/UTF-16#Code_points_from_U+010000_to_U+10FFFF
-  static bool _isUtf16Surrogate(int value) {
-    return value & 0xF800 == 0xD800;
+  static bool _isHighSurrogate(int value) {
+    return value & 0xFC00 == 0xD800;
+  }
+
+  // Whether the given UTF-16 code unit is a low (second) surrogate.
+  static bool _isLowSurrogate(int value) {
+    return value & 0xFC00 == 0xDC00;
   }
 
   // Checks if the glyph is either [Unicode.RLM] or [Unicode.LRM]. These values take
@@ -886,7 +1010,7 @@
       return null;
     }
     // TODO(goderbauer): doesn't handle extended grapheme clusters with more than one Unicode scalar value (https://github.com/flutter/flutter/issues/13404).
-    return _isUtf16Surrogate(nextCodeUnit) ? offset + 2 : offset + 1;
+    return _isHighSurrogate(nextCodeUnit) ? offset + 2 : offset + 1;
   }
 
   /// Returns the closest offset before `offset` at which the input cursor can
@@ -897,7 +1021,7 @@
       return null;
     }
     // TODO(goderbauer): doesn't handle extended grapheme clusters with more than one Unicode scalar value (https://github.com/flutter/flutter/issues/13404).
-    return _isUtf16Surrogate(prevCodeUnit) ? offset - 2 : offset - 1;
+    return _isLowSurrogate(prevCodeUnit) ? offset - 2 : offset - 1;
   }
 
   // Unicode value for a zero width joiner character.
@@ -916,7 +1040,7 @@
     const int NEWLINE_CODE_UNIT = 10;
 
     // Check for multi-code-unit glyphs such as emojis or zero width joiner.
-    final bool needsSearch = _isUtf16Surrogate(prevCodeUnit) || _text!.codeUnitAt(offset) == _zwjUtf16 || _isUnicodeDirectionality(prevCodeUnit);
+    final bool needsSearch = _isHighSurrogate(prevCodeUnit) || _isLowSurrogate(prevCodeUnit) || _text!.codeUnitAt(offset) == _zwjUtf16 || _isUnicodeDirectionality(prevCodeUnit);
     int graphemeClusterLength = needsSearch ? 2 : 1;
     List<TextBox> boxes = <TextBox>[];
     while (boxes.isEmpty) {
@@ -966,7 +1090,7 @@
     final int nextCodeUnit = plainText.codeUnitAt(min(offset, plainTextLength - 1));
 
     // Check for multi-code-unit glyphs such as emojis or zero width joiner
-    final bool needsSearch = _isUtf16Surrogate(nextCodeUnit) || nextCodeUnit == _zwjUtf16 || _isUnicodeDirectionality(nextCodeUnit);
+    final bool needsSearch = _isHighSurrogate(nextCodeUnit) || _isLowSurrogate(nextCodeUnit) || nextCodeUnit == _zwjUtf16 || _isUnicodeDirectionality(nextCodeUnit);
     int graphemeClusterLength = needsSearch ? 2 : 1;
     List<TextBox> boxes = <TextBox>[];
     while (boxes.isEmpty) {
@@ -1001,7 +1125,6 @@
 
   Offset get _emptyOffset {
     assert(_debugAssertTextLayoutIsValid); // implies textDirection is non-null
-    assert(textAlign != null);
     switch (textAlign) {
       case TextAlign.left:
         return Offset.zero;
@@ -1066,7 +1189,6 @@
       return;
     }
     final int offset = position.offset;
-    assert(position.affinity != null);
     Rect? rect;
     switch (position.affinity) {
       case TextAffinity.upstream: {
@@ -1111,8 +1233,6 @@
     ui.BoxWidthStyle boxWidthStyle = ui.BoxWidthStyle.tight,
   }) {
     assert(_debugAssertTextLayoutIsValid);
-    assert(boxHeightStyle != null);
-    assert(boxWidthStyle != null);
     return _paragraph!.getBoxesForRange(
       selection.start,
       selection.end,
@@ -1141,6 +1261,18 @@
     return _paragraph!.getWordBoundary(position);
   }
 
+  /// {@template flutter.painting.TextPainter.wordBoundaries}
+  /// Returns a [TextBoundary] that can be used to perform word boundary analysis
+  /// on the current [text].
+  ///
+  /// This [TextBoundary] uses word boundary rules defined in [Unicode Standard
+  /// Annex #29](http://www.unicode.org/reports/tr29/#Word_Boundaries).
+  /// {@endtemplate}
+  ///
+  /// Currently word boundary analysis can only be performed after [layout]
+  /// has been called.
+  WordBoundary get wordBoundaries => WordBoundary._(text!, _paragraph!);
+
   /// Returns the text range of the line at the given offset.
   ///
   /// The newline (if any) is not returned as part of the range.
diff --git a/framework/lib/src/painting/text_span.dart b/framework/lib/src/painting/text_span.dart
index 9dfab07..846d426 100644
--- a/framework/lib/src/painting/text_span.dart
+++ b/framework/lib/src/painting/text_span.dart
@@ -291,7 +291,6 @@
     }
     if (children != null) {
       for (final InlineSpan child in children!) {
-        assert(child != null);
         child.build(
           builder,
           textScaleFactor: textScaleFactor,
@@ -452,18 +451,6 @@
     assert(() {
       if (children != null) {
         for (final InlineSpan child in children!) {
-          if (child == null) {
-            throw FlutterError.fromParts(<DiagnosticsNode>[
-              ErrorSummary('TextSpan contains a null child.'),
-              ErrorDescription(
-                'A TextSpan object with a non-null child list should not have any nulls in its child list.',
-              ),
-              toDiagnosticsNode(
-                name: 'The full text in question was',
-                style: DiagnosticsTreeStyle.errorProperty,
-              ),
-            ]);
-          }
           assert(child.debugAssertIsValid());
         }
       }
@@ -590,14 +577,7 @@
       return const <DiagnosticsNode>[];
     }
     return children!.map<DiagnosticsNode>((InlineSpan child) {
-      // `child` has a non-nullable return type, but might be null when running
-      // with weak checking, so we need to null check it anyway (and ignore the
-      // warning that the null-handling logic is dead code).
-      if (child != null) {
-        return child.toDiagnosticsNode();
-      } else {
-        return DiagnosticsNode.message('<null child>');
-      }
+      return child.toDiagnosticsNode();
     }).toList();
   }
 }
diff --git a/framework/lib/src/painting/text_style.dart b/framework/lib/src/painting/text_style.dart
index 2cdc7bc..2b390a5 100644
--- a/framework/lib/src/painting/text_style.dart
+++ b/framework/lib/src/painting/text_style.dart
@@ -504,7 +504,6 @@
   }) : fontFamily = package == null ? fontFamily : 'packages/$package/$fontFamily',
        _fontFamilyFallback = fontFamilyFallback,
        _package = package,
-       assert(inherit != null),
        assert(color == null || foreground == null, _kColorForegroundWarning),
        assert(backgroundColor == null || background == null, _kColorBackgroundWarning);
 
@@ -897,7 +896,7 @@
       decorationThickness: decorationThickness ?? this.decorationThickness,
       debugLabel: newDebugLabel,
       fontFamily: fontFamily ?? _fontFamily,
-      fontFamilyFallback: fontFamilyFallback ?? this.fontFamilyFallback,
+      fontFamilyFallback: fontFamilyFallback ?? _fontFamilyFallback,
       package: package ?? _package,
       overflow: overflow ?? this.overflow,
     );
@@ -961,21 +960,10 @@
     String? package,
     TextOverflow? overflow,
   }) {
-    assert(fontSizeFactor != null);
-    assert(fontSizeDelta != null);
     assert(fontSize != null || (fontSizeFactor == 1.0 && fontSizeDelta == 0.0));
-    assert(fontWeightDelta != null);
     assert(fontWeight != null || fontWeightDelta == 0.0);
-    assert(letterSpacingFactor != null);
-    assert(letterSpacingDelta != null);
     assert(letterSpacing != null || (letterSpacingFactor == 1.0 && letterSpacingDelta == 0.0));
-    assert(wordSpacingFactor != null);
-    assert(wordSpacingDelta != null);
     assert(wordSpacing != null || (wordSpacingFactor == 1.0 && wordSpacingDelta == 0.0));
-    assert(heightFactor != null);
-    assert(heightDelta != null);
-    assert(decorationThicknessFactor != null);
-    assert(decorationThicknessDelta != null);
     assert(decorationThickness != null || (decorationThicknessFactor == 1.0 && decorationThicknessDelta == 0.0));
 
     String? modifiedDebugLabel;
@@ -991,7 +979,7 @@
       color: foreground == null ? color ?? this.color : null,
       backgroundColor: background == null ? backgroundColor ?? this.backgroundColor : null,
       fontFamily: fontFamily ?? _fontFamily,
-      fontFamilyFallback: fontFamilyFallback ?? this.fontFamilyFallback,
+      fontFamilyFallback: fontFamilyFallback ?? _fontFamilyFallback,
       fontSize: fontSize == null ? null : fontSize! * fontSizeFactor + fontSizeDelta,
       fontWeight: fontWeight == null ? null : FontWeight.values[(fontWeight!.index + fontWeightDelta).clamp(0, FontWeight.values.length - 1)], // ignore_clamp_double_lint
       fontStyle: fontStyle ?? this.fontStyle,
@@ -1106,7 +1094,6 @@
   /// as if they have a [background] paint (creating a new [Paint] if necessary
   /// based on the [backgroundColor] property).
   static TextStyle? lerp(TextStyle? a, TextStyle? b, double t) {
-    assert(t != null);
     if (a == null && b == null) {
       return null;
     }
@@ -1332,7 +1319,6 @@
     double? height,
     StrutStyle? strutStyle,
   }) {
-    assert(textScaleFactor != null);
     assert(maxLines == null || maxLines > 0);
     final ui.TextLeadingDistribution? leadingDistribution = this.leadingDistribution;
     final ui.TextHeightBehavior? effectiveTextHeightBehavior = textHeightBehavior
diff --git a/framework/lib/src/physics/clamped_simulation.dart b/framework/lib/src/physics/clamped_simulation.dart
index f5e4116..a7b23e1 100644
--- a/framework/lib/src/physics/clamped_simulation.dart
+++ b/framework/lib/src/physics/clamped_simulation.dart
@@ -36,8 +36,7 @@
     this.xMax = double.infinity,
     this.dxMin = double.negativeInfinity,
     this.dxMax = double.infinity,
-  }) : assert(simulation != null),
-       assert(xMax >= xMin),
+  }) : assert(xMax >= xMin),
        assert(dxMax >= dxMin);
 
   /// The simulation being clamped. Calls to [x], [dx], and [isDone] are
diff --git a/framework/lib/src/physics/gravity_simulation.dart b/framework/lib/src/physics/gravity_simulation.dart
index 0a1204b..7655a0a 100644
--- a/framework/lib/src/physics/gravity_simulation.dart
+++ b/framework/lib/src/physics/gravity_simulation.dart
@@ -70,11 +70,7 @@
     double distance,
     double endDistance,
     double velocity,
-  ) : assert(acceleration != null),
-      assert(distance != null),
-      assert(velocity != null),
-      assert(endDistance != null),
-      assert(endDistance >= 0),
+  ) : assert(endDistance >= 0),
       _a = acceleration,
       _x = distance,
       _v = velocity,
diff --git a/framework/lib/src/physics/spring_simulation.dart b/framework/lib/src/physics/spring_simulation.dart
index 0722f26..dbd8153 100644
--- a/framework/lib/src/physics/spring_simulation.dart
+++ b/framework/lib/src/physics/spring_simulation.dart
@@ -153,12 +153,6 @@
     double initialPosition,
     double initialVelocity,
   ) {
-    assert(spring != null);
-    assert(spring.mass != null);
-    assert(spring.stiffness != null);
-    assert(spring.damping != null);
-    assert(initialPosition != null);
-    assert(initialVelocity != null);
     final double cmk = spring.damping * spring.damping - 4 * spring.mass * spring.stiffness;
     if (cmk == 0.0) {
       return _CriticalSolution(spring, initialPosition, initialVelocity);
diff --git a/framework/lib/src/physics/utils.dart b/framework/lib/src/physics/utils.dart
index 7b9501b..7a8f82e 100644
--- a/framework/lib/src/physics/utils.dart
+++ b/framework/lib/src/physics/utils.dart
@@ -8,7 +8,6 @@
 /// The `a` and `b` arguments may be null. A null value is only considered
 /// near-equal to another null value.
 bool nearEqual(double? a, double? b, double epsilon) {
-  assert(epsilon != null);
   assert(epsilon >= 0.0);
   if (a == null || b == null) {
     return a == b;
diff --git a/framework/lib/src/rendering/animated_size.dart b/framework/lib/src/rendering/animated_size.dart
index cc8a677..f72992a 100644
--- a/framework/lib/src/rendering/animated_size.dart
+++ b/framework/lib/src/rendering/animated_size.dart
@@ -82,11 +82,7 @@
     super.textDirection,
     super.child,
     Clip clipBehavior = Clip.hardEdge,
-  }) : assert(vsync != null),
-       assert(duration != null),
-       assert(curve != null),
-       assert(clipBehavior != null),
-       _vsync = vsync,
+  }) : _vsync = vsync,
        _clipBehavior = clipBehavior {
     _controller = AnimationController(
       vsync: vsync,
@@ -119,7 +115,6 @@
   /// The duration of the animation.
   Duration get duration => _controller.duration!;
   set duration(Duration value) {
-    assert(value != null);
     if (value == _controller.duration) {
       return;
     }
@@ -138,7 +133,6 @@
   /// The curve of the animation.
   Curve get curve => _animation.curve;
   set curve(Curve value) {
-    assert(value != null);
     if (value == _animation.curve) {
       return;
     }
@@ -151,7 +145,6 @@
   Clip get clipBehavior => _clipBehavior;
   Clip _clipBehavior = Clip.hardEdge;
   set clipBehavior(Clip value) {
-    assert(value != null);
     if (value != _clipBehavior) {
       _clipBehavior = value;
       markNeedsPaint();
@@ -169,7 +162,6 @@
   TickerProvider get vsync => _vsync;
   TickerProvider _vsync;
   set vsync(TickerProvider value) {
-    assert(value != null);
     if (value == _vsync) {
       return;
     }
@@ -218,7 +210,6 @@
 
     child!.layout(constraints, parentUsesSize: true);
 
-    assert(_state != null);
     switch (_state) {
       case RenderAnimatedSizeState.start:
         _layoutStart();
@@ -253,7 +244,6 @@
     // size without modifying global state. See performLayout for comments
     // explaining the rational behind the implementation.
     final Size childSize = child!.getDryLayout(constraints);
-    assert(_state != null);
     switch (_state) {
       case RenderAnimatedSizeState.start:
         return constraints.constrain(childSize);
diff --git a/framework/lib/src/rendering/binding.dart b/framework/lib/src/rendering/binding.dart
index efb0407..da8c459 100644
--- a/framework/lib/src/rendering/binding.dart
+++ b/framework/lib/src/rendering/binding.dart
@@ -43,7 +43,6 @@
       ..onSemanticsAction = _handleSemanticsAction;
     initRenderView();
     _handleSemanticsEnabledChanged();
-    assert(renderView != null);
     addPersistentFrameCallback(_handlePersistentFrameCallback);
     initMouseTracker();
     if (kIsWeb) {
@@ -234,7 +233,6 @@
   /// Sets the given [RenderView] object (which must not be null), and its tree, to
   /// be the new render tree to display. The previous tree, if any, is detached.
   set renderView(RenderView value) {
-    assert(value != null);
     _pipelineOwner.rootNode = value;
   }
 
@@ -244,7 +242,6 @@
   @protected
   @visibleForTesting
   void handleMetricsChanged() {
-    assert(renderView != null);
     renderView.configuration = createViewConfiguration();
     if (renderView.child != null) {
       scheduleForcedFrame();
@@ -512,7 +509,6 @@
   // When editing the above, also update widgets/binding.dart's copy.
   @protected
   void drawFrame() {
-    assert(renderView != null);
     pipelineOwner.flushLayout();
     pipelineOwner.flushCompositingBits();
     pipelineOwner.flushPaint();
@@ -544,9 +540,6 @@
 
   @override
   void hitTest(HitTestResult result, Offset position) {
-    assert(renderView != null);
-    assert(result != null);
-    assert(position != null);
     renderView.hitTest(result, position: position);
     super.hitTest(result, position);
   }
@@ -620,7 +613,6 @@
   /// This binding does not automatically schedule any frames. Callers are
   /// responsible for deciding when to first call [scheduleFrame].
   RenderingFlutterBinding({ RenderBox? root }) {
-    assert(renderView != null);
     renderView.child = root;
   }
 
diff --git a/framework/lib/src/rendering/box.dart b/framework/lib/src/rendering/box.dart
index 9528cbb..057a831 100644
--- a/framework/lib/src/rendering/box.dart
+++ b/framework/lib/src/rendering/box.dart
@@ -96,10 +96,7 @@
     this.maxWidth = double.infinity,
     this.minHeight = 0.0,
     this.maxHeight = double.infinity,
-  }) : assert(minWidth != null),
-       assert(maxWidth != null),
-       assert(minHeight != null),
-       assert(maxHeight != null);
+  });
 
   /// Creates box constraints that is respected only by the given size.
   BoxConstraints.tight(Size size)
@@ -190,7 +187,6 @@
 
   /// Returns new box constraints that are smaller by the given edge dimensions.
   BoxConstraints deflate(EdgeInsets edges) {
-    assert(edges != null);
     assert(debugAssertIsValid());
     final double horizontal = edges.horizontal;
     final double vertical = edges.vertical;
@@ -472,7 +468,6 @@
   ///
   /// {@macro dart.ui.shadow.lerp}
   static BoxConstraints? lerp(BoxConstraints? a, BoxConstraints? b, double t) {
-    assert(t != null);
     if (a == null && b == null) {
       return null;
     }
@@ -762,8 +757,6 @@
     required Offset position,
     required BoxHitTest hitTest,
   }) {
-    assert(position != null);
-    assert(hitTest != null);
     if (transform != null) {
       transform = Matrix4.tryInvert(PointerEvent.removePerspectiveTransform(transform));
       if (transform == null) {
@@ -801,8 +794,6 @@
     required Offset position,
     required BoxHitTest hitTest,
   }) {
-    assert(position != null);
-    assert(hitTest != null);
     final Offset transformedPosition = offset == null ? position : position - offset;
     if (offset != null) {
       pushOffset(-offset);
@@ -838,9 +829,6 @@
     required Offset position,
     required BoxHitTest hitTest,
   }) {
-    assert(position != null);
-    assert(hitTest != null);
-    assert(position != null);
     final Offset transformedPosition = transform == null ?
         position : MatrixUtils.transformPoint(transform, position);
     if (transform != null) {
@@ -887,7 +875,6 @@
     Matrix4? rawTransform,
     required BoxHitTestWithOutOfBandPosition hitTest,
   }) {
-    assert(hitTest != null);
     assert(
       (paintOffset == null && paintTransform == null && rawTransform != null) ||
       (paintOffset == null && paintTransform != null && rawTransform == null) ||
@@ -915,8 +902,7 @@
   /// Creates a box hit test entry.
   ///
   /// The [localPosition] argument must not be null.
-  BoxHitTestEntry(super.target, this.localPosition)
-    : assert(localPosition != null);
+  BoxHitTestEntry(super.target, this.localPosition);
 
   /// The position of the hit test in the local coordinates of [target].
   final Offset localPosition;
@@ -1001,7 +987,6 @@
 /// AxisDirection get axis => _axis;
 /// AxisDirection _axis = AxisDirection.down; // or initialized in constructor
 /// set axis(AxisDirection value) {
-///   assert(value != null); // same checks as in the constructor
 ///   if (value == _axis) {
 ///     return;
 ///   }
@@ -1452,13 +1437,6 @@
   @mustCallSuper
   double getMinIntrinsicWidth(double height) {
     assert(() {
-      if (height == null) {
-        throw FlutterError.fromParts(<DiagnosticsNode>[
-          ErrorSummary('The height argument to getMinIntrinsicWidth was null.'),
-          ErrorDescription('The argument to getMinIntrinsicWidth must not be negative or null.'),
-          ErrorHint('If you do not have a specific height in mind, then pass double.infinity instead.'),
-        ]);
-      }
       if (height < 0.0) {
         throw FlutterError.fromParts(<DiagnosticsNode>[
           ErrorSummary('The height argument to getMinIntrinsicWidth was negative.'),
@@ -1601,13 +1579,6 @@
   @mustCallSuper
   double getMaxIntrinsicWidth(double height) {
     assert(() {
-      if (height == null) {
-        throw FlutterError.fromParts(<DiagnosticsNode>[
-          ErrorSummary('The height argument to getMaxIntrinsicWidth was null.'),
-          ErrorDescription('The argument to getMaxIntrinsicWidth must not be negative or null.'),
-          ErrorHint('If you do not have a specific height in mind, then pass double.infinity instead.'),
-        ]);
-      }
       if (height < 0.0) {
         throw FlutterError.fromParts(<DiagnosticsNode>[
           ErrorSummary('The height argument to getMaxIntrinsicWidth was negative.'),
@@ -1684,13 +1655,6 @@
   @mustCallSuper
   double getMinIntrinsicHeight(double width) {
     assert(() {
-      if (width == null) {
-        throw FlutterError.fromParts(<DiagnosticsNode>[
-          ErrorSummary('The width argument to getMinIntrinsicHeight was null.'),
-          ErrorDescription('The argument to getMinIntrinsicHeight must not be negative or null.'),
-          ErrorHint('If you do not have a specific width in mind, then pass double.infinity instead.'),
-        ]);
-      }
       if (width < 0.0) {
         throw FlutterError.fromParts(<DiagnosticsNode>[
           ErrorSummary('The width argument to getMinIntrinsicHeight was negative.'),
@@ -1766,13 +1730,6 @@
   @mustCallSuper
   double getMaxIntrinsicHeight(double width) {
     assert(() {
-      if (width == null) {
-        throw FlutterError.fromParts(<DiagnosticsNode>[
-          ErrorSummary('The width argument to getMaxIntrinsicHeight was null.'),
-          ErrorDescription('The argument to getMaxIntrinsicHeight must not be negative or null.'),
-          ErrorHint('If you do not have a specific width in mind, then pass double.infinity instead.'),
-        ]);
-      }
       if (width < 0.0) {
         throw FlutterError.fromParts(<DiagnosticsNode>[
           ErrorSummary('The width argument to getMaxIntrinsicHeight was negative.'),
@@ -1936,7 +1893,7 @@
   /// may depend on the baseline of a child (which is not available without
   /// doing a full layout), it may be computed by a callback about which the
   /// render object cannot reason, or the layout is so complex that it
-  /// is simply impractical to calculate the size in an efficient way.
+  /// is impractical to calculate the size in an efficient way.
   ///
   /// In such cases, it may be impossible (or at least impractical) to actually
   /// return a valid answer. In such cases, the function should call
@@ -1964,7 +1921,7 @@
   /// When asserts are enabled and [debugCheckingIntrinsics] is not true, this
   /// method will either throw the provided [FlutterError] or it will create and
   /// throw a [FlutterError] with the provided `reason`. Otherwise, it will
-  /// simply return true.
+  /// return true.
   ///
   /// One of the arguments has to be provided.
   ///
@@ -2253,7 +2210,6 @@
 
   @override
   void debugAssertDoesMeetConstraints() {
-    assert(constraints != null);
     assert(() {
       if (!hasSize) {
         final DiagnosticsNode contract;
@@ -2582,7 +2538,6 @@
   /// child's [parentData] in the [BoxParentData.offset] field.
   @override
   void applyPaintTransform(RenderObject child, Matrix4 transform) {
-    assert(child != null);
     assert(child.parent == this);
     assert(() {
       if (child.parentData is! BoxParentData) {
diff --git a/framework/lib/src/rendering/custom_layout.dart b/framework/lib/src/rendering/custom_layout.dart
index e029bff..7a7c126 100644
--- a/framework/lib/src/rendering/custom_layout.dart
+++ b/framework/lib/src/rendering/custom_layout.dart
@@ -187,11 +187,6 @@
           'There is no child with the id "$childId".',
         );
       }
-      if (offset == null) {
-        throw FlutterError(
-          'The $this custom multichild layout delegate provided a null position for the child with id "$childId".',
-        );
-      }
       return true;
     }());
     final MultiChildLayoutParentData childParentData = child!.parentData! as MultiChildLayoutParentData;
@@ -311,8 +306,7 @@
   RenderCustomMultiChildLayoutBox({
     List<RenderBox>? children,
     required MultiChildLayoutDelegate delegate,
-  }) : assert(delegate != null),
-       _delegate = delegate {
+  }) : _delegate = delegate {
     addAll(children);
   }
 
@@ -327,7 +321,6 @@
   MultiChildLayoutDelegate get delegate => _delegate;
   MultiChildLayoutDelegate _delegate;
   set delegate(MultiChildLayoutDelegate newDelegate) {
-    assert(newDelegate != null);
     if (_delegate == newDelegate) {
       return;
     }
diff --git a/framework/lib/src/rendering/custom_paint.dart b/framework/lib/src/rendering/custom_paint.dart
index 32a79be..43d68bb 100644
--- a/framework/lib/src/rendering/custom_paint.dart
+++ b/framework/lib/src/rendering/custom_paint.dart
@@ -294,8 +294,7 @@
     required this.properties,
     this.transform,
     this.tags,
-  }) : assert(rect != null),
-       assert(properties != null);
+  });
 
   /// Identifies this object in a list of siblings.
   ///
@@ -371,8 +370,7 @@
     this.isComplex = false,
     this.willChange = false,
     RenderBox? child,
-  }) : assert(preferredSize != null),
-       _painter = painter,
+  }) : _painter = painter,
        _foregroundPainter = foregroundPainter,
        _preferredSize = preferredSize,
        super(child);
@@ -467,7 +465,6 @@
   Size get preferredSize => _preferredSize;
   Size _preferredSize;
   set preferredSize(Size value) {
-    assert(value != null);
     if (preferredSize == value) {
       return;
     }
diff --git a/framework/lib/src/rendering/editable.dart b/framework/lib/src/rendering/editable.dart
index 1a1c6e3..407b88c 100644
--- a/framework/lib/src/rendering/editable.dart
+++ b/framework/lib/src/rendering/editable.dart
@@ -42,8 +42,7 @@
   /// Creates a description of a point in a text selection.
   ///
   /// The [point] argument must not be null.
-  const TextSelectionPoint(this.point, this.direction)
-    : assert(point != null);
+  const TextSelectionPoint(this.point, this.direction);
 
   /// Coordinates of the lower left or lower right corner of the selection,
   /// relative to the top left of the [RenderEditable] object.
@@ -325,37 +324,19 @@
     RenderEditablePainter? painter,
     RenderEditablePainter? foregroundPainter,
     List<RenderBox>? children,
-  }) : assert(textAlign != null),
-       assert(textDirection != null, 'RenderEditable created without a textDirection.'),
-       assert(maxLines == null || maxLines > 0),
+  }) : assert(maxLines == null || maxLines > 0),
        assert(minLines == null || minLines > 0),
-       assert(startHandleLayerLink != null),
-       assert(endHandleLayerLink != null),
        assert(
          (maxLines == null) || (minLines == null) || (maxLines >= minLines),
          "minLines can't be greater than maxLines",
        ),
-       assert(expands != null),
        assert(
          !expands || (maxLines == null && minLines == null),
          'minLines and maxLines must be null when expands is true.',
        ),
-       assert(textScaleFactor != null),
-       assert(offset != null),
-       assert(ignorePointer != null),
-       assert(textWidthBasis != null),
-       assert(paintCursorAboveText != null),
-       assert(obscuringCharacter != null && obscuringCharacter.characters.length == 1),
-       assert(obscureText != null),
-       assert(textSelectionDelegate != null),
-       assert(cursorWidth != null && cursorWidth >= 0.0),
+       assert(obscuringCharacter.characters.length == 1),
+       assert(cursorWidth >= 0.0),
        assert(cursorHeight == null || cursorHeight >= 0.0),
-       assert(readOnly != null),
-       assert(forceLine != null),
-       assert(devicePixelRatio != null),
-       assert(selectionHeightStyle != null),
-       assert(selectionWidthStyle != null),
-       assert(clipBehavior != null),
        _textPainter = TextPainter(
          text: text,
          textAlign: textAlign,
@@ -386,7 +367,6 @@
        _forceLine = forceLine,
        _clipBehavior = clipBehavior,
        _hasFocus = hasFocus ?? false {
-    assert(_showCursor != null);
     assert(!_showCursor.value || cursorColor != null);
 
     _selectionPainter.highlightColor = selectionColor;
@@ -592,7 +572,6 @@
   /// {@macro flutter.painting.textPainter.textWidthBasis}
   TextWidthBasis get textWidthBasis => _textPainter.textWidthBasis;
   set textWidthBasis(TextWidthBasis value) {
-    assert(value != null);
     if (_textPainter.textWidthBasis == value) {
       return;
     }
@@ -622,7 +601,7 @@
     if (_obscuringCharacter == value) {
       return;
     }
-    assert(value != null && value.characters.length == 1);
+    assert(value.characters.length == 1);
     _obscuringCharacter = value;
     markNeedsLayout();
   }
@@ -852,7 +831,6 @@
   /// This must not be null.
   TextAlign get textAlign => _textPainter.textAlign;
   set textAlign(TextAlign value) {
-    assert(value != null);
     if (_textPainter.textAlign == value) {
       return;
     }
@@ -878,7 +856,6 @@
   // set it to null here, so _textPainter.textDirection cannot be null.
   TextDirection get textDirection => _textPainter.textDirection!;
   set textDirection(TextDirection value) {
-    assert(value != null);
     if (_textPainter.textDirection == value) {
       return;
     }
@@ -936,7 +913,6 @@
   ValueNotifier<bool> get showCursor => _showCursor;
   ValueNotifier<bool> _showCursor;
   set showCursor(ValueNotifier<bool> value) {
-    assert(value != null);
     if (_showCursor == value) {
       return;
     }
@@ -958,7 +934,6 @@
   bool get hasFocus => _hasFocus;
   bool _hasFocus = false;
   set hasFocus(bool value) {
-    assert(value != null);
     if (_hasFocus == value) {
       return;
     }
@@ -970,7 +945,6 @@
   bool get forceLine => _forceLine;
   bool _forceLine = false;
   set forceLine(bool value) {
-    assert(value != null);
     if (_forceLine == value) {
       return;
     }
@@ -982,7 +956,6 @@
   bool get readOnly => _readOnly;
   bool _readOnly = false;
   set readOnly(bool value) {
-    assert(value != null);
     if (_readOnly == value) {
       return;
     }
@@ -1034,7 +1007,6 @@
   bool get expands => _expands;
   bool _expands;
   set expands(bool value) {
-    assert(value != null);
     if (expands == value) {
       return;
     }
@@ -1054,7 +1026,6 @@
   /// the specified font size.
   double get textScaleFactor => _textPainter.textScaleFactor;
   set textScaleFactor(double value) {
-    assert(value != null);
     if (_textPainter.textScaleFactor == value) {
       return;
     }
@@ -1088,7 +1059,6 @@
   ViewportOffset get offset => _offset;
   ViewportOffset _offset;
   set offset(ViewportOffset value) {
-    assert(value != null);
     if (_offset == value) {
       return;
     }
@@ -1295,7 +1265,6 @@
   Clip get clipBehavior => _clipBehavior;
   Clip _clipBehavior = Clip.hardEdge;
   set clipBehavior(Clip value) {
-    assert(value != null);
     if (value != _clipBehavior) {
       _clipBehavior = value;
       markNeedsPaint();
@@ -1316,11 +1285,16 @@
   /// Returns a list of rects that bound the given selection.
   ///
   /// See [TextPainter.getBoxesForSelection] for more details.
-  List<Rect> getBoxesForSelection(TextSelection selection) {
+  List<TextBox> getBoxesForSelection(TextSelection selection) {
     _computeTextMetricsIfNeeded();
     return _textPainter.getBoxesForSelection(selection)
-                       .map((TextBox textBox) => textBox.toRect().shift(_paintOffset))
-                       .toList();
+                       .map((TextBox textBox) => TextBox.fromLTRBD(
+                          textBox.left + _paintOffset.dx,
+                          textBox.top + _paintOffset.dy,
+                          textBox.right + _paintOffset.dx,
+                          textBox.bottom + _paintOffset.dy,
+                          textBox.direction
+                        )).toList();
   }
 
   @override
@@ -1600,7 +1574,7 @@
   TextRange? _getNextWord(int offset) {
     while (true) {
       final TextRange range = _textPainter.getWordBoundary(TextPosition(offset: offset));
-      if (range == null || !range.isValid || range.isCollapsed) {
+      if (!range.isValid || range.isCollapsed) {
         return null;
       }
       if (!_onlyWhitespace(range)) {
@@ -1613,7 +1587,7 @@
   TextRange? _getPreviousWord(int offset) {
     while (offset >= 0) {
       final TextRange range = _textPainter.getWordBoundary(TextPosition(offset: offset));
-      if (range == null || !range.isValid || range.isCollapsed) {
+      if (!range.isValid || range.isCollapsed) {
         return null;
       }
       if (!_onlyWhitespace(range)) {
@@ -1878,21 +1852,10 @@
       return math.max(estimatedHeight, minHeight);
     }
 
-    // TODO(LongCatIsLooong): this is a workaround for
-    // https://github.com/flutter/flutter/issues/112123.
-    // Use preferredLineHeight since SkParagraph currently returns an incorrect
-    // height.
-    final TextHeightBehavior? textHeightBehavior = this.textHeightBehavior;
-    final bool usePreferredLineHeightHack = maxLines == 1
-        && text?.codeUnitAt(0) == null
-        && strutStyle != null && strutStyle != StrutStyle.disabled
-        && textHeightBehavior != null
-        && (!textHeightBehavior.applyHeightToFirstAscent || !textHeightBehavior.applyHeightToLastDescent);
-
     // Special case maxLines == 1 since it forces the scrollable direction
     // to be horizontal. Report the real height to prevent the text from being
     // clipped.
-    if (maxLines == 1 && !usePreferredLineHeightHack) {
+    if (maxLines == 1) {
       // The _layoutText call lays out the paragraph using infinite width when
       // maxLines == 1. Also _textPainter.maxLines will be set to 1 so should
       // there be any line breaks only the first line is shown.
@@ -2084,8 +2047,6 @@
   /// [from] corresponds to the [TextSelection.baseOffset], and [to] corresponds
   /// to the [TextSelection.extentOffset].
   void selectPositionAt({ required Offset from, Offset? to, required SelectionChangedCause cause }) {
-    assert(cause != null);
-    assert(from != null);
     _layoutText(minWidth: constraints.minWidth, maxWidth: constraints.maxWidth);
     final TextPosition fromPosition = _textPainter.getPositionForOffset(globalToLocal(from - _paintOffset));
     final TextPosition? toPosition = to == null
@@ -2104,6 +2065,9 @@
     _setSelection(newSelection, cause);
   }
 
+  /// {@macro flutter.painting.TextPainter.wordBoundaries}
+  WordBoundary get wordBoundaries => _textPainter.wordBoundaries;
+
   /// Select a word around the location of the last tap down.
   ///
   /// {@macro flutter.rendering.RenderEditable.selectPosition}
@@ -2120,8 +2084,6 @@
   ///
   /// {@macro flutter.rendering.RenderEditable.selectPosition}
   void selectWordsInRange({ required Offset from, Offset? to, required SelectionChangedCause cause }) {
-    assert(cause != null);
-    assert(from != null);
     _computeTextMetricsIfNeeded();
     final TextPosition fromPosition = _textPainter.getPositionForOffset(globalToLocal(from - _paintOffset));
     final TextSelection fromWord = _getWordAtOffset(fromPosition);
@@ -2143,7 +2105,6 @@
   ///
   /// {@macro flutter.rendering.RenderEditable.selectPosition}
   void selectWordEdge({ required SelectionChangedCause cause }) {
-    assert(cause != null);
     _computeTextMetricsIfNeeded();
     assert(_lastTapDownPosition != null);
     final TextPosition position = _textPainter.getPositionForOffset(globalToLocal(_lastTapDownPosition! - _paintOffset));
@@ -2190,7 +2151,6 @@
     // the position.
     if (TextLayoutMetrics.isWhitespace(plainText.codeUnitAt(effectiveOffset))
         && effectiveOffset > 0) {
-      assert(defaultTargetPlatform != null);
       final TextRange? previousWord = _getPreviousWord(word.start);
       switch (defaultTargetPlatform) {
         case TargetPlatform.iOS:
@@ -2314,7 +2274,6 @@
   }
 
   void _layoutText({ double minWidth = 0.0, double maxWidth = double.infinity }) {
-    assert(maxWidth != null && minWidth != null);
     final double availableMaxWidth = math.max(0.0, maxWidth - _caretMargin);
     final double availableMinWidth = math.min(minWidth, availableMaxWidth);
     final double textMaxWidth = _isMultiline ? availableMaxWidth : double.infinity;
@@ -2347,7 +2306,6 @@
   // the constraints used to layout the `_textPainter` is different. See
   // `TextPainter.layout`.
   void _computeTextMetricsIfNeeded() {
-    assert(constraints != null);
     _layoutText(minWidth: constraints.minWidth, maxWidth: constraints.maxWidth);
   }
 
@@ -2365,7 +2323,6 @@
   /// of the cursor for iOS is approximate and obtained through an eyeball
   /// comparison.
   void _computeCaretPrototype() {
-    assert(defaultTargetPlatform != null);
     switch (defaultTargetPlatform) {
       case TargetPlatform.iOS:
       case TargetPlatform.macOS:
@@ -2527,9 +2484,6 @@
   /// Sets the screen position of the floating cursor and the text position
   /// closest to the cursor.
   void setFloatingCursor(FloatingCursorDragState state, Offset boundedOffset, TextPosition lastTextPosition, { double? resetLerpValue }) {
-    assert(state != null);
-    assert(boundedOffset != null);
-    assert(lastTextPosition != null);
     if (state == FloatingCursorDragState.Start) {
       _relativeOrigin = Offset.zero;
       _previousOffset = null;
@@ -2880,7 +2834,6 @@
   ui.BoxHeightStyle get selectionHeightStyle => _selectionHeightStyle;
   ui.BoxHeightStyle _selectionHeightStyle = ui.BoxHeightStyle.tight;
   set selectionHeightStyle(ui.BoxHeightStyle value) {
-    assert(value != null);
     if (_selectionHeightStyle == value) {
       return;
     }
@@ -2894,7 +2847,6 @@
   ui.BoxWidthStyle get selectionWidthStyle => _selectionWidthStyle;
   ui.BoxWidthStyle _selectionWidthStyle = ui.BoxWidthStyle.tight;
   set selectionWidthStyle(ui.BoxWidthStyle value) {
-    assert(value != null);
     if (_selectionWidthStyle == value) {
       return;
     }
@@ -3073,7 +3025,6 @@
   void paint(Canvas canvas, Size size, RenderEditable renderEditable) {
     // Compute the caret location even when `shouldPaint` is false.
 
-    assert(renderEditable != null);
     final TextSelection? selection = renderEditable.selection;
 
     // TODO(LongCatIsLooong): skip painting the caret when the selection is
@@ -3102,7 +3053,7 @@
     }
 
     canvas.drawRRect(
-      RRect.fromRectAndRadius(floatingCursorRect.shift(renderEditable._paintOffset), _kFloatingCaretRadius),
+      RRect.fromRectAndRadius(floatingCursorRect, _kFloatingCaretRadius),
       floatingCursorPaint..color = floatingCursorColor,
     );
   }
diff --git a/framework/lib/src/rendering/flex.dart b/framework/lib/src/rendering/flex.dart
index 7dab129..e66c006 100644
--- a/framework/lib/src/rendering/flex.dart
+++ b/framework/lib/src/rendering/flex.dart
@@ -199,7 +199,6 @@
 }
 
 bool? _startIsTopLeft(Axis direction, TextDirection? textDirection, VerticalDirection? verticalDirection) {
-  assert(direction != null);
   // If the relevant value of textDirection or verticalDirection is null, this returns null too.
   switch (direction) {
     case Axis.horizontal:
@@ -235,8 +234,8 @@
 ///
 /// Layout for a [RenderFlex] proceeds in six steps:
 ///
-/// 1. Layout each child a null or zero flex factor with unbounded main axis
-///    constraints and the incoming cross axis constraints. If the
+/// 1. Layout each child with a null or zero flex factor with unbounded main
+///    axis constraints and the incoming cross axis constraints. If the
 ///    [crossAxisAlignment] is [CrossAxisAlignment.stretch], instead use tight
 ///    cross axis constraints that match the incoming max extent in the cross
 ///    axis.
@@ -289,12 +288,7 @@
     VerticalDirection verticalDirection = VerticalDirection.down,
     TextBaseline? textBaseline,
     Clip clipBehavior = Clip.none,
-  }) : assert(direction != null),
-       assert(mainAxisAlignment != null),
-       assert(mainAxisSize != null),
-       assert(crossAxisAlignment != null),
-       assert(clipBehavior != null),
-       _direction = direction,
+  }) : _direction = direction,
        _mainAxisAlignment = mainAxisAlignment,
        _mainAxisSize = mainAxisSize,
        _crossAxisAlignment = crossAxisAlignment,
@@ -309,7 +303,6 @@
   Axis get direction => _direction;
   Axis _direction;
   set direction(Axis value) {
-    assert(value != null);
     if (_direction != value) {
       _direction = value;
       markNeedsLayout();
@@ -328,7 +321,6 @@
   MainAxisAlignment get mainAxisAlignment => _mainAxisAlignment;
   MainAxisAlignment _mainAxisAlignment;
   set mainAxisAlignment(MainAxisAlignment value) {
-    assert(value != null);
     if (_mainAxisAlignment != value) {
       _mainAxisAlignment = value;
       markNeedsLayout();
@@ -348,7 +340,6 @@
   MainAxisSize get mainAxisSize => _mainAxisSize;
   MainAxisSize _mainAxisSize;
   set mainAxisSize(MainAxisSize value) {
-    assert(value != null);
     if (_mainAxisSize != value) {
       _mainAxisSize = value;
       markNeedsLayout();
@@ -367,7 +358,6 @@
   CrossAxisAlignment get crossAxisAlignment => _crossAxisAlignment;
   CrossAxisAlignment _crossAxisAlignment;
   set crossAxisAlignment(CrossAxisAlignment value) {
-    assert(value != null);
     if (_crossAxisAlignment != value) {
       _crossAxisAlignment = value;
       markNeedsLayout();
@@ -444,8 +434,6 @@
   }
 
   bool get _debugHasNecessaryDirections {
-    assert(direction != null);
-    assert(crossAxisAlignment != null);
     if (firstChild != null && lastChild != firstChild) {
       // i.e. there's more than one child
       switch (direction) {
@@ -453,7 +441,6 @@
           assert(textDirection != null, 'Horizontal $runtimeType with multiple children has a null textDirection, so the layout order is undefined.');
           break;
         case Axis.vertical:
-          assert(verticalDirection != null, 'Vertical $runtimeType with multiple children has a null verticalDirection, so the layout order is undefined.');
           break;
       }
     }
@@ -464,7 +451,6 @@
           assert(textDirection != null, 'Horizontal $runtimeType with $mainAxisAlignment has a null textDirection, so the alignment cannot be resolved.');
           break;
         case Axis.vertical:
-          assert(verticalDirection != null, 'Vertical $runtimeType with $mainAxisAlignment has a null verticalDirection, so the alignment cannot be resolved.');
           break;
       }
     }
@@ -472,7 +458,6 @@
         crossAxisAlignment == CrossAxisAlignment.end) {
       switch (direction) {
         case Axis.horizontal:
-          assert(verticalDirection != null, 'Horizontal $runtimeType with $crossAxisAlignment has a null verticalDirection, so the alignment cannot be resolved.');
           break;
         case Axis.vertical:
           assert(textDirection != null, 'Vertical $runtimeType with $crossAxisAlignment has a null textDirection, so the alignment cannot be resolved.');
@@ -494,7 +479,6 @@
   Clip get clipBehavior => _clipBehavior;
   Clip _clipBehavior = Clip.none;
   set clipBehavior(Clip value) {
-    assert(value != null);
     if (value != _clipBehavior) {
       _clipBehavior = value;
       markNeedsPaint();
@@ -795,7 +779,6 @@
 
   _LayoutSizes _computeSizes({required BoxConstraints constraints, required ChildLayouter layoutChild}) {
     assert(_debugHasNecessaryDirections);
-    assert(constraints != null);
 
     // Determine used flex factor, size inflexible items, calculate free space.
     int totalFlex = 0;
@@ -861,7 +844,6 @@
               minChildExtent = 0.0;
               break;
           }
-          assert(minChildExtent != null);
           final BoxConstraints innerConstraints;
           if (crossAxisAlignment == CrossAxisAlignment.stretch) {
             switch (_direction) {
diff --git a/framework/lib/src/rendering/flow.dart b/framework/lib/src/rendering/flow.dart
index 3f6d8b9..f762740 100644
--- a/framework/lib/src/rendering/flow.dart
+++ b/framework/lib/src/rendering/flow.dart
@@ -159,8 +159,8 @@
 ///
 /// Rather than positioning the children during layout, the children are
 /// positioned using transformation matrices during the paint phase using the
-/// matrices from the [FlowDelegate.paintChildren] function. The children can be
-/// repositioned efficiently by simply repainting the flow.
+/// matrices from the [FlowDelegate.paintChildren] function. The children are thus
+/// repositioned efficiently by repainting the flow, skipping layout.
 ///
 /// The most efficient way to trigger a repaint of the flow is to supply a
 /// repaint argument to the constructor of the [FlowDelegate]. The flow will
@@ -183,9 +183,7 @@
     List<RenderBox>? children,
     required FlowDelegate delegate,
     Clip clipBehavior = Clip.hardEdge,
-  }) : assert(delegate != null),
-       assert(clipBehavior != null),
-       _delegate = delegate,
+  }) : _delegate = delegate,
        _clipBehavior = clipBehavior {
     addAll(children);
   }
@@ -209,7 +207,6 @@
   /// to determine whether the new delegate requires this object to update its
   /// layout or painting.
   set delegate(FlowDelegate newDelegate) {
-    assert(newDelegate != null);
     if (_delegate == newDelegate) {
       return;
     }
@@ -234,7 +231,6 @@
   Clip get clipBehavior => _clipBehavior;
   Clip _clipBehavior = Clip.hardEdge;
   set clipBehavior(Clip value) {
-    assert(value != null);
     if (value != _clipBehavior) {
       _clipBehavior = value;
       markNeedsPaint();
diff --git a/framework/lib/src/rendering/image.dart b/framework/lib/src/rendering/image.dart
index 8430b66..99d0877 100644
--- a/framework/lib/src/rendering/image.dart
+++ b/framework/lib/src/rendering/image.dart
@@ -44,13 +44,7 @@
     bool invertColors = false,
     bool isAntiAlias = false,
     FilterQuality filterQuality = FilterQuality.low,
-  }) : assert(scale != null),
-       assert(repeat != null),
-       assert(alignment != null),
-       assert(filterQuality != null),
-       assert(matchTextDirection != null),
-       assert(isAntiAlias != null),
-       _image = image,
+  }) : _image = image,
        _width = width,
        _height = height,
        _scale = scale,
@@ -144,7 +138,6 @@
   double get scale => _scale;
   double _scale;
   set scale(double value) {
-    assert(value != null);
     if (value == _scale) {
       return;
     }
@@ -200,7 +193,6 @@
   FilterQuality get filterQuality => _filterQuality;
   FilterQuality _filterQuality;
   set filterQuality(FilterQuality value) {
-    assert(value != null);
     if (value == _filterQuality) {
       return;
     }
@@ -249,7 +241,6 @@
   AlignmentGeometry get alignment => _alignment;
   AlignmentGeometry _alignment;
   set alignment(AlignmentGeometry value) {
-    assert(value != null);
     if (value == _alignment) {
       return;
     }
@@ -261,7 +252,6 @@
   ImageRepeat get repeat => _repeat;
   ImageRepeat _repeat;
   set repeat(ImageRepeat value) {
-    assert(value != null);
     if (value == _repeat) {
       return;
     }
@@ -318,7 +308,6 @@
   bool get matchTextDirection => _matchTextDirection;
   bool _matchTextDirection;
   set matchTextDirection(bool value) {
-    assert(value != null);
     if (value == _matchTextDirection) {
       return;
     }
@@ -350,7 +339,6 @@
     if (_isAntiAlias == value) {
       return;
     }
-    assert(value != null);
     _isAntiAlias = value;
     markNeedsPaint();
   }
diff --git a/framework/lib/src/rendering/layer.dart b/framework/lib/src/rendering/layer.dart
index 6995c8c..935c183 100644
--- a/framework/lib/src/rendering/layer.dart
+++ b/framework/lib/src/rendering/layer.dart
@@ -24,7 +24,7 @@
   const AnnotationEntry({
     required this.annotation,
     required this.localPosition,
-  }) : assert(localPosition != null);
+  });
 
   /// The annotation object that is found.
   final T annotation;
@@ -168,9 +168,7 @@
     assert(delta != 0);
     _compositionCallbackCount += delta;
     assert(_compositionCallbackCount >= 0);
-    if (parent != null) {
-      parent!._updateSubtreeCompositionObserverCount(delta);
-    }
+    parent?._updateSubtreeCompositionObserverCount(delta);
   }
 
   void _fireCompositionCallbacks({required bool includeChildren}) {
@@ -546,7 +544,7 @@
   /// should search for annotations, or if the layer has its own annotations to
   /// add.
   ///
-  /// The default implementation simply returns `false`, which means neither
+  /// The default implementation always returns `false`, which means neither
   /// the layer nor its children has annotations, and the annotation search
   /// is not absorbed either (see below for explanation).
   ///
@@ -602,7 +600,7 @@
   ///
   /// Returns null if no matching annotations are found.
   ///
-  /// By default this method simply calls [findAnnotations] with `onlyFirst:
+  /// By default this method calls [findAnnotations] with `onlyFirst:
   /// true` and returns the annotation of the first result. Prefer overriding
   /// [findAnnotations] instead of this method, because during an annotation
   /// search, only [findAnnotations] is recursively called, while custom
@@ -628,7 +626,7 @@
   ///
   /// Returns a result with empty entries if no matching annotations are found.
   ///
-  /// By default this method simply calls [findAnnotations] with `onlyFirst:
+  /// By default this method calls [findAnnotations] with `onlyFirst:
   /// false` and returns the annotations of its result. Prefer overriding
   /// [findAnnotations] instead of this method, because during an annotation
   /// search, only [findAnnotations] is recursively called, while custom
@@ -891,8 +889,7 @@
     required this.textureId,
     this.freeze = false,
     this.filterQuality = ui.FilterQuality.low,
-  }) : assert(rect != null),
-       assert(textureId != null);
+  });
 
   /// Bounding rectangle of this layer.
   final Rect rect;
@@ -939,8 +936,7 @@
   PlatformViewLayer({
     required this.rect,
     required this.viewId,
-  }) : assert(rect != null),
-       assert(viewId != null);
+  });
 
   /// Bounding rectangle of this layer in the global coordinate space.
   final Rect rect;
@@ -1027,7 +1023,6 @@
 
   @override
   void addToScene(ui.SceneBuilder builder) {
-    assert(optionsMask != null);
     builder.addPerformanceOverlay(optionsMask, overlayRect);
     builder.setRasterizerTracingThreshold(rasterizerThreshold);
     builder.setCheckerboardRasterCacheImages(checkerboardRasterCacheImages);
@@ -1255,7 +1250,6 @@
       child._nextSibling = null;
       assert(child.attached == attached);
       dropChild(child);
-      assert(child._parentHandle != null);
       child._parentHandle.layer = null;
       child = next;
     }
@@ -1318,7 +1312,6 @@
   /// position.
   void applyTransform(Layer? child, Matrix4 transform) {
     assert(child != null);
-    assert(transform != null);
   }
 
   /// Returns the descendants of this layer in depth first order.
@@ -1399,7 +1392,6 @@
   @override
   void applyTransform(Layer? child, Matrix4 transform) {
     assert(child != null);
-    assert(transform != null);
     transform.translate(offset.dx, offset.dy);
   }
 
@@ -1426,8 +1418,6 @@
   }
 
   ui.Scene _createSceneForImage(Rect bounds, { double pixelRatio = 1.0 }) {
-    assert(bounds != null);
-    assert(pixelRatio != null);
     final ui.SceneBuilder builder = ui.SceneBuilder();
     final Matrix4 transform = Matrix4.diagonal3Values(pixelRatio, pixelRatio, 1);
     transform.translate(-(bounds.left + offset.dx), -(bounds.top + offset.dy));
@@ -1521,7 +1511,6 @@
     Clip clipBehavior = Clip.hardEdge,
   }) : _clipRect = clipRect,
        _clipBehavior = clipBehavior,
-       assert(clipBehavior != null),
        assert(clipBehavior != Clip.none);
 
   /// The rectangle to clip in the parent's coordinate system.
@@ -1550,7 +1539,6 @@
   Clip get clipBehavior => _clipBehavior;
   Clip _clipBehavior;
   set clipBehavior(Clip value) {
-    assert(value != null);
     assert(value != Clip.none);
     if (value != _clipBehavior) {
       _clipBehavior = value;
@@ -1569,7 +1557,6 @@
   @override
   void addToScene(ui.SceneBuilder builder) {
     assert(clipRect != null);
-    assert(clipBehavior != null);
     bool enabled = true;
     assert(() {
       enabled = !debugDisableClipLayers;
@@ -1613,7 +1600,6 @@
     Clip clipBehavior = Clip.antiAlias,
   }) : _clipRRect = clipRRect,
        _clipBehavior = clipBehavior,
-       assert(clipBehavior != null),
        assert(clipBehavior != Clip.none);
 
   /// The rounded-rect to clip in the parent's coordinate system.
@@ -1638,7 +1624,6 @@
   Clip get clipBehavior => _clipBehavior;
   Clip _clipBehavior;
   set clipBehavior(Clip value) {
-    assert(value != null);
     assert(value != Clip.none);
     if (value != _clipBehavior) {
       _clipBehavior = value;
@@ -1657,7 +1642,6 @@
   @override
   void addToScene(ui.SceneBuilder builder) {
     assert(clipRRect != null);
-    assert(clipBehavior != null);
     bool enabled = true;
     assert(() {
       enabled = !debugDisableClipLayers;
@@ -1701,7 +1685,6 @@
     Clip clipBehavior = Clip.antiAlias,
   }) : _clipPath = clipPath,
        _clipBehavior = clipBehavior,
-       assert(clipBehavior != null),
        assert(clipBehavior != Clip.none);
 
   /// The path to clip in the parent's coordinate system.
@@ -1726,7 +1709,6 @@
   Clip get clipBehavior => _clipBehavior;
   Clip _clipBehavior;
   set clipBehavior(Clip value) {
-    assert(value != null);
     assert(value != Clip.none);
     if (value != _clipBehavior) {
       _clipBehavior = value;
@@ -1745,7 +1727,6 @@
   @override
   void addToScene(ui.SceneBuilder builder) {
     assert(clipPath != null);
-    assert(clipBehavior != null);
     bool enabled = true;
     assert(() {
       enabled = !debugDisableClipLayers;
@@ -1940,7 +1921,6 @@
   @override
   void applyTransform(Layer? child, Matrix4 transform) {
     assert(child != null);
-    assert(transform != null);
     assert(_lastEffectiveTransform != null || this.transform != null);
     if (_lastEffectiveTransform == null) {
       transform.multiply(this.transform!);
@@ -2250,7 +2230,6 @@
   Clip get clipBehavior => _clipBehavior;
   Clip _clipBehavior;
   set clipBehavior(Clip value) {
-    assert(value != null);
     if (value != _clipBehavior) {
       _clipBehavior = value;
       markNeedsAddToScene();
@@ -2310,7 +2289,6 @@
   @override
   void addToScene(ui.SceneBuilder builder) {
     assert(clipPath != null);
-    assert(clipBehavior != null);
     assert(elevation != null);
     assert(color != null);
     assert(shadowColor != null);
@@ -2437,7 +2415,7 @@
   ///
   /// The [offset] property must be non-null before the compositing phase of the
   /// pipeline.
-  LeaderLayer({ required LayerLink link, Offset offset = Offset.zero }) : assert(link != null), _link = link, _offset = offset;
+  LeaderLayer({ required LayerLink link, Offset offset = Offset.zero }) : _link = link, _offset = offset;
 
   /// The object with which this layer should register.
   ///
@@ -2446,7 +2424,6 @@
   LayerLink get link => _link;
   LayerLink _link;
   set link(LayerLink value) {
-    assert(value != null);
     if (_link == value) {
       return;
     }
@@ -2467,7 +2444,6 @@
   Offset get offset => _offset;
   Offset _offset;
   set offset(Offset value) {
-    assert(value != null);
     if (value == _offset) {
       return;
     }
@@ -2496,7 +2472,6 @@
 
   @override
   void addToScene(ui.SceneBuilder builder) {
-    assert(offset != null);
     if (offset != Offset.zero) {
       engineLayer = builder.pushTransform(
         Matrix4.translationValues(offset.dx, offset.dy, 0.0).storage,
@@ -2551,23 +2526,18 @@
   /// The [unlinkedOffset], [linkedOffset], and [showWhenUnlinked] properties
   /// must be non-null before the compositing phase of the pipeline.
   FollowerLayer({
-    required LayerLink link,
+    required this.link,
     this.showWhenUnlinked = true,
     this.unlinkedOffset = Offset.zero,
     this.linkedOffset = Offset.zero,
-  }) : assert(link != null), _link = link;
+  });
 
   /// The link to the [LeaderLayer].
   ///
   /// The same object should be provided to a [LeaderLayer] that is earlier in
   /// the layer tree. When this layer is composited, it will apply a transform
   /// that moves its children to match the position of the [LeaderLayer].
-  LayerLink get link => _link;
-  set link(LayerLink value) {
-    assert(value != null);
-    _link = value;
-  }
-  LayerLink _link;
+  LayerLink link;
 
   /// Whether to show the layer's contents when the [link] does not point to a
   /// [LeaderLayer].
@@ -2631,7 +2601,7 @@
 
   @override
   bool findAnnotations<S extends Object>(AnnotationResult<S> result, Offset localPosition, { required bool onlyFirst }) {
-    if (_link.leader == null) {
+    if (link.leader == null) {
       if (showWhenUnlinked!) {
         return super.findAnnotations(result, localPosition - unlinkedOffset!, onlyFirst: onlyFirst);
       }
@@ -2740,9 +2710,8 @@
 
   /// Populate [_lastTransform] given the current state of the tree.
   void _establishTransform() {
-    assert(link != null);
     _lastTransform = null;
-    final LeaderLayer? leader = _link.leader;
+    final LeaderLayer? leader = link.leader;
     // Check to see if we are linked.
     if (leader == null) {
       return;
@@ -2806,9 +2775,8 @@
 
   @override
   void addToScene(ui.SceneBuilder builder) {
-    assert(link != null);
     assert(showWhenUnlinked != null);
-    if (_link.leader == null && !showWhenUnlinked!) {
+    if (link.leader == null && !showWhenUnlinked!) {
       _lastTransform = null;
       _lastOffset = null;
       _inverseDirty = true;
@@ -2840,7 +2808,6 @@
   @override
   void applyTransform(Layer? child, Matrix4 transform) {
     assert(child != null);
-    assert(transform != null);
     if (_lastTransform != null) {
       transform.multiply(_lastTransform!);
     } else {
@@ -2886,9 +2853,7 @@
     this.size,
     Offset? offset,
     this.opaque = false,
-  }) : assert(value != null),
-       assert(opaque != null),
-       offset = offset ?? Offset.zero;
+  }) : offset = offset ?? Offset.zero;
 
   /// The annotated object, which is added to the result if all restrictions are
   /// met.
diff --git a/framework/lib/src/rendering/list_body.dart b/framework/lib/src/rendering/list_body.dart
index a4ca501..bc504cd 100644
--- a/framework/lib/src/rendering/list_body.dart
+++ b/framework/lib/src/rendering/list_body.dart
@@ -32,8 +32,7 @@
   RenderListBody({
     List<RenderBox>? children,
     AxisDirection axisDirection = AxisDirection.down,
-  }) : assert(axisDirection != null),
-       _axisDirection = axisDirection {
+  }) : _axisDirection = axisDirection {
     addAll(children);
   }
 
@@ -51,7 +50,6 @@
   AxisDirection get axisDirection => _axisDirection;
   AxisDirection _axisDirection;
   set axisDirection(AxisDirection value) {
-    assert(value != null);
     if (_axisDirection == value) {
       return;
     }
@@ -259,7 +257,6 @@
 
   @override
   double computeMinIntrinsicWidth(double height) {
-    assert(mainAxis != null);
     switch (mainAxis) {
       case Axis.horizontal:
         return _getIntrinsicMainAxis((RenderBox child) => child.getMinIntrinsicWidth(height));
@@ -270,7 +267,6 @@
 
   @override
   double computeMaxIntrinsicWidth(double height) {
-    assert(mainAxis != null);
     switch (mainAxis) {
       case Axis.horizontal:
         return _getIntrinsicMainAxis((RenderBox child) => child.getMaxIntrinsicWidth(height));
@@ -281,7 +277,6 @@
 
   @override
   double computeMinIntrinsicHeight(double width) {
-    assert(mainAxis != null);
     switch (mainAxis) {
       case Axis.horizontal:
         return _getIntrinsicMainAxis((RenderBox child) => child.getMinIntrinsicHeight(width));
@@ -292,7 +287,6 @@
 
   @override
   double computeMaxIntrinsicHeight(double width) {
-    assert(mainAxis != null);
     switch (mainAxis) {
       case Axis.horizontal:
         return _getIntrinsicMainAxis((RenderBox child) => child.getMaxIntrinsicHeight(width));
diff --git a/framework/lib/src/rendering/list_wheel_viewport.dart b/framework/lib/src/rendering/list_wheel_viewport.dart
index e2f2264..a6dbebe 100644
--- a/framework/lib/src/rendering/list_wheel_viewport.dart
+++ b/framework/lib/src/rendering/list_wheel_viewport.dart
@@ -149,25 +149,13 @@
     bool renderChildrenOutsideViewport = false,
     Clip clipBehavior = Clip.none,
     List<RenderBox>? children,
-  }) : assert(childManager != null),
-       assert(offset != null),
-       assert(diameterRatio != null),
-       assert(diameterRatio > 0, diameterRatioZeroMessage),
-       assert(perspective != null),
+  }) : assert(diameterRatio > 0, diameterRatioZeroMessage),
        assert(perspective > 0),
        assert(perspective <= 0.01, perspectiveTooHighMessage),
-       assert(offAxisFraction != null),
-       assert(useMagnifier != null),
-       assert(magnification != null),
        assert(magnification > 0),
-       assert(overAndUnderCenterOpacity != null),
        assert(overAndUnderCenterOpacity >= 0 && overAndUnderCenterOpacity <= 1),
-       assert(itemExtent != null),
-       assert(squeeze != null),
        assert(squeeze > 0),
        assert(itemExtent > 0),
-       assert(renderChildrenOutsideViewport != null),
-       assert(clipBehavior != null),
        assert(
          !renderChildrenOutsideViewport || clipBehavior == Clip.none,
          clipBehaviorAndRenderChildrenOutsideViewportConflict,
@@ -225,7 +213,6 @@
   ViewportOffset get offset => _offset;
   ViewportOffset _offset;
   set offset(ViewportOffset value) {
-    assert(value != null);
     if (value == _offset) {
       return;
     }
@@ -270,7 +257,6 @@
   double get diameterRatio => _diameterRatio;
   double _diameterRatio;
   set diameterRatio(double value) {
-    assert(value != null);
     assert(
       value > 0,
       diameterRatioZeroMessage,
@@ -300,7 +286,6 @@
   double get perspective => _perspective;
   double _perspective;
   set perspective(double value) {
-    assert(value != null);
     assert(value > 0);
     assert(
       value <= 0.01,
@@ -342,7 +327,6 @@
   double get offAxisFraction => _offAxisFraction;
   double _offAxisFraction = 0.0;
   set offAxisFraction(double value) {
-    assert(value != null);
     if (value == _offAxisFraction) {
       return;
     }
@@ -356,7 +340,6 @@
   bool get useMagnifier => _useMagnifier;
   bool _useMagnifier = false;
   set useMagnifier(bool value) {
-    assert(value != null);
     if (value == _useMagnifier) {
       return;
     }
@@ -376,7 +359,6 @@
   double get magnification => _magnification;
   double _magnification = 1.0;
   set magnification(double value) {
-    assert(value != null);
     assert(value > 0);
     if (value == _magnification) {
       return;
@@ -396,7 +378,6 @@
   double get overAndUnderCenterOpacity => _overAndUnderCenterOpacity;
   double _overAndUnderCenterOpacity = 1.0;
   set overAndUnderCenterOpacity(double value) {
-    assert(value != null);
     assert(value >= 0 && value <= 1);
     if (value == _overAndUnderCenterOpacity) {
       return;
@@ -414,7 +395,6 @@
   double get itemExtent => _itemExtent;
   double _itemExtent;
   set itemExtent(double value) {
-    assert(value != null);
     assert(value > 0);
     if (value == _itemExtent) {
       return;
@@ -447,7 +427,6 @@
   double get squeeze => _squeeze;
   double _squeeze;
   set squeeze(double value) {
-    assert(value != null);
     assert(value > 0);
     if (value == _squeeze) {
       return;
@@ -470,7 +449,6 @@
   bool get renderChildrenOutsideViewport => _renderChildrenOutsideViewport;
   bool _renderChildrenOutsideViewport;
   set renderChildrenOutsideViewport(bool value) {
-    assert(value != null);
     assert(
       !renderChildrenOutsideViewport || clipBehavior == Clip.none,
       clipBehaviorAndRenderChildrenOutsideViewportConflict,
@@ -489,7 +467,6 @@
   Clip get clipBehavior => _clipBehavior;
   Clip _clipBehavior = Clip.hardEdge;
   set clipBehavior(Clip value) {
-    assert(value != null);
     if (value != _clipBehavior) {
       _clipBehavior = value;
       markNeedsPaint();
@@ -636,7 +613,6 @@
   ///
   /// This relies on the [childManager] maintaining [ListWheelParentData.index].
   int indexOf(RenderBox child) {
-    assert(child != null);
     final ListWheelParentData childParentData = child.parentData! as ListWheelParentData;
     assert(childParentData.index != null);
     return childParentData.index!;
diff --git a/framework/lib/src/rendering/mouse_tracker.dart b/framework/lib/src/rendering/mouse_tracker.dart
index 8a02c9b..022c71a 100644
--- a/framework/lib/src/rendering/mouse_tracker.dart
+++ b/framework/lib/src/rendering/mouse_tracker.dart
@@ -30,8 +30,7 @@
 class _MouseState {
   _MouseState({
     required PointerEvent initialEvent,
-  }) : assert(initialEvent != null),
-       _latestEvent = initialEvent;
+  }) : _latestEvent = initialEvent;
 
   // The list of annotations that contains this device.
   //
@@ -40,7 +39,6 @@
   LinkedHashMap<MouseTrackerAnnotation, Matrix4> _annotations = LinkedHashMap<MouseTrackerAnnotation, Matrix4>();
 
   LinkedHashMap<MouseTrackerAnnotation, Matrix4> replaceAnnotations(LinkedHashMap<MouseTrackerAnnotation, Matrix4> value) {
-    assert(value != null);
     final LinkedHashMap<MouseTrackerAnnotation, Matrix4> previous = _annotations;
     _annotations = value;
     return previous;
@@ -51,7 +49,6 @@
   PointerEvent _latestEvent;
 
   PointerEvent replaceLatestEvent(PointerEvent value) {
-    assert(value != null);
     assert(value.device == _latestEvent.device);
     final PointerEvent previous = _latestEvent;
     _latestEvent = value;
@@ -83,10 +80,7 @@
     required this.lastAnnotations,
     required this.nextAnnotations,
     required PointerEvent this.previousEvent,
-  }) : assert(previousEvent != null),
-       assert(lastAnnotations != null),
-       assert(nextAnnotations != null),
-       triggeringEvent = null;
+  }) : triggeringEvent = null;
 
   /// When device update is triggered by a pointer event.
   ///
@@ -97,9 +91,7 @@
     required this.nextAnnotations,
     this.previousEvent,
     required PointerEvent this.triggeringEvent,
-  }) : assert(triggeringEvent != null),
-       assert(lastAnnotations != null),
-       assert(nextAnnotations != null);
+  });
 
   /// The annotations that the device is hovering before the update.
   ///
@@ -129,7 +121,6 @@
   /// The pointing device of this update.
   int get device {
     final int result = (previousEvent ?? triggeringEvent)!.device;
-    assert(result != null);
     return result;
   }
 
@@ -138,7 +129,6 @@
   /// The [latestEvent] is never null.
   PointerEvent get latestEvent {
     final PointerEvent result = triggeringEvent ?? previousEvent!;
-    assert(result != null);
     return result;
   }
 
@@ -219,7 +209,6 @@
     if (state == null) {
       return true;
     }
-    assert(event != null);
     final PointerEvent lastEvent = state.latestEvent;
     assert(event.device == lastEvent.device);
     // An Added can only follow a Removed, and a Removed can only be followed
@@ -236,7 +225,6 @@
   }
 
   LinkedHashMap<MouseTrackerAnnotation, Matrix4> _hitTestResultToAnnotations(HitTestResult result) {
-    assert(result != null);
     final LinkedHashMap<MouseTrackerAnnotation, Matrix4> annotations = LinkedHashMap<MouseTrackerAnnotation, Matrix4>();
     for (final HitTestEntry entry in result.path) {
       final Object target = entry.target;
@@ -253,8 +241,6 @@
   // If the device is not connected or not a mouse, an empty map is returned
   // without calling `hitTest`.
   LinkedHashMap<MouseTrackerAnnotation, Matrix4> _findAnnotations(_MouseState state, MouseDetectorAnnotationFinder hitTest) {
-    assert(state != null);
-    assert(hitTest != null);
     final Offset globalPosition = state.latestEvent.position;
     final int device = state.device;
     if (!_mouseStates.containsKey(device)) {
@@ -302,8 +288,8 @@
   /// [MouseTracker] filter which to react to.
   ///
   /// The `getResult` is a function to return the hit test result at the
-  /// position of the event. It should not simply return cached hit test
-  /// result, because the cache does not change throughout a tap sequence.
+  /// position of the event. It should not return a cached hit test
+  /// result, because the cache would not change during a tap sequence.
   void updateWithEvent(PointerEvent event, ValueGetter<HitTestResult> getResult) {
     if (event.kind != PointerDeviceKind.mouse) {
       return;
diff --git a/framework/lib/src/rendering/object.dart b/framework/lib/src/rendering/object.dart
index 40ced32..c1f1159 100644
--- a/framework/lib/src/rendering/object.dart
+++ b/framework/lib/src/rendering/object.dart
@@ -10,6 +10,7 @@
 import 'package:flute/foundation.dart';
 import 'package:flute/gestures.dart';
 import 'package:flute/painting.dart';
+import 'package:flute/scheduler.dart';
 import 'package:flute/semantics.dart';
 
 import 'debug.dart';
@@ -82,9 +83,7 @@
   /// Typically only called by [PaintingContext.repaintCompositedChild]
   /// and [pushLayer].
   @protected
-  PaintingContext(this._containerLayer, this.estimatedBounds)
-    : assert(_containerLayer != null),
-      assert(estimatedBounds != null);
+  PaintingContext(this._containerLayer, this.estimatedBounds);
 
   final ContainerLayer _containerLayer;
 
@@ -461,7 +460,6 @@
   ///  * [addLayer], for pushing a layer without painting further contents
   ///    within it.
   void pushLayer(ContainerLayer childLayer, PaintingContextCallback painter, Offset offset, { Rect? childPaintBounds }) {
-    assert(painter != null);
     // If a layer is being reused, it may already contain children. We remove
     // them so that `painter` can add children that are relevant for this frame.
     if (childLayer.hasChildren) {
@@ -559,7 +557,6 @@
   ///
   /// {@macro flutter.rendering.PaintingContext.pushClipRect.oldLayer}
   ClipRRectLayer? pushClipRRect(bool needsCompositing, Offset offset, Rect bounds, RRect clipRRect, PaintingContextCallback painter, { Clip clipBehavior = Clip.antiAlias, ClipRRectLayer? oldLayer }) {
-    assert(clipBehavior != null);
     if (clipBehavior == Clip.none) {
       painter(this, offset);
       return null;
@@ -599,7 +596,6 @@
   ///
   /// {@macro flutter.rendering.PaintingContext.pushClipRect.oldLayer}
   ClipPathLayer? pushClipPath(bool needsCompositing, Offset offset, Rect bounds, Path clipPath, PaintingContextCallback painter, { Clip clipBehavior = Clip.antiAlias, ClipPathLayer? oldLayer }) {
-    assert(clipBehavior != null);
     if (clipBehavior == Clip.none) {
       painter(this, offset);
       return null;
@@ -636,7 +632,6 @@
   /// ancestor render objects that this render object will include a composited
   /// layer, which, for example, causes them to use composited clips.
   ColorFilterLayer pushColorFilter(Offset offset, ColorFilter colorFilter, PaintingContextCallback painter, { ColorFilterLayer? oldLayer }) {
-    assert(colorFilter != null);
     final ColorFilterLayer layer = oldLayer ?? ColorFilterLayer();
     layer.colorFilter = colorFilter;
     pushLayer(layer, painter, offset);
@@ -830,8 +825,7 @@
 /// You can obtain the [PipelineOwner] using the [RenderObject.owner] property.
 class SemanticsHandle {
   SemanticsHandle._(PipelineOwner owner, this.listener)
-      : assert(owner != null),
-        _owner = owner {
+      : _owner = owner {
     if (listener != null) {
       _owner.semanticsOwner!.addListener(listener!);
     }
@@ -1509,7 +1503,6 @@
   @override
   void adoptChild(RenderObject child) {
     assert(_debugCanPerformMutations);
-    assert(child != null);
     setupParentData(child);
     markNeedsLayout();
     markNeedsCompositingBitsUpdate();
@@ -1524,7 +1517,6 @@
   @override
   void dropChild(RenderObject child) {
     assert(_debugCanPerformMutations);
-    assert(child != null);
     assert(child.parentData != null);
     child._cleanRelayoutBoundary();
     child.parentData!.detach();
@@ -2078,7 +2070,6 @@
         arguments: debugTimelineArguments,
       );
     }
-    assert(constraints != null);
     assert(constraints.debugAssertIsValid(
       isAppliedConstraint: true,
       informationCollector: () {
@@ -3100,6 +3091,10 @@
     if (_cachedSemanticsConfiguration == null) {
       _cachedSemanticsConfiguration = SemanticsConfiguration();
       describeSemanticsConfiguration(_cachedSemanticsConfiguration!);
+      assert(
+        !_cachedSemanticsConfiguration!.explicitChildNodes || _cachedSemanticsConfiguration!.childConfigurationsDelegate == null,
+        'A SemanticsConfiguration with explicitChildNode set to true cannot have a non-null childConfigsDelegate.',
+      );
     }
     return _cachedSemanticsConfiguration!;
   }
@@ -3160,15 +3155,30 @@
     // the semantics subtree starting at the identified semantics boundary.
 
     final bool wasSemanticsBoundary = _semantics != null && (_cachedSemanticsConfiguration?.isSemanticBoundary ?? false);
+
+    bool mayProduceSiblingNodes =
+      _cachedSemanticsConfiguration?.childConfigurationsDelegate != null ||
+      _semanticsConfiguration.childConfigurationsDelegate != null;
     _cachedSemanticsConfiguration = null;
+
     bool isEffectiveSemanticsBoundary = _semanticsConfiguration.isSemanticBoundary && wasSemanticsBoundary;
     RenderObject node = this;
 
-    while (!isEffectiveSemanticsBoundary && node.parent is RenderObject) {
+    // The sibling nodes will be attached to the parent of immediate semantics
+    // node, thus marking this semantics boundary dirty is not enough, it needs
+    // to find the first parent semantics boundary that does not have any
+    // possible sibling node.
+    while (node.parent is RenderObject && (mayProduceSiblingNodes || !isEffectiveSemanticsBoundary)) {
       if (node != this && node._needsSemanticsUpdate) {
         break;
       }
       node._needsSemanticsUpdate = true;
+      // Since this node is a semantics boundary, the produced sibling nodes will
+      // be attached to the parent semantics boundary. Thus, these sibling nodes
+      // will not be carried to the next loop.
+      if (isEffectiveSemanticsBoundary) {
+        mayProduceSiblingNodes = false;
+      }
 
       node = node.parent! as RenderObject;
       isEffectiveSemanticsBoundary = node._semanticsConfiguration.isSemanticBoundary;
@@ -3213,92 +3223,116 @@
     assert(fragment is _InterestingSemanticsFragment);
     final _InterestingSemanticsFragment interestingFragment = fragment as _InterestingSemanticsFragment;
     final List<SemanticsNode> result = <SemanticsNode>[];
+    final List<SemanticsNode> siblingNodes = <SemanticsNode>[];
     interestingFragment.compileChildren(
       parentSemanticsClipRect: _semantics?.parentSemanticsClipRect,
       parentPaintClipRect: _semantics?.parentPaintClipRect,
       elevationAdjustment: _semantics?.elevationAdjustment ?? 0.0,
       result: result,
+      siblingNodes: siblingNodes,
     );
-    final SemanticsNode node = result.single;
-    // Fragment only wants to add this node's SemanticsNode to the parent.
-    assert(interestingFragment.config == null && node == _semantics);
+    // Result may contain sibling nodes that are irrelevant for this update.
+    assert(interestingFragment.config == null && result.any((SemanticsNode node) => node == _semantics));
   }
 
   /// Returns the semantics that this node would like to add to its parent.
   _SemanticsFragment _getSemanticsForParent({
     required bool mergeIntoParent,
   }) {
-    assert(mergeIntoParent != null);
     assert(!_needsLayout, 'Updated layout information required for $this to calculate semantics.');
 
     final SemanticsConfiguration config = _semanticsConfiguration;
     bool dropSemanticsOfPreviousSiblings = config.isBlockingSemanticsOfPreviouslyPaintedNodes;
 
     final bool producesForkingFragment = !config.hasBeenAnnotated && !config.isSemanticBoundary;
-    final List<_InterestingSemanticsFragment> fragments = <_InterestingSemanticsFragment>[];
-    final Set<_InterestingSemanticsFragment> toBeMarkedExplicit = <_InterestingSemanticsFragment>{};
     final bool childrenMergeIntoParent = mergeIntoParent || config.isMergingSemanticsOfDescendants;
-
+    final List<SemanticsConfiguration> childConfigurations = <SemanticsConfiguration>[];
+    final bool explicitChildNode = config.explicitChildNodes || parent is! RenderObject;
+    final bool hasChildConfigurationsDelegate = config.childConfigurationsDelegate != null;
+    final Map<SemanticsConfiguration, _InterestingSemanticsFragment> configToFragment = <SemanticsConfiguration, _InterestingSemanticsFragment>{};
+    final List<_InterestingSemanticsFragment> mergeUpFragments = <_InterestingSemanticsFragment>[];
+    final List<List<_InterestingSemanticsFragment>> siblingMergeFragmentGroups = <List<_InterestingSemanticsFragment>>[];
     visitChildrenForSemantics((RenderObject renderChild) {
       assert(!_needsLayout);
       final _SemanticsFragment parentFragment = renderChild._getSemanticsForParent(
         mergeIntoParent: childrenMergeIntoParent,
       );
       if (parentFragment.dropsSemanticsOfPreviousSiblings) {
-        fragments.clear();
-        toBeMarkedExplicit.clear();
+        childConfigurations.clear();
+        mergeUpFragments.clear();
+        siblingMergeFragmentGroups.clear();
         if (!config.isSemanticBoundary) {
           dropSemanticsOfPreviousSiblings = true;
         }
       }
-      // Figure out which child fragments are to be made explicit.
-      for (final _InterestingSemanticsFragment fragment in parentFragment.interestingFragments) {
-        fragments.add(fragment);
+      for (final _InterestingSemanticsFragment fragment in parentFragment.mergeUpFragments) {
         fragment.addAncestor(this);
         fragment.addTags(config.tagsForChildren);
-        if (config.explicitChildNodes || parent is! RenderObject) {
-          fragment.markAsExplicit();
-          continue;
+        if (hasChildConfigurationsDelegate && fragment.config != null) {
+          // This fragment need to go through delegate to determine whether it
+          // merge up or not.
+          childConfigurations.add(fragment.config!);
+          configToFragment[fragment.config!] = fragment;
+        } else {
+          mergeUpFragments.add(fragment);
         }
-        if (!fragment.hasConfigForParent || producesForkingFragment) {
-          continue;
-        }
-        if (!config.isCompatibleWith(fragment.config)) {
-          toBeMarkedExplicit.add(fragment);
-        }
-        final int siblingLength = fragments.length - 1;
-        for (int i = 0; i < siblingLength; i += 1) {
-          final _InterestingSemanticsFragment siblingFragment = fragments[i];
-          if (!fragment.config!.isCompatibleWith(siblingFragment.config)) {
-            toBeMarkedExplicit.add(fragment);
-            toBeMarkedExplicit.add(siblingFragment);
+      }
+      if (parentFragment is _ContainerSemanticsFragment) {
+        // Container fragments needs to propagate sibling merge group to be
+        // compiled by _SwitchableSemanticsFragment.
+        for (final List<_InterestingSemanticsFragment> siblingMergeGroup in parentFragment.siblingMergeGroups) {
+          for (final _InterestingSemanticsFragment siblingMergingFragment in siblingMergeGroup) {
+            siblingMergingFragment.addAncestor(this);
+            siblingMergingFragment.addTags(config.tagsForChildren);
           }
+          siblingMergeFragmentGroups.add(siblingMergeGroup);
         }
       }
     });
 
-    for (final _InterestingSemanticsFragment fragment in toBeMarkedExplicit) {
-      fragment.markAsExplicit();
+    assert(hasChildConfigurationsDelegate || configToFragment.isEmpty);
+
+    if (explicitChildNode) {
+      for (final _InterestingSemanticsFragment fragment in mergeUpFragments) {
+        fragment.markAsExplicit();
+      }
+    } else if (hasChildConfigurationsDelegate && childConfigurations.isNotEmpty) {
+      final ChildSemanticsConfigurationsResult result = config.childConfigurationsDelegate!(childConfigurations);
+      mergeUpFragments.addAll(
+        result.mergeUp.map<_InterestingSemanticsFragment>((SemanticsConfiguration config) => configToFragment[config]!),
+      );
+      for (final Iterable<SemanticsConfiguration> group in result.siblingMergeGroups) {
+        siblingMergeFragmentGroups.add(
+          group.map<_InterestingSemanticsFragment>((SemanticsConfiguration config) => configToFragment[config]!).toList()
+        );
+      }
     }
 
     _needsSemanticsUpdate = false;
 
-    _SemanticsFragment result;
+    final _SemanticsFragment result;
     if (parent is! RenderObject) {
       assert(!config.hasBeenAnnotated);
       assert(!mergeIntoParent);
+      assert(siblingMergeFragmentGroups.isEmpty);
+      _marksExplicitInMergeGroup(mergeUpFragments, isMergeUp: true);
+      siblingMergeFragmentGroups.forEach(_marksExplicitInMergeGroup);
       result = _RootSemanticsFragment(
         owner: this,
         dropsSemanticsOfPreviousSiblings: dropSemanticsOfPreviousSiblings,
       );
     } else if (producesForkingFragment) {
       result = _ContainerSemanticsFragment(
+        siblingMergeGroups: siblingMergeFragmentGroups,
         dropsSemanticsOfPreviousSiblings: dropSemanticsOfPreviousSiblings,
       );
     } else {
+      _marksExplicitInMergeGroup(mergeUpFragments, isMergeUp: true);
+      siblingMergeFragmentGroups.forEach(_marksExplicitInMergeGroup);
       result = _SwitchableSemanticsFragment(
         config: config,
         mergeIntoParent: mergeIntoParent,
+        siblingMergeGroups: siblingMergeFragmentGroups,
         owner: this,
         dropsSemanticsOfPreviousSiblings: dropSemanticsOfPreviousSiblings,
       );
@@ -3307,12 +3341,34 @@
         fragment.markAsExplicit();
       }
     }
-
-    result.addAll(fragments);
-
+    result.addAll(mergeUpFragments);
     return result;
   }
 
+  void _marksExplicitInMergeGroup(List<_InterestingSemanticsFragment> mergeGroup, {bool isMergeUp = false}) {
+    final Set<_InterestingSemanticsFragment> toBeExplicit = <_InterestingSemanticsFragment>{};
+    for (int i = 0; i < mergeGroup.length; i += 1) {
+      final _InterestingSemanticsFragment fragment = mergeGroup[i];
+      if (!fragment.hasConfigForParent) {
+        continue;
+      }
+      if (isMergeUp && !_semanticsConfiguration.isCompatibleWith(fragment.config)) {
+        toBeExplicit.add(fragment);
+      }
+      final int siblingLength = i;
+      for (int j = 0; j < siblingLength; j += 1) {
+        final _InterestingSemanticsFragment siblingFragment = mergeGroup[j];
+        if (!fragment.config!.isCompatibleWith(siblingFragment.config)) {
+          toBeExplicit.add(fragment);
+          toBeExplicit.add(siblingFragment);
+        }
+      }
+    }
+    for (final _InterestingSemanticsFragment fragment in toBeExplicit) {
+      fragment.markAsExplicit();
+    }
+  }
+
   /// Called when collecting the semantics of this node.
   ///
   /// The implementation has to return the children in paint order skipping all
@@ -3908,7 +3964,6 @@
 
   /// The previous child before the given child in the child list.
   ChildType? childBefore(ChildType child) {
-    assert(child != null);
     assert(child.parent == this);
     final ParentDataType childParentData = child.parentData! as ParentDataType;
     return childParentData.previousSibling;
@@ -3916,7 +3971,6 @@
 
   /// The next child after the given child in the child list.
   ChildType? childAfter(ChildType child) {
-    assert(child != null);
     assert(child.parent == this);
     final ParentDataType childParentData = child.parentData! as ParentDataType;
     return childParentData.nextSibling;
@@ -3945,13 +3999,18 @@
 /// Mixin for [RenderObject] that will call [systemFontsDidChange] whenever the
 /// system fonts change.
 ///
-/// System fonts can change when the OS install or remove a font. Use this mixin if
-/// the [RenderObject] uses [TextPainter] or [Paragraph] to correctly update the
-/// text when it happens.
+/// System fonts can change when the OS installs or removes a font. Use this
+/// mixin if the [RenderObject] uses [TextPainter] or [Paragraph] to correctly
+/// update the text when it happens.
 mixin RelayoutWhenSystemFontsChangeMixin on RenderObject {
 
   /// A callback that is called when system fonts have changed.
   ///
+  /// The framework defers the invocation of the callback to the
+  /// [SchedulerPhase.transientCallbacks] phase to ensure that the
+  /// [RenderObject]'s text layout is still valid when user interactions are in
+  /// progress (which usually take place during the [SchedulerPhase.idle] phase).
+  ///
   /// By default, [markNeedsLayout] is called on the [RenderObject]
   /// implementing this mixin.
   ///
@@ -3963,15 +4022,44 @@
     markNeedsLayout();
   }
 
+  bool _hasPendingSystemFontsDidChangeCallBack = false;
+  void _scheduleSystemFontsUpdate() {
+    assert(
+      SchedulerBinding.instance.schedulerPhase == SchedulerPhase.idle,
+      '${objectRuntimeType(this, "RelayoutWhenSystemFontsChangeMixin")}._scheduleSystemFontsUpdate() '
+      'called during ${SchedulerBinding.instance.schedulerPhase}.',
+    );
+    if (_hasPendingSystemFontsDidChangeCallBack) {
+      return;
+    }
+    _hasPendingSystemFontsDidChangeCallBack = true;
+    SchedulerBinding.instance.scheduleFrameCallback((Duration timeStamp) {
+      assert(_hasPendingSystemFontsDidChangeCallBack);
+      _hasPendingSystemFontsDidChangeCallBack = false;
+      assert(
+        attached || (debugDisposed ?? true),
+        '$this is detached during ${SchedulerBinding.instance.schedulerPhase} but is not disposed.',
+      );
+      if (attached) {
+        systemFontsDidChange();
+      }
+    });
+  }
+
   @override
-  void attach(covariant PipelineOwner owner) {
+  void attach(PipelineOwner owner) {
     super.attach(owner);
-    PaintingBinding.instance.systemFonts.addListener(systemFontsDidChange);
+    // If there's a pending callback that would imply this node was detached
+    // between the idle phase and the next transientCallbacks phase. The tree
+    // can not be mutated between those two phases so that should never happen.
+    assert(!_hasPendingSystemFontsDidChangeCallBack);
+    PaintingBinding.instance.systemFonts.addListener(_scheduleSystemFontsUpdate);
   }
 
   @override
   void detach() {
-    PaintingBinding.instance.systemFonts.removeListener(systemFontsDidChange);
+    assert(!_hasPendingSystemFontsDidChangeCallBack);
+    PaintingBinding.instance.systemFonts.removeListener(_scheduleSystemFontsUpdate);
     super.detach();
   }
 }
@@ -3985,8 +4073,9 @@
 ///  * [_ContainerSemanticsFragment]: a container class to transport the semantic
 ///    information of multiple [_InterestingSemanticsFragment] to a parent.
 abstract class _SemanticsFragment {
-  _SemanticsFragment({ required this.dropsSemanticsOfPreviousSiblings })
-    : assert (dropsSemanticsOfPreviousSiblings != null);
+  _SemanticsFragment({
+    required this.dropsSemanticsOfPreviousSiblings,
+  });
 
   /// Incorporate the fragments of children into this fragment.
   void addAll(Iterable<_InterestingSemanticsFragment> fragments);
@@ -4002,25 +4091,29 @@
 
   /// Returns [_InterestingSemanticsFragment] describing the actual semantic
   /// information that this fragment wants to add to the parent.
-  List<_InterestingSemanticsFragment> get interestingFragments;
+  List<_InterestingSemanticsFragment> get mergeUpFragments;
 }
 
 /// A container used when a [RenderObject] wants to add multiple independent
 /// [_InterestingSemanticsFragment] to its parent.
 ///
 /// The [_InterestingSemanticsFragment] to be added to the parent can be
-/// obtained via [interestingFragments].
+/// obtained via [mergeUpFragments].
 class _ContainerSemanticsFragment extends _SemanticsFragment {
+  _ContainerSemanticsFragment({
+    required super.dropsSemanticsOfPreviousSiblings,
+    required this.siblingMergeGroups,
+  });
 
-  _ContainerSemanticsFragment({ required super.dropsSemanticsOfPreviousSiblings });
+  final List<List<_InterestingSemanticsFragment>> siblingMergeGroups;
 
   @override
   void addAll(Iterable<_InterestingSemanticsFragment> fragments) {
-    interestingFragments.addAll(fragments);
+    mergeUpFragments.addAll(fragments);
   }
 
   @override
-  final List<_InterestingSemanticsFragment> interestingFragments = <_InterestingSemanticsFragment>[];
+  final List<_InterestingSemanticsFragment> mergeUpFragments = <_InterestingSemanticsFragment>[];
 }
 
 /// A [_SemanticsFragment] that describes which concrete semantic information
@@ -4033,8 +4126,7 @@
   _InterestingSemanticsFragment({
     required RenderObject owner,
     required super.dropsSemanticsOfPreviousSiblings,
-  }) : assert(owner != null),
-       _ancestorChain = <RenderObject>[owner];
+  }) : _ancestorChain = <RenderObject>[owner];
 
   /// The [RenderObject] that owns this fragment (and any new [SemanticsNode]
   /// introduced by it).
@@ -4057,6 +4149,7 @@
     required Rect? parentPaintClipRect,
     required double elevationAdjustment,
     required List<SemanticsNode> result,
+    required List<SemanticsNode> siblingNodes,
   });
 
   /// The [SemanticsConfiguration] the child wants to merge into the parent's
@@ -4086,7 +4179,7 @@
   bool get hasConfigForParent => config != null;
 
   @override
-  List<_InterestingSemanticsFragment> get interestingFragments => <_InterestingSemanticsFragment>[this];
+  List<_InterestingSemanticsFragment> get mergeUpFragments => <_InterestingSemanticsFragment>[this];
 
   Set<SemanticsTag>? _tagsForChildren;
 
@@ -4124,7 +4217,13 @@
   });
 
   @override
-  void compileChildren({ Rect? parentSemanticsClipRect, Rect? parentPaintClipRect, required double elevationAdjustment, required List<SemanticsNode> result }) {
+  void compileChildren({
+    Rect? parentSemanticsClipRect,
+    Rect? parentPaintClipRect,
+    required double elevationAdjustment,
+    required List<SemanticsNode> result,
+    required List<SemanticsNode> siblingNodes,
+  }) {
     assert(_tagsForChildren == null || _tagsForChildren!.isEmpty);
     assert(parentSemanticsClipRect == null);
     assert(parentPaintClipRect == null);
@@ -4150,8 +4249,11 @@
         parentPaintClipRect: parentPaintClipRect,
         elevationAdjustment: 0.0,
         result: children,
+        siblingNodes: siblingNodes,
       );
     }
+    // Root node does not have a parent and thus can't attach sibling nodes.
+    assert(siblingNodes.isEmpty);
     node.updateWith(config: null, childrenInInversePaintOrder: children);
 
     // The root node is the only semantics node allowed to be invisible. This
@@ -4201,24 +4303,136 @@
   _SwitchableSemanticsFragment({
     required bool mergeIntoParent,
     required SemanticsConfiguration config,
+    required List<List<_InterestingSemanticsFragment>> siblingMergeGroups,
     required super.owner,
     required super.dropsSemanticsOfPreviousSiblings,
-  }) : _mergeIntoParent = mergeIntoParent,
-       _config = config,
-       assert(mergeIntoParent != null),
-       assert(config != null);
+  }) : _siblingMergeGroups = siblingMergeGroups,
+       _mergeIntoParent = mergeIntoParent,
+       _config = config;
 
   final bool _mergeIntoParent;
   SemanticsConfiguration _config;
   bool _isConfigWritable = false;
+  bool _mergesToSibling = false;
+
+  final List<List<_InterestingSemanticsFragment>> _siblingMergeGroups;
+
+  void _mergeSiblingGroup(Rect? parentSemanticsClipRect, Rect? parentPaintClipRect, List<SemanticsNode> result, Set<int> usedSemanticsIds) {
+    for (final List<_InterestingSemanticsFragment> group in _siblingMergeGroups) {
+      Rect? rect;
+      Rect? semanticsClipRect;
+      Rect? paintClipRect;
+      SemanticsConfiguration? configuration;
+      // Use empty set because the _tagsForChildren may not contains all of the
+      // tags if this fragment is not explicit. The _tagsForChildren are added
+      // to sibling nodes at the end of compileChildren if this fragment is
+      // explicit.
+      final Set<SemanticsTag> tags = <SemanticsTag>{};
+      SemanticsNode? node;
+      for (final _InterestingSemanticsFragment fragment in group) {
+        if (fragment.config != null) {
+          final _SwitchableSemanticsFragment switchableFragment = fragment as _SwitchableSemanticsFragment;
+          switchableFragment._mergesToSibling = true;
+          node ??= fragment.owner._semantics;
+          if (configuration == null) {
+            switchableFragment._ensureConfigIsWritable();
+            configuration = switchableFragment.config;
+          } else {
+            configuration.absorb(switchableFragment.config!);
+          }
+          // It is a child fragment of a _SwitchableFragment, it must have a
+          // geometry.
+          final _SemanticsGeometry geometry = switchableFragment._computeSemanticsGeometry(
+            parentSemanticsClipRect: parentSemanticsClipRect,
+            parentPaintClipRect: parentPaintClipRect,
+          )!;
+          final Rect fragmentRect = MatrixUtils.transformRect(geometry.transform, geometry.rect);
+          if (rect == null) {
+            rect = fragmentRect;
+          } else {
+            rect = rect.expandToInclude(fragmentRect);
+          }
+          if (geometry.semanticsClipRect != null) {
+            final Rect rect = MatrixUtils.transformRect(geometry.transform, geometry.semanticsClipRect!);
+            if (semanticsClipRect == null) {
+              semanticsClipRect = rect;
+            } else {
+              semanticsClipRect = semanticsClipRect.intersect(rect);
+            }
+          }
+          if (geometry.paintClipRect != null) {
+            final Rect rect = MatrixUtils.transformRect(geometry.transform, geometry.paintClipRect!);
+            if (paintClipRect == null) {
+              paintClipRect = rect;
+            } else {
+              paintClipRect = paintClipRect.intersect(rect);
+            }
+          }
+          if (switchableFragment._tagsForChildren != null) {
+            tags.addAll(switchableFragment._tagsForChildren!);
+          }
+        }
+      }
+      // Can be null if all fragments in group are marked as explicit.
+      if (configuration != null && !rect!.isEmpty) {
+        if (node == null || usedSemanticsIds.contains(node.id)) {
+          node = SemanticsNode(showOnScreen: owner.showOnScreen);
+        }
+        usedSemanticsIds.add(node.id);
+        node
+          ..tags = tags
+          ..rect = rect
+          ..transform = null // Will be set when compiling immediate parent node.
+          ..parentSemanticsClipRect = semanticsClipRect
+          ..parentPaintClipRect = paintClipRect;
+        for (final _InterestingSemanticsFragment fragment in group) {
+          if (fragment.config != null) {
+            fragment.owner._semantics = node;
+          }
+        }
+        node.updateWith(config: configuration);
+        result.add(node);
+      }
+    }
+  }
+
   final List<_InterestingSemanticsFragment> _children = <_InterestingSemanticsFragment>[];
 
   @override
-  void compileChildren({ Rect? parentSemanticsClipRect, Rect? parentPaintClipRect, required double elevationAdjustment, required List<SemanticsNode> result }) {
+  void compileChildren({
+    Rect? parentSemanticsClipRect,
+    Rect? parentPaintClipRect,
+    required double elevationAdjustment,
+    required List<SemanticsNode> result,
+    required List<SemanticsNode> siblingNodes,
+  }) {
+    final Set<int> usedSemanticsIds = <int>{};
+    Iterable<_InterestingSemanticsFragment> compilingFragments = _children;
+    for (final List<_InterestingSemanticsFragment> siblingGroup in _siblingMergeGroups) {
+      compilingFragments = compilingFragments.followedBy(siblingGroup);
+    }
     if (!_isExplicit) {
-      owner._semantics = null;
-      for (final _InterestingSemanticsFragment fragment in _children) {
+      if (!_mergesToSibling) {
+        owner._semantics = null;
+      }
+      _mergeSiblingGroup(
+        parentSemanticsClipRect,
+        parentPaintClipRect,
+        siblingNodes,
+        usedSemanticsIds,
+      );
+      for (final _InterestingSemanticsFragment fragment in compilingFragments) {
         assert(_ancestorChain.first == fragment._ancestorChain.last);
+        if (fragment is _SwitchableSemanticsFragment) {
+          // Cached semantics node may be part of sibling merging group prior
+          // to this update. In this case, the semantics node may continue to
+          // be reused in that sibling merging group.
+          if (fragment._isExplicit &&
+              fragment.owner._semantics != null &&
+              usedSemanticsIds.contains(fragment.owner._semantics!.id)) {
+            fragment.owner._semantics = null;
+          }
+        }
         fragment._ancestorChain.addAll(_ancestorChain.skip(1));
         fragment.compileChildren(
           parentSemanticsClipRect: parentSemanticsClipRect,
@@ -4228,14 +4442,16 @@
           // its children are placed at the elevation dictated by this config.
           elevationAdjustment: elevationAdjustment + _config.elevation,
           result: result,
+          siblingNodes: siblingNodes,
         );
       }
       return;
     }
 
-    final _SemanticsGeometry? geometry = _needsGeometryUpdate
-        ? _SemanticsGeometry(parentSemanticsClipRect: parentSemanticsClipRect, parentPaintClipRect: parentPaintClipRect, ancestors: _ancestorChain)
-        : null;
+    final _SemanticsGeometry? geometry = _computeSemanticsGeometry(
+      parentSemanticsClipRect: parentSemanticsClipRect,
+      parentPaintClipRect: parentPaintClipRect,
+    );
 
     if (!_mergeIntoParent && (geometry?.dropFromTree ?? false)) {
       return; // Drop the node, it's not going to be visible.
@@ -4264,22 +4480,66 @@
         _config.isHidden = true;
       }
     }
-
     final List<SemanticsNode> children = <SemanticsNode>[];
-    for (final _InterestingSemanticsFragment fragment in _children) {
+    _mergeSiblingGroup(
+      node.parentSemanticsClipRect,
+      node.parentPaintClipRect,
+      siblingNodes,
+      usedSemanticsIds,
+    );
+    for (final _InterestingSemanticsFragment fragment in compilingFragments) {
+      if (fragment is _SwitchableSemanticsFragment) {
+        // Cached semantics node may be part of sibling merging group prior
+        // to this update. In this case, the semantics node may continue to
+        // be reused in that sibling merging group.
+        if (fragment._isExplicit &&
+            fragment.owner._semantics != null &&
+            usedSemanticsIds.contains(fragment.owner._semantics!.id)) {
+          fragment.owner._semantics = null;
+        }
+      }
+      final List<SemanticsNode> childSiblingNodes = <SemanticsNode>[];
       fragment.compileChildren(
         parentSemanticsClipRect: node.parentSemanticsClipRect,
         parentPaintClipRect: node.parentPaintClipRect,
         elevationAdjustment: 0.0,
         result: children,
+        siblingNodes: childSiblingNodes,
       );
+      siblingNodes.addAll(childSiblingNodes);
     }
+
     if (_config.isSemanticBoundary) {
       owner.assembleSemanticsNode(node, _config, children);
     } else {
       node.updateWith(config: _config, childrenInInversePaintOrder: children);
     }
     result.add(node);
+    // Sibling node needs to attach to the parent of an explicit node.
+    for (final SemanticsNode siblingNode in siblingNodes) {
+      // sibling nodes are in the same coordinate of the immediate explicit node.
+      // They need to share the same transform if they are going to attach to the
+      // parent of the immediate explicit node.
+      assert(siblingNode.transform == null);
+      siblingNode
+        ..transform = node.transform
+        ..isMergedIntoParent = node.isMergedIntoParent;
+      if (_tagsForChildren != null) {
+        siblingNode.tags ??= <SemanticsTag>{};
+        siblingNode.tags!.addAll(_tagsForChildren!);
+      }
+    }
+    result.addAll(siblingNodes);
+    siblingNodes.clear();
+  }
+
+  _SemanticsGeometry? _computeSemanticsGeometry({
+    required Rect? parentSemanticsClipRect,
+    required Rect? parentPaintClipRect,
+  }) {
+    return _needsGeometryUpdate
+      ? _SemanticsGeometry(parentSemanticsClipRect: parentSemanticsClipRect, parentPaintClipRect: parentPaintClipRect, ancestors: _ancestorChain)
+      : null;
   }
 
   @override
@@ -4403,7 +4663,6 @@
 
   /// From parent to child coordinate system.
   static Rect? _transformRect(Rect? rect, Matrix4 transform) {
-    assert(transform != null);
     if (rect == null) {
       return null;
     }
@@ -4424,18 +4683,12 @@
     Matrix4 transform,
     Matrix4 clipRectTransform,
   ) {
-    assert(ancestor != null);
-    assert(child != null);
-    assert(transform != null);
-    assert(clipRectTransform != null);
     assert(clipRectTransform.isIdentity());
     RenderObject intermediateParent = child.parent! as RenderObject;
-    assert(intermediateParent != null);
     while (intermediateParent != ancestor) {
       intermediateParent.applyPaintTransform(child, transform);
       intermediateParent = intermediateParent.parent! as RenderObject;
       child = child.parent! as RenderObject;
-      assert(intermediateParent != null);
     }
     ancestor.applyPaintTransform(child, transform);
     ancestor.applyPaintTransform(child, clipRectTransform);
@@ -4481,8 +4734,7 @@
   /// Create a [DiagnosticsProperty] with its [value] initialized to input
   /// [RenderObject.debugCreator].
   DiagnosticsDebugCreator(Object value)
-    : assert(value != null),
-      super(
+    : super(
         'debugCreator',
         value,
         level: DiagnosticLevel.hidden,
diff --git a/framework/lib/src/rendering/paragraph.dart b/framework/lib/src/rendering/paragraph.dart
index d58ea02..5b44dc1 100644
--- a/framework/lib/src/rendering/paragraph.dart
+++ b/framework/lib/src/rendering/paragraph.dart
@@ -8,7 +8,6 @@
 
 import 'package:flute/foundation.dart';
 import 'package:flute/gestures.dart';
-import 'package:flute/scheduler.dart';
 import 'package:flute/semantics.dart';
 import 'package:flute/services.dart';
 
@@ -89,15 +88,8 @@
     List<RenderBox>? children,
     Color? selectionColor,
     SelectionRegistrar? registrar,
-  }) : assert(text != null),
-       assert(text.debugAssertIsValid()),
-       assert(textAlign != null),
-       assert(textDirection != null),
-       assert(softWrap != null),
-       assert(overflow != null),
-       assert(textScaleFactor != null),
+  }) : assert(text.debugAssertIsValid()),
        assert(maxLines == null || maxLines > 0),
-       assert(textWidthBasis != null),
        _softWrap = softWrap,
        _overflow = overflow,
        _selectionColor = selectionColor,
@@ -133,11 +125,14 @@
   /// The text to display.
   InlineSpan get text => _textPainter.text!;
   set text(InlineSpan value) {
-    assert(value != null);
     switch (_textPainter.text!.compareTo(value)) {
       case RenderComparison.identical:
-      case RenderComparison.metadata:
         return;
+      case RenderComparison.metadata:
+        _textPainter.text = value;
+        _cachedCombinedSemanticsInfos = null;
+        markNeedsSemanticsUpdate();
+        break;
       case RenderComparison.paint:
         _textPainter.text = value;
         _cachedAttributedLabel = null;
@@ -276,7 +271,6 @@
   /// How the text should be aligned horizontally.
   TextAlign get textAlign => _textPainter.textAlign;
   set textAlign(TextAlign value) {
-    assert(value != null);
     if (_textPainter.textAlign == value) {
       return;
     }
@@ -299,7 +293,6 @@
   /// This must not be null.
   TextDirection get textDirection => _textPainter.textDirection!;
   set textDirection(TextDirection value) {
-    assert(value != null);
     if (_textPainter.textDirection == value) {
       return;
     }
@@ -317,7 +310,6 @@
   bool get softWrap => _softWrap;
   bool _softWrap;
   set softWrap(bool value) {
-    assert(value != null);
     if (_softWrap == value) {
       return;
     }
@@ -329,7 +321,6 @@
   TextOverflow get overflow => _overflow;
   TextOverflow _overflow;
   set overflow(TextOverflow value) {
-    assert(value != null);
     if (_overflow == value) {
       return;
     }
@@ -344,7 +335,6 @@
   /// the specified font size.
   double get textScaleFactor => _textPainter.textScaleFactor;
   set textScaleFactor(double value) {
-    assert(value != null);
     if (_textPainter.textScaleFactor == value) {
       return;
     }
@@ -402,7 +392,6 @@
   /// {@macro flutter.painting.textPainter.textWidthBasis}
   TextWidthBasis get textWidthBasis => _textPainter.textWidthBasis;
   set textWidthBasis(TextWidthBasis value) {
-    assert(value != null);
     if (_textPainter.textWidthBasis == value) {
       return;
     }
@@ -487,7 +476,6 @@
   @override
   double computeDistanceToActualBaseline(TextBaseline baseline) {
     assert(!debugNeedsLayout);
-    assert(constraints != null);
     assert(constraints.debugAssertIsValid());
     _layoutTextWithConstraints(constraints);
     // TODO(garyq): Since our metric for ideographic baseline is currently
@@ -650,37 +638,10 @@
     );
   }
 
-  bool _systemFontsChangeScheduled = false;
   @override
   void systemFontsDidChange() {
-    final SchedulerPhase phase = SchedulerBinding.instance.schedulerPhase;
-    switch (phase) {
-      case SchedulerPhase.idle:
-      case SchedulerPhase.postFrameCallbacks:
-        if (_systemFontsChangeScheduled) {
-          return;
-        }
-        _systemFontsChangeScheduled = true;
-        SchedulerBinding.instance.scheduleFrameCallback((Duration timeStamp) {
-          assert(_systemFontsChangeScheduled);
-          _systemFontsChangeScheduled = false;
-          assert(
-            attached || (debugDisposed ?? true),
-            '$this is detached during $phase but not disposed.',
-          );
-          if (attached) {
-            super.systemFontsDidChange();
-            _textPainter.markNeedsLayout();
-          }
-        });
-        break;
-      case SchedulerPhase.transientCallbacks:
-      case SchedulerPhase.midFrameMicrotasks:
-      case SchedulerPhase.persistentCallbacks:
-        super.systemFontsDidChange();
-        _textPainter.markNeedsLayout();
-        break;
-    }
+    super.systemFontsDidChange();
+    _textPainter.markNeedsLayout();
   }
 
   // Placeholder dimensions representing the sizes of child inline widgets.
@@ -837,7 +798,6 @@
           _overflowShader = null;
           break;
         case TextOverflow.fade:
-          assert(textDirection != null);
           _needsClipping = true;
           final TextPainter fadeSizePainter = TextPainter(
             text: TextSpan(style: _textPainter.text!.style, text: '\u2026'),
@@ -999,8 +959,6 @@
     ui.BoxWidthStyle boxWidthStyle = ui.BoxWidthStyle.tight,
   }) {
     assert(!debugNeedsLayout);
-    assert(boxHeightStyle != null);
-    assert(boxWidthStyle != null);
     _layoutTextWithConstraints(constraints);
     return _textPainter.getBoxesForSelection(
       selection,
@@ -1583,21 +1541,21 @@
     switch (granularity) {
       case TextGranularity.character:
         final String text = range.textInside(fullText);
-        newPosition = _getNextPosition(CharacterBoundary(text), targetedEdge, forward);
+        newPosition = _moveBeyondTextBoundaryAtDirection(targetedEdge, forward, CharacterBoundary(text));
         result = SelectionResult.end;
         break;
       case TextGranularity.word:
-        final String text = range.textInside(fullText);
-        newPosition = _getNextPosition(WhitespaceBoundary(text) + WordBoundary(this), targetedEdge, forward);
+        final TextBoundary textBoundary = paragraph._textPainter.wordBoundaries.moveByWordBoundary;
+        newPosition = _moveBeyondTextBoundaryAtDirection(targetedEdge, forward, textBoundary);
         result = SelectionResult.end;
         break;
       case TextGranularity.line:
-        newPosition = _getNextPosition(LineBreak(this), targetedEdge, forward);
+        newPosition = _moveToTextBoundaryAtDirection(targetedEdge, forward, LineBoundary(this));
         result = SelectionResult.end;
         break;
       case TextGranularity.document:
         final String text = range.textInside(fullText);
-        newPosition = _getNextPosition(DocumentBoundary(text), targetedEdge, forward);
+        newPosition = _moveBeyondTextBoundaryAtDirection(targetedEdge, forward, DocumentBoundary(text));
         if (forward && newPosition.offset == range.end) {
           result = SelectionResult.next;
         } else if (!forward && newPosition.offset == range.start) {
@@ -1616,15 +1574,43 @@
     return result;
   }
 
-  TextPosition _getNextPosition(TextBoundary boundary, TextPosition position, bool forward) {
-    if (forward) {
-      return _clampTextPosition(
-        (PushTextPosition.forward + boundary).getTrailingTextBoundaryAt(position)
-      );
+  // Move **beyond** the local boundary of the given type (unless range.start or
+  // range.end is reached). Used for most TextGranularity types except for
+  // TextGranularity.line, to ensure the selection movement doesn't get stuck at
+  // a local fixed point.
+  TextPosition _moveBeyondTextBoundaryAtDirection(TextPosition end, bool forward, TextBoundary textBoundary) {
+    final int newOffset = forward
+      ? textBoundary.getTrailingTextBoundaryAt(end.offset) ?? range.end
+      : textBoundary.getLeadingTextBoundaryAt(end.offset - 1) ?? range.start;
+    return TextPosition(offset: newOffset);
+  }
+
+  // Move **to** the local boundary of the given type. Typically used for line
+  // boundaries, such that performing "move to line start" more than once never
+  // moves the selection to the previous line.
+  TextPosition _moveToTextBoundaryAtDirection(TextPosition end, bool forward, TextBoundary textBoundary) {
+    assert(end.offset >= 0);
+    final int caretOffset;
+    switch (end.affinity) {
+      case TextAffinity.upstream:
+        if (end.offset < 1 && !forward) {
+          assert (end.offset == 0);
+          return const TextPosition(offset: 0);
+        }
+        final CharacterBoundary characterBoundary = CharacterBoundary(fullText);
+        caretOffset = math.max(
+          0,
+          characterBoundary.getLeadingTextBoundaryAt(range.start + end.offset) ?? range.start,
+        ) - 1;
+        break;
+      case TextAffinity.downstream:
+        caretOffset = end.offset;
+        break;
     }
-    return _clampTextPosition(
-      (PushTextPosition.backward + boundary).getLeadingTextBoundaryAt(position),
-    );
+    final int offset = forward
+      ? textBoundary.getTrailingTextBoundaryAt(caretOffset) ?? range.end
+      : textBoundary.getLeadingTextBoundaryAt(caretOffset) ?? range.start;
+    return TextPosition(offset: offset);
   }
 
   MapEntry<TextPosition, SelectionResult> _handleVerticalMovement(TextPosition position, {required double horizontalBaselineInParagraphCoordinates, required bool below}) {
diff --git a/framework/lib/src/rendering/performance_overlay.dart b/framework/lib/src/rendering/performance_overlay.dart
index 773f2eb..d48e213 100644
--- a/framework/lib/src/rendering/performance_overlay.dart
+++ b/framework/lib/src/rendering/performance_overlay.dart
@@ -67,11 +67,7 @@
     int rasterizerThreshold = 0,
     bool checkerboardRasterCacheImages = false,
     bool checkerboardOffscreenLayers = false,
-  }) : assert(optionsMask != null),
-       assert(rasterizerThreshold != null),
-       assert(checkerboardRasterCacheImages != null),
-       assert(checkerboardOffscreenLayers != null),
-       _optionsMask = optionsMask,
+  }) : _optionsMask = optionsMask,
        _rasterizerThreshold = rasterizerThreshold,
        _checkerboardRasterCacheImages = checkerboardRasterCacheImages,
        _checkerboardOffscreenLayers = checkerboardOffscreenLayers;
@@ -81,7 +77,6 @@
   int get optionsMask => _optionsMask;
   int _optionsMask;
   set optionsMask(int value) {
-    assert(value != null);
     if (value == _optionsMask) {
       return;
     }
@@ -95,7 +90,6 @@
   int get rasterizerThreshold => _rasterizerThreshold;
   int _rasterizerThreshold;
   set rasterizerThreshold(int value) {
-    assert(value != null);
     if (value == _rasterizerThreshold) {
       return;
     }
@@ -107,7 +101,6 @@
   bool get checkerboardRasterCacheImages => _checkerboardRasterCacheImages;
   bool _checkerboardRasterCacheImages;
   set checkerboardRasterCacheImages(bool value) {
-    assert(value != null);
     if (value == _checkerboardRasterCacheImages) {
       return;
     }
@@ -119,7 +112,6 @@
   bool get checkerboardOffscreenLayers => _checkerboardOffscreenLayers;
   bool _checkerboardOffscreenLayers;
   set checkerboardOffscreenLayers(bool value) {
-    assert(value != null);
     if (value == _checkerboardOffscreenLayers) {
       return;
     }
diff --git a/framework/lib/src/rendering/platform_view.dart b/framework/lib/src/rendering/platform_view.dart
index 0f4136e..2c3c90d 100644
--- a/framework/lib/src/rendering/platform_view.dart
+++ b/framework/lib/src/rendering/platform_view.dart
@@ -80,11 +80,7 @@
     required PlatformViewHitTestBehavior hitTestBehavior,
     required Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizers,
     Clip clipBehavior = Clip.hardEdge,
-  }) : assert(viewController != null),
-       assert(hitTestBehavior != null),
-       assert(gestureRecognizers != null),
-       assert(clipBehavior != null),
-       _viewController = viewController,
+  }) : _viewController = viewController,
        _clipBehavior = clipBehavior,
        super(controller: viewController, hitTestBehavior: hitTestBehavior, gestureRecognizers: gestureRecognizers) {
     _viewController.pointTransformer = (Offset offset) => globalToLocal(offset);
@@ -110,8 +106,6 @@
   @override
   set controller(AndroidViewController controller) {
     assert(!_isDisposed);
-    assert(_viewController != null);
-    assert(controller != null);
     if (_viewController == controller) {
       return;
     }
@@ -132,7 +126,6 @@
   Clip get clipBehavior => _clipBehavior;
   Clip _clipBehavior = Clip.hardEdge;
   set clipBehavior(Clip value) {
-    assert(value != null);
     if (value != _clipBehavior) {
       _clipBehavior = value;
       markNeedsPaint();
@@ -278,13 +271,6 @@
 
 /// A render object for an iOS UIKit UIView.
 ///
-/// {@template flutter.rendering.RenderUiKitView}
-/// Embedding UIViews is still preview-quality. To enable the preview for an iOS app add a boolean
-/// field with the key 'io.flutter.embedded_views_preview' and the value set to 'YES' to the
-/// application's Info.plist file. A list of open issued with embedding UIViews is available on
-/// [Github](https://github.com/flutter/flutter/issues?q=is%3Aopen+is%3Aissue+label%3A%22a%3A+platform-views%22+label%3Aplatform-ios+sort%3Acreated-asc)
-/// {@endtemplate}
-///
 /// [RenderUiKitView] is responsible for sizing and displaying an iOS
 /// [UIView](https://developer.apple.com/documentation/uikit/uiview).
 ///
@@ -306,10 +292,7 @@
     required UiKitViewController viewController,
     required this.hitTestBehavior,
     required Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizers,
-  }) : assert(viewController != null),
-       assert(hitTestBehavior != null),
-       assert(gestureRecognizers != null),
-       _viewController = viewController {
+  }) : _viewController = viewController {
     updateGestureRecognizers(gestureRecognizers);
   }
 
@@ -321,7 +304,6 @@
   UiKitViewController get viewController => _viewController;
   UiKitViewController _viewController;
   set viewController(UiKitViewController value) {
-    assert(value != null);
     if (_viewController == value) {
       return;
     }
@@ -340,7 +322,6 @@
 
   /// {@macro flutter.rendering.PlatformViewRenderBox.updateGestureRecognizers}
   void updateGestureRecognizers(Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizers) {
-    assert(gestureRecognizers != null);
     assert(
       _factoriesTypeSet(gestureRecognizers).length == gestureRecognizers.length,
       'There were multiple gesture recognizer factories for the same type, there must only be a single '
@@ -639,9 +620,7 @@
     required PlatformViewController controller,
     required PlatformViewHitTestBehavior hitTestBehavior,
     required Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizers,
-  }) :  assert(controller != null && controller.viewId != null && controller.viewId > -1),
-        assert(hitTestBehavior != null),
-        assert(gestureRecognizers != null),
+  }) :  assert(controller.viewId > -1),
         _controller = controller {
     this.hitTestBehavior = hitTestBehavior;
     updateGestureRecognizers(gestureRecognizers);
@@ -652,8 +631,7 @@
   PlatformViewController _controller;
   /// This value must not be null, and setting it to a new value will result in a repaint.
   set controller(covariant PlatformViewController controller) {
-    assert(controller != null);
-    assert(controller.viewId != null && controller.viewId > -1);
+    assert(controller.viewId > -1);
 
     if (_controller == controller) {
       return;
@@ -702,7 +680,6 @@
 
   @override
   void paint(PaintingContext context, Offset offset) {
-    assert(_controller.viewId != null);
     context.addLayer(PlatformViewLayer(
       rect: offset & size,
       viewId: _controller.viewId,
@@ -712,7 +689,6 @@
   @override
   void describeSemanticsConfiguration(SemanticsConfiguration config) {
     super.describeSemanticsConfiguration(config);
-    assert(_controller.viewId != null);
     config.isSemanticBoundary = true;
     config.platformViewId = _controller.viewId;
   }
@@ -740,7 +716,6 @@
   /// Any active gesture arena the `PlatformView` participates in is rejected when the
   /// set of gesture recognizers is changed.
   void _updateGestureRecognizersWithCallBack(Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizers, _HandlePointerEvent handlePointerEvent) {
-    assert(gestureRecognizers != null);
     assert(
       _factoriesTypeSet(gestureRecognizers).length == gestureRecognizers.length,
       'There were multiple gesture recognizer factories for the same type, there must only be a single '
diff --git a/framework/lib/src/rendering/proxy_box.dart b/framework/lib/src/rendering/proxy_box.dart
index af37616..8f54447 100644
--- a/framework/lib/src/rendering/proxy_box.dart
+++ b/framework/lib/src/rendering/proxy_box.dart
@@ -24,7 +24,7 @@
 
 /// A base class for render boxes that resemble their children.
 ///
-/// A proxy box has a single child and simply mimics all the properties of that
+/// A proxy box has a single child and mimics all the properties of that
 /// child by calling through to the child for each function in the render box
 /// protocol. For example, a proxy box determines its size by asking its child
 /// to layout with the same constraints and then matching the size.
@@ -41,7 +41,7 @@
 class RenderProxyBox extends RenderBox with RenderObjectWithChildMixin<RenderBox>, RenderProxyBoxMixin<RenderBox> {
   /// Creates a proxy render box.
   ///
-  /// Proxy render boxes are rarely created directly because they simply proxy
+  /// Proxy render boxes are rarely created directly because they proxy
   /// the render box protocol to [child]. Instead, consider using one of the
   /// subclasses.
   RenderProxyBox([RenderBox? child]) {
@@ -220,8 +220,7 @@
   RenderConstrainedBox({
     RenderBox? child,
     required BoxConstraints additionalConstraints,
-  }) : assert(additionalConstraints != null),
-       assert(additionalConstraints.debugAssertIsValid()),
+  }) : assert(additionalConstraints.debugAssertIsValid()),
        _additionalConstraints = additionalConstraints,
        super(child);
 
@@ -229,7 +228,6 @@
   BoxConstraints get additionalConstraints => _additionalConstraints;
   BoxConstraints _additionalConstraints;
   set additionalConstraints(BoxConstraints value) {
-    assert(value != null);
     assert(value.debugAssertIsValid());
     if (_additionalConstraints == value) {
       return;
@@ -353,8 +351,8 @@
     RenderBox? child,
     double maxWidth = double.infinity,
     double maxHeight = double.infinity,
-  }) : assert(maxWidth != null && maxWidth >= 0.0),
-       assert(maxHeight != null && maxHeight >= 0.0),
+  }) : assert(maxWidth >= 0.0),
+       assert(maxHeight >= 0.0),
        _maxWidth = maxWidth,
        _maxHeight = maxHeight,
        super(child);
@@ -363,7 +361,7 @@
   double get maxWidth => _maxWidth;
   double _maxWidth;
   set maxWidth(double value) {
-    assert(value != null && value >= 0.0);
+    assert(value >= 0.0);
     if (_maxWidth == value) {
       return;
     }
@@ -375,7 +373,7 @@
   double get maxHeight => _maxHeight;
   double _maxHeight;
   set maxHeight(double value) {
-    assert(value != null && value >= 0.0);
+    assert(value >= 0.0);
     if (_maxHeight == value) {
       return;
     }
@@ -457,8 +455,7 @@
   RenderAspectRatio({
     RenderBox? child,
     required double aspectRatio,
-  }) : assert(aspectRatio != null),
-       assert(aspectRatio > 0.0),
+  }) : assert(aspectRatio > 0.0),
        assert(aspectRatio.isFinite),
        _aspectRatio = aspectRatio,
        super(child);
@@ -470,7 +467,6 @@
   double get aspectRatio => _aspectRatio;
   double _aspectRatio;
   set aspectRatio(double value) {
-    assert(value != null);
     assert(value > 0.0);
     assert(value.isFinite);
     if (_aspectRatio == value) {
@@ -869,7 +865,7 @@
 ///
 /// For values of opacity other than 0.0 and 1.0, this class is relatively
 /// expensive because it requires painting the child into an intermediate
-/// buffer. For the value 0.0, the child is simply not painted at all. For the
+/// buffer. For the value 0.0, the child is not painted at all. For the
 /// value 1.0, the child is painted immediately without an intermediate buffer.
 class RenderOpacity extends RenderProxyBox {
   /// Creates a partially transparent render object.
@@ -879,9 +875,7 @@
     double opacity = 1.0,
     bool alwaysIncludeSemantics = false,
     RenderBox? child,
-  }) : assert(opacity != null),
-       assert(opacity >= 0.0 && opacity <= 1.0),
-       assert(alwaysIncludeSemantics != null),
+  }) : assert(opacity >= 0.0 && opacity <= 1.0),
        _opacity = opacity,
        _alwaysIncludeSemantics = alwaysIncludeSemantics,
        _alpha = ui.Color.getAlphaFromOpacity(opacity),
@@ -890,6 +884,9 @@
   @override
   bool get alwaysNeedsCompositing => child != null && _alpha > 0;
 
+  @override
+  bool get isRepaintBoundary => alwaysNeedsCompositing;
+
   int _alpha;
 
   /// The fraction to scale the child's alpha value.
@@ -905,7 +902,6 @@
   double get opacity => _opacity;
   double _opacity;
   set opacity(double value) {
-    assert(value != null);
     assert(value >= 0.0 && value <= 1.0);
     if (_opacity == value) {
       return;
@@ -917,7 +913,7 @@
     if (didNeedCompositing != alwaysNeedsCompositing) {
       markNeedsCompositingBitsUpdate();
     }
-    markNeedsPaint();
+    markNeedsCompositedLayerUpdate();
     if (wasVisible != (_alpha != 0) && !alwaysIncludeSemantics) {
       markNeedsSemanticsUpdate();
     }
@@ -945,22 +941,18 @@
   }
 
   @override
-  void paint(PaintingContext context, Offset offset) {
-    if (child == null) {
-      return;
-    }
-    if (_alpha == 0) {
-      // No need to keep the layer. We'll create a new one if necessary.
-      layer = null;
-      return;
-    }
+  OffsetLayer updateCompositedLayer({required covariant OpacityLayer? oldLayer}) {
+    final OpacityLayer layer = oldLayer ?? OpacityLayer();
+    layer.alpha = _alpha;
+    return layer;
+  }
 
-    assert(needsCompositing);
-    layer = context.pushOpacity(offset, _alpha, super.paint, oldLayer: layer as OpacityLayer?);
-    assert(() {
-      layer!.debugCreator = debugCreator;
-      return true;
-    }());
+  @override
+  void paint(PaintingContext context, Offset offset) {
+    if (child == null || _alpha == 0) {
+      return;
+    }
+    super.paint(context, offset);
   }
 
   @override
@@ -1010,7 +1002,6 @@
   Animation<double> get opacity => _opacity!;
   Animation<double>? _opacity;
   set opacity(Animation<double> value) {
-    assert(value != null);
     if (_opacity == value) {
       return;
     }
@@ -1112,9 +1103,7 @@
     required Animation<double> opacity,
     bool alwaysIncludeSemantics = false,
     RenderBox? child,
-  }) : assert(opacity != null),
-       assert(alwaysIncludeSemantics != null),
-       super(child) {
+  }) : super(child) {
     this.opacity = opacity;
     this.alwaysIncludeSemantics = alwaysIncludeSemantics;
   }
@@ -1137,9 +1126,7 @@
     RenderBox? child,
     required ShaderCallback shaderCallback,
     BlendMode blendMode = BlendMode.modulate,
-  }) : assert(shaderCallback != null),
-       assert(blendMode != null),
-       _shaderCallback = shaderCallback,
+  }) : _shaderCallback = shaderCallback,
        _blendMode = blendMode,
        super(child);
 
@@ -1158,7 +1145,6 @@
   ShaderCallback get shaderCallback => _shaderCallback;
   ShaderCallback _shaderCallback;
   set shaderCallback(ShaderCallback value) {
-    assert(value != null);
     if (_shaderCallback == value) {
       return;
     }
@@ -1173,7 +1159,6 @@
   BlendMode get blendMode => _blendMode;
   BlendMode _blendMode;
   set blendMode(BlendMode value) {
-    assert(value != null);
     if (_blendMode == value) {
       return;
     }
@@ -1215,9 +1200,7 @@
   /// The [blendMode] argument, if provided, must not be null
   /// and will default to [BlendMode.srcOver].
   RenderBackdropFilter({ RenderBox? child, required ui.ImageFilter filter, BlendMode blendMode = BlendMode.srcOver })
-    : assert(filter != null),
-      assert(blendMode != null),
-      _filter = filter,
+    : _filter = filter,
       _blendMode = blendMode,
       super(child);
 
@@ -1232,7 +1215,6 @@
   ui.ImageFilter get filter => _filter;
   ui.ImageFilter _filter;
   set filter(ui.ImageFilter value) {
-    assert(value != null);
     if (_filter == value) {
       return;
     }
@@ -1247,7 +1229,6 @@
   BlendMode get blendMode => _blendMode;
   BlendMode _blendMode;
   set blendMode(BlendMode value) {
-    assert(value != null);
     if (_blendMode == value) {
       return;
     }
@@ -1372,7 +1353,7 @@
   const ShapeBorderClipper({
     required this.shape,
     this.textDirection,
-  }) : assert(shape != null);
+  });
 
   /// The shape border whose outer path this clipper clips to.
   final ShapeBorder shape;
@@ -1405,8 +1386,7 @@
     RenderBox? child,
     CustomClipper<T>? clipper,
     Clip clipBehavior = Clip.antiAlias,
-  }) : assert(clipBehavior != null),
-       _clipper = clipper,
+  }) : _clipper = clipper,
        _clipBehavior = clipBehavior,
        super(child);
 
@@ -1541,7 +1521,7 @@
     super.child,
     super.clipper,
     super.clipBehavior,
-  }) : assert(clipBehavior != null);
+  });
 
   @override
   Rect get _defaultClip => Offset.zero & size;
@@ -1613,15 +1593,11 @@
   RenderClipRRect({
     super.child,
     BorderRadiusGeometry borderRadius = BorderRadius.zero,
-    CustomClipper<RRect>? clipper,
+    super.clipper,
     super.clipBehavior,
     TextDirection? textDirection,
-  }) : assert(clipBehavior != null),
-       _borderRadius = borderRadius,
-       _textDirection = textDirection,
-       super(clipper: clipper) {
-    assert(_borderRadius != null || clipper != null);
-  }
+  }) : _borderRadius = borderRadius,
+       _textDirection = textDirection;
 
   /// The border radius of the rounded corners.
   ///
@@ -1632,7 +1608,6 @@
   BorderRadiusGeometry get borderRadius => _borderRadius;
   BorderRadiusGeometry _borderRadius;
   set borderRadius(BorderRadiusGeometry value) {
-    assert(value != null);
     if (_borderRadius == value) {
       return;
     }
@@ -1721,7 +1696,7 @@
     super.child,
     super.clipper,
     super.clipBehavior,
-  }) : assert(clipBehavior != null);
+  });
 
   Rect? _cachedRect;
   late Path _cachedPath;
@@ -1817,7 +1792,7 @@
     super.child,
     super.clipper,
     super.clipBehavior,
-  }) : assert(clipBehavior != null);
+  });
 
   @override
   Path get _defaultClip => Path()..addRect(Offset.zero & size);
@@ -1886,10 +1861,7 @@
     required Color shadowColor,
     super.clipBehavior = Clip.none,
     super.clipper,
-  }) : assert(elevation != null && elevation >= 0.0),
-       assert(color != null),
-       assert(shadowColor != null),
-       assert(clipBehavior != null),
+  }) : assert(elevation >= 0.0),
        _elevation = elevation,
        _color = color,
        _shadowColor = shadowColor;
@@ -1903,7 +1875,7 @@
   double get elevation => _elevation;
   double _elevation;
   set elevation(double value) {
-    assert(value != null && value >= 0.0);
+    assert(value >= 0.0);
     if (elevation == value) {
       return;
     }
@@ -1919,7 +1891,6 @@
   Color get shadowColor => _shadowColor;
   Color _shadowColor;
   set shadowColor(Color value) {
-    assert(value != null);
     if (shadowColor == value) {
       return;
     }
@@ -1931,7 +1902,6 @@
   Color get color => _color;
   Color _color;
   set color(Color value) {
-    assert(value != null);
     if (color == value) {
       return;
     }
@@ -1976,11 +1946,7 @@
     super.elevation = 0.0,
     required super.color,
     super.shadowColor = const Color(0xFF000000),
-  }) : assert(shape != null),
-       assert(clipBehavior != null),
-       assert(elevation != null && elevation >= 0.0),
-       assert(color != null),
-       assert(shadowColor != null),
+  }) : assert(elevation >= 0.0),
        _shape = shape,
        _borderRadius = borderRadius;
 
@@ -1991,7 +1957,6 @@
   BoxShape get shape => _shape;
   BoxShape _shape;
   set shape(BoxShape value) {
-    assert(value != null);
     if (shape == value) {
       return;
     }
@@ -2020,7 +1985,6 @@
   @override
   RRect get _defaultClip {
     assert(hasSize);
-    assert(_shape != null);
     final Rect rect = Offset.zero & size;
     switch (_shape) {
       case BoxShape.rectangle:
@@ -2149,10 +2113,7 @@
     super.elevation = 0.0,
     required super.color,
     super.shadowColor = const Color(0xFF000000),
-  }) : assert(clipper != null),
-       assert(elevation != null && elevation >= 0.0),
-       assert(color != null),
-       assert(shadowColor != null);
+  }) : assert(elevation >= 0.0);
 
   @override
   Path get _defaultClip => Path()..addRect(Offset.zero & size);
@@ -2275,10 +2236,7 @@
     DecorationPosition position = DecorationPosition.background,
     ImageConfiguration configuration = ImageConfiguration.empty,
     RenderBox? child,
-  }) : assert(decoration != null),
-       assert(position != null),
-       assert(configuration != null),
-       _decoration = decoration,
+  }) : _decoration = decoration,
        _position = position,
        _configuration = configuration,
        super(child);
@@ -2291,7 +2249,6 @@
   Decoration get decoration => _decoration;
   Decoration _decoration;
   set decoration(Decoration value) {
-    assert(value != null);
     if (value == _decoration) {
       return;
     }
@@ -2305,7 +2262,6 @@
   DecorationPosition get position => _position;
   DecorationPosition _position;
   set position(DecorationPosition value) {
-    assert(value != null);
     if (value == _position) {
       return;
     }
@@ -2322,7 +2278,6 @@
   ImageConfiguration get configuration => _configuration;
   ImageConfiguration _configuration;
   set configuration(ImageConfiguration value) {
-    assert(value != null);
     if (value == _configuration) {
       return;
     }
@@ -2350,8 +2305,6 @@
 
   @override
   void paint(PaintingContext context, Offset offset) {
-    assert(size.width != null);
-    assert(size.height != null);
     _painter ??= _decoration.createBoxPainter(markNeedsPaint);
     final ImageConfiguration filledConfiguration = configuration.copyWith(size: size);
     if (position == DecorationPosition.background) {
@@ -2410,8 +2363,7 @@
     this.transformHitTests = true,
     FilterQuality? filterQuality,
     RenderBox? child,
-  }) : assert(transform != null),
-       super(child) {
+  }) : super(child) {
     this.transform = transform;
     this.alignment = alignment;
     this.textDirection = textDirection;
@@ -2491,7 +2443,6 @@
   /// mutations outside of the control of the render object could not reliably
   /// be reflected in the rendering.
   set transform(Matrix4 value) { // ignore: avoid_setters_without_getters
-    assert(value != null);
     if (_transform == value) {
       return;
     }
@@ -2678,10 +2629,7 @@
     TextDirection? textDirection,
     RenderBox? child,
     Clip clipBehavior = Clip.none,
-  }) : assert(fit != null),
-       assert(alignment != null),
-       assert(clipBehavior != null),
-       _fit = fit,
+  }) : _fit = fit,
        _alignment = alignment,
        _textDirection = textDirection,
        _clipBehavior = clipBehavior,
@@ -2719,7 +2667,6 @@
   BoxFit get fit => _fit;
   BoxFit _fit;
   set fit(BoxFit value) {
-    assert(value != null);
     if (_fit == value) {
       return;
     }
@@ -2744,7 +2691,6 @@
   AlignmentGeometry get alignment => _alignment;
   AlignmentGeometry _alignment;
   set alignment(AlignmentGeometry value) {
-    assert(value != null);
     if (_alignment == value) {
       return;
     }
@@ -2845,7 +2791,6 @@
   Clip get clipBehavior => _clipBehavior;
   Clip _clipBehavior = Clip.none;
   set clipBehavior(Clip value) {
-    assert(value != null);
     if (value != _clipBehavior) {
       _clipBehavior = value;
       markNeedsPaint();
@@ -2977,8 +2922,7 @@
     required Offset translation,
     this.transformHitTests = true,
     RenderBox? child,
-  }) : assert(translation != null),
-       _translation = translation,
+  }) : _translation = translation,
        super(child);
 
   /// The translation to apply to the child, scaled to the child's size.
@@ -2988,7 +2932,6 @@
   Offset get translation => _translation;
   Offset _translation;
   set translation(Offset value) {
-    assert(value != null);
     if (_translation == value) {
       return;
     }
@@ -3247,9 +3190,7 @@
     bool opaque = true,
     super.child,
     HitTestBehavior? hitTestBehavior = HitTestBehavior.opaque,
-  }) : assert(opaque != null),
-       assert(cursor != null),
-       _cursor = cursor,
+  }) : _cursor = cursor,
        _validForMouseTracker = validForMouseTracker,
        _opaque = opaque,
        super(behavior: hitTestBehavior ?? HitTestBehavior.opaque);
@@ -3650,9 +3591,7 @@
     bool? ignoringSemantics,
   }) : _ignoring = ignoring,
        _ignoringSemantics = ignoringSemantics,
-       super(child) {
-    assert(_ignoring != null);
-  }
+       super(child);
 
   /// Whether this render object is ignored during hit testing.
   ///
@@ -3661,7 +3600,6 @@
   bool get ignoring => _ignoring;
   bool _ignoring;
   set ignoring(bool value) {
-    assert(value != null);
     if (value == _ignoring) {
       return;
     }
@@ -3728,8 +3666,7 @@
   RenderOffstage({
     bool offstage = true,
     RenderBox? child,
-  }) : assert(offstage != null),
-       _offstage = offstage,
+  }) : _offstage = offstage,
        super(child);
 
   /// Whether the child is hidden from the rest of the tree.
@@ -3742,7 +3679,6 @@
   bool get offstage => _offstage;
   bool _offstage;
   set offstage(bool value) {
-    assert(value != null);
     if (value == _offstage) {
       return;
     }
@@ -3883,8 +3819,7 @@
     RenderBox? child,
     bool absorbing = true,
     bool? ignoringSemantics,
-  }) : assert(absorbing != null),
-       _absorbing = absorbing,
+  }) : _absorbing = absorbing,
        _ignoringSemantics = ignoringSemantics,
        super(child);
 
@@ -3993,8 +3928,7 @@
     GestureDragUpdateCallback? onVerticalDragUpdate,
     this.scrollFactor = 0.8,
     super.behavior,
-  }) : assert(scrollFactor != null),
-       _onTap = onTap,
+  }) : _onTap = onTap,
        _onLongPress = onLongPress,
        _onHorizontalDragUpdate = onHorizontalDragUpdate,
        _onVerticalDragUpdate = onVerticalDragUpdate;
@@ -4187,8 +4121,7 @@
     bool explicitChildNodes = false,
     bool excludeSemantics = false,
     TextDirection? textDirection,
-  })  : assert(container != null),
-        _container = container,
+  })  : _container = container,
         _explicitChildNodes = explicitChildNodes,
         _excludeSemantics = excludeSemantics,
         _textDirection = textDirection,
@@ -4201,7 +4134,6 @@
   SemanticsProperties get properties => _properties;
   SemanticsProperties _properties;
   set properties(SemanticsProperties value) {
-    assert(value != null);
     if (_properties == value) {
       return;
     }
@@ -4220,7 +4152,6 @@
   bool get container => _container;
   bool _container;
   set container(bool value) {
-    assert(value != null);
     if (container == value) {
       return;
     }
@@ -4244,7 +4175,6 @@
   bool get explicitChildNodes => _explicitChildNodes;
   bool _explicitChildNodes;
   set explicitChildNodes(bool value) {
-    assert(value != null);
     if (_explicitChildNodes == value) {
       return;
     }
@@ -4261,7 +4191,6 @@
   bool get excludeSemantics => _excludeSemantics;
   bool _excludeSemantics;
   set excludeSemantics(bool value) {
-    assert(value != null);
     if (_excludeSemantics == value) {
       return;
     }
@@ -4624,7 +4553,6 @@
   bool get blocking => _blocking;
   bool _blocking;
   set blocking(bool value) {
-    assert(value != null);
     if (value == _blocking) {
       return;
     }
@@ -4678,15 +4606,12 @@
     RenderBox? child,
     bool excluding = true,
   }) : _excluding = excluding,
-       super(child) {
-    assert(_excluding != null);
-  }
+       super(child);
 
   /// Whether this render object is excluded from the semantic tree.
   bool get excluding => _excluding;
   bool _excluding;
   set excluding(bool value) {
-    assert(value != null);
     if (value == _excluding) {
       return;
     }
@@ -4724,8 +4649,7 @@
   RenderIndexedSemantics({
     RenderBox? child,
     required int index,
-  }) : assert(index != null),
-       _index = index,
+  }) : _index = index,
        super(child);
 
   /// The index used to annotated child semantics.
@@ -4765,8 +4689,7 @@
   RenderLeaderLayer({
     required LayerLink link,
     RenderBox? child,
-  }) : assert(link != null),
-       _link = link,
+  }) : _link = link,
        super(child);
 
   /// The link object that connects this [RenderLeaderLayer] with one or more
@@ -4777,7 +4700,6 @@
   LayerLink get link => _link;
   LayerLink _link;
   set link(LayerLink value) {
-    assert(value != null);
     if (_link == value) {
       return;
     }
@@ -4852,10 +4774,7 @@
     Alignment leaderAnchor = Alignment.topLeft,
     Alignment followerAnchor = Alignment.topLeft,
     RenderBox? child,
-  }) : assert(link != null),
-       assert(showWhenUnlinked != null),
-       assert(offset != null),
-       _link = link,
+  }) : _link = link,
        _showWhenUnlinked = showWhenUnlinked,
        _offset = offset,
        _leaderAnchor = leaderAnchor,
@@ -4867,7 +4786,6 @@
   LayerLink get link => _link;
   LayerLink _link;
   set link(LayerLink value) {
-    assert(value != null);
     if (_link == value) {
       return;
     }
@@ -4887,7 +4805,6 @@
   bool get showWhenUnlinked => _showWhenUnlinked;
   bool _showWhenUnlinked;
   set showWhenUnlinked(bool value) {
-    assert(value != null);
     if (_showWhenUnlinked == value) {
       return;
     }
@@ -4900,7 +4817,6 @@
   Offset get offset => _offset;
   Offset _offset;
   set offset(Offset value) {
-    assert(value != null);
     if (_offset == value) {
       return;
     }
@@ -4925,7 +4841,6 @@
   Alignment get leaderAnchor => _leaderAnchor;
   Alignment _leaderAnchor;
   set leaderAnchor(Alignment value) {
-    assert(value != null);
     if (_leaderAnchor == value) {
       return;
     }
@@ -4942,7 +4857,6 @@
   Alignment get followerAnchor => _followerAnchor;
   Alignment _followerAnchor;
   set followerAnchor(Alignment value) {
-    assert(value != null);
     if (_followerAnchor == value) {
       return;
     }
@@ -5009,7 +4923,6 @@
     final Offset effectiveLinkedOffset = leaderSize == null
       ? this.offset
       : leaderAnchor.alongSize(leaderSize) - followerAnchor.alongSize(size) + this.offset;
-    assert(showWhenUnlinked != null);
     if (layer == null) {
       layer = FollowerLayer(
         link: link,
@@ -5076,9 +4989,7 @@
     required T value,
     required bool sized,
     RenderBox? child,
-  }) : assert(value != null),
-       assert(sized != null),
-       _value = value,
+  }) : _value = value,
        _sized = sized,
        super(child);
 
diff --git a/framework/lib/src/rendering/proxy_sliver.dart b/framework/lib/src/rendering/proxy_sliver.dart
index 2a1f151..0e23813 100644
--- a/framework/lib/src/rendering/proxy_sliver.dart
+++ b/framework/lib/src/rendering/proxy_sliver.dart
@@ -15,7 +15,7 @@
 
 /// A base class for sliver render objects that resemble their children.
 ///
-/// A proxy sliver has a single child and simply mimics all the properties of
+/// A proxy sliver has a single child and mimics all the properties of
 /// that child by calling through to the child for each function in the render
 /// sliver protocol. For example, a proxy sliver determines its geometry by
 /// asking its sliver child to layout with the same constraints and then
@@ -33,7 +33,7 @@
 abstract class RenderProxySliver extends RenderSliver with RenderObjectWithChildMixin<RenderSliver> {
   /// Creates a proxy render sliver.
   ///
-  /// Proxy render slivers aren't created directly because they simply proxy
+  /// Proxy render slivers aren't created directly because they proxy
   /// the render sliver protocol to their sliver [child]. Instead, use one of
   /// the subclasses.
   RenderProxySliver([RenderSliver? child]) {
@@ -74,14 +74,12 @@
 
   @override
   double childMainAxisPosition(RenderSliver child) {
-    assert(child != null);
     assert(child == this.child);
     return 0.0;
   }
 
   @override
   void applyPaintTransform(RenderObject child, Matrix4 transform) {
-    assert(child != null);
     final SliverPhysicalParentData childParentData = child.parentData! as SliverPhysicalParentData;
     childParentData.applyPaintTransform(transform);
   }
@@ -94,7 +92,7 @@
 ///
 /// For values of opacity other than 0.0 and 1.0, this class is relatively
 /// expensive, because it requires painting the sliver child into an intermediate
-/// buffer. For the value 0.0, the sliver child is simply not painted at all.
+/// buffer. For the value 0.0, the sliver child is not painted at all.
 /// For the value 1.0, the sliver child is painted immediately without an
 /// intermediate buffer.
 class RenderSliverOpacity extends RenderProxySliver {
@@ -105,8 +103,7 @@
     double opacity = 1.0,
     bool alwaysIncludeSemantics = false,
     RenderSliver? sliver,
-  }) : assert(opacity != null && opacity >= 0.0 && opacity <= 1.0),
-       assert(alwaysIncludeSemantics != null),
+  }) : assert(opacity >= 0.0 && opacity <= 1.0),
        _opacity = opacity,
        _alwaysIncludeSemantics = alwaysIncludeSemantics,
        _alpha = ui.Color.getAlphaFromOpacity(opacity) {
@@ -131,7 +128,6 @@
   double get opacity => _opacity;
   double _opacity;
   set opacity(double value) {
-    assert(value != null);
     assert(value >= 0.0 && value <= 1.0);
     if (_opacity == value) {
       return;
@@ -220,8 +216,7 @@
     RenderSliver? sliver,
     bool ignoring = true,
     bool? ignoringSemantics,
-  }) : assert(ignoring != null),
-       _ignoring = ignoring,
+  }) : _ignoring = ignoring,
        _ignoringSemantics = ignoringSemantics {
     child = sliver;
   }
@@ -233,7 +228,6 @@
   bool get ignoring => _ignoring;
   bool _ignoring;
   set ignoring(bool value) {
-    assert(value != null);
     if (value == _ignoring) {
       return;
     }
@@ -297,8 +291,7 @@
   RenderSliverOffstage({
     bool offstage = true,
     RenderSliver? sliver,
-  }) : assert(offstage != null),
-       _offstage = offstage {
+  }) : _offstage = offstage {
     child = sliver;
   }
 
@@ -313,7 +306,6 @@
   bool _offstage;
 
   set offstage(bool value) {
-    assert(value != null);
     if (value == _offstage) {
       return;
     }
@@ -401,8 +393,7 @@
     required Animation<double> opacity,
     bool alwaysIncludeSemantics = false,
     RenderSliver? sliver,
-  }) : assert(opacity != null),
-       assert(alwaysIncludeSemantics != null) {
+  }) {
     this.opacity = opacity;
     this.alwaysIncludeSemantics = alwaysIncludeSemantics;
     child = sliver;
diff --git a/framework/lib/src/rendering/rotated_box.dart b/framework/lib/src/rendering/rotated_box.dart
index 8d01084..03cb105 100644
--- a/framework/lib/src/rendering/rotated_box.dart
+++ b/framework/lib/src/rendering/rotated_box.dart
@@ -24,8 +24,7 @@
   RenderRotatedBox({
     required int quarterTurns,
     RenderBox? child,
-  }) : assert(quarterTurns != null),
-       _quarterTurns = quarterTurns {
+  }) : _quarterTurns = quarterTurns {
     this.child = child;
   }
 
@@ -33,7 +32,6 @@
   int get quarterTurns => _quarterTurns;
   int _quarterTurns;
   set quarterTurns(int value) {
-    assert(value != null);
     if (_quarterTurns == value) {
       return;
     }
diff --git a/framework/lib/src/rendering/selection.dart b/framework/lib/src/rendering/selection.dart
index c3b2c9d..d4bd7ed 100644
--- a/framework/lib/src/rendering/selection.dart
+++ b/framework/lib/src/rendering/selection.dart
@@ -687,9 +687,7 @@
     required this.localPosition,
     required this.lineHeight,
     required this.handleType,
-  }) : assert(localPosition != null),
-       assert(lineHeight != null),
-       assert(handleType != null);
+  });
 
   /// The position of the selection point in the local coordinates of the
   /// containing [Selectable].
diff --git a/framework/lib/src/rendering/shifted_box.dart b/framework/lib/src/rendering/shifted_box.dart
index 9c4d1c5..ca04288 100644
--- a/framework/lib/src/rendering/shifted_box.dart
+++ b/framework/lib/src/rendering/shifted_box.dart
@@ -107,8 +107,7 @@
     required EdgeInsetsGeometry padding,
     TextDirection? textDirection,
     RenderBox? child,
-  }) : assert(padding != null),
-       assert(padding.isNonNegative),
+  }) : assert(padding.isNonNegative),
        _textDirection = textDirection,
        _padding = padding,
        super(child);
@@ -135,7 +134,6 @@
   EdgeInsetsGeometry get padding => _padding;
   EdgeInsetsGeometry _padding;
   set padding(EdgeInsetsGeometry value) {
-    assert(value != null);
     assert(value.isNonNegative);
     if (_padding == value) {
       return;
@@ -277,8 +275,7 @@
     AlignmentGeometry alignment = Alignment.center,
     required TextDirection? textDirection,
     RenderBox? child,
-  }) : assert(alignment != null),
-       _alignment = alignment,
+  }) : _alignment = alignment,
        _textDirection = textDirection,
        super(child);
 
@@ -314,7 +311,6 @@
   ///
   /// The new alignment must not be null.
   set alignment(AlignmentGeometry value) {
-    assert(value != null);
     if (_alignment == value) {
       return;
     }
@@ -697,10 +693,7 @@
     required BoxConstraintsTransform constraintsTransform,
     super.child,
     Clip clipBehavior = Clip.none,
-  }) : assert(alignment != null),
-       assert(clipBehavior != null),
-       assert(constraintsTransform != null),
-       _constraintsTransform = constraintsTransform,
+  }) : _constraintsTransform = constraintsTransform,
        _clipBehavior = clipBehavior;
 
   /// {@macro flutter.widgets.constraintsTransform}
@@ -729,7 +722,6 @@
   Clip get clipBehavior => _clipBehavior;
   Clip _clipBehavior;
   set clipBehavior(Clip value) {
-    assert(value != null);
     if (value != _clipBehavior) {
       _clipBehavior = value;
       markNeedsPaint();
@@ -783,7 +775,6 @@
     final RenderBox? child = this.child;
     if (child != null) {
       final BoxConstraints childConstraints = constraintsTransform(constraints);
-      assert(childConstraints != null);
       assert(childConstraints.isNormalized, '$childConstraints is not normalized');
       _childConstraints = childConstraints;
       child.layout(childConstraints, parentUsesSize: true);
@@ -896,14 +887,12 @@
     required Size requestedSize,
     super.alignment,
     super.textDirection,
-  }) : assert(requestedSize != null),
-       _requestedSize = requestedSize;
+  }) : _requestedSize = requestedSize;
 
   /// The size this render box should attempt to be.
   Size get requestedSize => _requestedSize;
   Size _requestedSize;
   set requestedSize(Size value) {
-    assert(value != null);
     if (_requestedSize == value) {
       return;
     }
@@ -1212,15 +1201,13 @@
   RenderCustomSingleChildLayoutBox({
     RenderBox? child,
     required SingleChildLayoutDelegate delegate,
-  }) : assert(delegate != null),
-       _delegate = delegate,
+  }) : _delegate = delegate,
        super(child);
 
   /// A delegate that controls this object's layout.
   SingleChildLayoutDelegate get delegate => _delegate;
   SingleChildLayoutDelegate _delegate;
   set delegate(SingleChildLayoutDelegate newDelegate) {
-    assert(newDelegate != null);
     if (_delegate == newDelegate) {
       return;
     }
@@ -1333,9 +1320,7 @@
     RenderBox? child,
     required double baseline,
     required TextBaseline baselineType,
-  }) : assert(baseline != null),
-       assert(baselineType != null),
-       _baseline = baseline,
+  }) : _baseline = baseline,
        _baselineType = baselineType,
        super(child);
 
@@ -1344,7 +1329,6 @@
   double get baseline => _baseline;
   double _baseline;
   set baseline(double value) {
-    assert(value != null);
     if (_baseline == value) {
       return;
     }
@@ -1356,7 +1340,6 @@
   TextBaseline get baselineType => _baselineType;
   TextBaseline _baselineType;
   set baselineType(TextBaseline value) {
-    assert(value != null);
     if (_baselineType == value) {
       return;
     }
diff --git a/framework/lib/src/rendering/sliver.dart b/framework/lib/src/rendering/sliver.dart
index b58b019..1910cd0 100644
--- a/framework/lib/src/rendering/sliver.dart
+++ b/framework/lib/src/rendering/sliver.dart
@@ -49,8 +49,6 @@
 /// [AxisDirection] and a [GrowthDirection] and wish to compute the
 /// [AxisDirection] in which growth will occur.
 AxisDirection applyGrowthDirectionToAxisDirection(AxisDirection axisDirection, GrowthDirection growthDirection) {
-  assert(axisDirection != null);
-  assert(growthDirection != null);
   switch (growthDirection) {
     case GrowthDirection.forward:
       return axisDirection;
@@ -69,8 +67,6 @@
 /// [ScrollDirection] and a [GrowthDirection] and wish to compute the
 /// [ScrollDirection] in which growth will occur.
 ScrollDirection applyGrowthDirectionToScrollDirection(ScrollDirection scrollDirection, GrowthDirection growthDirection) {
-  assert(scrollDirection != null);
-  assert(growthDirection != null);
   switch (growthDirection) {
     case GrowthDirection.forward:
       return scrollDirection;
@@ -103,18 +99,7 @@
     required this.viewportMainAxisExtent,
     required this.remainingCacheExtent,
     required this.cacheOrigin,
-  }) : assert(axisDirection != null),
-       assert(growthDirection != null),
-       assert(userScrollDirection != null),
-       assert(scrollOffset != null),
-       assert(precedingScrollExtent != null),
-       assert(overlap != null),
-       assert(remainingPaintExtent != null),
-       assert(crossAxisExtent != null),
-       assert(crossAxisDirection != null),
-       assert(viewportMainAxisExtent != null),
-       assert(remainingCacheExtent != null),
-       assert(cacheOrigin != null);
+  });
 
   /// Creates a copy of this object but with the given fields replaced with the
   /// new values.
@@ -341,7 +326,6 @@
   /// This can be useful in combination with [axis] to view the [axisDirection]
   /// and [growthDirection] in different terms.
   GrowthDirection get normalizedGrowthDirection {
-    assert(axisDirection != null);
     switch (axisDirection) {
       case AxisDirection.down:
       case AxisDirection.right:
@@ -417,7 +401,6 @@
         errorMessage.writeln('  $message');
       }
       void verifyDouble(double property, String name, {bool mustBePositive = false, bool mustBeNegative = false}) {
-        verify(property != null, 'The "$name" is null.');
         if (property.isNaN) {
           String additional = '.';
           if (mustBePositive) {
@@ -432,13 +415,10 @@
           verify(property <= 0.0, 'The "$name" is positive.');
         }
       }
-      verify(axis != null, 'The "axis" is null.');
-      verify(growthDirection != null, 'The "growthDirection" is null.');
       verifyDouble(scrollOffset, 'scrollOffset');
       verifyDouble(overlap, 'overlap');
       verifyDouble(crossAxisExtent, 'crossAxisExtent');
       verifyDouble(scrollOffset, 'scrollOffset', mustBePositive: true);
-      verify(crossAxisDirection != null, 'The "crossAxisDirection" is null.');
       verify(axisDirectionToAxis(axisDirection) != axisDirectionToAxis(crossAxisDirection), 'The "axisDirection" and the "crossAxisDirection" are along the same axis.');
       verifyDouble(viewportMainAxisExtent, 'viewportMainAxisExtent', mustBePositive: true);
       verifyDouble(remainingPaintExtent, 'remainingPaintExtent', mustBePositive: true);
@@ -539,12 +519,7 @@
     this.hasVisualOverflow = false,
     this.scrollOffsetCorrection,
     double? cacheExtent,
-  }) : assert(scrollExtent != null),
-       assert(paintExtent != null),
-       assert(paintOrigin != null),
-       assert(maxPaintExtent != null),
-       assert(hasVisualOverflow != null),
-       assert(scrollOffsetCorrection != 0.0),
+  }) : assert(scrollOffsetCorrection != 0.0),
        layoutExtent = layoutExtent ?? paintExtent,
        hitTestExtent = hitTestExtent ?? paintExtent,
        cacheExtent = cacheExtent ?? layoutExtent ?? paintExtent,
@@ -717,12 +692,8 @@
         ]);
       }
 
-      verify(scrollExtent != null, 'The "scrollExtent" is null.');
       verify(scrollExtent >= 0.0, 'The "scrollExtent" is negative.');
-      verify(paintExtent != null, 'The "paintExtent" is null.');
       verify(paintExtent >= 0.0, 'The "paintExtent" is negative.');
-      verify(paintOrigin != null, 'The "paintOrigin" is null.');
-      verify(layoutExtent != null, 'The "layoutExtent" is null.');
       verify(layoutExtent >= 0.0, 'The "layoutExtent" is negative.');
       verify(cacheExtent >= 0.0, 'The "cacheExtent" is negative.');
       if (layoutExtent > paintExtent) {
@@ -731,7 +702,6 @@
           details: _debugCompareFloats('paintExtent', paintExtent, 'layoutExtent', layoutExtent),
         );
       }
-      verify(maxPaintExtent != null, 'The "maxPaintExtent" is null.');
       // If the paintExtent is slightly more than the maxPaintExtent, but the difference is still less
       // than precisionErrorTolerance, we will not throw the assert below.
       if (paintExtent - maxPaintExtent > precisionErrorTolerance) {
@@ -742,10 +712,7 @@
               ..add(ErrorDescription("By definition, a sliver can't paint more than the maximum that it can paint!")),
         );
       }
-      verify(hitTestExtent != null, 'The "hitTestExtent" is null.');
       verify(hitTestExtent >= 0.0, 'The "hitTestExtent" is negative.');
-      verify(visible != null, 'The "visible" property is null.');
-      verify(hasVisualOverflow != null, 'The "hasVisualOverflow" is null.');
       verify(scrollOffsetCorrection != 0.0, 'The "scrollOffsetCorrection" is zero.');
       return true;
     }());
@@ -847,11 +814,6 @@
     required double crossAxisPosition,
     required SliverHitTest hitTest,
   }) {
-    assert(mainAxisOffset != null);
-    assert(crossAxisOffset != null);
-    assert(mainAxisPosition != null);
-    assert(crossAxisPosition != null);
-    assert(hitTest != null);
     if (paintOffset != null) {
       pushOffset(-paintOffset);
     }
@@ -879,8 +841,7 @@
     super.target, {
     required this.mainAxisPosition,
     required this.crossAxisPosition,
-  }) : assert(mainAxisPosition != null),
-       assert(crossAxisPosition != null);
+  });
 
   /// The distance in the [AxisDirection] from the edge of the sliver's painted
   /// area (as given by the [SliverConstraints.scrollOffset]) to the hit point.
@@ -1180,7 +1141,6 @@
 
   @override
   Rect get paintBounds {
-    assert(constraints.axis != null);
     switch (constraints.axis) {
       case Axis.horizontal:
         return Rect.fromLTWH(
@@ -1599,8 +1559,6 @@
 /// Mixin for [RenderSliver] subclasses that provides some utility functions.
 mixin RenderSliverHelpers implements RenderSliver {
   bool _getRightWayUp(SliverConstraints constraints) {
-    assert(constraints != null);
-    assert(constraints.axisDirection != null);
     bool rightWayUp;
     switch (constraints.axisDirection) {
       case AxisDirection.up:
@@ -1612,7 +1570,6 @@
         rightWayUp = true;
         break;
     }
-    assert(constraints.growthDirection != null);
     switch (constraints.growthDirection) {
       case GrowthDirection.forward:
         break;
@@ -1620,7 +1577,6 @@
         rightWayUp = !rightWayUp;
         break;
     }
-    assert(rightWayUp != null);
     return rightWayUp;
   }
 
@@ -1642,7 +1598,6 @@
     double absolutePosition = mainAxisPosition - delta;
     final double absoluteCrossAxisPosition = crossAxisPosition - crossAxisDelta;
     Offset paintOffset, transformedPosition;
-    assert(constraints.axis != null);
     switch (constraints.axis) {
       case Axis.horizontal:
         if (!rightWayUp) {
@@ -1661,8 +1616,6 @@
         transformedPosition = Offset(absoluteCrossAxisPosition, absolutePosition);
         break;
     }
-    assert(paintOffset != null);
-    assert(transformedPosition != null);
     return result.addWithOutOfBandPosition(
       paintOffset: paintOffset,
       hitTest: (BoxHitTestResult result) {
@@ -1684,7 +1637,6 @@
     final bool rightWayUp = _getRightWayUp(constraints);
     double delta = childMainAxisPosition(child);
     final double crossAxisDelta = childCrossAxisPosition(child);
-    assert(constraints.axis != null);
     switch (constraints.axis) {
       case Axis.horizontal:
         if (!rightWayUp) {
@@ -1736,8 +1688,6 @@
   @protected
   void setChildParentData(RenderObject child, SliverConstraints constraints, SliverGeometry geometry) {
     final SliverPhysicalParentData childParentData = child.parentData! as SliverPhysicalParentData;
-    assert(constraints.axisDirection != null);
-    assert(constraints.growthDirection != null);
     switch (applyGrowthDirectionToAxisDirection(constraints.axisDirection, constraints.growthDirection)) {
       case AxisDirection.up:
         childParentData.paintOffset = Offset(0.0, -(geometry.scrollExtent - (geometry.paintExtent + constraints.scrollOffset)));
@@ -1752,7 +1702,6 @@
         childParentData.paintOffset = Offset(-(geometry.scrollExtent - (geometry.paintExtent + constraints.scrollOffset)), 0.0);
         break;
     }
-    assert(childParentData.paintOffset != null);
   }
 
   @override
@@ -1771,7 +1720,6 @@
 
   @override
   void applyPaintTransform(RenderObject child, Matrix4 transform) {
-    assert(child != null);
     assert(child == this.child);
     final SliverPhysicalParentData childParentData = child.parentData! as SliverPhysicalParentData;
     childParentData.applyPaintTransform(transform);
@@ -1821,7 +1769,6 @@
         childExtent = child!.size.height;
         break;
     }
-    assert(childExtent != null);
     final double paintedChildSize = calculatePaintOffset(constraints, from: 0.0, to: childExtent);
     final double cacheExtent = calculateCacheOffset(constraints, from: 0.0, to: childExtent);
 
diff --git a/framework/lib/src/rendering/sliver_fill.dart b/framework/lib/src/rendering/sliver_fill.dart
index 9608f70..75d63c3 100644
--- a/framework/lib/src/rendering/sliver_fill.dart
+++ b/framework/lib/src/rendering/sliver_fill.dart
@@ -32,8 +32,7 @@
   RenderSliverFillViewport({
     required super.childManager,
     double viewportFraction = 1.0,
-  }) : assert(viewportFraction != null),
-       assert(viewportFraction > 0.0),
+  }) : assert(viewportFraction > 0.0),
        _viewportFraction = viewportFraction;
 
   @override
@@ -47,7 +46,6 @@
   double get viewportFraction => _viewportFraction;
   double _viewportFraction;
   set viewportFraction(double value) {
-    assert(value != null);
     if (_viewportFraction == value) {
       return;
     }
diff --git a/framework/lib/src/rendering/sliver_fixed_extent_list.dart b/framework/lib/src/rendering/sliver_fixed_extent_list.dart
index 388c60d..efadf2c 100644
--- a/framework/lib/src/rendering/sliver_fixed_extent_list.dart
+++ b/framework/lib/src/rendering/sliver_fixed_extent_list.dart
@@ -257,7 +257,6 @@
         child.layout(childConstraints);
       }
       trailingChildWithLayout = child;
-      assert(child != null);
       final SliverMultiBoxAdaptorParentData childParentData = child.parentData! as SliverMultiBoxAdaptorParentData;
       assert(childParentData.index == index);
       childParentData.layoutOffset = indexToLayoutOffset(itemExtent, childParentData.index!);
@@ -351,7 +350,6 @@
   double get itemExtent => _itemExtent;
   double _itemExtent;
   set itemExtent(double value) {
-    assert(value != null);
     if (_itemExtent == value) {
       return;
     }
diff --git a/framework/lib/src/rendering/sliver_grid.dart b/framework/lib/src/rendering/sliver_grid.dart
index 956b050..5944019 100644
--- a/framework/lib/src/rendering/sliver_grid.dart
+++ b/framework/lib/src/rendering/sliver_grid.dart
@@ -155,12 +155,11 @@
     required this.childMainAxisExtent,
     required this.childCrossAxisExtent,
     required this.reverseCrossAxis,
-  }) : assert(crossAxisCount != null && crossAxisCount > 0),
-       assert(mainAxisStride != null && mainAxisStride >= 0),
-       assert(crossAxisStride != null && crossAxisStride >= 0),
-       assert(childMainAxisExtent != null && childMainAxisExtent >= 0),
-       assert(childCrossAxisExtent != null && childCrossAxisExtent >= 0),
-       assert(reverseCrossAxis != null);
+  }) : assert(crossAxisCount > 0),
+       assert(mainAxisStride >= 0),
+       assert(crossAxisStride >= 0),
+       assert(childMainAxisExtent >= 0),
+       assert(childCrossAxisExtent >= 0);
 
   /// The number of children in the cross axis.
   final int crossAxisCount;
@@ -226,7 +225,6 @@
 
   @override
   double computeMaxScrollOffset(int childCount) {
-    assert(childCount != null);
     final int mainAxisCount = ((childCount - 1) ~/ crossAxisCount) + 1;
     final double mainAxisSpacing = mainAxisStride - childMainAxisExtent;
     return mainAxisStride * mainAxisCount - mainAxisSpacing;
@@ -317,10 +315,10 @@
     this.crossAxisSpacing = 0.0,
     this.childAspectRatio = 1.0,
     this.mainAxisExtent,
-  }) : assert(crossAxisCount != null && crossAxisCount > 0),
-       assert(mainAxisSpacing != null && mainAxisSpacing >= 0),
-       assert(crossAxisSpacing != null && crossAxisSpacing >= 0),
-       assert(childAspectRatio != null && childAspectRatio > 0);
+  }) : assert(crossAxisCount > 0),
+       assert(mainAxisSpacing >= 0),
+       assert(crossAxisSpacing >= 0),
+       assert(childAspectRatio > 0);
 
   /// The number of children in the cross axis.
   final int crossAxisCount;
@@ -416,10 +414,10 @@
     this.crossAxisSpacing = 0.0,
     this.childAspectRatio = 1.0,
     this.mainAxisExtent,
-  }) : assert(maxCrossAxisExtent != null && maxCrossAxisExtent > 0),
-       assert(mainAxisSpacing != null && mainAxisSpacing >= 0),
-       assert(crossAxisSpacing != null && crossAxisSpacing >= 0),
-       assert(childAspectRatio != null && childAspectRatio > 0);
+  }) : assert(maxCrossAxisExtent > 0),
+       assert(mainAxisSpacing >= 0),
+       assert(crossAxisSpacing >= 0),
+       assert(childAspectRatio > 0);
 
   /// The maximum extent of tiles in the cross axis.
   ///
@@ -525,8 +523,7 @@
   RenderSliverGrid({
     required super.childManager,
     required SliverGridDelegate gridDelegate,
-  }) : assert(gridDelegate != null),
-       _gridDelegate = gridDelegate;
+  }) : _gridDelegate = gridDelegate;
 
   @override
   void setupParentData(RenderObject child) {
@@ -539,7 +536,6 @@
   SliverGridDelegate get gridDelegate => _gridDelegate;
   SliverGridDelegate _gridDelegate;
   set gridDelegate(SliverGridDelegate value) {
-    assert(value != null);
     if (_gridDelegate == value) {
       return;
     }
@@ -640,7 +636,6 @@
         child.layout(childConstraints);
       }
       trailingChildWithLayout = child;
-      assert(child != null);
       final SliverGridParentData childParentData = child.parentData! as SliverGridParentData;
       childParentData.layoutOffset = gridGeometry.scrollOffset;
       childParentData.crossAxisOffset = gridGeometry.crossAxisOffset;
diff --git a/framework/lib/src/rendering/sliver_multi_box_adaptor.dart b/framework/lib/src/rendering/sliver_multi_box_adaptor.dart
index a90af66..c14f5d5 100644
--- a/framework/lib/src/rendering/sliver_multi_box_adaptor.dart
+++ b/framework/lib/src/rendering/sliver_multi_box_adaptor.dart
@@ -113,7 +113,7 @@
   /// This function always returns true.
   ///
   /// The manager is not required to track whether it is expecting modifications
-  /// to the [RenderSliverMultiBoxAdaptor]'s child list and can simply return
+  /// to the [RenderSliverMultiBoxAdaptor]'s child list and can return
   /// true without making any assertions.
   bool debugAssertChildListLocked() => true;
 }
@@ -169,7 +169,7 @@
 ///   been laid out during that layout pass.
 /// * Children cannot be added except during a call to [childManager], and
 ///   then only if there is no child corresponding to that index (or the child
-///   child corresponding to that index was first removed).
+///   corresponding to that index was first removed).
 ///
 /// See also:
 ///
@@ -188,8 +188,7 @@
   /// The [childManager] argument must not be null.
   RenderSliverMultiBoxAdaptor({
     required RenderSliverBoxChildManager childManager,
-  }) : assert(childManager != null),
-       _childManager = childManager {
+  }) : _childManager = childManager {
     assert(() {
       _debugDanglingKeepAlives = <RenderBox>[];
       return true;
@@ -229,7 +228,6 @@
   bool get debugChildIntegrityEnabled => _debugChildIntegrityEnabled;
   bool _debugChildIntegrityEnabled = true;
   set debugChildIntegrityEnabled(bool enabled) {
-    assert(enabled != null);
     assert(() {
       _debugChildIntegrityEnabled = enabled;
       return _debugVerifyChildOrder() &&
@@ -536,7 +534,6 @@
   /// Returns the index of the given child, as given by the
   /// [SliverMultiBoxAdaptorParentData.index] field of the child's [parentData].
   int indexOf(RenderBox child) {
-    assert(child != null);
     final SliverMultiBoxAdaptorParentData childParentData = child.parentData! as SliverMultiBoxAdaptorParentData;
     assert(childParentData.index != null);
     return childParentData.index!;
@@ -546,7 +543,6 @@
   /// child's [RenderBox.size] property. This is only valid after layout.
   @protected
   double paintExtentOf(RenderBox child) {
-    assert(child != null);
     assert(child.hasSize);
     switch (constraints.axis) {
       case Axis.horizontal:
@@ -576,7 +572,6 @@
 
   @override
   double? childScrollOffset(RenderObject child) {
-    assert(child != null);
     assert(child.parent == this);
     final SliverMultiBoxAdaptorParentData childParentData = child.parentData! as SliverMultiBoxAdaptorParentData;
     return childParentData.layoutOffset;
@@ -640,8 +635,6 @@
         addExtent = true;
         break;
     }
-    assert(mainAxisUnit != null);
-    assert(addExtent != null);
     RenderBox? child = firstChild;
     while (child != null) {
       final double mainAxisDelta = childMainAxisPosition(child);
diff --git a/framework/lib/src/rendering/sliver_padding.dart b/framework/lib/src/rendering/sliver_padding.dart
index 34fadde..15f173c 100644
--- a/framework/lib/src/rendering/sliver_padding.dart
+++ b/framework/lib/src/rendering/sliver_padding.dart
@@ -35,9 +35,6 @@
   /// Only valid after layout has started, since before layout the render object
   /// doesn't know what direction it will be laid out in.
   double get beforePadding {
-    assert(constraints != null);
-    assert(constraints.axisDirection != null);
-    assert(constraints.growthDirection != null);
     assert(resolvedPadding != null);
     switch (applyGrowthDirectionToAxisDirection(constraints.axisDirection, constraints.growthDirection)) {
       case AxisDirection.up:
@@ -56,9 +53,6 @@
   /// Only valid after layout has started, since before layout the render object
   /// doesn't know what direction it will be laid out in.
   double get afterPadding {
-    assert(constraints != null);
-    assert(constraints.axisDirection != null);
-    assert(constraints.growthDirection != null);
     assert(resolvedPadding != null);
     switch (applyGrowthDirectionToAxisDirection(constraints.axisDirection, constraints.growthDirection)) {
       case AxisDirection.up:
@@ -79,8 +73,6 @@
   /// Only valid after layout has started, since before layout the render object
   /// doesn't know what direction it will be laid out in.
   double get mainAxisPadding {
-    assert(constraints != null);
-    assert(constraints.axis != null);
     assert(resolvedPadding != null);
     return resolvedPadding!.along(constraints.axis);
   }
@@ -92,8 +84,6 @@
   /// Only valid after layout has started, since before layout the render object
   /// doesn't know what direction it will be laid out in.
   double get crossAxisPadding {
-    assert(constraints != null);
-    assert(constraints.axis != null);
     assert(resolvedPadding != null);
     switch (constraints.axis) {
       case Axis.horizontal:
@@ -201,8 +191,6 @@
     );
 
     final SliverPhysicalParentData childParentData = child!.parentData! as SliverPhysicalParentData;
-    assert(constraints.axisDirection != null);
-    assert(constraints.growthDirection != null);
     switch (applyGrowthDirectionToAxisDirection(constraints.axisDirection, constraints.growthDirection)) {
       case AxisDirection.up:
         childParentData.paintOffset = Offset(resolvedPadding!.left, calculatePaintOffset(constraints, from: resolvedPadding!.bottom + childLayoutGeometry.scrollExtent, to: resolvedPadding!.bottom + childLayoutGeometry.scrollExtent + resolvedPadding!.top));
@@ -217,7 +205,6 @@
         childParentData.paintOffset = Offset(calculatePaintOffset(constraints, from: resolvedPadding!.right + childLayoutGeometry.scrollExtent, to: resolvedPadding!.right + childLayoutGeometry.scrollExtent + resolvedPadding!.left), resolvedPadding!.top);
         break;
     }
-    assert(childParentData.paintOffset != null);
     assert(beforePadding == this.beforePadding);
     assert(afterPadding == this.afterPadding);
     assert(mainAxisPadding == this.mainAxisPadding);
@@ -242,18 +229,13 @@
 
   @override
   double childMainAxisPosition(RenderSliver child) {
-    assert(child != null);
     assert(child == this.child);
     return calculatePaintOffset(constraints, from: 0.0, to: beforePadding);
   }
 
   @override
   double childCrossAxisPosition(RenderSliver child) {
-    assert(child != null);
     assert(child == this.child);
-    assert(constraints != null);
-    assert(constraints.axisDirection != null);
-    assert(constraints.growthDirection != null);
     assert(resolvedPadding != null);
     switch (applyGrowthDirectionToAxisDirection(constraints.axisDirection, constraints.growthDirection)) {
       case AxisDirection.up:
@@ -273,7 +255,6 @@
 
   @override
   void applyPaintTransform(RenderObject child, Matrix4 transform) {
-    assert(child != null);
     assert(child == this.child);
     final SliverPhysicalParentData childParentData = child.parentData! as SliverPhysicalParentData;
     childParentData.applyPaintTransform(transform);
@@ -326,8 +307,7 @@
     required EdgeInsetsGeometry padding,
     TextDirection? textDirection,
     RenderSliver? child,
-  }) : assert(padding != null),
-       assert(padding.isNonNegative),
+  }) : assert(padding.isNonNegative),
        _padding = padding,
        _textDirection = textDirection {
     this.child = child;
@@ -357,7 +337,6 @@
   EdgeInsetsGeometry get padding => _padding;
   EdgeInsetsGeometry _padding;
   set padding(EdgeInsetsGeometry value) {
-    assert(value != null);
     assert(padding.isNonNegative);
     if (_padding == value) {
       return;
diff --git a/framework/lib/src/rendering/sliver_persistent_header.dart b/framework/lib/src/rendering/sliver_persistent_header.dart
index 68257cf..904429f 100644
--- a/framework/lib/src/rendering/sliver_persistent_header.dart
+++ b/framework/lib/src/rendering/sliver_persistent_header.dart
@@ -37,7 +37,7 @@
   OverScrollHeaderStretchConfiguration({
     this.stretchTriggerOffset = 100.0,
     this.onStretchTrigger,
-  }) : assert(stretchTriggerOffset != null);
+  });
 
   /// The offset of overscroll required to trigger the [onStretchTrigger].
   final double stretchTriggerOffset;
@@ -152,7 +152,6 @@
       return 0.0;
     }
     assert(child!.hasSize);
-    assert(constraints.axis != null);
     switch (constraints.axis) {
       case Axis.vertical:
         return child!.size.height;
@@ -217,7 +216,6 @@
   /// The `overlapsContent` argument is passed to [updateChild].
   @protected
   void layoutChild(double scrollOffset, double maxExtent, { bool overlapsContent = false }) {
-    assert(maxExtent != null);
     final double shrinkOffset = math.min(scrollOffset, maxExtent);
     if (_needsUpdateChild || _lastShrinkOffset != shrinkOffset || _lastOverlapsContent != overlapsContent) {
       invokeLayoutCallback<SliverConstraints>((SliverConstraints constraints) {
@@ -228,7 +226,6 @@
       _lastOverlapsContent = overlapsContent;
       _needsUpdateChild = false;
     }
-    assert(minExtent != null);
     assert(() {
       if (minExtent <= maxExtent) {
         return true;
@@ -294,7 +291,6 @@
 
   @override
   void applyPaintTransform(RenderObject child, Matrix4 transform) {
-    assert(child != null);
     assert(child == this.child);
     applyPaintTransformForBoxChild(child as RenderBox, transform);
   }
@@ -302,7 +298,6 @@
   @override
   void paint(PaintingContext context, Offset offset) {
     if (child != null && geometry!.visible) {
-      assert(constraints.axisDirection != null);
       switch (applyGrowthDirectionToAxisDirection(constraints.axisDirection, constraints.growthDirection)) {
         case AxisDirection.up:
           offset += Offset(0.0, geometry!.paintExtent - childMainAxisPosition(child!) - childExtent);
@@ -496,8 +491,7 @@
   FloatingHeaderSnapConfiguration({
     this.curve = Curves.ease,
     this.duration = const Duration(milliseconds: 300),
-  }) : assert(curve != null),
-       assert(duration != null);
+  });
 
   /// The snap animation curve.
   final Curve curve;
@@ -607,9 +601,6 @@
   }
 
   void _updateAnimation(Duration duration, double endValue, Curve curve) {
-    assert(duration != null);
-    assert(endValue != null);
-    assert(curve != null);
     assert(
       vsync != null,
       'vsync must not be null if the floating header changes size animatedly.',
diff --git a/framework/lib/src/rendering/stack.dart b/framework/lib/src/rendering/stack.dart
index 6027b65..d490d6b 100644
--- a/framework/lib/src/rendering/stack.dart
+++ b/framework/lib/src/rendering/stack.dart
@@ -25,8 +25,7 @@
   /// Creates a RelativeRect with the given values.
   ///
   /// The arguments must not be null.
-  const RelativeRect.fromLTRB(this.left, this.top, this.right, this.bottom)
-    : assert(left != null && top != null && right != null && bottom != null);
+  const RelativeRect.fromLTRB(this.left, this.top, this.right, this.bottom);
 
   /// Creates a RelativeRect from a Rect and a Size. The Rect (first argument)
   /// and the RelativeRect (the output) are in the coordinate space of the
@@ -166,7 +165,6 @@
   ///
   /// {@macro dart.ui.shadow.lerp}
   static RelativeRect? lerp(RelativeRect? a, RelativeRect? b, double t) {
-    assert(t != null);
     if (a == null && b == null) {
       return null;
     }
@@ -354,10 +352,7 @@
     TextDirection? textDirection,
     StackFit fit = StackFit.loose,
     Clip clipBehavior = Clip.hardEdge,
-  }) : assert(alignment != null),
-       assert(fit != null),
-       assert(clipBehavior != null),
-       _alignment = alignment,
+  }) : _alignment = alignment,
        _textDirection = textDirection,
        _fit = fit,
        _clipBehavior = clipBehavior {
@@ -405,7 +400,6 @@
   AlignmentGeometry get alignment => _alignment;
   AlignmentGeometry _alignment;
   set alignment(AlignmentGeometry value) {
-    assert(value != null);
     if (_alignment == value) {
       return;
     }
@@ -435,7 +429,6 @@
   StackFit get fit => _fit;
   StackFit _fit;
   set fit(StackFit value) {
-    assert(value != null);
     if (_fit != value) {
       _fit = value;
       markNeedsLayout();
@@ -448,7 +441,6 @@
   Clip get clipBehavior => _clipBehavior;
   Clip _clipBehavior = Clip.hardEdge;
   set clipBehavior(Clip value) {
-    assert(value != null);
     if (value != _clipBehavior) {
       _clipBehavior = value;
       markNeedsPaint();
@@ -571,7 +563,6 @@
     double height = constraints.minHeight;
 
     final BoxConstraints nonPositionedConstraints;
-    assert(fit != null);
     switch (fit) {
       case StackFit.loose:
         nonPositionedConstraints = constraints.loosen();
@@ -583,7 +574,6 @@
         nonPositionedConstraints = constraints;
         break;
     }
-    assert(nonPositionedConstraints != null);
 
     RenderBox? child = firstChild;
     while (child != null) {
@@ -755,7 +745,6 @@
     if (firstChild == null || index == null) {
       return false;
     }
-    assert(position != null);
     final RenderBox child = _childAtIndex();
     final StackParentData childParentData = child.parentData! as StackParentData;
     return result.addWithPaintOffset(
diff --git a/framework/lib/src/rendering/table.dart b/framework/lib/src/rendering/table.dart
index 311477c..21e6bd7 100644
--- a/framework/lib/src/rendering/table.dart
+++ b/framework/lib/src/rendering/table.dart
@@ -134,7 +134,7 @@
   /// Creates a column width based on a fixed number of logical pixels.
   ///
   /// The [value] argument must not be null.
-  const FixedColumnWidth(this.value) : assert(value != null);
+  const FixedColumnWidth(this.value);
 
   /// The width the column should occupy in logical pixels.
   final double value;
@@ -161,7 +161,7 @@
   /// maxWidth.
   ///
   /// The [value] argument must not be null.
-  const FractionColumnWidth(this.value) : assert(value != null);
+  const FractionColumnWidth(this.value);
 
   /// The fraction of the table's constraints' maxWidth that this column should
   /// occupy.
@@ -199,7 +199,7 @@
   /// the other columns have been laid out.
   ///
   /// The [value] argument must not be null.
-  const FlexColumnWidth([this.value = 1.0]) : assert(value != null);
+  const FlexColumnWidth([this.value = 1.0]);
 
   /// The fraction of the remaining space once all the other columns have
   /// been laid out that this column should occupy.
@@ -388,9 +388,6 @@
   }) : assert(columns == null || columns >= 0),
        assert(rows == null || rows >= 0),
        assert(rows == null || children == null),
-       assert(defaultColumnWidth != null),
-       assert(textDirection != null),
-       assert(configuration != null),
        _textDirection = textDirection,
        _columns = columns ?? (children != null && children.isNotEmpty ? children.first.length : 0),
        _rows = rows ?? 0,
@@ -419,7 +416,6 @@
   int get columns => _columns;
   int _columns;
   set columns(int value) {
-    assert(value != null);
     assert(value >= 0);
     if (value == columns) {
       return;
@@ -454,7 +450,6 @@
   int get rows => _rows;
   int _rows;
   set rows(int value) {
-    assert(value != null);
     assert(value >= 0);
     if (value == rows) {
       return;
@@ -513,7 +508,6 @@
   TableColumnWidth get defaultColumnWidth => _defaultColumnWidth;
   TableColumnWidth _defaultColumnWidth;
   set defaultColumnWidth(TableColumnWidth value) {
-    assert(value != null);
     if (defaultColumnWidth == value) {
       return;
     }
@@ -525,7 +519,6 @@
   TextDirection get textDirection => _textDirection;
   TextDirection _textDirection;
   set textDirection(TextDirection value) {
-    assert(value != null);
     if (_textDirection == value) {
       return;
     }
@@ -573,7 +566,6 @@
   ImageConfiguration get configuration => _configuration;
   ImageConfiguration _configuration;
   set configuration(ImageConfiguration value) {
-    assert(value != null);
     if (value == _configuration) {
       return;
     }
@@ -585,7 +577,6 @@
   TableCellVerticalAlignment get defaultVerticalAlignment => _defaultVerticalAlignment;
   TableCellVerticalAlignment _defaultVerticalAlignment;
   set defaultVerticalAlignment(TableCellVerticalAlignment value) {
-    assert(value != null);
     if (_defaultVerticalAlignment == value) {
       return;
     }
@@ -617,7 +608,7 @@
   /// replacing the existing children.
   ///
   /// If the new cells contain any existing children of the table, those
-  /// children are simply moved to their new location in the table rather than
+  /// children are moved to their new location in the table rather than
   /// removed from the table and re-added.
   void setFlatChildren(int columns, List<RenderBox?> cells) {
     if (cells == _children && columns == _columns) {
@@ -626,7 +617,7 @@
     assert(columns >= 0);
     // consider the case of a newly empty table
     if (columns == 0 || cells.isEmpty) {
-      assert(cells == null || cells.isEmpty);
+      assert(cells.isEmpty);
       _columns = columns;
       if (_children.isEmpty) {
         assert(_rows == 0);
@@ -642,7 +633,6 @@
       markNeedsLayout();
       return;
     }
-    assert(cells != null);
     assert(cells.length % columns == 0);
     // fill a set with the cells that are moving (it's important not
     // to dropChild a child that's remaining with us, because that
@@ -722,8 +712,6 @@
   /// does not modify the table. Otherwise, the given child must not already
   /// have a parent.
   void setChild(int x, int y, RenderBox? value) {
-    assert(x != null);
-    assert(y != null);
     assert(x >= 0 && x < columns && y >= 0 && y < rows);
     assert(_children.length == rows * columns);
     final int xy = x + y * columns;
@@ -864,7 +852,6 @@
   }
 
   List<double> _computeColumnWidths(BoxConstraints constraints) {
-    assert(constraints != null);
     assert(_children.length == rows * columns);
     // We apply the constraints to the column widths in the order of
     // least important to most important:
@@ -1061,7 +1048,6 @@
         final RenderBox? child = _children[xy];
         if (child != null) {
           final TableCellParentData childParentData = child.parentData! as TableCellParentData;
-          assert(childParentData != null);
           switch (childParentData.verticalAlignment ?? defaultVerticalAlignment) {
             case TableCellVerticalAlignment.baseline:
               assert(debugCannotComputeDryLayout(
@@ -1133,7 +1119,6 @@
         final RenderBox? child = _children[xy];
         if (child != null) {
           final TableCellParentData childParentData = child.parentData! as TableCellParentData;
-          assert(childParentData != null);
           childParentData.x = x;
           childParentData.y = y;
           switch (childParentData.verticalAlignment ?? defaultVerticalAlignment) {
diff --git a/framework/lib/src/rendering/table_border.dart b/framework/lib/src/rendering/table_border.dart
index b4bcfb8..c4fecb7 100644
--- a/framework/lib/src/rendering/table_border.dart
+++ b/framework/lib/src/rendering/table_border.dart
@@ -89,12 +89,6 @@
   /// Whether all the sides of the border (outside and inside) are identical.
   /// Uniform borders are typically more efficient to paint.
   bool get isUniform {
-    assert(top != null);
-    assert(right != null);
-    assert(bottom != null);
-    assert(left != null);
-    assert(horizontalInside != null);
-    assert(verticalInside != null);
 
     final Color topColor = top.color;
     if (right.color != topColor ||
@@ -159,7 +153,6 @@
   ///
   /// {@macro dart.ui.shadow.lerp}
   static TableBorder? lerp(TableBorder? a, TableBorder? b, double t) {
-    assert(t != null);
     if (a == null && b == null) {
       return null;
     }
@@ -212,19 +205,9 @@
     required Iterable<double> columns,
   }) {
     // properties can't be null
-    assert(top != null);
-    assert(right != null);
-    assert(bottom != null);
-    assert(left != null);
-    assert(horizontalInside != null);
-    assert(verticalInside != null);
 
     // arguments can't be null
-    assert(canvas != null);
-    assert(rect != null);
-    assert(rows != null);
     assert(rows.isEmpty || (rows.first >= 0.0 && rows.last <= rect.height));
-    assert(columns != null);
     assert(columns.isEmpty || (columns.first >= 0.0 && columns.last <= rect.width));
 
     if (columns.isNotEmpty || rows.isNotEmpty) {
diff --git a/framework/lib/src/rendering/texture.dart b/framework/lib/src/rendering/texture.dart
index c28a8b2..0485ab1 100644
--- a/framework/lib/src/rendering/texture.dart
+++ b/framework/lib/src/rendering/texture.dart
@@ -40,8 +40,7 @@
     required int textureId,
     bool freeze = false,
     FilterQuality filterQuality = FilterQuality.low,
-  }) : assert(textureId != null),
-      _textureId = textureId,
+  }) : _textureId = textureId,
       _freeze = freeze,
       _filterQuality = filterQuality;
 
@@ -49,7 +48,6 @@
   int get textureId => _textureId;
   int _textureId;
   set textureId(int value) {
-    assert(value != null);
     if (value != _textureId) {
       _textureId = value;
       markNeedsPaint();
@@ -60,7 +58,6 @@
   bool get freeze => _freeze;
   bool _freeze;
   set freeze(bool value) {
-    assert(value != null);
     if (value != _freeze) {
       _freeze = value;
       markNeedsPaint();
@@ -71,7 +68,6 @@
   FilterQuality get filterQuality => _filterQuality;
   FilterQuality _filterQuality;
   set filterQuality(FilterQuality value) {
-    assert(value != null);
     if (value != _filterQuality) {
       _filterQuality = value;
       markNeedsPaint();
diff --git a/framework/lib/src/rendering/view.dart b/framework/lib/src/rendering/view.dart
index fbe3097..31dcf88 100644
--- a/framework/lib/src/rendering/view.dart
+++ b/framework/lib/src/rendering/view.dart
@@ -69,8 +69,7 @@
     RenderBox? child,
     required ViewConfiguration configuration,
     required ui.FlutterView window,
-  }) : assert(configuration != null),
-       _configuration = configuration,
+  }) : _configuration = configuration,
        _window = window {
     this.child = child;
   }
@@ -88,7 +87,6 @@
   ///
   /// Always call [prepareInitialFrame] before changing the configuration.
   set configuration(ViewConfiguration value) {
-    assert(value != null);
     if (configuration == value) {
       return;
     }
@@ -201,7 +199,6 @@
   ///  * [Layer.findAllAnnotations], which is used by this method to find all
   ///    [AnnotatedRegionLayer]s annotated for mouse tracking.
   HitTestResult hitTestMouseTrackers(Offset position) {
-    assert(position != null);
     final BoxHitTestResult result = BoxHitTestResult();
     hitTest(result, position: position);
     return result;
diff --git a/framework/lib/src/rendering/viewport.dart b/framework/lib/src/rendering/viewport.dart
index 042ffdc..f714a8a 100644
--- a/framework/lib/src/rendering/viewport.dart
+++ b/framework/lib/src/rendering/viewport.dart
@@ -136,8 +136,7 @@
   const RevealedOffset({
     required this.offset,
     required this.rect,
-  }) : assert(offset != null),
-       assert(rect != null);
+  });
 
   /// Offset for the viewport to reveal a specific element in the viewport.
   ///
@@ -213,13 +212,8 @@
     double? cacheExtent,
     CacheExtentStyle cacheExtentStyle = CacheExtentStyle.pixel,
     Clip clipBehavior = Clip.hardEdge,
-  }) : assert(axisDirection != null),
-       assert(crossAxisDirection != null),
-       assert(offset != null),
-       assert(axisDirectionToAxis(axisDirection) != axisDirectionToAxis(crossAxisDirection)),
-       assert(cacheExtentStyle != null),
+  }) : assert(axisDirectionToAxis(axisDirection) != axisDirectionToAxis(crossAxisDirection)),
        assert(cacheExtent != null || cacheExtentStyle == CacheExtentStyle.pixel),
-       assert(clipBehavior != null),
        _axisDirection = axisDirection,
        _crossAxisDirection = crossAxisDirection,
        _offset = offset,
@@ -264,7 +258,6 @@
   AxisDirection get axisDirection => _axisDirection;
   AxisDirection _axisDirection;
   set axisDirection(AxisDirection value) {
-    assert(value != null);
     if (value == _axisDirection) {
       return;
     }
@@ -281,7 +274,6 @@
   AxisDirection get crossAxisDirection => _crossAxisDirection;
   AxisDirection _crossAxisDirection;
   set crossAxisDirection(AxisDirection value) {
-    assert(value != null);
     if (value == _crossAxisDirection) {
       return;
     }
@@ -304,7 +296,6 @@
   ViewportOffset get offset => _offset;
   ViewportOffset _offset;
   set offset(ViewportOffset value) {
-    assert(value != null);
     if (value == _offset) {
       return;
     }
@@ -357,7 +348,6 @@
   double _cacheExtent;
   set cacheExtent(double? value) {
     value ??= RenderAbstractViewport.defaultCacheExtent;
-    assert(value != null);
     if (value == _cacheExtent) {
       return;
     }
@@ -390,7 +380,6 @@
   CacheExtentStyle get cacheExtentStyle => _cacheExtentStyle;
   CacheExtentStyle _cacheExtentStyle;
   set cacheExtentStyle(CacheExtentStyle value) {
-    assert(value != null);
     if (value == _cacheExtentStyle) {
       return;
     }
@@ -404,7 +393,6 @@
   Clip get clipBehavior => _clipBehavior;
   Clip _clipBehavior = Clip.hardEdge;
   set clipBehavior(Clip value) {
-    assert(value != null);
     if (value != _clipBehavior) {
       _clipBehavior = value;
       markNeedsPaint();
@@ -530,7 +518,6 @@
     final double initialLayoutOffset = layoutOffset;
     final ScrollDirection adjustedUserScrollDirection =
         applyGrowthDirectionToScrollDirection(offset.userScrollDirection, growthDirection);
-    assert(adjustedUserScrollDirection != null);
     double maxPaintOffset = layoutOffset + overlap;
     double precedingScrollExtent = 0.0;
 
@@ -653,7 +640,6 @@
 
   @override
   Rect describeSemanticsClip(RenderSliver? child) {
-    assert(axis != null);
 
     if (_calculatedCacheExtent == null) {
       return semanticBounds;
@@ -733,7 +719,6 @@
             size = Size(child.geometry!.layoutExtent, child.constraints.crossAxisExtent);
             break;
         }
-        assert(size != null);
         canvas.drawRect(((offset + paintOffsetOf(child)) & size).deflate(0.5), paint);
         child = childAfter(child);
       }
@@ -754,8 +739,6 @@
         crossAxisPosition = position.dy;
         break;
     }
-    assert(mainAxisPosition != null);
-    assert(crossAxisPosition != null);
     final SliverHitTestResult sliverResult = SliverHitTestResult.wrap(result);
     for (final RenderSliver child in childrenInHitTestOrder) {
       if (!child.geometry!.visible) {
@@ -808,7 +791,6 @@
     bool onlySlivers = target is RenderSliver; // ... between viewport and `target` (`target` included).
     while (child.parent != this) {
       final RenderObject parent = child.parent! as RenderObject;
-      assert(parent != null, '$target must be a descendant of $this');
       if (child is RenderBox) {
         pivot = child;
       }
@@ -878,10 +860,6 @@
       return RevealedOffset(offset: offset.pixels, rect: rect!);
     }
 
-    assert(pivotExtent != null);
-    assert(rect != null);
-    assert(rectLocal != null);
-    assert(growthDirection != null);
     assert(child.parent == this);
     assert(child is RenderSliver);
     final RenderSliver sliver = child as RenderSliver;
@@ -994,9 +972,6 @@
   @protected
   Offset computeAbsolutePaintOffset(RenderSliver child, double layoutOffset, GrowthDirection growthDirection) {
     assert(hasSize); // this is only usable once we have a size
-    assert(axisDirection != null);
-    assert(growthDirection != null);
-    assert(child != null);
     assert(child.geometry != null);
     switch (applyGrowthDirectionToAxisDirection(axisDirection, growthDirection)) {
       case AxisDirection.up:
@@ -1213,8 +1188,6 @@
     Duration duration = Duration.zero,
     Curve curve = Curves.ease,
   }) {
-    assert(viewport != null);
-    assert(offset != null);
     if (descendant == null) {
       return rect;
     }
@@ -1265,7 +1238,6 @@
       return MatrixUtils.transformRect(transform, rect ?? descendant.paintBounds);
     }
 
-    assert(targetOffset != null);
 
     offset.moveTo(targetOffset.offset, duration: duration, curve: curve);
     return targetOffset.rect;
@@ -1321,10 +1293,8 @@
     super.cacheExtent,
     super.cacheExtentStyle,
     super.clipBehavior,
-  }) : assert(anchor != null),
-       assert(anchor >= 0.0 && anchor <= 1.0),
+  }) : assert(anchor >= 0.0 && anchor <= 1.0),
        assert(cacheExtentStyle != CacheExtentStyle.viewport || cacheExtent != null),
-       assert(clipBehavior != null),
        _anchor = anchor,
        _center = center {
     addAll(children);
@@ -1385,7 +1355,6 @@
   double get anchor => _anchor;
   double _anchor;
   set anchor(double value) {
-    assert(value != null);
     assert(value >= 0.0 && value <= 1.0);
     if (value == _anchor) {
       return;
@@ -1537,7 +1506,6 @@
     double correction;
     int count = 0;
     do {
-      assert(offset.pixels != null);
       correction = _attemptLayout(mainAxisExtent, crossAxisExtent, offset.pixels + centerOffsetAdjustment);
       if (correction != 0.0) {
         offset.correctBy(correction);
@@ -1680,7 +1648,6 @@
   double scrollOffsetOf(RenderSliver child, double scrollOffsetWithinChild) {
     assert(child.parent == this);
     final GrowthDirection growthDirection = child.constraints.growthDirection;
-    assert(growthDirection != null);
     switch (growthDirection) {
       case GrowthDirection.forward:
         double scrollOffsetToChild = 0.0;
@@ -1705,7 +1672,6 @@
   double maxScrollObstructionExtentBefore(RenderSliver child) {
     assert(child.parent == this);
     final GrowthDirection growthDirection = child.constraints.growthDirection;
-    assert(growthDirection != null);
     switch (growthDirection) {
       case GrowthDirection.forward:
         double pinnedExtent = 0.0;
@@ -1729,15 +1695,12 @@
   @override
   void applyPaintTransform(RenderObject child, Matrix4 transform) {
     // Hit test logic relies on this always providing an invertible matrix.
-    assert(child != null);
     final SliverPhysicalParentData childParentData = child.parentData! as SliverPhysicalParentData;
     childParentData.applyPaintTransform(transform);
   }
 
   @override
   double computeChildMainAxisPosition(RenderSliver child, double parentMainAxisPosition) {
-    assert(child != null);
-    assert(child.constraints != null);
     final SliverPhysicalParentData childParentData = child.parentData! as SliverPhysicalParentData;
     switch (applyGrowthDirectionToAxisDirection(child.constraints.axisDirection, child.constraints.growthDirection)) {
       case AxisDirection.down:
@@ -1935,7 +1898,6 @@
     double correction;
     double effectiveExtent;
     do {
-      assert(offset.pixels != null);
       correction = _attemptLayout(mainAxisExtent, crossAxisExtent, offset.pixels);
       if (correction != 0.0) {
         offset.correctBy(correction);
@@ -2061,15 +2023,12 @@
   @override
   void applyPaintTransform(RenderObject child, Matrix4 transform) {
     // Hit test logic relies on this always providing an invertible matrix.
-    assert(child != null);
     final Offset offset = paintOffsetOf(child as RenderSliver);
     transform.translate(offset.dx, offset.dy);
   }
 
   @override
   double computeChildMainAxisPosition(RenderSliver child, double parentMainAxisPosition) {
-    assert(child != null);
-    assert(child.constraints != null);
     assert(hasSize);
     final SliverLogicalParentData childParentData = child.parentData! as SliverLogicalParentData;
     switch (applyGrowthDirectionToAxisDirection(child.constraints.axisDirection, child.constraints.growthDirection)) {
diff --git a/framework/lib/src/rendering/viewport_offset.dart b/framework/lib/src/rendering/viewport_offset.dart
index 89f991c..59b178c 100644
--- a/framework/lib/src/rendering/viewport_offset.dart
+++ b/framework/lib/src/rendering/viewport_offset.dart
@@ -196,7 +196,6 @@
     Curve? curve,
     bool? clamp,
   }) {
-    assert(to != null);
     if (duration == null || duration == Duration.zero) {
       jumpTo(to);
       return Future<void>.value();
diff --git a/framework/lib/src/rendering/wrap.dart b/framework/lib/src/rendering/wrap.dart
index 6857f87..4d8bd50 100644
--- a/framework/lib/src/rendering/wrap.dart
+++ b/framework/lib/src/rendering/wrap.dart
@@ -121,14 +121,7 @@
     TextDirection? textDirection,
     VerticalDirection verticalDirection = VerticalDirection.down,
     Clip clipBehavior = Clip.none,
-  }) : assert(direction != null),
-       assert(alignment != null),
-       assert(spacing != null),
-       assert(runAlignment != null),
-       assert(runSpacing != null),
-       assert(crossAxisAlignment != null),
-       assert(clipBehavior != null),
-       _direction = direction,
+  }) : _direction = direction,
        _alignment = alignment,
        _spacing = spacing,
        _runAlignment = runAlignment,
@@ -149,7 +142,6 @@
   Axis get direction => _direction;
   Axis _direction;
   set direction (Axis value) {
-    assert(value != null);
     if (_direction == value) {
       return;
     }
@@ -173,7 +165,6 @@
   WrapAlignment get alignment => _alignment;
   WrapAlignment _alignment;
   set alignment (WrapAlignment value) {
-    assert(value != null);
     if (_alignment == value) {
       return;
     }
@@ -195,7 +186,6 @@
   double get spacing => _spacing;
   double _spacing;
   set spacing (double value) {
-    assert(value != null);
     if (_spacing == value) {
       return;
     }
@@ -220,7 +210,6 @@
   WrapAlignment get runAlignment => _runAlignment;
   WrapAlignment _runAlignment;
   set runAlignment (WrapAlignment value) {
-    assert(value != null);
     if (_runAlignment == value) {
       return;
     }
@@ -241,7 +230,6 @@
   double get runSpacing => _runSpacing;
   double _runSpacing;
   set runSpacing (double value) {
-    assert(value != null);
     if (_runSpacing == value) {
       return;
     }
@@ -267,7 +255,6 @@
   WrapCrossAlignment get crossAxisAlignment => _crossAxisAlignment;
   WrapCrossAlignment _crossAxisAlignment;
   set crossAxisAlignment (WrapCrossAlignment value) {
-    assert(value != null);
     if (_crossAxisAlignment == value) {
       return;
     }
@@ -344,7 +331,6 @@
   Clip get clipBehavior => _clipBehavior;
   Clip _clipBehavior = Clip.none;
   set clipBehavior(Clip value) {
-    assert(value != null);
     if (value != _clipBehavior) {
       _clipBehavior = value;
       markNeedsPaint();
@@ -353,10 +339,6 @@
   }
 
   bool get _debugHasNecessaryDirections {
-    assert(direction != null);
-    assert(alignment != null);
-    assert(runAlignment != null);
-    assert(crossAxisAlignment != null);
     if (firstChild != null && lastChild != firstChild) {
       // i.e. there's more than one child
       switch (direction) {
@@ -364,7 +346,6 @@
           assert(textDirection != null, 'Horizontal $runtimeType with multiple children has a null textDirection, so the layout order is undefined.');
           break;
         case Axis.vertical:
-          assert(verticalDirection != null, 'Vertical $runtimeType with multiple children has a null verticalDirection, so the layout order is undefined.');
           break;
       }
     }
@@ -374,14 +355,12 @@
           assert(textDirection != null, 'Horizontal $runtimeType with alignment $alignment has a null textDirection, so the alignment cannot be resolved.');
           break;
         case Axis.vertical:
-          assert(verticalDirection != null, 'Vertical $runtimeType with alignment $alignment has a null verticalDirection, so the alignment cannot be resolved.');
           break;
       }
     }
     if (runAlignment == WrapAlignment.start || runAlignment == WrapAlignment.end) {
       switch (direction) {
         case Axis.horizontal:
-          assert(verticalDirection != null, 'Horizontal $runtimeType with runAlignment $runAlignment has a null verticalDirection, so the alignment cannot be resolved.');
           break;
         case Axis.vertical:
           assert(textDirection != null, 'Vertical $runtimeType with runAlignment $runAlignment has a null textDirection, so the alignment cannot be resolved.');
@@ -391,7 +370,6 @@
     if (crossAxisAlignment == WrapCrossAlignment.start || crossAxisAlignment == WrapCrossAlignment.end) {
       switch (direction) {
         case Axis.horizontal:
-          assert(verticalDirection != null, 'Horizontal $runtimeType with crossAxisAlignment $crossAxisAlignment has a null verticalDirection, so the alignment cannot be resolved.');
           break;
         case Axis.vertical:
           assert(textDirection != null, 'Vertical $runtimeType with crossAxisAlignment $crossAxisAlignment has a null textDirection, so the alignment cannot be resolved.');
@@ -610,8 +588,6 @@
         }
         break;
     }
-    assert(childConstraints != null);
-    assert(mainAxisLimit != null);
     final double spacing = this.spacing;
     final double runSpacing = this.runSpacing;
     final List<_RunMetrics> runMetrics = <_RunMetrics>[];
diff --git a/framework/lib/src/scheduler/binding.dart b/framework/lib/src/scheduler/binding.dart
index f0392cb..8af3176 100644
--- a/framework/lib/src/scheduler/binding.dart
+++ b/framework/lib/src/scheduler/binding.dart
@@ -385,7 +385,6 @@
   @protected
   @mustCallSuper
   void handleAppLifecycleStateChanged(AppLifecycleState state) {
-    assert(state != null);
     _lifecycleState = state;
     switch (state) {
       case AppLifecycleState.resumed:
@@ -1026,7 +1025,6 @@
   /// presentation time, and can be used to ensure that animations running in
   /// different processes are synchronized.
   Duration get currentSystemFrameTimeStamp {
-    assert(_lastRawTimeStamp != null);
     return _lastRawTimeStamp;
   }
 
@@ -1275,11 +1273,10 @@
   // Calls the given [callback] with [timestamp] as argument.
   //
   // Wraps the callback in a try/catch and forwards any error to
-  // [debugSchedulerExceptionHandler], if set. If not set, then simply prints
+  // [debugSchedulerExceptionHandler], if set. If not set, prints
   // the error.
   @pragma('vm:notify-debugger-on-exception')
   void _invokeFrameCallback(FrameCallback callback, Duration timeStamp, [ StackTrace? callbackStack ]) {
-    assert(callback != null);
     assert(_FrameCallbackEntry.debugCurrentCallbackStack == null);
     assert(() {
       _FrameCallbackEntry.debugCurrentCallbackStack = callbackStack;
diff --git a/framework/lib/src/semantics/semantics.dart b/framework/lib/src/semantics/semantics.dart
index c36f471..32fb160 100644
--- a/framework/lib/src/semantics/semantics.dart
+++ b/framework/lib/src/semantics/semantics.dart
@@ -6,6 +6,7 @@
 import 'package:engine/ui.dart' as ui;
 import 'package:engine/ui.dart' show Offset, Rect, SemanticsAction, SemanticsFlag, StringAttribute, TextDirection;
 
+import 'package:collection/collection.dart';
 import 'package:flute/foundation.dart';
 import 'package:flute/painting.dart' show MatrixUtils, TransformProperty;
 import 'package:flute/services.dart';
@@ -53,6 +54,20 @@
 /// Used by [SemanticsOwner.onSemanticsUpdate].
 typedef SemanticsUpdateCallback = void Function(ui.SemanticsUpdate update);
 
+/// Signature for the [SemanticsConfiguration.childConfigurationsDelegate].
+///
+/// The input list contains all [SemanticsConfiguration]s that rendering
+/// children want to merge upward. One can tag a render child with a
+/// [SemanticsTag] and look up its [SemanticsConfiguration]s through
+/// [SemanticsConfiguration.tagsChildrenWith].
+///
+/// The return value is the arrangement of these configs, including which
+/// configs continue to merge upward and which configs form sibling merge group.
+///
+/// Use [ChildSemanticsConfigurationsResultBuilder] to generate the return
+/// value.
+typedef ChildSemanticsConfigurationsDelegate = ChildSemanticsConfigurationsResult Function(List<SemanticsConfiguration>);
+
 /// A tag for a [SemanticsNode].
 ///
 /// Tags can be interpreted by the parent of a [SemanticsNode]
@@ -85,6 +100,89 @@
   String toString() => '${objectRuntimeType(this, 'SemanticsTag')}($name)';
 }
 
+/// The result that contains the arrangement for the child
+/// [SemanticsConfiguration]s.
+///
+/// When the [PipelineOwner] builds the semantics tree, it uses the returned
+/// [ChildSemanticsConfigurationsResult] from
+/// [SemanticsConfiguration.childConfigurationsDelegate] to decide how semantics nodes
+/// should form.
+///
+/// Use [ChildSemanticsConfigurationsResultBuilder] to build the result.
+class ChildSemanticsConfigurationsResult {
+  ChildSemanticsConfigurationsResult._(this.mergeUp, this.siblingMergeGroups);
+
+  /// Returns the [SemanticsConfiguration]s that are supposed to be merged into
+  /// the parent semantics node.
+  ///
+  /// [SemanticsConfiguration]s that are either semantics boundaries or are
+  /// conflicting with other [SemanticsConfiguration]s will form explicit
+  /// semantics nodes. All others will be merged into the parent.
+  final List<SemanticsConfiguration> mergeUp;
+
+  /// The groups of child semantics configurations that want to merge together
+  /// and form a sibling [SemanticsNode].
+  ///
+  /// All the [SemanticsConfiguration]s in a given group that are either
+  /// semantics boundaries or are conflicting with other
+  /// [SemanticsConfiguration]s of the same group will be excluded from the
+  /// sibling merge group and form independent semantics nodes as usual.
+  ///
+  /// The result [SemanticsNode]s from the merges are attached as the sibling
+  /// nodes of the immediate parent semantics node. For example, a `RenderObjectA`
+  /// has a rendering child, `RenderObjectB`. If both of them form their own
+  /// semantics nodes, `SemanticsNodeA` and `SemanticsNodeB`, any semantics node
+  /// created from sibling merge groups of `RenderObjectB` will be attach to
+  /// `SemanticsNodeA` as a sibling of `SemanticsNodeB`.
+  final List<List<SemanticsConfiguration>> siblingMergeGroups;
+}
+
+/// The builder to build a [ChildSemanticsConfigurationsResult] based on its
+/// annotations.
+///
+/// To use this builder, one can use [markAsMergeUp] and
+/// [markAsSiblingMergeGroup] to annotate the arrangement of
+/// [SemanticsConfiguration]s. Once all the configs are annotated, use [build]
+/// to generate the [ChildSemanticsConfigurationsResult].
+class ChildSemanticsConfigurationsResultBuilder {
+  /// Creates a [ChildSemanticsConfigurationsResultBuilder].
+  ChildSemanticsConfigurationsResultBuilder();
+
+  final List<SemanticsConfiguration> _mergeUp = <SemanticsConfiguration>[];
+  final List<List<SemanticsConfiguration>> _siblingMergeGroups = <List<SemanticsConfiguration>>[];
+
+  /// Marks the [SemanticsConfiguration] to be merged into the parent semantics
+  /// node.
+  ///
+  /// The [SemanticsConfiguration] will be added to the
+  /// [ChildSemanticsConfigurationsResult.mergeUp] that this builder builds.
+  void markAsMergeUp(SemanticsConfiguration config) => _mergeUp.add(config);
+
+  /// Marks a group of [SemanticsConfiguration]s to merge together
+  /// and form a sibling [SemanticsNode].
+  ///
+  /// The group of [SemanticsConfiguration]s will be added to the
+  /// [ChildSemanticsConfigurationsResult.siblingMergeGroups] that this builder builds.
+  void markAsSiblingMergeGroup(List<SemanticsConfiguration> configs) => _siblingMergeGroups.add(configs);
+
+  /// Builds a [ChildSemanticsConfigurationsResult] contains the arrangement.
+  ChildSemanticsConfigurationsResult build() {
+    assert((){
+      final Set<SemanticsConfiguration> seenConfigs = <SemanticsConfiguration>{};
+      for (final SemanticsConfiguration config in <SemanticsConfiguration>[..._mergeUp, ..._siblingMergeGroups.flattened]) {
+        assert(
+          seenConfigs.add(config),
+          'Duplicated SemanticsConfigurations. This can happen if the same '
+          'SemanticsConfiguration was marked twice in markAsMergeUp and/or '
+          'markAsSiblingMergeGroup'
+        );
+      }
+      return true;
+    }());
+    return ChildSemanticsConfigurationsResult._(_mergeUp, _siblingMergeGroups);
+  }
+}
+
 /// An identifier of a custom semantics action.
 ///
 /// Custom semantics actions can be provided to make complex user
@@ -113,8 +211,7 @@
   ///
   /// The [label] must not be null or the empty string.
   const CustomSemanticsAction({required String this.label})
-    : assert(label != null),
-      assert(label != ''),
+    : assert(label != ''),
       hint = null,
       action = null;
 
@@ -123,9 +220,7 @@
   ///
   /// The [hint] must not be null or the empty string.
   const CustomSemanticsAction.overridingAction({required String this.hint, required SemanticsAction this.action})
-    : assert(hint != null),
-      assert(hint != ''),
-      assert(action != null),
+    : assert(hint != ''),
       label = null;
 
   /// The user readable name of this custom semantics action.
@@ -273,8 +368,7 @@
     super.defaultValue,
     super.level,
     super.description,
-  }) : assert(showName != null),
-       assert(level != null);
+  });
 
   /// Whether to show the property when the [value] is an [AttributedString]
   /// whose [AttributedString.string] is the empty string.
@@ -343,20 +437,12 @@
     this.tags,
     this.transform,
     this.customSemanticsActionIds,
-  }) : assert(flags != null),
-       assert(actions != null),
-       assert(attributedLabel != null),
-       assert(attributedValue != null),
-       assert(attributedDecreasedValue != null),
-       assert(attributedIncreasedValue != null),
-       assert(attributedHint != null),
-       assert(tooltip == '' || textDirection != null, 'A SemanticsData object with tooltip "$tooltip" had a null textDirection.'),
+  }) : assert(tooltip == '' || textDirection != null, 'A SemanticsData object with tooltip "$tooltip" had a null textDirection.'),
        assert(attributedLabel.string == '' || textDirection != null, 'A SemanticsData object with label "${attributedLabel.string}" had a null textDirection.'),
        assert(attributedValue.string == '' || textDirection != null, 'A SemanticsData object with value "${attributedValue.string}" had a null textDirection.'),
        assert(attributedDecreasedValue.string == '' || textDirection != null, 'A SemanticsData object with decreasedValue "${attributedDecreasedValue.string}" had a null textDirection.'),
        assert(attributedIncreasedValue.string == '' || textDirection != null, 'A SemanticsData object with increasedValue "${attributedIncreasedValue.string}" had a null textDirection.'),
-       assert(attributedHint.string == '' || textDirection != null, 'A SemanticsData object with hint "${attributedHint.string}" had a null textDirection.'),
-       assert(rect != null);
+       assert(attributedHint.string == '' || textDirection != null, 'A SemanticsData object with hint "${attributedHint.string}" had a null textDirection.');
 
   /// A bit field of [SemanticsFlag]s that apply to this node.
   final int flags;
@@ -1623,7 +1709,6 @@
   Rect get rect => _rect;
   Rect _rect = Rect.zero;
   set rect(Rect value) {
-    assert(value != null);
     assert(value.isFinite, '$this (with $owner) tried to set a non-finite rect.');
     if (_rect != value) {
       _rect = value;
@@ -1708,7 +1793,6 @@
   bool get isMergedIntoParent => _isMergedIntoParent;
   bool _isMergedIntoParent = false;
   set isMergedIntoParent(bool value) {
-    assert(value != null);
     if (_isMergedIntoParent == value) {
       return;
     }
@@ -1831,7 +1915,6 @@
       }
     }
     if (!sawChange && _children != null) {
-      assert(newChildren != null);
       assert(newChildren.length == _children!.length);
       // Did the order change?
       for (int i = 0; i < _children!.length; i++) {
@@ -2406,13 +2489,13 @@
         platformViewId ??= node._platformViewId;
         maxValueLength ??= node._maxValueLength;
         currentValueLength ??= node._currentValueLength;
-        if (attributedValue == null || attributedValue.string == '') {
+        if (attributedValue.string == '') {
           attributedValue = node._attributedValue;
         }
-        if (attributedIncreasedValue == null || attributedIncreasedValue.string == '') {
+        if (attributedIncreasedValue.string == '') {
           attributedIncreasedValue = node._attributedIncreasedValue;
         }
-        if (attributedDecreasedValue == null || attributedDecreasedValue.string == '') {
+        if (attributedDecreasedValue.string == '') {
           attributedDecreasedValue = node._attributedDecreasedValue;
         }
         if (tooltip == '') {
@@ -2710,7 +2793,6 @@
     DiagnosticLevel minLevel = DiagnosticLevel.debug,
     DebugSemanticsDumpOrder childOrder = DebugSemanticsDumpOrder.traversalOrder,
   }) {
-    assert(childOrder != null);
     return toDiagnosticsNode(childOrder: childOrder).toStringDeep(prefixLineOne: prefixLineOne, prefixOtherLines: prefixOtherLines, minLevel: minLevel);
   }
 
@@ -2737,7 +2819,6 @@
 
   /// Returns the list of direct children of this node in the specified order.
   List<SemanticsNode> debugListChildrenInOrder(DebugSemanticsDumpOrder childOrder) {
-    assert(childOrder != null);
     if (_children == null) {
       return const <SemanticsNode>[];
     }
@@ -2764,10 +2845,7 @@
     required this.isLeadingEdge,
     required this.offset,
     required this.node,
-  }) : assert(isLeadingEdge != null),
-       assert(offset != null),
-       assert(offset.isFinite),
-       assert(node != null);
+  }) : assert(offset.isFinite);
 
   /// True if the edge comes before the seconds edge along the traversal
   /// direction, and false otherwise.
@@ -2801,7 +2879,7 @@
   _SemanticsSortGroup({
     required this.startOffset,
     required this.textDirection,
-  }) : assert(startOffset != null);
+  });
 
   /// The offset from the start edge of the parent [SemanticsNode] in the
   /// direction of the traversal.
@@ -3024,9 +3102,7 @@
     required this.node,
     this.sortKey,
     required this.position,
-  })
-    : assert(node != null),
-      assert(position != null);
+  });
 
   /// The node whose position this sort node determines.
   final SemanticsNode node;
@@ -3165,7 +3241,6 @@
   /// If the given `action` requires arguments they need to be passed in via
   /// the `args` parameter.
   void performAction(int id, SemanticsAction action, [ Object? args ]) {
-    assert(action != null);
     final SemanticsActionHandler? handler = _getSemanticsActionHandlerForId(id, action);
     if (handler != null) {
       handler(args);
@@ -3219,7 +3294,6 @@
   /// If the given `action` requires arguments they need to be passed in via
   /// the `args` parameter.
   void performActionAt(Offset position, SemanticsAction action, [ Object? args ]) {
-    assert(action != null);
     final SemanticsNode? node = rootSemanticsNode;
     if (node == null) {
       return;
@@ -3321,7 +3395,6 @@
   /// The provided `handler` is called to respond to the user triggered
   /// `action`.
   void _addAction(SemanticsAction action, SemanticsActionHandler handler) {
-    assert(handler != null);
     _actions[action] = handler;
     _actionsAsBits |= action.index;
     _hasBeenAnnotated = true;
@@ -3333,7 +3406,6 @@
   /// The provided `handler` is called to respond to the user triggered
   /// `action`.
   void _addArgumentlessAction(SemanticsAction action, VoidCallback handler) {
-    assert(handler != null);
     _addAction(action, (Object? args) {
       assert(args == null);
       handler();
@@ -3648,7 +3720,7 @@
     _addAction(SemanticsAction.setSelection, (Object? args) {
       assert(args != null && args is Map);
       final Map<String, int> selection = (args! as Map<dynamic, dynamic>).cast<String, int>();
-      assert(selection != null && selection['base'] != null && selection['extent'] != null);
+      assert(selection['base'] != null && selection['extent'] != null);
       value!(TextSelection(
         baseOffset: selection['base']!,
         extentOffset: selection['extent']!,
@@ -3724,6 +3796,25 @@
     _onDidLoseAccessibilityFocus = value;
   }
 
+  /// A delegate that decides how to handle [SemanticsConfiguration]s produced
+  /// in the widget subtree.
+  ///
+  /// The [SemanticsConfiguration]s are produced by rendering objects in the
+  /// subtree and want to merge up to their parent. This delegate can decide
+  /// which of these should be merged together to form sibling SemanticsNodes and
+  /// which of them should be merged upwards into the parent SemanticsNode.
+  ///
+  /// The input list of [SemanticsConfiguration]s can be empty if the rendering
+  /// object of this semantics configuration is a leaf node.
+  ChildSemanticsConfigurationsDelegate? get childConfigurationsDelegate => _childConfigurationsDelegate;
+  ChildSemanticsConfigurationsDelegate? _childConfigurationsDelegate;
+  set childConfigurationsDelegate(ChildSemanticsConfigurationsDelegate? value) {
+    assert(value != null);
+    _childConfigurationsDelegate = value;
+    // Setting the childConfigsDelegate does not annotate any meaningful
+    // semantics information of the config.
+  }
+
   /// Returns the action handler registered for [action] or null if none was
   /// registered.
   SemanticsActionHandler? getActionHandler(SemanticsAction action) => _actions[action];
@@ -3887,7 +3978,6 @@
   ///  * [attributedLabel], which is the [AttributedString] of this property.
   String get label => _attributedLabel.string;
   set label(String label) {
-    assert(label != null);
     _attributedLabel = AttributedString(label);
     _hasBeenAnnotated = true;
   }
@@ -3928,7 +4018,6 @@
   ///    [value] will be after performing [SemanticsAction.decrease].
   String get value => _attributedValue.string;
   set value(String value) {
-    assert(value != null);
     _attributedValue = AttributedString(value);
     _hasBeenAnnotated = true;
   }
@@ -3974,7 +4063,6 @@
   ///  * [attributedIncreasedValue], which is the [AttributedString] of this property.
   String get increasedValue => _attributedIncreasedValue.string;
   set increasedValue(String increasedValue) {
-    assert(increasedValue != null);
     _attributedIncreasedValue = AttributedString(increasedValue);
     _hasBeenAnnotated = true;
   }
@@ -4012,7 +4100,6 @@
   ///  * [attributedDecreasedValue], which is the [AttributedString] of this property.
   String get decreasedValue => _attributedDecreasedValue.string;
   set decreasedValue(String decreasedValue) {
-    assert(decreasedValue != null);
     _attributedDecreasedValue = AttributedString(decreasedValue);
     _hasBeenAnnotated = true;
   }
@@ -4047,7 +4134,6 @@
   ///  * [attributedHint], which is the [AttributedString] of this property.
   String get hint => _attributedHint.string;
   set hint(String hint) {
-    assert(hint != null);
     _attributedHint = AttributedString(hint);
     _hasBeenAnnotated = true;
   }
@@ -4100,7 +4186,7 @@
   double get elevation => _elevation;
   double _elevation = 0.0;
   set elevation(double value) {
-    assert(value != null && value >= 0.0);
+    assert(value >= 0.0);
     if (value == _elevation) {
       return;
     }
@@ -4117,7 +4203,7 @@
   double get thickness => _thickness;
   double _thickness = 0.0;
   set thickness(double value) {
-    assert(value != null && value >= 0.0);
+    assert(value >= 0.0);
     if (value == _thickness) {
       return;
     }
@@ -4448,6 +4534,11 @@
   ///  * [addTagForChildren] to add a tag and for more information about their
   ///    usage.
   Iterable<SemanticsTag>? get tagsForChildren => _tagsForChildren;
+
+  /// Whether this configuration will tag the child semantics nodes with a
+  /// given [SemanticsTag].
+  bool tagsChildrenWith(SemanticsTag tag) => _tagsForChildren?.contains(tag) ?? false;
+
   Set<SemanticsTag>? _tagsForChildren;
 
   /// Specifies a [SemanticsTag] that this configuration wants to apply to all
@@ -4510,7 +4601,7 @@
     if (_currentValueLength != null && other._currentValueLength != null) {
       return false;
     }
-    if (_attributedValue != null && _attributedValue.string.isNotEmpty && other._attributedValue != null && other._attributedValue.string.isNotEmpty) {
+    if (_attributedValue.string.isNotEmpty && other._attributedValue.string.isNotEmpty) {
       return false;
     }
     return true;
@@ -4558,13 +4649,13 @@
       otherAttributedString: child._attributedLabel,
       otherTextDirection: child.textDirection,
     );
-    if (_attributedValue == null || _attributedValue.string == '') {
+    if (_attributedValue.string == '') {
       _attributedValue = child._attributedValue;
     }
-    if (_attributedIncreasedValue == null || _attributedIncreasedValue.string == '') {
+    if (_attributedIncreasedValue.string == '') {
       _attributedIncreasedValue = child._attributedIncreasedValue;
     }
-    if (_attributedDecreasedValue == null || _attributedDecreasedValue.string == '') {
+    if (_attributedDecreasedValue.string == '') {
       _attributedDecreasedValue = child._attributedDecreasedValue;
     }
     _attributedHint = _concatAttributedString(
@@ -4733,7 +4824,7 @@
   }
 }
 
-/// A [SemanticsSortKey] that sorts simply based on the `double` value it is
+/// A [SemanticsSortKey] that sorts based on the `double` value it is
 /// given.
 ///
 /// The [OrdinalSortKey] compares itself with other [OrdinalSortKey]s
@@ -4758,8 +4849,7 @@
   const OrdinalSortKey(
     this.order, {
     super.name,
-  }) : assert(order != null),
-       assert(order > double.negativeInfinity),
+  }) : assert(order > double.negativeInfinity),
        assert(order < double.infinity);
 
   /// Determines the placement of this key in a sequence of keys that defines
@@ -4772,7 +4862,7 @@
 
   @override
   int doCompare(OrdinalSortKey other) {
-    if (other.order == null || order == null || other.order == order) {
+    if (other.order == order) {
       return 0;
     }
     return order.compareTo(other.order);
diff --git a/framework/lib/src/semantics/semantics_event.dart b/framework/lib/src/semantics/semantics_event.dart
index c59b0b1..63d3cbb 100644
--- a/framework/lib/src/semantics/semantics_event.dart
+++ b/framework/lib/src/semantics/semantics_event.dart
@@ -87,9 +87,7 @@
 
   /// Constructs an event that triggers an announcement by the platform.
   const AnnounceSemanticsEvent(this.message, this.textDirection, {this.assertiveness = Assertiveness.polite})
-    : assert(message != null),
-      assert(textDirection != null),
-      super('announce');
+    : super('announce');
 
   /// The message to announce.
   ///
diff --git a/framework/lib/src/services/asset_bundle.dart b/framework/lib/src/services/asset_bundle.dart
index fab80de..b90ad57 100644
--- a/framework/lib/src/services/asset_bundle.dart
+++ b/framework/lib/src/services/asset_bundle.dart
@@ -96,12 +96,22 @@
   }
 
   /// Retrieve a string from the asset bundle, parse it with the given function,
-  /// and return the function's result.
+  /// and return that function's result.
   ///
   /// Implementations may cache the result, so a particular key should only be
   /// used with one parser for the lifetime of the asset bundle.
   Future<T> loadStructuredData<T>(String key, Future<T> Function(String value) parser);
 
+  /// Retrieve [ByteData] from the asset bundle, parse it with the given function,
+  /// and return that function's result.
+  ///
+  /// Implementations may cache the result, so a particular key should only be
+  /// used with one parser for the lifetime of the asset bundle.
+  Future<T> loadStructuredBinaryData<T>(String key, FutureOr<T> Function(ByteData data) parser) async {
+    final ByteData data = await load(key);
+    return parser(data);
+  }
+
   /// If this is a caching asset bundle, and the given key describes a cached
   /// asset, then evict the asset from the cache so that the next time it is
   /// loaded, the cache will be reread from the asset bundle.
@@ -151,11 +161,19 @@
   /// fetched.
   @override
   Future<T> loadStructuredData<T>(String key, Future<T> Function(String value) parser) async {
-    assert(key != null);
-    assert(parser != null);
     return parser(await loadString(key));
   }
 
+  /// Retrieve [ByteData] from the asset bundle, parse it with the given function,
+  /// and return the function's result.
+  ///
+  /// The result is not cached. The parser is run each time the resource is
+  /// fetched.
+  @override
+  Future<T> loadStructuredBinaryData<T>(String key, FutureOr<T> Function(ByteData data) parser) async {
+    return parser(await load(key));
+  }
+
   // TODO(ianh): Once the underlying network logic learns about caching, we
   // should implement evict().
 
@@ -175,6 +193,7 @@
   // TODO(ianh): Replace this with an intelligent cache, see https://github.com/flutter/flutter/issues/3568
   final Map<String, Future<String>> _stringCache = <String, Future<String>>{};
   final Map<String, Future<dynamic>> _structuredDataCache = <String, Future<dynamic>>{};
+  final Map<String, Future<dynamic>> _structuredBinaryDataCache = <String, Future<dynamic>>{};
 
   @override
   Future<String> loadString(String key, { bool cache = true }) {
@@ -196,8 +215,6 @@
   /// callback synchronously.
   @override
   Future<T> loadStructuredData<T>(String key, Future<T> Function(String value) parser) {
-    assert(key != null);
-    assert(parser != null);
     if (_structuredDataCache.containsKey(key)) {
       return _structuredDataCache[key]! as Future<T>;
     }
@@ -225,16 +242,66 @@
     return completer.future;
   }
 
+  /// Retrieve bytedata from the asset bundle, parse it with the given function,
+  /// and return the function's result.
+  ///
+  /// The result of parsing the bytedata is cached (the bytedata itself is not).
+  /// For any given `key`, the `parser` is only run the first time.
+  ///
+  /// Once the value has been parsed, the future returned by this function for
+  /// subsequent calls will be a [SynchronousFuture], which resolves its
+  /// callback synchronously.
+  @override
+  Future<T> loadStructuredBinaryData<T>(String key, FutureOr<T> Function(ByteData data) parser) {
+    if (_structuredBinaryDataCache.containsKey(key)) {
+      return _structuredBinaryDataCache[key]! as Future<T>;
+    }
+
+    // load can return a SynchronousFuture in certain cases, like in the
+    // flutter_test framework. So, we need to support both async and sync flows.
+    Completer<T>? completer; // For async flow.
+    SynchronousFuture<T>? result; // For sync flow.
+
+    load(key)
+      .then<T>(parser)
+      .then<void>((T value) {
+        result = SynchronousFuture<T>(value);
+        if (completer != null) {
+          // The load and parse operation ran asynchronously. We already returned
+          // from the loadStructuredBinaryData function and therefore the caller
+          // was given the future of the completer.
+          completer.complete(value);
+        }
+      }, onError: (Object error, StackTrace stack) {
+        completer!.completeError(error, stack);
+      });
+
+    if (result != null) {
+      // The above code ran synchronously. We can synchronously return the result.
+      _structuredBinaryDataCache[key] = result!;
+      return result!;
+    }
+
+    // Since the above code is being run asynchronously and thus hasn't run its
+    // `then` handler yet, we'll return a completer that will be completed
+    // when the handler does run.
+    completer = Completer<T>();
+    _structuredBinaryDataCache[key] = completer.future;
+    return completer.future;
+  }
+
   @override
   void evict(String key) {
     _stringCache.remove(key);
     _structuredDataCache.remove(key);
+    _structuredBinaryDataCache.remove(key);
   }
 
   @override
   void clear() {
     _stringCache.clear();
     _structuredDataCache.clear();
+    _structuredBinaryDataCache.clear();
   }
 
   @override
@@ -276,7 +343,7 @@
     bool debugUsePlatformChannel = false;
     assert(() {
       // dart:io is safe to use here since we early return for web
-      // above. If that code is changed, this needs to be gaurded on
+      // above. If that code is changed, this needs to be guarded on
       // web presence. Override how assets are loaded in tests so that
       // the old loader behavior that allows tests to load assets from
       // the current package using the package prefix.
diff --git a/framework/lib/src/services/asset_manifest.dart b/framework/lib/src/services/asset_manifest.dart
new file mode 100644
index 0000000..879a17c
--- /dev/null
+++ b/framework/lib/src/services/asset_manifest.dart
@@ -0,0 +1,134 @@
+// 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:flute/foundation.dart';
+
+import 'asset_bundle.dart';
+import 'message_codecs.dart';
+
+const String _kAssetManifestFilename = 'AssetManifest.bin';
+
+/// Contains details about available assets and their variants.
+/// See [Asset variants](https://docs.flutter.dev/development/ui/assets-and-images#asset-variants)
+/// to learn about asset variants and how to declare them.
+abstract class AssetManifest {
+  /// Loads asset manifest data from an [AssetBundle] object and creates an
+  /// [AssetManifest] object from that data.
+  static Future<AssetManifest> loadFromAssetBundle(AssetBundle bundle) {
+    return bundle.loadStructuredBinaryData(_kAssetManifestFilename, _AssetManifestBin.fromStandardMessageCodecMessage);
+  }
+
+  /// Lists the keys of all main assets. This does not include assets
+  /// that are variants of other assets.
+  ///
+  /// The logical key maps to the path of an asset specified in the pubspec.yaml
+  /// file at build time.
+  ///
+  /// See [Specifying assets](https://docs.flutter.dev/development/ui/assets-and-images#specifying-assets)
+  /// and [Loading assets](https://docs.flutter.dev/development/ui/assets-and-images#loading-assets) for more
+  /// information.
+  List<String> listAssets();
+
+  /// Retrieves metadata about an asset and its variants.
+  ///
+  /// Note that this method considers a main asset to be a variant of itself and
+  /// includes it in the returned list.
+  ///
+  /// Throws an [ArgumentError] if [key] cannot be found within the manifest. To
+  /// avoid this, use a key obtained from the [listAssets] method.
+  List<AssetMetadata> getAssetVariants(String key);
+}
+
+// Lazily parses the binary asset manifest into a data structure that's easier to work
+// with.
+//
+// The binary asset manifest is a map of asset keys to a list of objects
+// representing the asset's variants.
+//
+// The entries with each variant object are:
+//  - "asset": the location of this variant to load it from.
+//  - "dpr": The device-pixel-ratio that the asset is best-suited for.
+//
+// New fields could be added to this object schema to support new asset variation
+// features, such as themes, locale/region support, reading directions, and so on.
+class _AssetManifestBin implements AssetManifest {
+  _AssetManifestBin(Map<Object?, Object?> standardMessageData): _data = standardMessageData;
+
+  factory _AssetManifestBin.fromStandardMessageCodecMessage(ByteData message) {
+    final dynamic data = const StandardMessageCodec().decodeMessage(message);
+    return _AssetManifestBin(data as Map<Object?, Object?>);
+  }
+
+  final Map<Object?, Object?> _data;
+  final Map<String, List<AssetMetadata>> _typeCastedData = <String, List<AssetMetadata>>{};
+
+  @override
+  List<AssetMetadata> getAssetVariants(String key) {
+    // We lazily delay typecasting to prevent a performance hiccup when parsing
+    // large asset manifests. This is important to keep an app's first asset
+    // load fast.
+    if (!_typeCastedData.containsKey(key)) {
+      final Object? variantData = _data[key];
+      if (variantData == null) {
+        throw ArgumentError('Asset key $key was not found within the asset manifest.');
+      }
+      _typeCastedData[key] = ((_data[key] ?? <Object?>[]) as Iterable<Object?>)
+        .cast<Map<Object?, Object?>>()
+        .map((Map<Object?, Object?> data) => AssetMetadata(
+            key: data['asset']! as String,
+            targetDevicePixelRatio: data['dpr']! as double,
+            main: false,
+        ))
+        .toList();
+
+      _data.remove(key);
+    }
+
+    final AssetMetadata mainAsset = AssetMetadata(key: key,
+      targetDevicePixelRatio: null,
+      main: true
+    );
+
+    return <AssetMetadata>[mainAsset, ..._typeCastedData[key]!];
+  }
+
+  @override
+  List<String> listAssets() {
+    return <String>[..._data.keys.cast<String>(), ..._typeCastedData.keys];
+  }
+}
+
+/// Contains information about an asset.
+@immutable
+class AssetMetadata {
+  /// Creates an object containing information about an asset.
+  const AssetMetadata({
+    required this.key,
+    required this.targetDevicePixelRatio,
+    required this.main,
+  });
+
+  /// The device pixel ratio that this asset is most ideal for. This is determined
+  /// by the name of the parent folder of the asset file. For example, if the
+  /// parent folder is named "3.0x", the target device pixel ratio of that
+  /// asset will be interpreted as 3.
+  ///
+  /// This will be null if the parent folder name is not a ratio value followed
+  /// by an "x".
+  ///
+  /// See [Declaring resolution-aware image assets](https://docs.flutter.dev/development/ui/assets-and-images#resolution-aware)
+  /// for more information.
+  final double? targetDevicePixelRatio;
+
+  /// The asset's key, which is the path to the asset specified in the pubspec.yaml
+  /// file at build time.
+  final String key;
+
+  /// Whether or not this is a main asset. In other words, this is true if
+  /// this asset is not a variant of another asset.
+  ///
+  /// See [Asset variants](https://docs.flutter.dev/development/ui/assets-and-images#asset-variants)
+  /// for more about asset variants.
+  final bool main;
+}
diff --git a/framework/lib/src/services/autofill.dart b/framework/lib/src/services/autofill.dart
index b7e9a86..54010cf 100644
--- a/framework/lib/src/services/autofill.dart
+++ b/framework/lib/src/services/autofill.dart
@@ -651,8 +651,7 @@
     this.autofillHints = const <String>[],
     this.hintText,
     required this.currentEditingValue,
-  }) : assert(uniqueIdentifier != null),
-       assert(autofillHints != null);
+  });
 
   /// An [AutofillConfiguration] that indicates the [AutofillClient] does not
   /// wish to be autofilled.
@@ -810,9 +809,7 @@
   _AutofillScopeTextInputConfiguration({
     required this.allConfigurations,
     required TextInputConfiguration currentClientConfiguration,
-  }) : assert(allConfigurations != null),
-       assert(currentClientConfiguration != null),
-       super(inputType: currentClientConfiguration.inputType,
+  }) : super(inputType: currentClientConfiguration.inputType,
          obscureText: currentClientConfiguration.obscureText,
          autocorrect: currentClientConfiguration.autocorrect,
          smartDashesType: currentClientConfiguration.smartDashesType,
@@ -843,7 +840,6 @@
 mixin AutofillScopeMixin implements AutofillScope {
   @override
   TextInputConnection attach(TextInputClient trigger, TextInputConfiguration configuration) {
-    assert(trigger != null);
     assert(
       !autofillClients.any((AutofillClient client) => !client.textInputConfiguration.autofillConfiguration.enabled),
       'Every client in AutofillScope.autofillClients must enable autofill',
diff --git a/framework/lib/src/services/browser_context_menu.dart b/framework/lib/src/services/browser_context_menu.dart
new file mode 100644
index 0000000..0f5c667
--- /dev/null
+++ b/framework/lib/src/services/browser_context_menu.dart
@@ -0,0 +1,83 @@
+// 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:flute/foundation.dart';
+
+import 'system_channels.dart';
+
+/// Controls the browser's context menu on the web platform.
+///
+/// The context menu is the menu that appears on right clicking or selecting
+/// text in the browser, for example.
+///
+/// On web, by default, the browser's context menu is enabled and Flutter's
+/// context menus are hidden.
+///
+/// On all non-web platforms, this does nothing.
+class BrowserContextMenu {
+  BrowserContextMenu._();
+
+  static final BrowserContextMenu _instance = BrowserContextMenu._();
+
+  /// Whether showing the browser's context menu is enabled.
+  ///
+  /// When true, any event that the browser typically uses to trigger its
+  /// context menu (e.g. right click) will do so. When false, the browser's
+  /// context menu will not show.
+  ///
+  /// It's possible for this to be true but for the browser's context menu to
+  /// not show due to direct manipulation of the DOM. For example, handlers for
+  /// the browser's `contextmenu` event could be added/removed in the browser's
+  /// JavaScript console, and this boolean wouldn't know about it. This boolean
+  /// only indicates the results of calling [disableContextMenu] and
+  /// [enableContextMenu] here.
+  ///
+  /// Defaults to true.
+  static bool get enabled => _instance._enabled;
+
+  bool _enabled = true;
+
+  final MethodChannel _channel = SystemChannels.contextMenu;
+
+  /// Disable the browser's context menu.
+  ///
+  /// By default, when the app starts, the browser's context menu is already
+  /// enabled.
+  ///
+  /// This is an asynchronous action. The context menu can be considered to be
+  /// disabled at the time that the Future resolves. [enabled] won't reflect the
+  /// change until that time.
+  ///
+  /// See also:
+  ///  * [enableContextMenu], which performs the opposite operation.
+  static Future<void> disableContextMenu() {
+    assert(kIsWeb, 'This has no effect on platforms other than web.');
+    return _instance._channel.invokeMethod<void>(
+      'disableContextMenu',
+    ).then((_) {
+      _instance._enabled = false;
+    });
+  }
+
+  /// Enable the browser's context menu.
+  ///
+  /// By default, when the app starts, the browser's context menu is already
+  /// enabled. Typically this method would be called after first calling
+  /// [disableContextMenu].
+  ///
+  /// This is an asynchronous action. The context menu can be considered to be
+  /// enabled at the time that the Future resolves. [enabled] won't reflect the
+  /// change until that time.
+  ///
+  /// See also:
+  ///  * [disableContextMenu], which performs the opposite operation.
+  static Future<void> enableContextMenu() {
+    assert(kIsWeb, 'This has no effect on platforms other than web.');
+    return _instance._channel.invokeMethod<void>(
+      'enableContextMenu',
+    ).then((_) {
+      _instance._enabled = true;
+    });
+  }
+}
diff --git a/framework/lib/src/services/debug.dart b/framework/lib/src/services/debug.dart
index 610a1ff..ade9404 100644
--- a/framework/lib/src/services/debug.dart
+++ b/framework/lib/src/services/debug.dart
@@ -17,7 +17,7 @@
 
 /// Profile and print statistics on Platform Channel usage.
 ///
-/// When this is is true statistics about the usage of Platform Channels will be
+/// When this is true statistics about the usage of Platform Channels will be
 /// printed out periodically to the console and Timeline events will show the
 /// time between sending and receiving a message (encoding and decoding time
 /// excluded).
diff --git a/framework/lib/src/services/hardware_keyboard.dart b/framework/lib/src/services/hardware_keyboard.dart
index 5cc2f5e..7fcbe1a 100644
--- a/framework/lib/src/services/hardware_keyboard.dart
+++ b/framework/lib/src/services/hardware_keyboard.dart
@@ -26,34 +26,33 @@
 /// Only a limited number of modes are supported, which are enumerated as
 /// static members of this class. Manual constructing of this class is
 /// prohibited.
-@immutable
-class KeyboardLockMode {
-  // KeyboardLockMode has a fixed pool of supported keys, enumerated as static
-  // members of this class, therefore constructing is prohibited.
-  const KeyboardLockMode._(this.logicalKey);
-
-  /// The logical key that triggers this lock mode.
-  final LogicalKeyboardKey logicalKey;
-
+enum KeyboardLockMode {
   /// Represents the number lock mode on the keyboard.
   ///
   /// On supporting systems, enabling number lock mode usually allows key
   /// presses of the number pad to input numbers, instead of acting as up, down,
   /// left, right, page up, end, etc.
-  static const KeyboardLockMode numLock = KeyboardLockMode._(LogicalKeyboardKey.numLock);
+  numLock._(LogicalKeyboardKey.numLock),
 
   /// Represents the scrolling lock mode on the keyboard.
   ///
   /// On supporting systems and applications (such as a spreadsheet), enabling
   /// scrolling lock mode usually allows key presses of the cursor keys to
   /// scroll the document instead of the cursor.
-  static const KeyboardLockMode scrollLock = KeyboardLockMode._(LogicalKeyboardKey.scrollLock);
+  scrollLock._(LogicalKeyboardKey.scrollLock),
 
   /// Represents the capital letters lock mode on the keyboard.
   ///
   /// On supporting systems, enabling capital lock mode allows key presses of
   /// the letter keys to input uppercase letters instead of lowercase.
-  static const KeyboardLockMode capsLock = KeyboardLockMode._(LogicalKeyboardKey.capsLock);
+  capsLock._(LogicalKeyboardKey.capsLock);
+
+  // KeyboardLockMode has a fixed pool of supported keys, enumerated as static
+  // members of this class, therefore constructing is prohibited.
+  const KeyboardLockMode._(this.logicalKey);
+
+  /// The logical key that triggers this lock mode.
+  final LogicalKeyboardKey logicalKey;
 
   static final Map<int, KeyboardLockMode> _knownLockModes = <int, KeyboardLockMode>{
     numLock.logicalKey.keyId: numLock,
@@ -101,7 +100,7 @@
   /// is a mnemonic ("keyA" is easier to remember than 0x70004), derived from the
   /// key's effect on a QWERTY keyboard. The name does not represent the key's
   /// effect whatsoever (a physical "keyA" can be the Q key on an AZERTY
-  /// keyboard.)
+  /// keyboard).
   ///
   /// For instance, if you wanted to make a game where the key to the right of
   /// the CAPS LOCK key made the player move left, you would be comparing a
@@ -115,10 +114,10 @@
   ///
   /// Also, even though physical keys are defined with USB HID codes, their
   /// values are not necessarily the same HID codes produced by the hardware and
-  /// presented to the driver, because on most platforms Flutter has to map the
-  /// platform representation back to an HID code since the original HID
-  /// code is not provided. USB HID is simply a conveniently well-defined
-  /// standard to list possible keys that a Flutter app can encounter.
+  /// presented to the driver. On most platforms, Flutter has to map the
+  /// platform representation back to a HID code because the original HID
+  /// code is not provided. USB HID was chosen because it is a well-defined
+  /// standard for referring to keys such as those a Flutter app may encounter.
   ///
   /// See also:
   ///
@@ -242,6 +241,9 @@
 /// An event indicating that the user has been holding a key on the keyboard
 /// and causing repeated events.
 ///
+/// Repeat events are not guaranteed and are provided only if supported by the
+/// underlying platform.
+///
 /// See also:
 ///
 ///  * [KeyDownEvent], a key event representing the user
@@ -259,7 +261,7 @@
   });
 }
 
-/// The signature for [HardwareKeyboard.addHandler], a callback to to decide whether
+/// The signature for [HardwareKeyboard.addHandler], a callback to decide whether
 /// the entire framework handles a key event.
 typedef KeyEventCallback = bool Function(KeyEvent event);
 
@@ -738,7 +740,7 @@
   /// The global entrance which handles all key events sent to Flutter.
   ///
   /// Typical applications use [WidgetsBinding], where this field is
-  /// set by the focus system (see `FocusManger`) on startup to a function that
+  /// set by the focus system (see `FocusManager`) on startup to a function that
   /// dispatches incoming events to the focus system, including
   /// `FocusNode.onKey`, `FocusNode.onKeyEvent`, and `Shortcuts`. In this case,
   /// the application does not need to read, assign, or invoke this value.
diff --git a/framework/lib/src/services/keyboard_inserted_content.dart b/framework/lib/src/services/keyboard_inserted_content.dart
new file mode 100644
index 0000000..4276539
--- /dev/null
+++ b/framework/lib/src/services/keyboard_inserted_content.dart
@@ -0,0 +1,58 @@
+// 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:flute/foundation.dart';
+
+/// A class representing rich content (such as a PNG image) inserted via the
+/// system input method.
+///
+/// The following data is represented in this class:
+///  - MIME Type
+///  - Bytes
+///  - URI
+@immutable
+class KeyboardInsertedContent {
+  /// Creates an object to represent content that is inserted from the virtual
+  /// keyboard.
+  ///
+  /// The mime type and URI will always be provided, but the bytedata may be null.
+  const KeyboardInsertedContent({required this.mimeType, required this.uri, this.data});
+
+  /// Converts JSON received from the Flutter Engine into the Dart class.
+  KeyboardInsertedContent.fromJson(Map<String, dynamic> metadata):
+      mimeType = metadata['mimeType'] as String,
+      uri = metadata['uri'] as String,
+      data = metadata['data'] != null
+          ? Uint8List.fromList(List<int>.from(metadata['data'] as Iterable<dynamic>))
+          : null;
+
+  /// The mime type of the inserted content.
+  final String mimeType;
+
+  /// The URI (location) of the inserted content, usually a "content://" URI.
+  final String uri;
+
+  /// The bytedata of the inserted content.
+  final Uint8List? data;
+
+  /// Convenience getter to check if bytedata is available for the inserted content.
+  bool get hasData => data?.isNotEmpty ?? false;
+
+  @override
+  String toString() => '${objectRuntimeType(this, 'KeyboardInsertedContent')}($mimeType, $uri, $data)';
+
+  @override
+  bool operator ==(Object other) {
+    if (other.runtimeType != runtimeType) {
+      return false;
+    }
+    return other is KeyboardInsertedContent
+        && other.mimeType == mimeType
+        && other.uri == uri
+        && other.data == data;
+  }
+
+  @override
+  int get hashCode => Object.hash(mimeType, uri, data);
+}
diff --git a/framework/lib/src/services/message_codec.dart b/framework/lib/src/services/message_codec.dart
index ede7d76..1816667 100644
--- a/framework/lib/src/services/message_codec.dart
+++ b/framework/lib/src/services/message_codec.dart
@@ -29,13 +29,12 @@
   T? decodeMessage(ByteData? message);
 }
 
-/// An command object representing the invocation of a named method.
+/// A command object representing the invocation of a named method.
 @immutable
 class MethodCall {
   /// Creates a [MethodCall] representing the invocation of [method] with the
   /// specified [arguments].
-  const MethodCall(this.method, [this.arguments])
-    : assert(method != null);
+  const MethodCall(this.method, [this.arguments]);
 
   /// The name of the method to be called.
   final String method;
@@ -114,7 +113,7 @@
     this.message,
     this.details,
     this.stacktrace,
-  }) : assert(code != null);
+  });
 
   /// An error code.
   final String code;
diff --git a/framework/lib/src/services/message_codecs.dart b/framework/lib/src/services/message_codecs.dart
index 1b09f28..0aa6d32 100644
--- a/framework/lib/src/services/message_codecs.dart
+++ b/framework/lib/src/services/message_codecs.dart
@@ -195,7 +195,6 @@
 
   @override
   ByteData encodeErrorEnvelope({ required String code, String? message, Object? details}) {
-    assert(code != null);
     return const JSONMessageCodec().encodeMessage(<Object?>[code, message, details])!;
   }
 }
diff --git a/framework/lib/src/services/mouse_cursor.dart b/framework/lib/src/services/mouse_cursor.dart
index 57ac614..6df02a0 100644
--- a/framework/lib/src/services/mouse_cursor.dart
+++ b/framework/lib/src/services/mouse_cursor.dart
@@ -104,9 +104,7 @@
   /// Create a session.
   ///
   /// All arguments must be non-null.
-  MouseCursorSession(this.cursor, this.device)
-    : assert(cursor != null),
-      assert(device != null);
+  MouseCursorSession(this.cursor, this.device);
 
   /// The cursor that created this session.
   final MouseCursor cursor;
@@ -215,7 +213,7 @@
   @override
   String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) {
     final String debugDescription = this.debugDescription;
-    if (minLevel.index >= DiagnosticLevel.info.index && debugDescription != null) {
+    if (minLevel.index >= DiagnosticLevel.info.index) {
       return debugDescription;
     }
     return super.toString(minLevel: minLevel);
@@ -262,7 +260,6 @@
   /// Returns the first cursor that is not a [MouseCursor.defer].
   static MouseCursor? firstNonDeferred(Iterable<MouseCursor> cursors) {
     for (final MouseCursor cursor in cursors) {
-      assert(cursor != null);
       if (cursor != MouseCursor.defer) {
         return cursor;
       }
@@ -358,7 +355,7 @@
   // the supported system cursors are enumerated in [SystemMouseCursors].
   const SystemMouseCursor._({
     required this.kind,
-  }) : assert(kind != null);
+  });
 
   /// A string that identifies the kind of the cursor.
   ///
diff --git a/framework/lib/src/services/mouse_tracking.dart b/framework/lib/src/services/mouse_tracking.dart
index 93ec4ff..e354e3e 100644
--- a/framework/lib/src/services/mouse_tracking.dart
+++ b/framework/lib/src/services/mouse_tracking.dart
@@ -51,7 +51,7 @@
     this.onExit,
     this.cursor = MouseCursor.defer,
     this.validForMouseTracker = true,
-  }) : assert(cursor != null);
+  });
 
   /// Triggered when a mouse pointer, with or without buttons pressed, has
   /// entered the region and [validForMouseTracker] is true.
diff --git a/framework/lib/src/services/platform_channel.dart b/framework/lib/src/services/platform_channel.dart
index 053fc7b..c556691 100644
--- a/framework/lib/src/services/platform_channel.dart
+++ b/framework/lib/src/services/platform_channel.dart
@@ -163,9 +163,7 @@
   /// The [name] and [codec] arguments cannot be null. The default [ServicesBinding.defaultBinaryMessenger]
   /// instance is used if [binaryMessenger] is null.
   const BasicMessageChannel(this.name, this.codec, { BinaryMessenger? binaryMessenger })
-      : assert(name != null),
-        assert(codec != null),
-        _binaryMessenger = binaryMessenger;
+      : _binaryMessenger = binaryMessenger;
 
   /// The logical channel on which communication happens, not null.
   final String name;
@@ -253,9 +251,7 @@
   /// The [name] and [codec] arguments cannot be null. The default [ServicesBinding.defaultBinaryMessenger]
   /// instance is used if [binaryMessenger] is null.
   const MethodChannel(this.name, [this.codec = const StandardMethodCodec(), BinaryMessenger? binaryMessenger ])
-      : assert(name != null),
-        assert(codec != null),
-        _binaryMessenger = binaryMessenger;
+      : _binaryMessenger = binaryMessenger;
 
   /// The logical channel on which communication happens, not null.
   final String name;
@@ -300,7 +296,6 @@
   /// nullable.
   @optionalTypeArgs
   Future<T?> _invokeMethod<T>(String method, { required bool missingOk, dynamic arguments }) async {
-    assert(method != null);
     final ByteData input = codec.encodeMethodCall(MethodCall(method, arguments));
     final ByteData? result =
       !kReleaseMode && debugProfilePlatformChannels ?
@@ -535,7 +530,7 @@
   /// Any other exception results in an error envelope being sent.
   void setMethodCallHandler(Future<dynamic> Function(MethodCall call)? handler) {
     assert(
-      _binaryMessenger != null || ServicesBinding.instance != null,
+      _binaryMessenger != null || BindingBase.debugBindingType() != null,
       'Cannot set the method call handler before the binary messenger has been initialized. '
       'This happens when you call setMethodCallHandler() before the WidgetsFlutterBinding '
       'has been initialized. You can fix this by either calling WidgetsFlutterBinding.ensureInitialized() '
@@ -609,9 +604,7 @@
   /// Neither [name] nor [codec] may be null. The default [ServicesBinding.defaultBinaryMessenger]
   /// instance is used if [binaryMessenger] is null.
   const EventChannel(this.name, [this.codec = const StandardMethodCodec(), BinaryMessenger? binaryMessenger])
-      : assert(name != null),
-        assert(codec != null),
-        _binaryMessenger = binaryMessenger;
+      : _binaryMessenger = binaryMessenger;
 
   /// The logical channel on which communication happens, not null.
   final String name;
diff --git a/framework/lib/src/services/platform_views.dart b/framework/lib/src/services/platform_views.dart
index bb919ab..6229559 100644
--- a/framework/lib/src/services/platform_views.dart
+++ b/framework/lib/src/services/platform_views.dart
@@ -119,9 +119,6 @@
     MessageCodec<dynamic>? creationParamsCodec,
     VoidCallback? onFocus,
   }) {
-    assert(id != null);
-    assert(viewType != null);
-    assert(layoutDirection != null);
     assert(creationParams == null || creationParamsCodec != null);
 
     final TextureAndroidViewController controller = TextureAndroidViewController._(
@@ -150,9 +147,6 @@
     MessageCodec<dynamic>? creationParamsCodec,
     VoidCallback? onFocus,
   }) {
-    assert(id != null);
-    assert(viewType != null);
-    assert(layoutDirection != null);
     assert(creationParams == null || creationParamsCodec != null);
 
     final SurfaceAndroidViewController controller = SurfaceAndroidViewController._(
@@ -222,9 +216,6 @@
     MessageCodec<dynamic>? creationParamsCodec,
     VoidCallback? onFocus,
   }) async {
-    assert(id != null);
-    assert(viewType != null);
-    assert(layoutDirection != null);
     assert(creationParams == null || creationParamsCodec != null);
 
     // TODO(amirh): pass layoutDirection once the system channel supports it.
@@ -258,8 +249,7 @@
   const AndroidPointerProperties({
     required this.id,
     required this.toolType,
-  }) : assert(id != null),
-       assert(toolType != null);
+  });
 
   /// See Android's [MotionEvent.PointerProperties#id](https://developer.android.com/reference/android/view/MotionEvent.PointerProperties.html#id).
   final int id;
@@ -308,15 +298,7 @@
     required this.touchMinor,
     required this.x,
     required this.y,
-  }) : assert(orientation != null),
-       assert(pressure != null),
-       assert(size != null),
-       assert(toolMajor != null),
-       assert(toolMinor != null),
-       assert(touchMajor != null),
-       assert(touchMinor != null),
-       assert(x != null),
-       assert(y != null);
+  });
 
   /// The orientation of the touch area and tool area in radians clockwise from vertical.
   ///
@@ -404,21 +386,7 @@
     required this.source,
     required this.flags,
     required this.motionEventId,
-  }) : assert(downTime != null),
-       assert(eventTime != null),
-       assert(action != null),
-       assert(pointerCount != null),
-       assert(pointerProperties != null),
-       assert(pointerCoords != null),
-       assert(metaState != null),
-       assert(buttonState != null),
-       assert(xPrecision != null),
-       assert(yPrecision != null),
-       assert(deviceId != null),
-       assert(edgeFlags != null),
-       assert(source != null),
-       assert(flags != null),
-       assert(pointerProperties.length == pointerCount),
+  }) : assert(pointerProperties.length == pointerCount),
        assert(pointerCoords.length == pointerCount);
 
   /// The time (in ms) when the user originally pressed down to start a stream of position events,
@@ -532,12 +500,7 @@
       <int, AndroidPointerProperties>{};
   final Set<int> usedAndroidPointerIds = <int>{};
 
-  PointTransformer get pointTransformer => _pointTransformer;
-  late PointTransformer _pointTransformer;
-  set pointTransformer(PointTransformer transformer) {
-    assert(transformer != null);
-    _pointTransformer = transformer;
-  }
+  late PointTransformer pointTransformer;
 
   int? downTimeMillis;
 
@@ -554,7 +517,7 @@
   }
 
   void updatePointerPositions(PointerEvent event) {
-    final Offset position = _pointTransformer(event.position);
+    final Offset position = pointTransformer(event.position);
     pointerPositions[event.pointer] = AndroidPointerCoords(
       orientation: event.orientation,
       pressure: event.pressure,
@@ -691,10 +654,7 @@
     required TextDirection layoutDirection,
     dynamic creationParams,
     MessageCodec<dynamic>? creationParamsCodec,
-  })  : assert(viewId != null),
-        assert(viewType != null),
-        assert(layoutDirection != null),
-        assert(creationParams == null || creationParamsCodec != null),
+  })  : assert(creationParams == null || creationParamsCodec != null),
         _viewType = viewType,
         _layoutDirection = layoutDirection,
         _creationParams = creationParams == null ? null : _CreationParams(creationParams, creationParamsCodec!);
@@ -755,7 +715,6 @@
       <PlatformViewCreatedCallback>[];
 
   static int _getAndroidDirection(TextDirection direction) {
-    assert(direction != null);
     switch (direction) {
       case TextDirection.ltr:
         return kAndroidLayoutDirectionLtr;
@@ -874,10 +833,9 @@
   ///
   /// This is required to convert a [PointerEvent] to an [AndroidMotionEvent].
   /// It is typically provided by using [RenderBox.globalToLocal].
-  PointTransformer get pointTransformer => _motionEventConverter._pointTransformer;
+  PointTransformer get pointTransformer => _motionEventConverter.pointTransformer;
   set pointTransformer(PointTransformer transformer) {
-    assert(transformer != null);
-    _motionEventConverter._pointTransformer = transformer;
+    _motionEventConverter.pointTransformer = transformer;
   }
 
   /// Whether the platform view has already been created.
@@ -886,14 +844,12 @@
   /// Adds a callback that will get invoke after the platform view has been
   /// created.
   void addOnPlatformViewCreatedListener(PlatformViewCreatedCallback listener) {
-    assert(listener != null);
     assert(_state != _AndroidViewState.disposed);
     _platformViewCreatedCallbacks.add(listener);
   }
 
   /// Removes a callback added with [addOnPlatformViewCreatedListener].
   void removeOnPlatformViewCreatedListener(PlatformViewCreatedCallback listener) {
-    assert(listener != null);
     assert(_state != _AndroidViewState.disposed);
     _platformViewCreatedCallbacks.remove(listener);
   }
@@ -914,7 +870,6 @@
       return;
     }
 
-    assert(layoutDirection != null);
     _layoutDirection = layoutDirection;
 
     // If the view was not yet created we just update _layoutDirection and return, as the new
@@ -1361,9 +1316,7 @@
   UiKitViewController._(
     this.id,
     TextDirection layoutDirection,
-  ) : assert(id != null),
-      assert(layoutDirection != null),
-      _layoutDirection = layoutDirection;
+  ) : _layoutDirection = layoutDirection;
 
 
   /// The unique identifier of the iOS view controlled by this controller.
@@ -1384,7 +1337,6 @@
       return;
     }
 
-    assert(layoutDirection != null);
     _layoutDirection = layoutDirection;
 
     // TODO(amirh): invoke the iOS platform views channel direction method once available.
diff --git a/framework/lib/src/services/raw_keyboard.dart b/framework/lib/src/services/raw_keyboard.dart
index 833b9f0..842af59 100644
--- a/framework/lib/src/services/raw_keyboard.dart
+++ b/framework/lib/src/services/raw_keyboard.dart
@@ -585,6 +585,24 @@
 /// These key events are typically only key events generated by a hardware
 /// keyboard, and not those from software keyboards or input method editors.
 ///
+/// ## Compared to [HardwareKeyboard]
+///
+/// [RawKeyboard] is the legacy API, and will be deprecated and removed in the
+/// future. It is recommended to always use [HardwareKeyboard] and [KeyEvent]
+/// APIs (such as [FocusNode.onKeyEvent]) to handle key events.
+///
+/// Behavior-wise, [RawKeyboard] provides a less unified, less regular
+/// event model than [HardwareKeyboard]. For example:
+///
+///  * Down events might not be matched with an up event, and vice versa (the
+///    set of pressed keys is silently updated).
+///  * The logical key of the down event might not be the same as that of the up
+///    event.
+///  * Down events and repeat events are not easily distinguishable (must be
+///    tracked manually).
+///  * Lock modes (such as CapsLock) only have their "enabled" state recorded.
+///    There's no way to acquire their pressing state.
+///
 /// See also:
 ///
 ///  * [RawKeyDownEvent] and [RawKeyUpEvent], the classes used to describe
@@ -592,6 +610,7 @@
 ///  * [RawKeyboardListener], a widget that listens for raw key events.
 ///  * [SystemChannels.keyEvent], the low-level channel used for receiving
 ///    events from the system.
+///  * [HardwareKeyboard], the recommended replacement.
 class RawKeyboard {
   RawKeyboard._();
 
diff --git a/framework/lib/src/services/raw_keyboard_android.dart b/framework/lib/src/services/raw_keyboard_android.dart
index 0be9ada..11877c5 100644
--- a/framework/lib/src/services/raw_keyboard_android.dart
+++ b/framework/lib/src/services/raw_keyboard_android.dart
@@ -42,11 +42,7 @@
     this.productId = 0,
     this.deviceId = 0,
     this.repeatCount = 0,
-  }) : assert(flags != null),
-       assert(codePoint != null),
-       assert(keyCode != null),
-       assert(scanCode != null),
-       assert(metaState != null);
+  });
 
   /// The current set of additional flags for this event.
   ///
@@ -227,7 +223,6 @@
 
   @override
   bool isModifierPressed(ModifierKey key, { KeyboardSide side = KeyboardSide.any }) {
-    assert(side != null);
     switch (key) {
       case ModifierKey.controlModifier:
         return _isLeftRightModifierPressed(side, modifierControl, modifierLeftControl, modifierRightControl);
diff --git a/framework/lib/src/services/raw_keyboard_fuchsia.dart b/framework/lib/src/services/raw_keyboard_fuchsia.dart
index f4feeba..4dd715b 100644
--- a/framework/lib/src/services/raw_keyboard_fuchsia.dart
+++ b/framework/lib/src/services/raw_keyboard_fuchsia.dart
@@ -28,9 +28,7 @@
     this.hidUsage = 0,
     this.codePoint = 0,
     this.modifiers = 0,
-  }) : assert(hidUsage != null),
-       assert(codePoint != null),
-       assert(modifiers != null);
+  });
 
   /// The USB HID usage.
   ///
@@ -107,7 +105,6 @@
 
   @override
   bool isModifierPressed(ModifierKey key, { KeyboardSide side = KeyboardSide.any }) {
-    assert(side != null);
     switch (key) {
       case ModifierKey.controlModifier:
         return _isLeftRightModifierPressed(side, modifierControl, modifierLeftControl, modifierRightControl);
diff --git a/framework/lib/src/services/raw_keyboard_ios.dart b/framework/lib/src/services/raw_keyboard_ios.dart
index 426f077..e351df3 100644
--- a/framework/lib/src/services/raw_keyboard_ios.dart
+++ b/framework/lib/src/services/raw_keyboard_ios.dart
@@ -30,10 +30,7 @@
     this.charactersIgnoringModifiers = '',
     this.keyCode = 0,
     this.modifiers = 0,
-  }) : assert(characters != null),
-       assert(charactersIgnoringModifiers != null),
-       assert(keyCode != null),
-       assert(modifiers != null);
+  });
 
   /// The Unicode characters associated with a key-up or key-down event.
   ///
diff --git a/framework/lib/src/services/raw_keyboard_linux.dart b/framework/lib/src/services/raw_keyboard_linux.dart
index 009728d..46ec9fb 100644
--- a/framework/lib/src/services/raw_keyboard_linux.dart
+++ b/framework/lib/src/services/raw_keyboard_linux.dart
@@ -33,12 +33,7 @@
     this.modifiers = 0,
     required this.isDown,
     this.specifiedLogicalKey,
-  }) : assert(scanCode != null),
-       assert(unicodeScalarValues != null),
-       assert((unicodeScalarValues & ~LogicalKeyboardKey.valueMask) == 0),
-       assert(keyCode != null),
-       assert(modifiers != null),
-       assert(keyHelper != null);
+  }) : assert((unicodeScalarValues & ~LogicalKeyboardKey.valueMask) == 0);
 
   /// A helper class that abstracts the fetching of the toolkit-specific mappings.
   ///
diff --git a/framework/lib/src/services/raw_keyboard_macos.dart b/framework/lib/src/services/raw_keyboard_macos.dart
index 4dd0ab2..becbac4 100644
--- a/framework/lib/src/services/raw_keyboard_macos.dart
+++ b/framework/lib/src/services/raw_keyboard_macos.dart
@@ -42,10 +42,7 @@
     this.keyCode = 0,
     this.modifiers = 0,
     this.specifiedLogicalKey,
-  }) : assert(characters != null),
-       assert(charactersIgnoringModifiers != null),
-       assert(keyCode != null),
-       assert(modifiers != null);
+  });
 
   /// The Unicode characters associated with a key-up or key-down event.
   ///
diff --git a/framework/lib/src/services/raw_keyboard_web.dart b/framework/lib/src/services/raw_keyboard_web.dart
index cd3a8a2..9d313a3 100644
--- a/framework/lib/src/services/raw_keyboard_web.dart
+++ b/framework/lib/src/services/raw_keyboard_web.dart
@@ -35,8 +35,7 @@
     this.location = 0,
     this.metaState = modifierNone,
     this.keyCode = 0,
-  })  : assert(code != null),
-        assert(metaState != null);
+  });
 
   /// The `KeyboardEvent.code` corresponding to this event.
   ///
diff --git a/framework/lib/src/services/raw_keyboard_windows.dart b/framework/lib/src/services/raw_keyboard_windows.dart
index 5a6c477..4d2ef1f 100644
--- a/framework/lib/src/services/raw_keyboard_windows.dart
+++ b/framework/lib/src/services/raw_keyboard_windows.dart
@@ -35,10 +35,7 @@
     this.scanCode = 0,
     this.characterCodePoint = 0,
     this.modifiers = 0,
-  }) : assert(keyCode != null),
-       assert(scanCode != null),
-       assert(characterCodePoint != null),
-       assert(modifiers != null);
+  });
 
   /// The hardware key code corresponding to this key event.
   ///
diff --git a/framework/lib/src/services/restoration.dart b/framework/lib/src/services/restoration.dart
index 24f5074..073026b 100644
--- a/framework/lib/src/services/restoration.dart
+++ b/framework/lib/src/services/restoration.dart
@@ -259,7 +259,6 @@
   /// called.
   @protected
   void handleRestorationUpdateFromEngine({required bool enabled, required Uint8List? data}) {
-    assert(enabled != null);
     assert(enabled || data == null);
 
     _isReplacing = _rootBucketIsValid && enabled;
@@ -297,7 +296,6 @@
   /// by the data.
   @protected
   Future<void> sendToEngine(Uint8List encodedData) {
-    assert(encodedData != null);
     return SystemChannels.restoration.invokeMethod<void>(
       'put',
       encodedData,
@@ -344,7 +342,6 @@
   @protected
   @visibleForTesting
   void scheduleSerializationFor(RestorationBucket bucket) {
-    assert(bucket != null);
     assert(bucket._manager == this);
     assert(!_debugDoingUpdate);
     _bucketsNeedingSerialization.add(bucket);
@@ -366,7 +363,6 @@
   @protected
   @visibleForTesting
   void unscheduleSerializationFor(RestorationBucket bucket) {
-    assert(bucket != null);
     assert(bucket._manager == this);
     assert(!_debugDoingUpdate);
     _bucketsNeedingSerialization.remove(bucket);
@@ -502,8 +498,7 @@
   RestorationBucket.empty({
     required String restorationId,
     required Object? debugOwner,
-  }) : assert(restorationId != null),
-       _restorationId = restorationId,
+  }) : _restorationId = restorationId,
        _rawData = <String, Object?>{} {
     assert(() {
       _debugOwner = debugOwner;
@@ -537,8 +532,7 @@
   RestorationBucket.root({
     required RestorationManager manager,
     required Map<Object?, Object?>? rawData,
-  }) : assert(manager != null),
-       _manager = manager,
+  }) : _manager = manager,
        _rawData = rawData ?? <Object?, Object?>{},
        _restorationId = 'root' {
     assert(() {
@@ -561,9 +555,7 @@
     required String restorationId,
     required RestorationBucket parent,
     required Object? debugOwner,
-  }) : assert(restorationId != null),
-       assert(parent != null),
-       assert(parent._rawChildren[restorationId] != null),
+  }) : assert(parent._rawChildren[restorationId] != null),
        _manager = parent._manager,
        _parent = parent,
        _rawData = parent._rawChildren[restorationId]! as Map<Object?, Object?>,
@@ -635,7 +627,6 @@
   ///    restoration ID.
   P? read<P>(String restorationId) {
     assert(_debugAssertNotDisposed());
-    assert(restorationId != null);
     return _rawValues[restorationId] as P?;
   }
 
@@ -657,7 +648,6 @@
   ///    restoration ID.
   void write<P>(String restorationId, P value) {
     assert(_debugAssertNotDisposed());
-    assert(restorationId != null);
     assert(debugIsSerializableForRestoration(value));
     if (_rawValues[restorationId] != value || !_rawValues.containsKey(restorationId)) {
       _rawValues[restorationId] = value;
@@ -679,7 +669,6 @@
   ///    restoration ID.
   P? remove<P>(String restorationId) {
     assert(_debugAssertNotDisposed());
-    assert(restorationId != null);
     final bool needsUpdate = _rawValues.containsKey(restorationId);
     final P? result = _rawValues.remove(restorationId) as P?;
     if (_rawValues.isEmpty) {
@@ -701,7 +690,6 @@
   ///  * [remove], which removes a value from the bucket.
   bool contains(String restorationId) {
     assert(_debugAssertNotDisposed());
-    assert(restorationId != null);
     return _rawValues.containsKey(restorationId);
   }
 
@@ -737,7 +725,6 @@
   /// delete the information stored in it from the app's restoration data.
   RestorationBucket claimChild(String restorationId, {required Object? debugOwner}) {
     assert(_debugAssertNotDisposed());
-    assert(restorationId != null);
     // There are three cases to consider:
     // 1. Claiming an ID that has already been claimed.
     // 2. Claiming an ID that doesn't yet exist in [_rawChildren].
@@ -787,7 +774,6 @@
   /// No-op if the provided bucket is already a child of this bucket.
   void adoptChild(RestorationBucket child) {
     assert(_debugAssertNotDisposed());
-    assert(child != null);
     if (child._parent != this) {
       child._parent?._removeChildData(child);
       child._parent = this;
@@ -801,7 +787,6 @@
   }
 
   void _dropChild(RestorationBucket child) {
-    assert(child != null);
     assert(child._parent == this);
     _removeChildData(child);
     child._parent = null;
@@ -876,7 +861,6 @@
   }
 
   void _removeChildData(RestorationBucket child) {
-    assert(child != null);
     assert(child._parent == this);
     if (_claimedChildren.remove(child.restorationId) == child) {
       _rawChildren.remove(child.restorationId);
@@ -901,7 +885,6 @@
   }
 
   void _addChildData(RestorationBucket child) {
-    assert(child != null);
     assert(child._parent == this);
     if (_claimedChildren.containsKey(child.restorationId)) {
       // Delay addition until the end of the frame in the hopes that the current
@@ -944,7 +927,6 @@
   /// another ID, or has moved it to a new parent via [adoptChild].
   void rename(String newRestorationId) {
     assert(_debugAssertNotDisposed());
-    assert(newRestorationId != null);
     if (newRestorationId == restorationId) {
       return;
     }
diff --git a/framework/lib/src/services/spell_check.dart b/framework/lib/src/services/spell_check.dart
index 1de1c34..da7055c 100644
--- a/framework/lib/src/services/spell_check.dart
+++ b/framework/lib/src/services/spell_check.dart
@@ -27,9 +27,7 @@
   ///
   /// The [range] and replacement [suggestions] must all not
   /// be null.
-  const SuggestionSpan(this.range, this.suggestions)
-      : assert(range != null),
-        assert(suggestions != null);
+  const SuggestionSpan(this.range, this.suggestions);
 
   /// The misspelled range of text.
   final TextRange range;
@@ -58,9 +56,7 @@
 @immutable
 class SpellCheckResults {
   /// Creates results based off those received by spell checking some text input.
-  const SpellCheckResults(this.spellCheckedText, this.suggestionSpans)
-      : assert(spellCheckedText != null),
-        assert(suggestionSpans != null);
+  const SpellCheckResults(this.spellCheckedText, this.suggestionSpans);
 
   /// The text that the [suggestionSpans] correspond to.
   final String spellCheckedText;
@@ -170,8 +166,6 @@
   @override
   Future<List<SuggestionSpan>?> fetchSpellCheckSuggestions(
       Locale locale, String text) async {
-    assert(locale != null);
-    assert(text != null);
 
     final List<dynamic> rawResults;
     final String languageTag = locale.toLanguageTag();
diff --git a/framework/lib/src/services/system_channels.dart b/framework/lib/src/services/system_channels.dart
index 77d3daf..c6e4b18 100644
--- a/framework/lib/src/services/system_channels.dart
+++ b/framework/lib/src/services/system_channels.dart
@@ -465,4 +465,17 @@
   ///
   ///  * [DefaultPlatformMenuDelegate], which uses this channel.
   static const MethodChannel menu = OptionalMethodChannel('flutter/menu');
+
+  /// A [MethodChannel] for configuring the browser's context menu on web.
+  ///
+  /// The following outgoing methods are defined for this channel (invoked using
+  /// [OptionalMethodChannel.invokeMethod]):
+  ///
+  ///  * `enableContextMenu`: enables the browser's context menu. When a Flutter
+  ///    app starts, the browser's context menu is already enabled.
+  ///  * `disableContextMenu`: disables the browser's context menu.
+  static const MethodChannel contextMenu = OptionalMethodChannel(
+    'flutter/contextmenu',
+    JSONMethodCodec(),
+  );
 }
diff --git a/framework/lib/src/services/system_chrome.dart b/framework/lib/src/services/system_chrome.dart
index c213d72..e37f78b 100644
--- a/framework/lib/src/services/system_chrome.dart
+++ b/framework/lib/src/services/system_chrome.dart
@@ -577,7 +577,6 @@
   ///
   ///  * [AnnotatedRegion], the widget used to place data into the layer tree.
   static void setSystemUIOverlayStyle(SystemUiOverlayStyle style) {
-    assert(style != null);
     if (_pendingStyle != null) {
       // The microtask has already been queued; just update the pending value.
       _pendingStyle = style;
diff --git a/framework/lib/src/services/system_navigator.dart b/framework/lib/src/services/system_navigator.dart
index 1c3d05d..3d1a5b5 100644
--- a/framework/lib/src/services/system_navigator.dart
+++ b/framework/lib/src/services/system_navigator.dart
@@ -2,7 +2,6 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-
 import 'system_channels.dart';
 
 /// Controls specific aspects of the system navigation stack.
@@ -98,28 +97,4 @@
       },
     );
   }
-
-  /// Notifies the platform of a route change, and selects single-entry history
-  /// mode.
-  ///
-  /// This is equivalent to calling [selectSingleEntryHistory] and
-  /// [routeInformationUpdated] together.
-  ///
-  /// The `previousRouteName` argument is ignored.
-  @Deprecated(
-    'Use routeInformationUpdated instead. '
-    'This feature was deprecated after v2.3.0-1.0.pre.'
-  )
-  static Future<void> routeUpdated({
-    String? routeName,
-    String? previousRouteName,
-  }) {
-    return SystemChannels.navigation.invokeMethod<void>(
-      'routeUpdated',
-      <String, dynamic>{
-        'previousRouteName': previousRouteName,
-        'routeName': routeName,
-      },
-    );
-  }
 }
diff --git a/framework/lib/src/services/text_boundary.dart b/framework/lib/src/services/text_boundary.dart
index c35b071..efbea24 100644
--- a/framework/lib/src/services/text_boundary.dart
+++ b/framework/lib/src/services/text_boundary.dart
@@ -2,60 +2,68 @@
 // 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:engine/ui.dart';
+import 'dart:math';
 
 import 'package:characters/characters.dart' show CharacterRange;
 
 import 'text_layout_metrics.dart';
 
-/// An interface for retrieving the logical text boundary (left-closed-right-open)
-/// at a given location in a document.
+// Examples can assume:
+// late TextLayoutMetrics textLayout;
+// late TextSpan text;
+// bool isWhitespace(int? codeUnit) => true;
+
+/// Signature for a predicate that takes an offset into a UTF-16 string, and a
+/// boolean that indicates the search direction.
+typedef UntilPredicate = bool Function(int offset, bool forward);
+
+/// An interface for retrieving the logical text boundary (as opposed to the
+/// visual boundary) at a given code unit offset in a document.
 ///
-/// The input [TextPosition] points to a position between 2 code units (which
-/// can be visually represented by the caret if the selection were to collapse
-/// to that position). The [TextPosition.affinity] is used to determine which
-/// code unit it points. For example, `TextPosition(i, upstream)` points to
-/// code unit `i - 1` and `TextPosition(i, downstream)` points to code unit `i`.
+/// Either the [getTextBoundaryAt] method, or both the
+/// [getLeadingTextBoundaryAt] method and the [getTrailingTextBoundaryAt] method
+/// must be implemented.
 abstract class TextBoundary {
   /// A constant constructor to enable subclass override.
   const TextBoundary();
 
-  /// Returns the leading text boundary at the given location.
+  /// Returns the offset of the closest text boundary before or at the given
+  /// `position`, or null if no boundaries can be found.
   ///
-  /// The return value must be less or equal to the input position.
-  TextPosition getLeadingTextBoundaryAt(TextPosition position);
-
-  /// Returns the trailing text boundary at the given location, exclusive.
-  ///
-  /// The return value must be greater or equal to the input position.
-  TextPosition getTrailingTextBoundaryAt(TextPosition position);
-
-  /// Gets the text boundary range that encloses the input position.
-  TextRange getTextBoundaryAt(TextPosition position) {
-    return TextRange(
-      start: getLeadingTextBoundaryAt(position).offset,
-      end: getTrailingTextBoundaryAt(position).offset,
-    );
+  /// The return value, if not null, is usually less than or equal to `position`.
+  int? getLeadingTextBoundaryAt(int position) {
+    if (position < 0) {
+      return null;
+    }
+    final int start = getTextBoundaryAt(position).start;
+    return start >= 0 ? start : null;
   }
 
-  /// Gets the boundary by calling the left-hand side and pipe the result to
-  /// right-hand side.
+  /// Returns the offset of the closest text boundaries after the given `position`,
+  /// or null if there is no boundaries can be found after `position`.
   ///
-  /// Combining two text boundaries can be useful if one wants to ignore certain
-  /// text before finding the text boundary. For example, use
-  /// [WhitespaceBoundary] + [WordBoundary] to ignores any white space before
-  /// finding word boundary if the input position happens to be a whitespace
-  /// character.
-  TextBoundary operator +(TextBoundary other) {
-    return _ExpandedTextBoundary(inner: other, outer: this);
+  /// The return value, if not null, is usually greater than `position`.
+  int? getTrailingTextBoundaryAt(int position) {
+    final int end = getTextBoundaryAt(max(0, position)).end;
+    return end >= 0 ? end : null;
+  }
+
+  /// Returns the text boundary range that encloses the input position.
+  ///
+  /// The returned [TextRange] may contain `-1`, which indicates no boundaries
+  /// can be found in that direction.
+  TextRange getTextBoundaryAt(int position) {
+    final int start = getLeadingTextBoundaryAt(position) ?? -1;
+    final int end = getTrailingTextBoundaryAt(position) ?? -1;
+    return TextRange(start: start, end: end);
   }
 }
 
-/// A text boundary that uses characters as logical boundaries.
+/// A [TextBoundary] subclass for retriving the range of the grapheme the given
+/// `position` is in.
 ///
-/// This class takes grapheme clusters into account and avoid creating
-/// boundaries that generate malformed utf-16 characters.
+/// The class is implemented using the
+/// [characters](https://pub.dev/packages/characters) package.
 class CharacterBoundary extends TextBoundary {
   /// Creates a [CharacterBoundary] with the text.
   const CharacterBoundary(this._text);
@@ -63,286 +71,145 @@
   final String _text;
 
   @override
-  TextPosition getLeadingTextBoundaryAt(TextPosition position) {
-    if (position.offset <= 0) {
-      return const TextPosition(offset: 0);
+  int? getLeadingTextBoundaryAt(int position) {
+    if (position < 0) {
+      return null;
     }
-    if (position.offset > _text.length ||
-        (position.offset == _text.length && position.affinity == TextAffinity.downstream)) {
-      return TextPosition(offset: _text.length, affinity: TextAffinity.upstream);
-    }
-    final int endOffset;
-    final int startOffset;
-    switch (position.affinity) {
-      case TextAffinity.upstream:
-        startOffset = math.min(position.offset - 1, _text.length);
-        endOffset = math.min(position.offset, _text.length);
-        break;
-      case TextAffinity.downstream:
-        startOffset = math.min(position.offset, _text.length);
-        endOffset = math.min(position.offset + 1, _text.length);
-        break;
-    }
-    return TextPosition(
-      offset: CharacterRange.at(_text, startOffset, endOffset).stringBeforeLength,
-    );
+    final int graphemeStart = CharacterRange.at(_text, min(position, _text.length)).stringBeforeLength;
+    assert(CharacterRange.at(_text, graphemeStart).isEmpty);
+    return graphemeStart;
   }
 
   @override
-  TextPosition getTrailingTextBoundaryAt(TextPosition position) {
-    if (position.offset < 0 ||
-        (position.offset == 0 && position.affinity == TextAffinity.upstream)) {
-      return const TextPosition(offset: 0);
+  int? getTrailingTextBoundaryAt(int position) {
+    if (position >= _text.length) {
+      return null;
     }
-    if (position.offset >= _text.length) {
-      return TextPosition(offset: _text.length, affinity: TextAffinity.upstream);
+    final CharacterRange rangeAtPosition = CharacterRange.at(_text, max(0, position + 1));
+    final int nextBoundary = rangeAtPosition.stringBeforeLength + rangeAtPosition.current.length;
+    assert(nextBoundary == _text.length || CharacterRange.at(_text, nextBoundary).isEmpty);
+    return nextBoundary;
+  }
+
+  @override
+  TextRange getTextBoundaryAt(int position) {
+    if (position < 0) {
+      return TextRange(start: -1, end: getTrailingTextBoundaryAt(position) ?? -1);
+    } else if (position >= _text.length) {
+      return TextRange(start: getLeadingTextBoundaryAt(position) ?? -1, end: -1);
     }
-    final int endOffset;
-    final int startOffset;
-    switch (position.affinity) {
-      case TextAffinity.upstream:
-        startOffset = math.min(position.offset - 1, _text.length);
-        endOffset = math.min(position.offset, _text.length);
-        break;
-      case TextAffinity.downstream:
-        startOffset = math.min(position.offset, _text.length);
-        endOffset = math.min(position.offset + 1, _text.length);
-        break;
-    }
-    final CharacterRange range = CharacterRange.at(_text, startOffset, endOffset);
-    return TextPosition(
-      offset: _text.length - range.stringAfterLength,
-      affinity: TextAffinity.upstream,
-    );
+    final CharacterRange rangeAtPosition = CharacterRange.at(_text, position);
+    return rangeAtPosition.isNotEmpty
+      ? TextRange(start: rangeAtPosition.stringBeforeLength, end: rangeAtPosition.stringBeforeLength + rangeAtPosition.current.length)
+      // rangeAtPosition is empty means `position` is a grapheme boundary.
+      : TextRange(start: rangeAtPosition.stringBeforeLength, end: getTrailingTextBoundaryAt(position) ?? -1);
   }
 }
 
-/// A text boundary that uses words as logical boundaries.
+/// A [TextBoundary] subclass for locating closest line breaks to a given
+/// `position`.
 ///
-/// This class uses [UAX #29](https://unicode.org/reports/tr29/) defined word
-/// boundaries to calculate its logical boundaries.
-class WordBoundary extends TextBoundary {
-  /// Creates a [WordBoundary] with the text and layout information.
-  const WordBoundary(this._textLayout);
+/// When the given `position` points to a hard line break, the returned range
+/// is the line's content range before the hard line break, and does not contain
+/// the given `position`. For instance, the line breaks at `position = 1` for
+/// "a\nb" is `[0, 1)`, which does not contain the position `1`.
+class LineBoundary extends TextBoundary {
+  /// Creates a [LineBoundary] with the text and layout information.
+  const LineBoundary(this._textLayout);
 
   final TextLayoutMetrics _textLayout;
 
   @override
-  TextPosition getLeadingTextBoundaryAt(TextPosition position) {
-    return TextPosition(
-      offset: _textLayout.getWordBoundary(position).start,
-      affinity: TextAffinity.downstream,  // ignore: avoid_redundant_argument_values
-    );
-  }
-
-  @override
-  TextPosition getTrailingTextBoundaryAt(TextPosition position) {
-    return TextPosition(
-      offset: _textLayout.getWordBoundary(position).end,
-      affinity: TextAffinity.upstream,
-    );
-  }
-
-  @override
-  TextRange getTextBoundaryAt(TextPosition position) {
-    return _textLayout.getWordBoundary(position);
-  }
+  TextRange getTextBoundaryAt(int position) => _textLayout.getLineAtOffset(TextPosition(offset: max(position, 0)));
 }
 
-/// A text boundary that uses line breaks as logical boundaries.
+/// A text boundary that uses paragraphs as logical boundaries.
 ///
-/// The input [TextPosition]s will be interpreted as caret locations if
-/// [TextLayoutMetrics.getLineAtOffset] is text-affinity-aware.
-class LineBreak extends TextBoundary {
-  /// Creates a [LineBreak] with the text and layout information.
-  const LineBreak(this._textLayout);
+/// A paragraph is defined as the range between line terminators. If no
+/// line terminators exist then the paragraph boundary is the entire document.
+class ParagraphBoundary extends TextBoundary {
+  /// Creates a [ParagraphBoundary] with the text.
+  const ParagraphBoundary(this._text);
 
-  final TextLayoutMetrics _textLayout;
+  final String _text;
 
+  /// Returns the [int] representing the start position of the paragraph that
+  /// bounds the given `position`. The returned [int] is the position of the code unit
+  /// that follows the line terminator that encloses the desired paragraph.
   @override
-  TextPosition getLeadingTextBoundaryAt(TextPosition position) {
-    return TextPosition(
-      offset: _textLayout.getLineAtOffset(position).start,
-    );
+  int? getLeadingTextBoundaryAt(int position) {
+    if (position < 0 || _text.isEmpty) {
+      return null;
+    }
+
+    if (position >= _text.length) {
+      return _text.length;
+    }
+
+    if (position == 0) {
+      return 0;
+    }
+
+    final List<int> codeUnits = _text.codeUnits;
+    int index = position;
+
+    if (index > 1 && codeUnits[index] == 0xA && codeUnits[index - 1] == 0xD) {
+      index -= 2;
+    } else if (TextLayoutMetrics.isLineTerminator(codeUnits[index])) {
+      index -= 1;
+    }
+
+    while (index > 0) {
+      if (TextLayoutMetrics.isLineTerminator(codeUnits[index])) {
+        return index + 1;
+      }
+      index -= 1;
+    }
+
+    return max(index, 0);
   }
 
+  /// Returns the [int] representing the end position of the paragraph that
+  /// bounds the given `position`. The returned [int] is the position of the
+  /// code unit representing the trailing line terminator that encloses the
+  /// desired paragraph.
   @override
-  TextPosition getTrailingTextBoundaryAt(TextPosition position) {
-    return TextPosition(
-      offset: _textLayout.getLineAtOffset(position).end,
-      affinity: TextAffinity.upstream,
-    );
-  }
+  int? getTrailingTextBoundaryAt(int position) {
+    if (position >= _text.length || _text.isEmpty) {
+      return null;
+    }
 
-  @override
-  TextRange getTextBoundaryAt(TextPosition position) {
-    return _textLayout.getLineAtOffset(position);
+    if (position < 0) {
+      return 0;
+    }
+
+    final List<int> codeUnits = _text.codeUnits;
+    int index = position;
+
+    while (!TextLayoutMetrics.isLineTerminator(codeUnits[index])) {
+      index += 1;
+      if (index == codeUnits.length) {
+        return index;
+      }
+    }
+
+    return index < codeUnits.length - 1
+                && codeUnits[index] == 0xD
+                && codeUnits[index + 1] == 0xA
+                ? index + 2
+                : index + 1;
   }
 }
 
 /// A text boundary that uses the entire document as logical boundary.
-///
-/// The document boundary is unique and is a constant function of the input
-/// position.
 class DocumentBoundary extends TextBoundary {
-  /// Creates a [DocumentBoundary] with the text
+  /// Creates a [DocumentBoundary] with the text.
   const DocumentBoundary(this._text);
 
   final String _text;
 
   @override
-  TextPosition getLeadingTextBoundaryAt(TextPosition position) => const TextPosition(offset: 0);
+  int? getLeadingTextBoundaryAt(int position) => position < 0 ? null : 0;
   @override
-  TextPosition getTrailingTextBoundaryAt(TextPosition position) {
-    return TextPosition(
-      offset: _text.length,
-      affinity: TextAffinity.upstream,
-    );
-  }
-}
-
-/// A text boundary that uses the first non-whitespace character as the logical
-/// boundary.
-///
-/// This text boundary uses [TextLayoutMetrics.isWhitespace] to identify white
-/// spaces, this includes newline characters from ASCII and separators from the
-/// [unicode separator category](https://en.wikipedia.org/wiki/Whitespace_character).
-class WhitespaceBoundary extends TextBoundary {
-  /// Creates a [WhitespaceBoundary] with the text.
-  const WhitespaceBoundary(this._text);
-
-  final String _text;
-
-  @override
-  TextPosition getLeadingTextBoundaryAt(TextPosition position) {
-    // Handles outside of string end.
-    if (position.offset > _text.length || (position.offset == _text.length  && position.affinity == TextAffinity.downstream)) {
-      position = TextPosition(offset: _text.length, affinity: TextAffinity.upstream);
-    }
-    // Handles outside of string start.
-    if (position.offset <= 0) {
-      return const TextPosition(offset: 0);
-    }
-    int index = position.offset;
-    if (position.affinity == TextAffinity.downstream && !TextLayoutMetrics.isWhitespace(_text.codeUnitAt(index))) {
-      return position;
-    }
-
-    while ((index -= 1) >= 0) {
-      if (!TextLayoutMetrics.isWhitespace(_text.codeUnitAt(index))) {
-        return TextPosition(offset: index + 1, affinity: TextAffinity.upstream);
-      }
-    }
-    return const TextPosition(offset: 0);
-  }
-
-  @override
-  TextPosition getTrailingTextBoundaryAt(TextPosition position) {
-    // Handles outside of right bound.
-    if (position.offset >= _text.length) {
-      return TextPosition(offset: _text.length, affinity: TextAffinity.upstream);
-    }
-    // Handles outside of left bound.
-    if (position.offset < 0 || (position.offset == 0 && position.affinity == TextAffinity.upstream)) {
-      position = const TextPosition(offset: 0);
-    }
-
-    int index = position.offset;
-    if (position.affinity == TextAffinity.upstream && !TextLayoutMetrics.isWhitespace(_text.codeUnitAt(index - 1))) {
-      return position;
-    }
-
-    for (; index < _text.length; index += 1) {
-      if (!TextLayoutMetrics.isWhitespace(_text.codeUnitAt(index))) {
-        return TextPosition(offset: index);
-      }
-    }
-    return TextPosition(offset: _text.length, affinity: TextAffinity.upstream);
-  }
-}
-
-/// Gets the boundary by calling the [outer] and pipe the result to
-/// [inner].
-class _ExpandedTextBoundary extends TextBoundary {
-  /// Creates a [_ExpandedTextBoundary] with inner and outter boundaries
-  const _ExpandedTextBoundary({required this.inner, required this.outer});
-
-  /// The inner boundary to call with the result from [outer].
-  final TextBoundary inner;
-
-  /// The outer boundary to call with the input position.
-  ///
-  /// The result is piped to the [inner] before returning to the caller.
-  final TextBoundary outer;
-
-  @override
-  TextPosition getLeadingTextBoundaryAt(TextPosition position) {
-    return inner.getLeadingTextBoundaryAt(
-      outer.getLeadingTextBoundaryAt(position),
-    );
-  }
-
-  @override
-  TextPosition getTrailingTextBoundaryAt(TextPosition position) {
-    return inner.getTrailingTextBoundaryAt(
-      outer.getTrailingTextBoundaryAt(position),
-    );
-  }
-}
-
-/// A text boundary that will push input text position forward or backward
-/// one affinity
-///
-/// To push a text position forward one affinity unit, this proxy converts
-/// affinity to downstream if it is upstream; otherwise it increase the offset
-/// by one with its affinity sets to upstream. For example,
-/// `TextPosition(1, upstream)` becomes `TextPosition(1, downstream)`,
-/// `TextPosition(4, downstream)` becomes `TextPosition(5, upstream)`.
-///
-/// See also:
-/// * [PushTextPosition.forward], a text boundary to push the input position
-///   forward.
-/// * [PushTextPosition.backward], a text boundary to push the input position
-///   backward.
-class PushTextPosition extends TextBoundary {
-  const PushTextPosition._(this._forward);
-
-  /// A text boundary that pushes the input position forward.
-  static const TextBoundary forward = PushTextPosition._(true);
-
-  /// A text boundary that pushes the input position backward.
-  static const TextBoundary backward = PushTextPosition._(false);
-
-  /// Whether to push the input position forward or backward.
-  final bool _forward;
-
-  TextPosition _calculateTargetPosition(TextPosition position) {
-    if (_forward) {
-      switch(position.affinity) {
-        case TextAffinity.upstream:
-          return TextPosition(offset: position.offset);
-        case TextAffinity.downstream:
-          return position = TextPosition(
-            offset: position.offset + 1,
-            affinity: TextAffinity.upstream,
-          );
-      }
-    } else {
-      switch(position.affinity) {
-        case TextAffinity.upstream:
-          return position = TextPosition(offset: position.offset - 1);
-        case TextAffinity.downstream:
-          return TextPosition(
-            offset: position.offset,
-            affinity: TextAffinity.upstream,
-          );
-      }
-    }
-  }
-
-  @override
-  TextPosition getLeadingTextBoundaryAt(TextPosition position) => _calculateTargetPosition(position);
-
-  @override
-  TextPosition getTrailingTextBoundaryAt(TextPosition position) => _calculateTargetPosition(position);
+  int? getTrailingTextBoundaryAt(int position) => position >= _text.length ? null : _text.length;
 }
diff --git a/framework/lib/src/services/text_editing_delta.dart b/framework/lib/src/services/text_editing_delta.dart
index a9d40ea..91872c6 100644
--- a/framework/lib/src/services/text_editing_delta.dart
+++ b/framework/lib/src/services/text_editing_delta.dart
@@ -64,9 +64,7 @@
     required this.oldText,
     required this.selection,
     required this.composing,
-  }) : assert(oldText != null),
-       assert(selection != null),
-       assert(composing != null);
+  });
 
   /// Creates an instance of this class from a JSON object by inferring the
   /// type of delta based on values sent from the engine.
@@ -95,7 +93,7 @@
     // 'world'{replacementDestinationEnd, replacementDestinationStart + replacementSourceEnd}
     // can be considered an insertion. In this case we inserted 'd'.
     //
-    // Similarly for a a deletion, say we are currently composing the word: 'worl'.
+    // Similarly for a deletion, say we are currently composing the word: 'worl'.
     // Our current state is 'world|' with the cursor at the end of 'd'. If we
     // press backspace to delete the character 'd', the platform will tell us 'world'
     // was replaced with 'worl' at range (0,5). Here we can check if the text found
diff --git a/framework/lib/src/services/text_formatter.dart b/framework/lib/src/services/text_formatter.dart
index 8e5c647..c22cebb 100644
--- a/framework/lib/src/services/text_formatter.dart
+++ b/framework/lib/src/services/text_formatter.dart
@@ -88,6 +88,9 @@
 ///  * [FilteringTextInputFormatter], a provided formatter for filtering
 ///    characters.
 abstract class TextInputFormatter {
+  /// This constructor enables subclasses to provide const constructors so that they can be used in const expressions.
+  const TextInputFormatter();
+
   /// Called when text is being typed or cut/copy/pasted in the [EditableText].
   ///
   /// You can override the resulting text based on the previous text value and
@@ -118,8 +121,7 @@
 
 /// Wiring for [TextInputFormatter.withFunction].
 class _SimpleTextInputFormatter extends TextInputFormatter {
-  _SimpleTextInputFormatter(this.formatFunction)
-    : assert(formatFunction != null);
+  _SimpleTextInputFormatter(this.formatFunction);
 
   final TextInputFormatFunction formatFunction;
 
@@ -268,9 +270,7 @@
     this.filterPattern, {
     required this.allow,
     this.replacementString = '',
-  }) : assert(filterPattern != null),
-       assert(allow != null),
-       assert(replacementString != null);
+  });
 
   /// Creates a formatter that only allows characters matching a pattern.
   ///
diff --git a/framework/lib/src/services/text_input.dart b/framework/lib/src/services/text_input.dart
index cf7b494..d244c2a 100644
--- a/framework/lib/src/services/text_input.dart
+++ b/framework/lib/src/services/text_input.dart
@@ -17,6 +17,7 @@
 
 import 'autofill.dart';
 import 'clipboard.dart' show Clipboard;
+import 'keyboard_inserted_content.dart';
 import 'message_codec.dart';
 import 'platform_channel.dart';
 import 'system_channels.dart';
@@ -477,18 +478,10 @@
     this.textCapitalization = TextCapitalization.none,
     this.autofillConfiguration = AutofillConfiguration.disabled,
     this.enableIMEPersonalizedLearning = true,
+    this.allowedMimeTypes = const <String>[],
     this.enableDeltaModel = false,
-  }) : assert(inputType != null),
-       assert(obscureText != null),
-       smartDashesType = smartDashesType ?? (obscureText ? SmartDashesType.disabled : SmartDashesType.enabled),
-       smartQuotesType = smartQuotesType ?? (obscureText ? SmartQuotesType.disabled : SmartQuotesType.enabled),
-       assert(autocorrect != null),
-       assert(enableSuggestions != null),
-       assert(keyboardAppearance != null),
-       assert(inputAction != null),
-       assert(textCapitalization != null),
-       assert(enableIMEPersonalizedLearning != null),
-       assert(enableDeltaModel != null);
+  }) : smartDashesType = smartDashesType ?? (obscureText ? SmartDashesType.disabled : SmartDashesType.enabled),
+       smartQuotesType = smartQuotesType ?? (obscureText ? SmartQuotesType.disabled : SmartQuotesType.enabled);
 
   /// The type of information for which to optimize the text input control.
   final TextInputType inputType;
@@ -627,6 +620,9 @@
   /// {@endtemplate}
   final bool enableIMEPersonalizedLearning;
 
+  /// {@macro flutter.widgets.contentInsertionConfiguration.allowedMimeTypes}
+  final List<String> allowedMimeTypes;
+
   /// Creates a copy of this [TextInputConfiguration] with the given fields
   /// replaced with new values.
   TextInputConfiguration copyWith({
@@ -643,6 +639,7 @@
     Brightness? keyboardAppearance,
     TextCapitalization? textCapitalization,
     bool? enableIMEPersonalizedLearning,
+    List<String>? allowedMimeTypes,
     AutofillConfiguration? autofillConfiguration,
     bool? enableDeltaModel,
   }) {
@@ -659,6 +656,7 @@
       textCapitalization: textCapitalization ?? this.textCapitalization,
       keyboardAppearance: keyboardAppearance ?? this.keyboardAppearance,
       enableIMEPersonalizedLearning: enableIMEPersonalizedLearning?? this.enableIMEPersonalizedLearning,
+      allowedMimeTypes: allowedMimeTypes ?? this.allowedMimeTypes,
       autofillConfiguration: autofillConfiguration ?? this.autofillConfiguration,
       enableDeltaModel: enableDeltaModel ?? this.enableDeltaModel,
     );
@@ -706,6 +704,7 @@
       'textCapitalization': textCapitalization.toString(),
       'keyboardAppearance': keyboardAppearance.toString(),
       'enableIMEPersonalizedLearning': enableIMEPersonalizedLearning,
+      'contentCommitMimeTypes': allowedMimeTypes,
       if (autofill != null) 'autofill': autofill,
       'enableDeltaModel' : enableDeltaModel,
     };
@@ -746,8 +745,7 @@
   RawFloatingCursorPoint({
     this.offset,
     required this.state,
-  }) : assert(state != null),
-       assert(state != FloatingCursorDragState.Update || offset != null);
+  }) : assert(state != FloatingCursorDragState.Update || offset != null);
 
   /// The raw position of the floating cursor as determined by the iOS sdk.
   final Offset? offset;
@@ -773,13 +771,7 @@
     this.text = '',
     this.selection = const TextSelection.collapsed(offset: -1),
     this.composing = TextRange.empty,
-  }) : assert(text != null),
-       // The constructor does not verify that `selection` and `composing` are
-       // valid ranges within `text`, and is unable to do so due to limitation
-       // of const constructors. Some checks are performed by assertion in
-       // other occasions. See `_textRangeIsValid`.
-       assert(selection != null),
-       assert(composing != null);
+  });
 
   /// Creates an instance of this class from a JSON object.
   factory TextEditingValue.fromJSON(Map<String, dynamic> encoded) {
@@ -1121,6 +1113,9 @@
   /// Requests that this client perform the given action.
   void performAction(TextInputAction action);
 
+  /// Notify client about new content insertion from Android keyboard.
+  void insertContent(KeyboardInsertedContent content) {}
+
   /// Request from the input method that this client perform the given private
   /// command.
   ///
@@ -1211,7 +1206,11 @@
 class SelectionRect {
   /// Constructor for creating a [SelectionRect] from a text [position] and
   /// [bounds].
-  const SelectionRect({required this.position, required this.bounds});
+  const SelectionRect({
+    required this.position,
+    required this.bounds,
+    this.direction = TextDirection.ltr,
+  });
 
   /// The position of this selection rect within the text String.
   final int position;
@@ -1220,6 +1219,9 @@
   /// currently focused [RenderEditable]'s coordinate space.
   final Rect bounds;
 
+  /// The direction text flows within this selection rect.
+  final TextDirection direction;
+
   @override
   bool operator ==(Object other) {
     if (identical(this, other)) {
@@ -1230,7 +1232,8 @@
     }
     return other is SelectionRect
         && other.position == position
-        && other.bounds   == bounds;
+        && other.bounds == bounds
+        && other.direction == direction;
   }
 
   @override
@@ -1292,8 +1295,7 @@
 ///    the system's text input using a [TextInputConnection].
 class TextInputConnection {
   TextInputConnection._(this._client)
-      : assert(_client != null),
-        _id = _nextId++;
+      : _id = _nextId++;
 
   Size? _cachedSize;
   Matrix4? _cachedTransform;
@@ -1310,7 +1312,6 @@
   /// application code will likely break text input for the application.
   @visibleForTesting
   static void debugResetId({int to = 1}) {
-    assert(to != null);
     assert(() {
       _nextId = to;
       return true;
@@ -1387,7 +1388,6 @@
   /// This information is used for positioning the IME candidates menu on each
   /// platform.
   void setComposingRect(Rect rect) {
-    assert(rect != null);
     if (rect == _cachedRect) {
       return;
     }
@@ -1399,7 +1399,6 @@
   /// Sends the coordinates of caret rect. This is used on macOS for positioning
   /// the accent selection menu.
   void setCaretRect(Rect rect) {
-    assert(rect != null);
     if (rect == _cachedCaretRect) {
       return;
     }
@@ -1507,7 +1506,6 @@
 }
 
 RawFloatingCursorPoint _toTextPoint(FloatingCursorDragState state, Map<String, dynamic> encoded) {
-  assert(state != null, 'You must provide a state to set a new editing point.');
   assert(encoded['X'] != null, 'You must provide a value for the horizontal location of the floating cursor.');
   assert(encoded['Y'] != null, 'You must provide a value for the vertical location of the floating cursor.');
   final Offset offset = state == FloatingCursorDragState.Update
@@ -1691,8 +1689,6 @@
   /// should call [TextInputConnection.close] on the returned
   /// [TextInputConnection].
   static TextInputConnection attach(TextInputClient client, TextInputConfiguration configuration) {
-    assert(client != null);
-    assert(configuration != null);
     final TextInputConnection connection = TextInputConnection._(client);
     _instance._attach(connection, configuration);
     return connection;
@@ -1702,9 +1698,6 @@
   // by [attach] and by [_handleTextInputInvocation] for the
   // `TextInputClient.requestExistingInputState` method.
   void _attach(TextInputConnection connection, TextInputConfiguration configuration) {
-    assert(connection != null);
-    assert(connection._client != null);
-    assert(configuration != null);
     assert(_debugEnsureInputActionWorksOnPlatform(configuration.inputAction));
     _currentConnection = connection;
     _currentConfiguration = configuration;
@@ -1798,7 +1791,6 @@
     // The requestExistingInputState request needs to be handled regardless of
     // the client ID, as long as we have a _currentConnection.
     if (method == 'TextInputClient.requestExistingInputState') {
-      assert(_currentConnection!._client != null);
       _attach(_currentConnection!, _currentConfiguration);
       final TextEditingValue? editingValue = _currentConnection!._client.currentTextEditingValue;
       if (editingValue != null) {
@@ -1812,7 +1804,6 @@
     // The updateEditingStateWithTag request (autofill) can come up even to a
     // text field that doesn't have a connection.
     if (method == 'TextInputClient.updateEditingStateWithTag') {
-      assert(_currentConnection!._client != null);
       final TextInputClient client = _currentConnection!._client;
       final AutofillScope? scope = client.currentAutofillScope;
       final Map<String, dynamic> editingValue = args[1] as Map<String, dynamic>;
@@ -1867,7 +1858,12 @@
         (_currentConnection!._client as DeltaTextInputClient).updateEditingValueWithDeltas(deltas);
         break;
       case 'TextInputClient.performAction':
-        _currentConnection!._client.performAction(_toTextInputAction(args[1] as String));
+        if (args[1] as String == 'TextInputAction.commitContent') {
+          final KeyboardInsertedContent content = KeyboardInsertedContent.fromJson(args[2] as Map<String, dynamic>);
+          _currentConnection!._client.insertContent(content);
+        } else {
+          _currentConnection!._client.performAction(_toTextInputAction(args[1] as String));
+        }
         break;
       case 'TextInputClient.performSelectors':
         final List<String> selectors = (args[1] as List<dynamic>).cast<String>();
@@ -1943,14 +1939,12 @@
   }
 
   void _updateConfig(TextInputConfiguration configuration) {
-    assert(configuration != null);
     for (final TextInputControl control in _inputControls) {
       control.updateConfig(configuration);
     }
   }
 
   void _setEditingState(TextEditingValue value) {
-    assert(value != null);
     for (final TextInputControl control in _inputControls) {
       control.setEditingState(value);
     }
@@ -2086,7 +2080,6 @@
   /// * [AutofillGroup.onDisposeAction], a configurable action that runs when a
   ///   topmost [AutofillGroup] is getting disposed.
   static void finishAutofillContext({ bool shouldSave = true }) {
-    assert(shouldSave != null);
     for (final TextInputControl control in TextInput._instance._inputControls) {
       control.finishAutofillContext(shouldSave: shouldSave);
     }
@@ -2321,7 +2314,14 @@
     _channel.invokeMethod<void>(
       'TextInput.setSelectionRects',
       selectionRects.map((SelectionRect rect) {
-        return <num>[rect.bounds.left, rect.bounds.top, rect.bounds.width, rect.bounds.height, rect.position];
+        return <num>[
+          rect.bounds.left,
+          rect.bounds.top,
+          rect.bounds.width,
+          rect.bounds.height,
+          rect.position,
+          rect.direction.index,
+        ];
       }).toList(),
     );
   }
diff --git a/framework/lib/src/services/text_layout_metrics.dart b/framework/lib/src/services/text_layout_metrics.dart
index fdaf5a6..6ae774a 100644
--- a/framework/lib/src/services/text_layout_metrics.dart
+++ b/framework/lib/src/services/text_layout_metrics.dart
@@ -54,6 +54,25 @@
     return true;
   }
 
+  /// Check if the given code unit is a line terminator character.
+  ///
+  /// Includes newline characters from ASCII
+  /// (https://www.unicode.org/standard/reports/tr13/tr13-5.html).
+  static bool isLineTerminator(int codeUnit) {
+    switch (codeUnit) {
+      case 0xA: // line feed
+      case 0xB: // vertical feed
+      case 0xC: // form feed
+      case 0xD: // carriage return
+      case 0x85: // new line
+      case 0x2028: // line separator
+      case 0x2029: // paragraph separator
+        return true;
+      default:
+        return false;
+    }
+  }
+
   /// {@template flutter.services.TextLayoutMetrics.getLineAtOffset}
   /// Return a [TextSelection] containing the line of the given [TextPosition].
   /// {@endtemplate}
diff --git a/framework/lib/src/widgets/actions.dart b/framework/lib/src/widgets/actions.dart
index b7b699c..4987c41 100644
--- a/framework/lib/src/widgets/actions.dart
+++ b/framework/lib/src/widgets/actions.dart
@@ -256,13 +256,15 @@
   /// This is typically used when the action is invoked in response to a keyboard
   /// shortcut.
   ///
+  /// The [invokeResult] argument is the value returned by the [invoke] method.
+  ///
   /// By default, calls [consumesKey] and converts the returned boolean to
   /// [KeyEventResult.handled] if it's true, and [KeyEventResult.skipRemainingHandlers]
   /// if it's false.
   ///
-  /// Concrete implementations may refine the type of [actionResult], since
-  /// they know type returned by [invoke].
-  KeyEventResult toKeyEventResult(T intent, covariant Object? actionResult) {
+  /// Concrete implementations may refine the type of [invokeResult], since
+  /// they know the type returned by [invoke].
+  KeyEventResult toKeyEventResult(T intent, covariant Object? invokeResult) {
     return consumesKey(intent)
       ? KeyEventResult.handled
       : KeyEventResult.skipRemainingHandlers;
@@ -429,9 +431,7 @@
     required this.listener,
     required this.action,
     required this.child,
-  })  : assert(listener != null),
-        assert(action != null),
-        assert(child != null);
+  });
 
   /// The [ActionListenerCallback] callback to register with the [action].
   ///
@@ -546,7 +546,7 @@
   ///
   /// The `intentKey` and [onInvoke] parameters must not be null.
   /// The [onInvoke] parameter is required.
-  CallbackAction({required this.onInvoke}) : assert(onInvoke != null);
+  CallbackAction({required this.onInvoke});
 
   /// The callback to be called when invoked.
   ///
@@ -558,7 +558,13 @@
   Object? invoke(T intent) => onInvoke(intent);
 }
 
-/// An action dispatcher that simply invokes the actions given to it.
+/// An action dispatcher that invokes the actions given to it.
+///
+/// The [invokeAction] method on this class directly calls the [Action.invoke]
+/// method on the [Action] object.
+///
+/// For [ContextAction] actions, if no `context` is provided, the
+/// [BuildContext] of the [primaryFocus] is used instead.
 ///
 /// See also:
 ///
@@ -587,8 +593,6 @@
     covariant Intent intent, [
     BuildContext? context,
   ]) {
-    assert(action != null);
-    assert(intent != null);
     assert(action.isEnabled(intent), 'Action must be enabled when calling invokeAction');
     if (action is ContextAction) {
       context ??= primaryFocus?.context;
@@ -643,8 +647,7 @@
     this.dispatcher,
     required this.actions,
     required this.child,
-  })  : assert(actions != null),
-        assert(child != null);
+  });
 
   /// The [ActionDispatcher] object that invokes actions.
   ///
@@ -673,7 +676,7 @@
   // getElementForInheritedWidgetOfExactType. Returns true if the visitor found
   // what it was looking for.
   static bool _visitActionsAncestors(BuildContext context, bool Function(InheritedElement element) visitor) {
-    InheritedElement? actionsElement = context.getElementForInheritedWidgetOfExactType<_ActionsMarker>();
+    InheritedElement? actionsElement = context.getElementForInheritedWidgetOfExactType<_ActionsScope>();
     while (actionsElement != null) {
       if (visitor(actionsElement) == true) {
         break;
@@ -682,7 +685,7 @@
       // context.getElementForInheritedWidgetOfExactType will return itself if it
       // happens to be of the correct type.
       final BuildContext parent = _getParent(actionsElement);
-      actionsElement = parent.getElementForInheritedWidgetOfExactType<_ActionsMarker>();
+      actionsElement = parent.getElementForInheritedWidgetOfExactType<_ActionsScope>();
     }
     return actionsElement != null;
   }
@@ -692,7 +695,7 @@
   static ActionDispatcher _findDispatcher(BuildContext context) {
     ActionDispatcher? dispatcher;
     _visitActionsAncestors(context, (InheritedElement element) {
-      final ActionDispatcher? found = (element.widget as _ActionsMarker).dispatcher;
+      final ActionDispatcher? found = (element.widget as _ActionsScope).dispatcher;
       if (found != null) {
         dispatcher = found;
         return true;
@@ -799,7 +802,7 @@
     );
 
     _visitActionsAncestors(context, (InheritedElement element) {
-      final _ActionsMarker actions = element.widget as _ActionsMarker;
+      final _ActionsScope actions = element.widget as _ActionsScope;
       final Action<T>? result = _castAction(actions, intent: intent);
       if (result != null) {
         context.dependOnInheritedElement(element);
@@ -828,7 +831,7 @@
     );
 
     _visitActionsAncestors(context, (InheritedElement element) {
-      final _ActionsMarker actions = element.widget as _ActionsMarker;
+      final _ActionsScope actions = element.widget as _ActionsScope;
       final Action<T>? result = _castAction(actions, intent: intent);
       if (result != null) {
         action = result;
@@ -841,8 +844,8 @@
   }
 
   // Find the [Action] that handles the given `intent` in the given
-  // `_ActionsMarker`, and verify it has the right type parameter.
-  static Action<T>? _castAction<T extends Intent>(_ActionsMarker actionsMarker, { T? intent }) {
+  // `_ActionsScope`, and verify it has the right type parameter.
+  static Action<T>? _castAction<T extends Intent>(_ActionsScope actionsMarker, { T? intent }) {
     final Action<Intent>? mappedAction = actionsMarker.actions[intent?.runtimeType ?? T];
     if (mappedAction is Action<T>?) {
       return mappedAction;
@@ -861,8 +864,7 @@
   /// Will return a newly created [ActionDispatcher] if no ambient [Actions]
   /// widget is found.
   static ActionDispatcher of(BuildContext context) {
-    assert(context != null);
-    final _ActionsMarker? marker = context.dependOnInheritedWidgetOfExactType<_ActionsMarker>();
+    final _ActionsScope? marker = context.dependOnInheritedWidgetOfExactType<_ActionsScope>();
     return marker?.dispatcher ?? _findDispatcher(context);
   }
 
@@ -884,12 +886,10 @@
     BuildContext context,
     T intent,
   ) {
-    assert(intent != null);
-    assert(context != null);
     Object? returnValue;
 
     final bool actionFound = _visitActionsAncestors(context, (InheritedElement element) {
-      final _ActionsMarker actions = element.widget as _ActionsMarker;
+      final _ActionsScope actions = element.widget as _ActionsScope;
       final Action<T>? result = _castAction(actions, intent: intent);
       if (result != null && result.isEnabled(intent)) {
         // Invoke the action we found using the relevant dispatcher from the Actions
@@ -937,16 +937,14 @@
     BuildContext context,
     T intent,
   ) {
-    assert(intent != null);
-    assert(context != null);
     Object? returnValue;
 
     _visitActionsAncestors(context, (InheritedElement element) {
-      final _ActionsMarker actions = element.widget as _ActionsMarker;
+      final _ActionsScope actions = element.widget as _ActionsScope;
       final Action<T>? result = _castAction(actions, intent: intent);
       if (result != null && result.isEnabled(intent)) {
         // Invoke the action we found using the relevant dispatcher from the Actions
-        // Element we found.
+        // element we found.
         returnValue = _findDispatcher(element).invokeAction(result, intent, context);
       }
       return result != null;
@@ -1016,7 +1014,7 @@
 
   @override
   Widget build(BuildContext context) {
-    return _ActionsMarker(
+    return _ActionsScope(
       actions: widget.actions,
       dispatcher: widget.dispatcher,
       rebuildKey: rebuildKey,
@@ -1027,21 +1025,20 @@
 
 // An inherited widget used by Actions widget for fast lookup of the Actions
 // widget information.
-class _ActionsMarker extends InheritedWidget {
-  const _ActionsMarker({
+class _ActionsScope extends InheritedWidget {
+  const _ActionsScope({
     required this.dispatcher,
     required this.actions,
     required this.rebuildKey,
     required super.child,
-  })  : assert(child != null),
-        assert(actions != null);
+  });
 
   final ActionDispatcher? dispatcher;
   final Map<Type, Action<Intent>> actions;
   final Object rebuildKey;
 
   @override
-  bool updateShouldNotify(_ActionsMarker oldWidget) {
+  bool updateShouldNotify(_ActionsScope oldWidget) {
     return rebuildKey != oldWidget.rebuildKey
         || oldWidget.dispatcher != dispatcher
         || !mapEquals<Type, Action<Intent>>(oldWidget.actions, actions);
@@ -1098,10 +1095,7 @@
     this.mouseCursor = MouseCursor.defer,
     this.includeFocusSemantics = true,
     required this.child,
-  })  : assert(enabled != null),
-        assert(autofocus != null),
-        assert(mouseCursor != null),
-        assert(child != null);
+  });
 
   /// Is this widget enabled or not.
   ///
@@ -1247,7 +1241,7 @@
     }
 
     bool canRequestFocus(FocusableActionDetector target) {
-      final NavigationMode mode = MediaQuery.maybeOf(context)?.navigationMode ?? NavigationMode.traditional;
+      final NavigationMode mode = MediaQuery.maybeNavigationModeOf(context) ?? NavigationMode.traditional;
       switch (mode) {
         case NavigationMode.traditional:
           return target.enabled;
@@ -1288,7 +1282,7 @@
   }
 
   bool get _canRequestFocus {
-    final NavigationMode mode = MediaQuery.maybeOf(context)?.navigationMode ?? NavigationMode.traditional;
+    final NavigationMode mode = MediaQuery.maybeNavigationModeOf(context) ?? NavigationMode.traditional;
     switch (mode) {
       case NavigationMode.traditional:
         return widget.enabled;
@@ -1470,7 +1464,8 @@
 ///  * [WidgetsApp.shortcuts], which defines the shortcuts to use in an
 ///    application (and defaults to [WidgetsApp.defaultShortcuts]).
 class ButtonActivateIntent extends Intent {
-  /// Creates an intent that the currently focused control, if it's a button.
+  /// Creates an intent that activates the currently focused control,
+  /// if it's a button.
   const ButtonActivateIntent();
 }
 
@@ -1521,7 +1516,7 @@
   /// of intents, the first available of which will be used.
   const PrioritizedIntents({
     required this.orderedIntents,
-  })  : assert(orderedIntents != null);
+  });
 
   /// List of intents to be evaluated in order for execution. When an
   /// [Action.isEnabled] returns true, that action will be invoked and
@@ -1557,8 +1552,6 @@
 
   @override
   void invoke(PrioritizedIntents intent) {
-    assert(_selectedAction != null);
-    assert(_selectedIntent != null);
     _selectedAction.invoke(_selectedIntent);
   }
 }
@@ -1573,7 +1566,7 @@
   bool debugAssertConsumeKeyMutuallyRecursive = false;
 
   // The default action to invoke if an enabled override Action can't be found
-  // using [lookupContext];
+  // using [lookupContext].
   Action<T> get defaultAction;
 
   // The [BuildContext] used to find the override of this [Action].
diff --git a/framework/lib/src/widgets/animated_cross_fade.dart b/framework/lib/src/widgets/animated_cross_fade.dart
index 2d6b8af..543d9a8 100644
--- a/framework/lib/src/widgets/animated_cross_fade.dart
+++ b/framework/lib/src/widgets/animated_cross_fade.dart
@@ -130,16 +130,7 @@
     this.reverseDuration,
     this.layoutBuilder = defaultLayoutBuilder,
     this.excludeBottomFocus = true,
-  }) : assert(firstChild != null),
-       assert(secondChild != null),
-       assert(firstCurve != null),
-       assert(secondCurve != null),
-       assert(sizeCurve != null),
-       assert(alignment != null),
-       assert(crossFadeState != null),
-       assert(duration != null),
-       assert(layoutBuilder != null),
-       assert(excludeBottomFocus != null);
+  });
 
   /// The child that is visible when [crossFadeState] is
   /// [CrossFadeState.showFirst]. It fades out when transitioning
diff --git a/framework/lib/src/widgets/animated_scroll_view.dart b/framework/lib/src/widgets/animated_scroll_view.dart
index 649d4aa..7f40664 100644
--- a/framework/lib/src/widgets/animated_scroll_view.dart
+++ b/framework/lib/src/widgets/animated_scroll_view.dart
@@ -52,8 +52,7 @@
     super.shrinkWrap = false,
     super.padding,
     super.clipBehavior = Clip.hardEdge,
-  }) : assert(itemBuilder != null),
-       assert(initialItemCount != null && initialItemCount >= 0);
+  }) : assert(initialItemCount >= 0);
 
   /// The state from the closest instance of this class that encloses the given
   /// context.
@@ -74,7 +73,6 @@
   ///  * [maybeOf], a similar function that will return null if no
   ///    [AnimatedList] ancestor is found.
   static AnimatedListState of(BuildContext context) {
-    assert(context != null);
     final AnimatedListState? result = AnimatedList.maybeOf(context);
     assert(() {
       if (result == null) {
@@ -116,7 +114,6 @@
   ///  * [of], a similar function that will throw if no [AnimatedList] ancestor
   ///    is found.
   static AnimatedListState? maybeOf(BuildContext context) {
-    assert(context != null);
     return context.findAncestorStateOfType<AnimatedListState>();
   }
 
@@ -131,6 +128,10 @@
 /// animation is passed to [AnimatedList.itemBuilder] whenever the item's widget
 /// is needed.
 ///
+/// When multiple items are inserted with [insertAllItems] an animation begins running.
+/// The animation is passed to [AnimatedList.itemBuilder] whenever the item's widget
+/// is needed.
+///
 /// When an item is removed with [removeItem] its animation is reversed.
 /// The removed item's animation is passed to the [removeItem] builder
 /// parameter.
@@ -217,8 +218,7 @@
     super.physics,
     super.padding,
     super.clipBehavior = Clip.hardEdge,
-  })  : assert(itemBuilder != null),
-        assert(initialItemCount != null && initialItemCount >= 0);
+  })  : assert(initialItemCount >= 0);
 
   /// {@template flutter.widgets.AnimatedGrid.gridDelegate}
   /// A delegate that controls the layout of the children within the
@@ -252,7 +252,6 @@
   ///  * [maybeOf], a similar function that will return null if no
   ///    [AnimatedGrid] ancestor is found.
   static AnimatedGridState of(BuildContext context) {
-    assert(context != null);
     final AnimatedGridState? result = AnimatedGrid.maybeOf(context);
     assert(() {
       if (result == null) {
@@ -294,7 +293,6 @@
   ///  * [of], a similar function that will throw if no [AnimatedGrid] ancestor
   ///    is found.
   static AnimatedGridState? maybeOf(BuildContext context) {
-    assert(context != null);
     return context.findAncestorStateOfType<AnimatedGridState>();
   }
 
@@ -373,8 +371,7 @@
     this.shrinkWrap = false,
     this.padding,
     this.clipBehavior = Clip.hardEdge,
-  }) : assert(itemBuilder != null),
-        assert(initialItemCount != null && initialItemCount >= 0);
+  }) : assert(initialItemCount >= 0);
 
   /// {@template flutter.widgets.AnimatedScrollView.itemBuilder}
   /// Called, as needed, to build children widgets.
@@ -493,6 +490,13 @@
     _sliverAnimatedMultiBoxKey.currentState!.insertItem(index, duration: duration);
   }
 
+  /// Insert multiple items at [index] and start an animation that will be passed
+  /// to [AnimatedGrid.itemBuilder] or [AnimatedList.itemBuilder] when the items
+  /// are visible.
+  void insertAllItems(int index, int length, { Duration duration = _kDuration, bool isAsync = false }) {
+    _sliverAnimatedMultiBoxKey.currentState!.insertAllItems(index, length, duration: duration);
+  }
+
   /// Remove the item at `index` and start an animation that will be passed to
   /// `builder` when the item is visible.
   ///
@@ -513,6 +517,19 @@
     _sliverAnimatedMultiBoxKey.currentState!.removeItem(index, builder, duration: duration);
   }
 
+  /// Remove all the items and start an animation that will be passed to
+  /// `builder` when the items are visible.
+  ///
+  /// Items are removed immediately. However, the
+  /// items will still appear for `duration`, and during that time
+  /// `builder` must construct its widget as needed.
+  ///
+  /// This method's semantics are the same as Dart's [List.clear] method: it
+  /// removes all the items in the list.
+  void removeAllItems(AnimatedRemovedItemBuilder builder, { Duration duration = _kDuration }) {
+    _sliverAnimatedMultiBoxKey.currentState!.removeAllItems(builder, duration: duration);
+  }
+
   Widget _wrap(Widget sliver) {
     return CustomScrollView(
       scrollDirection: widget.scrollDirection,
@@ -630,8 +647,7 @@
     required super.itemBuilder,
     super.findChildIndexCallback,
     super.initialItemCount = 0,
-  }) : assert(itemBuilder != null),
-        assert(initialItemCount != null && initialItemCount >= 0);
+  }) : assert(initialItemCount >= 0);
 
   @override
   SliverAnimatedListState createState() => SliverAnimatedListState();
@@ -655,7 +671,6 @@
   ///  * [maybeOf], a similar function that will return null if no
   ///    [SliverAnimatedList] ancestor is found.
   static SliverAnimatedListState of(BuildContext context) {
-    assert(context != null);
     final SliverAnimatedListState? result = SliverAnimatedList.maybeOf(context);
     assert(() {
       if (result == null) {
@@ -695,7 +710,6 @@
   ///  * [of], a similar function that will throw if no [SliverAnimatedList]
   ///    ancestor is found.
   static SliverAnimatedListState? maybeOf(BuildContext context) {
-    assert(context != null);
     return context.findAncestorStateOfType<SliverAnimatedListState>();
   }
 }
@@ -782,8 +796,7 @@
     required this.gridDelegate,
     super.findChildIndexCallback,
     super.initialItemCount = 0,
-  })  : assert(itemBuilder != null),
-        assert(initialItemCount != null && initialItemCount >= 0);
+  })  : assert(initialItemCount >= 0);
 
   @override
   SliverAnimatedGridState createState() => SliverAnimatedGridState();
@@ -807,7 +820,6 @@
   ///  * [maybeOf], a similar function that will return null if no
   ///    [SliverAnimatedGrid] ancestor is found.
   static SliverAnimatedGridState of(BuildContext context) {
-    assert(context != null);
     final SliverAnimatedGridState? result = context.findAncestorStateOfType<SliverAnimatedGridState>();
     assert(() {
       if (result == null) {
@@ -844,7 +856,6 @@
   ///  * [of], a similar function that will throw if no [SliverAnimatedGrid]
   ///    ancestor is found.
   static SliverAnimatedGridState? maybeOf(BuildContext context) {
-    assert(context != null);
     return context.findAncestorStateOfType<SliverAnimatedGridState>();
   }
 }
@@ -908,8 +919,7 @@
     required this.itemBuilder,
     this.findChildIndexCallback,
     this.initialItemCount = 0,
-  })  : assert(itemBuilder != null),
-        assert(initialItemCount != null && initialItemCount >= 0);
+  })  : assert(initialItemCount >= 0);
 
   /// {@macro flutter.widgets.AnimatedScrollView.itemBuilder}
   final AnimatedItemBuilder itemBuilder;
@@ -1022,8 +1032,7 @@
   /// increases the length of the list of items by one and shifts
   /// all items at or after [index] towards the end of the list of items.
   void insertItem(int index, { Duration duration = _kDuration }) {
-    assert(index != null && index >= 0);
-    assert(duration != null);
+    assert(index >= 0);
 
     final int itemIndex = _indexToItemIndex(index);
     assert(itemIndex >= 0 && itemIndex <= _itemsCount);
@@ -1061,6 +1070,15 @@
     });
   }
 
+  /// Insert multiple items at [index] and start an animation that will be passed
+  /// to [AnimatedGrid.itemBuilder] or [AnimatedList.itemBuilder] when the items
+  /// are visible.
+  void insertAllItems(int index, int length, { Duration duration = _kDuration }) {
+    for (int i = 0; i < length; i++) {
+      insertItem(index + i, duration: duration);
+    }
+  }
+
   /// Remove the item at [index] and start an animation that will be passed
   /// to [builder] when the item is visible.
   ///
@@ -1074,9 +1092,7 @@
   /// decreases the length of items by one and shifts
   /// all items at or before [index] towards the beginning of the list of items.
   void removeItem(int index, AnimatedRemovedItemBuilder builder, { Duration duration = _kDuration }) {
-    assert(index != null && index >= 0);
-    assert(builder != null);
-    assert(duration != null);
+    assert(index >= 0);
 
     final int itemIndex = _indexToItemIndex(index);
     assert(itemIndex >= 0 && itemIndex < _itemsCount);
@@ -1111,4 +1127,19 @@
       setState(() => _itemsCount -= 1);
     });
   }
+
+  /// Remove all the items and start an animation that will be passed to
+  /// `builder` when the items are visible.
+  ///
+  /// Items are removed immediately. However, the
+  /// items will still appear for `duration` and during that time
+  /// `builder` must construct its widget as needed.
+  ///
+  /// This method's semantics are the same as Dart's [List.clear] method: it
+  /// removes all the items in the list.
+  void removeAllItems(AnimatedRemovedItemBuilder builder, { Duration duration = _kDuration }) {
+    for(int i = _itemsCount - 1 ; i >= 0; i--) {
+      removeItem(i, builder, duration: duration);
+    }
+  }
 }
diff --git a/framework/lib/src/widgets/animated_size.dart b/framework/lib/src/widgets/animated_size.dart
index b8a2b0b..b14cabb 100644
--- a/framework/lib/src/widgets/animated_size.dart
+++ b/framework/lib/src/widgets/animated_size.dart
@@ -32,13 +32,8 @@
     this.curve = Curves.linear,
     required this.duration,
     this.reverseDuration,
-    @Deprecated(
-      'This field is now ignored. '
-      'This feature was deprecated after v2.2.0-10.1.pre.'
-    )
-    TickerProvider? vsync,
     this.clipBehavior = Clip.hardEdge,
-  }) : assert(clipBehavior != null);
+  });
 
   /// The widget below this widget in the tree.
   ///
@@ -114,7 +109,7 @@
     this.reverseDuration,
     required this.vsync,
     this.clipBehavior = Clip.hardEdge,
-  }) : assert(clipBehavior != null);
+  });
 
   final AlignmentGeometry alignment;
   final Curve curve;
diff --git a/framework/lib/src/widgets/animated_switcher.dart b/framework/lib/src/widgets/animated_switcher.dart
index fe8be50..5f3ccc7 100644
--- a/framework/lib/src/widgets/animated_switcher.dart
+++ b/framework/lib/src/widgets/animated_switcher.dart
@@ -19,9 +19,7 @@
     required this.animation,
     required this.transition,
     required this.widgetChild,
-  }) : assert(animation != null),
-       assert(transition != null),
-       assert(controller != null);
+  });
 
   // The animation controller for the child's transition.
   final AnimationController controller;
@@ -117,11 +115,7 @@
     this.switchOutCurve = Curves.linear,
     this.transitionBuilder = AnimatedSwitcher.defaultTransitionBuilder,
     this.layoutBuilder = AnimatedSwitcher.defaultLayoutBuilder,
-  }) : assert(duration != null),
-       assert(switchInCurve != null),
-       assert(switchOutCurve != null),
-       assert(transitionBuilder != null),
-       assert(layoutBuilder != null);
+  });
 
   /// The current child widget to display. If there was a previous child, then
   /// that child will be faded out using the [switchOutCurve], while the new
diff --git a/framework/lib/src/widgets/annotated_region.dart b/framework/lib/src/widgets/annotated_region.dart
index 8f7603e..e55e2ca 100644
--- a/framework/lib/src/widgets/annotated_region.dart
+++ b/framework/lib/src/widgets/annotated_region.dart
@@ -24,8 +24,7 @@
     required Widget super.child,
     required this.value,
     this.sized = true,
-  }) : assert(value != null),
-       assert(child != null);
+  });
 
   /// A value which can be retrieved using [Layer.find].
   final T value;
diff --git a/framework/lib/src/widgets/app.dart b/framework/lib/src/widgets/app.dart
index 820ef11..1791729 100644
--- a/framework/lib/src/widgets/app.dart
+++ b/framework/lib/src/widgets/app.dart
@@ -241,9 +241,6 @@
 /// It is used by both [MaterialApp] and [CupertinoApp] to implement base
 /// functionality for an app.
 ///
-/// Builds a [MediaQuery] using [MediaQuery.fromWindow]. To use an inherited
-/// [MediaQuery] instead, set [useInheritedMediaQuery] to true.
-///
 /// Find references to many of the widgets that [WidgetsApp] wraps in the "See
 /// also" section.
 ///
@@ -343,10 +340,13 @@
     this.shortcuts,
     this.actions,
     this.restorationScopeId,
+    @Deprecated(
+      'Remove this parameter as it is now ignored. '
+      'WidgetsApp never introduces its own MediaQuery; the View widget takes care of that. '
+      'This feature was deprecated after v3.7.0-29.0.pre.'
+    )
     this.useInheritedMediaQuery = false,
-  }) : assert(navigatorObservers != null),
-       assert(routes != null),
-       assert(
+  }) : assert(
          home == null ||
          onGenerateInitialRoutes == null,
          'If onGenerateInitialRoutes is specified, the home argument will be '
@@ -398,15 +398,7 @@
          'pageRouteBuilder must be specified so that the default handler '
          'will know what kind of PageRoute transition to build.',
        ),
-       assert(title != null),
-       assert(color != null),
-       assert(supportedLocales != null && supportedLocales.isNotEmpty),
-       assert(showPerformanceOverlay != null),
-       assert(checkerboardRasterCacheImages != null),
-       assert(checkerboardOffscreenLayers != null),
-       assert(showSemanticsDebugger != null),
-       assert(debugShowCheckedModeBanner != null),
-       assert(debugShowWidgetInspector != null),
+       assert(supportedLocales.isNotEmpty),
        routeInformationProvider = null,
        routeInformationParser = null,
        routerDelegate = null,
@@ -447,6 +439,11 @@
     this.shortcuts,
     this.actions,
     this.restorationScopeId,
+    @Deprecated(
+      'Remove this parameter as it is now ignored. '
+      'WidgetsApp never introduces its own MediaQuery; the View widget takes care of that. '
+      'This feature was deprecated after v3.7.0-29.0.pre.'
+    )
     this.useInheritedMediaQuery = false,
   }) : assert((){
          if (routerConfig != null) {
@@ -463,15 +460,7 @@
          );
          return true;
        }()),
-       assert(title != null),
-       assert(color != null),
-       assert(supportedLocales != null && supportedLocales.isNotEmpty),
-       assert(showPerformanceOverlay != null),
-       assert(checkerboardRasterCacheImages != null),
-       assert(checkerboardOffscreenLayers != null),
-       assert(showSemanticsDebugger != null),
-       assert(debugShowCheckedModeBanner != null),
-       assert(debugShowWidgetInspector != null),
+       assert(supportedLocales.isNotEmpty),
        navigatorObservers = null,
        navigatorKey = null,
        onGenerateRoute = null,
@@ -1171,11 +1160,16 @@
   final String? restorationScopeId;
 
   /// {@template flutter.widgets.widgetsApp.useInheritedMediaQuery}
-  /// If true, an inherited MediaQuery will be used. If one is not available,
-  /// or this is false, one will be built from the window.
+  /// Deprecated. This setting is not ignored.
   ///
-  /// Cannot be null, defaults to false.
+  /// The widget never introduces its own [MediaQuery]; the [View] widget takes
+  /// care of that.
   /// {@endtemplate}
+  @Deprecated(
+    'This setting is now ignored. '
+    'WidgetsApp never introduces its own MediaQuery; the View widget takes care of that. '
+    'This feature was deprecated after v3.7.0-29.0.pre.'
+  )
   final bool useInheritedMediaQuery;
 
   /// If true, forces the performance overlay to be visible in all instances.
@@ -1432,7 +1426,6 @@
         settings,
         pageContentBuilder,
       );
-      assert(route != null, 'The pageRouteBuilder for WidgetsApp must return a valid non-null Route.');
       return route;
     }
     if (widget.onGenerateRoute != null) {
@@ -1722,7 +1715,6 @@
         // parameter.
         builder: (BuildContext context) {
           final String title = widget.onGenerateTitle!(context);
-          assert(title != null, 'onGenerateTitle must return a non-null String');
           return Title(
             title: title,
             color: widget.color.withOpacity(1.0),
@@ -1744,19 +1736,6 @@
 
     assert(_debugCheckLocalizations(appLocale));
 
-    Widget child = Localizations(
-      locale: appLocale,
-      delegates: _localizationsDelegates.toList(),
-      child: title,
-    );
-
-    final MediaQueryData? data = MediaQuery.maybeOf(context);
-    if (!widget.useInheritedMediaQuery || data == null) {
-      child = MediaQuery.fromWindow(
-        child: child,
-      );
-    }
-
     return RootRestorationScope(
       restorationId: widget.restorationScopeId,
       child: SharedAppData(
@@ -1775,7 +1754,11 @@
                 policy: ReadingOrderTraversalPolicy(),
                 child: TapRegionSurface(
                   child: ShortcutRegistrar(
-                    child: child,
+                    child: Localizations(
+                      locale: appLocale,
+                      delegates: _localizationsDelegates.toList(),
+                      child: title,
+                    ),
                   ),
                 ),
               ),
diff --git a/framework/lib/src/widgets/async.dart b/framework/lib/src/widgets/async.dart
index cf99291..fe2b233 100644
--- a/framework/lib/src/widgets/async.dart
+++ b/framework/lib/src/widgets/async.dart
@@ -2,10 +2,6 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-/// Widgets that handle interaction with asynchronous computations.
-///
-/// Asynchronous computations are represented by [Future]s and [Stream]s.
-
 import 'dart:async' show StreamSubscription;
 
 import 'package:flute/foundation.dart';
@@ -204,8 +200,7 @@
   /// and optionally either [data] or [error] with an optional [stackTrace]
   /// (but not both data and error).
   const AsyncSnapshot._(this.connectionState, this.data, this.error, this.stackTrace)
-    : assert(connectionState != null),
-      assert(!(data != null && error != null)),
+    : assert(!(data != null && error != null)),
       assert(stackTrace == null || error != null);
 
   /// Creates an [AsyncSnapshot] in [ConnectionState.none] with null data and error.
@@ -398,7 +393,7 @@
     this.initialData,
     super.stream,
     required this.builder,
-  }) : assert(builder != null);
+  });
 
   /// The build strategy currently used by this builder.
   ///
@@ -444,11 +439,15 @@
   Widget build(BuildContext context, AsyncSnapshot<T> currentSummary) => builder(context, currentSummary);
 }
 
-/// Widget that builds itself based on the latest snapshot of interaction with
+/// A widget that builds itself based on the latest snapshot of interaction with
 /// a [Future].
 ///
+/// {@youtube 560 315 https://www.youtube.com/watch?v=ek8ZPdWj4Qo}
+///
 /// {@youtube 560 315 https://www.youtube.com/watch?v=zEdw_1B7JHY}
 ///
+/// ## Managing the future
+///
 /// The [future] must have been obtained earlier, e.g. during [State.initState],
 /// [State.didUpdateWidget], or [State.didChangeDependencies]. It must not be
 /// created during the [State.build] or [StatelessWidget.build] method call when
@@ -459,8 +458,6 @@
 /// A general guideline is to assume that every `build` method could get called
 /// every frame, and to treat omitted calls as an optimization.
 ///
-/// {@youtube 560 315 https://www.youtube.com/watch?v=ek8ZPdWj4Qo}
-///
 /// ## Timing
 ///
 /// Widget rebuilding is scheduled by the completion of the future, using
@@ -530,7 +527,7 @@
     this.future,
     this.initialData,
     required this.builder,
-  }) : assert(builder != null);
+  });
 
   /// The asynchronous computation to which this builder is currently connected,
   /// possibly null.
diff --git a/framework/lib/src/widgets/autocomplete.dart b/framework/lib/src/widgets/autocomplete.dart
index f34b9b5..a6aea13 100644
--- a/framework/lib/src/widgets/autocomplete.dart
+++ b/framework/lib/src/widgets/autocomplete.dart
@@ -134,14 +134,11 @@
     this.onSelected,
     this.textEditingController,
     this.initialValue,
-  }) : assert(displayStringForOption != null),
-       assert(
+  }) : assert(
          fieldViewBuilder != null
             || (key != null && focusNode != null && textEditingController != null),
          'Pass in a fieldViewBuilder, or otherwise create a separate field and pass in the FocusNode, TextEditingController, and a key. Use the key with RawAutocomplete.onFieldSubmitted.',
         ),
-       assert(optionsBuilder != null),
-       assert(optionsViewBuilder != null),
        assert((focusNode == null) == (textEditingController == null)),
        assert(
          !(textEditingController != null && initialValue != null),
@@ -263,8 +260,8 @@
   /// The default way to convert an option to a string in
   /// [displayStringForOption].
   ///
-  /// Simply uses the `toString` method on the option.
-  static String defaultStringForOption(dynamic option) {
+  /// Uses the `toString` method of the given `option`.
+  static String defaultStringForOption(Object? option) {
     return option.toString();
   }
 
diff --git a/framework/lib/src/widgets/autofill.dart b/framework/lib/src/widgets/autofill.dart
index a6d02ed..8f6ff16 100644
--- a/framework/lib/src/widgets/autofill.dart
+++ b/framework/lib/src/widgets/autofill.dart
@@ -70,7 +70,7 @@
     super.key,
     required this.child,
     this.onDisposeAction = AutofillContextAction.commit,
-  }) : assert(child != null);
+  });
 
   /// Returns the [AutofillGroupState] of the closest [AutofillGroup] widget
   /// which encloses the given context, or null if one cannot be found.
@@ -189,7 +189,6 @@
   /// * [EditableTextState.didChangeDependencies], where this method is called
   ///   to update the current [AutofillScope] when needed.
   void register(AutofillClient client) {
-    assert(client != null);
     _clients.putIfAbsent(client.autofillId, () => client);
   }
 
@@ -207,7 +206,7 @@
   ///   from the current [AutofillScope] when the widget is about to be removed
   ///   from the tree.
   void unregister(String autofillId) {
-    assert(autofillId != null && _clients.containsKey(autofillId));
+    assert(_clients.containsKey(autofillId));
     _clients.remove(autofillId);
   }
 
@@ -229,7 +228,7 @@
   void dispose() {
     super.dispose();
 
-    if (!_isTopmostAutofillGroup || widget.onDisposeAction == null) {
+    if (!_isTopmostAutofillGroup) {
       return;
     }
     switch (widget.onDisposeAction) {
diff --git a/framework/lib/src/widgets/automatic_keep_alive.dart b/framework/lib/src/widgets/automatic_keep_alive.dart
index 812cdf1..9377c4d 100644
--- a/framework/lib/src/widgets/automatic_keep_alive.dart
+++ b/framework/lib/src/widgets/automatic_keep_alive.dart
@@ -294,7 +294,7 @@
   /// Creates a notification to indicate that a subtree must be kept alive.
   ///
   /// The [handle] must not be null.
-  const KeepAliveNotification(this.handle) : assert(handle != null);
+  const KeepAliveNotification(this.handle);
 
   /// A [Listenable] that will inform its clients when the widget that fired the
   /// notification no longer needs to be kept alive.
diff --git a/framework/lib/src/widgets/banner.dart b/framework/lib/src/widgets/banner.dart
index 7aa24a7..dbdfd52 100644
--- a/framework/lib/src/widgets/banner.dart
+++ b/framework/lib/src/widgets/banner.dart
@@ -64,12 +64,7 @@
     required this.layoutDirection,
     this.color = _kColor,
     this.textStyle = _kTextStyle,
-  }) : assert(message != null),
-       assert(textDirection != null),
-       assert(location != null),
-       assert(color != null),
-       assert(textStyle != null),
-       super(repaint: PaintingBinding.instance.systemFonts);
+  }) : super(repaint: PaintingBinding.instance.systemFonts);
 
   /// The message to show in the banner.
   final String message;
@@ -169,8 +164,6 @@
   bool hitTest(Offset position) => false;
 
   double _translationX(double width) {
-    assert(location != null);
-    assert(layoutDirection != null);
     switch (layoutDirection) {
       case TextDirection.rtl:
         switch (location) {
@@ -198,7 +191,6 @@
   }
 
   double _translationY(double height) {
-    assert(location != null);
     switch (location) {
       case BannerLocation.bottomStart:
       case BannerLocation.bottomEnd:
@@ -210,8 +202,6 @@
   }
 
   double get _rotation {
-    assert(location != null);
-    assert(layoutDirection != null);
     switch (layoutDirection) {
       case TextDirection.rtl:
         switch (location) {
@@ -257,10 +247,7 @@
     this.layoutDirection,
     this.color = _kColor,
     this.textStyle = _kTextStyle,
-  }) : assert(message != null),
-       assert(location != null),
-       assert(color != null),
-       assert(textStyle != null);
+  });
 
   /// The widget to show behind the banner.
   ///
diff --git a/framework/lib/src/widgets/basic.dart b/framework/lib/src/widgets/basic.dart
index d85c4ae..29c0181 100644
--- a/framework/lib/src/widgets/basic.dart
+++ b/framework/lib/src/widgets/basic.dart
@@ -152,8 +152,7 @@
     super.key,
     required this.textDirection,
     required super.child,
-  }) : assert(textDirection != null),
-       assert(child != null);
+  });
 
   /// The text direction for this subtree.
   final TextDirection textDirection;
@@ -222,7 +221,7 @@
 ///
 /// For values of opacity other than 0.0 and 1.0, this class is relatively
 /// expensive because it requires painting the child into an intermediate
-/// buffer. For the value 0.0, the child is simply not painted at all. For the
+/// buffer. For the value 0.0, the child is not painted at all. For the
 /// value 1.0, the child is painted immediately without an intermediate buffer.
 ///
 /// The presence of the intermediate buffer which has a transparent background
@@ -311,8 +310,7 @@
     required this.opacity,
     this.alwaysIncludeSemantics = false,
     super.child,
-  }) : assert(opacity != null && opacity >= 0.0 && opacity <= 1.0),
-       assert(alwaysIncludeSemantics != null);
+  }) : assert(opacity >= 0.0 && opacity <= 1.0);
 
   /// The fraction to scale the child's alpha value.
   ///
@@ -400,8 +398,7 @@
     required this.shaderCallback,
     this.blendMode = BlendMode.modulate,
     super.child,
-  }) : assert(shaderCallback != null),
-       assert(blendMode != null);
+  });
 
   /// Called to create the [dart:ui.Shader] that generates the mask.
   ///
@@ -551,7 +548,7 @@
     required this.filter,
     super.child,
     this.blendMode = BlendMode.srcOver,
-  }) : assert(filter != null);
+  });
 
   /// The image filter to apply to the existing painted content before painting the child.
   ///
@@ -643,10 +640,7 @@
     this.isComplex = false,
     this.willChange = false,
     super.child,
-  }) : assert(size != null),
-       assert(isComplex != null),
-       assert(willChange != null),
-       assert(painter != null || foregroundPainter != null || (!isComplex && !willChange));
+  }) : assert(painter != null || foregroundPainter != null || (!isComplex && !willChange));
 
   /// The painter that paints before the children.
   final CustomPainter? painter;
@@ -763,7 +757,7 @@
     this.clipper,
     this.clipBehavior = Clip.hardEdge,
     super.child,
-  }) : assert(clipBehavior != null);
+  });
 
   /// If non-null, determines which clip to use.
   final CustomClipper<Rect>? clipper;
@@ -848,8 +842,7 @@
     this.clipper,
     this.clipBehavior = Clip.antiAlias,
     super.child,
-  }) : assert(borderRadius != null || clipper != null),
-       assert(clipBehavior != null);
+  }) : assert(borderRadius != null || clipper != null);
 
   /// The border radius of the rounded corners.
   ///
@@ -921,7 +914,7 @@
     this.clipper,
     this.clipBehavior = Clip.antiAlias,
     super.child,
-  }) : assert(clipBehavior != null);
+  });
 
   /// If non-null, determines which clip to use.
   ///
@@ -996,7 +989,7 @@
     this.clipper,
     this.clipBehavior = Clip.antiAlias,
     super.child,
-  }) : assert(clipBehavior != null);
+  });
 
   /// Creates a shape clip.
   ///
@@ -1008,8 +1001,6 @@
     Clip clipBehavior = Clip.antiAlias,
     Widget? child,
   }) {
-    assert(clipBehavior != null);
-    assert(shape != null);
     return Builder(
       key: key,
       builder: (BuildContext context) {
@@ -1093,11 +1084,7 @@
     required this.color,
     this.shadowColor = const Color(0xFF000000),
     super.child,
-  }) : assert(shape != null),
-       assert(elevation != null && elevation >= 0.0),
-       assert(color != null),
-       assert(shadowColor != null),
-       assert(clipBehavior != null);
+  }) : assert(elevation >= 0.0);
 
   /// The type of shape.
   final BoxShape shape;
@@ -1196,11 +1183,7 @@
     required this.color,
     this.shadowColor = const Color(0xFF000000),
     super.child,
-  }) : assert(clipper != null),
-       assert(clipBehavior != null),
-       assert(elevation != null && elevation >= 0.0),
-       assert(color != null),
-       assert(shadowColor != null);
+  }) : assert(elevation >= 0.0);
 
   /// Determines which clip to use.
   ///
@@ -1274,7 +1257,7 @@
 /// top right corner pinned to its original position.
 ///
 /// ```dart
-/// Container(
+/// ColoredBox(
 ///   color: Colors.black,
 ///   child: Transform(
 ///     alignment: Alignment.topRight,
@@ -1310,7 +1293,7 @@
     this.transformHitTests = true,
     this.filterQuality,
     super.child,
-  }) : assert(transform != null);
+  });
 
   /// Creates a widget that transforms its child using a rotation around the
   /// center.
@@ -1424,6 +1407,36 @@
         assert(scale == null || (scaleX == null && scaleY == null), "If 'scale' is non-null then 'scaleX' and 'scaleY' must be left null"),
         transform = Matrix4.diagonal3Values(scale ?? scaleX ?? 1.0, scale ?? scaleY ?? 1.0, 1.0);
 
+  /// Creates a widget that mirrors its child about the widget's center point.
+  ///
+  /// If `flipX` is true, the child widget will be flipped horizontally. Defaults to false.
+  ///
+  /// If `flipY` is true, the child widget will be flipped vertically. Defaults to false.
+  ///
+  /// If both are true, the child widget will be flipped both vertically and horizontally, equivalent to a 180 degree rotation.
+  ///
+  /// {@tool snippet}
+  ///
+  /// This example flips the text horizontally.
+  ///
+  /// ```dart
+  /// Transform.flip(
+  ///   flipX: true,
+  ///   child: const Text('Horizontal Flip'),
+  /// )
+  /// ```
+  /// {@end-tool}
+  Transform.flip({
+      super.key,
+      bool flipX = false,
+      bool flipY = false,
+      this.origin,
+      this.transformHitTests = true,
+      this.filterQuality,
+      super.child,
+  })  : alignment = Alignment.center,
+        transform = Matrix4.diagonal3Values(flipX ? -1.0 : 1.0, flipY ? -1.0 : 1.0, 1.0);
+
   // Computes a rotation matrix for an angle in radians, attempting to keep rotations
   // at integral values for angles of 0, π/2, π, 3π/2.
   static Matrix4 _computeRotation(double radians) {
@@ -1543,7 +1556,7 @@
     super.key,
     required this.link,
     super.child,
-  }) : assert(link != null);
+  });
 
   /// The link object that connects this [CompositedTransformTarget] with one or
   /// more [CompositedTransformFollower]s.
@@ -1608,11 +1621,7 @@
     this.targetAnchor = Alignment.topLeft,
     this.followerAnchor = Alignment.topLeft,
     super.child,
-  }) : assert(link != null),
-       assert(showWhenUnlinked != null),
-       assert(offset != null),
-       assert(targetAnchor != null),
-       assert(followerAnchor != null);
+  });
 
   /// The link object that connects this [CompositedTransformFollower] with a
   /// [CompositedTransformTarget].
@@ -1708,9 +1717,7 @@
     this.alignment = Alignment.center,
     this.clipBehavior = Clip.none,
     super.child,
-  }) : assert(fit != null),
-       assert(alignment != null),
-       assert(clipBehavior != null);
+  });
 
   /// How to inscribe the child into the space allocated during layout.
   final BoxFit fit;
@@ -1789,7 +1796,7 @@
     required this.translation,
     this.transformHitTests = true,
     super.child,
-  }) : assert(translation != null);
+  });
 
   /// The translation to apply to the child, scaled to the child's size.
   ///
@@ -1851,7 +1858,7 @@
     super.key,
     required this.quarterTurns,
     super.child,
-  }) : assert(quarterTurns != null);
+  });
 
   /// The number of clockwise quarter turns the child should be rotated.
   final int quarterTurns;
@@ -1896,7 +1903,7 @@
 /// ### Why use a [Padding] widget rather than a [Container] with a [Container.padding] property?
 ///
 /// There isn't really any difference between the two. If you supply a
-/// [Container.padding] argument, [Container] simply builds a [Padding] widget
+/// [Container.padding] argument, [Container] builds a [Padding] widget
 /// for you.
 ///
 /// [Container] doesn't implement its properties directly. Instead, [Container]
@@ -1907,7 +1914,7 @@
 /// convenient, feel free to use it. If not, feel free to build these simpler
 /// widgets in whatever combination meets your needs.
 ///
-/// In fact, the majority of widgets in Flutter are simply combinations of other
+/// In fact, the majority of widgets in Flutter are combinations of other
 /// simpler widgets. Composition, rather than inheritance, is the primary
 /// mechanism for building up widgets.
 ///
@@ -1925,7 +1932,7 @@
     super.key,
     required this.padding,
     super.child,
-  }) : assert(padding != null);
+  });
 
   /// The amount of space by which to inset the child.
   final EdgeInsetsGeometry padding;
@@ -2090,8 +2097,7 @@
     this.widthFactor,
     this.heightFactor,
     super.child,
-  }) : assert(alignment != null),
-       assert(widthFactor == null || widthFactor >= 0.0),
+  }) : assert(widthFactor == null || widthFactor >= 0.0),
        assert(heightFactor == null || heightFactor >= 0.0);
 
   /// How to align the child.
@@ -2199,7 +2205,7 @@
     super.key,
     required this.delegate,
     super.child,
-  }) : assert(delegate != null);
+  });
 
   /// The delegate that controls the layout of the child.
   final SingleChildLayoutDelegate delegate;
@@ -2228,9 +2234,7 @@
     Key? key,
     required this.id,
     required super.child,
-  }) : assert(child != null),
-       assert(id != null),
-       super(key: key ?? ValueKey<Object>(id));
+  }) : super(key: key ?? ValueKey<Object>(id));
 
   /// An object representing the identity of this child.
   ///
@@ -2300,11 +2304,11 @@
   /// Creates a custom multi-child layout.
   ///
   /// The [delegate] argument must not be null.
-  CustomMultiChildLayout({
+  const CustomMultiChildLayout({
     super.key,
     required this.delegate,
     super.children,
-  }) : assert(delegate != null);
+  });
 
   /// The delegate that controls the layout of the children.
   final MultiChildLayoutDelegate delegate;
@@ -2492,8 +2496,7 @@
     super.key,
     required this.constraints,
     super.child,
-  }) : assert(constraints != null),
-       assert(constraints.debugAssertIsValid());
+  }) : assert(constraints.debugAssertIsValid());
 
   /// The additional constraints to impose on the child.
   final BoxConstraints constraints;
@@ -2585,11 +2588,7 @@
     required this.constraintsTransform,
     this.clipBehavior = Clip.none,
     String debugTransformType = '',
-  }) : _debugTransformLabel = debugTransformType,
-       assert(alignment != null),
-       assert(clipBehavior != null),
-       assert(constraintsTransform != null),
-       assert(debugTransformType != null);
+  }) : _debugTransformLabel = debugTransformType;
 
   /// A [BoxConstraintsTransform] that always returns its argument as-is (i.e.,
   /// it is an identity function).
@@ -2774,8 +2773,7 @@
     this.alignment = Alignment.center,
     this.constrainedAxis,
     this.clipBehavior = Clip.none,
-  }) : assert(alignment != null),
-       assert(clipBehavior != null);
+  });
 
   /// The text direction to use when interpreting the [alignment] if it is an
   /// [AlignmentDirectional].
@@ -2876,8 +2874,7 @@
     this.widthFactor,
     this.heightFactor,
     super.child,
-  }) : assert(alignment != null),
-       assert(widthFactor == null || widthFactor >= 0.0),
+  }) : assert(widthFactor == null || widthFactor >= 0.0),
        assert(heightFactor == null || heightFactor >= 0.0);
 
   /// {@template flutter.widgets.basic.fractionallySizedBox.widthFactor}
@@ -2987,8 +2984,8 @@
     this.maxWidth = double.infinity,
     this.maxHeight = double.infinity,
     super.child,
-  }) : assert(maxWidth != null && maxWidth >= 0.0),
-       assert(maxHeight != null && maxHeight >= 0.0);
+  }) : assert(maxWidth >= 0.0),
+       assert(maxHeight >= 0.0);
 
   /// The maximum width limit to apply in the absence of a
   /// [BoxConstraints.maxWidth] constraint.
@@ -3140,8 +3137,7 @@
     required this.size,
     this.alignment = Alignment.center,
     super.child,
-  }) : assert(size != null),
-       assert(alignment != null);
+  });
 
   /// How to align the child.
   ///
@@ -3223,8 +3219,7 @@
 ///  * The [catalog of layout widgets](https://flutter.dev/widgets/layout/).
 class Offstage extends SingleChildRenderObjectWidget {
   /// Creates a widget that visually hides its child.
-  const Offstage({ super.key, this.offstage = true, super.child })
-    : assert(offstage != null);
+  const Offstage({ super.key, this.offstage = true, super.child });
 
   /// Whether the child is hidden from the rest of the tree.
   ///
@@ -3339,8 +3334,7 @@
     super.key,
     required this.aspectRatio,
     super.child,
-  }) : assert(aspectRatio != null),
-       assert(aspectRatio > 0.0);
+  }) : assert(aspectRatio > 0.0);
 
   /// The aspect ratio to attempt to use.
   ///
@@ -3505,8 +3499,7 @@
     required this.baseline,
     required this.baselineType,
     super.child,
-  }) : assert(baseline != null),
-       assert(baselineType != null);
+  });
 
   /// The number of logical pixels from the top of this box at which to position
   /// the child's baseline.
@@ -3584,8 +3577,7 @@
     super.key,
     required this.padding,
     Widget? sliver,
-  }) : assert(padding != null),
-       super(child: sliver);
+  }) : super(child: sliver);
 
   /// The amount of space by which to inset the child sliver.
   final EdgeInsetsGeometry padding;
@@ -3674,12 +3666,12 @@
   /// given axis.
   ///
   /// By default, the [mainAxis] is [Axis.vertical].
-  ListBody({
+  const ListBody({
     super.key,
     this.mainAxis = Axis.vertical,
     this.reverse = false,
     super.children,
-  }) : assert(mainAxis != null);
+  });
 
   /// The direction to use as the main axis.
   final Axis mainAxis;
@@ -3833,14 +3825,14 @@
   ///
   /// By default, the non-positioned children of the stack are aligned by their
   /// top left corners.
-  Stack({
+  const Stack({
     super.key,
     this.alignment = AlignmentDirectional.topStart,
     this.textDirection,
     this.fit = StackFit.loose,
     this.clipBehavior = Clip.hardEdge,
     super.children,
-  }) : assert(clipBehavior != null);
+  });
 
   /// How to align the non-positioned and partially-positioned children in the
   /// stack.
@@ -3949,7 +3941,7 @@
   /// Creates a [Stack] widget that paints a single child.
   ///
   /// The [index] argument must not be null.
-  IndexedStack({
+  const IndexedStack({
     super.key,
     super.alignment,
     super.textDirection,
@@ -4118,7 +4110,6 @@
     double? height,
     required Widget child,
   }) {
-    assert(textDirection != null);
     double? left;
     double? right;
     switch (textDirection) {
@@ -4399,8 +4390,8 @@
 ///
 /// Layout for a [Flex] proceeds in six steps:
 ///
-/// 1. Layout each child a null or zero flex factor (e.g., those that are not
-///    [Expanded]) with unbounded main axis constraints and the incoming
+/// 1. Layout each child with a null or zero flex factor (e.g., those that are
+///    not [Expanded]) with unbounded main axis constraints and the incoming
 ///    cross axis constraints. If the [crossAxisAlignment] is
 ///    [CrossAxisAlignment.stretch], instead use tight cross axis constraints
 ///    that match the incoming max extent in the cross axis.
@@ -4454,7 +4445,7 @@
   /// to be necessary to decide which direction to lay the children in or to
   /// disambiguate `start` or `end` values for the main or cross axis
   /// directions, the [textDirection] must not be null.
-  Flex({
+  const Flex({
     super.key,
     required this.direction,
     this.mainAxisAlignment = MainAxisAlignment.start,
@@ -4465,13 +4456,8 @@
     this.textBaseline, // NO DEFAULT: we don't know what the text's baseline should be
     this.clipBehavior = Clip.none,
     super.children,
-  }) : assert(direction != null),
-       assert(mainAxisAlignment != null),
-       assert(mainAxisSize != null),
-       assert(crossAxisAlignment != null),
-       assert(verticalDirection != null),
-       assert(crossAxisAlignment != CrossAxisAlignment.baseline || textBaseline != null, 'textBaseline is required if you specify the crossAxisAlignment with CrossAxisAlignment.baseline'),
-       assert(clipBehavior != null);
+  }) : assert(!identical(crossAxisAlignment, CrossAxisAlignment.baseline) || textBaseline != null, 'textBaseline is required if you specify the crossAxisAlignment with CrossAxisAlignment.baseline');
+  // Cannot use == in the assert above instead of identical because of https://github.com/dart-lang/language/issues/1811.
 
   /// The direction to use as the main axis.
   ///
@@ -4569,12 +4555,10 @@
   final Clip clipBehavior;
 
   bool get _needTextDirection {
-    assert(direction != null);
     switch (direction) {
       case Axis.horizontal:
         return true; // because it affects the layout order.
       case Axis.vertical:
-        assert(crossAxisAlignment != null);
         return crossAxisAlignment == CrossAxisAlignment.start
             || crossAxisAlignment == CrossAxisAlignment.end;
     }
@@ -4664,8 +4648,8 @@
 /// ![](https://flutter.github.io/assets-for-api-docs/assets/widgets/row.png)
 ///
 /// ```dart
-/// Row(
-///   children: const <Widget>[
+/// const Row(
+///   children: <Widget>[
 ///     Expanded(
 ///       child: Text('Deliver features faster', textAlign: TextAlign.center),
 ///     ),
@@ -4699,8 +4683,8 @@
 /// Suppose, for instance, that you had this code:
 ///
 /// ```dart
-/// Row(
-///   children: const <Widget>[
+/// const Row(
+///   children: <Widget>[
 ///     FlutterLogo(),
 ///     Text("Flutter's hot reload helps you quickly and easily experiment, build UIs, add features, and fix bug faster. Experience sub-second reload times, without losing state, on emulators, simulators, and hardware for iOS and Android."),
 ///     Icon(Icons.sentiment_very_satisfied),
@@ -4726,8 +4710,8 @@
 /// row that the child should be given the remaining room:
 ///
 /// ```dart
-/// Row(
-///   children: const <Widget>[
+/// const Row(
+///   children: <Widget>[
 ///     FlutterLogo(),
 ///     Expanded(
 ///       child: Text("Flutter's hot reload helps you quickly and easily experiment, build UIs, add features, and fix bug faster. Experience sub-second reload times, without losing state, on emulators, simulators, and hardware for iOS and Android."),
@@ -4755,9 +4739,9 @@
 /// [TextDirection.rtl]. This is shown in the example below
 ///
 /// ```dart
-/// Row(
+/// const Row(
 ///   textDirection: TextDirection.rtl,
-///   children: const <Widget>[
+///   children: <Widget>[
 ///     FlutterLogo(),
 ///     Expanded(
 ///       child: Text("Flutter's hot reload helps you quickly and easily experiment, build UIs, add features, and fix bug faster. Experience sub-second reload times, without losing state, on emulators, simulators, and hardware for iOS and Android."),
@@ -4776,8 +4760,8 @@
 ///
 /// Layout for a [Row] proceeds in six steps:
 ///
-/// 1. Layout each child a null or zero flex factor (e.g., those that are not
-///    [Expanded]) with unbounded horizontal constraints and the incoming
+/// 1. Layout each child with a null or zero flex factor (e.g., those that are
+///    not [Expanded]) with unbounded horizontal constraints and the incoming
 ///    vertical constraints. If the [crossAxisAlignment] is
 ///    [CrossAxisAlignment.stretch], instead use tight vertical constraints that
 ///    match the incoming max height.
@@ -4829,7 +4813,7 @@
   /// unless the row has no children or only one child) or to disambiguate
   /// `start` or `end` values for the [mainAxisAlignment], the [textDirection]
   /// must not be null.
-  Row({
+  const Row({
     super.key,
     super.mainAxisAlignment,
     super.mainAxisSize,
@@ -4866,8 +4850,8 @@
 /// ![Using the Column in this way creates two short lines of text with a large Flutter underneath.](https://flutter.github.io/assets-for-api-docs/assets/widgets/column.png)
 ///
 /// ```dart
-/// Column(
-///   children: const <Widget>[
+/// const Column(
+///   children: <Widget>[
 ///     Text('Deliver features faster'),
 ///     Text('Craft beautiful UIs'),
 ///     Expanded(
@@ -4967,8 +4951,8 @@
 ///
 /// Layout for a [Column] proceeds in six steps:
 ///
-/// 1. Layout each child a null or zero flex factor (e.g., those that are not
-///    [Expanded]) with unbounded vertical constraints and the incoming
+/// 1. Layout each child with a null or zero flex factor (e.g., those that are
+///    not [Expanded]) with unbounded vertical constraints and the incoming
 ///    horizontal constraints. If the [crossAxisAlignment] is
 ///    [CrossAxisAlignment.stretch], instead use tight horizontal constraints
 ///    that match the incoming max width.
@@ -5022,7 +5006,7 @@
   /// any. If there is no ambient directionality, and a text direction is going
   /// to be necessary to disambiguate `start` or `end` values for the
   /// [crossAxisAlignment], the [textDirection] must not be null.
-  Column({
+  const Column({
     super.key,
     super.mainAxisAlignment,
     super.mainAxisSize,
@@ -5229,7 +5213,7 @@
   /// to be necessary to decide which direction to lay the children in or to
   /// disambiguate `start` or `end` values for the main or cross axis
   /// directions, the [textDirection] must not be null.
-  Wrap({
+  const Wrap({
     super.key,
     this.direction = Axis.horizontal,
     this.alignment = WrapAlignment.start,
@@ -5241,7 +5225,7 @@
     this.verticalDirection = VerticalDirection.down,
     this.clipBehavior = Clip.none,
     super.children,
-  }) : assert(clipBehavior != null);
+  });
 
   /// The direction to use as the main axis.
   ///
@@ -5439,7 +5423,7 @@
 /// Rather than positioning the children during layout, the children are
 /// positioned using transformation matrices during the paint phase using the
 /// matrices from the [FlowDelegate.paintChildren] function. The children can be
-/// repositioned efficiently by simply repainting the flow, which happens
+/// repositioned efficiently by only _repainting_ the flow, which happens
 /// without the children being laid out again (contrast this with a [Stack],
 /// which does the sizing and positioning together during layout).
 ///
@@ -5481,9 +5465,7 @@
     required this.delegate,
     List<Widget> children = const <Widget>[],
     this.clipBehavior = Clip.hardEdge,
-  }) : assert(delegate != null),
-       assert(clipBehavior != null),
-       super(children: RepaintBoundary.wrapAll(children));
+  }) : super(children: RepaintBoundary.wrapAll(children));
        // https://github.com/dart-lang/sdk/issues/29277
 
   /// Creates a flow layout.
@@ -5493,13 +5475,12 @@
   /// a repaint boundary.
   ///
   /// The [delegate] argument must not be null.
-  Flow.unwrapped({
+  const Flow.unwrapped({
     super.key,
     required this.delegate,
     super.children,
     this.clipBehavior = Clip.hardEdge,
-  }) : assert(delegate != null),
-       assert(clipBehavior != null);
+  });
 
   /// The delegate that controls the transformation matrices of the children.
   final FlowDelegate delegate;
@@ -5626,13 +5607,7 @@
     this.textHeightBehavior,
     this.selectionRegistrar,
     this.selectionColor,
-  }) : assert(text != null),
-       assert(textAlign != null),
-       assert(softWrap != null),
-       assert(overflow != null),
-       assert(textScaleFactor != null),
-       assert(maxLines == null || maxLines > 0),
-       assert(textWidthBasis != null),
+  }) : assert(maxLines == null || maxLines > 0),
        assert(selectionRegistrar == null || selectionColor != null),
        super(children: _extractChildren(text));
 
@@ -5817,11 +5792,7 @@
     this.invertColors = false,
     this.filterQuality = FilterQuality.low,
     this.isAntiAlias = false,
-  }) : assert(scale != null),
-       assert(alignment != null),
-       assert(repeat != null),
-       assert(matchTextDirection != null),
-       assert(isAntiAlias != null);
+  });
 
   /// The image to display.
   ///
@@ -6094,8 +6065,7 @@
     super.key,
     required this.bundle,
     required super.child,
-  }) : assert(bundle != null),
-       assert(child != null);
+  });
 
   /// The bundle to use as a default.
   final AssetBundle bundle;
@@ -6138,12 +6108,7 @@
     required this.renderBox,
     this.onBuild,
     this.onUnmount,
-  }) : assert(renderBox != null),
-       // WidgetToRenderBoxAdapter objects are keyed to their render box. This
-       // prevents the widget being used in the widget hierarchy in two different
-       // places, which would cause the RenderBox to get inserted in multiple
-       // places in the RenderObject tree.
-       super(key: GlobalObjectKey(renderBox));
+  }) : super(key: GlobalObjectKey(renderBox));
 
   /// The render box to place in the widget tree.
   ///
@@ -6236,7 +6201,7 @@
     this.onPointerSignal,
     this.behavior = HitTestBehavior.deferToChild,
     super.child,
-  }) : assert(behavior != null);
+  });
 
   /// Called when a pointer comes into contact with the screen (for touch
   /// pointers), or has its button pressed (for mouse pointers) at this widget's
@@ -6375,8 +6340,7 @@
     this.opaque = true,
     this.hitTestBehavior,
     super.child,
-  }) : assert(cursor != null),
-       assert(opaque != null);
+  });
 
   /// Triggered when a mouse pointer has entered this widget.
   ///
@@ -6480,7 +6444,7 @@
   /// Because the widget conditionally creates the `MouseRegion`, and leaks the
   /// hover state, it needs to take the restriction into consideration. In this
   /// case, since it has access to the event that triggers the disappearance of
-  /// the `MouseRegion`, it simply trigger the exit callback during that event
+  /// the `MouseRegion`, it triggers the exit callback during that event
   /// as well.
   ///
   /// ** See code in examples/api/lib/widgets/basic/mouse_region.on_exit.1.dart **
@@ -6632,7 +6596,6 @@
   /// The key for the [RepaintBoundary] is derived either from the child's key
   /// (if the child has a non-null key) or from the given `childIndex`.
   factory RepaintBoundary.wrap(Widget child, int childIndex) {
-    assert(child != null);
     final Key key = child.key != null ? ValueKey<Key>(child.key!) : ValueKey<int>(childIndex);
     return RepaintBoundary(key: key, child: child);
   }
@@ -6687,7 +6650,7 @@
     this.ignoring = true,
     this.ignoringSemantics,
     super.child,
-  }) : assert(ignoring != null);
+  });
 
   /// Whether this widget is ignored during hit testing.
   ///
@@ -6756,7 +6719,7 @@
     this.absorbing = true,
     super.child,
     this.ignoringSemantics,
-  }) : assert(absorbing != null);
+  });
 
   /// Whether this widget absorbs pointers during hit testing.
   ///
@@ -7025,8 +6988,7 @@
     this.explicitChildNodes = false,
     this.excludeSemantics = false,
     required this.properties,
-  }) : assert(container != null),
-       assert(properties != null);
+  });
 
   /// Contains properties used by assistive technologies to make the application
   /// more accessible.
@@ -7217,7 +7179,7 @@
     super.key,
     this.excluding = true,
     super.child,
-  }) : assert(excluding != null);
+  });
 
   /// Whether this widget is excluded in the semantics tree.
   final bool excluding;
@@ -7277,7 +7239,7 @@
     super.key,
     required this.index,
     super.child,
-  }) : assert(index != null);
+  });
 
   /// The index used to annotate the first child semantics node.
   final int index;
@@ -7304,7 +7266,7 @@
   const KeyedSubtree({
     super.key,
     required this.child,
-  }) : assert(child != null);
+  });
 
   /// Creates a KeyedSubtree for child with a key that's based on the child's existing key or childIndex.
   factory KeyedSubtree.wrap(Widget child, int childIndex) {
@@ -7320,7 +7282,7 @@
   /// Wrap each item in a KeyedSubtree whose key is based on the item's existing key or
   /// the sum of its list index and `baseIndex`.
   static List<Widget> ensureUniqueKeysForList(List<Widget> items, { int baseIndex = 0 }) {
-    if (items == null || items.isEmpty) {
+    if (items.isEmpty) {
       return items;
     }
 
@@ -7373,8 +7335,8 @@
 /// )
 /// ```
 ///
-/// The difference between either of the previous examples and simply
-/// creating a child directly, without an intervening widget, is the
+/// The difference between either of the previous examples and
+/// creating a child directly without an intervening widget, is the
 /// extra [BuildContext] element that the additional widget adds. This
 /// is particularly noticeable when the tree contains an inherited
 /// widget that is referred to by a method like [Scaffold.of],
@@ -7433,7 +7395,7 @@
   const Builder({
     super.key,
     required this.builder,
-  }) : assert(builder != null);
+  });
 
   /// Called to obtain the child widget.
   ///
@@ -7506,7 +7468,7 @@
   const StatefulBuilder({
     super.key,
     required this.builder,
-  }) : assert(builder != null);
+  });
 
   /// Called to obtain the child widget.
   ///
@@ -7532,8 +7494,7 @@
   /// Creates a widget that paints its area with the specified [Color].
   ///
   /// The [color] parameter must not be null.
-  const ColoredBox({ required this.color, super.child, super.key })
-      : assert(color != null);
+  const ColoredBox({ required this.color, super.child, super.key });
 
   /// The color to paint the background area with.
   final Color color;
@@ -7566,7 +7527,6 @@
   Color get color => _color;
   Color _color;
   set color(Color value) {
-    assert(value != null);
     if (value == _color) {
       return;
     }
diff --git a/framework/lib/src/widgets/binding.dart b/framework/lib/src/widgets/binding.dart
index 934f5c4..7633f50 100644
--- a/framework/lib/src/widgets/binding.dart
+++ b/framework/lib/src/widgets/binding.dart
@@ -19,6 +19,7 @@
 import 'platform_menu_bar.dart';
 import 'router.dart';
 import 'service_extensions.dart';
+import 'view.dart';
 import 'widget_inspector.dart';
 
 export 'package:engine/ui.dart' show AppLifecycleState, Locale;
@@ -116,7 +117,9 @@
   ///   @override
   ///   void initState() {
   ///     super.initState();
-  ///     _lastSize = WidgetsBinding.instance.window.physicalSize;
+  ///     // [View.of] exposes the view from `WidgetsBinding.instance.platformDispatcher.views`
+  ///     // into which this widget is drawn.
+  ///     _lastSize = View.of(context).physicalSize;
   ///     WidgetsBinding.instance.addObserver(this);
   ///   }
   ///
@@ -128,7 +131,7 @@
   ///
   ///   @override
   ///   void didChangeMetrics() {
-  ///     setState(() { _lastSize = WidgetsBinding.instance.window.physicalSize; });
+  ///     setState(() { _lastSize = View.of(context).physicalSize; });
   ///   }
   ///
   ///   @override
@@ -185,7 +188,7 @@
   ///
   ///   @override
   ///   void didChangeTextScaleFactor() {
-  ///     setState(() { _lastTextScaleFactor = WidgetsBinding.instance.window.textScaleFactor; });
+  ///     setState(() { _lastTextScaleFactor = WidgetsBinding.instance.platformDispatcher.textScaleFactor; });
   ///   }
   ///
   ///   @override
@@ -896,6 +899,22 @@
   @override
   bool get framesEnabled => super.framesEnabled && _readyToProduceFrames;
 
+  /// Used by [runApp] to wrap the provided `rootWidget` in the default [View].
+  ///
+  /// The [View] determines into what [FlutterView] the app is rendered into.
+  /// For backwards-compatibility reasons, this method currently chooses
+  /// [window] (which is a [FlutterView]) as the rendering target. This will
+  /// change in a future version of Flutter.
+  ///
+  /// The `rootWidget` widget provided to this method must not already be
+  /// wrapped in a [View].
+  Widget wrapWithDefaultView(Widget rootWidget) {
+    return View(
+      view: window,
+      child: rootWidget,
+    );
+  }
+
   /// Schedules a [Timer] for attaching the root widget.
   ///
   /// This is called by [runApp] to configure the widget tree. Consider using
@@ -1014,8 +1033,9 @@
 ///  * [WidgetsBinding.handleBeginFrame], which pumps the widget pipeline to
 ///    ensure the widget, element, and render trees are all built.
 void runApp(Widget app) {
-  WidgetsFlutterBinding.ensureInitialized()
-    ..scheduleAttachRootWidget(app)
+  final WidgetsBinding binding = WidgetsFlutterBinding.ensureInitialized();
+  binding
+    ..scheduleAttachRootWidget(binding.wrapWithDefaultView(app))
     ..scheduleWarmUpFrame();
 }
 
diff --git a/framework/lib/src/widgets/bottom_navigation_bar_item.dart b/framework/lib/src/widgets/bottom_navigation_bar_item.dart
index 7684c07..82b2647 100644
--- a/framework/lib/src/widgets/bottom_navigation_bar_item.dart
+++ b/framework/lib/src/widgets/bottom_navigation_bar_item.dart
@@ -28,8 +28,7 @@
     Widget? activeIcon,
     this.backgroundColor,
     this.tooltip,
-  }) : activeIcon = activeIcon ?? icon,
-       assert(icon != null);
+  }) : activeIcon = activeIcon ?? icon;
 
   /// The icon of the item.
   ///
diff --git a/framework/lib/src/widgets/color_filter.dart b/framework/lib/src/widgets/color_filter.dart
index ccb790d..04ad22a 100644
--- a/framework/lib/src/widgets/color_filter.dart
+++ b/framework/lib/src/widgets/color_filter.dart
@@ -34,8 +34,7 @@
   /// Creates a widget that applies a [ColorFilter] to its child.
   ///
   /// The [colorFilter] must not be null.
-  const ColorFiltered({required this.colorFilter, super.child, super.key})
-      : assert(colorFilter != null);
+  const ColorFiltered({required this.colorFilter, super.child, super.key});
 
   /// The color filter to apply to the child of this widget.
   final ColorFilter colorFilter;
@@ -61,7 +60,6 @@
   ColorFilter get colorFilter => _colorFilter;
   ColorFilter _colorFilter;
   set colorFilter(ColorFilter value) {
-    assert(value != null);
     if (value != _colorFilter) {
       _colorFilter = value;
       markNeedsPaint();
diff --git a/framework/lib/src/widgets/container.dart b/framework/lib/src/widgets/container.dart
index 1a5f174..be46d4a 100644
--- a/framework/lib/src/widgets/container.dart
+++ b/framework/lib/src/widgets/container.dart
@@ -62,8 +62,7 @@
     required this.decoration,
     this.position = DecorationPosition.background,
     super.child,
-  }) : assert(decoration != null),
-       assert(position != null);
+  });
 
   /// What decoration to paint.
   ///
@@ -268,7 +267,6 @@
        assert(padding == null || padding.isNonNegative),
        assert(decoration == null || decoration.debugAssertIsValid()),
        assert(constraints == null || constraints.debugAssertIsValid()),
-       assert(clipBehavior != null),
        assert(decoration != null || clipBehavior == Clip.none),
        assert(color == null || decoration == null,
          'Cannot provide both a color and a decoration\n'
@@ -369,14 +367,14 @@
   final Clip clipBehavior;
 
   EdgeInsetsGeometry? get _paddingIncludingDecoration {
-    if (decoration == null || decoration!.padding == null) {
+    if (decoration == null) {
       return padding;
     }
-    final EdgeInsetsGeometry? decorationPadding = decoration!.padding;
+    final EdgeInsetsGeometry decorationPadding = decoration!.padding;
     if (padding == null) {
       return decorationPadding;
     }
-    return padding!.add(decorationPadding!);
+    return padding!.add(decorationPadding);
   }
 
   @override
@@ -464,8 +462,7 @@
   _DecorationClipper({
     TextDirection? textDirection,
     required this.decoration,
-  }) : assert(decoration != null),
-       textDirection = textDirection ?? TextDirection.ltr;
+  }) : textDirection = textDirection ?? TextDirection.ltr;
 
   final TextDirection textDirection;
   final Decoration decoration;
diff --git a/framework/lib/src/widgets/context_menu_button_item.dart b/framework/lib/src/widgets/context_menu_button_item.dart
index 2c5e723..c89bda3 100644
--- a/framework/lib/src/widgets/context_menu_button_item.dart
+++ b/framework/lib/src/widgets/context_menu_button_item.dart
@@ -23,6 +23,9 @@
   /// A button that selects all the contents of the focused text field.
   selectAll,
 
+  /// A button that deletes the current text selection.
+  delete,
+
   /// Anything other than the default button types.
   custom,
 }
diff --git a/framework/lib/src/widgets/context_menu_controller.dart b/framework/lib/src/widgets/context_menu_controller.dart
index bfc6f96..664c27d 100644
--- a/framework/lib/src/widgets/context_menu_controller.dart
+++ b/framework/lib/src/widgets/context_menu_controller.dart
@@ -18,6 +18,11 @@
 ///
 /// ** See code in examples/api/lib/material/context_menu/context_menu_controller.0.dart **
 /// {@end-tool}
+///
+/// See also:
+///
+///   * [BrowserContextMenu], which allows the browser's context menu on web to
+///     be disabled and Flutter-rendered context menus to appear.
 class ContextMenuController {
   /// Creates a context menu that can be shown with [show].
   ContextMenuController({
diff --git a/framework/lib/src/widgets/debug.dart b/framework/lib/src/widgets/debug.dart
index 43086e2..6ac04f9 100644
--- a/framework/lib/src/widgets/debug.dart
+++ b/framework/lib/src/widgets/debug.dart
@@ -10,6 +10,7 @@
 import 'basic.dart';
 import 'framework.dart';
 import 'localizations.dart';
+import 'lookup_boundary.dart';
 import 'media_query.dart';
 import 'overlay.dart';
 import 'table.dart';
@@ -169,7 +170,6 @@
 Key? _firstNonUniqueKey(Iterable<Widget> widgets) {
   final Set<Key> keySet = HashSet<Key>();
   for (final Widget widget in widgets) {
-    assert(widget != null);
     if (widget.key == null) {
       continue;
     }
@@ -468,12 +468,17 @@
 /// Does nothing if asserts are disabled. Always returns true.
 bool debugCheckHasOverlay(BuildContext context) {
   assert(() {
-    if (context.widget is! Overlay && context.findAncestorWidgetOfExactType<Overlay>() == null) {
+    if (LookupBoundary.findAncestorWidgetOfExactType<Overlay>(context) == null) {
+      final bool hiddenByBoundary = LookupBoundary.debugIsHidingAncestorWidgetOfExactType<Overlay>(context);
       throw FlutterError.fromParts(<DiagnosticsNode>[
-        ErrorSummary('No Overlay widget found.'),
+        ErrorSummary('No Overlay widget found${hiddenByBoundary ? ' within the closest LookupBoundary' : ''}.'),
+        if (hiddenByBoundary)
+          ErrorDescription(
+              'There is an ancestor Overlay widget, but it is hidden by a LookupBoundary.'
+          ),
         ErrorDescription(
           '${context.widget.runtimeType} widgets require an Overlay '
-          'widget ancestor.\n'
+          'widget ancestor within the closest LookupBoundary.\n'
           'An overlay lets widgets float on top of other widget children.',
         ),
         ErrorHint(
diff --git a/framework/lib/src/widgets/default_text_editing_shortcuts.dart b/framework/lib/src/widgets/default_text_editing_shortcuts.dart
index 46f8416..ae75678 100644
--- a/framework/lib/src/widgets/default_text_editing_shortcuts.dart
+++ b/framework/lib/src/widgets/default_text_editing_shortcuts.dart
@@ -37,15 +37,15 @@
 ///   // If using WidgetsApp or its descendents MaterialApp or CupertinoApp,
 ///   // then DefaultTextEditingShortcuts is already being inserted into the
 ///   // widget tree.
-///   return DefaultTextEditingShortcuts(
+///   return const DefaultTextEditingShortcuts(
 ///     child: Center(
 ///       child: Shortcuts(
-///         shortcuts: const <ShortcutActivator, Intent>{
+///         shortcuts: <ShortcutActivator, Intent>{
 ///           SingleActivator(LogicalKeyboardKey.arrowDown, alt: true): NextFocusIntent(),
 ///           SingleActivator(LogicalKeyboardKey.arrowUp, alt: true): PreviousFocusIntent(),
 ///         },
 ///         child: Column(
-///           children: const <Widget>[
+///           children: <Widget>[
 ///             TextField(
 ///               decoration: InputDecoration(
 ///                 hintText: 'alt + down moves to the next field.',
@@ -303,8 +303,8 @@
 
     const SingleActivator(LogicalKeyboardKey.arrowLeft, shift: true, alt: true): const ExtendSelectionToNextWordBoundaryOrCaretLocationIntent(forward: false),
     const SingleActivator(LogicalKeyboardKey.arrowRight, shift: true, alt: true): const ExtendSelectionToNextWordBoundaryOrCaretLocationIntent(forward: true),
-    const SingleActivator(LogicalKeyboardKey.arrowUp, shift: true, alt: true): const ExtendSelectionVerticallyToAdjacentLineIntent(forward: false, collapseSelection: false),
-    const SingleActivator(LogicalKeyboardKey.arrowDown, shift: true, alt: true): const ExtendSelectionVerticallyToAdjacentLineIntent(forward: true, collapseSelection: false),
+    const SingleActivator(LogicalKeyboardKey.arrowUp, shift: true, alt: true): const ExtendSelectionToNextParagraphBoundaryOrCaretLocationIntent(forward: false),
+    const SingleActivator(LogicalKeyboardKey.arrowDown, shift: true, alt: true): const ExtendSelectionToNextParagraphBoundaryOrCaretLocationIntent(forward: true),
 
     const SingleActivator(LogicalKeyboardKey.arrowLeft, meta: true): const ExtendSelectionToLineBreakIntent(forward: false, collapseSelection: true),
     const SingleActivator(LogicalKeyboardKey.arrowRight, meta: true): const ExtendSelectionToLineBreakIntent(forward: true, collapseSelection: true),
@@ -412,16 +412,10 @@
     SingleActivator(LogicalKeyboardKey.arrowLeft, alt: true): DoNothingAndStopPropagationTextIntent(),
     SingleActivator(LogicalKeyboardKey.arrowRight, alt: true): DoNothingAndStopPropagationTextIntent(),
     SingleActivator(LogicalKeyboardKey.arrowUp, alt: true): DoNothingAndStopPropagationTextIntent(),
-    SingleActivator(LogicalKeyboardKey.arrowLeft, shift: true, alt: true): DoNothingAndStopPropagationTextIntent(),
-    SingleActivator(LogicalKeyboardKey.arrowRight, shift: true, alt: true): DoNothingAndStopPropagationTextIntent(),
     SingleActivator(LogicalKeyboardKey.arrowDown, meta: true): DoNothingAndStopPropagationTextIntent(),
     SingleActivator(LogicalKeyboardKey.arrowLeft, meta: true): DoNothingAndStopPropagationTextIntent(),
     SingleActivator(LogicalKeyboardKey.arrowRight, meta: true): DoNothingAndStopPropagationTextIntent(),
     SingleActivator(LogicalKeyboardKey.arrowUp, meta: true): DoNothingAndStopPropagationTextIntent(),
-    SingleActivator(LogicalKeyboardKey.arrowLeft, shift: true, meta: true): DoNothingAndStopPropagationTextIntent(),
-    SingleActivator(LogicalKeyboardKey.arrowRight, shift: true, meta: true): DoNothingAndStopPropagationTextIntent(),
-    SingleActivator(LogicalKeyboardKey.arrowLeft, shift: true): DoNothingAndStopPropagationTextIntent(),
-    SingleActivator(LogicalKeyboardKey.arrowRight, shift: true): DoNothingAndStopPropagationTextIntent(),
     SingleActivator(LogicalKeyboardKey.pageUp, shift: true): DoNothingAndStopPropagationTextIntent(),
     SingleActivator(LogicalKeyboardKey.pageDown, shift: true): DoNothingAndStopPropagationTextIntent(),
     SingleActivator(LogicalKeyboardKey.end, shift: true): DoNothingAndStopPropagationTextIntent(),
@@ -452,6 +446,12 @@
     const SingleActivator(LogicalKeyboardKey.tab, shift: true): const DoNothingAndStopPropagationTextIntent(),
     const SingleActivator(LogicalKeyboardKey.arrowDown, shift: true, alt: true): const DoNothingAndStopPropagationTextIntent(),
     const SingleActivator(LogicalKeyboardKey.arrowUp, shift: true, alt: true): const DoNothingAndStopPropagationTextIntent(),
+    const SingleActivator(LogicalKeyboardKey.arrowLeft, shift: true): const DoNothingAndStopPropagationTextIntent(),
+    const SingleActivator(LogicalKeyboardKey.arrowRight, shift: true): const DoNothingAndStopPropagationTextIntent(),
+    const SingleActivator(LogicalKeyboardKey.arrowLeft, shift: true, alt: true): const DoNothingAndStopPropagationTextIntent(),
+    const SingleActivator(LogicalKeyboardKey.arrowRight, shift: true, alt: true): const DoNothingAndStopPropagationTextIntent(),
+    const SingleActivator(LogicalKeyboardKey.arrowLeft, shift: true, meta: true): const DoNothingAndStopPropagationTextIntent(),
+    const SingleActivator(LogicalKeyboardKey.arrowRight, shift: true, meta: true): const DoNothingAndStopPropagationTextIntent(),
   };
 
   // Hand backspace/delete events that do not depend on text layout (delete
@@ -560,8 +560,8 @@
 
     'moveWordLeftAndModifySelection:': ExtendSelectionToNextWordBoundaryOrCaretLocationIntent(forward: false),
     'moveWordRightAndModifySelection:': ExtendSelectionToNextWordBoundaryOrCaretLocationIntent(forward: true),
-    'moveParagraphBackwardAndModifySelection:': ExtendSelectionToLineBreakIntent(forward: false, collapseSelection: false, collapseAtReversal: true),
-    'moveParagraphForwardAndModifySelection:': ExtendSelectionToLineBreakIntent(forward: true, collapseSelection: false, collapseAtReversal: true),
+    'moveParagraphBackwardAndModifySelection:': ExtendSelectionToNextParagraphBoundaryOrCaretLocationIntent(forward: false),
+    'moveParagraphForwardAndModifySelection:': ExtendSelectionToNextParagraphBoundaryOrCaretLocationIntent(forward: true),
 
     'moveToLeftEndOfLine:': ExtendSelectionToLineBreakIntent(forward: false, collapseSelection: true),
     'moveToRightEndOfLine:': ExtendSelectionToLineBreakIntent(forward: true, collapseSelection: true),
diff --git a/framework/lib/src/widgets/dismissible.dart b/framework/lib/src/widgets/dismissible.dart
index 60c6777..037d577 100644
--- a/framework/lib/src/widgets/dismissible.dart
+++ b/framework/lib/src/widgets/dismissible.dart
@@ -112,9 +112,7 @@
     this.crossAxisEndOffset = 0.0,
     this.dragStartBehavior = DragStartBehavior.start,
     this.behavior = HitTestBehavior.opaque,
-  }) : assert(key != null),
-       assert(secondaryBackground == null || background != null),
-       assert(dragStartBehavior != null),
+  }) : assert(secondaryBackground == null || background != null),
        super(key: key);
 
   /// The widget below this widget in the tree.
@@ -263,16 +261,13 @@
   _DismissibleClipper({
     required this.axis,
     required this.moveAnimation,
-  }) : assert(axis != null),
-       assert(moveAnimation != null),
-       super(reclip: moveAnimation);
+  }) : super(reclip: moveAnimation);
 
   final Axis axis;
   final Animation<Offset> moveAnimation;
 
   @override
   Rect getClip(Size size) {
-    assert(axis != null);
     switch (axis) {
       case Axis.horizontal:
         final double offset = moveAnimation.value.dx * size.width;
@@ -480,7 +475,6 @@
   }
 
   _FlingGestureKind _describeFlingGesture(Velocity velocity) {
-    assert(widget.direction != null);
     if (_dragExtent == 0.0) {
       // If it was a fling, then it was a fling that was let loose at the exact
       // middle of the range (i.e. when there's no displacement). In that case,
@@ -506,7 +500,6 @@
       assert(vy != 0.0);
       flingDirection = _extentToDirection(vy);
     }
-    assert(_dismissDirection != null);
     if (flingDirection == _dismissDirection) {
       return _FlingGestureKind.forward;
     }
diff --git a/framework/lib/src/widgets/display_feature_sub_screen.dart b/framework/lib/src/widgets/display_feature_sub_screen.dart
index febb215..5253cc0 100644
--- a/framework/lib/src/widgets/display_feature_sub_screen.dart
+++ b/framework/lib/src/widgets/display_feature_sub_screen.dart
@@ -125,7 +125,7 @@
 
   /// Returns the areas of the screen that are obstructed by display features.
   ///
-  /// A [DisplayFeature] obstructs the screen when the the area it occupies is
+  /// A [DisplayFeature] obstructs the screen when the area it occupies is
   /// not 0 or the `state` is [DisplayFeatureState.postureHalfOpened].
   static Iterable<Rect> avoidBounds(MediaQueryData mediaQuery) {
     return mediaQuery.displayFeatures
diff --git a/framework/lib/src/widgets/disposable_build_context.dart b/framework/lib/src/widgets/disposable_build_context.dart
index 3a049ed..2283aca 100644
--- a/framework/lib/src/widgets/disposable_build_context.dart
+++ b/framework/lib/src/widgets/disposable_build_context.dart
@@ -28,8 +28,7 @@
   ///
   /// The [State] must not be null, and [State.mounted] must be true.
   DisposableBuildContext(T this._state)
-      : assert(_state != null),
-        assert(_state.mounted, 'A DisposableBuildContext was given a BuildContext for an Element that is not mounted.');
+      : assert(_state.mounted, 'A DisposableBuildContext was given a BuildContext for an Element that is not mounted.');
 
   T? _state;
 
diff --git a/framework/lib/src/widgets/drag_target.dart b/framework/lib/src/widgets/drag_target.dart
index 01a30db..950d30a 100644
--- a/framework/lib/src/widgets/drag_target.dart
+++ b/framework/lib/src/widgets/drag_target.dart
@@ -179,12 +179,8 @@
     this.ignoringFeedbackPointer = true,
     this.rootOverlay = false,
     this.hitTestBehavior = HitTestBehavior.deferToChild,
-  }) : assert(child != null),
-       assert(feedback != null),
-       assert(ignoringFeedbackSemantics != null),
-       assert(ignoringFeedbackPointer != null),
-       assert(maxSimultaneousDrags == null || maxSimultaneousDrags >= 0),
-       assert(dragAnchorStrategy != null);
+    this.allowedButtonsFilter,
+  }) : assert(maxSimultaneousDrags == null || maxSimultaneousDrags >= 0);
 
   /// The data that will be dropped by this draggable.
   final T? data;
@@ -359,6 +355,9 @@
   /// Defaults to [HitTestBehavior.deferToChild].
   final HitTestBehavior hitTestBehavior;
 
+  /// {@macro flutter.gestures.multidrag._allowedButtonsFilter}
+  final AllowedButtonsFilter? allowedButtonsFilter;
+
   /// Creates a gesture recognizer that recognizes the start of the drag.
   ///
   /// Subclasses can override this function to customize when they start
@@ -367,11 +366,11 @@
   MultiDragGestureRecognizer createRecognizer(GestureMultiDragStartCallback onStart) {
     switch (affinity) {
       case Axis.horizontal:
-        return HorizontalMultiDragGestureRecognizer()..onStart = onStart;
+        return HorizontalMultiDragGestureRecognizer(allowedButtonsFilter: allowedButtonsFilter)..onStart = onStart;
       case Axis.vertical:
-        return VerticalMultiDragGestureRecognizer()..onStart = onStart;
+        return VerticalMultiDragGestureRecognizer(allowedButtonsFilter: allowedButtonsFilter)..onStart = onStart;
       case null:
-        return ImmediateMultiDragGestureRecognizer()..onStart = onStart;
+        return ImmediateMultiDragGestureRecognizer(allowedButtonsFilter: allowedButtonsFilter)..onStart = onStart;
     }
   }
 
@@ -409,6 +408,7 @@
     super.ignoringFeedbackSemantics,
     super.ignoringFeedbackPointer,
     this.delay = kLongPressTimeout,
+    super.allowedButtonsFilter,
   });
 
   /// Whether haptic feedback should be triggered on drag start.
@@ -421,7 +421,7 @@
 
   @override
   DelayedMultiDragGestureRecognizer createRecognizer(GestureMultiDragStartCallback onStart) {
-    return DelayedMultiDragGestureRecognizer(delay: delay)
+    return DelayedMultiDragGestureRecognizer(delay: delay, allowedButtonsFilter: allowedButtonsFilter)
       ..onStart = (Offset position) {
         final Drag? result = onStart(position);
         if (result != null && hapticFeedbackOnStart) {
@@ -447,7 +447,7 @@
 
   @override
   void didChangeDependencies() {
-    _recognizer!.gestureSettings = MediaQuery.maybeOf(context)?.gestureSettings;
+    _recognizer!.gestureSettings = MediaQuery.maybeGestureSettingsOf(context);
     super.didChangeDependencies();
   }
 
@@ -561,8 +561,7 @@
     this.wasAccepted = false,
     required this.velocity,
     required this.offset,
-  }) : assert(velocity != null),
-       assert(offset != null);
+  });
 
   /// Determines whether the [DragTarget] accepted this draggable.
   final bool wasAccepted;
@@ -581,7 +580,7 @@
   /// Creates details for a [DragTarget] callback.
   ///
   /// The [offset] must not be null.
-  DragTargetDetails({required this.data, required this.offset}) : assert(offset != null);
+  DragTargetDetails({required this.data, required this.offset});
 
   /// The data that was dropped onto this [DragTarget].
   final T data;
@@ -729,7 +728,6 @@
 
   @override
   Widget build(BuildContext context) {
-    assert(widget.builder != null);
     return MetaData(
       metaData: this,
       behavior: widget.hitTestBehavior,
@@ -758,12 +756,7 @@
     this.onDragEnd,
     required this.ignoringFeedbackSemantics,
     required this.ignoringFeedbackPointer,
-  }) : assert(overlayState != null),
-       assert(ignoringFeedbackSemantics != null),
-       assert(ignoringFeedbackPointer != null),
-       assert(dragStartPoint != null),
-       assert(feedbackOffset != null),
-       _position = initialPosition {
+  }) : _position = initialPosition {
     _entry = OverlayEntry(builder: _build);
     overlayState.insert(_entry!);
     updateDrag(initialPosition);
diff --git a/framework/lib/src/widgets/draggable_scrollable_sheet.dart b/framework/lib/src/widgets/draggable_scrollable_sheet.dart
index 31dd48e..090fa0e 100644
--- a/framework/lib/src/widgets/draggable_scrollable_sheet.dart
+++ b/framework/lib/src/widgets/draggable_scrollable_sheet.dart
@@ -308,21 +308,16 @@
     this.snapAnimationDuration,
     this.controller,
     required this.builder,
-  })  : assert(initialChildSize != null),
-        assert(minChildSize != null),
-        assert(maxChildSize != null),
-        assert(minChildSize >= 0.0),
+  })  : assert(minChildSize >= 0.0),
         assert(maxChildSize <= 1.0),
         assert(minChildSize <= initialChildSize),
         assert(initialChildSize <= maxChildSize),
-        assert(snapAnimationDuration == null || snapAnimationDuration > Duration.zero),
-        assert(expand != null),
-        assert(builder != null);
+        assert(snapAnimationDuration == null || snapAnimationDuration > Duration.zero);
 
   /// The initial fractional value of the parent container's height to use when
   /// displaying the widget.
   ///
-  /// Rebuilding the sheet with a new [initialChildSize] will only move the
+  /// Rebuilding the sheet with a new [initialChildSize] will only move
   /// the sheet to the new value if the sheet has not yet been dragged since it
   /// was first built or since the last call to [DraggableScrollableActuator.reset].
   ///
@@ -436,17 +431,12 @@
     required this.maxExtent,
     required this.initialExtent,
     required this.context,
-  }) : assert(extent != null),
-       assert(initialExtent != null),
-       assert(minExtent != null),
-       assert(maxExtent != null),
-       assert(0.0 <= minExtent),
+  }) : assert(0.0 <= minExtent),
        assert(maxExtent <= 1.0),
        assert(minExtent <= extent),
        assert(minExtent <= initialExtent),
        assert(extent <= maxExtent),
-       assert(initialExtent <= maxExtent),
-       assert(context != null);
+       assert(initialExtent <= maxExtent);
 
   /// The current value of the extent, between [minExtent] and [maxExtent].
   final double extent;
@@ -496,10 +486,7 @@
     ValueNotifier<double>? currentSize,
     bool? hasDragged,
     bool? hasChanged,
-  })  : assert(minSize != null),
-        assert(maxSize != null),
-        assert(initialSize != null),
-        assert(minSize >= 0),
+  })  : assert(minSize >= 0),
         assert(maxSize <= 1),
         assert(minSize <= initialSize),
         assert(initialSize <= maxSize),
@@ -577,7 +564,6 @@
   /// This can be triggered by a programmatic (e.g. controller triggered) change
   /// or a user drag.
   void updateSize(double newSize, BuildContext context) {
-    assert(newSize != null);
     final double clampedSize = clampDouble(newSize, minSize, maxSize);
     if (_currentSize.value == clampedSize) {
       return;
@@ -783,7 +769,7 @@
 class _DraggableScrollableSheetScrollController extends ScrollController {
   _DraggableScrollableSheetScrollController({
     required this.extent,
-  }) : assert(extent != null);
+  });
 
   _DraggableSheetExtent extent;
   VoidCallback? onPositionDetached;
@@ -902,7 +888,7 @@
   bool get _isAtSnapSize {
     return extent.snapSizes.any(
       (double snapSize) {
-        return (extent.currentSize - snapSize).abs() <= extent.pixelsToSize(physics.tolerance.distance);
+        return (extent.currentSize - snapSize).abs() <= extent.pixelsToSize(physics.toleranceFor(this).distance);
       },
     );
   }
@@ -937,7 +923,7 @@
         initialVelocity: velocity,
         pixelSnapSize: extent.pixelSnapSizes,
         snapAnimationDuration: extent.snapAnimationDuration,
-        tolerance: physics.tolerance,
+        tolerance: physics.toleranceFor(this),
       );
     } else {
       // The iOS bouncing simulation just isn't right here - once we delegate
@@ -946,7 +932,7 @@
         // Run the simulation in terms of pixels, not extent.
         position: extent.currentPixels,
         velocity: velocity,
-        tolerance: physics.tolerance,
+        tolerance: physics.toleranceFor(this),
       );
     }
 
@@ -965,7 +951,7 @@
         // Make sure we pass along enough velocity to keep scrolling - otherwise
         // we just "bounce" off the top making it look like the list doesn't
         // have more to scroll.
-        velocity = ballisticController.velocity + (physics.tolerance.velocity * ballisticController.velocity.sign);
+        velocity = ballisticController.velocity + (physics.toleranceFor(this).velocity * ballisticController.velocity.sign);
         super.goBallistic(velocity);
         ballisticController.stop();
       } else if (ballisticController.isCompleted) {
diff --git a/framework/lib/src/widgets/dual_transition_builder.dart b/framework/lib/src/widgets/dual_transition_builder.dart
index 560a39b..7789479 100644
--- a/framework/lib/src/widgets/dual_transition_builder.dart
+++ b/framework/lib/src/widgets/dual_transition_builder.dart
@@ -41,9 +41,7 @@
     required this.forwardBuilder,
     required this.reverseBuilder,
     this.child,
-  }) : assert(animation != null),
-       assert(forwardBuilder != null),
-       assert(reverseBuilder != null);
+  });
 
   /// The animation that drives the [child]'s transition.
   ///
@@ -134,8 +132,6 @@
     required AnimationStatus lastEffective,
     required AnimationStatus current,
   }) {
-    assert(current != null);
-    assert(lastEffective != null);
     switch (current) {
       case AnimationStatus.dismissed:
       case AnimationStatus.completed:
diff --git a/framework/lib/src/widgets/editable_text.dart b/framework/lib/src/widgets/editable_text.dart
index 68b721b..95353e9 100644
--- a/framework/lib/src/widgets/editable_text.dart
+++ b/framework/lib/src/widgets/editable_text.dart
@@ -43,12 +43,14 @@
 import 'text_selection.dart';
 import 'text_selection_toolbar_anchors.dart';
 import 'ticker_provider.dart';
+import 'view.dart';
 import 'widget_span.dart';
 
-export 'package:flute/services.dart' show SelectionChangedCause, SmartDashesType, SmartQuotesType, TextEditingValue, TextInputType, TextSelection;
+export 'package:flute/services.dart' show KeyboardInsertedContent, SelectionChangedCause, SmartDashesType, SmartQuotesType, TextEditingValue, TextInputType, TextSelection;
 
 // Examples can assume:
 // late BuildContext context;
+// late WidgetTester tester;
 
 /// Signature for the callback that reports when the user changes the selection
 /// (including the cursor location).
@@ -69,6 +71,10 @@
   EditableTextState editableTextState,
 );
 
+// Signature for a function that determines the target location of the given
+// [TextPosition] after applying the given [TextBoundary].
+typedef _ApplyTextBoundary = TextPosition Function(TextPosition, bool, TextBoundary);
+
 // The time it takes for the cursor to fade from fully opaque to fully
 // transparent and vice versa. A full cursor blink, from transparent to opaque
 // to transparent, is twice this duration.
@@ -78,6 +84,64 @@
 // is shown in an obscured text field.
 const int _kObscureShowLatestCharCursorTicks = 3;
 
+/// The default mime types to be used when allowedMimeTypes is not provided.
+///
+/// The default value supports inserting images of any supported format.
+const List<String> kDefaultContentInsertionMimeTypes = <String>[
+  'image/png',
+  'image/bmp',
+  'image/jpg',
+  'image/tiff',
+  'image/gif',
+  'image/jpeg',
+  'image/webp'
+];
+
+class _CompositionCallback extends SingleChildRenderObjectWidget {
+  const _CompositionCallback({ required this.compositeCallback, required this.enabled, super.child });
+  final CompositionCallback compositeCallback;
+  final bool enabled;
+
+  @override
+  RenderObject createRenderObject(BuildContext context) {
+    return _RenderCompositionCallback(compositeCallback, enabled);
+  }
+  @override
+  void updateRenderObject(BuildContext context, _RenderCompositionCallback renderObject) {
+    super.updateRenderObject(context, renderObject);
+    // _EditableTextState always uses the same callback.
+    assert(renderObject.compositeCallback == compositeCallback);
+    renderObject.enabled = enabled;
+  }
+}
+
+class _RenderCompositionCallback extends RenderProxyBox {
+  _RenderCompositionCallback(this.compositeCallback, this._enabled);
+
+  final CompositionCallback compositeCallback;
+  VoidCallback? _cancelCallback;
+
+  bool get enabled => _enabled;
+  bool _enabled = false;
+  set enabled(bool newValue) {
+    _enabled = newValue;
+    if (!newValue) {
+      _cancelCallback?.call();
+      _cancelCallback = null;
+    } else if (_cancelCallback == null) {
+      markNeedsPaint();
+    }
+  }
+
+  @override
+  void paint(PaintingContext context, ui.Offset offset) {
+    if (enabled) {
+      _cancelCallback ??= context.addCompositionCallback(compositeCallback);
+    }
+    super.paint(context, offset);
+  }
+}
+
 /// A controller for an editable text field.
 ///
 /// Whenever the user modifies a text field with an associated
@@ -296,10 +360,7 @@
     this.cut = false,
     this.paste = false,
     this.selectAll = false,
-  }) : assert(copy != null),
-       assert(cut != null),
-       assert(paste != null),
-       assert(selectAll != null);
+  });
 
   /// An instance of [ToolbarOptions] with no options enabled.
   static const ToolbarOptions empty = ToolbarOptions();
@@ -329,6 +390,72 @@
   final bool selectAll;
 }
 
+/// Configures the ability to insert media content through the soft keyboard.
+///
+/// The configuration provides a handler for any rich content inserted through
+/// the system input method, and also provides the ability to limit the mime
+/// types of the inserted content.
+///
+/// See also:
+///
+/// * [EditableText.contentInsertionConfiguration]
+class ContentInsertionConfiguration {
+  /// Creates a content insertion configuration with the specified options.
+  ///
+  /// A handler for inserted content, in the form of [onContentInserted], must
+  /// be supplied.
+  ///
+  /// The allowable mime types of inserted content may also
+  /// be provided via [allowedMimeTypes], which cannot be an empty list.
+  ContentInsertionConfiguration({
+    required this.onContentInserted,
+    this.allowedMimeTypes = kDefaultContentInsertionMimeTypes,
+  }) : assert(allowedMimeTypes.isNotEmpty);
+
+  /// Called when a user inserts content through the virtual / on-screen keyboard,
+  /// currently only used on Android.
+  ///
+  /// [KeyboardInsertedContent] holds the data representing the inserted content.
+  ///
+  /// {@tool dartpad}
+  ///
+  /// This example shows how to access the data for inserted content in your
+  /// `TextField`.
+  ///
+  /// ** See code in examples/api/lib/widgets/editable_text/editable_text.on_content_inserted.0.dart **
+  /// {@end-tool}
+  ///
+  /// See also:
+  ///
+  ///  * <https://developer.android.com/guide/topics/text/image-keyboard>
+  final ValueChanged<KeyboardInsertedContent> onContentInserted;
+
+  /// {@template flutter.widgets.contentInsertionConfiguration.allowedMimeTypes}
+  /// Used when a user inserts image-based content through the device keyboard,
+  /// currently only used on Android.
+  ///
+  /// The passed list of strings will determine which MIME types are allowed to
+  /// be inserted via the device keyboard.
+  ///
+  /// The default mime types are given by [kDefaultContentInsertionMimeTypes].
+  /// These are all the mime types that are able to be handled and inserted
+  /// from keyboards.
+  ///
+  /// This field cannot be an empty list.
+  ///
+  /// {@tool dartpad}
+  /// This example shows how to limit image insertion to specific file types.
+  ///
+  /// ** See code in examples/api/lib/widgets/editable_text/editable_text.on_content_inserted.0.dart **
+  /// {@end-tool}
+  ///
+  /// See also:
+  ///
+  ///  * <https://developer.android.com/guide/topics/text/image-keyboard>
+  /// {@endtemplate}
+  final List<String> allowedMimeTypes;
+}
+
 // A time-value pair that represents a key frame in an animation.
 class _KeyFrame {
   const _KeyFrame(this.time, this.value);
@@ -675,44 +802,24 @@
     this.scrollBehavior,
     this.scribbleEnabled = true,
     this.enableIMEPersonalizedLearning = true,
+    this.contentInsertionConfiguration,
     this.contextMenuBuilder,
     this.spellCheckConfiguration,
     this.magnifierConfiguration = TextMagnifierConfiguration.disabled,
-  }) : assert(controller != null),
-       assert(focusNode != null),
-       assert(obscuringCharacter != null && obscuringCharacter.length == 1),
-       assert(obscureText != null),
-       assert(autocorrect != null),
+  }) : assert(obscuringCharacter.length == 1),
        smartDashesType = smartDashesType ?? (obscureText ? SmartDashesType.disabled : SmartDashesType.enabled),
        smartQuotesType = smartQuotesType ?? (obscureText ? SmartQuotesType.disabled : SmartQuotesType.enabled),
-       assert(enableSuggestions != null),
-       assert(showSelectionHandles != null),
-       assert(readOnly != null),
-       assert(forceLine != null),
-       assert(style != null),
-       assert(cursorColor != null),
-       assert(cursorOpacityAnimates != null),
-       assert(paintCursorAboveText != null),
-       assert(backgroundCursorColor != null),
-       assert(selectionHeightStyle != null),
-       assert(selectionWidthStyle != null),
-       assert(textAlign != null),
        assert(maxLines == null || maxLines > 0),
        assert(minLines == null || minLines > 0),
        assert(
          (maxLines == null) || (minLines == null) || (maxLines >= minLines),
          "minLines can't be greater than maxLines",
        ),
-       assert(expands != null),
        assert(
          !expands || (maxLines == null && minLines == null),
          'minLines and maxLines must be null when expands is true.',
        ),
        assert(!obscureText || maxLines == 1, 'Obscured fields cannot be multiline.'),
-       assert(autofocus != null),
-       assert(rendererIgnoresPointer != null),
-       assert(scrollPadding != null),
-       assert(dragStartBehavior != null),
        enableInteractiveSelection = enableInteractiveSelection ?? (!readOnly || !obscureText),
        toolbarOptions = selectionControls is TextSelectionHandleControls && toolbarOptions == null ? ToolbarOptions.empty : toolbarOptions ??
            (obscureText
@@ -738,8 +845,6 @@
                        selectAll: true,
                        paste: true,
                      ))),
-       assert(clipBehavior != null),
-       assert(enableIMEPersonalizedLearning != null),
        assert(
           spellCheckConfiguration == null ||
           spellCheckConfiguration == const SpellCheckConfiguration.disabled() ||
@@ -1232,6 +1337,18 @@
   /// By default, [onSubmitted] is called after [onChanged] when the user
   /// has finalized editing; or, if the default behavior has been overridden,
   /// after [onEditingComplete]. See [onEditingComplete] for details.
+  ///
+  /// ## Testing
+  /// The following is the recommended way to trigger [onSubmitted] in a test:
+  ///
+  /// ```dart
+  /// await tester.testTextInput.receiveAction(TextInputAction.done);
+  /// ```
+  ///
+  /// Sending a `LogicalKeyboardKey.enter` via `tester.sendKeyEvent` will not
+  /// trigger [onSubmitted]. This is because on a real device, the engine
+  /// translates the enter key to a done action, but `tester.sendKeyEvent` sends
+  /// the key to the framework only.
   /// {@endtemplate}
   final ValueChanged<String>? onSubmitted;
 
@@ -1595,6 +1712,37 @@
   /// {@macro flutter.services.TextInputConfiguration.enableIMEPersonalizedLearning}
   final bool enableIMEPersonalizedLearning;
 
+  /// {@template flutter.widgets.editableText.contentInsertionConfiguration}
+  /// Configuration of handler for media content inserted via the system input
+  /// method.
+  ///
+  /// Defaults to null in which case media content insertion will be disabled,
+  /// and the system will display a message informing the user that the text field
+  /// does not support inserting media content.
+  ///
+  /// Set [ContentInsertionConfiguration.onContentInserted] to provide a handler.
+  /// Additionally, set [ContentInsertionConfiguration.allowedMimeTypes]
+  /// to limit the allowable mime types for inserted content.
+  ///
+  /// {@tool dartpad}
+  ///
+  /// This example shows how to access the data for inserted content in your
+  /// `TextField`.
+  ///
+  /// ** See code in examples/api/lib/widgets/editable_text/editable_text.on_content_inserted.0.dart **
+  /// {@end-tool}
+  ///
+  /// If [contentInsertionConfiguration] is not provided, by default
+  /// an empty list of mime types will be sent to the Flutter Engine.
+  /// A handler function must be provided in order to customize the allowable
+  /// mime types for inserted content.
+  ///
+  /// If rich content is inserted without a handler, the system will display
+  /// a message informing the user that the current text input does not support
+  /// inserting rich content.
+  /// {@endtemplate}
+  final ContentInsertionConfiguration? contentInsertionConfiguration;
+
   /// {@template flutter.widgets.EditableText.contextMenuBuilder}
   /// Builds the text selection toolbar when requested by the user.
   ///
@@ -1625,6 +1773,8 @@
   ///   * [AdaptiveTextSelectionToolbar.getAdaptiveButtons], which builds the
   ///     button Widgets for the current platform given
   ///     [ContextMenuButtonItem]s.
+  ///   * [BrowserContextMenu], which allows the browser's context menu on web
+  ///     to be disabled and Flutter-rendered context menus to appear.
   /// {@endtemplate}
   ///
   /// If not provided, no context menu will be shown.
@@ -1883,6 +2033,7 @@
     properties.add(DiagnosticsProperty<bool>('enableIMEPersonalizedLearning', enableIMEPersonalizedLearning, defaultValue: true));
     properties.add(DiagnosticsProperty<bool>('enableInteractiveSelection', enableInteractiveSelection, defaultValue: true));
     properties.add(DiagnosticsProperty<SpellCheckConfiguration>('spellCheckConfiguration', spellCheckConfiguration, defaultValue: null));
+    properties.add(DiagnosticsProperty<List<String>>('contentCommitMimeTypes', contentInsertionConfiguration?.allowedMimeTypes ?? const <String>[], defaultValue: contentInsertionConfiguration == null ? const <String>[] : kDefaultContentInsertionMimeTypes));
   }
 }
 
@@ -1901,7 +2052,7 @@
   final GlobalKey _editableKey = GlobalKey();
 
   /// Detects whether the clipboard can paste.
-  final ClipboardStatusNotifier? clipboardStatus = kIsWeb ? null : ClipboardStatusNotifier();
+  final ClipboardStatusNotifier clipboardStatus = ClipboardStatusNotifier();
 
   TextInputConnection? _textInputConnection;
   bool get _hasInputConnection => _textInputConnection?.attached ?? false;
@@ -1947,7 +2098,9 @@
   /// These results will be updated via calls to spell check through a
   /// [SpellCheckService] and used by this widget to build the [TextSpan] tree
   /// for text input and menus for replacement suggestions of misspelled words.
-  SpellCheckResults? _spellCheckResults;
+  SpellCheckResults? spellCheckResults;
+
+  bool get _spellCheckResultsReceived => spellCheckEnabled && spellCheckResults != null && spellCheckResults!.suggestionSpans.isNotEmpty;
 
   /// Whether to create an input connection with the platform for text editing
   /// or not.
@@ -2002,8 +2155,7 @@
       return widget.toolbarOptions.paste && !widget.readOnly;
     }
     return !widget.readOnly
-        && (clipboardStatus == null
-          || clipboardStatus!.value == ClipboardStatus.pasteable);
+        && (clipboardStatus.value == ClipboardStatus.pasteable);
   }
 
   @override
@@ -2052,7 +2204,6 @@
   @override
   void copySelection(SelectionChangedCause cause) {
     final TextSelection selection = textEditingValue.selection;
-    assert(selection != null);
     if (selection.isCollapsed || widget.obscureText) {
       return;
     }
@@ -2081,7 +2232,7 @@
           break;
       }
     }
-    clipboardStatus?.update();
+    clipboardStatus.update();
   }
 
   /// Cut current selection to [Clipboard].
@@ -2092,7 +2243,6 @@
     }
     final TextSelection selection = textEditingValue.selection;
     final String text = textEditingValue.text;
-    assert(selection != null);
     if (selection.isCollapsed) {
       return;
     }
@@ -2107,7 +2257,7 @@
       });
       hideToolbar();
     }
-    clipboardStatus?.update();
+    clipboardStatus.update();
   }
 
   /// Paste text from [Clipboard].
@@ -2117,7 +2267,6 @@
       return;
     }
     final TextSelection selection = textEditingValue.selection;
-    assert(selection != null);
     if (!selection.isValid) {
       return;
     }
@@ -2190,6 +2339,63 @@
     }
   }
 
+  /// Replace composing region with specified text.
+  void replaceComposingRegion(SelectionChangedCause cause, String text) {
+    // Replacement cannot be performed if the text is read only or obscured.
+    assert(!widget.readOnly && !widget.obscureText);
+
+    _replaceText(ReplaceTextIntent(textEditingValue, text, textEditingValue.composing, cause));
+
+    if (cause == SelectionChangedCause.toolbar) {
+      // Schedule a call to bringIntoView() after renderEditable updates.
+      SchedulerBinding.instance.addPostFrameCallback((_) {
+        if (mounted) {
+          bringIntoView(textEditingValue.selection.extent);
+        }
+      });
+      hideToolbar();
+    }
+  }
+
+  /// Finds specified [SuggestionSpan] that matches the provided index using
+  /// binary search.
+  ///
+  /// See also:
+  ///
+  ///  * [SpellCheckSuggestionsToolbar], the Material style spell check
+  ///    suggestions toolbar that uses this method to render the correct
+  ///    suggestions in the toolbar for a misspelled word.
+  SuggestionSpan? findSuggestionSpanAtCursorIndex(int cursorIndex) {
+    if (!_spellCheckResultsReceived
+        || spellCheckResults!.suggestionSpans.last.range.end < cursorIndex) {
+      // No spell check results have been recieved or the cursor index is out
+      // of range that suggestionSpans covers.
+      return null;
+    }
+
+    final List<SuggestionSpan> suggestionSpans = spellCheckResults!.suggestionSpans;
+    int leftIndex = 0;
+    int rightIndex = suggestionSpans.length - 1;
+    int midIndex = 0;
+
+    while (leftIndex <= rightIndex) {
+      midIndex = ((leftIndex + rightIndex) / 2).floor();
+      final int currentSpanStart = suggestionSpans[midIndex].range.start;
+      final int currentSpanEnd = suggestionSpans[midIndex].range.end;
+
+      if (cursorIndex <= currentSpanEnd && cursorIndex >= currentSpanStart) {
+        return suggestionSpans[midIndex];
+      }
+      else if (cursorIndex <= currentSpanStart) {
+        rightIndex = midIndex - 1;
+      }
+      else {
+        leftIndex = midIndex + 1;
+      }
+    }
+    return null;
+  }
+
   /// Infers the [SpellCheckConfiguration] used to perform spell check.
   ///
   /// If spell check is enabled, this will try to infer a value for
@@ -2226,9 +2432,9 @@
       if (toolbarOptions.cut && cutEnabled)
         ContextMenuButtonItem(
           onPressed: () {
-            selectAll(SelectionChangedCause.toolbar);
+            cutSelection(SelectionChangedCause.toolbar);
           },
-          type: ContextMenuButtonType.selectAll,
+          type: ContextMenuButtonType.cut,
         ),
       if (toolbarOptions.copy && copyEnabled)
         ContextMenuButtonItem(
@@ -2237,7 +2443,7 @@
           },
           type: ContextMenuButtonType.copy,
         ),
-      if (toolbarOptions.paste && clipboardStatus != null && pasteEnabled)
+      if (toolbarOptions.paste && pasteEnabled)
         ContextMenuButtonItem(
           onPressed: () {
             pasteText(SelectionChangedCause.toolbar);
@@ -2269,7 +2475,7 @@
     final InlineSpan span = renderEditable.text!;
     final String prevText = span.toPlainText();
     final String currText = textEditingValue.text;
-    if (prevText != currText || selection == null || !selection.isValid || selection.isCollapsed) {
+    if (prevText != currText || !selection.isValid || selection.isCollapsed) {
       return _GlyphHeights(
         start: renderEditable.preferredLineHeight,
         end: renderEditable.preferredLineHeight,
@@ -2338,7 +2544,7 @@
   ///   button Widgets for the current platform given [ContextMenuButtonItem]s.
   List<ContextMenuButtonItem> get contextMenuButtonItems {
     return buttonItemsForToolbarOptions() ?? EditableText.getEditableButtonItems(
-      clipboardStatus: clipboardStatus?.value,
+      clipboardStatus: clipboardStatus.value,
       onCopy: copyEnabled
           ? () => copySelection(SelectionChangedCause.toolbar)
           : null,
@@ -2359,7 +2565,7 @@
   @override
   void initState() {
     super.initState();
-    clipboardStatus?.addListener(_onChangedClipboardStatus);
+    clipboardStatus.addListener(_onChangedClipboardStatus);
     widget.controller.addListener(_didChangeTextEditingValue);
     widget.focusNode.addListener(_handleFocusChanged);
     _scrollController.addListener(_onEditableScroll);
@@ -2386,6 +2592,7 @@
       _didAutoFocus = true;
       SchedulerBinding.instance.addPostFrameCallback((_) {
         if (mounted && renderEditable.hasSize) {
+          _flagInternalFocus();
           FocusScope.of(context).autofocus(widget.focusNode);
         }
       });
@@ -2409,7 +2616,7 @@
     }
 
     // Hide the text selection toolbar on mobile when orientation changes.
-    final Orientation orientation = MediaQuery.of(context).orientation;
+    final Orientation orientation = MediaQuery.orientationOf(context);
     if (_lastOrientation == null) {
       _lastOrientation = orientation;
       return;
@@ -2483,8 +2690,8 @@
     final bool canPaste = widget.selectionControls is TextSelectionHandleControls
         ? pasteEnabled
         : widget.selectionControls?.canPaste(this) ?? false;
-    if (widget.selectionEnabled && pasteEnabled && clipboardStatus != null && canPaste) {
-      clipboardStatus!.update();
+    if (widget.selectionEnabled && pasteEnabled && canPaste) {
+      clipboardStatus.update();
     }
   }
 
@@ -2505,9 +2712,10 @@
     _selectionOverlay = null;
     widget.focusNode.removeListener(_handleFocusChanged);
     WidgetsBinding.instance.removeObserver(this);
-    clipboardStatus?.removeListener(_onChangedClipboardStatus);
-    clipboardStatus?.dispose();
+    clipboardStatus.removeListener(_onChangedClipboardStatus);
+    clipboardStatus.dispose();
     _cursorVisibilityNotifier.dispose();
+    FocusManager.instance.removeListener(_unflagInternalFocus);
     super.dispose();
     assert(_batchEditDepth <= 0, 'unfinished batch edits: $_batchEditDepth');
   }
@@ -2562,9 +2770,12 @@
       // `selection` is the only change.
       _handleSelectionChanged(value.selection, (_textInputConnection?.scribbleInProgress ?? false) ? SelectionChangedCause.scribble : SelectionChangedCause.keyboard);
     } else {
-      // Only hide the toolbar overlay, the selection handle's visibility will be handled
-      // by `_handleSelectionChanged`. https://github.com/flutter/flutter/issues/108673
-      hideToolbar(false);
+      if (value.text != _value.text) {
+        // Hide the toolbar if the text was changed, but only hide the toolbar
+        // overlay; the selection handle's visibility will be handled
+        // by `_handleSelectionChanged`. https://github.com/flutter/flutter/issues/108673
+        hideToolbar(false);
+      }
       _currentPromptRectRange = null;
 
       final bool revealObscuredInput = _hasInputConnection
@@ -2635,6 +2846,12 @@
     widget.onAppPrivateCommand?.call(action, data);
   }
 
+  @override
+  void insertContent(KeyboardInsertedContent content) {
+    assert(widget.contentInsertionConfiguration?.allowedMimeTypes.contains(content.mimeType) ?? false);
+    widget.contentInsertionConfiguration?.onContentInserted.call(content);
+  }
+
   // The original position of the caret on FloatingCursorDragState.start.
   Rect? _startCaretRect;
 
@@ -2671,7 +2888,7 @@
         // we cache the position.
         _pointOffsetOrigin = point.offset;
 
-        final TextPosition currentTextPosition = TextPosition(offset: renderEditable.selection!.baseOffset);
+        final TextPosition currentTextPosition = TextPosition(offset: renderEditable.selection!.baseOffset, affinity: renderEditable.selection!.affinity);
         _startCaretRect = renderEditable.getLocalRectForCaret(currentTextPosition);
 
         _lastBoundedOffset = _startCaretRect!.center - _floatingCursorOffset;
@@ -2702,9 +2919,11 @@
     final Offset finalPosition = renderEditable.getLocalRectForCaret(_lastTextPosition!).centerLeft - _floatingCursorOffset;
     if (_floatingCursorResetController!.isCompleted) {
       renderEditable.setFloatingCursor(FloatingCursorDragState.End, finalPosition, _lastTextPosition!);
-      if (_lastTextPosition!.offset != renderEditable.selection!.baseOffset) {
+      // Only change if the current selection range is collapsed, to prevent
+      // overwriting the result of the iOS keyboard selection gesture.
+      if (renderEditable.selection!.isCollapsed) {
         // The cause is technically the force cursor, but the cause is listed as tap as the desired functionality is the same.
-        _handleSelectionChanged(TextSelection.collapsed(offset: _lastTextPosition!.offset), SelectionChangedCause.forcePress);
+        _handleSelectionChanged(TextSelection.fromPosition(_lastTextPosition!), SelectionChangedCause.forcePress);
       }
       _startCaretRect = null;
       _lastTextPosition = null;
@@ -2918,8 +3137,7 @@
         ? currentAutofillScope!.attach(this, _effectiveAutofillClient.textInputConfiguration)
         : TextInput.attach(this, _effectiveAutofillClient.textInputConfiguration);
       _updateSizeAndTransform();
-      _updateComposingRectIfNeeded();
-      _updateCaretRectIfNeeded();
+      _schedulePeriodicPostFrameCallbacks();
       final TextStyle style = widget.style;
       _textInputConnection!
         ..setStyle(
@@ -2947,6 +3165,7 @@
       _textInputConnection!.close();
       _textInputConnection = null;
       _lastKnownRemoteTextEditingValue = null;
+      removeTextPlaceholder();
     }
   }
 
@@ -3019,6 +3238,23 @@
     }
   }
 
+  // Indicates that a call to _handleFocusChanged originated within
+  // EditableText, allowing it to distinguish between internal and external
+  // focus changes.
+  bool _nextFocusChangeIsInternal = false;
+
+  // Sets _nextFocusChangeIsInternal to true only until any subsequent focus
+  // change happens.
+  void _flagInternalFocus() {
+    _nextFocusChangeIsInternal = true;
+    FocusManager.instance.addListener(_unflagInternalFocus);
+  }
+
+  void _unflagInternalFocus() {
+    _nextFocusChangeIsInternal = false;
+    FocusManager.instance.removeListener(_unflagInternalFocus);
+  }
+
   /// Express interest in interacting with the keyboard.
   ///
   /// If this control is already attached to the keyboard, this function will
@@ -3030,6 +3266,7 @@
     if (_hasFocus) {
       _openInputConnection();
     } else {
+      _flagInternalFocus();
       widget.focusNode.requestFocus(); // This eventually calls _openInputConnection also, see _handleFocusChanged.
     }
   }
@@ -3201,9 +3438,16 @@
       if (selection.isCollapsed) {
         rectToReveal = targetOffset.rect;
       } else {
-        final List<Rect> selectionBoxes = renderEditable.getBoxesForSelection(selection);
-        rectToReveal = selection.baseOffset < selection.extentOffset ?
-          selectionBoxes.last : selectionBoxes.first;
+        final List<TextBox> selectionBoxes = renderEditable.getBoxesForSelection(selection);
+        // selectionBoxes may be empty if, for example, the selection does not
+        // encompass a full character, like if it only contained part of an
+        // extended grapheme cluster.
+        if (selectionBoxes.isEmpty) {
+          rectToReveal = targetOffset.rect;
+        } else {
+          rectToReveal = selection.baseOffset < selection.extentOffset ?
+            selectionBoxes.last.toRect() : selectionBoxes.first.toRect();
+        }
       }
 
       if (withAnimation) {
@@ -3232,17 +3476,21 @@
 
   @override
   void didChangeMetrics() {
-    if (_lastBottomViewInset != WidgetsBinding.instance.window.viewInsets.bottom) {
+    if (!mounted) {
+      return;
+    }
+    final ui.FlutterView view = View.of(context);
+    if (_lastBottomViewInset != view.viewInsets.bottom) {
       SchedulerBinding.instance.addPostFrameCallback((Duration _) {
         _selectionOverlay?.updateForScroll();
       });
-      if (_lastBottomViewInset < WidgetsBinding.instance.window.viewInsets.bottom) {
+      if (_lastBottomViewInset < view.viewInsets.bottom) {
         // Because the metrics change signal from engine will come here every frame
         // (on both iOS and Android). So we don't need to show caret with animation.
         _scheduleShowCaretOnScreen(withAnimation: false);
       }
     }
-    _lastBottomViewInset = WidgetsBinding.instance.window.viewInsets.bottom;
+    _lastBottomViewInset = view.viewInsets.bottom;
   }
 
   Future<void> _performSpellCheck(final String text) async {
@@ -3254,17 +3502,17 @@
         'Locale must be specified in widget or Localization widget must be in scope',
       );
 
-      final List<SuggestionSpan>? spellCheckResults = await
+      final List<SuggestionSpan>? suggestions = await
         _spellCheckConfiguration
           .spellCheckService!
             .fetchSpellCheckSuggestions(localeForSpellChecking!, text);
 
-      if (spellCheckResults == null) {
+      if (suggestions == null) {
         // The request to fetch spell check suggestions was canceled due to ongoing request.
         return;
       }
 
-      _spellCheckResults = SpellCheckResults(text, spellCheckResults);
+      spellCheckResults = SpellCheckResults(text, suggestions);
       renderEditable.text = buildTextSpan();
     } catch (exception, stack) {
       FlutterError.reportError(FlutterErrorDetails(
@@ -3445,11 +3693,23 @@
     if (_hasFocus) {
       // Listen for changing viewInsets, which indicates keyboard showing up.
       WidgetsBinding.instance.addObserver(this);
-      _lastBottomViewInset = WidgetsBinding.instance.window.viewInsets.bottom;
+      _lastBottomViewInset = View.of(context).viewInsets.bottom;
       if (!widget.readOnly) {
         _scheduleShowCaretOnScreen(withAnimation: true);
       }
-      if (!_value.selection.isValid) {
+      final bool shouldSelectAll = widget.selectionEnabled && kIsWeb
+          && !_isMultiline && !_nextFocusChangeIsInternal;
+      if (shouldSelectAll) {
+        // On native web, single line <input> tags select all when receiving
+        // focus.
+        _handleSelectionChanged(
+          TextSelection(
+            baseOffset: 0,
+            extentOffset: _value.text.length,
+          ),
+          null,
+        );
+      } else if (!_value.selection.isValid) {
         // Place cursor at the end if the selection is invalid when we receive focus.
         _handleSelectionChanged(TextSelection.collapsed(offset: _value.text.length), null);
       }
@@ -3460,6 +3720,33 @@
     updateKeepAlive();
   }
 
+  void _compositeCallback(Layer layer) {
+    // The callback can be invoked when the layer is detached.
+    // The input connection can be closed by the platform in which case this
+    // widget doesn't rebuild.
+    if (!renderEditable.attached || !_hasInputConnection) {
+      return;
+    }
+    assert(mounted);
+    assert((context as Element).debugIsActive);
+    _updateSizeAndTransform();
+  }
+
+  void _updateSizeAndTransform() {
+    final Size size = renderEditable.size;
+    final Matrix4 transform = renderEditable.getTransformTo(null);
+    _textInputConnection!.setEditableSizeAndTransform(size, transform);
+  }
+
+  void _schedulePeriodicPostFrameCallbacks([Duration? duration]) {
+    if (!_hasInputConnection) {
+      return;
+    }
+    _updateSelectionRects();
+    _updateComposingRectIfNeeded();
+    _updateCaretRectIfNeeded();
+    SchedulerBinding.instance.addPostFrameCallback(_schedulePeriodicPostFrameCallbacks);
+  }
   _ScribbleCacheKey? _scribbleCacheKey;
 
   void _updateSelectionRects({bool force = false}) {
@@ -3501,11 +3788,11 @@
     final CharacterRange characterRange = CharacterRange(plainText);
     while (characterRange.moveNext()) {
       final int graphemeEnd = graphemeStart + characterRange.current.length;
-      final List<Rect> boxes = renderEditable.getBoxesForSelection(
+      final List<TextBox> boxes = renderEditable.getBoxesForSelection(
         TextSelection(baseOffset: graphemeStart, extentOffset: graphemeEnd),
       );
 
-      final Rect? box = boxes.isEmpty ? null : boxes.first;
+      final TextBox? box = boxes.isEmpty ? null : boxes.first;
       if (box != null) {
         final Rect paintBounds = renderEditable.paintBounds;
         // Stop early when characters are already below the bottom edge of the
@@ -3513,8 +3800,8 @@
         if (paintBounds.bottom <= box.top) {
           break;
         }
-        if (paintBounds.contains(box.topLeft) || paintBounds.contains(box.bottomRight)) {
-          rects.add(SelectionRect(position: graphemeStart, bounds: box));
+        if (paintBounds.contains(Offset(box.left, box.top)) || paintBounds.contains(Offset(box.right, box.bottom))) {
+          rects.add(SelectionRect(position: graphemeStart, bounds: box.toRect(), direction: box.direction));
         }
       }
       graphemeStart = graphemeEnd;
@@ -3522,18 +3809,6 @@
     _textInputConnection!.setSelectionRects(rects);
   }
 
-  void _updateSizeAndTransform() {
-    if (_hasInputConnection) {
-      final Size size = renderEditable.size;
-      final Matrix4 transform = renderEditable.getTransformTo(null);
-      _textInputConnection!.setEditableSizeAndTransform(size, transform);
-      _updateSelectionRects();
-      SchedulerBinding.instance.addPostFrameCallback((Duration _) => _updateSizeAndTransform());
-    } else if (_placeholderLocation != -1) {
-      removeTextPlaceholder();
-    }
-  }
-
   // Sends the current composing rect to the iOS text input plugin via the text
   // input channel. We need to keep sending the information even if no text is
   // currently marked, as the information usually lags behind. The text input
@@ -3541,49 +3816,39 @@
   // when the composing rect info didn't arrive in time.
   void _updateComposingRectIfNeeded() {
     final TextRange composingRange = _value.composing;
-    if (_hasInputConnection) {
-      assert(mounted);
-      Rect? composingRect = renderEditable.getRectForComposingRange(composingRange);
-      // Send the caret location instead if there's no marked text yet.
-      if (composingRect == null) {
-        assert(!composingRange.isValid || composingRange.isCollapsed);
-        final int offset = composingRange.isValid ? composingRange.start : 0;
-        composingRect = renderEditable.getLocalRectForCaret(TextPosition(offset: offset));
-      }
-      assert(composingRect != null);
-      _textInputConnection!.setComposingRect(composingRect);
-      SchedulerBinding.instance.addPostFrameCallback((Duration _) => _updateComposingRectIfNeeded());
+    assert(mounted);
+    Rect? composingRect = renderEditable.getRectForComposingRange(composingRange);
+    // Send the caret location instead if there's no marked text yet.
+    if (composingRect == null) {
+      assert(!composingRange.isValid || composingRange.isCollapsed);
+      final int offset = composingRange.isValid ? composingRange.start : 0;
+      composingRect = renderEditable.getLocalRectForCaret(TextPosition(offset: offset));
     }
+    _textInputConnection!.setComposingRect(composingRect);
   }
 
   void _updateCaretRectIfNeeded() {
-    if (_hasInputConnection) {
-      if (renderEditable.selection != null && renderEditable.selection!.isValid &&
-          renderEditable.selection!.isCollapsed) {
-        final TextPosition currentTextPosition = TextPosition(offset: renderEditable.selection!.baseOffset);
-        final Rect caretRect = renderEditable.getLocalRectForCaret(currentTextPosition);
-        _textInputConnection!.setCaretRect(caretRect);
-      }
-      SchedulerBinding.instance.addPostFrameCallback((Duration _) => _updateCaretRectIfNeeded());
+    final TextSelection? selection = renderEditable.selection;
+    if (selection == null || !selection.isValid || !selection.isCollapsed) {
+      return;
     }
+    final TextPosition currentTextPosition = TextPosition(offset: selection.baseOffset);
+    final Rect caretRect = renderEditable.getLocalRectForCaret(currentTextPosition);
+    _textInputConnection!.setCaretRect(caretRect);
   }
 
-  TextDirection get _textDirection {
-    final TextDirection result = widget.textDirection ?? Directionality.of(context);
-    assert(result != null, '$runtimeType created without a textDirection and with no ambient Directionality.');
-    return result;
-  }
+  TextDirection get _textDirection => widget.textDirection ?? Directionality.of(context);
 
   /// The renderer for this widget's descendant.
   ///
   /// This property is typically used to notify the renderer of input gestures
   /// when [RenderEditable.ignorePointer] is true.
-  RenderEditable get renderEditable => _editableKey.currentContext!.findRenderObject()! as RenderEditable;
+  late final RenderEditable renderEditable = _editableKey.currentContext!.findRenderObject()! as RenderEditable;
 
   @override
   TextEditingValue get textEditingValue => _value;
 
-  double get _devicePixelRatio => MediaQuery.of(context).devicePixelRatio;
+  double get _devicePixelRatio => MediaQuery.devicePixelRatioOf(context);
 
   @override
   void userUpdateTextEditingValue(TextEditingValue value, SelectionChangedCause? cause) {
@@ -3601,6 +3866,7 @@
     // unfocused field that previously had a selection in the same spot.
     if (value == textEditingValue) {
       if (!widget.focusNode.hasFocus) {
+        _flagInternalFocus();
         widget.focusNode.requestFocus();
         _selectionOverlay = _createSelectionOverlay();
       }
@@ -3626,17 +3892,18 @@
   @override
   bool showToolbar() {
     // Web is using native dom elements to enable clipboard functionality of the
-    // toolbar: copy, paste, select, cut. It might also provide additional
-    // functionality depending on the browser (such as translate). Due to this
-    // we should not show a Flutter toolbar for the editable text elements.
-    if (kIsWeb) {
+    // context menu: copy, paste, select, cut. It might also provide additional
+    // functionality depending on the browser (such as translate). Due to this,
+    // we should not show a Flutter toolbar for the editable text elements
+    // unless the browser's context menu is explicitly disabled.
+    if (kIsWeb && BrowserContextMenu.enabled) {
       return false;
     }
 
     if (_selectionOverlay == null) {
       return false;
     }
-    clipboardStatus?.update();
+    clipboardStatus.update();
     _selectionOverlay!.showToolbar();
     return true;
   }
@@ -3663,6 +3930,38 @@
     }
   }
 
+  /// Shows toolbar with spell check suggestions of misspelled words that are
+  /// available for click-and-replace.
+  bool showSpellCheckSuggestionsToolbar() {
+    if (!spellCheckEnabled
+        || widget.readOnly
+        || _selectionOverlay == null
+        || !_spellCheckResultsReceived) {
+      // Only attempt to show the spell check suggestions toolbar if there
+      // is a toolbar specified and spell check suggestions available to show.
+      return false;
+    }
+
+    assert(
+      _spellCheckConfiguration.spellCheckSuggestionsToolbarBuilder != null,
+      'spellCheckSuggestionsToolbarBuilder must be defined in '
+      'SpellCheckConfiguration to show a toolbar with spell check '
+      'suggestions',
+    );
+
+    _selectionOverlay!
+      .showSpellCheckSuggestionsToolbar(
+        (BuildContext context) {
+          return _spellCheckConfiguration
+            .spellCheckSuggestionsToolbarBuilder!(
+              context,
+              this,
+          );
+        },
+    );
+    return true;
+  }
+
   /// Shows the magnifier at the position given by `positionToShow`,
   /// if there is no magnifier visible.
   ///
@@ -3718,7 +4017,7 @@
 
   @override
   void removeTextPlaceholder() {
-    if (!widget.scribbleEnabled) {
+    if (!widget.scribbleEnabled || _placeholderLocation == -1) {
       return;
     }
 
@@ -3770,6 +4069,9 @@
       keyboardAppearance: widget.keyboardAppearance,
       autofillConfiguration: autofillConfiguration,
       enableIMEPersonalizedLearning: widget.enableIMEPersonalizedLearning,
+      allowedMimeTypes: widget.contentInsertionConfiguration == null
+        ? const <String>[]
+        : widget.contentInsertionConfiguration!.allowedMimeTypes,
     );
   }
 
@@ -3818,7 +4120,7 @@
         && (widget.selectionControls is TextSelectionHandleControls
             ? pasteEnabled
             : pasteEnabled && (widget.selectionControls?.canPaste(this) ?? false))
-        && (clipboardStatus == null || clipboardStatus!.value == ClipboardStatus.pasteable)
+        && (clipboardStatus.value == ClipboardStatus.pasteable)
       ? () {
         controls?.handlePaste(this);
         pasteText(SelectionChangedCause.toolbar);
@@ -3826,61 +4128,66 @@
       : null;
   }
 
+  // Returns the closest boundary location to `extent` but not including `extent`
+  // itself (unless already at the start/end of the text), in the direction
+  // specified by `forward`.
+  TextPosition _moveBeyondTextBoundary(TextPosition extent, bool forward, TextBoundary textBoundary) {
+    assert(extent.offset >= 0);
+    final int newOffset = forward
+      ? textBoundary.getTrailingTextBoundaryAt(extent.offset) ?? _value.text.length
+      // if x is a boundary defined by `textBoundary`, most textBoundaries (except
+      // LineBreaker) guarantees `x == textBoundary.getLeadingTextBoundaryAt(x)`.
+      // Use x - 1 here to make sure we don't get stuck at the fixed point x.
+      : textBoundary.getLeadingTextBoundaryAt(extent.offset - 1) ?? 0;
+    return TextPosition(offset: newOffset);
+  }
+
+  // Returns the closest boundary location to `extent`, including `extent`
+  // itself, in the direction specified by `forward`.
+  //
+  // This method returns a fixed point of itself: applying `_toTextBoundary`
+  // again on the returned TextPosition gives the same TextPosition. It's used
+  // exclusively for handling line boundaries, since performing "move to line
+  // start" more than once usually doesn't move you to the previous line.
+  TextPosition _moveToTextBoundary(TextPosition extent, bool forward, TextBoundary textBoundary) {
+    assert(extent.offset >= 0);
+    final int caretOffset;
+    switch (extent.affinity) {
+      case TextAffinity.upstream:
+        if (extent.offset < 1 && !forward) {
+          assert (extent.offset == 0);
+          return const TextPosition(offset: 0);
+        }
+        // When the text affinity is upstream, the caret is associated with the
+        // grapheme before the code unit at `extent.offset`.
+        // TODO(LongCatIsLooong): don't assume extent.offset is at a grapheme
+        // boundary, and do this instead:
+        // final int graphemeStart = CharacterRange.at(string, extent.offset).stringBeforeLength - 1;
+        caretOffset = math.max(0, extent.offset - 1);
+        break;
+      case TextAffinity.downstream:
+        caretOffset = extent.offset;
+        break;
+    }
+    // The line boundary range does not include some control characters
+    // (most notably, Line Feed), in which case there's
+    // `x ∉ getTextBoundaryAt(x)`. In case `caretOffset` points to one such
+    // control character, we define that these control characters themselves are
+    // still part of the previous line, but also exclude them from the
+    // line boundary range since they're non-printing. IOW, no additional
+    // processing needed since the LineBoundary class does exactly that.
+    return forward
+      ? TextPosition(offset: textBoundary.getTrailingTextBoundaryAt(caretOffset) ?? _value.text.length, affinity: TextAffinity.upstream)
+      : TextPosition(offset: textBoundary.getLeadingTextBoundaryAt(caretOffset) ?? 0);
+  }
 
   // --------------------------- Text Editing Actions ---------------------------
 
-  TextBoundary _characterBoundary(DirectionalTextEditingIntent intent) {
-    final TextBoundary atomicTextBoundary = widget.obscureText ? _CodeUnitBoundary(_value.text) : CharacterBoundary(_value.text);
-    return intent.forward ? PushTextPosition.forward + atomicTextBoundary : PushTextPosition.backward + atomicTextBoundary;
-  }
-
-  TextBoundary _nextWordBoundary(DirectionalTextEditingIntent intent) {
-    final TextBoundary atomicTextBoundary;
-    final TextBoundary boundary;
-
-    if (widget.obscureText) {
-      atomicTextBoundary = _CodeUnitBoundary(_value.text);
-      boundary = DocumentBoundary(_value.text);
-    } else {
-      final TextEditingValue textEditingValue = _textEditingValueforTextLayoutMetrics;
-      atomicTextBoundary = CharacterBoundary(textEditingValue.text);
-      // This isn't enough. Newline characters.
-      boundary = WhitespaceBoundary(textEditingValue.text) + WordBoundary(renderEditable);
-    }
-
-    final _MixedBoundary mixedBoundary = intent.forward
-      ? _MixedBoundary(atomicTextBoundary, boundary)
-      : _MixedBoundary(boundary, atomicTextBoundary);
-    // Use a _MixedBoundary to make sure we don't leave invalid codepoints in
-    // the field after deletion.
-    return intent.forward ? PushTextPosition.forward + mixedBoundary : PushTextPosition.backward + mixedBoundary;
-  }
-
-  TextBoundary _linebreak(DirectionalTextEditingIntent intent) {
-    final TextBoundary atomicTextBoundary;
-    final TextBoundary boundary;
-
-    if (widget.obscureText) {
-      atomicTextBoundary = _CodeUnitBoundary(_value.text);
-      boundary = DocumentBoundary(_value.text);
-    } else {
-      final TextEditingValue textEditingValue = _textEditingValueforTextLayoutMetrics;
-      atomicTextBoundary = CharacterBoundary(textEditingValue.text);
-      boundary = LineBreak(renderEditable);
-    }
-
-    // The _MixedBoundary is to make sure we don't leave invalid code units in
-    // the field after deletion.
-    // `boundary` doesn't need to be wrapped in a _CollapsedSelectionBoundary,
-    // since the document boundary is unique and the linebreak boundary is
-    // already caret-location based.
-    final TextBoundary pushed = intent.forward
-      ? PushTextPosition.forward + atomicTextBoundary
-      : PushTextPosition.backward + atomicTextBoundary;
-    return intent.forward ? _MixedBoundary(pushed, boundary) : _MixedBoundary(boundary, pushed);
-  }
-
-  TextBoundary _documentBoundary(DirectionalTextEditingIntent intent) => DocumentBoundary(_value.text);
+  TextBoundary _characterBoundary() => widget.obscureText ? _CodeUnitBoundary(_value.text) : CharacterBoundary(_value.text);
+  TextBoundary _nextWordBoundary() => widget.obscureText ? _documentBoundary() : renderEditable.wordBoundaries.moveByWordBoundary;
+  TextBoundary _linebreak() => widget.obscureText ? _documentBoundary() : LineBoundary(renderEditable);
+  TextBoundary _paragraphBoundary() => ParagraphBoundary(_value.text);
+  TextBoundary _documentBoundary() => DocumentBoundary(_value.text);
 
   Action<T> _makeOverridable<T extends Intent>(Action<T> defaultAction) {
     return Action<T>.overridable(context: context, defaultAction: defaultAction);
@@ -3895,7 +4202,6 @@
   /// When the cursor is at the start of the text, does nothing.
   void _transposeCharacters(TransposeCharactersIntent intent) {
     if (_value.text.characters.length <= 1
-        || _value.selection == null
         || !_value.selection.isCollapsed
         || _value.selection.baseOffset == 0) {
       return;
@@ -4057,40 +4363,6 @@
   late final _UpdateTextSelectionVerticallyAction<DirectionalCaretMovementIntent> _verticalSelectionUpdateAction =
       _UpdateTextSelectionVerticallyAction<DirectionalCaretMovementIntent>(this);
 
-  void _expandSelectionToDocumentBoundary(ExpandSelectionToDocumentBoundaryIntent intent) {
-    final TextBoundary textBoundary = _documentBoundary(intent);
-    _expandSelection(intent.forward, textBoundary, true);
-  }
-
-  void _expandSelectionToLinebreak(ExpandSelectionToLineBreakIntent intent) {
-    final TextBoundary textBoundary = _linebreak(intent);
-    _expandSelection(intent.forward, textBoundary);
-  }
-
-  void _expandSelection(bool forward, TextBoundary textBoundary, [bool extentAtIndex = false]) {
-    final TextSelection textBoundarySelection = _value.selection;
-    if (!textBoundarySelection.isValid) {
-      return;
-    }
-
-    final bool inOrder = textBoundarySelection.baseOffset <= textBoundarySelection.extentOffset;
-    final bool towardsExtent = forward == inOrder;
-    final TextPosition position = towardsExtent
-        ? textBoundarySelection.extent
-        : textBoundarySelection.base;
-
-    final TextPosition newExtent = forward
-      ? textBoundary.getTrailingTextBoundaryAt(position)
-      : textBoundary.getLeadingTextBoundaryAt(position);
-
-    final TextSelection newSelection = textBoundarySelection.expandTo(newExtent, textBoundarySelection.isCollapsed || extentAtIndex);
-    userUpdateTextEditingValue(
-      _value.copyWith(selection: newSelection),
-      SelectionChangedCause.keyboard,
-    );
-    bringIntoView(newSelection.extent);
-  }
-
   Object? _hideToolbarIfVisible(DismissIntent intent) {
     if (_selectionOverlay?.toolbarIsVisible ?? false) {
       hideToolbar(false);
@@ -4144,24 +4416,27 @@
     DismissIntent: CallbackAction<DismissIntent>(onInvoke: _hideToolbarIfVisible),
 
     // Delete
-    DeleteCharacterIntent: _makeOverridable(_DeleteTextAction<DeleteCharacterIntent>(this, _characterBoundary)),
-    DeleteToNextWordBoundaryIntent: _makeOverridable(_DeleteTextAction<DeleteToNextWordBoundaryIntent>(this, _nextWordBoundary)),
-    DeleteToLineBreakIntent: _makeOverridable(_DeleteTextAction<DeleteToLineBreakIntent>(this, _linebreak)),
+    DeleteCharacterIntent: _makeOverridable(_DeleteTextAction<DeleteCharacterIntent>(this, _characterBoundary, _moveBeyondTextBoundary)),
+    DeleteToNextWordBoundaryIntent: _makeOverridable(_DeleteTextAction<DeleteToNextWordBoundaryIntent>(this, _nextWordBoundary, _moveBeyondTextBoundary)),
+    DeleteToLineBreakIntent: _makeOverridable(_DeleteTextAction<DeleteToLineBreakIntent>(this, _linebreak, _moveToTextBoundary)),
 
     // Extend/Move Selection
-    ExtendSelectionByCharacterIntent: _makeOverridable(_UpdateTextSelectionAction<ExtendSelectionByCharacterIntent>(this, false, _characterBoundary)),
+    ExtendSelectionByCharacterIntent: _makeOverridable(_UpdateTextSelectionAction<ExtendSelectionByCharacterIntent>(this, _characterBoundary, _moveBeyondTextBoundary, ignoreNonCollapsedSelection: false)),
     ExtendSelectionByPageIntent: _makeOverridable(CallbackAction<ExtendSelectionByPageIntent>(onInvoke: _extendSelectionByPage)),
-    ExtendSelectionToNextWordBoundaryIntent: _makeOverridable(_UpdateTextSelectionAction<ExtendSelectionToNextWordBoundaryIntent>(this, true, _nextWordBoundary)),
-    ExtendSelectionToLineBreakIntent: _makeOverridable(_UpdateTextSelectionAction<ExtendSelectionToLineBreakIntent>(this, true, _linebreak)),
-    ExpandSelectionToLineBreakIntent: _makeOverridable(CallbackAction<ExpandSelectionToLineBreakIntent>(onInvoke: _expandSelectionToLinebreak)),
-    ExpandSelectionToDocumentBoundaryIntent: _makeOverridable(CallbackAction<ExpandSelectionToDocumentBoundaryIntent>(onInvoke: _expandSelectionToDocumentBoundary)),
+    ExtendSelectionToNextWordBoundaryIntent: _makeOverridable(_UpdateTextSelectionAction<ExtendSelectionToNextWordBoundaryIntent>(this, _nextWordBoundary, _moveBeyondTextBoundary, ignoreNonCollapsedSelection: true)),
+    ExtendSelectionToLineBreakIntent: _makeOverridable(_UpdateTextSelectionAction<ExtendSelectionToLineBreakIntent>(this, _linebreak, _moveToTextBoundary, ignoreNonCollapsedSelection: true)),
     ExtendSelectionVerticallyToAdjacentLineIntent: _makeOverridable(_verticalSelectionUpdateAction),
     ExtendSelectionVerticallyToAdjacentPageIntent: _makeOverridable(_verticalSelectionUpdateAction),
-    ExtendSelectionToDocumentBoundaryIntent: _makeOverridable(_UpdateTextSelectionAction<ExtendSelectionToDocumentBoundaryIntent>(this, true, _documentBoundary)),
-    ExtendSelectionToNextWordBoundaryOrCaretLocationIntent: _makeOverridable(_ExtendSelectionOrCaretPositionAction(this, _nextWordBoundary)),
+    ExtendSelectionToNextParagraphBoundaryOrCaretLocationIntent: _makeOverridable(_UpdateTextSelectionAction<ExtendSelectionToNextParagraphBoundaryOrCaretLocationIntent>(this, _paragraphBoundary, _moveBeyondTextBoundary, ignoreNonCollapsedSelection: true)),
+    ExtendSelectionToDocumentBoundaryIntent: _makeOverridable(_UpdateTextSelectionAction<ExtendSelectionToDocumentBoundaryIntent>(this, _documentBoundary, _moveBeyondTextBoundary, ignoreNonCollapsedSelection: true)),
+    ExtendSelectionToNextWordBoundaryOrCaretLocationIntent: _makeOverridable(_UpdateTextSelectionAction<ExtendSelectionToNextWordBoundaryOrCaretLocationIntent>(this, _nextWordBoundary, _moveBeyondTextBoundary, ignoreNonCollapsedSelection: true)),
     ScrollToDocumentBoundaryIntent: _makeOverridable(CallbackAction<ScrollToDocumentBoundaryIntent>(onInvoke: _scrollToDocumentBoundary)),
     ScrollIntent: CallbackAction<ScrollIntent>(onInvoke: _scroll),
 
+    // Expand Selection
+    ExpandSelectionToLineBreakIntent: _makeOverridable(_UpdateTextSelectionAction<ExpandSelectionToLineBreakIntent>(this, _linebreak, _moveToTextBoundary, ignoreNonCollapsedSelection: true, isExpand: true)),
+    ExpandSelectionToDocumentBoundaryIntent: _makeOverridable(_UpdateTextSelectionAction<ExpandSelectionToDocumentBoundaryIntent>(this, _documentBoundary, _moveToTextBoundary, ignoreNonCollapsedSelection: true, isExpand: true, extentAtIndex: true)),
+
     // Copy Paste
     SelectAllTextIntent: _makeOverridable(_SelectAllAction(this)),
     CopySelectionTextIntent: _makeOverridable(_CopySelectionAction(this)),
@@ -4176,100 +4451,104 @@
     super.build(context); // See AutomaticKeepAliveClientMixin.
 
     final TextSelectionControls? controls = widget.selectionControls;
-    return TextFieldTapRegion(
-      onTapOutside: widget.onTapOutside ?? _defaultOnTapOutside,
-      debugLabel: kReleaseMode ? null : 'EditableText',
-      child: MouseRegion(
-        cursor: widget.mouseCursor ?? SystemMouseCursors.text,
-        child: Actions(
-          actions: _actions,
-          child: _TextEditingHistory(
-            controller: widget.controller,
-            onTriggered: (TextEditingValue value) {
-              userUpdateTextEditingValue(value, SelectionChangedCause.keyboard);
-            },
-            child: Focus(
-              focusNode: widget.focusNode,
-              includeSemantics: false,
-              debugLabel: kReleaseMode ? null : 'EditableText',
-              child: Scrollable(
-                key: _scrollableKey,
-                excludeFromSemantics: true,
-                axisDirection: _isMultiline ? AxisDirection.down : AxisDirection.right,
-                controller: _scrollController,
-                physics: widget.scrollPhysics,
-                dragStartBehavior: widget.dragStartBehavior,
-                restorationId: widget.restorationId,
-                // If a ScrollBehavior is not provided, only apply scrollbars when
-                // multiline. The overscroll indicator should not be applied in
-                // either case, glowing or stretching.
-                scrollBehavior: widget.scrollBehavior ?? ScrollConfiguration.of(context).copyWith(
-                  scrollbars: _isMultiline,
-                  overscroll: false,
-                ),
-                viewportBuilder: (BuildContext context, ViewportOffset offset) {
-                  return CompositedTransformTarget(
-                    link: _toolbarLayerLink,
-                    child: Semantics(
-                      onCopy: _semanticsOnCopy(controls),
-                      onCut: _semanticsOnCut(controls),
-                      onPaste: _semanticsOnPaste(controls),
-                      child: _ScribbleFocusable(
-                        focusNode: widget.focusNode,
-                        editableKey: _editableKey,
-                        enabled: widget.scribbleEnabled,
-                        updateSelectionRects: () {
-                          _openInputConnection();
-                          _updateSelectionRects(force: true);
-                        },
-                        child: _Editable(
-                          key: _editableKey,
-                          startHandleLayerLink: _startHandleLayerLink,
-                          endHandleLayerLink: _endHandleLayerLink,
-                          inlineSpan: buildTextSpan(),
-                          value: _value,
-                          cursorColor: _cursorColor,
-                          backgroundCursorColor: widget.backgroundCursorColor,
-                          showCursor: EditableText.debugDeterministicCursor
-                              ? ValueNotifier<bool>(widget.showCursor)
-                              : _cursorVisibilityNotifier,
-                          forceLine: widget.forceLine,
-                          readOnly: widget.readOnly,
-                          hasFocus: _hasFocus,
-                          maxLines: widget.maxLines,
-                          minLines: widget.minLines,
-                          expands: widget.expands,
-                          strutStyle: widget.strutStyle,
-                          selectionColor: widget.selectionColor,
-                          textScaleFactor: widget.textScaleFactor ?? MediaQuery.textScaleFactorOf(context),
-                          textAlign: widget.textAlign,
-                          textDirection: _textDirection,
-                          locale: widget.locale,
-                          textHeightBehavior: widget.textHeightBehavior ?? DefaultTextHeightBehavior.maybeOf(context),
-                          textWidthBasis: widget.textWidthBasis,
-                          obscuringCharacter: widget.obscuringCharacter,
-                          obscureText: widget.obscureText,
-                          offset: offset,
-                          onCaretChanged: _handleCaretChanged,
-                          rendererIgnoresPointer: widget.rendererIgnoresPointer,
-                          cursorWidth: widget.cursorWidth,
-                          cursorHeight: widget.cursorHeight,
-                          cursorRadius: widget.cursorRadius,
-                          cursorOffset: widget.cursorOffset ?? Offset.zero,
-                          selectionHeightStyle: widget.selectionHeightStyle,
-                          selectionWidthStyle: widget.selectionWidthStyle,
-                          paintCursorAboveText: widget.paintCursorAboveText,
-                          enableInteractiveSelection: widget._userSelectionEnabled,
-                          textSelectionDelegate: this,
-                          devicePixelRatio: _devicePixelRatio,
-                          promptRectRange: _currentPromptRectRange,
-                          promptRectColor: widget.autocorrectionTextRectColor,
-                          clipBehavior: widget.clipBehavior,
+    return _CompositionCallback(
+      compositeCallback: _compositeCallback,
+      enabled: _hasInputConnection,
+      child: TextFieldTapRegion(
+        onTapOutside: widget.onTapOutside ?? _defaultOnTapOutside,
+        debugLabel: kReleaseMode ? null : 'EditableText',
+        child: MouseRegion(
+          cursor: widget.mouseCursor ?? SystemMouseCursors.text,
+          child: Actions(
+            actions: _actions,
+            child: _TextEditingHistory(
+              controller: widget.controller,
+              onTriggered: (TextEditingValue value) {
+                userUpdateTextEditingValue(value, SelectionChangedCause.keyboard);
+              },
+              child: Focus(
+                focusNode: widget.focusNode,
+                includeSemantics: false,
+                debugLabel: kReleaseMode ? null : 'EditableText',
+                child: Scrollable(
+                  key: _scrollableKey,
+                  excludeFromSemantics: true,
+                  axisDirection: _isMultiline ? AxisDirection.down : AxisDirection.right,
+                  controller: _scrollController,
+                  physics: widget.scrollPhysics,
+                  dragStartBehavior: widget.dragStartBehavior,
+                  restorationId: widget.restorationId,
+                  // If a ScrollBehavior is not provided, only apply scrollbars when
+                  // multiline. The overscroll indicator should not be applied in
+                  // either case, glowing or stretching.
+                  scrollBehavior: widget.scrollBehavior ?? ScrollConfiguration.of(context).copyWith(
+                    scrollbars: _isMultiline,
+                    overscroll: false,
+                  ),
+                  viewportBuilder: (BuildContext context, ViewportOffset offset) {
+                    return CompositedTransformTarget(
+                      link: _toolbarLayerLink,
+                      child: Semantics(
+                        onCopy: _semanticsOnCopy(controls),
+                        onCut: _semanticsOnCut(controls),
+                        onPaste: _semanticsOnPaste(controls),
+                        child: _ScribbleFocusable(
+                          focusNode: widget.focusNode,
+                          editableKey: _editableKey,
+                          enabled: widget.scribbleEnabled,
+                          updateSelectionRects: () {
+                            _openInputConnection();
+                            _updateSelectionRects(force: true);
+                          },
+                          child: _Editable(
+                            key: _editableKey,
+                            startHandleLayerLink: _startHandleLayerLink,
+                            endHandleLayerLink: _endHandleLayerLink,
+                            inlineSpan: buildTextSpan(),
+                            value: _value,
+                            cursorColor: _cursorColor,
+                            backgroundCursorColor: widget.backgroundCursorColor,
+                            showCursor: EditableText.debugDeterministicCursor
+                                ? ValueNotifier<bool>(widget.showCursor)
+                                : _cursorVisibilityNotifier,
+                            forceLine: widget.forceLine,
+                            readOnly: widget.readOnly,
+                            hasFocus: _hasFocus,
+                            maxLines: widget.maxLines,
+                            minLines: widget.minLines,
+                            expands: widget.expands,
+                            strutStyle: widget.strutStyle,
+                            selectionColor: widget.selectionColor,
+                            textScaleFactor: widget.textScaleFactor ?? MediaQuery.textScaleFactorOf(context),
+                            textAlign: widget.textAlign,
+                            textDirection: _textDirection,
+                            locale: widget.locale,
+                            textHeightBehavior: widget.textHeightBehavior ?? DefaultTextHeightBehavior.maybeOf(context),
+                            textWidthBasis: widget.textWidthBasis,
+                            obscuringCharacter: widget.obscuringCharacter,
+                            obscureText: widget.obscureText,
+                            offset: offset,
+                            onCaretChanged: _handleCaretChanged,
+                            rendererIgnoresPointer: widget.rendererIgnoresPointer,
+                            cursorWidth: widget.cursorWidth,
+                            cursorHeight: widget.cursorHeight,
+                            cursorRadius: widget.cursorRadius,
+                            cursorOffset: widget.cursorOffset ?? Offset.zero,
+                            selectionHeightStyle: widget.selectionHeightStyle,
+                            selectionWidthStyle: widget.selectionWidthStyle,
+                            paintCursorAboveText: widget.paintCursorAboveText,
+                            enableInteractiveSelection: widget._userSelectionEnabled,
+                            textSelectionDelegate: this,
+                            devicePixelRatio: _devicePixelRatio,
+                            promptRectRange: _currentPromptRectRange,
+                            promptRectColor: widget.autocorrectionTextRectColor,
+                            clipBehavior: widget.clipBehavior,
+                          ),
                         ),
                       ),
-                    ),
-                  );
-                },
+                    );
+                  },
+                ),
               ),
             ),
           ),
@@ -4319,9 +4598,8 @@
         ],
       );
     }
-    final bool spellCheckResultsReceived = spellCheckEnabled && _spellCheckResults != null;
     final bool withComposing = !widget.readOnly && _hasFocus;
-    if (spellCheckResultsReceived) {
+    if (_spellCheckResultsReceived) {
       // If the composing range is out of range for the current text, ignore it to
       // preserve the tree integrity, otherwise in release mode a RangeError will
       // be thrown and this EditableText will be built with a broken subtree.
@@ -4334,7 +4612,7 @@
         composingRegionOutOfRange,
         widget.style,
         _spellCheckConfiguration.misspelledTextStyle!,
-        _spellCheckResults!,
+        spellCheckResults!,
       );
     }
 
@@ -4389,9 +4667,7 @@
     this.promptRectRange,
     this.promptRectColor,
     required this.clipBehavior,
-  }) : assert(textDirection != null),
-       assert(rendererIgnoresPointer != null),
-       super(children: _extractChildren(inlineSpan));
+  }) : super(children: _extractChildren(inlineSpan));
 
   // Traverses the InlineSpan tree and depth-first collects the list of
   // child widgets that are created in WidgetSpans.
@@ -4679,8 +4955,7 @@
     super.alignment,
     super.baseline,
     required this.size,
-  }) : assert(child != null),
-       assert(baseline != null || !(
+  }) : assert(baseline != null || !(
          identical(alignment, ui.PlaceholderAlignment.aboveBaseline) ||
          identical(alignment, ui.PlaceholderAlignment.belowBaseline) ||
          identical(alignment, ui.PlaceholderAlignment.baseline)
@@ -4712,7 +4987,7 @@
 ///
 /// This text boundary treats every character in input string as an utf-16 code
 /// unit. This can be useful when handling text without any grapheme cluster,
-/// e.g. the obscure string in [EditableText]. If you are handling text that may
+/// e.g. password input in [EditableText]. If you are handling text that may
 /// include grapheme clusters, consider using [CharacterBoundary].
 class _CodeUnitBoundary extends TextBoundary {
   const _CodeUnitBoundary(this._text);
@@ -4720,113 +4995,51 @@
   final String _text;
 
   @override
-  TextPosition getLeadingTextBoundaryAt(TextPosition position) {
-    if (position.offset <= 0) {
-      return const TextPosition(offset: 0);
-    }
-    if (position.offset > _text.length ||
-        (position.offset == _text.length && position.affinity == TextAffinity.downstream)) {
-      return TextPosition(offset: _text.length, affinity: TextAffinity.upstream);
-    }
-    switch (position.affinity) {
-      case TextAffinity.upstream:
-        return TextPosition(offset: math.min(position.offset - 1, _text.length));
-      case TextAffinity.downstream:
-        return TextPosition(offset: math.min(position.offset, _text.length));
-    }
-  }
-
+  int getLeadingTextBoundaryAt(int position) => position.clamp(0, _text.length); // ignore_clamp_double_lint
   @override
-  TextPosition getTrailingTextBoundaryAt(TextPosition position) {
-    if (position.offset < 0 ||
-        (position.offset == 0 && position.affinity == TextAffinity.upstream)) {
-      return const TextPosition(offset: 0);
-    }
-    if (position.offset >= _text.length) {
-      return TextPosition(offset: _text.length, affinity: TextAffinity.upstream);
-    }
-    switch (position.affinity) {
-      case TextAffinity.upstream:
-        return TextPosition(offset: math.min(position.offset, _text.length), affinity: TextAffinity.upstream);
-      case TextAffinity.downstream:
-        return TextPosition(offset: math.min(position.offset + 1, _text.length), affinity: TextAffinity.upstream);
-    }
-  }
-}
-
-// ------------------------  Text Boundary Combinators ------------------------
-
-// A _TextBoundary that creates a [TextRange] where its start is from the
-// specified leading text boundary and its end is from the specified trailing
-// text boundary.
-class _MixedBoundary extends TextBoundary {
-  _MixedBoundary(
-    this.leadingTextBoundary,
-    this.trailingTextBoundary
-  );
-
-  final TextBoundary leadingTextBoundary;
-  final TextBoundary trailingTextBoundary;
-
-  @override
-  TextPosition getLeadingTextBoundaryAt(TextPosition position) => leadingTextBoundary.getLeadingTextBoundaryAt(position);
-
-  @override
-  TextPosition getTrailingTextBoundaryAt(TextPosition position) => trailingTextBoundary.getTrailingTextBoundaryAt(position);
+  int getTrailingTextBoundaryAt(int position) => (position + 1).clamp(0, _text.length); // ignore_clamp_double_lint
 }
 
 // -------------------------------  Text Actions -------------------------------
 class _DeleteTextAction<T extends DirectionalTextEditingIntent> extends ContextAction<T> {
-  _DeleteTextAction(this.state, this.getTextBoundariesForIntent);
+  _DeleteTextAction(this.state, this.getTextBoundary, this._applyTextBoundary);
 
   final EditableTextState state;
-  final TextBoundary Function(T intent) getTextBoundariesForIntent;
-
-  TextRange _expandNonCollapsedRange(TextEditingValue value) {
-    final TextRange selection = value.selection;
-    assert(selection.isValid);
-    assert(!selection.isCollapsed);
-    final TextBoundary atomicBoundary = state.widget.obscureText
-      ? _CodeUnitBoundary(value.text)
-      : CharacterBoundary(value.text);
-
-    return TextRange(
-      start: atomicBoundary.getLeadingTextBoundaryAt(TextPosition(offset: selection.start)).offset,
-      end: atomicBoundary.getTrailingTextBoundaryAt(TextPosition(offset: selection.end - 1)).offset,
-    );
-  }
+  final TextBoundary Function() getTextBoundary;
+  final _ApplyTextBoundary _applyTextBoundary;
 
   @override
   Object? invoke(T intent, [BuildContext? context]) {
     final TextSelection selection = state._value.selection;
-    assert(selection.isValid);
-
-    if (!selection.isCollapsed) {
-      return Actions.invoke(
-        context!,
-        ReplaceTextIntent(state._value, '', _expandNonCollapsedRange(state._value), SelectionChangedCause.keyboard),
-      );
-    }
-
-    final TextBoundary textBoundary = getTextBoundariesForIntent(intent);
-    if (!state._value.selection.isValid) {
+    if (!selection.isValid) {
       return null;
     }
-    if (!state._value.selection.isCollapsed) {
+    assert(selection.isValid);
+    // Expands the selection to ensure the range covers full graphemes.
+    final TextBoundary atomicBoundary = state._characterBoundary();
+    if (!selection.isCollapsed) {
+      // Expands the selection to ensure the range covers full graphemes.
+      final TextRange range = TextRange(
+        start: atomicBoundary.getLeadingTextBoundaryAt(selection.start) ?? state._value.text.length,
+        end: atomicBoundary.getTrailingTextBoundaryAt(selection.end - 1) ?? 0,
+      );
       return Actions.invoke(
         context!,
-        ReplaceTextIntent(state._value, '', _expandNonCollapsedRange(state._value), SelectionChangedCause.keyboard),
+        ReplaceTextIntent(state._value, '', range, SelectionChangedCause.keyboard),
       );
     }
 
+    final int target = _applyTextBoundary(selection.base, intent.forward, getTextBoundary()).offset;
+
+    final TextRange rangeToDelete = TextSelection(
+      baseOffset: intent.forward
+        ? atomicBoundary.getLeadingTextBoundaryAt(selection.baseOffset) ?? state._value.text.length
+        : atomicBoundary.getTrailingTextBoundaryAt(selection.baseOffset - 1) ?? 0,
+      extentOffset: target,
+    );
     return Actions.invoke(
       context!,
-      ReplaceTextIntent(
-        state._value,
-        '',
-        textBoundary.getTextBoundaryAt(state._value.selection.base),
-        SelectionChangedCause.keyboard,
-      ),
+      ReplaceTextIntent(state._value, '', rangeToDelete, SelectionChangedCause.keyboard),
     );
   }
 
@@ -4837,13 +5050,19 @@
 class _UpdateTextSelectionAction<T extends DirectionalCaretMovementIntent> extends ContextAction<T> {
   _UpdateTextSelectionAction(
     this.state,
-    this.ignoreNonCollapsedSelection,
-    this.getTextBoundariesForIntent,
-  );
+    this.getTextBoundary,
+    this.applyTextBoundary, {
+    required this.ignoreNonCollapsedSelection,
+    this.isExpand = false,
+    this.extentAtIndex = false,
+  });
 
   final EditableTextState state;
   final bool ignoreNonCollapsedSelection;
-  final TextBoundary Function(T intent) getTextBoundariesForIntent;
+  final bool isExpand;
+  final bool extentAtIndex;
+  final TextBoundary Function() getTextBoundary;
+  final _ApplyTextBoundary applyTextBoundary;
 
   static const int NEWLINE_CODE_UNIT = 10;
 
@@ -4874,25 +5093,14 @@
     assert(selection.isValid);
 
     final bool collapseSelection = intent.collapseSelection || !state.widget.selectionEnabled;
-    // Collapse to the logical start/end.
-    TextSelection collapse(TextSelection selection) {
-      assert(selection.isValid);
-      assert(!selection.isCollapsed);
-      return selection.copyWith(
-        baseOffset: intent.forward ? selection.end : selection.start,
-        extentOffset: intent.forward ? selection.end : selection.start,
-      );
-    }
-
     if (!selection.isCollapsed && !ignoreNonCollapsedSelection && collapseSelection) {
-      return Actions.invoke(
-        context!,
-        UpdateSelectionIntent(state._value, collapse(selection), SelectionChangedCause.keyboard),
-      );
+      return Actions.invoke(context!, UpdateSelectionIntent(
+        state._value,
+        TextSelection.collapsed(offset: intent.forward ? selection.end : selection.start),
+        SelectionChangedCause.keyboard,
+      ));
     }
 
-    final TextBoundary textBoundary = getTextBoundariesForIntent(intent);
-
     TextPosition extent = selection.extent;
     // If continuesAtWrap is true extent and is at the relevant wordwrap, then
     // move it just to the other side of the wordwrap.
@@ -4909,76 +5117,22 @@
       }
     }
 
-    final TextPosition newExtent = intent.forward
-      ? textBoundary.getTrailingTextBoundaryAt(extent)
-      : textBoundary.getLeadingTextBoundaryAt(extent);
-    final TextSelection newSelection = collapseSelection
+    final bool shouldTargetBase = isExpand && (intent.forward ? selection.baseOffset > selection.extentOffset : selection.baseOffset < selection.extentOffset);
+    final TextPosition newExtent = applyTextBoundary(shouldTargetBase ? selection.base : extent, intent.forward, getTextBoundary());
+    final TextSelection newSelection = collapseSelection || (!isExpand && newExtent.offset == selection.baseOffset)
       ? TextSelection.fromPosition(newExtent)
-      : selection.extendTo(newExtent);
+      : isExpand ? selection.expandTo(newExtent, extentAtIndex || selection.isCollapsed) : selection.extendTo(newExtent);
 
-    // If collapseAtReversal is true and would have an effect, collapse it.
-    if (!selection.isCollapsed && intent.collapseAtReversal
-        && (selection.baseOffset < selection.extentOffset !=
-        newSelection.baseOffset < newSelection.extentOffset)) {
-      return Actions.invoke(
-        context!,
-        UpdateSelectionIntent(
-          state._value,
-          TextSelection.fromPosition(selection.base),
-          SelectionChangedCause.keyboard,
-        ),
-      );
-    }
-
-    return Actions.invoke(
-      context!,
-      UpdateSelectionIntent(state._value, newSelection, SelectionChangedCause.keyboard),
-    );
+    final bool shouldCollapseToBase = intent.collapseAtReversal
+      && (selection.baseOffset - selection.extentOffset) * (selection.baseOffset - newSelection.extentOffset) < 0;
+    final TextSelection newRange = shouldCollapseToBase ? TextSelection.fromPosition(selection.base) : newSelection;
+    return Actions.invoke(context!, UpdateSelectionIntent(state._value, newRange, SelectionChangedCause.keyboard));
   }
 
   @override
   bool get isActionEnabled => state._value.selection.isValid;
 }
 
-class _ExtendSelectionOrCaretPositionAction extends ContextAction<ExtendSelectionToNextWordBoundaryOrCaretLocationIntent> {
-  _ExtendSelectionOrCaretPositionAction(this.state, this.getTextBoundariesForIntent);
-
-  final EditableTextState state;
-  final TextBoundary Function(ExtendSelectionToNextWordBoundaryOrCaretLocationIntent intent) getTextBoundariesForIntent;
-
-  @override
-  Object? invoke(ExtendSelectionToNextWordBoundaryOrCaretLocationIntent intent, [BuildContext? context]) {
-    final TextSelection selection = state._value.selection;
-    assert(selection.isValid);
-
-    final TextBoundary textBoundary = getTextBoundariesForIntent(intent);
-    final TextSelection textBoundarySelection = state._value.selection;
-    if (!textBoundarySelection.isValid) {
-      return null;
-    }
-
-    final TextPosition extent = textBoundarySelection.extent;
-    final TextPosition newExtent = intent.forward
-      ? textBoundary.getTrailingTextBoundaryAt(extent)
-      : textBoundary.getLeadingTextBoundaryAt(extent);
-
-    final TextSelection newSelection = (newExtent.offset - textBoundarySelection.baseOffset) * (textBoundarySelection.extentOffset - textBoundarySelection.baseOffset) < 0
-      ? textBoundarySelection.copyWith(
-        extentOffset: textBoundarySelection.baseOffset,
-        affinity: textBoundarySelection.extentOffset > textBoundarySelection.baseOffset ? TextAffinity.downstream : TextAffinity.upstream,
-      )
-      : textBoundarySelection.extendTo(newExtent);
-
-    return Actions.invoke(
-      context!,
-      UpdateSelectionIntent(state._value, newSelection, SelectionChangedCause.keyboard),
-    );
-  }
-
-  @override
-  bool get isActionEnabled => state.widget.selectionEnabled && state._value.selection.isValid;
-}
-
 class _UpdateTextSelectionVerticallyAction<T extends DirectionalCaretMovementIntent> extends ContextAction<T> {
   _UpdateTextSelectionVerticallyAction(this.state);
 
@@ -5256,7 +5410,7 @@
 
     // If anything has been undone in this stack, remove those irrelevant states
     // before adding the new one.
-    if (_index != null && _index != _list.length - 1) {
+    if (_index != _list.length - 1) {
       _list.removeRange(_index + 1, _list.length);
     }
     _list.add(value);
diff --git a/framework/lib/src/widgets/fade_in_image.dart b/framework/lib/src/widgets/fade_in_image.dart
index e77ef9b..d346bf5 100644
--- a/framework/lib/src/widgets/fade_in_image.dart
+++ b/framework/lib/src/widgets/fade_in_image.dart
@@ -97,15 +97,7 @@
     this.alignment = Alignment.center,
     this.repeat = ImageRepeat.noRepeat,
     this.matchTextDirection = false,
-  }) : assert(placeholder != null),
-       assert(image != null),
-       assert(fadeOutDuration != null),
-       assert(fadeOutCurve != null),
-       assert(fadeInDuration != null),
-       assert(fadeInCurve != null),
-       assert(alignment != null),
-       assert(repeat != null),
-       assert(matchTextDirection != null);
+  });
 
   /// Creates a widget that uses a placeholder image stored in memory while
   /// loading the final image from the network.
@@ -162,18 +154,7 @@
     int? placeholderCacheHeight,
     int? imageCacheWidth,
     int? imageCacheHeight,
-  }) : assert(placeholder != null),
-       assert(image != null),
-       assert(placeholderScale != null),
-       assert(imageScale != null),
-       assert(fadeOutDuration != null),
-       assert(fadeOutCurve != null),
-       assert(fadeInDuration != null),
-       assert(fadeInCurve != null),
-       assert(alignment != null),
-       assert(repeat != null),
-       assert(matchTextDirection != null),
-       placeholder = ResizeImage.resizeIfNeeded(placeholderCacheWidth, placeholderCacheHeight, MemoryImage(placeholder, scale: placeholderScale)),
+  }) : placeholder = ResizeImage.resizeIfNeeded(placeholderCacheWidth, placeholderCacheHeight, MemoryImage(placeholder, scale: placeholderScale)),
        image = ResizeImage.resizeIfNeeded(imageCacheWidth, imageCacheHeight, NetworkImage(image, scale: imageScale));
 
   /// Creates a widget that uses a placeholder image stored in an asset bundle
@@ -235,19 +216,9 @@
     int? placeholderCacheHeight,
     int? imageCacheWidth,
     int? imageCacheHeight,
-  }) : assert(placeholder != null),
-       assert(image != null),
-       placeholder = placeholderScale != null
+  }) : placeholder = placeholderScale != null
          ? ResizeImage.resizeIfNeeded(placeholderCacheWidth, placeholderCacheHeight, ExactAssetImage(placeholder, bundle: bundle, scale: placeholderScale))
          : ResizeImage.resizeIfNeeded(placeholderCacheWidth, placeholderCacheHeight, AssetImage(placeholder, bundle: bundle)),
-       assert(imageScale != null),
-       assert(fadeOutDuration != null),
-       assert(fadeOutCurve != null),
-       assert(fadeInDuration != null),
-       assert(fadeInCurve != null),
-       assert(alignment != null),
-       assert(repeat != null),
-       assert(matchTextDirection != null),
        image = ResizeImage.resizeIfNeeded(imageCacheWidth, imageCacheHeight, NetworkImage(image, scale: imageScale));
 
   /// Image displayed while the target [image] is loading.
@@ -401,7 +372,6 @@
     required FilterQuality filterQuality,
     required Animation<double> opacity,
   }) {
-    assert(image != null);
     return Image(
       image: image,
       errorBuilder: errorBuilder,
@@ -477,14 +447,7 @@
     required this.fadeInDuration,
     required this.fadeInCurve,
     required this.wasSynchronouslyLoaded,
-  }) : assert(target != null),
-       assert(placeholder != null),
-       assert(isTargetLoaded != null),
-       assert(fadeOutDuration != null),
-       assert(fadeOutCurve != null),
-       assert(fadeInDuration != null),
-       assert(fadeInCurve != null),
-       assert(!wasSynchronouslyLoaded || isTargetLoaded),
+  }) : assert(!wasSynchronouslyLoaded || isTargetLoaded),
        super(duration: fadeInDuration + fadeOutDuration);
 
   final Widget target;
diff --git a/framework/lib/src/widgets/focus_manager.dart b/framework/lib/src/widgets/focus_manager.dart
index f38793c..a0226ea 100644
--- a/framework/lib/src/widgets/focus_manager.dart
+++ b/framework/lib/src/widgets/focus_manager.dart
@@ -22,16 +22,38 @@
 /// be logged.
 bool debugFocusChanges = false;
 
-bool _focusDebug(String message, [Iterable<String>? details]) {
-  if (debugFocusChanges) {
-    debugPrint('FOCUS: $message');
-    if (details != null && details.isNotEmpty) {
-      for (final String detail in details) {
-        debugPrint('    $detail');
-      }
+// When using _focusDebug, always call it like so:
+//
+// assert(_focusDebug(() => 'Blah $foo'));
+//
+// It needs to be inside the assert in order to be removed in release mode, and
+// it needs to use a closure to generate the string in order to avoid string
+// interpolation when debugFocusChanges is false.
+//
+// It will throw a StateError if you try to call it when the app is in release
+// mode.
+bool _focusDebug(
+  String Function() messageFunc, [
+  Iterable<Object> Function()? detailsFunc,
+]) {
+  if (kReleaseMode) {
+    throw StateError(
+      '_focusDebug was called in Release mode. It should always be wrapped in '
+      'an assert. Always call _focusDebug like so:\n'
+      r"  assert(_focusDebug(() => 'Blah $foo'));"
+    );
+  }
+  if (!debugFocusChanges) {
+    return true;
+  }
+  debugPrint('FOCUS: ${messageFunc()}');
+  final Iterable<Object> details = detailsFunc?.call() ?? const <Object>[];
+  if (details.isNotEmpty) {
+    for (final Object detail in details) {
+      debugPrint('    $detail');
     }
   }
-  // Return true so that it can be easily used inside of an assert.
+  // Return true so that it can be used inside of an assert.
   return true;
 }
 
@@ -110,7 +132,7 @@
   // The widget tree is responsible for calling reparent/detach on attached
   // nodes to keep their parent/manager information up-to-date, so here we can
   // safely check if the scope/node involved in each autofocus request is
-  // still attached, and discard the ones are no longer attached to the
+  // still attached, and discard the ones which are no longer attached to the
   // original manager.
   void applyIfValid(FocusManager manager) {
     final bool shouldApply  = (scope.parent != null || identical(scope, manager.rootScope))
@@ -118,18 +140,18 @@
                            && scope.focusedChild == null
                            && autofocusNode.ancestors.contains(scope);
     if (shouldApply) {
-      assert(_focusDebug('Applying autofocus: $autofocusNode'));
+      assert(_focusDebug(() => 'Applying autofocus: $autofocusNode'));
       autofocusNode._doRequestFocus(findFirstFocus: true);
     } else {
-      assert(_focusDebug('Autofocus request discarded for node: $autofocusNode.'));
+      assert(_focusDebug(() => 'Autofocus request discarded for node: $autofocusNode.'));
     }
   }
 }
 
 /// An attachment point for a [FocusNode].
 ///
-/// Using a [FocusAttachment] is rarely needed, unless you are building
-/// something akin to the [Focus] or [FocusScope] widgets from scratch.
+/// Using a [FocusAttachment] is rarely needed, unless building something
+/// akin to the [Focus] or [FocusScope] widgets from scratch.
 ///
 /// Once created, a [FocusNode] must be attached to the widget tree by its
 /// _host_ [StatefulWidget] via a [FocusAttachment] object. [FocusAttachment]s
@@ -151,7 +173,7 @@
 class FocusAttachment {
   /// A private constructor, because [FocusAttachment]s are only to be created
   /// by [FocusNode.attach].
-  FocusAttachment._(this._node) : assert(_node != null);
+  FocusAttachment._(this._node);
 
   // The focus node that this attachment manages an attachment for. The node may
   // not yet have a parent, or may have been detached from this attachment, so
@@ -169,8 +191,7 @@
   ///
   /// Calling [FocusNode.dispose] will also automatically detach the node.
   void detach() {
-    assert(_node != null);
-    assert(_focusDebug('Detaching node:', <String>[_node.toString(), 'With enclosing scope ${_node.enclosingScope}']));
+    assert(_focusDebug(() => 'Detaching node:', () => <Object>[_node, 'With enclosing scope ${_node.enclosingScope}']));
     if (isAttached) {
       if (_node.hasPrimaryFocus || (_node._manager != null && _node._manager!._markedForFocus == _node)) {
         _node.unfocus(disposition: UnfocusDisposition.previouslyFocusedChild);
@@ -208,12 +229,10 @@
   /// [FocusScope] widgets to build the focus tree, or if there is a need to
   /// supply the parent explicitly (which are both uncommon).
   void reparent({FocusNode? parent}) {
-    assert(_node != null);
     if (isAttached) {
       assert(_node.context != null);
       parent ??= Focus.maybeOf(_node.context!, scopeOk: true);
       parent ??= _node.context!.owner!.focusManager.rootScope;
-      assert(parent != null);
       parent._reparent(_node);
     }
   }
@@ -261,8 +280,8 @@
 ///
 /// _Please see the [Focus] and [FocusScope] widgets, which are utility widgets
 /// that manage their own [FocusNode]s and [FocusScopeNode]s, respectively. If
-/// they aren't appropriate, [FocusNode]s can be managed directly, but doing
-/// this yourself is rare._
+/// they aren't appropriate, [FocusNode]s can be managed directly, but doing this
+/// is rare._
 ///
 /// [FocusNode]s are persistent objects that form a _focus tree_ that is a
 /// representation of the widgets in the hierarchy that are interested in focus.
@@ -410,10 +429,7 @@
     bool canRequestFocus = true,
     bool descendantsAreFocusable = true,
     bool descendantsAreTraversable = true,
-  })  : assert(skipTraversal != null),
-        assert(canRequestFocus != null),
-        assert(descendantsAreFocusable != null),
-        _skipTraversal = skipTraversal,
+  })  : _skipTraversal = skipTraversal,
         _canRequestFocus = canRequestFocus,
         _descendantsAreFocusable = descendantsAreFocusable,
         _descendantsAreTraversable = descendantsAreTraversable {
@@ -845,7 +861,6 @@
   void unfocus({
     UnfocusDisposition disposition = UnfocusDisposition.scope,
   }) {
-    assert(disposition != null);
     if (!hasFocus && (_manager == null || _manager!._markedForFocus != this)) {
       return;
     }
@@ -884,7 +899,7 @@
         scope._doRequestFocus(findFirstFocus: true);
         break;
     }
-    assert(_focusDebug('Unfocused node:', <String>['primary focus was $this', 'next focus will be ${_manager?._markedForFocus}']));
+    assert(_focusDebug(() => 'Unfocused node:', () => <Object>['primary focus was $this', 'next focus will be ${_manager?._markedForFocus}']));
   }
 
   /// Removes the keyboard token from this focus node if it has one.
@@ -931,7 +946,6 @@
   // Removes the given FocusNode and its children as a child of this node.
   @mustCallSuper
   void _removeChild(FocusNode node, {bool removeScopeFocus = true}) {
-    assert(node != null);
     assert(_children.contains(node), "Tried to remove a node that wasn't a child.");
     assert(node._parent == this);
     assert(node._manager == _manager);
@@ -960,7 +974,6 @@
   // Used by FocusAttachment.reparent to perform the actual parenting operation.
   @mustCallSuper
   void _reparent(FocusNode child) {
-    assert(child != null);
     assert(child != this, 'Tried to make a child into a parent of itself.');
     if (child._parent == this) {
       assert(_children.contains(child), "Found a node that says it's a child, but doesn't appear in the child list.");
@@ -1071,9 +1084,8 @@
 
   // Note that this is overridden in FocusScopeNode.
   void _doRequestFocus({required bool findFirstFocus}) {
-    assert(findFirstFocus != null);
     if (!canRequestFocus) {
-      assert(_focusDebug('Node NOT requesting focus because canRequestFocus is false: $this'));
+      assert(_focusDebug(() => 'Node NOT requesting focus because canRequestFocus is false: $this'));
       return;
     }
     // If the node isn't part of the tree, then we just defer the focus request
@@ -1088,7 +1100,7 @@
       return;
     }
     _hasKeyboardToken = true;
-    assert(_focusDebug('Node requesting focus: $this'));
+    assert(_focusDebug(() => 'Node requesting focus: $this'));
     _markNextFocus(this);
   }
 
@@ -1119,7 +1131,7 @@
     FocusNode scopeFocus = this;
     for (final FocusScopeNode ancestor in ancestors.whereType<FocusScopeNode>()) {
       assert(scopeFocus != ancestor, 'Somehow made a loop by setting focusedChild to its scope.');
-      assert(_focusDebug('Setting $scopeFocus as focused child for scope:', <String>[ancestor.toString()]));
+      assert(_focusDebug(() => 'Setting $scopeFocus as focused child for scope:', () => <Object>[ancestor]));
       // Remove it anywhere in the focused child history.
       ancestor._focusedChildren.remove(scopeFocus);
       // Add it to the end of the list, which is also the top of the queue: The
@@ -1214,16 +1226,15 @@
     super.skipTraversal,
     super.canRequestFocus,
     this.traversalEdgeBehavior = TraversalEdgeBehavior.closedLoop,
-  })  : assert(skipTraversal != null),
-        assert(canRequestFocus != null),
-        super(
+  })  : super(
           descendantsAreFocusable: true,
         );
 
   @override
   FocusScopeNode get nearestScope => this;
 
-  /// {@macro flutter.focus.TraversalEdgeBehavior}
+  /// Controls the transfer of focus beyond the first and the last items of a
+  /// [FocusScopeNode].
   ///
   /// Changing this field value has no immediate effect on the UI. Instead, next time
   /// focus traversal takes place [FocusTraversalPolicy] will read this value
@@ -1286,9 +1297,8 @@
   /// the tree as a child of this scope. If it is already part of the focus
   /// tree, the given scope must be a descendant of this scope.
   void setFirstFocus(FocusScopeNode scope) {
-    assert(scope != null);
     assert(scope != this, 'Unexpected self-reference in setFirstFocus.');
-    assert(_focusDebug('Setting scope as first focus in $this to node:', <String>[scope.toString()]));
+    assert(_focusDebug(() => 'Setting scope as first focus in $this to node:', () => <Object>[scope]));
     if (scope._parent == null) {
       _reparent(scope);
     }
@@ -1318,14 +1328,13 @@
     }
 
     assert(_manager != null);
-    assert(_focusDebug('Autofocus scheduled for $node: scope $this'));
+    assert(_focusDebug(() => 'Autofocus scheduled for $node: scope $this'));
     _manager?._pendingAutofocuses.add(_Autofocus(scope: this, autofocusNode: node));
     _manager?._markNeedsUpdate();
   }
 
   @override
   void _doRequestFocus({required bool findFirstFocus}) {
-    assert(findFirstFocus != null);
 
     // It is possible that a previously focused child is no longer focusable.
     while (this.focusedChild != null && !this.focusedChild!.canRequestFocus) {
@@ -1404,8 +1413,8 @@
 /// The focus manager is responsible for tracking which [FocusNode] has the
 /// primary input focus (the [primaryFocus]), holding the [FocusScopeNode] that
 /// is the root of the focus tree (the [rootScope]), and what the current
-/// [highlightMode] is. It also distributes key events from [RawKeyboard] to the
-/// nodes in the focus tree.
+/// [highlightMode] is. It also distributes key events from [KeyEventManager]
+/// to the nodes in the focus tree.
 ///
 /// The singleton [FocusManager] instance is held by the [WidgetsBinding] as
 /// [WidgetsBinding.focusManager], and can be conveniently accessed using the
@@ -1466,17 +1475,11 @@
   ///
   /// When this focus manager is no longer needed, calling [dispose] on it will
   /// unregister these handlers.
-  void registerGlobalHandlers() {
-    assert(ServicesBinding.instance.keyEventManager.keyMessageHandler == null);
-    ServicesBinding.instance.keyEventManager.keyMessageHandler = _handleKeyMessage;
-    GestureBinding.instance.pointerRouter.addGlobalRoute(_handlePointerEvent);
-  }
+  void registerGlobalHandlers() => _highlightManager.registerGlobalHandlers();
 
   @override
   void dispose() {
-    if (ServicesBinding.instance.keyEventManager.keyMessageHandler == _handleKeyMessage) {
-      GestureBinding.instance.pointerRouter.removeGlobalRoute(_handlePointerEvent);
-    }
+    _highlightManager.dispose();
     super.dispose();
   }
 
@@ -1484,6 +1487,8 @@
   /// the [WidgetsBinding] instance.
   static FocusManager get instance => WidgetsBinding.instance.focusManager;
 
+  final _HighlightModeManager _highlightManager = _HighlightModeManager();
+
   /// Sets the strategy by which [highlightMode] is determined.
   ///
   /// If set to [FocusHighlightStrategy.automatic], then the highlight mode will
@@ -1507,33 +1512,12 @@
   /// most appropriate for the initial interaction mode.
   ///
   /// Defaults to [FocusHighlightStrategy.automatic].
-  FocusHighlightStrategy get highlightStrategy => _highlightStrategy;
-  FocusHighlightStrategy _highlightStrategy = FocusHighlightStrategy.automatic;
-  set highlightStrategy(FocusHighlightStrategy highlightStrategy) {
-    _highlightStrategy = highlightStrategy;
-    _updateHighlightMode();
-  }
-
-  static FocusHighlightMode get _defaultModeForPlatform {
-    // Assume that if we're on one of the mobile platforms, and there's no mouse
-    // connected, that the initial interaction will be touch-based, and that
-    // it's traditional mouse and keyboard on all other platforms.
-    //
-    // This only affects the initial value: the ongoing value is updated to a
-    // known correct value as soon as any pointer/keyboard events are received.
-    switch (defaultTargetPlatform) {
-      case TargetPlatform.android:
-      case TargetPlatform.fuchsia:
-      case TargetPlatform.iOS:
-        if (WidgetsBinding.instance.mouseTracker.mouseIsConnected) {
-          return FocusHighlightMode.traditional;
-        }
-        return FocusHighlightMode.touch;
-      case TargetPlatform.linux:
-      case TargetPlatform.macOS:
-      case TargetPlatform.windows:
-        return FocusHighlightMode.traditional;
+  FocusHighlightStrategy get highlightStrategy => _highlightManager.strategy;
+  set highlightStrategy(FocusHighlightStrategy value) {
+    if (_highlightManager.strategy == value) {
+      return;
     }
+    _highlightManager.strategy = value;
   }
 
   /// Indicates the current interaction mode for focus highlights.
@@ -1549,93 +1533,15 @@
   /// draw their focus highlight whenever they are focused.
   // Don't want to set _highlightMode here, since it's possible for the target
   // platform to change (especially in tests).
-  FocusHighlightMode get highlightMode => _highlightMode ?? _defaultModeForPlatform;
-  FocusHighlightMode? _highlightMode;
-
-  // If set, indicates if the last interaction detected was touch or not.
-  // If null, no interactions have occurred yet.
-  bool? _lastInteractionWasTouch;
-
-  // Update function to be called whenever the state relating to highlightMode
-  // changes.
-  void _updateHighlightMode() {
-    final FocusHighlightMode newMode;
-    switch (highlightStrategy) {
-      case FocusHighlightStrategy.automatic:
-        if (_lastInteractionWasTouch == null) {
-          // If we don't have any information about the last interaction yet,
-          // then just rely on the default value for the platform, which will be
-          // determined based on the target platform if _highlightMode is not
-          // set.
-          return;
-        }
-        if (_lastInteractionWasTouch!) {
-          newMode = FocusHighlightMode.touch;
-        } else {
-          newMode = FocusHighlightMode.traditional;
-        }
-        break;
-      case FocusHighlightStrategy.alwaysTouch:
-        newMode = FocusHighlightMode.touch;
-        break;
-      case FocusHighlightStrategy.alwaysTraditional:
-        newMode = FocusHighlightMode.traditional;
-        break;
-    }
-    // We can't just compare newMode with _highlightMode here, since
-    // _highlightMode could be null, so we want to compare with the return value
-    // for the getter, since that's what clients will be looking at.
-    final FocusHighlightMode oldMode = highlightMode;
-    _highlightMode = newMode;
-    if (highlightMode != oldMode) {
-      _notifyHighlightModeListeners();
-    }
-  }
-
-  // The list of listeners for [highlightMode] state changes.
-  final HashedObserverList<ValueChanged<FocusHighlightMode>> _listeners = HashedObserverList<ValueChanged<FocusHighlightMode>>();
+  FocusHighlightMode get highlightMode => _highlightManager.highlightMode;
 
   /// Register a closure to be called when the [FocusManager] notifies its listeners
   /// that the value of [highlightMode] has changed.
-  void addHighlightModeListener(ValueChanged<FocusHighlightMode> listener) => _listeners.add(listener);
+  void addHighlightModeListener(ValueChanged<FocusHighlightMode> listener) => _highlightManager.addListener(listener);
 
   /// Remove a previously registered closure from the list of closures that the
   /// [FocusManager] notifies.
-  void removeHighlightModeListener(ValueChanged<FocusHighlightMode> listener) => _listeners.remove(listener);
-
-  @pragma('vm:notify-debugger-on-exception')
-  void _notifyHighlightModeListeners() {
-    if (_listeners.isEmpty) {
-      return;
-    }
-    final List<ValueChanged<FocusHighlightMode>> localListeners = List<ValueChanged<FocusHighlightMode>>.of(_listeners);
-    for (final ValueChanged<FocusHighlightMode> listener in localListeners) {
-      try {
-        if (_listeners.contains(listener)) {
-          listener(highlightMode);
-        }
-      } catch (exception, stack) {
-        InformationCollector? collector;
-        assert(() {
-          collector = () => <DiagnosticsNode>[
-            DiagnosticsProperty<FocusManager>(
-              'The $runtimeType sending notification was',
-              this,
-              style: DiagnosticsTreeStyle.errorProperty,
-            ),
-          ];
-          return true;
-        }());
-        FlutterError.reportError(FlutterErrorDetails(
-          exception: exception,
-          stack: stack,
-          library: 'widgets library',
-          context: ErrorDescription('while dispatching notifications for $runtimeType'),
-          informationCollector: collector,
-        ));
-      }
-    }
-  }
+  void removeHighlightModeListener(ValueChanged<FocusHighlightMode> listener) => _highlightManager.removeListener(listener);
 
   /// The root [FocusScopeNode] in the focus tree.
   ///
@@ -1643,77 +1549,6 @@
   /// for a given [FocusNode], call [FocusNode.nearestScope].
   final FocusScopeNode rootScope = FocusScopeNode(debugLabel: 'Root Focus Scope');
 
-  void _handlePointerEvent(PointerEvent event) {
-    final FocusHighlightMode expectedMode;
-    switch (event.kind) {
-      case PointerDeviceKind.touch:
-      case PointerDeviceKind.stylus:
-      case PointerDeviceKind.invertedStylus:
-        _lastInteractionWasTouch = true;
-        expectedMode = FocusHighlightMode.touch;
-        break;
-      case PointerDeviceKind.mouse:
-      case PointerDeviceKind.trackpad:
-      case PointerDeviceKind.unknown:
-        _lastInteractionWasTouch = false;
-        expectedMode = FocusHighlightMode.traditional;
-        break;
-    }
-    if (expectedMode != highlightMode) {
-      _updateHighlightMode();
-    }
-  }
-
-  bool _handleKeyMessage(KeyMessage message) {
-    // Update highlightMode first, since things responding to the keys might
-    // look at the highlight mode, and it should be accurate.
-    _lastInteractionWasTouch = false;
-    _updateHighlightMode();
-
-    assert(_focusDebug('Received key event $message'));
-    if (_primaryFocus == null) {
-      assert(_focusDebug('No primary focus for key event, ignored: $message'));
-      return false;
-    }
-
-    // Walk the current focus from the leaf to the root, calling each one's
-    // onKey on the way up, and if one responds that they handled it or want to
-    // stop propagation, stop.
-    bool handled = false;
-    for (final FocusNode node in <FocusNode>[_primaryFocus!, ..._primaryFocus!.ancestors]) {
-      final List<KeyEventResult> results = <KeyEventResult>[];
-      if (node.onKeyEvent != null) {
-        for (final KeyEvent event in message.events) {
-          results.add(node.onKeyEvent!(node, event));
-        }
-      }
-      if (node.onKey != null && message.rawEvent != null) {
-        results.add(node.onKey!(node, message.rawEvent!));
-      }
-      final KeyEventResult result = combineKeyEventResults(results);
-      switch (result) {
-        case KeyEventResult.ignored:
-          continue;
-        case KeyEventResult.handled:
-          assert(_focusDebug('Node $node handled key event $message.'));
-          handled = true;
-          break;
-        case KeyEventResult.skipRemainingHandlers:
-          assert(_focusDebug('Node $node stopped key event propagation: $message.'));
-          handled = false;
-          break;
-      }
-      // Only KeyEventResult.ignored will continue the for loop. All other
-      // options will stop the event propagation.
-      assert(result != KeyEventResult.ignored);
-      break;
-    }
-    if (!handled) {
-      assert(_focusDebug('Key event not handled by anyone: $message.'));
-    }
-    return handled;
-  }
-
   /// The node that currently has the primary focus.
   FocusNode? get primaryFocus => _primaryFocus;
   FocusNode? _primaryFocus;
@@ -1729,7 +1564,7 @@
   void _markDetached(FocusNode node) {
     // The node has been removed from the tree, so it no longer needs to be
     // notified of changes.
-    assert(_focusDebug('Node was detached: $node'));
+    assert(_focusDebug(() => 'Node was detached: $node'));
     if (_primaryFocus == node) {
       _primaryFocus = null;
     }
@@ -1738,7 +1573,7 @@
 
   void _markPropertiesChanged(FocusNode node) {
     _markNeedsUpdate();
-    assert(_focusDebug('Properties changed for node $node.'));
+    assert(_focusDebug(() => 'Properties changed for node $node.'));
     _dirtyNodes.add(node);
   }
 
@@ -1762,7 +1597,7 @@
   // Request that an update be scheduled, optionally requesting focus for the
   // given newFocus node.
   void _markNeedsUpdate() {
-    assert(_focusDebug('Scheduling update, current focus is $_primaryFocus, next focus will be $_markedForFocus'));
+    assert(_focusDebug(() => 'Scheduling update, current focus is $_primaryFocus, next focus will be $_markedForFocus'));
     if (_haveScheduledUpdate) {
       return;
     }
@@ -1784,7 +1619,7 @@
       // then revert to the root scope.
       _markedForFocus = rootScope;
     }
-    assert(_focusDebug('Refreshing focus state. Next focus will be $_markedForFocus'));
+    assert(_focusDebug(() => 'Refreshing focus state. Next focus will be $_markedForFocus'));
     // A node has requested to be the next focus, and isn't already the primary
     // focus.
     if (_markedForFocus != null && _markedForFocus != _primaryFocus) {
@@ -1800,7 +1635,7 @@
     }
     assert(_markedForFocus == null);
     if (previousFocus != _primaryFocus) {
-      assert(_focusDebug('Updating focus from $previousFocus to $_primaryFocus'));
+      assert(_focusDebug(() => 'Updating focus from $previousFocus to $_primaryFocus'));
       if (previousFocus != null) {
         _dirtyNodes.add(previousFocus);
       }
@@ -1811,7 +1646,7 @@
     for (final FocusNode node in _dirtyNodes) {
       node._notify();
     }
-    assert(_focusDebug('Notified ${_dirtyNodes.length} dirty nodes:', _dirtyNodes.toList().map<String>((FocusNode node) => node.toString())));
+    assert(_focusDebug(() => 'Notified ${_dirtyNodes.length} dirty nodes:', () => _dirtyNodes));
     _dirtyNodes.clear();
     if (previousFocus != _primaryFocus) {
       notifyListeners();
@@ -1843,8 +1678,224 @@
   }
 }
 
-/// Provides convenient access to the current [FocusManager.primaryFocus] from the
-/// [WidgetsBinding] instance.
+// A class to detect and manage the highlight mode transitions. An instance of
+// this is owned by the FocusManager.
+//
+// This doesn't extend ChangeNotifier because the callback passes the updated
+// value, and ChangeNotifier requires using VoidCallback.
+class _HighlightModeManager {
+  // If set, indicates if the last interaction detected was touch or not. If
+  // null, no interactions have occurred yet.
+  bool? _lastInteractionWasTouch;
+
+  FocusHighlightMode get highlightMode => _highlightMode ?? _defaultModeForPlatform;
+  FocusHighlightMode? _highlightMode;
+
+  FocusHighlightStrategy get strategy => _strategy;
+  FocusHighlightStrategy _strategy = FocusHighlightStrategy.automatic;
+  set strategy(FocusHighlightStrategy value) {
+    if (_strategy == value) {
+      return;
+    }
+    _strategy = value;
+    updateMode();
+  }
+
+  /// Register a closure to be called when the [FocusManager] notifies its
+  /// listeners that the value of [highlightMode] has changed.
+  void addListener(ValueChanged<FocusHighlightMode> listener) => _listeners.add(listener);
+
+  /// Remove a previously registered closure from the list of closures that the
+  /// [FocusManager] notifies.
+  void removeListener(ValueChanged<FocusHighlightMode> listener) => _listeners.remove(listener);
+
+  // The list of listeners for [highlightMode] state changes.
+  HashedObserverList<ValueChanged<FocusHighlightMode>> _listeners = HashedObserverList<ValueChanged<FocusHighlightMode>>();
+
+  void registerGlobalHandlers() {
+    assert(ServicesBinding.instance.keyEventManager.keyMessageHandler == null);
+    ServicesBinding.instance.keyEventManager.keyMessageHandler = handleKeyMessage;
+    GestureBinding.instance.pointerRouter.addGlobalRoute(handlePointerEvent);
+  }
+
+  @mustCallSuper
+  void dispose() {
+    if (ServicesBinding.instance.keyEventManager.keyMessageHandler == handleKeyMessage) {
+      GestureBinding.instance.pointerRouter.removeGlobalRoute(handlePointerEvent);
+      ServicesBinding.instance.keyEventManager.keyMessageHandler = null;
+    }
+    _listeners = HashedObserverList<ValueChanged<FocusHighlightMode>>();
+  }
+
+  @pragma('vm:notify-debugger-on-exception')
+  void notifyListeners() {
+    if (_listeners.isEmpty) {
+      return;
+    }
+    final List<ValueChanged<FocusHighlightMode>> localListeners = List<ValueChanged<FocusHighlightMode>>.of(_listeners);
+    for (final ValueChanged<FocusHighlightMode> listener in localListeners) {
+      try {
+        if (_listeners.contains(listener)) {
+          listener(highlightMode);
+        }
+      } catch (exception, stack) {
+        InformationCollector? collector;
+        assert(() {
+          collector = () => <DiagnosticsNode>[
+            DiagnosticsProperty<_HighlightModeManager>(
+              'The $runtimeType sending notification was',
+              this,
+              style: DiagnosticsTreeStyle.errorProperty,
+            ),
+          ];
+          return true;
+        }());
+        FlutterError.reportError(FlutterErrorDetails(
+          exception: exception,
+          stack: stack,
+          library: 'widgets library',
+          context: ErrorDescription('while dispatching notifications for $runtimeType'),
+          informationCollector: collector,
+        ));
+      }
+    }
+  }
+
+  void handlePointerEvent(PointerEvent event) {
+    final FocusHighlightMode expectedMode;
+    switch (event.kind) {
+      case PointerDeviceKind.touch:
+      case PointerDeviceKind.stylus:
+      case PointerDeviceKind.invertedStylus:
+        _lastInteractionWasTouch = true;
+        expectedMode = FocusHighlightMode.touch;
+        break;
+      case PointerDeviceKind.mouse:
+      case PointerDeviceKind.trackpad:
+      case PointerDeviceKind.unknown:
+        _lastInteractionWasTouch = false;
+        expectedMode = FocusHighlightMode.traditional;
+        break;
+    }
+    if (expectedMode != highlightMode) {
+      updateMode();
+    }
+  }
+
+  bool handleKeyMessage(KeyMessage message) {
+    // Update highlightMode first, since things responding to the keys might
+    // look at the highlight mode, and it should be accurate.
+    _lastInteractionWasTouch = false;
+    updateMode();
+
+    assert(_focusDebug(() => 'Received key event $message'));
+    if (FocusManager.instance.primaryFocus == null) {
+      assert(_focusDebug(() => 'No primary focus for key event, ignored: $message'));
+      return false;
+    }
+
+    // Walk the current focus from the leaf to the root, calling each one's
+    // onKey on the way up, and if one responds that they handled it or want to
+    // stop propagation, stop.
+    bool handled = false;
+    for (final FocusNode node in <FocusNode>[
+      FocusManager.instance.primaryFocus!,
+      ...FocusManager.instance.primaryFocus!.ancestors,
+    ]) {
+      final List<KeyEventResult> results = <KeyEventResult>[];
+      if (node.onKeyEvent != null) {
+        for (final KeyEvent event in message.events) {
+          results.add(node.onKeyEvent!(node, event));
+        }
+      }
+      if (node.onKey != null && message.rawEvent != null) {
+        results.add(node.onKey!(node, message.rawEvent!));
+      }
+      final KeyEventResult result = combineKeyEventResults(results);
+      switch (result) {
+        case KeyEventResult.ignored:
+          continue;
+        case KeyEventResult.handled:
+          assert(_focusDebug(() => 'Node $node handled key event $message.'));
+          handled = true;
+          break;
+        case KeyEventResult.skipRemainingHandlers:
+          assert(_focusDebug(() => 'Node $node stopped key event propagation: $message.'));
+          handled = false;
+          break;
+      }
+      // Only KeyEventResult.ignored will continue the for loop. All other
+      // options will stop the event propagation.
+      assert(result != KeyEventResult.ignored);
+      break;
+    }
+    if (!handled) {
+      assert(_focusDebug(() => 'Key event not handled by anyone: $message.'));
+    }
+    return handled;
+  }
+
+  // Update function to be called whenever the state relating to highlightMode
+  // changes.
+  void updateMode() {
+    final FocusHighlightMode newMode;
+    switch (strategy) {
+      case FocusHighlightStrategy.automatic:
+        if (_lastInteractionWasTouch == null) {
+          // If we don't have any information about the last interaction yet,
+          // then just rely on the default value for the platform, which will be
+          // determined based on the target platform if _highlightMode is not
+          // set.
+          return;
+        }
+        if (_lastInteractionWasTouch!) {
+          newMode = FocusHighlightMode.touch;
+        } else {
+          newMode = FocusHighlightMode.traditional;
+        }
+        break;
+      case FocusHighlightStrategy.alwaysTouch:
+        newMode = FocusHighlightMode.touch;
+        break;
+      case FocusHighlightStrategy.alwaysTraditional:
+        newMode = FocusHighlightMode.traditional;
+        break;
+    }
+    // We can't just compare newMode with _highlightMode here, since
+    // _highlightMode could be null, so we want to compare with the return value
+    // for the getter, since that's what clients will be looking at.
+    final FocusHighlightMode oldMode = highlightMode;
+    _highlightMode = newMode;
+    if (highlightMode != oldMode) {
+      notifyListeners();
+    }
+  }
+
+  static FocusHighlightMode get _defaultModeForPlatform {
+    // Assume that if we're on one of the mobile platforms, and there's no mouse
+    // connected, that the initial interaction will be touch-based, and that
+    // it's traditional mouse and keyboard on all other platforms.
+    //
+    // This only affects the initial value: the ongoing value is updated to a
+    // known correct value as soon as any pointer/keyboard events are received.
+    switch (defaultTargetPlatform) {
+      case TargetPlatform.android:
+      case TargetPlatform.fuchsia:
+      case TargetPlatform.iOS:
+        if (WidgetsBinding.instance.mouseTracker.mouseIsConnected) {
+          return FocusHighlightMode.traditional;
+        }
+        return FocusHighlightMode.touch;
+      case TargetPlatform.linux:
+      case TargetPlatform.macOS:
+      case TargetPlatform.windows:
+        return FocusHighlightMode.traditional;
+    }
+  }
+}
+
+/// Provides convenient access to the current [FocusManager.primaryFocus] from
+/// the [WidgetsBinding] instance.
 FocusNode? get primaryFocus => WidgetsBinding.instance.focusManager.primaryFocus;
 
 /// Returns a text representation of the current focus tree, along with the
@@ -1852,7 +1903,6 @@
 ///
 /// Will return an empty string in release builds.
 String debugDescribeFocusTree() {
-  assert(WidgetsBinding.instance != null);
   String? result;
   assert(() {
     result = FocusManager.instance.toStringDeep();
diff --git a/framework/lib/src/widgets/focus_scope.dart b/framework/lib/src/widgets/focus_scope.dart
index ad885a4..81bcb45 100644
--- a/framework/lib/src/widgets/focus_scope.dart
+++ b/framework/lib/src/widgets/focus_scope.dart
@@ -136,10 +136,7 @@
         _skipTraversal = skipTraversal,
         _descendantsAreFocusable = descendantsAreFocusable,
         _descendantsAreTraversable = descendantsAreTraversable,
-        _debugLabel = debugLabel,
-        assert(child != null),
-        assert(autofocus != null),
-        assert(includeSemantics != null);
+        _debugLabel = debugLabel;
 
   /// Creates a Focus widget that uses the given [focusNode] as the source of
   /// truth for attributes on the node, rather than the attributes of this widget.
@@ -195,7 +192,7 @@
   /// {@endtemplate}
   ///
   /// A non-null [focusNode] must be supplied if using the
-  /// [Focus.withExternalFocusNode] constructor is used.
+  /// [Focus.withExternalFocusNode] constructor.
   final FocusNode? focusNode;
 
   /// {@template flutter.widgets.Focus.autofocus}
@@ -386,9 +383,7 @@
   ///  * [maybeOf], which is similar to this function, but will return null
   ///    instead of throwing if it doesn't find a [Focus] node.
   static FocusNode of(BuildContext context, { bool scopeOk = false }) {
-    assert(context != null);
-    assert(scopeOk != null);
-    final _FocusMarker? marker = context.dependOnInheritedWidgetOfExactType<_FocusMarker>();
+    final _FocusInheritedScope? marker = context.dependOnInheritedWidgetOfExactType<_FocusInheritedScope>();
     final FocusNode? node = marker?.notifier;
     assert(() {
       if (node == null) {
@@ -438,9 +433,7 @@
   ///  * [of], which is similar to this function, but will throw an exception if
   ///    it doesn't find a [Focus] node instead of returning null.
   static FocusNode? maybeOf(BuildContext context, { bool scopeOk = false }) {
-    assert(context != null);
-    assert(scopeOk != null);
-    final _FocusMarker? marker = context.dependOnInheritedWidgetOfExactType<_FocusMarker>();
+    final _FocusInheritedScope? marker = context.dependOnInheritedWidgetOfExactType<_FocusInheritedScope>();
     final FocusNode? node = marker?.notifier;
     if (node == null) {
       return null;
@@ -536,9 +529,7 @@
     }
     focusNode.descendantsAreFocusable = widget.descendantsAreFocusable;
     focusNode.descendantsAreTraversable = widget.descendantsAreTraversable;
-    if (widget.skipTraversal != null) {
-      focusNode.skipTraversal = widget.skipTraversal;
-    }
+    focusNode.skipTraversal = widget.skipTraversal;
     if (widget._canRequestFocus != null) {
       focusNode.canRequestFocus = widget._canRequestFocus!;
     }
@@ -625,9 +616,7 @@
         if (widget.onKeyEvent != focusNode.onKeyEvent) {
           focusNode.onKeyEvent = widget.onKeyEvent;
         }
-        if (widget.skipTraversal != null) {
-          focusNode.skipTraversal = widget.skipTraversal;
-        }
+        focusNode.skipTraversal = widget.skipTraversal;
         if (widget._canRequestFocus != null) {
           focusNode.canRequestFocus = widget._canRequestFocus!;
         }
@@ -686,7 +675,7 @@
         child: widget.child,
       );
     }
-    return _FocusMarker(
+    return _FocusInheritedScope(
       node: focusNode,
       child: child,
     );
@@ -771,9 +760,7 @@
     super.onKeyEvent,
     super.onKey,
     super.debugLabel,
-  })  : assert(child != null),
-        assert(autofocus != null),
-        super(
+  })  : super(
           focusNode: node,
         );
 
@@ -797,8 +784,7 @@
   ///
   /// The [context] argument must not be null.
   static FocusScopeNode of(BuildContext context) {
-    assert(context != null);
-    final _FocusMarker? marker = context.dependOnInheritedWidgetOfExactType<_FocusMarker>();
+    final _FocusInheritedScope? marker = context.dependOnInheritedWidgetOfExactType<_FocusInheritedScope>();
     return marker?.notifier?.nearestScope ?? context.owner!.focusManager.rootScope;
   }
 
@@ -853,7 +839,7 @@
     _focusAttachment!.reparent(parent: widget.parentNode);
     return Semantics(
       explicitChildNodes: true,
-      child: _FocusMarker(
+      child: _FocusInheritedScope(
         node: focusNode,
         child: widget.child,
       ),
@@ -861,14 +847,12 @@
   }
 }
 
-// The InheritedWidget marker for Focus and FocusScope.
-class _FocusMarker extends InheritedNotifier<FocusNode> {
-  const _FocusMarker({
+// The InheritedWidget for Focus and FocusScope.
+class _FocusInheritedScope extends InheritedNotifier<FocusNode> {
+  const _FocusInheritedScope({
     required FocusNode node,
     required super.child,
-  })  : assert(node != null),
-        assert(child != null),
-        super(notifier: node);
+  })  : super(notifier: node);
 }
 
 /// A widget that controls whether or not the descendants of this widget are
@@ -892,8 +876,7 @@
     super.key,
     this.excluding = true,
     required this.child,
-  })  : assert(excluding != null),
-        assert(child != null);
+  });
 
   /// If true, will make this widget's descendants unfocusable.
   ///
diff --git a/framework/lib/src/widgets/focus_traversal.dart b/framework/lib/src/widgets/focus_traversal.dart
index fd63d28..ec3a40d 100644
--- a/framework/lib/src/widgets/focus_traversal.dart
+++ b/framework/lib/src/widgets/focus_traversal.dart
@@ -47,7 +47,7 @@
 // sorting their contents.
 class _FocusTraversalGroupInfo {
   _FocusTraversalGroupInfo(
-    _FocusTraversalGroupMarker? marker, {
+    _FocusTraversalGroupScope? marker, {
     FocusTraversalPolicy? defaultPolicy,
     List<FocusNode>? members,
   })  : groupNode = marker?.focusNode,
@@ -84,10 +84,8 @@
   left,
 }
 
-/// {@template flutter.focus.TraversalEdgeBehavior}
 /// Controls the transfer of focus beyond the first and the last items of a
 /// [FocusScopeNode].
-/// {@endtemplate}
 ///
 /// This enumeration only controls the traversal behavior performed by
 /// [FocusTraversalPolicy]. Other methods of focus transfer, such as direct
@@ -202,7 +200,7 @@
   ///
   /// See also:
   ///
-  ///  * [previous], the function that is called to move the focus to the next node.
+  ///  * [previous], the function that is called to move the focus to the previous node.
   ///  * [DirectionalFocusTraversalPolicyMixin.findFirstFocusInDirection], a
   ///    function that finds the first focusable widget in a particular direction.
   FocusNode findLastFocus(FocusNode currentNode, {bool ignoreCurrentFocus = false}) {
@@ -210,7 +208,6 @@
   }
 
   FocusNode _findInitialFocus(FocusNode currentNode, {bool fromEnd = false, bool ignoreCurrentFocus = false}) {
-    assert(currentNode != null);
     final FocusScopeNode scope = currentNode.nearestScope!;
     FocusNode? candidate = scope.focusedChild;
     if (ignoreCurrentFocus || candidate == null && scope.descendants.isNotEmpty) {
@@ -321,20 +318,19 @@
   @protected
   Iterable<FocusNode> sortDescendants(Iterable<FocusNode> descendants, FocusNode currentNode);
 
-  _FocusTraversalGroupMarker? _getMarker(BuildContext? context) {
-    return context?.getElementForInheritedWidgetOfExactType<_FocusTraversalGroupMarker>()?.widget as _FocusTraversalGroupMarker?;
+  _FocusTraversalGroupScope? _getMarker(BuildContext? context) {
+    return context?.getElementForInheritedWidgetOfExactType<_FocusTraversalGroupScope>()?.widget as _FocusTraversalGroupScope?;
   }
 
   // Sort all descendants, taking into account the FocusTraversalGroup
   // that they are each in, and filtering out non-traversable/focusable nodes.
   List<FocusNode> _sortAllDescendants(FocusScopeNode scope, FocusNode currentNode) {
-    assert(scope != null);
-    final _FocusTraversalGroupMarker? scopeGroupMarker = _getMarker(scope.context);
+    final _FocusTraversalGroupScope? scopeGroupMarker = _getMarker(scope.context);
     final FocusTraversalPolicy defaultPolicy = scopeGroupMarker?.policy ?? ReadingOrderTraversalPolicy();
     // Build the sorting data structure, separating descendants into groups.
     final Map<FocusNode?, _FocusTraversalGroupInfo> groups = <FocusNode?, _FocusTraversalGroupInfo>{};
     for (final FocusNode node in scope.descendants) {
-      final _FocusTraversalGroupMarker? groupMarker = _getMarker(node.context);
+      final _FocusTraversalGroupScope? groupMarker = _getMarker(node.context);
       final FocusNode? groupNode = groupMarker?.focusNode;
       // Group nodes need to be added to their parent's node, or to the "null"
       // node if no parent is found. This creates the hierarchy of group nodes
@@ -346,7 +342,7 @@
         // looking with that node's parent, since _getMarker will return the
         // context it was called on if it matches the type.
         final BuildContext? parentContext = _getAncestor(groupNode!.context!, count: 2);
-        final _FocusTraversalGroupMarker? parentMarker = _getMarker(parentContext);
+        final _FocusTraversalGroupScope? parentMarker = _getMarker(parentContext);
         final FocusNode? parentNode = parentMarker?.focusNode;
         groups[parentNode] ??= _FocusTraversalGroupInfo(parentMarker, members: <FocusNode>[], defaultPolicy: defaultPolicy);
         assert(!groups[parentNode]!.members.contains(node));
@@ -423,7 +419,6 @@
   /// Returns true if a node requested focus.
   @protected
   bool _moveFocus(FocusNode currentNode, {required bool forward}) {
-    assert(forward != null);
     final FocusScopeNode nearestScope = currentNode.nearestScope!;
     invalidateScopeData(nearestScope);
     final FocusNode? focusedChild = nearestScope.focusedChild;
@@ -444,20 +439,24 @@
       return false;
     }
     if (forward && focusedChild == sortedNodes.last) {
-      if (nearestScope.traversalEdgeBehavior == TraversalEdgeBehavior.leaveFlutterView) {
-        focusedChild!.unfocus();
-        return false;
+      switch (nearestScope.traversalEdgeBehavior) {
+        case TraversalEdgeBehavior.leaveFlutterView:
+          focusedChild!.unfocus();
+          return false;
+        case TraversalEdgeBehavior.closedLoop:
+          _focusAndEnsureVisible(sortedNodes.first, alignmentPolicy: ScrollPositionAlignmentPolicy.keepVisibleAtEnd);
+          return true;
       }
-      _focusAndEnsureVisible(sortedNodes.first, alignmentPolicy: ScrollPositionAlignmentPolicy.keepVisibleAtEnd);
-      return true;
     }
     if (!forward && focusedChild == sortedNodes.first) {
-      if (nearestScope.traversalEdgeBehavior == TraversalEdgeBehavior.leaveFlutterView) {
-        focusedChild!.unfocus();
-        return false;
+      switch (nearestScope.traversalEdgeBehavior) {
+        case TraversalEdgeBehavior.leaveFlutterView:
+          focusedChild!.unfocus();
+          return false;
+        case TraversalEdgeBehavior.closedLoop:
+          _focusAndEnsureVisible(sortedNodes.last, alignmentPolicy: ScrollPositionAlignmentPolicy.keepVisibleAtStart);
+          return true;
       }
-      _focusAndEnsureVisible(sortedNodes.last, alignmentPolicy: ScrollPositionAlignmentPolicy.keepVisibleAtStart);
-      return true;
     }
 
     final Iterable<FocusNode> maybeFlipped = forward ? sortedNodes : sortedNodes.reversed;
@@ -479,16 +478,14 @@
 // A policy data object for use by the DirectionalFocusTraversalPolicyMixin so
 // it can keep track of the traversal history.
 class _DirectionalPolicyDataEntry {
-  const _DirectionalPolicyDataEntry({required this.direction, required this.node})
-      : assert(direction != null),
-        assert(node != null);
+  const _DirectionalPolicyDataEntry({required this.direction, required this.node});
 
   final TraversalDirection direction;
   final FocusNode node;
 }
 
 class _DirectionalPolicyData {
-  const _DirectionalPolicyData({required this.history}) : assert(history != null);
+  const _DirectionalPolicyData({required this.history});
 
   /// A queue of entries that describe the path taken to the current node.
   final List<_DirectionalPolicyDataEntry> history;
@@ -545,8 +542,6 @@
 
   @override
   FocusNode? findFirstFocusInDirection(FocusNode currentNode, TraversalDirection direction) {
-    assert(direction != null);
-    assert(currentNode != null);
     switch (direction) {
       case TraversalDirection.up:
         // Find the bottom-most node so we can go up from there.
@@ -936,8 +931,7 @@
 // the sort data.
 class _ReadingOrderSortData with Diagnosticable {
   _ReadingOrderSortData(this.node)
-      : assert(node != null),
-        rect = node.rect,
+      : rect = node.rect,
         directionality = _findDirectionality(node.context!);
 
   final TextDirection? directionality;
@@ -1137,7 +1131,7 @@
     // It has to have at least topmost in it if the topmost is not degenerate.
     assert(topmost.rect.isEmpty || inBandOfTop.isNotEmpty);
 
-    // The topmost rect in is in a band by itself, so just return that one.
+    // The topmost rect is in a band by itself, so just return that one.
     if (inBandOfTop.length <= 1) {
       return topmost;
     }
@@ -1174,7 +1168,6 @@
   // order based on the directionality of the context for each node.
   @override
   Iterable<FocusNode> sortDescendants(Iterable<FocusNode> descendants, FocusNode currentNode) {
-    assert(descendants != null);
     if (descendants.length <= 1) {
       return descendants;
     }
@@ -1282,7 +1275,7 @@
 ///    for the [OrderedTraversalPolicy] to use.
 class NumericFocusOrder extends FocusOrder {
   /// Creates an object that describes a focus traversal order numerically.
-  const NumericFocusOrder(this.order) : assert(order != null);
+  const NumericFocusOrder(this.order);
 
   /// The numerical order to assign to the widget subtree using
   /// [FocusTraversalOrder].
@@ -1319,7 +1312,7 @@
 ///    for the [OrderedTraversalPolicy] to use.
 class LexicalFocusOrder extends FocusOrder {
   /// Creates an object that describes a focus traversal order lexically.
-  const LexicalFocusOrder(this.order) : assert(order != null);
+  const LexicalFocusOrder(this.order);
 
   /// The String that defines the lexical order to assign to the widget subtree
   /// using [FocusTraversalOrder].
@@ -1342,9 +1335,7 @@
 
 // Used to help sort the focus nodes in an OrderedFocusTraversalPolicy.
 class _OrderedFocusInfo {
-  const _OrderedFocusInfo({required this.node, required this.order})
-      : assert(node != null),
-        assert(order != null);
+  const _OrderedFocusInfo({required this.node, required this.order});
 
   final FocusNode node;
   final FocusOrder order;
@@ -1448,7 +1439,6 @@
   /// If no [FocusTraversalOrder] ancestor exists, or the order is null, this
   /// will assert in debug mode, and throw an exception in release mode.
   static FocusOrder of(BuildContext context) {
-    assert(context != null);
     final FocusTraversalOrder? marker = context.getElementForInheritedWidgetOfExactType<FocusTraversalOrder>()?.widget as FocusTraversalOrder?;
     assert(() {
       if (marker == null) {
@@ -1474,7 +1464,6 @@
   ///
   /// If no [FocusTraversalOrder] ancestor exists, or the order is null, returns null.
   static FocusOrder? maybeOf(BuildContext context) {
-    assert(context != null);
     final FocusTraversalOrder? marker = context.getElementForInheritedWidgetOfExactType<FocusTraversalOrder>()?.widget as FocusTraversalOrder?;
     return marker?.order;
   }
@@ -1537,9 +1526,7 @@
     this.descendantsAreFocusable = true,
     this.descendantsAreTraversable = true,
     required this.child,
-  }) : assert(descendantsAreFocusable != null),
-       assert(descendantsAreTraversable != null),
-       policy = policy ?? ReadingOrderTraversalPolicy();
+  }) : policy = policy ?? ReadingOrderTraversalPolicy();
 
   /// The policy used to move the focus from one focus node to another when
   /// traversing them using a keyboard.
@@ -1583,8 +1570,7 @@
   ///  * [maybeOf] for a similar function that will return null if no
   ///    [FocusTraversalGroup] ancestor is found.
   static FocusTraversalPolicy of(BuildContext context) {
-    assert(context != null);
-    final _FocusTraversalGroupMarker? inherited = context.dependOnInheritedWidgetOfExactType<_FocusTraversalGroupMarker>();
+    final _FocusTraversalGroupScope? inherited = context.dependOnInheritedWidgetOfExactType<_FocusTraversalGroupScope>();
     assert(() {
       if (inherited == null) {
         throw FlutterError(
@@ -1618,8 +1604,7 @@
   ///  * [of] for a similar function that will throw if no [FocusTraversalGroup]
   ///    ancestor is found.
   static FocusTraversalPolicy? maybeOf(BuildContext context) {
-    assert(context != null);
-    final _FocusTraversalGroupMarker? inherited = context.dependOnInheritedWidgetOfExactType<_FocusTraversalGroupMarker>();
+    final _FocusTraversalGroupScope? inherited = context.dependOnInheritedWidgetOfExactType<_FocusTraversalGroupScope>();
     return inherited?.policy;
   }
 
@@ -1657,7 +1642,7 @@
 
   @override
   Widget build(BuildContext context) {
-    return _FocusTraversalGroupMarker(
+    return _FocusTraversalGroupScope(
       policy: widget.policy,
       focusNode: focusNode,
       child: Focus(
@@ -1674,13 +1659,12 @@
 }
 
 // A "marker" inherited widget to make the group faster to find.
-class _FocusTraversalGroupMarker extends InheritedWidget {
-  const _FocusTraversalGroupMarker({
+class _FocusTraversalGroupScope extends InheritedWidget {
+  const _FocusTraversalGroupScope({
     required this.policy,
     required this.focusNode,
     required super.child,
-  })  : assert(policy != null),
-        assert(focusNode != null);
+  });
 
   final FocusTraversalPolicy policy;
   final FocusNode focusNode;
@@ -1695,8 +1679,7 @@
   /// Creates an intent used with [RequestFocusAction].
   ///
   /// The argument must not be null.
-  const RequestFocusIntent(this.focusNode)
-      : assert(focusNode != null);
+  const RequestFocusIntent(this.focusNode);
 
   /// The [FocusNode] that is to be focused.
   final FocusNode focusNode;
@@ -1722,7 +1705,7 @@
 /// logging, or undo and redo functionality.
 ///
 /// This [RequestFocusAction] class is the default action associated with the
-/// [RequestFocusIntent] in the [WidgetsApp], and it simply requests focus. You
+/// [RequestFocusIntent] in the [WidgetsApp]. It requests focus. You
 /// can redefine the associated action with your own [Actions] widget.
 ///
 /// See [FocusTraversalPolicy] for more information about focus traversal.
@@ -1762,8 +1745,8 @@
   }
 
   @override
-  KeyEventResult toKeyEventResult(NextFocusIntent intent, bool actionResult) {
-    return actionResult ? KeyEventResult.handled : KeyEventResult.skipRemainingHandlers;
+  KeyEventResult toKeyEventResult(NextFocusIntent intent, bool invokeResult) {
+    return invokeResult ? KeyEventResult.handled : KeyEventResult.skipRemainingHandlers;
   }
 }
 
@@ -1797,8 +1780,8 @@
   }
 
   @override
-  KeyEventResult toKeyEventResult(PreviousFocusIntent intent, bool actionResult) {
-    return actionResult ? KeyEventResult.handled : KeyEventResult.skipRemainingHandlers;
+  KeyEventResult toKeyEventResult(PreviousFocusIntent intent, bool invokeResult) {
+    return invokeResult ? KeyEventResult.handled : KeyEventResult.skipRemainingHandlers;
   }
 }
 
@@ -1813,8 +1796,7 @@
 /// See [FocusTraversalPolicy] for more information about focus traversal.
 class DirectionalFocusIntent extends Intent {
   /// Creates an intent used to move the focus in the given [direction].
-  const DirectionalFocusIntent(this.direction, {this.ignoreTextFields = true})
-      : assert(ignoreTextFields != null);
+  const DirectionalFocusIntent(this.direction, {this.ignoreTextFields = true});
 
   /// The direction in which to look for the next focusable node when the
   /// associated [DirectionalFocusAction] is invoked.
@@ -1881,8 +1863,7 @@
     super.key,
     this.excluding = true,
     required this.child,
-  }) : assert(excluding != null),
-       assert(child != null);
+  });
 
   /// If true, will make this widget's descendants untraversable.
   ///
diff --git a/framework/lib/src/widgets/form.dart b/framework/lib/src/widgets/form.dart
index 24d4afb..12dd37a 100644
--- a/framework/lib/src/widgets/form.dart
+++ b/framework/lib/src/widgets/form.dart
@@ -46,8 +46,7 @@
     this.onWillPop,
     this.onChanged,
     AutovalidateMode? autovalidateMode,
-  }) : assert(child != null),
-       autovalidateMode = autovalidateMode ?? AutovalidateMode.disabled;
+  }) : autovalidateMode = autovalidateMode ?? AutovalidateMode.disabled;
 
   /// Returns the [FormState] of the closest [Form] widget which encloses the
   /// given context, or null if none is found.
@@ -296,7 +295,7 @@
 /// Use a [GlobalKey] with [FormField] if you want to retrieve its current
 /// state, for example if you want one form field to depend on another.
 ///
-/// A [Form] ancestor is not required. The [Form] simply makes it easier to
+/// A [Form] ancestor is not required. The [Form] allows one to
 /// save, reset, or validate multiple fields at once. To use without a [Form],
 /// pass a [GlobalKey] to the constructor and use [GlobalKey.currentState] to
 /// save or reset the form field.
@@ -318,8 +317,7 @@
     this.enabled = true,
     AutovalidateMode? autovalidateMode,
     this.restorationId,
-  }) : assert(builder != null),
-       autovalidateMode = autovalidateMode ?? AutovalidateMode.disabled;
+  }) : autovalidateMode = autovalidateMode ?? AutovalidateMode.disabled;
 
   /// An optional method to call with the final value when the form is saved via
   /// [FormState.save].
diff --git a/framework/lib/src/widgets/framework.dart b/framework/lib/src/widgets/framework.dart
index 7bffc4f..e576a78 100644
--- a/framework/lib/src/widgets/framework.dart
+++ b/framework/lib/src/widgets/framework.dart
@@ -501,7 +501,7 @@
 ///
 ///   @override
 ///   Widget build(BuildContext context) {
-///     return Container(color: color, child: child);
+///     return ColoredBox(color: color, child: child);
 ///   }
 /// }
 /// ```
@@ -654,7 +654,7 @@
 ///    this ideal, the more efficient it will be.)
 ///
 ///  * If a subtree does not change, cache the widget that represents that
-///    subtree and re-use it each time it can be used. To do this, simply assign
+///    subtree and re-use it each time it can be used. To do this, assign
 ///    a widget to a `final` state variable and re-use it in the build method. It
 ///    is massively more efficient for a widget to be re-used than for a new (but
 ///    identically-configured) widget to be created. Another caching strategy
@@ -1091,7 +1091,6 @@
   /// whether the [mounted] property is true.
   @protected
   void setState(VoidCallback fn) {
-    assert(fn != null);
     assert(() {
       if (_debugLifecycleState == _StateLifecycle.defunct) {
         throw FlutterError.fromParts(<DiagnosticsNode>[
@@ -1519,7 +1518,6 @@
   }) {
     assert(T != dynamic);
     assert(T != ParentData);
-    assert(debugTypicalAncestorWidgetClass != null);
 
     final String description = 'The ParentDataWidget $this wants to apply ParentData of type $T to a RenderObject';
     return <DiagnosticsNode>[
@@ -1583,6 +1581,8 @@
 
 /// Base class for widgets that efficiently propagate information down the tree.
 ///
+/// {@youtube 560 315 https://www.youtube.com/watch?v=og-vJqLzg2c}
+///
 /// To obtain the nearest instance of a particular type of inherited widget from
 /// a build context, use [BuildContext.dependOnInheritedWidgetOfExactType].
 ///
@@ -1855,21 +1855,7 @@
   ///
   /// The [children] argument must not be null and must not contain any null
   /// objects.
-  MultiChildRenderObjectWidget({ super.key, this.children = const <Widget>[] })
-    : assert(children != null) {
-    assert(() {
-      for (int index = 0; index < children.length; index++) {
-        // TODO(a14n): remove this check to have a lot more const widget
-        if (children[index] == null) {
-          throw FlutterError(
-            "$runtimeType's children must not contain any null values, "
-            'but a null value was found at index $index',
-          );
-        }
-      }
-      return true;
-    }()); // https://github.com/dart-lang/sdk/issues/29276
-  }
+  const MultiChildRenderObjectWidget({ super.key, this.children = const <Widget>[] });
 
   /// The widgets below this widget in the tree.
   ///
@@ -2020,6 +2006,13 @@
 /// this callback.
 typedef ElementVisitor = void Function(Element element);
 
+/// Signature for the callback to [BuildContext.visitAncestorElements].
+///
+/// The argument is the ancestor being visited.
+///
+/// Return false to stop the walk.
+typedef ConditionalElementVisitor = bool Function(Element element);
+
 /// A handle to the location of a widget in the widget tree.
 ///
 /// This class presents a set of methods that can be used from
@@ -2221,7 +2214,7 @@
   ///
   /// All of the qualifications about when [dependOnInheritedWidgetOfExactType] can
   /// be called apply to this method as well.
-  InheritedWidget dependOnInheritedElement(InheritedElement ancestor, { Object aspect });
+  InheritedWidget dependOnInheritedElement(InheritedElement ancestor, { Object? aspect });
 
   /// Obtains the nearest widget of the given type `T`, which must be the type of a
   /// concrete [InheritedWidget] subclass, and registers this build context with
@@ -2229,6 +2222,7 @@
   /// type is introduced, or the widget goes away), this build context is
   /// rebuilt so that it can obtain new values from that widget.
   ///
+  /// {@template flutter.widgets.BuildContext.dependOnInheritedWidgetOfExactType}
   /// This is typically called implicitly from `of()` static methods, e.g.
   /// [Theme.of].
   ///
@@ -2262,6 +2256,7 @@
   /// [InheritedWidget] subclasses that supports partial updates, like
   /// [InheritedModel]. It specifies what "aspect" of the inherited
   /// widget this context depends on.
+  /// {@endtemplate}
   T? dependOnInheritedWidgetOfExactType<T extends InheritedWidget>({ Object? aspect });
 
   /// Obtains the element corresponding to the nearest widget of the given type `T`,
@@ -2269,6 +2264,7 @@
   ///
   /// Returns null if no such element is found.
   ///
+  /// {@template flutter.widgets.BuildContext.getElementForInheritedWidgetOfExactType}
   /// Calling this method is O(1) with a small constant factor.
   ///
   /// This method does not establish a relationship with the target in the way
@@ -2280,11 +2276,13 @@
   /// [dependOnInheritedWidgetOfExactType] in [State.didChangeDependencies]. It is
   /// safe to use this method from [State.deactivate], which is called whenever
   /// the widget is removed from the tree.
+  /// {@endtemplate}
   InheritedElement? getElementForInheritedWidgetOfExactType<T extends InheritedWidget>();
 
   /// Returns the nearest ancestor widget of the given type `T`, which must be the
   /// type of a concrete [Widget] subclass.
   ///
+  /// {@template flutter.widgets.BuildContext.findAncestorWidgetOfExactType}
   /// In general, [dependOnInheritedWidgetOfExactType] is more useful, since
   /// inherited widgets will trigger consumers to rebuild when they change. This
   /// method is appropriate when used in interaction event handlers (e.g.
@@ -2306,11 +2304,13 @@
   ///
   /// Returns null if a widget of the requested type does not appear in the
   /// ancestors of this context.
+  /// {@endtemplate}
   T? findAncestorWidgetOfExactType<T extends Widget>();
 
   /// Returns the [State] object of the nearest ancestor [StatefulWidget] widget
   /// that is an instance of the given type `T`.
   ///
+  /// {@template flutter.widgets.BuildContext.findAncestorStateOfType}
   /// This should not be used from build methods, because the build context will
   /// not be rebuilt if the value that would be returned by this method changes.
   /// In general, [dependOnInheritedWidgetOfExactType] is more appropriate for such
@@ -2332,6 +2332,7 @@
   /// because the widget tree is no longer stable at that time. To refer to
   /// an ancestor from one of those methods, save a reference to the ancestor
   /// by calling [findAncestorStateOfType] in [State.didChangeDependencies].
+  /// {@endtemplate}
   ///
   /// {@tool snippet}
   ///
@@ -2344,17 +2345,20 @@
   /// Returns the [State] object of the furthest ancestor [StatefulWidget] widget
   /// that is an instance of the given type `T`.
   ///
+  /// {@template flutter.widgets.BuildContext.findRootAncestorStateOfType}
   /// Functions the same way as [findAncestorStateOfType] but keeps visiting subsequent
   /// ancestors until there are none of the type instance of `T` remaining.
   /// Then returns the last one found.
   ///
   /// This operation is O(N) as well though N is the entire widget tree rather than
   /// a subtree.
+  /// {@endtemplate}
   T? findRootAncestorStateOfType<T extends State>();
 
   /// Returns the [RenderObject] object of the nearest ancestor [RenderObjectWidget] widget
   /// that is an instance of the given type `T`.
   ///
+  /// {@template flutter.widgets.BuildContext.findAncestorRenderObjectOfType}
   /// This should not be used from build methods, because the build context will
   /// not be rebuilt if the value that would be returned by this method changes.
   /// In general, [dependOnInheritedWidgetOfExactType] is more appropriate for such
@@ -2371,13 +2375,16 @@
   /// because the widget tree is no longer stable at that time. To refer to
   /// an ancestor from one of those methods, save a reference to the ancestor
   /// by calling [findAncestorRenderObjectOfType] in [State.didChangeDependencies].
+  /// {@endtemplate}
   T? findAncestorRenderObjectOfType<T extends RenderObject>();
 
   /// Walks the ancestor chain, starting with the parent of this build context's
-  /// widget, invoking the argument for each ancestor. The callback is given a
-  /// reference to the ancestor widget's corresponding [Element] object. The
-  /// walk stops when it reaches the root widget or when the callback returns
-  /// false. The callback must not return null.
+  /// widget, invoking the argument for each ancestor.
+  ///
+  /// {@template flutter.widgets.BuildContext.visitAncestorElements}
+  /// The callback is given a reference to the ancestor widget's corresponding
+  /// [Element] object. The walk stops when it reaches the root widget or when
+  /// the callback returns false. The callback must not return null.
   ///
   /// This is useful for inspecting the widget tree.
   ///
@@ -2387,10 +2394,12 @@
   /// because the element tree is no longer stable at that time. To refer to
   /// an ancestor from one of those methods, save a reference to the ancestor
   /// by calling [visitAncestorElements] in [State.didChangeDependencies].
-  void visitAncestorElements(bool Function(Element element) visitor);
+  /// {@endtemplate}
+  void visitAncestorElements(ConditionalElementVisitor visitor);
 
   /// Walks the children of this widget.
   ///
+  /// {@template flutter.widgets.BuildContext.visitChildElements}
   /// This is useful for applying changes to children after they are built
   /// without waiting for the next frame, especially if the children are known,
   /// and especially if there is exactly one child (as is always the case for
@@ -2408,6 +2417,7 @@
   /// significantly cheaper to use an [InheritedWidget] and have the descendants
   /// pull data down, than it is to use [visitChildElements] recursively to push
   /// data down to them.
+  /// {@endtemplate}
   void visitChildElements(ElementVisitor visitor);
 
   /// Start bubbling this notification at the given build context.
@@ -2519,7 +2529,6 @@
   /// Adds an element to the dirty elements list so that it will be rebuilt
   /// when [WidgetsBinding.drawFrame] calls [buildScope].
   void scheduleBuildFor(Element element) {
-    assert(element != null);
     assert(element.owner == this);
     assert(() {
       if (debugPrintScheduleBuildForStacks) {
@@ -2591,7 +2600,6 @@
   /// This mechanism is used to ensure that, for instance, [State.dispose] does
   /// not call [State.setState].
   void lockState(VoidCallback callback) {
-    assert(callback != null);
     assert(_debugStateLockLevel >= 0);
     assert(() {
       _debugStateLockLevel += 1;
@@ -2638,7 +2646,6 @@
     if (callback == null && _dirtyElements.isEmpty) {
       return;
     }
-    assert(context != null);
     assert(_debugStateLockLevel >= 0);
     assert(!_debugBuilding);
     assert(() {
@@ -2695,7 +2702,6 @@
       int index = 0;
       while (index < dirtyCount) {
         final Element element = _dirtyElements[index];
-        assert(element != null);
         assert(element._inDirtyList);
         assert(() {
           if (element._lifecycleState == _ElementLifecycle.active && !element._debugIsInScope(context)) {
@@ -2846,8 +2852,6 @@
 
   void _debugRemoveGlobalKeyReservationFor(Element parent, Element child) {
     assert(() {
-      assert(parent != null);
-      assert(child != null);
       _debugGlobalKeyReservations?[parent]?.remove(child);
       return true;
     }());
@@ -2856,9 +2860,7 @@
   void _registerGlobalKey(GlobalKey key, Element element) {
     assert(() {
       if (_globalKeyRegistry.containsKey(key)) {
-        assert(element.widget != null);
         final Element oldElement = _globalKeyRegistry[key]!;
-        assert(oldElement.widget != null);
         assert(element.widget.runtimeType != oldElement.widget.runtimeType);
         _debugIllFatedElements?.add(oldElement);
       }
@@ -2870,9 +2872,7 @@
   void _unregisterGlobalKey(GlobalKey key, Element element) {
     assert(() {
       if (_globalKeyRegistry.containsKey(key) && _globalKeyRegistry[key] != element) {
-        assert(element.widget != null);
         final Element oldElement = _globalKeyRegistry[key]!;
-        assert(oldElement.widget != null);
         assert(element.widget.runtimeType != oldElement.widget.runtimeType);
       }
       return true;
@@ -2884,8 +2884,6 @@
 
   void _debugReserveGlobalKeyFor(Element parent, Element child, GlobalKey key) {
     assert(() {
-      assert(parent != null);
-      assert(child != null);
       _debugGlobalKeyReservations?[parent] ??= <Element, GlobalKey>{};
       _debugGlobalKeyReservations?[parent]![child] = key;
       return true;
@@ -2972,8 +2970,6 @@
       Map<GlobalKey, Set<Element>>? duplicates;
       for (final Element element in _debugIllFatedElements ?? const <Element>{}) {
         if (element._lifecycleState != _ElementLifecycle.defunct) {
-          assert(element != null);
-          assert(element.widget != null);
           assert(element.widget.key != null);
           final GlobalKey key = element.widget.key! as GlobalKey;
           assert(_globalKeyRegistry.containsKey(key));
@@ -3235,8 +3231,7 @@
   ///
   /// Typically called by an override of [Widget.createElement].
   Element(Widget widget)
-    : assert(widget != null),
-      _widget = widget {
+    : _widget = widget {
     if (kFlutterMemoryAllocationsEnabled) {
       MemoryAllocations.instance.dispatchObjectCreated(
         library: _flutterWidgetsLibrary,
@@ -3717,7 +3712,6 @@
   @mustCallSuper
   void mount(Element? parent, Object? newSlot) {
     assert(_lifecycleState == _ElementLifecycle.initial);
-    assert(widget != null);
     assert(_parent == null);
     assert(parent == null || parent._lifecycleState == _ElementLifecycle.active);
     assert(slot == null);
@@ -3758,10 +3752,7 @@
     // only call _AssertionError._evaluateAssertion once.
     assert(
       _lifecycleState == _ElementLifecycle.active
-        && widget != null
-        && newWidget != null
         && newWidget != widget
-        && depth != null
         && Widget.canUpdate(widget, newWidget),
     );
     // This Element was told to update and we can now release all the global key
@@ -3785,7 +3776,6 @@
   @protected
   void updateSlotForChild(Element child, Object? newSlot) {
     assert(_lifecycleState == _ElementLifecycle.active);
-    assert(child != null);
     assert(child._parent == this);
     void visit(Element element) {
       element._updateSlot(newSlot);
@@ -3798,10 +3788,8 @@
 
   void _updateSlot(Object? newSlot) {
     assert(_lifecycleState == _ElementLifecycle.active);
-    assert(widget != null);
     assert(_parent != null);
     assert(_parent!._lifecycleState == _ElementLifecycle.active);
-    assert(depth != null);
     _slot = newSlot;
   }
 
@@ -3817,7 +3805,7 @@
 
   /// Remove [renderObject] from the render tree.
   ///
-  /// The default implementation of this function simply calls
+  /// The default implementation of this function calls
   /// [detachRenderObject] recursively on each child. The
   /// [RenderObjectElement.detachRenderObject] override does the actual work of
   /// removing [renderObject] from the render tree.
@@ -3832,7 +3820,7 @@
 
   /// Add [renderObject] to the render tree at the location specified by `newSlot`.
   ///
-  /// The default implementation of this function simply calls
+  /// The default implementation of this function calls
   /// [attachRenderObject] recursively on each child. The
   /// [RenderObjectElement.attachRenderObject] override does the actual work of
   /// adding [renderObject] to the render tree.
@@ -3912,7 +3900,6 @@
   @protected
   @pragma('vm:prefer-inline')
   Element inflateWidget(Widget newWidget, Object? newSlot) {
-    assert(newWidget != null);
 
     final bool isTimelineTracked = !kReleaseMode && _isProfileBuildsEnabledFor(newWidget);
     if (isTimelineTracked) {
@@ -3989,7 +3976,6 @@
   /// [forgetChild] to cause the old parent to update its child model.
   @protected
   void deactivateChild(Element child) {
-    assert(child != null);
     assert(child._parent == this);
     child._parent = null;
     child.detachRenderObject();
@@ -4078,9 +4064,7 @@
   @mustCallSuper
   void activate() {
     assert(_lifecycleState == _ElementLifecycle.inactive);
-    assert(widget != null);
     assert(owner != null);
-    assert(depth != null);
     final bool hadDependencies = (_dependencies != null && _dependencies!.isNotEmpty) || _hadUnsatisfiedDependencies;
     _lifecycleState = _ElementLifecycle.active;
     // We unregistered our dependencies in deactivate, but never cleared the list.
@@ -4116,7 +4100,6 @@
   void deactivate() {
     assert(_lifecycleState == _ElementLifecycle.active);
     assert(_widget != null); // Use the private property to avoid a CastError during hot reload.
-    assert(depth != null);
     if (_dependencies != null && _dependencies!.isNotEmpty) {
       for (final InheritedElement dependency in _dependencies!) {
         dependency._dependents.remove(this);
@@ -4162,7 +4145,6 @@
   void unmount() {
     assert(_lifecycleState == _ElementLifecycle.inactive);
     assert(_widget != null); // Use the private property to avoid a CastError during hot reload.
-    assert(depth != null);
     assert(owner != null);
     if (kFlutterMemoryAllocationsEnabled) {
       MemoryAllocations.instance.dispatchObjectDisposed(object: this);
@@ -4356,7 +4338,6 @@
 
   @override
   InheritedWidget dependOnInheritedElement(InheritedElement ancestor, { Object? aspect }) {
-    assert(ancestor != null);
     _dependencies ??= HashSet<InheritedElement>();
     _dependencies!.add(ancestor);
     ancestor.updateDependencies(this, aspect);
@@ -4452,7 +4433,7 @@
   }
 
   @override
-  void visitAncestorElements(bool Function(Element element) visitor) {
+  void visitAncestorElements(ConditionalElementVisitor visitor) {
     assert(_debugCheckStateIsActiveForAncestorLookup());
     Element? ancestor = _parent;
     while (ancestor != null && visitor(ancestor)) {
@@ -4687,12 +4668,15 @@
       owner!._debugCurrentBuildTarget = this;
       return true;
     }());
-    performRebuild();
-    assert(() {
-      assert(owner!._debugCurrentBuildTarget == this);
-      owner!._debugCurrentBuildTarget = debugPreviousBuildTarget;
-      return true;
-    }());
+    try {
+      performRebuild();
+    } finally {
+      assert(() {
+        assert(owner!._debugCurrentBuildTarget == this);
+        owner!._debugCurrentBuildTarget = debugPreviousBuildTarget;
+        return true;
+      }());
+    }
     assert(!_dirty);
   }
 
@@ -4910,14 +4894,14 @@
 ///
 /// The child should typically be part of the returned widget tree.
 ///
-/// Used by [AnimatedBuilder.builder], as well as [WidgetsApp.builder] and
-/// [MaterialApp.builder].
+/// Used by [AnimatedBuilder.builder], [ListenableBuilder.builder],
+/// [WidgetsApp.builder], and [MaterialApp.builder].
 ///
 /// See also:
 ///
-///  * [WidgetBuilder], which is similar but only takes a [BuildContext].
-///  * [IndexedWidgetBuilder], which is similar but also takes an index.
-///  * [ValueWidgetBuilder], which is similar but takes a value and a child.
+/// * [WidgetBuilder], which is similar but only takes a [BuildContext].
+/// * [IndexedWidgetBuilder], which is similar but also takes an index.
+/// * [ValueWidgetBuilder], which is similar but takes a value and a child.
 typedef TransitionBuilder = Widget Function(BuildContext context, Widget? child);
 
 /// An [Element] that composes other [Element]s.
@@ -5197,7 +5181,6 @@
 
   @override
   InheritedWidget dependOnInheritedElement(Element ancestor, { Object? aspect }) {
-    assert(ancestor != null);
     assert(() {
       final Type targetType = ancestor.widget.runtimeType;
       if (state._debugLifecycleState == _StateLifecycle.created) {
@@ -5293,7 +5276,6 @@
   @override
   void update(ProxyWidget newWidget) {
     final ProxyWidget oldWidget = widget as ProxyWidget;
-    assert(widget != null);
     assert(widget != newWidget);
     super.update(newWidget);
     assert(widget == newWidget);
@@ -5370,7 +5352,6 @@
   /// It is more efficient than requesting an additional frame just for the
   /// purpose of updating the [KeepAlive] widget.
   void applyWidgetOutOfTurn(ParentDataWidget<T> newWidget) {
-    assert(newWidget != null);
     assert(newWidget.debugCanApplyOutOfTurn());
     assert(newWidget.child == (widget as ParentDataWidget<T>).child);
     _applyParentData(newWidget);
@@ -5622,7 +5603,7 @@
 ///
 /// However, the immediate children of the element may not be the ones that
 /// eventually produce the actual [RenderObject] that they correspond to. For
-/// example a [StatelessElement] (the element of a [StatelessWidget]) simply
+/// example, a [StatelessElement] (the element of a [StatelessWidget])
 /// corresponds to whatever [RenderObject] its child (the element returned by
 /// its [StatelessWidget.build] method) corresponds to.
 ///
@@ -5899,7 +5880,7 @@
   /// list after the [RenderObject] associated with the [Element] provided as
   /// [IndexedSlot.value] in the [slot] object.
   ///
-  /// Simply using the previous sibling as a [slot] is not enough, though, because
+  /// Using the previous sibling as a [slot] is not enough, though, because
   /// child [RenderObject]s are only moved around when the [slot] of their
   /// associated [RenderObjectElement]s is updated. When the order of child
   /// [Element]s is changed, some elements in the list may move to a new index
@@ -5915,8 +5896,6 @@
   /// previous sibling.
   @protected
   List<Element> updateChildren(List<Element> oldChildren, List<Widget> newWidgets, { Set<Element>? forgottenChildren, List<Object?>? slots }) {
-    assert(oldChildren != null);
-    assert(newWidgets != null);
     assert(slots == null || newWidgets.length == slots.length);
 
     Element? replaceWithNullIfForgotten(Element child) {
@@ -6062,7 +6041,7 @@
       assert(Widget.canUpdate(oldChild.widget, newWidget));
       final Element newChild = updateChild(oldChild, newWidget, slotFor(newChildrenTop, previousChild))!;
       assert(newChild._lifecycleState == _ElementLifecycle.active);
-      assert(oldChild == newChild || oldChild == null || oldChild._lifecycleState != _ElementLifecycle.active);
+      assert(oldChild == newChild || oldChild._lifecycleState != _ElementLifecycle.active);
       newChildren[newChildrenTop] = newChild;
       previousChild = newChild;
       newChildrenTop += 1;
diff --git a/framework/lib/src/widgets/gesture_detector.dart b/framework/lib/src/widgets/gesture_detector.dart
index 100eaba..42adfac 100644
--- a/framework/lib/src/widgets/gesture_detector.dart
+++ b/framework/lib/src/widgets/gesture_detector.dart
@@ -93,9 +93,7 @@
   /// Creates a gesture recognizer factory with the given callbacks.
   ///
   /// The arguments must not be null.
-  const GestureRecognizerFactoryWithHandlers(this._constructor, this._initializer)
-    : assert(_constructor != null),
-      assert(_initializer != null);
+  const GestureRecognizerFactoryWithHandlers(this._constructor, this._initializer);
 
   final GestureRecognizerFactoryConstructor<T> _constructor;
 
@@ -215,11 +213,11 @@
   /// Creates a widget that detects gestures.
   ///
   /// Pan and scale callbacks cannot be used simultaneously because scale is a
-  /// superset of pan. Simply use the scale callbacks instead.
+  /// superset of pan. Use the scale callbacks instead.
   ///
   /// Horizontal and vertical drag callbacks cannot be used simultaneously
-  /// because a combination of a horizontal and vertical drag is a pan. Simply
-  /// use the pan callbacks instead.
+  /// because a combination of a horizontal and vertical drag is a pan.
+  /// Use the pan callbacks instead.
   ///
   /// {@youtube 560 315 https://www.youtube.com/watch?v=WhVXkCFPmK4}
   ///
@@ -288,10 +286,10 @@
     this.behavior,
     this.excludeFromSemantics = false,
     this.dragStartBehavior = DragStartBehavior.start,
+    this.trackpadScrollCausesScale = false,
+    this.trackpadScrollToScaleFactor = kDefaultTrackpadScrollToScaleFactor,
     this.supportedDevices,
-  }) : assert(excludeFromSemantics != null),
-       assert(dragStartBehavior != null),
-       assert(() {
+  }) : assert(() {
          final bool haveVerticalDrag = onVerticalDragStart != null || onVerticalDragUpdate != null || onVerticalDragEnd != null;
          final bool haveHorizontalDrag = onHorizontalDragStart != null || onHorizontalDragUpdate != null || onHorizontalDragEnd != null;
          final bool havePan = onPanStart != null || onPanUpdate != null || onPanEnd != null;
@@ -1014,10 +1012,16 @@
   /// If set to null, events from all device types will be recognized. Defaults to null.
   final Set<PointerDeviceKind>? supportedDevices;
 
+  /// {@macro flutter.gestures.scale.trackpadScrollCausesScale}
+  final bool trackpadScrollCausesScale;
+
+  /// {@macro flutter.gestures.scale.trackpadScrollToScaleFactor}
+  final Offset trackpadScrollToScaleFactor;
+
   @override
   Widget build(BuildContext context) {
     final Map<Type, GestureRecognizerFactory> gestures = <Type, GestureRecognizerFactory>{};
-    final DeviceGestureSettings? gestureSettings = MediaQuery.maybeOf(context)?.gestureSettings;
+    final DeviceGestureSettings? gestureSettings = MediaQuery.maybeGestureSettingsOf(context);
 
     if (onTapDown != null ||
         onTapUp != null ||
@@ -1186,7 +1190,9 @@
             ..onUpdate = onScaleUpdate
             ..onEnd = onScaleEnd
             ..dragStartBehavior = dragStartBehavior
-            ..gestureSettings = gestureSettings;
+            ..gestureSettings = gestureSettings
+            ..trackpadScrollCausesScale = trackpadScrollCausesScale
+            ..trackpadScrollToScaleFactor = trackpadScrollToScaleFactor;
         },
       );
     }
@@ -1276,8 +1282,7 @@
     this.behavior,
     this.excludeFromSemantics = false,
     this.semantics,
-  }) : assert(gestures != null),
-       assert(excludeFromSemantics != null);
+  });
 
   /// The widget below this widget in the tree.
   ///
@@ -1559,7 +1564,7 @@
     super.child,
     required this.behavior,
     required this.assignSemantics,
-  }) : assert(assignSemantics != null);
+  });
 
   final HitTestBehavior behavior;
   final _AssignSemantics assignSemantics;
@@ -1632,7 +1637,6 @@
     }
 
     return () {
-      assert(tap != null);
       tap.onTapDown?.call(TapDownDetails());
       tap.onTapUp?.call(TapUpDetails(kind: PointerDeviceKind.unknown));
       tap.onTap?.call();
diff --git a/framework/lib/src/widgets/heroes.dart b/framework/lib/src/widgets/heroes.dart
index b88d411..06219e5 100644
--- a/framework/lib/src/widgets/heroes.dart
+++ b/framework/lib/src/widgets/heroes.dart
@@ -180,9 +180,7 @@
     this.placeholderBuilder,
     this.transitionOnUserGestures = false,
     required this.child,
-  }) : assert(tag != null),
-       assert(transitionOnUserGestures != null),
-       assert(child != null);
+  });
 
   /// The identifier for this particular hero. If the tag of this hero matches
   /// the tag of a hero on a [PageRoute] that we're navigating to or from, then
@@ -272,9 +270,6 @@
     bool isUserGestureTransition,
     NavigatorState navigator,
   ) {
-    assert(context != null);
-    assert(isUserGestureTransition != null);
-    assert(navigator != null);
     final Map<Object, _HeroState> result = <Object, _HeroState>{};
 
     void inviteHero(StatefulElement hero, Object tag) {
@@ -308,7 +303,6 @@
       if (widget is Hero) {
         final StatefulElement hero = element as StatefulElement;
         final Object tag = widget.tag;
-        assert(tag != null);
         if (Navigator.of(hero) == navigator) {
           inviteHero(hero, tag);
         } else {
@@ -372,7 +366,7 @@
     _shouldIncludeChild = shouldIncludedChildInPlaceholder;
     assert(mounted);
     final RenderBox box = context.findRenderObject()! as RenderBox;
-    assert(box != null && box.hasSize);
+    assert(box.hasSize);
     setState(() {
       _placeholderSize = box.size;
     });
@@ -480,7 +474,7 @@
   static Rect _boundingBoxFor(BuildContext context, BuildContext? ancestorContext) {
     assert(ancestorContext != null);
     final RenderBox box = context.findRenderObject()! as RenderBox;
-    assert(box != null && box.hasSize && box.size.isFinite);
+    assert(box.hasSize && box.size.isFinite);
     return MatrixUtils.transformRect(
       box.getTransformTo(ancestorContext?.findRenderObject()),
       Offset.zero & box.size,
@@ -537,7 +531,6 @@
 
   // The OverlayEntry WidgetBuilder callback for the hero's overlay.
   Widget _buildOverlay(BuildContext context) {
-    assert(manifest != null);
     shuttle ??= manifest.shuttleBuilder(
       context,
       manifest.animation,
@@ -650,9 +643,7 @@
     assert(!_aborted);
     assert(() {
       final Animation<double> initial = initialManifest.animation;
-      assert(initial != null);
       final HeroFlightDirection type = initialManifest.type;
-      assert(type != null);
       switch (type) {
         case HeroFlightDirection.pop:
           return initial.value == 1.0 && initialManifest.isUserGestureTransition
@@ -796,14 +787,12 @@
   @override
   void didPush(Route<dynamic> route, Route<dynamic>? previousRoute) {
     assert(navigator != null);
-    assert(route != null);
     _maybeStartHeroTransition(previousRoute, route, HeroFlightDirection.push, false);
   }
 
   @override
   void didPop(Route<dynamic> route, Route<dynamic>? previousRoute) {
     assert(navigator != null);
-    assert(route != null);
     // Don't trigger another flight when a pop is committed as a user gesture
     // back swipe is snapped.
     if (!navigator!.userGestureInProgress) {
@@ -823,7 +812,6 @@
   @override
   void didStartUserGesture(Route<dynamic> route, Route<dynamic>? previousRoute) {
     assert(navigator != null);
-    assert(route != null);
     _maybeStartHeroTransition(route, previousRoute, HeroFlightDirection.pop, true);
   }
 
@@ -1054,8 +1042,7 @@
     super.key,
     required this.child,
     this.enabled = true,
-  }) : assert(child != null),
-       assert(enabled != null);
+  });
 
   /// The subtree to place inside the [HeroMode].
   final Widget child;
diff --git a/framework/lib/src/widgets/icon.dart b/framework/lib/src/widgets/icon.dart
index 7bea0a9..81964ce 100644
--- a/framework/lib/src/widgets/icon.dart
+++ b/framework/lib/src/widgets/icon.dart
@@ -36,9 +36,9 @@
 /// ![The following code snippet would generate a row of icons consisting of a pink heart, a green musical note, and a blue umbrella, each progressively bigger than the last.](https://flutter.github.io/assets-for-api-docs/assets/widgets/icon.png)
 ///
 /// ```dart
-/// Row(
+/// const Row(
 ///   mainAxisAlignment: MainAxisAlignment.spaceAround,
-///   children: const <Widget>[
+///   children: <Widget>[
 ///     Icon(
 ///       Icons.favorite,
 ///       color: Colors.pink,
diff --git a/framework/lib/src/widgets/icon_data.dart b/framework/lib/src/widgets/icon_data.dart
index 69349df..15a8234 100644
--- a/framework/lib/src/widgets/icon_data.dart
+++ b/framework/lib/src/widgets/icon_data.dart
@@ -78,9 +78,7 @@
     super.showName,
     super.style,
     super.level,
-  }) : assert(showName != null),
-       assert(style != null),
-       assert(level != null);
+  });
 
   @override
   Map<String, Object?> toJsonMap(DiagnosticsSerializationDelegate delegate) {
@@ -93,3 +91,14 @@
     return json;
   }
 }
+
+class _StaticIconProvider {
+  const _StaticIconProvider();
+}
+
+/// Annotation for classes that only provide static const [IconData] instances.
+///
+/// This is a hint to the font tree shaker to ignore the constant instances
+/// of [IconData] appearing in the class when tracking which code points
+/// should be retained in the bundled font.
+const Object staticIconProvider = _StaticIconProvider();
diff --git a/framework/lib/src/widgets/icon_theme.dart b/framework/lib/src/widgets/icon_theme.dart
index 0de6203..1d8a899 100644
--- a/framework/lib/src/widgets/icon_theme.dart
+++ b/framework/lib/src/widgets/icon_theme.dart
@@ -23,8 +23,7 @@
     super.key,
     required this.data,
     required super.child,
-  }) : assert(data != null),
-       assert(child != null);
+  });
 
   /// Creates an icon theme that controls the properties of
   /// descendant widgets, and merges in the current icon theme, if any.
diff --git a/framework/lib/src/widgets/icon_theme_data.dart b/framework/lib/src/widgets/icon_theme_data.dart
index 3e15285..c1285bd 100644
--- a/framework/lib/src/widgets/icon_theme_data.dart
+++ b/framework/lib/src/widgets/icon_theme_data.dart
@@ -76,7 +76,7 @@
 
   /// Returns a new icon theme that matches this icon theme but with some values
   /// replaced by the non-null parameters of the given icon theme. If the given
-  /// icon theme is null, simply returns this icon theme.
+  /// icon theme is null, returns this icon theme.
   IconThemeData merge(IconThemeData? other) {
     if (other == null) {
       return this;
@@ -167,7 +167,6 @@
   ///
   /// {@macro dart.ui.shadow.lerp}
   static IconThemeData lerp(IconThemeData? a, IconThemeData? b, double t) {
-    assert(t != null);
     return IconThemeData(
       size: ui.lerpDouble(a?.size, b?.size, t),
       fill: ui.lerpDouble(a?.fill, b?.fill, t),
diff --git a/framework/lib/src/widgets/image.dart b/framework/lib/src/widgets/image.dart
index 6b54548..615fc31 100644
--- a/framework/lib/src/widgets/image.dart
+++ b/framework/lib/src/widgets/image.dart
@@ -53,7 +53,7 @@
 ImageConfiguration createLocalImageConfiguration(BuildContext context, { Size? size }) {
   return ImageConfiguration(
     bundle: DefaultAssetBundle.of(context),
-    devicePixelRatio: MediaQuery.maybeOf(context)?.devicePixelRatio ?? 1.0,
+    devicePixelRatio: MediaQuery.maybeDevicePixelRatioOf(context) ?? 1.0,
     locale: Localizations.maybeLocaleOf(context),
     textDirection: Directionality.maybeOf(context),
     size: size,
@@ -288,8 +288,9 @@
 /// memory usage of [ImageCache].
 ///
 /// In the case where a network image is used on the Web platform, the
-/// `cacheWidth` and `cacheHeight` parameters are ignored as the Web engine
-/// delegates image decoding of network images to the Web, which does not support
+/// `cacheWidth` and `cacheHeight` parameters are only supported when the application is
+/// running with the CanvasKit renderer. When the application is using the HTML renderer,
+/// the web engine delegates image decoding of network images to the Web, which does not support
 /// custom decode sizes.
 ///
 /// See also:
@@ -342,12 +343,7 @@
     this.gaplessPlayback = false,
     this.isAntiAlias = false,
     this.filterQuality = FilterQuality.low,
-  }) : assert(image != null),
-       assert(alignment != null),
-       assert(repeat != null),
-       assert(filterQuality != null),
-       assert(matchTextDirection != null),
-       assert(isAntiAlias != null);
+  });
 
   /// Creates a widget that displays an [ImageStream] obtained from the network.
   ///
@@ -405,12 +401,8 @@
     int? cacheWidth,
     int? cacheHeight,
   }) : image = ResizeImage.resizeIfNeeded(cacheWidth, cacheHeight, NetworkImage(src, scale: scale, headers: headers)),
-       assert(alignment != null),
-       assert(repeat != null),
-       assert(matchTextDirection != null),
        assert(cacheWidth == null || cacheWidth > 0),
-       assert(cacheHeight == null || cacheHeight > 0),
-       assert(isAntiAlias != null);
+       assert(cacheHeight == null || cacheHeight > 0);
 
   /// Creates a widget that displays an [ImageStream] obtained from a [File].
   ///
@@ -474,13 +466,8 @@
         ),
        image = ResizeImage.resizeIfNeeded(cacheWidth, cacheHeight, FileImage(file, scale: scale)),
        loadingBuilder = null,
-       assert(alignment != null),
-       assert(repeat != null),
-       assert(filterQuality != null),
-       assert(matchTextDirection != null),
        assert(cacheWidth == null || cacheWidth > 0),
-       assert(cacheHeight == null || cacheHeight > 0),
-       assert(isAntiAlias != null);
+       assert(cacheHeight == null || cacheHeight > 0);
 
   // TODO(ianh): Implement the following (see ../services/image_resolution.dart):
   //
@@ -640,12 +627,8 @@
            : AssetImage(name, bundle: bundle, package: package),
        ),
        loadingBuilder = null,
-       assert(alignment != null),
-       assert(repeat != null),
-       assert(matchTextDirection != null),
        assert(cacheWidth == null || cacheWidth > 0),
-       assert(cacheHeight == null || cacheHeight > 0),
-       assert(isAntiAlias != null);
+       assert(cacheHeight == null || cacheHeight > 0);
 
   /// Creates a widget that displays an [ImageStream] obtained from a [Uint8List].
   ///
@@ -702,12 +685,8 @@
     int? cacheHeight,
   }) : image = ResizeImage.resizeIfNeeded(cacheWidth, cacheHeight, MemoryImage(bytes, scale: scale)),
        loadingBuilder = null,
-       assert(alignment != null),
-       assert(repeat != null),
-       assert(matchTextDirection != null),
        assert(cacheWidth == null || cacheWidth > 0),
-       assert(cacheHeight == null || cacheHeight > 0),
-       assert(isAntiAlias != null);
+       assert(cacheHeight == null || cacheHeight > 0);
 
   /// The image to display.
   final ImageProvider image;
@@ -1106,7 +1085,7 @@
   }
 
   void _updateInvertColors() {
-    _invertColors = MediaQuery.maybeOf(context)?.invertColors
+    _invertColors = MediaQuery.maybeInvertColorsOf(context)
         ?? SemanticsBinding.instance.accessibilityFeatures.invertColors;
   }
 
@@ -1120,7 +1099,6 @@
         context,
         size: widget.width != null && widget.height != null ? Size(widget.width!, widget.height!) : null,
       ));
-    assert(newStream != null);
     _updateSourceStream(newStream);
   }
 
diff --git a/framework/lib/src/widgets/image_filter.dart b/framework/lib/src/widgets/image_filter.dart
index 963e231..5dec119 100644
--- a/framework/lib/src/widgets/image_filter.dart
+++ b/framework/lib/src/widgets/image_filter.dart
@@ -41,7 +41,7 @@
     required this.imageFilter,
     super.child,
     this.enabled = true,
-  }) : assert(imageFilter != null);
+  });
 
   /// The image filter to apply to the child of this widget.
   final ImageFilter imageFilter;
@@ -90,7 +90,6 @@
   ImageFilter get imageFilter => _imageFilter;
   ImageFilter _imageFilter;
   set imageFilter(ImageFilter value) {
-    assert(value != null);
     if (value != _imageFilter) {
       _imageFilter = value;
       markNeedsCompositedLayerUpdate();
diff --git a/framework/lib/src/widgets/implicit_animations.dart b/framework/lib/src/widgets/implicit_animations.dart
index 6473601..302e865 100644
--- a/framework/lib/src/widgets/implicit_animations.dart
+++ b/framework/lib/src/widgets/implicit_animations.dart
@@ -281,8 +281,7 @@
     this.curve = Curves.linear,
     required this.duration,
     this.onEnd,
-  }) : assert(curve != null),
-       assert(duration != null);
+  });
 
   /// The curve to apply when animating the parameters of this container.
   final Curve curve;
@@ -816,8 +815,7 @@
     super.curve,
     required super.duration,
     super.onEnd,
-  }) : assert(padding != null),
-       assert(padding.isNonNegative);
+  }) : assert(padding.isNonNegative);
 
   /// The amount of space by which to inset the child.
   final EdgeInsetsGeometry padding;
@@ -907,8 +905,7 @@
     super.curve,
     required super.duration,
     super.onEnd,
-  }) : assert(alignment != null),
-       assert(widthFactor == null || widthFactor >= 0.0),
+  }) : assert(widthFactor == null || widthFactor >= 0.0),
        assert(heightFactor == null || heightFactor >= 0.0);
 
   /// How to align the child.
@@ -1361,7 +1358,7 @@
     super.curve,
     required super.duration,
     super.onEnd,
-  }) : assert(scale != null);
+  });
 
   /// The widget below this widget in the tree.
   ///
@@ -1488,7 +1485,7 @@
     super.curve,
     required super.duration,
     super.onEnd,
-  }) : assert(turns != null);
+  });
 
   /// The widget below this widget in the tree.
   ///
@@ -1699,7 +1696,7 @@
     required super.duration,
     super.onEnd,
     this.alwaysIncludeSemantics = false,
-  }) : assert(opacity != null && opacity >= 0.0 && opacity <= 1.0);
+  }) : assert(opacity >= 0.0 && opacity <= 1.0);
 
   /// The widget below this widget in the tree.
   ///
@@ -1794,7 +1791,7 @@
     required super.duration,
     super.onEnd,
     this.alwaysIncludeSemantics = false,
-  }) : assert(opacity != null && opacity >= 0.0 && opacity <= 1.0);
+  }) : assert(opacity >= 0.0 && opacity <= 1.0);
 
   /// The sliver below this widget in the tree.
   final Widget? sliver;
@@ -1889,12 +1886,7 @@
     super.curve,
     required super.duration,
     super.onEnd,
-  }) : assert(style != null),
-       assert(child != null),
-       assert(softWrap != null),
-       assert(overflow != null),
-       assert(maxLines == null || maxLines > 0),
-       assert(textWidthBasis != null);
+  }) : assert(maxLines == null || maxLines > 0);
 
   /// The widget below this widget in the tree.
   ///
@@ -2017,15 +2009,7 @@
     super.curve,
     required super.duration,
     super.onEnd,
-  }) : assert(child != null),
-       assert(shape != null),
-       assert(clipBehavior != null),
-       assert(borderRadius != null),
-       assert(elevation != null && elevation >= 0.0),
-       assert(color != null),
-       assert(shadowColor != null),
-       assert(animateColor != null),
-       assert(animateShadowColor != null);
+  }) : assert(elevation >= 0.0);
 
   /// The widget below this widget in the tree.
   ///
@@ -2151,8 +2135,7 @@
     super.curve,
     required super.duration,
     super.onEnd,
-  }) : assert(alignment != null),
-       assert(widthFactor == null || widthFactor >= 0.0),
+  }) : assert(widthFactor == null || widthFactor >= 0.0),
        assert(heightFactor == null || heightFactor >= 0.0);
 
   /// The widget below this widget in the tree.
diff --git a/framework/lib/src/widgets/inherited_notifier.dart b/framework/lib/src/widgets/inherited_notifier.dart
index d4ad02c..aa77678 100644
--- a/framework/lib/src/widgets/inherited_notifier.dart
+++ b/framework/lib/src/widgets/inherited_notifier.dart
@@ -64,7 +64,7 @@
     super.key,
     this.notifier,
     required super.child,
-  }) : assert(child != null);
+  });
 
   /// The [Listenable] object to which to listen.
   ///
diff --git a/framework/lib/src/widgets/inherited_theme.dart b/framework/lib/src/widgets/inherited_theme.dart
index 920ebaf..673a94f 100644
--- a/framework/lib/src/widgets/inherited_theme.dart
+++ b/framework/lib/src/widgets/inherited_theme.dart
@@ -64,8 +64,6 @@
   /// the wrapped `child` - unless this method is called again to re-wrap the
   /// child.
   static Widget captureAll(BuildContext context, Widget child, {BuildContext? to}) {
-    assert(child != null);
-    assert(context != null);
 
     return capture(from: context, to: to).wrap(child);
   }
@@ -88,7 +86,6 @@
   /// This method can be expensive if there are many widgets between `from` and
   /// `to` (it walks the element tree between those nodes).
   static CapturedThemes capture({ required BuildContext from, required BuildContext? to }) {
-    assert(from != null);
 
     if (from == to) {
       // Nothing to capture.
@@ -148,7 +145,7 @@
   const _CaptureAll({
     required this.themes,
     required this.child,
-  }) : assert(themes != null), assert(child != null);
+  });
 
   final List<InheritedTheme> themes;
   final Widget child;
diff --git a/framework/lib/src/widgets/interactive_viewer.dart b/framework/lib/src/widgets/interactive_viewer.dart
index ac80144..27719e5 100644
--- a/framework/lib/src/widgets/interactive_viewer.dart
+++ b/framework/lib/src/widgets/interactive_viewer.dart
@@ -85,24 +85,17 @@
     this.onInteractionUpdate,
     this.panEnabled = true,
     this.scaleEnabled = true,
-    this.scaleFactor = 200.0,
+    this.scaleFactor = kDefaultMouseScrollToScaleFactor,
     this.transformationController,
     this.alignment,
+    this.trackpadScrollCausesScale = false,
     required Widget this.child,
-  }) : assert(alignPanAxis != null),
-       assert(panAxis != null),
-       assert(child != null),
-       assert(constrained != null),
-       assert(minScale != null),
-       assert(minScale > 0),
+  }) : assert(minScale > 0),
        assert(interactionEndFrictionCoefficient > 0),
        assert(minScale.isFinite),
-       assert(maxScale != null),
        assert(maxScale > 0),
        assert(!maxScale.isNaN),
        assert(maxScale >= minScale),
-       assert(panEnabled != null),
-       assert(scaleEnabled != null),
        // boundaryMargin must be either fully infinite or fully finite, but not
        // a mix of both.
        assert(
@@ -143,19 +136,14 @@
     this.scaleFactor = 200.0,
     this.transformationController,
     this.alignment,
+    this.trackpadScrollCausesScale = false,
     required InteractiveViewerWidgetBuilder this.builder,
-  }) : assert(panAxis != null),
-       assert(builder != null),
-       assert(minScale != null),
-       assert(minScale > 0),
+  }) : assert(minScale > 0),
        assert(interactionEndFrictionCoefficient > 0),
        assert(minScale.isFinite),
-       assert(maxScale != null),
        assert(maxScale > 0),
        assert(!maxScale.isNaN),
        assert(maxScale >= minScale),
-       assert(panEnabled != null),
-       assert(scaleEnabled != null),
        // boundaryMargin must be either fully infinite or fully finite, but not
        // a mix of both.
        assert(
@@ -295,10 +283,12 @@
   ///   * [panEnabled], which is similar but for panning.
   final bool scaleEnabled;
 
+  /// {@macro flutter.gestures.scale.trackpadScrollCausesScale}
+  final bool trackpadScrollCausesScale;
+
   /// Determines the amount of scale to be performed per pointer scroll.
   ///
-  /// Defaults to 200.0, which was arbitrarily chosen to feel natural for most
-  /// trackpads and mousewheels on all supported platforms.
+  /// Defaults to [kDefaultMouseScrollToScaleFactor].
   ///
   /// Increasing this value above the default causes scaling to feel slower,
   /// while decreasing it causes scaling to feel faster.
@@ -556,7 +546,10 @@
   final GlobalKey _childKey = GlobalKey();
   final GlobalKey _parentKey = GlobalKey();
   Animation<Offset>? _animation;
+  Animation<double>? _scaleAnimation;
+  late Offset _scaleAnimationFocalPoint;
   late AnimationController _controller;
+  late AnimationController _scaleController;
   Axis? _currentAxis; // Used with panAxis.
   Offset? _referenceFocalPoint; // Point where the current gesture began.
   double? _scaleStart; // Scale value at start of scaling gesture.
@@ -795,6 +788,12 @@
       _animation?.removeListener(_onAnimate);
       _animation = null;
     }
+    if (_scaleController.isAnimating) {
+      _scaleController.stop();
+      _scaleController.reset();
+      _scaleAnimation?.removeListener(_onScaleAnimate);
+      _scaleAnimation = null;
+    }
 
     _gestureType = null;
     _currentAxis = null;
@@ -809,6 +808,7 @@
   // handled with GestureDetector's scale gesture.
   void _onScaleUpdate(ScaleUpdateDetails details) {
     final double scale = _transformationController!.value.getMaxScaleOnAxis();
+    _scaleAnimationFocalPoint = details.localFocalPoint;
     final Offset focalPointScene = _transformationController!.toScene(
       details.localFocalPoint,
     );
@@ -913,52 +913,122 @@
     _referenceFocalPoint = null;
 
     _animation?.removeListener(_onAnimate);
+    _scaleAnimation?.removeListener(_onScaleAnimate);
     _controller.reset();
+    _scaleController.reset();
 
     if (!_gestureIsSupported(_gestureType)) {
       _currentAxis = null;
       return;
     }
 
-    // If the scale ended with enough velocity, animate inertial movement.
-    if (_gestureType != _GestureType.pan || details.velocity.pixelsPerSecond.distance < kMinFlingVelocity) {
-      _currentAxis = null;
-      return;
+    if (_gestureType == _GestureType.pan) {
+      if (details.velocity.pixelsPerSecond.distance < kMinFlingVelocity) {
+        _currentAxis = null;
+        return;
+      }
+      final Vector3 translationVector = _transformationController!.value.getTranslation();
+      final Offset translation = Offset(translationVector.x, translationVector.y);
+      final FrictionSimulation frictionSimulationX = FrictionSimulation(
+        widget.interactionEndFrictionCoefficient,
+        translation.dx,
+        details.velocity.pixelsPerSecond.dx,
+      );
+      final FrictionSimulation frictionSimulationY = FrictionSimulation(
+        widget.interactionEndFrictionCoefficient,
+        translation.dy,
+        details.velocity.pixelsPerSecond.dy,
+      );
+      final double tFinal = _getFinalTime(
+        details.velocity.pixelsPerSecond.distance,
+        widget.interactionEndFrictionCoefficient,
+      );
+      _animation = Tween<Offset>(
+        begin: translation,
+        end: Offset(frictionSimulationX.finalX, frictionSimulationY.finalX),
+      ).animate(CurvedAnimation(
+        parent: _controller,
+        curve: Curves.decelerate,
+      ));
+      _controller.duration = Duration(milliseconds: (tFinal * 1000).round());
+      _animation!.addListener(_onAnimate);
+      _controller.forward();
+    } else if (_gestureType == _GestureType.scale) {
+      if (details.scaleVelocity.abs() < 0.1) {
+        _currentAxis = null;
+        return;
+      }
+      final double scale = _transformationController!.value.getMaxScaleOnAxis();
+      final FrictionSimulation frictionSimulation = FrictionSimulation(
+        widget.interactionEndFrictionCoefficient * widget.scaleFactor,
+        scale,
+        details.scaleVelocity / 10
+      );
+      final double tFinal = _getFinalTime(details.scaleVelocity.abs(), widget.interactionEndFrictionCoefficient, effectivelyMotionless: 0.1);
+      _scaleAnimation = Tween<double>(
+        begin: scale,
+        end: frictionSimulation.x(tFinal)
+      ).animate(CurvedAnimation(
+        parent: _scaleController,
+        curve: Curves.decelerate
+      ));
+      _scaleController.duration = Duration(milliseconds: (tFinal * 1000).round());
+      _scaleAnimation!.addListener(_onScaleAnimate);
+      _scaleController.forward();
     }
-
-    final Vector3 translationVector = _transformationController!.value.getTranslation();
-    final Offset translation = Offset(translationVector.x, translationVector.y);
-    final FrictionSimulation frictionSimulationX = FrictionSimulation(
-      widget.interactionEndFrictionCoefficient,
-      translation.dx,
-      details.velocity.pixelsPerSecond.dx,
-    );
-    final FrictionSimulation frictionSimulationY = FrictionSimulation(
-      widget.interactionEndFrictionCoefficient,
-      translation.dy,
-      details.velocity.pixelsPerSecond.dy,
-    );
-    final double tFinal = _getFinalTime(
-      details.velocity.pixelsPerSecond.distance,
-      widget.interactionEndFrictionCoefficient,
-    );
-    _animation = Tween<Offset>(
-      begin: translation,
-      end: Offset(frictionSimulationX.finalX, frictionSimulationY.finalX),
-    ).animate(CurvedAnimation(
-      parent: _controller,
-      curve: Curves.decelerate,
-    ));
-    _controller.duration = Duration(milliseconds: (tFinal * 1000).round());
-    _animation!.addListener(_onAnimate);
-    _controller.forward();
   }
 
-  // Handle mousewheel scroll events.
+  // Handle mousewheel and web trackpad scroll events.
   void _receivedPointerSignal(PointerSignalEvent event) {
     final double scaleChange;
     if (event is PointerScrollEvent) {
-      // Ignore left and right scroll.
+      if (event.kind == PointerDeviceKind.trackpad) {
+        // Trackpad scroll, so treat it as a pan.
+        widget.onInteractionStart?.call(
+          ScaleStartDetails(
+            focalPoint: event.position,
+            localFocalPoint: event.localPosition,
+          ),
+        );
+
+        final Offset localDelta = PointerEvent.transformDeltaViaPositions(
+          untransformedEndPosition: event.position + event.scrollDelta,
+          untransformedDelta: event.scrollDelta,
+          transform: event.transform,
+        );
+
+        if (!_gestureIsSupported(_GestureType.pan)) {
+          widget.onInteractionUpdate?.call(ScaleUpdateDetails(
+            focalPoint: event.position - event.scrollDelta,
+            localFocalPoint: event.localPosition - event.scrollDelta,
+            focalPointDelta: -localDelta,
+          ));
+          widget.onInteractionEnd?.call(ScaleEndDetails());
+          return;
+        }
+
+        final Offset focalPointScene = _transformationController!.toScene(
+          event.localPosition,
+        );
+
+        final Offset newFocalPointScene = _transformationController!.toScene(
+          event.localPosition - localDelta,
+        );
+
+        _transformationController!.value = _matrixTranslate(
+          _transformationController!.value,
+          newFocalPointScene - focalPointScene
+        );
+
+        widget.onInteractionUpdate?.call(ScaleUpdateDetails(
+          focalPoint: event.position - event.scrollDelta,
+          localFocalPoint: event.localPosition - localDelta,
+          focalPointDelta: -localDelta
+        ));
+        widget.onInteractionEnd?.call(ScaleEndDetails());
+        return;
+      }
+      // Ignore left and right mouse wheel scroll.
       if (event.scrollDelta.dy == 0.0) {
         return;
       }
@@ -1039,6 +1109,38 @@
     );
   }
 
+  // Handle inertia scale animation.
+  void _onScaleAnimate() {
+    if (!_scaleController.isAnimating) {
+      _currentAxis = null;
+      _scaleAnimation?.removeListener(_onScaleAnimate);
+      _scaleAnimation = null;
+      _scaleController.reset();
+      return;
+    }
+    final double desiredScale = _scaleAnimation!.value;
+    final double scaleChange = desiredScale / _transformationController!.value.getMaxScaleOnAxis();
+    final Offset referenceFocalPoint = _transformationController!.toScene(
+      _scaleAnimationFocalPoint,
+    );
+    _transformationController!.value = _matrixScale(
+      _transformationController!.value,
+      scaleChange,
+    );
+
+    // While scaling, translate such that the user's two fingers stay on
+    // the same places in the scene. That means that the focal point of
+    // the scale should be on the same place in the scene before and after
+    // the scale.
+    final Offset focalPointSceneScaled = _transformationController!.toScene(
+      _scaleAnimationFocalPoint,
+    );
+    _transformationController!.value = _matrixTranslate(
+      _transformationController!.value,
+      focalPointSceneScaled - referenceFocalPoint,
+    );
+  }
+
   void _onTransformationControllerChange() {
     // A change to the TransformationController's value is a change to the
     // state.
@@ -1055,6 +1157,9 @@
     _controller = AnimationController(
       vsync: this,
     );
+    _scaleController = AnimationController(
+      vsync: this
+    );
   }
 
   @override
@@ -1085,6 +1190,7 @@
   @override
   void dispose() {
     _controller.dispose();
+    _scaleController.dispose();
     _transformationController!.removeListener(_onTransformationControllerChange);
     if (widget.transformationController == null) {
       _transformationController!.dispose();
@@ -1135,13 +1241,15 @@
         onScaleEnd: _onScaleEnd,
         onScaleStart: _onScaleStart,
         onScaleUpdate: _onScaleUpdate,
+        trackpadScrollCausesScale: widget.trackpadScrollCausesScale,
+        trackpadScrollToScaleFactor: Offset(0, -1/widget.scaleFactor),
         child: child,
       ),
     );
   }
 }
 
-// This widget simply allows us to easily swap in and out the LayoutBuilder in
+// This widget allows us to easily swap in and out the LayoutBuilder in
 // InteractiveViewer's depending on if it's using a builder or a child.
 class _InteractiveViewerBuilt extends StatelessWidget {
   const _InteractiveViewerBuilt({
@@ -1259,8 +1367,7 @@
 
 // Given a velocity and drag, calculate the time at which motion will come to
 // a stop, within the margin of effectivelyMotionless.
-double _getFinalTime(double velocity, double drag) {
-  const double effectivelyMotionless = 10.0;
+double _getFinalTime(double velocity, double drag, {double effectivelyMotionless = 10}) {
   return math.log(effectivelyMotionless / velocity) / math.log(drag / 100);
 }
 
diff --git a/framework/lib/src/widgets/keyboard_listener.dart b/framework/lib/src/widgets/keyboard_listener.dart
index d17b959..fcca974 100644
--- a/framework/lib/src/widgets/keyboard_listener.dart
+++ b/framework/lib/src/widgets/keyboard_listener.dart
@@ -50,10 +50,7 @@
     this.includeSemantics = true,
     this.onKeyEvent,
     required this.child,
-  }) : assert(focusNode != null),
-       assert(autofocus != null),
-       assert(includeSemantics != null),
-       assert(child != null);
+  });
 
   /// Controls whether this widget has keyboard focus.
   final FocusNode focusNode;
diff --git a/framework/lib/src/widgets/layout_builder.dart b/framework/lib/src/widgets/layout_builder.dart
index aac041e..be1a907 100644
--- a/framework/lib/src/widgets/layout_builder.dart
+++ b/framework/lib/src/widgets/layout_builder.dart
@@ -40,7 +40,7 @@
   const ConstrainedLayoutBuilder({
     super.key,
     required this.builder,
-  }) : assert(builder != null);
+  });
 
   @override
   RenderObjectElement createElement() => _LayoutBuilderElement<ConstraintType>(this);
@@ -267,7 +267,7 @@
   const LayoutBuilder({
     super.key,
     required super.builder,
-  }) : assert(builder != null);
+  });
 
   @override
   RenderObject createRenderObject(BuildContext context) => _RenderLayoutBuilder();
diff --git a/framework/lib/src/widgets/list_wheel_scroll_view.dart b/framework/lib/src/widgets/list_wheel_scroll_view.dart
index bc0f6c9..18464d5 100644
--- a/framework/lib/src/widgets/list_wheel_scroll_view.dart
+++ b/framework/lib/src/widgets/list_wheel_scroll_view.dart
@@ -81,7 +81,7 @@
 /// conditions.
 class ListWheelChildListDelegate extends ListWheelChildDelegate {
   /// Constructs the delegate from a concrete list of children.
-  ListWheelChildListDelegate({required this.children}) : assert(children != null);
+  ListWheelChildListDelegate({required this.children});
 
   /// The list containing all children that can be supplied.
   final List<Widget> children;
@@ -125,7 +125,7 @@
 /// conditions.
 class ListWheelChildLoopingListDelegate extends ListWheelChildDelegate {
   /// Constructs the delegate from a concrete list of children.
-  ListWheelChildLoopingListDelegate({required this.children}) : assert(children != null);
+  ListWheelChildLoopingListDelegate({required this.children});
 
   /// The list containing all children that can be supplied.
   final List<Widget> children;
@@ -162,7 +162,7 @@
   ListWheelChildBuilderDelegate({
     required this.builder,
     this.childCount,
-  }) : assert(builder != null);
+  });
 
   /// Called lazily to build children.
   final NullableIndexedWidgetBuilder builder;
@@ -218,7 +218,7 @@
   /// [initialItem] defaults to 0 and must not be null.
   FixedExtentScrollController({
     this.initialItem = 0,
-  }) : assert(initialItem != null);
+  });
 
   /// The page to show when first creating the scroll view.
   ///
@@ -316,6 +316,7 @@
     required super.viewportDimension,
     required super.axisDirection,
     required this.itemIndex,
+    required super.devicePixelRatio,
   });
 
   @override
@@ -326,6 +327,7 @@
     double? viewportDimension,
     AxisDirection? axisDirection,
     int? itemIndex,
+    double? devicePixelRatio,
   }) {
     return FixedExtentMetrics(
       minScrollExtent: minScrollExtent ?? (hasContentDimensions ? this.minScrollExtent : null),
@@ -334,6 +336,7 @@
       viewportDimension: viewportDimension ?? (hasViewportDimension ? this.viewportDimension : null),
       axisDirection: axisDirection ?? this.axisDirection,
       itemIndex: itemIndex ?? this.itemIndex,
+      devicePixelRatio: devicePixelRatio ?? this.devicePixelRatio,
     );
   }
 
@@ -399,6 +402,7 @@
     double? viewportDimension,
     AxisDirection? axisDirection,
     int? itemIndex,
+    double? devicePixelRatio,
   }) {
     return FixedExtentMetrics(
       minScrollExtent: minScrollExtent ?? (hasContentDimensions ? this.minScrollExtent : null),
@@ -407,6 +411,7 @@
       viewportDimension: viewportDimension ?? (hasViewportDimension ? this.viewportDimension : null),
       axisDirection: axisDirection ?? this.axisDirection,
       itemIndex: itemIndex ?? this.itemIndex,
+      devicePixelRatio: devicePixelRatio ?? this.devicePixelRatio,
     );
   }
 }
@@ -505,8 +510,8 @@
     // Scenario 3:
     // If there's no velocity and we're already at where we intend to land,
     // do nothing.
-    if (velocity.abs() < tolerance.velocity
-        && (settlingPixels - metrics.pixels).abs() < tolerance.distance) {
+    if (velocity.abs() < toleranceFor(position).velocity
+        && (settlingPixels - metrics.pixels).abs() < toleranceFor(position).distance) {
       return null;
     }
 
@@ -519,7 +524,7 @@
         metrics.pixels,
         settlingPixels,
         velocity,
-        tolerance: tolerance,
+        tolerance: toleranceFor(position),
       );
     }
 
@@ -530,7 +535,7 @@
       metrics.pixels,
       settlingPixels,
       velocity,
-      tolerance.velocity * velocity.sign,
+      toleranceFor(position).velocity * velocity.sign,
     );
   }
 }
@@ -569,21 +574,13 @@
     this.restorationId,
     this.scrollBehavior,
     required List<Widget> children,
-  }) : assert(children != null),
-       assert(diameterRatio != null),
-       assert(diameterRatio > 0.0, RenderListWheelViewport.diameterRatioZeroMessage),
-       assert(perspective != null),
+  }) : assert(diameterRatio > 0.0, RenderListWheelViewport.diameterRatioZeroMessage),
        assert(perspective > 0),
        assert(perspective <= 0.01, RenderListWheelViewport.perspectiveTooHighMessage),
        assert(magnification > 0),
-       assert(overAndUnderCenterOpacity != null),
        assert(overAndUnderCenterOpacity >= 0 && overAndUnderCenterOpacity <= 1),
-       assert(itemExtent != null),
        assert(itemExtent > 0),
-       assert(squeeze != null),
        assert(squeeze > 0),
-       assert(renderChildrenOutsideViewport != null),
-       assert(clipBehavior != null),
        assert(
          !renderChildrenOutsideViewport || clipBehavior == Clip.none,
          RenderListWheelViewport.clipBehaviorAndRenderChildrenOutsideViewportConflict,
@@ -610,21 +607,13 @@
     this.restorationId,
     this.scrollBehavior,
     required this.childDelegate,
-  }) : assert(childDelegate != null),
-       assert(diameterRatio != null),
-       assert(diameterRatio > 0.0, RenderListWheelViewport.diameterRatioZeroMessage),
-       assert(perspective != null),
+  }) : assert(diameterRatio > 0.0, RenderListWheelViewport.diameterRatioZeroMessage),
        assert(perspective > 0),
        assert(perspective <= 0.01, RenderListWheelViewport.perspectiveTooHighMessage),
        assert(magnification > 0),
-       assert(overAndUnderCenterOpacity != null),
        assert(overAndUnderCenterOpacity >= 0 && overAndUnderCenterOpacity <= 1),
-       assert(itemExtent != null),
        assert(itemExtent > 0),
-       assert(squeeze != null),
        assert(squeeze > 0),
-       assert(renderChildrenOutsideViewport != null),
-       assert(clipBehavior != null),
        assert(
          !renderChildrenOutsideViewport || clipBehavior == Clip.none,
          RenderListWheelViewport.clipBehaviorAndRenderChildrenOutsideViewportConflict,
@@ -982,21 +971,12 @@
     required this.offset,
     required this.childDelegate,
     this.clipBehavior = Clip.hardEdge,
-  }) : assert(childDelegate != null),
-       assert(offset != null),
-       assert(diameterRatio != null),
-       assert(diameterRatio > 0, RenderListWheelViewport.diameterRatioZeroMessage),
-       assert(perspective != null),
+  }) : assert(diameterRatio > 0, RenderListWheelViewport.diameterRatioZeroMessage),
        assert(perspective > 0),
        assert(perspective <= 0.01, RenderListWheelViewport.perspectiveTooHighMessage),
-       assert(overAndUnderCenterOpacity != null),
        assert(overAndUnderCenterOpacity >= 0 && overAndUnderCenterOpacity <= 1),
-       assert(itemExtent != null),
        assert(itemExtent > 0),
-       assert(squeeze != null),
        assert(squeeze > 0),
-       assert(renderChildrenOutsideViewport != null),
-       assert(clipBehavior != null),
        assert(
          !renderChildrenOutsideViewport || clipBehavior == Clip.none,
          RenderListWheelViewport.clipBehaviorAndRenderChildrenOutsideViewportConflict,
diff --git a/framework/lib/src/widgets/localizations.dart b/framework/lib/src/widgets/localizations.dart
index 8e5bdba..77db0fa 100644
--- a/framework/lib/src/widgets/localizations.dart
+++ b/framework/lib/src/widgets/localizations.dart
@@ -235,8 +235,7 @@
     required this.localizationsState,
     required this.typeToResources,
     required super.child,
-  }) : assert(localizationsState != null),
-       assert(typeToResources != null);
+  });
 
   final Locale locale;
   final _LocalizationsState localizationsState;
@@ -319,8 +318,8 @@
 /// }
 /// ```
 ///
-/// Each delegate can be viewed as a factory for objects that encapsulate a
-/// a set of localized resources. These objects are retrieved with
+/// Each delegate can be viewed as a factory for objects that encapsulate a set
+/// of localized resources. These objects are retrieved with
 /// by runtime type with [Localizations.of].
 ///
 /// The [WidgetsApp] class creates a [Localizations] widget so most apps
@@ -366,9 +365,7 @@
     required this.locale,
     required this.delegates,
     this.child,
-  }) : assert(locale != null),
-       assert(delegates != null),
-       assert(delegates.any((LocalizationsDelegate<dynamic> delegate) => delegate is LocalizationsDelegate<WidgetsLocalizations>));
+  }) : assert(delegates.any((LocalizationsDelegate<dynamic> delegate) => delegate is LocalizationsDelegate<WidgetsLocalizations>));
 
   /// Overrides the inherited [Locale] or [LocalizationsDelegate]s for `child`.
   ///
@@ -434,7 +431,6 @@
   /// If no [Localizations] widget is in scope then the [Localizations.localeOf]
   /// method will throw an exception.
   static Locale localeOf(BuildContext context) {
-    assert(context != null);
     final _LocalizationsScope? scope = context.dependOnInheritedWidgetOfExactType<_LocalizationsScope>();
     assert(() {
       if (scope == null) {
@@ -460,7 +456,6 @@
   /// If no [Localizations] widget is in scope then this function will return
   /// null.
   static Locale? maybeLocaleOf(BuildContext context) {
-    assert(context != null);
     final _LocalizationsScope? scope = context.dependOnInheritedWidgetOfExactType<_LocalizationsScope>();
     return scope?.localizationsState.locale;
   }
@@ -468,7 +463,6 @@
   // There doesn't appear to be a need to make this public. See the
   // Localizations.override factory constructor.
   static List<LocalizationsDelegate<dynamic>> _delegatesOf(BuildContext context) {
-    assert(context != null);
     final _LocalizationsScope? scope = context.dependOnInheritedWidgetOfExactType<_LocalizationsScope>();
     assert(scope != null, 'a Localizations ancestor was not found');
     return List<LocalizationsDelegate<dynamic>>.of(scope!.localizationsState.widget.delegates);
@@ -490,8 +484,6 @@
   /// }
   /// ```
   static T? of<T>(BuildContext context, Type type) {
-    assert(context != null);
-    assert(type != null);
     final _LocalizationsScope? scope = context.dependOnInheritedWidgetOfExactType<_LocalizationsScope>();
     return scope?.localizationsState.resourcesFor<T?>(type);
   }
@@ -539,17 +531,14 @@
   @override
   void didUpdateWidget(Localizations old) {
     super.didUpdateWidget(old);
-    if (widget.locale != old.locale
-        || (widget.delegates == null)
-        || (widget.delegates != null && old.delegates == null)
-        || (widget.delegates != null && _anyDelegatesShouldReload(old))) {
+    if (widget.locale != old.locale || (_anyDelegatesShouldReload(old))) {
       load(widget.locale);
     }
   }
 
   void load(Locale locale) {
     final Iterable<LocalizationsDelegate<dynamic>> delegates = widget.delegates;
-    if (delegates == null || delegates.isEmpty) {
+    if (delegates.isEmpty) {
       _locale = locale;
       return;
     }
@@ -583,14 +572,12 @@
   }
 
   T resourcesFor<T>(Type type) {
-    assert(type != null);
     final T resources = _typeToResources[type] as T;
     return resources;
   }
 
   TextDirection get _textDirection {
     final WidgetsLocalizations resources = _typeToResources[WidgetsLocalizations] as WidgetsLocalizations;
-    assert(resources != null);
     return resources.textDirection;
   }
 
diff --git a/framework/lib/src/widgets/lookup_boundary.dart b/framework/lib/src/widgets/lookup_boundary.dart
new file mode 100644
index 0000000..40ebaea
--- /dev/null
+++ b/framework/lib/src/widgets/lookup_boundary.dart
@@ -0,0 +1,325 @@
+// 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 'framework.dart';
+
+// Examples can assume:
+// class MyWidget extends StatelessWidget { const MyWidget({super.key, required this.child}); final Widget child; @override Widget build(BuildContext context) => child; }
+
+/// A lookup boundary controls what entities are visible to descendants of the
+/// boundary via the static lookup methods provided by the boundary.
+///
+/// The static lookup methods of the boundary mirror the lookup methods by the
+/// same name exposed on [BuildContext] and they can be used as direct
+/// replacements. Unlike the methods on [BuildContext], these methods do not
+/// find any ancestor entities of the closest [LookupBoundary] surrounding the
+/// provided [BuildContext]. The root of the tree is an implicit lookup boundary.
+///
+/// {@tool snippet}
+/// In the example below, the [LookupBoundary.findAncestorWidgetOfExactType]
+/// call returns null because the [LookupBoundary] "hides" `MyWidget` from the
+/// [BuildContext] that was queried.
+///
+/// ```dart
+/// MyWidget(
+///   child: LookupBoundary(
+///     child: Builder(
+///       builder: (BuildContext context) {
+///         MyWidget? widget = LookupBoundary.findAncestorWidgetOfExactType<MyWidget>(context);
+///         return Text('$widget'); // "null"
+///       },
+///     ),
+///   ),
+/// )
+/// ```
+/// {@end-tool}
+///
+/// A [LookupBoundary] only affects the behavior of the static lookup methods
+/// defined on the boundary. It does not affect the behavior of the lookup
+/// methods defined on [BuildContext].
+///
+/// A [LookupBoundary] is rarely instantiated directly. They are inserted at
+/// locations of the widget tree where the render tree diverges from the element
+/// tree, which is rather uncommon. Such anomalies are created by
+/// [RenderObjectElement]s that don't attach their [RenderObject] to the closest
+/// ancestor [RenderObjectElement], e.g. because they bootstrap a separate
+/// stand-alone render tree.
+// TODO(goderbauer): Reference the View widget here once available.
+/// This behavior breaks the assumption some widgets have about the structure of
+/// the render tree: These widgets may try to reach out to an ancestor widget,
+/// assuming that their associated [RenderObject]s are also ancestors, which due
+/// to the anomaly may not be the case. At the point where the divergence in the
+/// two trees is introduced, a [LookupBoundary] can be used to hide that ancestor
+/// from the querying widget.
+///
+/// As an example, [Material.of] relies on lookup boundaries to hide the
+/// [Material] widget from certain descendant button widget. Buttons reach out
+/// to their [Material] ancestor to draw ink splashes on its associated render
+/// object. This only produces the desired effect if the button render object
+/// is a descendant of the [Material] render object. If the element tree and
+/// the render tree are not in sync due to anomalies described above, this may
+/// not be the case. To avoid incorrect visuals, the [Material] relies on
+/// lookup boundaries to hide itself from descendants in subtrees with such
+/// anomalies. Those subtrees are expected to introduce their own [Material]
+/// widget that buttons there can utilize without crossing a lookup boundary.
+class LookupBoundary extends InheritedWidget {
+  /// Creates a [LookupBoundary].
+  ///
+  /// A none-null [child] widget must be provided.
+  const LookupBoundary({super.key, required super.child});
+
+  /// Obtains the nearest widget of the given type `T` within the current
+  /// [LookupBoundary] of `context`, which must be the type of a concrete
+  /// [InheritedWidget] subclass, and registers the provided build `context`
+  /// with that widget such that when that widget changes (or a new widget of
+  /// that type is introduced, or the widget goes away), the build context is
+  /// rebuilt so that it can obtain new values from that widget.
+  ///
+  /// This method behaves exactly like
+  /// [BuildContext.dependOnInheritedWidgetOfExactType], except it only
+  /// considers [InheritedWidget]s of the specified type `T` between the
+  /// provided [BuildContext] and its closest [LookupBoundary] ancestor.
+  /// [InheritedWidget]s past that [LookupBoundary] are invisible to this
+  /// method. The root of the tree is treated as an implicit lookup boundary.
+  ///
+  /// {@macro flutter.widgets.BuildContext.dependOnInheritedWidgetOfExactType}
+  static T? dependOnInheritedWidgetOfExactType<T extends InheritedWidget>(BuildContext context, { Object? aspect }) {
+    // The following call makes sure that context depends on something so
+    // Element.didChangeDependencies is called when context moves in the tree
+    // even when requested dependency remains unfulfilled (i.e. null is
+    // returned).
+    context.dependOnInheritedWidgetOfExactType<LookupBoundary>();
+    final InheritedElement? candidate = getElementForInheritedWidgetOfExactType<T>(context);
+    if (candidate == null) {
+      return null;
+    }
+    context.dependOnInheritedElement(candidate, aspect: aspect);
+    return candidate.widget as T;
+  }
+
+  /// Obtains the element corresponding to the nearest widget of the given type
+  /// `T` within the current [LookupBoundary] of `context`.
+  ///
+  /// `T` must be the type of a concrete [InheritedWidget] subclass. Returns
+  /// null if no such element is found.
+  ///
+  /// This method behaves exactly like
+  /// [BuildContext.getElementForInheritedWidgetOfExactType], except it only
+  /// considers [InheritedWidget]s of the specified type `T` between the
+  /// provided [BuildContext] and its closest [LookupBoundary] ancestor.
+  /// [InheritedWidget]s past that [LookupBoundary] are invisible to this
+  /// method. The root of the tree is treated as an implicit lookup boundary.
+  ///
+  /// {@macro flutter.widgets.BuildContext.getElementForInheritedWidgetOfExactType}
+  static InheritedElement? getElementForInheritedWidgetOfExactType<T extends InheritedWidget>(BuildContext context) {
+    final InheritedElement? candidate = context.getElementForInheritedWidgetOfExactType<T>();
+    if (candidate == null) {
+      return null;
+    }
+    final Element? boundary = context.getElementForInheritedWidgetOfExactType<LookupBoundary>();
+    if (boundary != null && boundary.depth > candidate.depth) {
+      return null;
+    }
+    return candidate;
+  }
+
+  /// Returns the nearest ancestor widget of the given type `T` within the
+  /// current [LookupBoundary] of `context`.
+  ///
+  /// `T` must be the type of a concrete [Widget] subclass.
+  ///
+  /// This method behaves exactly like
+  /// [BuildContext.findAncestorWidgetOfExactType], except it only considers
+  /// [Widget]s of the specified type `T` between the provided [BuildContext]
+  /// and its closest [LookupBoundary] ancestor. [Widget]s past that
+  /// [LookupBoundary] are invisible to this method. The root of the tree is
+  /// treated as an implicit lookup boundary.
+  ///
+  /// {@macro flutter.widgets.BuildContext.findAncestorWidgetOfExactType}
+  static T? findAncestorWidgetOfExactType<T extends Widget>(BuildContext context) {
+    Element? target;
+    context.visitAncestorElements((Element ancestor) {
+      if (ancestor.widget.runtimeType == T) {
+        target = ancestor;
+        return false;
+      }
+      return ancestor.widget.runtimeType != LookupBoundary;
+    });
+    return target?.widget as T?;
+  }
+
+  /// Returns the [State] object of the nearest ancestor [StatefulWidget] widget
+  /// within the current [LookupBoundary] of `context` that is an instance of
+  /// the given type `T`.
+  ///
+  /// This method behaves exactly like
+  /// [BuildContext.findAncestorWidgetOfExactType], except it only considers
+  /// [State] objects of the specified type `T` between the provided
+  /// [BuildContext] and its closest [LookupBoundary] ancestor. [State] objects
+  /// past that [LookupBoundary] are invisible to this method. The root of the
+  /// tree is treated as an implicit lookup boundary.
+  ///
+  /// {@macro flutter.widgets.BuildContext.findAncestorStateOfType}
+  static T? findAncestorStateOfType<T extends State>(BuildContext context) {
+    StatefulElement? target;
+    context.visitAncestorElements((Element ancestor) {
+      if (ancestor is StatefulElement && ancestor.state is T) {
+        target = ancestor;
+        return false;
+      }
+      return ancestor.widget.runtimeType != LookupBoundary;
+    });
+    return target?.state as T?;
+  }
+
+  /// Returns the [State] object of the furthest ancestor [StatefulWidget]
+  /// widget within the current [LookupBoundary] of `context` that is an
+  /// instance of the given type `T`.
+  ///
+  /// This method behaves exactly like
+  /// [BuildContext.findRootAncestorStateOfType], except it considers the
+  /// closest [LookupBoundary] ancestor of `context` to be the root. [State]
+  /// objects past that [LookupBoundary] are invisible to this method. The root
+  /// of the tree is treated as an implicit lookup boundary.
+  ///
+  /// {@macro flutter.widgets.BuildContext.findRootAncestorStateOfType}
+  static T? findRootAncestorStateOfType<T extends State>(BuildContext context) {
+    StatefulElement? target;
+    context.visitAncestorElements((Element ancestor) {
+      if (ancestor is StatefulElement && ancestor.state is T) {
+        target = ancestor;
+      }
+      return ancestor.widget.runtimeType != LookupBoundary;
+    });
+    return target?.state as T?;
+  }
+
+  /// Returns the [RenderObject] object of the nearest ancestor
+  /// [RenderObjectWidget] widget within the current [LookupBoundary] of
+  /// `context` that is an instance of the given type `T`.
+  ///
+  /// This method behaves exactly like
+  /// [BuildContext.findAncestorRenderObjectOfType], except it only considers
+  /// [RenderObject]s of the specified type `T` between the provided
+  /// [BuildContext] and its closest [LookupBoundary] ancestor. [RenderObject]s
+  /// past that [LookupBoundary] are invisible to this method. The root of the
+  /// tree is treated as an implicit lookup boundary.
+  ///
+  /// {@macro flutter.widgets.BuildContext.findAncestorRenderObjectOfType}
+  static T? findAncestorRenderObjectOfType<T extends RenderObject>(BuildContext context) {
+    Element? target;
+    context.visitAncestorElements((Element ancestor) {
+      if (ancestor is RenderObjectElement && ancestor.renderObject is T) {
+        target = ancestor;
+        return false;
+      }
+      return ancestor.widget.runtimeType != LookupBoundary;
+    });
+    return target?.renderObject as T?;
+  }
+
+  /// Walks the ancestor chain, starting with the parent of the build context's
+  /// widget, invoking the argument for each ancestor until a [LookupBoundary]
+  /// or the root is reached.
+  ///
+  /// This method behaves exactly like [BuildContext.visitAncestorElements],
+  /// except it only walks the tree up to the closest [LookupBoundary] ancestor
+  /// of the provided context. The root of the tree is treated as an implicit
+  /// lookup boundary.
+  ///
+  /// {@macro flutter.widgets.BuildContext.visitAncestorElements}
+  static void visitAncestorElements(BuildContext context, ConditionalElementVisitor visitor) {
+    context.visitAncestorElements((Element ancestor) {
+      return visitor(ancestor) && ancestor.widget.runtimeType != LookupBoundary;
+    });
+  }
+
+  /// Walks the non-[LookupBoundary] child [Element]s of the provided
+  /// `context`.
+  ///
+  /// This method behaves exactly like [BuildContext.visitChildElements],
+  /// except it only visits children that are not a [LookupBoundary].
+  ///
+  /// {@macro flutter.widgets.BuildContext.visitChildElements}
+  static void visitChildElements(BuildContext context, ElementVisitor visitor) {
+    context.visitChildElements((Element child) {
+      if (child.widget.runtimeType != LookupBoundary) {
+        visitor(child);
+      }
+    });
+  }
+
+  /// Returns true if a [LookupBoundary] is hiding the nearest
+  /// [Widget] of the specified type `T` from the provided [BuildContext].
+  ///
+  /// This method throws when asserts are disabled.
+  static bool debugIsHidingAncestorWidgetOfExactType<T extends Widget>(BuildContext context) {
+    bool? result;
+    assert(() {
+      bool hiddenByBoundary = false;
+      bool ancestorFound = false;
+      context.visitAncestorElements((Element ancestor) {
+        if (ancestor.widget.runtimeType == T) {
+          ancestorFound = true;
+          return false;
+        }
+        hiddenByBoundary = hiddenByBoundary || ancestor.widget.runtimeType == LookupBoundary;
+        return true;
+      });
+      result = ancestorFound & hiddenByBoundary;
+      return true;
+    } ());
+    return result!;
+  }
+
+  /// Returns true if a [LookupBoundary] is hiding the nearest [StatefulWidget]
+  /// with a [State] of the specified type `T` from the provided [BuildContext].
+  ///
+  /// This method throws when asserts are disabled.
+  static bool debugIsHidingAncestorStateOfType<T extends State>(BuildContext context) {
+    bool? result;
+    assert(() {
+      bool hiddenByBoundary = false;
+      bool ancestorFound = false;
+      context.visitAncestorElements((Element ancestor) {
+        if (ancestor is StatefulElement && ancestor.state is T) {
+          ancestorFound = true;
+          return false;
+        }
+        hiddenByBoundary = hiddenByBoundary || ancestor.widget.runtimeType == LookupBoundary;
+        return true;
+      });
+      result = ancestorFound & hiddenByBoundary;
+      return true;
+    } ());
+    return result!;
+  }
+
+  /// Returns true if a [LookupBoundary] is hiding the nearest
+  /// [RenderObjectWidget] with a [RenderObject] of the specified type `T`
+  /// from the provided [BuildContext].
+  ///
+  /// This method throws when asserts are disabled.
+  static bool debugIsHidingAncestorRenderObjectOfType<T extends RenderObject>(BuildContext context) {
+    bool? result;
+    assert(() {
+      bool hiddenByBoundary = false;
+      bool ancestorFound = false;
+      context.visitAncestorElements((Element ancestor) {
+        if (ancestor is RenderObjectElement && ancestor.renderObject is T) {
+          ancestorFound = true;
+          return false;
+        }
+        hiddenByBoundary = hiddenByBoundary || ancestor.widget.runtimeType == LookupBoundary;
+        return true;
+      });
+      result = ancestorFound & hiddenByBoundary;
+      return true;
+    } ());
+    return result!;
+  }
+
+  @override
+  bool updateShouldNotify(covariant InheritedWidget oldWidget) => false;
+}
diff --git a/framework/lib/src/widgets/media_query.dart b/framework/lib/src/widgets/media_query.dart
index 3a9adfd..7757bc9 100644
--- a/framework/lib/src/widgets/media_query.dart
+++ b/framework/lib/src/widgets/media_query.dart
@@ -12,6 +12,7 @@
 import 'binding.dart';
 import 'debug.dart';
 import 'framework.dart';
+import 'inherited_model.dart';
 
 // Examples can assume:
 // late BuildContext context;
@@ -25,6 +26,52 @@
   landscape
 }
 
+/// Specifies a part of MediaQueryData to depend on.
+///
+/// [MediaQuery] contains a large number of related properties. Widgets frequently
+/// depend on only a few of these attributes. For example, a widget that needs to
+/// rebuild when the [MediaQueryData.textScaleFactor] changes does not need to
+/// be notified when the [MediaQueryData.size] changes. Specifying an aspect avoids
+/// unnecessary rebuilds.
+enum _MediaQueryAspect {
+  /// Specifies the aspect corresponding to [MediaQueryData.size].
+  size,
+  /// Specifies the aspect corresponding to [MediaQueryData.orientation].
+  orientation,
+  /// Specifies the aspect corresponding to [MediaQueryData.devicePixelRatio].
+  devicePixelRatio,
+  /// Specifies the aspect corresponding to [MediaQueryData.textScaleFactor].
+  textScaleFactor,
+  /// Specifies the aspect corresponding to [MediaQueryData.platformBrightness].
+  platformBrightness,
+  /// Specifies the aspect corresponding to [MediaQueryData.padding].
+  padding,
+  /// Specifies the aspect corresponding to [MediaQueryData.viewInsets].
+  viewInsets,
+  /// Specifies the aspect corresponding to [MediaQueryData.systemGestureInsets].
+  systemGestureInsets,
+  /// Specifies the aspect corresponding to [MediaQueryData.viewPadding].
+  viewPadding,
+  /// Specifies the aspect corresponding to [MediaQueryData.alwaysUse24HourFormat].
+  alwaysUse24HourFormat,
+  /// Specifies the aspect corresponding to [MediaQueryData.accessibleNavigation].
+  accessibleNavigation,
+  /// Specifies the aspect corresponding to [MediaQueryData.invertColors].
+  invertColors,
+  /// Specifies the aspect corresponding to [MediaQueryData.highContrast].
+  highContrast,
+  /// Specifies the aspect corresponding to [MediaQueryData.disableAnimations].
+  disableAnimations,
+  /// Specifies the aspect corresponding to [MediaQueryData.boldText].
+  boldText,
+  /// Specifies the aspect corresponding to [MediaQueryData.navigationMode].
+  navigationMode,
+  /// Specifies the aspect corresponding to [MediaQueryData.gestureSettings].
+  gestureSettings,
+  /// Specifies the aspect corresponding to [MediaQueryData.displayFeatures].
+  displayFeatures,
+}
+
 /// Information about a piece of media (e.g., a window).
 ///
 /// For example, the [MediaQueryData.size] property contains the width and
@@ -91,8 +138,8 @@
 class MediaQueryData {
   /// Creates data for a media query with explicit values.
   ///
-  /// Consider using [MediaQueryData.fromWindow] to create data based on a
-  /// [dart:ui.PlatformDispatcher].
+  /// Consider using [MediaQueryData.fromView] to create data based on a
+  /// [dart:ui.FlutterView].
   const MediaQueryData({
     this.size = Size.zero,
     this.devicePixelRatio = 1.0,
@@ -111,49 +158,72 @@
     this.navigationMode = NavigationMode.traditional,
     this.gestureSettings = const DeviceGestureSettings(touchSlop: kTouchSlop),
     this.displayFeatures = const <ui.DisplayFeature>[],
-  }) : assert(size != null),
-       assert(devicePixelRatio != null),
-       assert(textScaleFactor != null),
-       assert(platformBrightness != null),
-       assert(padding != null),
-       assert(viewInsets != null),
-       assert(systemGestureInsets != null),
-       assert(viewPadding != null),
-       assert(alwaysUse24HourFormat != null),
-       assert(accessibleNavigation != null),
-       assert(invertColors != null),
-       assert(highContrast != null),
-       assert(disableAnimations != null),
-       assert(boldText != null),
-       assert(navigationMode != null),
-       assert(gestureSettings != null),
-       assert(displayFeatures != null);
+  });
 
-  /// Creates data for a media query based on the given window.
+  /// Deprecated. Use [MediaQueryData.fromView] instead.
   ///
-  /// If you use this, you should ensure that you also register for
-  /// notifications so that you can update your [MediaQueryData] when the
-  /// window's metrics change. For example, see
-  /// [WidgetsBindingObserver.didChangeMetrics] or
-  /// [dart:ui.PlatformDispatcher.onMetricsChanged].
-  MediaQueryData.fromWindow(ui.FlutterView window)
-    : size = window.physicalSize / window.devicePixelRatio,
-      devicePixelRatio = window.devicePixelRatio,
-      textScaleFactor = window.platformDispatcher.textScaleFactor,
-      platformBrightness = window.platformDispatcher.platformBrightness,
-      padding = EdgeInsets.fromWindowPadding(window.padding, window.devicePixelRatio),
-      viewPadding = EdgeInsets.fromWindowPadding(window.viewPadding, window.devicePixelRatio),
-      viewInsets = EdgeInsets.fromWindowPadding(window.viewInsets, window.devicePixelRatio),
-      systemGestureInsets = EdgeInsets.fromWindowPadding(window.systemGestureInsets, window.devicePixelRatio),
-      accessibleNavigation = window.platformDispatcher.accessibilityFeatures.accessibleNavigation,
-      invertColors = window.platformDispatcher.accessibilityFeatures.invertColors,
-      disableAnimations = window.platformDispatcher.accessibilityFeatures.disableAnimations,
-      boldText = window.platformDispatcher.accessibilityFeatures.boldText,
-      highContrast = window.platformDispatcher.accessibilityFeatures.highContrast,
-      alwaysUse24HourFormat = window.platformDispatcher.alwaysUse24HourFormat,
-      navigationMode = NavigationMode.traditional,
-      gestureSettings = DeviceGestureSettings.fromWindow(window),
-      displayFeatures = window.displayFeatures;
+  /// This constructor was operating on a single window assumption. In
+  /// preparation for Flutter's upcoming multi-window support, it has been
+  /// deprecated.
+  @Deprecated(
+    'Use MediaQueryData.fromView instead. '
+    'This constructor was deprecated in preparation for the upcoming multi-window support. '
+    'This feature was deprecated after v3.7.0-32.0.pre.'
+  )
+  factory MediaQueryData.fromWindow(ui.FlutterView window) => MediaQueryData.fromView(window);
+
+  /// Creates data for a [MediaQuery] based on the given `view`.
+  ///
+  /// If provided, the `platformData` is used to fill in the platform-specific
+  /// aspects of the newly created [MediaQueryData]. If `platformData` is null,
+  /// the `view`'s [PlatformDispatcher] is consulted to construct the
+  /// platform-specific data.
+  ///
+  /// Data which is exposed directly on the [FlutterView] is considered
+  /// view-specific. Data which is only exposed via the
+  /// [FlutterView.platformDispatcher] property is considered platform-specific.
+  ///
+  /// Callers of this method should ensure that they also register for
+  /// notifications so that the [MediaQueryData] can be updated when any data
+  /// used to construct it changes. Notifications to consider are:
+  ///
+  ///  * [WidgetsBindingObserver.didChangeMetrics] or
+  ///    [dart:ui.PlatformDispatcher.onMetricsChanged],
+  ///  * [WidgetsBindingObserver.didChangeAccessibilityFeatures] or
+  ///    [dart:ui.PlatformDispatcher.onAccessibilityFeaturesChanged],
+  ///  * [WidgetsBindingObserver.didChangeTextScaleFactor] or
+  ///    [dart:ui.PlatformDispatcher.onTextScaleFactorChanged],
+  ///  * [WidgetsBindingObserver.didChangePlatformBrightness] or
+  ///    [dart:ui.PlatformDispatcher.onPlatformBrightnessChanged].
+  ///
+  /// The last three notifications are only relevant if no `platformData` is
+  /// provided. If `platformData` is provided, callers should ensure to call
+  /// this method again when it changes to keep the constructed [MediaQueryData]
+  /// updated.
+  ///
+  /// See also:
+  ///
+  ///  * [MediaQuery.fromView], which constructs [MediaQueryData] from a provided
+  ///    [FlutterView], makes it available to descendant widgets, and sets up
+  ///    the appropriate notification listeners to keep the data updated.
+  MediaQueryData.fromView(ui.FlutterView view, {MediaQueryData? platformData})
+    : size = view.physicalSize / view.devicePixelRatio,
+      devicePixelRatio = view.devicePixelRatio,
+      textScaleFactor = platformData?.textScaleFactor ?? view.platformDispatcher.textScaleFactor,
+      platformBrightness = platformData?.platformBrightness ?? view.platformDispatcher.platformBrightness,
+      padding = EdgeInsets.fromWindowPadding(view.padding, view.devicePixelRatio),
+      viewPadding = EdgeInsets.fromWindowPadding(view.viewPadding, view.devicePixelRatio),
+      viewInsets = EdgeInsets.fromWindowPadding(view.viewInsets, view.devicePixelRatio),
+      systemGestureInsets = EdgeInsets.fromWindowPadding(view.systemGestureInsets, view.devicePixelRatio),
+      accessibleNavigation = platformData?.accessibleNavigation ?? view.platformDispatcher.accessibilityFeatures.accessibleNavigation,
+      invertColors = platformData?.invertColors ?? view.platformDispatcher.accessibilityFeatures.invertColors,
+      disableAnimations = platformData?.disableAnimations ?? view.platformDispatcher.accessibilityFeatures.disableAnimations,
+      boldText = platformData?.boldText ?? view.platformDispatcher.accessibilityFeatures.boldText,
+      highContrast = platformData?.highContrast ?? view.platformDispatcher.accessibilityFeatures.highContrast,
+      alwaysUse24HourFormat = platformData?.alwaysUse24HourFormat ?? view.platformDispatcher.alwaysUse24HourFormat,
+      navigationMode = platformData?.navigationMode ?? NavigationMode.traditional,
+      gestureSettings = DeviceGestureSettings.fromView(view),
+      displayFeatures = view.displayFeatures;
 
   /// The size of the media in logical pixels (e.g, the size of the screen).
   ///
@@ -178,6 +248,8 @@
   /// See also:
   ///
   ///  * [FlutterView.physicalSize], which returns the size in physical pixels.
+  ///  * [MediaQuery.sizeOf], a method to find and depend on the size defined
+  ///    for a [BuildContext].
   final Size size;
 
   /// The number of device pixels for each logical pixel. This number might not
@@ -192,7 +264,7 @@
   ///
   /// See also:
   ///
-  ///  * [MediaQuery.textScaleFactorOf], a convenience method which returns the
+  ///  * [MediaQuery.textScaleFactorOf], a method to find and depend on the
   ///    textScaleFactor defined for a [BuildContext].
   final double textScaleFactor;
 
@@ -203,6 +275,11 @@
   ///
   /// Not all platforms necessarily support a concept of brightness mode. Those
   /// platforms will report [Brightness.light] in this property.
+  ///
+  /// See also:
+  ///
+  ///  * [MediaQuery.platformBrightnessOf], a method to find and depend on the
+  ///    platformBrightness defined for a [BuildContext].
   final Brightness platformBrightness;
 
   /// The parts of the display that are completely obscured by system UI,
@@ -217,6 +294,8 @@
   /// level MediaQuery created by [WidgetsApp] are the same as the window
   /// (often the mobile device screen) that contains the app.
   ///
+  /// {@youtube 560 315 https://www.youtube.com/watch?v=ceCo8U0XHqw}
+  ///
   /// See also:
   ///
   ///  * [ui.window], which provides some additional detail about this property
@@ -234,6 +313,8 @@
   ///
   /// Padding is derived from the values of [viewInsets] and [viewPadding].
   ///
+  /// {@youtube 560 315 https://www.youtube.com/watch?v=ceCo8U0XHqw}
+  ///
   /// See also:
   ///
   ///  * [ui.window], which provides some additional detail about this
@@ -256,6 +337,8 @@
   /// same as the window that contains the app. On mobile devices, this will
   /// typically be the full screen.
   ///
+  /// {@youtube 560 315 https://www.youtube.com/watch?v=ceCo8U0XHqw}
+  ///
   /// See also:
   ///
   ///  * [ui.window], which provides some additional detail about this
@@ -613,6 +696,7 @@
         && other.padding == padding
         && other.viewPadding == viewPadding
         && other.viewInsets == viewInsets
+        && other.systemGestureInsets == systemGestureInsets
         && other.alwaysUse24HourFormat == alwaysUse24HourFormat
         && other.highContrast == highContrast
         && other.disableAnimations == disableAnimations
@@ -654,6 +738,7 @@
       'padding: $padding',
       'viewPadding: $viewPadding',
       'viewInsets: $viewInsets',
+      'systemGestureInsets: $systemGestureInsets',
       'alwaysUse24HourFormat: $alwaysUse24HourFormat',
       'accessibleNavigation: $accessibleNavigation',
       'highContrast: $highContrast',
@@ -690,7 +775,7 @@
 ///  * [WidgetsApp] and [MaterialApp], which introduce a [MediaQuery] and keep
 ///    it up to date with the current screen metrics as they change.
 ///  * [MediaQueryData], the data structure that represents the metrics.
-class MediaQuery extends InheritedWidget {
+class MediaQuery extends InheritedModel<_MediaQueryAspect> {
   /// Creates a widget that provides [MediaQueryData] to its descendants.
   ///
   /// The [data] and [child] arguments must not be null.
@@ -698,8 +783,7 @@
     super.key,
     required this.data,
     required super.child,
-  }) : assert(child != null),
-       assert(data != null);
+  });
 
   /// Creates a new [MediaQuery] that inherits from the ambient [MediaQuery]
   /// from the given context, but removes the specified padding.
@@ -837,24 +921,57 @@
     );
   }
 
-  /// Provides a [MediaQuery] which is built and updated using the latest
-  /// [WidgetsBinding.window] values.
+  /// Deprecated. Use [MediaQuery.fromView] instead.
   ///
-  /// The [MediaQuery] is wrapped in a separate widget to ensure that only it
-  /// and its dependents are updated when `window` changes, instead of
-  /// rebuilding the whole widget tree.
+  /// This constructor was operating on a single window assumption. In
+  /// preparation for Flutter's upcoming multi-window support, it has been
+  /// deprecated.
   ///
-  /// This should be inserted into the widget tree when the [MediaQuery] view
-  /// padding is consumed by a widget in such a way that the view padding is no
-  /// longer exposed to the widget's descendants or siblings.
-  ///
-  /// The [child] argument is required and must not be null.
+  /// Replaced by [MediaQuery.fromView], which requires specifying the
+  /// [FlutterView] the [MediaQuery] is constructed for. The [FlutterView] can,
+  /// for example, be obtained from the context via [View.of] or from
+  /// [PlatformDispatcher.views].
+  @Deprecated(
+    'Use MediaQuery.fromView instead. '
+    'This constructor was deprecated in preparation for the upcoming multi-window support. '
+    'This feature was deprecated after v3.7.0-32.0.pre.'
+  )
   static Widget fromWindow({
     Key? key,
     required Widget child,
   }) {
-    return _MediaQueryFromWindow(
+    return _MediaQueryFromView(
       key: key,
+      view: WidgetsBinding.instance.window,
+      ignoreParentData: true,
+      child: child,
+    );
+  }
+
+  /// Wraps the [child] in a [MediaQuery] which is built using data from the
+  /// provided [view].
+  ///
+  /// The [MediaQuery] is constructed using the platform-specific data of the
+  /// surrounding [MediaQuery] and the view-specific data of the provided
+  /// [view]. If no surrounding [MediaQuery] exists, the platform-specific data
+  /// is generated from the [PlatformDispatcher] associated with the provided
+  /// [view]. Any information that's exposed via the [PlatformDispatcher] is
+  /// considered platform-specific. Data exposed directly on the [FlutterView]
+  /// (excluding its [FlutterView.platformDispatcher] property) is considered
+  /// view-specific.
+  ///
+  /// The injected [MediaQuery] automatically updates when any of the data used
+  /// to construct it changes.
+  ///
+  /// The [view] and [child] arguments are required and must not be null.
+  static Widget fromView({
+    Key? key,
+    required FlutterView view,
+    required Widget child,
+  }) {
+    return _MediaQueryFromView(
+      key: key,
+      view: view,
       child: child,
     );
   }
@@ -873,6 +990,11 @@
   /// examples). When that information changes, your widget will be scheduled to
   /// be rebuilt, keeping your widget up-to-date.
   ///
+  /// If the widget only requires a subset of properties of the [MediaQueryData]
+  /// object, it is preferred to use the specific methods (for example:
+  /// [MediaQuery.sizeOf] and [MediaQuery.paddingOf]), as those methods will not
+  /// cause a widget to rebuild when unrelated properties are updated.
+  ///
   /// Typical usage is as follows:
   ///
   /// ```dart
@@ -888,9 +1010,12 @@
   ///  * [maybeOf], which doesn't throw or assert if it doesn't find a
   ///    [MediaQuery] ancestor, it returns null instead.
   static MediaQueryData of(BuildContext context) {
-    assert(context != null);
+    return _of(context);
+  }
+
+  static MediaQueryData _of(BuildContext context, [_MediaQueryAspect? aspect]) {
     assert(debugCheckHasMediaQuery(context));
-    return context.dependOnInheritedWidgetOfExactType<MediaQuery>()!.data;
+    return InheritedModel.inheritFrom<MediaQuery>(context, aspect: aspect)!.data;
   }
 
   /// The data from the closest instance of this class that encloses the given
@@ -907,6 +1032,11 @@
   /// examples). When that information changes, your widget will be scheduled to
   /// be rebuilt, keeping your widget up-to-date.
   ///
+  /// If the widget only requires a subset of properties of the [MediaQueryData]
+  /// object, it is preferred to use the specific methods (for example:
+  /// [MediaQuery.maybeSizeOf] and [MediaQuery.maybePaddingOf]), as those methods
+  /// will not cause a widget to rebuild when unrelated properties are updated.
+  ///
   /// Typical usage is as follows:
   ///
   /// ```dart
@@ -921,24 +1051,182 @@
   ///  * [of], which will throw if it doesn't find a [MediaQuery] ancestor,
   ///    instead of returning null.
   static MediaQueryData? maybeOf(BuildContext context) {
-    assert(context != null);
-    return context.dependOnInheritedWidgetOfExactType<MediaQuery>()?.data;
+    return _maybeOf(context);
   }
 
-  /// Returns textScaleFactor for the nearest MediaQuery ancestor or 1.0, if
-  /// no such ancestor exists.
-  static double textScaleFactorOf(BuildContext context) {
-    return MediaQuery.maybeOf(context)?.textScaleFactor ?? 1.0;
+  static MediaQueryData? _maybeOf(BuildContext context, [_MediaQueryAspect? aspect]) {
+    return InheritedModel.inheritFrom<MediaQuery>(context, aspect: aspect)?.data;
   }
 
+  /// Returns size for the nearest MediaQuery ancestor or
+  /// throws an exception, if no such ancestor exists.
+  ///
+  /// Use of this method will cause the given [context] to rebuild any time that
+  /// the [MediaQueryData.size] property of the ancestor [MediaQuery] changes.
+  static Size sizeOf(BuildContext context) => _of(context, _MediaQueryAspect.size).size;
+
+  /// Returns size for the nearest MediaQuery ancestor or
+  /// null, if no such ancestor exists.
+  ///
+  /// Use of this method will cause the given [context] to rebuild any time that
+  /// the [MediaQueryData.size] property of the ancestor [MediaQuery] changes.
+  static Size? maybeSizeOf(BuildContext context) => _maybeOf(context, _MediaQueryAspect.size)?.size;
+
+  /// Returns orientation for the nearest MediaQuery ancestor or
+  /// throws an exception, if no such ancestor exists.
+  ///
+  /// Use of this method will cause the given [context] to rebuild any time that
+  /// the [MediaQueryData.orientation] property of the ancestor [MediaQuery] changes.
+  static Orientation orientationOf(BuildContext context) => _of(context, _MediaQueryAspect.orientation).orientation;
+
+  /// Returns orientation for the nearest MediaQuery ancestor or
+  /// null, if no such ancestor exists.
+  ///
+  /// Use of this method will cause the given [context] to rebuild any time that
+  /// the [MediaQueryData.orientation] property of the ancestor [MediaQuery] changes.
+  static Orientation? maybeOrientationOf(BuildContext context) => _maybeOf(context, _MediaQueryAspect.orientation)?.orientation;
+
+  /// Returns devicePixelRatio for the nearest MediaQuery ancestor or
+  /// throws an exception, if no such ancestor exists.
+  ///
+  /// Use of this method will cause the given [context] to rebuild any time that
+  /// the [MediaQueryData.devicePixelRatio] property of the ancestor [MediaQuery] changes.
+  static double devicePixelRatioOf(BuildContext context) => _of(context, _MediaQueryAspect.devicePixelRatio).devicePixelRatio;
+
+  /// Returns devicePixelRatio for the nearest MediaQuery ancestor or
+  /// null, if no such ancestor exists.
+  ///
+  /// Use of this method will cause the given [context] to rebuild any time that
+  /// the [MediaQueryData.devicePixelRatio] property of the ancestor [MediaQuery] changes.
+  static double? maybeDevicePixelRatioOf(BuildContext context) => _maybeOf(context, _MediaQueryAspect.devicePixelRatio)?.devicePixelRatio;
+
+  /// Returns textScaleFactor for the nearest MediaQuery ancestor or
+  /// 1.0, if no such ancestor exists.
+  ///
+  /// Use of this method will cause the given [context] to rebuild any time that
+  /// the [MediaQueryData.textScaleFactor] property of the ancestor [MediaQuery] changes.
+  static double textScaleFactorOf(BuildContext context) => maybeTextScaleFactorOf(context) ?? 1.0;
+
+  /// Returns textScaleFactor for the nearest MediaQuery ancestor or
+  /// null, if no such ancestor exists.
+  ///
+  /// Use of this method will cause the given [context] to rebuild any time that
+  /// the [MediaQueryData.textScaleFactor] property of the ancestor [MediaQuery] changes.
+  static double? maybeTextScaleFactorOf(BuildContext context) => _maybeOf(context, _MediaQueryAspect.textScaleFactor)?.textScaleFactor;
+
   /// Returns platformBrightness for the nearest MediaQuery ancestor or
   /// [Brightness.light], if no such ancestor exists.
   ///
   /// Use of this method will cause the given [context] to rebuild any time that
-  /// any property of the ancestor [MediaQuery] changes.
-  static Brightness platformBrightnessOf(BuildContext context) {
-    return MediaQuery.maybeOf(context)?.platformBrightness ?? Brightness.light;
-  }
+  /// the [MediaQueryData.platformBrightness] property of the ancestor
+  /// [MediaQuery] changes.
+  static Brightness platformBrightnessOf(BuildContext context) => maybePlatformBrightnessOf(context) ?? Brightness.light;
+
+  /// Returns platformBrightness for the nearest MediaQuery ancestor or
+  /// null, if no such ancestor exists.
+  ///
+  /// Use of this method will cause the given [context] to rebuild any time that
+  /// the [MediaQueryData.platformBrightness] property of the ancestor
+  /// [MediaQuery] changes.
+  static Brightness? maybePlatformBrightnessOf(BuildContext context) => _maybeOf(context, _MediaQueryAspect.platformBrightness)?.platformBrightness;
+
+  /// Returns padding for the nearest MediaQuery ancestor or
+  /// throws an exception, if no such ancestor exists.
+  ///
+  /// Use of this method will cause the given [context] to rebuild any time that
+  /// the [MediaQueryData.padding] property of the ancestor [MediaQuery] changes.
+  static EdgeInsets paddingOf(BuildContext context) => _of(context, _MediaQueryAspect.padding).padding;
+
+  /// Returns viewInsets for the nearest MediaQuery ancestor or
+  /// null, if no such ancestor exists.
+  ///
+  /// Use of this method will cause the given [context] to rebuild any time that
+  /// the [MediaQueryData.viewInsets] property of the ancestor [MediaQuery] changes.
+  static EdgeInsets? maybePaddingOf(BuildContext context) => _maybeOf(context, _MediaQueryAspect.padding)?.padding;
+
+  /// Returns viewInsets for the nearest MediaQuery ancestor or
+  /// throws an exception, if no such ancestor exists.
+  ///
+  /// Use of this method will cause the given [context] to rebuild any time that
+  /// the [MediaQueryData.viewInsets] property of the ancestor [MediaQuery] changes.
+  static EdgeInsets viewInsetsOf(BuildContext context) => _of(context, _MediaQueryAspect.viewInsets).viewInsets;
+
+  /// Returns viewInsets for the nearest MediaQuery ancestor or
+  /// null, if no such ancestor exists.
+  ///
+  /// Use of this method will cause the given [context] to rebuild any time that
+  /// the [MediaQueryData.viewInsets] property of the ancestor [MediaQuery] changes.
+  static EdgeInsets? maybeViewInsetsOf(BuildContext context) => _maybeOf(context, _MediaQueryAspect.viewInsets)?.viewInsets;
+
+  /// Returns systemGestureInsets for the nearest MediaQuery ancestor or
+  /// throws an exception, if no such ancestor exists.
+  ///
+  /// Use of this method will cause the given [context] to rebuild any time that
+  /// the [MediaQueryData.systemGestureInsets] property of the ancestor [MediaQuery] changes.
+  static EdgeInsets systemGestureInsetsOf(BuildContext context) => _of(context, _MediaQueryAspect.systemGestureInsets).systemGestureInsets;
+
+  /// Returns systemGestureInsets for the nearest MediaQuery ancestor or
+  /// null, if no such ancestor exists.
+  ///
+  /// Use of this method will cause the given [context] to rebuild any time that
+  /// the [MediaQueryData.systemGestureInsets] property of the ancestor [MediaQuery] changes.
+  static EdgeInsets? maybeSystemGestureInsetsOf(BuildContext context) => _maybeOf(context, _MediaQueryAspect.systemGestureInsets)?.systemGestureInsets;
+
+  /// Returns viewPadding for the nearest MediaQuery ancestor or
+  /// throws an exception, if no such ancestor exists.
+  ///
+  /// Use of this method will cause the given [context] to rebuild any time that
+  /// the [MediaQueryData.viewPadding] property of the ancestor [MediaQuery] changes.
+  static EdgeInsets viewPaddingOf(BuildContext context) => _of(context, _MediaQueryAspect.viewPadding).viewPadding;
+
+  /// Returns viewPadding for the nearest MediaQuery ancestor or
+  /// null, if no such ancestor exists.
+  ///
+  /// Use of this method will cause the given [context] to rebuild any time that
+  /// the [MediaQueryData.viewPadding] property of the ancestor [MediaQuery] changes.
+  static EdgeInsets? maybeViewPaddingOf(BuildContext context) => _maybeOf(context, _MediaQueryAspect.viewPadding)?.viewPadding;
+
+  /// Returns alwaysUse for the nearest MediaQuery ancestor or
+  /// throws an exception, if no such ancestor exists.
+  ///
+  /// Use of this method will cause the given [context] to rebuild any time that
+  /// the [MediaQueryData.devicePixelRatio] property of the ancestor [MediaQuery] changes.
+  static bool alwaysUse24HourFormatOf(BuildContext context) => _of(context, _MediaQueryAspect.alwaysUse24HourFormat).alwaysUse24HourFormat;
+
+  /// Returns alwaysUse24HourFormat for the nearest MediaQuery ancestor or
+  /// null, if no such ancestor exists.
+  ///
+  /// Use of this method will cause the given [context] to rebuild any time that
+  /// the [MediaQueryData.alwaysUse24HourFormat] property of the ancestor [MediaQuery] changes.
+  static bool? maybeAlwaysUse24HourFormatOf(BuildContext context) => _maybeOf(context, _MediaQueryAspect.alwaysUse24HourFormat)?.alwaysUse24HourFormat;
+
+  /// Returns accessibleNavigationOf for the nearest MediaQuery ancestor or
+  /// throws an exception, if no such ancestor exists.
+  ///
+  /// Use of this method will cause the given [context] to rebuild any time that
+  /// the [MediaQueryData.accessibleNavigation] property of the ancestor [MediaQuery] changes.
+  static bool accessibleNavigationOf(BuildContext context) => _of(context, _MediaQueryAspect.accessibleNavigation).accessibleNavigation;
+
+  /// Returns accessibleNavigation for the nearest MediaQuery ancestor or
+  /// null, if no such ancestor exists.
+  ///
+  /// Use of this method will cause the given [context] to rebuild any time that
+  /// the [MediaQueryData.accessibleNavigation] property of the ancestor [MediaQuery] changes.
+  static bool? maybeAccessibleNavigationOf(BuildContext context) => _maybeOf(context, _MediaQueryAspect.accessibleNavigation)?.accessibleNavigation;
+
+  /// Returns invertColorsOf for the nearest MediaQuery ancestor or
+  /// throws an exception, if no such ancestor exists.
+  ///
+  /// Use of this method will cause the given [context] to rebuild any time that
+  /// the [MediaQueryData.invertColors] property of the ancestor [MediaQuery] changes.
+  static bool invertColorsOf(BuildContext context) => _of(context, _MediaQueryAspect.invertColors).invertColors;
+
+  /// Returns invertColors for the nearest MediaQuery ancestor or
+  /// null, if no such ancestor exists.
+  ///
+  /// Use of this method will cause the given [context] to rebuild any time that
+  /// the [MediaQueryData.invertColors] property of the ancestor [MediaQuery] changes.
+  static bool? maybeInvertColorsOf(BuildContext context) => _maybeOf(context, _MediaQueryAspect.invertColors)?.invertColors;
 
   /// Returns highContrast for the nearest MediaQuery ancestor or false, if no
   /// such ancestor exists.
@@ -947,15 +1235,102 @@
   ///
   ///  * [MediaQueryData.highContrast], which indicates the platform's
   ///    desire to increase contrast.
-  static bool highContrastOf(BuildContext context) {
-    return MediaQuery.maybeOf(context)?.highContrast ?? false;
-  }
+  ///
+  /// Use of this method will cause the given [context] to rebuild any time that
+  /// the [MediaQueryData.highContrast] property of the ancestor [MediaQuery] changes.
+  static bool highContrastOf(BuildContext context) => maybeHighContrastOf(context) ?? false;
+
+  /// Returns highContrast for the nearest MediaQuery ancestor or
+  /// null, if no such ancestor exists.
+  ///
+  /// Use of this method will cause the given [context] to rebuild any time that
+  /// the [MediaQueryData.highContrast] property of the ancestor [MediaQuery] changes.
+  static bool? maybeHighContrastOf(BuildContext context) => _maybeOf(context, _MediaQueryAspect.highContrast)?.highContrast;
+
+  /// Returns disableAnimations for the nearest MediaQuery ancestor or
+  /// [Brightness.light], if no such ancestor exists.
+  ///
+  /// Use of this method will cause the given [context] to rebuild any time that
+  /// the [MediaQueryData.disableAnimations] property of the ancestor
+  /// [MediaQuery] changes.
+  static bool disableAnimationsOf(BuildContext context) => _of(context, _MediaQueryAspect.disableAnimations).disableAnimations;
+
+  /// Returns disableAnimations for the nearest MediaQuery ancestor or
+  /// null, if no such ancestor exists.
+  ///
+  /// Use of this method will cause the given [context] to rebuild any time that
+  /// the [MediaQueryData.disableAnimations] property of the ancestor [MediaQuery] changes.
+  static bool? maybeDisableAnimationsOf(BuildContext context) => _maybeOf(context, _MediaQueryAspect.disableAnimations)?.disableAnimations;
+
 
   /// Returns the boldText accessibility setting for the nearest MediaQuery
-  /// ancestor, or false if no such ancestor exists.
-  static bool boldTextOverride(BuildContext context) {
-    return MediaQuery.maybeOf(context)?.boldText ?? false;
-  }
+  /// ancestor or false, if no such ancestor exists.
+  ///
+  /// Use of this method will cause the given [context] to rebuild any time that
+  /// the [MediaQueryData.boldText] property of the ancestor [MediaQuery] changes.
+  static bool boldTextOf(BuildContext context) => maybeBoldTextOf(context) ?? false;
+
+  /// Returns the boldText accessibility setting for the nearest MediaQuery
+  /// ancestor or false, if no such ancestor exists.
+  ///
+  /// Use of this method will cause the given [context] to rebuild any time that
+  /// the [MediaQueryData.boldText] property of the ancestor [MediaQuery] changes.
+  ///
+  /// Deprecated in favor of [boldTextOf].
+  @Deprecated(
+    'Migrate to boldTextOf. '
+    'This feature was deprecated after v3.5.0-9.0.pre.'
+  )
+  static bool boldTextOverride(BuildContext context) => boldTextOf(context);
+
+  /// Returns the boldText accessibility setting for the nearest MediaQuery
+  /// ancestor or null, if no such ancestor exists.
+  ///
+  /// Use of this method will cause the given [context] to rebuild any time that
+  /// the [MediaQueryData.boldText] property of the ancestor [MediaQuery] changes.
+  static bool? maybeBoldTextOf(BuildContext context) => _maybeOf(context, _MediaQueryAspect.boldText)?.boldText;
+
+  /// Returns navigationMode for the nearest MediaQuery ancestor or
+  /// throws an exception, if no such ancestor exists.
+  ///
+  /// Use of this method will cause the given [context] to rebuild any time that
+  /// the [MediaQueryData.navigationMode] property of the ancestor [MediaQuery] changes.
+  static NavigationMode navigationModeOf(BuildContext context) => _of(context, _MediaQueryAspect.navigationMode).navigationMode;
+
+  /// Returns navigationMode for the nearest MediaQuery ancestor or
+  /// null, if no such ancestor exists.
+  ///
+  /// Use of this method will cause the given [context] to rebuild any time that
+  /// the [MediaQueryData.navigationMode] property of the ancestor [MediaQuery] changes.
+  static NavigationMode? maybeNavigationModeOf(BuildContext context) => _maybeOf(context, _MediaQueryAspect.navigationMode)?.navigationMode;
+
+  /// Returns gestureSettings for the nearest MediaQuery ancestor or
+  /// throws an exception, if no such ancestor exists.
+  ///
+  /// Use of this method will cause the given [context] to rebuild any time that
+  /// the [MediaQueryData.gestureSettings] property of the ancestor [MediaQuery] changes.
+  static DeviceGestureSettings gestureSettingsOf(BuildContext context) => _of(context, _MediaQueryAspect.gestureSettings).gestureSettings;
+
+  /// Returns gestureSettings for the nearest MediaQuery ancestor or
+  /// null, if no such ancestor exists.
+  ///
+  /// Use of this method will cause the given [context] to rebuild any time that
+  /// the [MediaQueryData.gestureSettings] property of the ancestor [MediaQuery] changes.
+  static DeviceGestureSettings? maybeGestureSettingsOf(BuildContext context) => _maybeOf(context, _MediaQueryAspect.gestureSettings)?.gestureSettings;
+
+  /// Returns displayFeatures for the nearest MediaQuery ancestor or
+  /// throws an exception, if no such ancestor exists.
+  ///
+  /// Use of this method will cause the given [context] to rebuild any time that
+  /// the [MediaQueryData.displayFeatures] property of the ancestor [MediaQuery] changes.
+  static List<ui.DisplayFeature> displayFeaturesOf(BuildContext context) => _of(context, _MediaQueryAspect.displayFeatures).displayFeatures;
+
+  /// Returns displayFeatures for the nearest MediaQuery ancestor or
+  /// null, if no such ancestor exists.
+  ///
+  /// Use of this method will cause the given [context] to rebuild any time that
+  /// the [MediaQueryData.displayFeatures] property of the ancestor [MediaQuery] changes.
+  static List<ui.DisplayFeature>? maybeDisplayFeaturesOf(BuildContext context) => _maybeOf(context, _MediaQueryAspect.displayFeatures)?.displayFeatures;
 
   @override
   bool updateShouldNotify(MediaQuery oldWidget) => data != oldWidget.data;
@@ -965,6 +1340,107 @@
     super.debugFillProperties(properties);
     properties.add(DiagnosticsProperty<MediaQueryData>('data', data, showName: false));
   }
+
+  @override
+  bool updateShouldNotifyDependent(MediaQuery oldWidget, Set<Object> dependencies) {
+    for (final Object dependency in dependencies) {
+      if (dependency is _MediaQueryAspect) {
+        switch (dependency) {
+          case _MediaQueryAspect.size:
+            if (data.size != oldWidget.data.size) {
+              return true;
+            }
+            break;
+          case _MediaQueryAspect.orientation:
+            if (data.orientation != oldWidget.data.orientation) {
+              return true;
+            }
+            break;
+          case _MediaQueryAspect.devicePixelRatio:
+            if (data.devicePixelRatio != oldWidget.data.devicePixelRatio) {
+              return true;
+            }
+            break;
+          case _MediaQueryAspect.textScaleFactor:
+            if (data.textScaleFactor != oldWidget.data.textScaleFactor) {
+              return true;
+            }
+            break;
+          case _MediaQueryAspect.platformBrightness:
+            if (data.platformBrightness != oldWidget.data.platformBrightness) {
+              return true;
+            }
+            break;
+          case _MediaQueryAspect.padding:
+            if (data.padding != oldWidget.data.padding) {
+              return true;
+            }
+            break;
+          case _MediaQueryAspect.viewInsets:
+            if (data.viewInsets != oldWidget.data.viewInsets) {
+              return true;
+            }
+            break;
+          case _MediaQueryAspect.systemGestureInsets:
+            if (data.systemGestureInsets != oldWidget.data.systemGestureInsets) {
+              return true;
+            }
+            break;
+          case _MediaQueryAspect.viewPadding:
+            if (data.viewPadding != oldWidget.data.viewPadding) {
+              return true;
+            }
+            break;
+          case _MediaQueryAspect.alwaysUse24HourFormat:
+            if (data.alwaysUse24HourFormat != oldWidget.data.alwaysUse24HourFormat) {
+              return true;
+            }
+            break;
+          case _MediaQueryAspect.accessibleNavigation:
+            if (data.accessibleNavigation != oldWidget.data.accessibleNavigation) {
+              return true;
+            }
+            break;
+          case _MediaQueryAspect.invertColors:
+            if (data.invertColors != oldWidget.data.invertColors) {
+              return true;
+            }
+            break;
+          case _MediaQueryAspect.highContrast:
+            if (data.highContrast != oldWidget.data.highContrast) {
+              return true;
+            }
+            break;
+          case _MediaQueryAspect.disableAnimations:
+            if (data.disableAnimations != oldWidget.data.disableAnimations) {
+              return true;
+            }
+            break;
+          case _MediaQueryAspect.boldText:
+            if (data.boldText != oldWidget.data.boldText) {
+              return true;
+            }
+            break;
+          case _MediaQueryAspect.navigationMode:
+            if (data.navigationMode != oldWidget.data.navigationMode) {
+              return true;
+            }
+            break;
+          case _MediaQueryAspect.gestureSettings:
+            if (data.gestureSettings != oldWidget.data.gestureSettings) {
+              return true;
+            }
+            break;
+          case _MediaQueryAspect.displayFeatures:
+            if (data.displayFeatures != oldWidget.data.displayFeatures) {
+              return true;
+            }
+            break;
+        }
+      }
+    }
+    return false;
+  }
 }
 
 /// Describes the navigation mode to be set by a [MediaQuery] widget.
@@ -972,7 +1448,7 @@
 /// The different modes indicate the type of navigation to be used in a widget
 /// subtree for those widgets sensitive to it.
 ///
-/// Use `MediaQuery.of(context).navigationMode` to determine the navigation mode
+/// Use `MediaQuery.navigationModeOf(context)` to determine the navigation mode
 /// in effect for the given context. Use a [MediaQuery] widget to set the
 /// navigation mode for its descendant widgets.
 enum NavigationMode {
@@ -995,94 +1471,102 @@
   directional,
 }
 
-/// Provides a [MediaQuery] which is built and updated using the latest
-/// [WidgetsBinding.window] values.
-///
-/// Receives `window` updates by listening to [WidgetsBinding].
-///
-/// The standalone widget ensures that it rebuilds **only** [MediaQuery] and
-/// its dependents when `window` changes, instead of rebuilding the entire
-/// widget tree.
-///
-/// It is used by [WidgetsApp] if no other [MediaQuery] is available above it.
-///
-/// See also:
-///
-///  * [MediaQuery], which establishes a subtree in which media queries resolve
-///    to a [MediaQueryData].
-class _MediaQueryFromWindow extends StatefulWidget {
-  /// Creates a [_MediaQueryFromWindow] that provides a [MediaQuery] to its
-  /// descendants using the `window` to keep [MediaQueryData] up to date.
-  ///
-  /// The [child] must not be null.
-  const _MediaQueryFromWindow({
+class _MediaQueryFromView extends StatefulWidget {
+  const _MediaQueryFromView({
     super.key,
+    required this.view,
+    this.ignoreParentData = false,
     required this.child,
   });
 
-  /// {@macro flutter.widgets.ProxyWidget.child}
+  final FlutterView view;
+  final bool ignoreParentData;
   final Widget child;
 
   @override
-  State<_MediaQueryFromWindow> createState() => _MediaQueryFromWindowState();
+  State<_MediaQueryFromView> createState() => _MediaQueryFromViewState();
 }
 
-class _MediaQueryFromWindowState extends State<_MediaQueryFromWindow> with WidgetsBindingObserver {
+class _MediaQueryFromViewState extends State<_MediaQueryFromView> with WidgetsBindingObserver {
+  MediaQueryData? _parentData;
+  MediaQueryData? _data;
+
   @override
   void initState() {
     super.initState();
     WidgetsBinding.instance.addObserver(this);
   }
 
-  // ACCESSIBILITY
+  @override
+  void didChangeDependencies() {
+    super.didChangeDependencies();
+    _updateParentData();
+    _updateData();
+    assert(_data != null);
+  }
+
+  @override
+  void didUpdateWidget(_MediaQueryFromView oldWidget) {
+    super.didUpdateWidget(oldWidget);
+    if (widget.ignoreParentData != oldWidget.ignoreParentData) {
+      _updateParentData();
+    }
+    if (_data == null || oldWidget.view != widget.view) {
+      _updateData();
+    }
+    assert(_data != null);
+  }
+
+  void _updateParentData() {
+    _parentData = widget.ignoreParentData ? null : MediaQuery.maybeOf(context);
+    _data = null; // _updateData must be called again after changing parent data.
+  }
+
+  void _updateData() {
+    final MediaQueryData newData = MediaQueryData.fromView(widget.view, platformData: _parentData);
+    if (newData != _data) {
+      setState(() {
+        _data = newData;
+      });
+    }
+  }
 
   @override
   void didChangeAccessibilityFeatures() {
-    setState(() {
-      // The properties of window have changed. We use them in our build
-      // function, so we need setState(), but we don't cache anything locally.
-    });
+    // If we have a parent, it dictates our accessibility features. If we don't
+    // have a parent, we get our accessibility features straight from the
+    // PlatformDispatcher and need to update our data in response to the
+    // PlatformDispatcher changing its accessibility features setting.
+    if (_parentData == null) {
+      _updateData();
+    }
   }
 
-  // METRICS
-
   @override
   void didChangeMetrics() {
-    setState(() {
-      // The properties of window have changed. We use them in our build
-      // function, so we need setState(), but we don't cache anything locally.
-    });
+    _updateData();
   }
 
   @override
   void didChangeTextScaleFactor() {
-    setState(() {
-      // The textScaleFactor property of window has changed. We reference
-      // window in our build function, so we need to call setState(), but
-      // we don't need to cache anything locally.
-    });
+    // If we have a parent, it dictates our text scale factor. If we don't have
+    // a parent, we get our text scale factor from the PlatformDispatcher and
+    // need to update our data in response to the PlatformDispatcher changing
+    // its text scale factor setting.
+    if (_parentData == null) {
+      _updateData();
+    }
   }
 
-  // RENDERING
   @override
   void didChangePlatformBrightness() {
-    setState(() {
-      // The platformBrightness property of window has changed. We reference
-      // window in our build function, so we need to call setState(), but
-      // we don't need to cache anything locally.
-    });
-  }
-
-  @override
-  Widget build(BuildContext context) {
-    MediaQueryData data = MediaQueryData.fromWindow(WidgetsBinding.instance.window);
-    if (!kReleaseMode) {
-      data = data.copyWith(platformBrightness: debugBrightnessOverride);
+    // If we have a parent, it dictates our platform brightness. If we don't
+    // have a parent, we get our platform brightness from the PlatformDispatcher
+    // and need to update our data in response to the PlatformDispatcher
+    // changing its platform brightness setting.
+    if (_parentData == null) {
+      _updateData();
     }
-    return MediaQuery(
-      data: data,
-      child: widget.child,
-    );
   }
 
   @override
@@ -1090,4 +1574,18 @@
     WidgetsBinding.instance.removeObserver(this);
     super.dispose();
   }
+
+  @override
+  Widget build(BuildContext context) {
+    MediaQueryData effectiveData = _data!;
+    // If we get our platformBrightness from the PlatformDispatcher (i.e. we have no parentData) replace it
+    // with the debugBrightnessOverride in non-release mode.
+    if (!kReleaseMode && _parentData == null && effectiveData.platformBrightness != debugBrightnessOverride) {
+      effectiveData = effectiveData.copyWith(platformBrightness: debugBrightnessOverride);
+    }
+    return MediaQuery(
+      data: effectiveData,
+      child: widget.child,
+    );
+  }
 }
diff --git a/framework/lib/src/widgets/modal_barrier.dart b/framework/lib/src/widgets/modal_barrier.dart
index 1618a94..9a15b11 100644
--- a/framework/lib/src/widgets/modal_barrier.dart
+++ b/framework/lib/src/widgets/modal_barrier.dart
@@ -14,6 +14,102 @@
 import 'navigator.dart';
 import 'transitions.dart';
 
+/// A widget that modifies the size of the [SemanticsNode.rect] created by its
+/// child widget.
+///
+/// It clips the focus in potentially four directions based on the
+/// specified [EdgeInsets].
+///
+/// The size of the accessibility focus is adjusted based on value changes
+/// inside the given [ValueNotifier].
+///
+/// See also:
+///
+///  * [ModalBarrier], which utilizes this widget to adjust the barrier focus
+/// size based on the size of the content layer rendered on top of it.
+class _SemanticsClipper extends SingleChildRenderObjectWidget{
+  /// creates a [SemanticsClipper] that updates the size of the
+  /// [SemanticsNode.rect] of its child based on the value inside the provided
+  /// [ValueNotifier], or a default value of [EdgeInsets.zero].
+  const _SemanticsClipper({
+    super.child,
+    required this.clipDetailsNotifier,
+  });
+
+  /// The [ValueNotifier] whose value determines how the child's
+  /// [SemanticsNode.rect] should be clipped in four directions.
+  final ValueNotifier<EdgeInsets> clipDetailsNotifier;
+
+  @override
+  _RenderSemanticsClipper createRenderObject(BuildContext context) {
+    return _RenderSemanticsClipper(clipDetailsNotifier: clipDetailsNotifier,);
+  }
+
+  @override
+  void updateRenderObject(BuildContext context, _RenderSemanticsClipper renderObject) {
+    renderObject.clipDetailsNotifier = clipDetailsNotifier;
+  }
+}
+/// Updates the [SemanticsNode.rect] of its child based on the value inside
+/// provided [ValueNotifier].
+class _RenderSemanticsClipper extends RenderProxyBox {
+  /// Creats a [RenderProxyBox] that Updates the [SemanticsNode.rect] of its child
+  /// based on the value inside provided [ValueNotifier].
+  _RenderSemanticsClipper({
+    required ValueNotifier<EdgeInsets> clipDetailsNotifier,
+    RenderBox? child,
+  }) : _clipDetailsNotifier = clipDetailsNotifier,
+      super(child);
+
+  ValueNotifier<EdgeInsets> _clipDetailsNotifier;
+
+  /// The getter and setter retrieves / updates the [ValueNotifier] associated
+  /// with this clipper.
+  ValueNotifier<EdgeInsets> get clipDetailsNotifier => _clipDetailsNotifier;
+  set clipDetailsNotifier (ValueNotifier<EdgeInsets> newNotifier) {
+    if (_clipDetailsNotifier == newNotifier) {
+      return;
+    }
+    if(attached) {
+      _clipDetailsNotifier.removeListener(markNeedsSemanticsUpdate);
+    }
+    _clipDetailsNotifier = newNotifier;
+    _clipDetailsNotifier.addListener(markNeedsSemanticsUpdate);
+    markNeedsSemanticsUpdate();
+  }
+
+  @override
+  Rect get semanticBounds {
+    final EdgeInsets clipDetails = _clipDetailsNotifier.value;
+    final Rect originalRect = super.semanticBounds;
+    final Rect clippedRect = Rect.fromLTRB(
+      originalRect.left + clipDetails.left,
+      originalRect.top + clipDetails.top,
+      originalRect.right - clipDetails.right,
+      originalRect.bottom - clipDetails.bottom,
+    );
+    return clippedRect;
+  }
+
+  @override
+  void attach(PipelineOwner owner) {
+    super.attach(owner);
+    clipDetailsNotifier.addListener(markNeedsSemanticsUpdate);
+  }
+
+  @override
+  void detach() {
+    clipDetailsNotifier.removeListener(markNeedsSemanticsUpdate);
+    super.detach();
+  }
+
+  @override
+  void describeSemanticsConfiguration(SemanticsConfiguration config) {
+    super.describeSemanticsConfiguration(config);
+    config.isSemanticBoundary = true;
+  }
+}
+
 /// A widget that prevents the user from interacting with widgets behind itself.
 ///
 /// The modal barrier is the scrim that is rendered behind each route, which
@@ -37,6 +133,8 @@
     this.onDismiss,
     this.semanticsLabel,
     this.barrierSemanticsDismissible = true,
+    this.clipDetailsNotifier,
+    this.semanticsOnTapHint,
   });
 
   /// If non-null, fill the barrier with this color.
@@ -91,23 +189,36 @@
   ///    [ModalBarrier] built by [ModalRoute] pages.
   final String? semanticsLabel;
 
+  /// {@template flutter.widgets.ModalBarrier.clipDetailsNotifier}
+  /// Contains a value of type [EdgeInsets] that specifies how the
+  /// [SemanticsNode.rect] of the widget should be clipped.
+  ///
+  /// See also:
+  ///
+  ///  * [_SemanticsClipper], which utilizes the value inside to update the
+  /// [SemanticsNode.rect] for its child.
+  /// {@endtemplate}
+  final ValueNotifier<EdgeInsets>? clipDetailsNotifier;
+
+  /// {@macro flutter.material.ModalBottomSheetRoute.barrierOnTapHint}
+  final String? semanticsOnTapHint;
+
   @override
   Widget build(BuildContext context) {
     assert(!dismissible || semanticsLabel == null || debugCheckHasDirectionality(context));
     final bool platformSupportsDismissingBarrier;
     switch (defaultTargetPlatform) {
-      case TargetPlatform.android:
       case TargetPlatform.fuchsia:
       case TargetPlatform.linux:
       case TargetPlatform.windows:
         platformSupportsDismissingBarrier = false;
         break;
+      case TargetPlatform.android:
       case TargetPlatform.iOS:
       case TargetPlatform.macOS:
         platformSupportsDismissingBarrier = true;
         break;
     }
-    assert(platformSupportsDismissingBarrier != null);
     final bool semanticsDismissible = dismissible && platformSupportsDismissingBarrier;
     final bool modalBarrierSemanticsDismissible = barrierSemanticsDismissible ?? semanticsDismissible;
 
@@ -123,27 +234,42 @@
       }
     }
 
+    Widget barrier = Semantics(
+      onTapHint: semanticsOnTapHint,
+      onTap: semanticsDismissible && semanticsLabel != null ? handleDismiss : null,
+      onDismiss: semanticsDismissible && semanticsLabel != null ? handleDismiss : null,
+      label: semanticsDismissible ? semanticsLabel : null,
+      textDirection: semanticsDismissible && semanticsLabel != null ? Directionality.of(context) : null,
+      child: MouseRegion(
+        cursor: SystemMouseCursors.basic,
+        child: ConstrainedBox(
+        constraints: const BoxConstraints.expand(),
+        child: color == null ? null : ColoredBox(
+          color: color!,
+          ),
+        ),
+      ),
+    );
+
+    // Developers can set [dismissible: true] and [barrierSemanticsDismissible: true]
+    // to allow assistive technology users to dismiss a modal BottomSheet by
+    // tapping on the Scrim focus.
+    // On iOS, some modal barriers are not dismissible in accessibility mode.
+    final bool excluding = !semanticsDismissible || !modalBarrierSemanticsDismissible;
+
+    if (!excluding && clipDetailsNotifier != null) {
+      barrier = _SemanticsClipper(
+        clipDetailsNotifier: clipDetailsNotifier!,
+        child: barrier,
+      );
+    }
+
     return BlockSemantics(
       child: ExcludeSemantics(
-        // On Android, the back button is used to dismiss a modal. On iOS, some
-        // modal barriers are not dismissible in accessibility mode.
-        excluding: !semanticsDismissible || !modalBarrierSemanticsDismissible,
+        excluding: excluding,
         child: _ModalBarrierGestureDetector(
           onDismiss: handleDismiss,
-          child: Semantics(
-            label: semanticsDismissible ? semanticsLabel : null,
-            onDismiss: semanticsDismissible ? handleDismiss : null,
-            textDirection: semanticsDismissible && semanticsLabel != null ? Directionality.of(context) : null,
-            child: MouseRegion(
-              cursor: SystemMouseCursors.basic,
-              child: ConstrainedBox(
-                constraints: const BoxConstraints.expand(),
-                child: color == null ? null : ColoredBox(
-                  color: color!,
-                ),
-              ),
-            ),
-          ),
+          child: barrier,
         ),
       ),
     );
@@ -175,6 +301,8 @@
     this.semanticsLabel,
     this.barrierSemanticsDismissible,
     this.onDismiss,
+    this.clipDetailsNotifier,
+    this.semanticsOnTapHint,
   }) : super(listenable: color);
 
   /// If non-null, fill the barrier with this color.
@@ -214,6 +342,19 @@
   /// {@macro flutter.widgets.ModalBarrier.onDismiss}
   final VoidCallback? onDismiss;
 
+  /// {@macro flutter.widgets.ModalBarrier.clipDetailsNotifier}
+  final ValueNotifier<EdgeInsets>? clipDetailsNotifier;
+
+  /// This hint text instructs users what they are able to do when they tap on
+  /// the [ModalBarrier]
+  ///
+  /// E.g. If the hint text is 'close bottom sheet", it will be announced as
+  /// "Double tap to close bottom sheet".
+  ///
+  /// If this value is null, the default onTapHint will be applied, resulting
+  /// in the announcement of 'Double tap to activate'.
+  final String? semanticsOnTapHint;
+
   @override
   Widget build(BuildContext context) {
     return ModalBarrier(
@@ -222,6 +363,8 @@
       semanticsLabel: semanticsLabel,
       barrierSemanticsDismissible: barrierSemanticsDismissible,
       onDismiss: onDismiss,
+      clipDetailsNotifier: clipDetailsNotifier,
+      semanticsOnTapHint: semanticsOnTapHint,
     );
   }
 }
@@ -266,17 +409,6 @@
   String get debugDescription => 'any tap';
 }
 
-class _ModalBarrierSemanticsDelegate extends SemanticsGestureDelegate {
-  const _ModalBarrierSemanticsDelegate({this.onDismiss});
-
-  final VoidCallback? onDismiss;
-
-  @override
-  void assignSemantics(RenderSemanticsGestureHandler renderObject) {
-    renderObject.onTap = onDismiss;
-  }
-}
-
 class _AnyTapGestureRecognizerFactory extends GestureRecognizerFactory<_AnyTapGestureRecognizer> {
   const _AnyTapGestureRecognizerFactory({this.onAnyTapUp});
 
@@ -297,8 +429,7 @@
   const _ModalBarrierGestureDetector({
     required this.child,
     required this.onDismiss,
-  }) : assert(child != null),
-       assert(onDismiss != null);
+  });
 
   /// The widget below this widget in the tree.
   /// See [RawGestureDetector.child].
@@ -317,7 +448,6 @@
     return RawGestureDetector(
       gestures: gestures,
       behavior: HitTestBehavior.opaque,
-      semantics: _ModalBarrierSemanticsDelegate(onDismiss: onDismiss),
       child: child,
     );
   }
diff --git a/framework/lib/src/widgets/navigation_toolbar.dart b/framework/lib/src/widgets/navigation_toolbar.dart
index c67a789..c2024a0 100644
--- a/framework/lib/src/widgets/navigation_toolbar.dart
+++ b/framework/lib/src/widgets/navigation_toolbar.dart
@@ -30,8 +30,7 @@
     this.trailing,
     this.centerMiddle = true,
     this.middleSpacing = kMiddleSpacing,
-  }) : assert(centerMiddle != null),
-       assert(middleSpacing != null);
+  });
 
   /// The default spacing around the [middle] widget in dp.
   static const double kMiddleSpacing = 16.0;
@@ -85,8 +84,7 @@
     required this.centerMiddle,
     required this.middleSpacing,
     required this.textDirection,
-  }) : assert(middleSpacing != null),
-       assert(textDirection != null);
+  });
 
   // If false the middle widget should be start-justified within the space
   // between the leading and trailing widgets.
diff --git a/framework/lib/src/widgets/navigator.dart b/framework/lib/src/widgets/navigator.dart
index a81d7ae..d5e0b38 100644
--- a/framework/lib/src/widgets/navigator.dart
+++ b/framework/lib/src/widgets/navigator.dart
@@ -55,8 +55,9 @@
 ///
 /// Used by the restorable methods of the [Navigator] that add anonymous routes
 /// (e.g. [NavigatorState.restorablePush]). For this use case, the
-/// [RestorableRouteBuilder] must be static function as the [Navigator] will
-/// call it again during state restoration to re-create the route.
+/// [RestorableRouteBuilder] must be static function annotated with
+/// `@pragma('vm:entry-point')`. The [Navigator] will call it again during
+/// state restoration to re-create the route.
 typedef RestorableRouteBuilder<T> = Route<T> Function(BuildContext context, Object? arguments);
 
 /// Signature for the [Navigator.popUntil] predicate argument.
@@ -170,7 +171,6 @@
   final ValueNotifier<String?> _restorationScopeId = ValueNotifier<String?>(null);
 
   void _updateSettings(RouteSettings newSettings) {
-    assert(newSettings != null);
     if (_settings != newSettings) {
       _settings = newSettings;
       changedInternalState();
@@ -611,15 +611,15 @@
   /// The navigator that the observer is observing, if any.
   NavigatorState? get navigator => _navigators[this];
 
-  /// Expando mapping instances of NavigatorObserver to their associated
-  /// NavigatorState (or `null`, if there is no associated NavigatorState). The
-  /// reason we don't simply use a private instance field of type
-  /// `NavigatorState?` is because as part of implementing
-  /// https://github.com/dart-lang/language/issues/2020, it will soon become a
-  /// runtime error to invoke a private member that is mocked in another
-  /// library. By using an expando rather than an instance field, we ensure
-  /// that a mocked NavigatorObserver can still properly keep track of its
-  /// associated NavigatorState.
+  // Expando mapping instances of NavigatorObserver to their associated
+  // NavigatorState (or `null`, if there is no associated NavigatorState). The
+  // reason we don't use a private instance field of type
+  // `NavigatorState?` is because as part of implementing
+  // https://github.com/dart-lang/language/issues/2020, it will soon become a
+  // runtime error to invoke a private member that is mocked in another
+  // library. By using an expando rather than an instance field, we ensure
+  // that a mocked NavigatorObserver can still properly keep track of its
+  // associated NavigatorState.
   static final Expando<NavigatorState> _navigators = Expando<NavigatorState>();
 
   /// The [Navigator] pushed `route`.
@@ -676,7 +676,7 @@
     super.key,
     required HeroController this.controller,
     required super.child,
-  }) : assert(controller != null);
+  });
 
   /// Creates a widget to prevent the subtree from receiving the hero controller
   /// above.
@@ -909,7 +909,6 @@
       int indexOfNextRouteInNewHistory = 0;
 
       for (final _RouteEntry routeEntry in resultsToVerify.cast<_RouteEntry>()) {
-        assert(routeEntry != null);
         assert(!routeEntry.isWaitingForEnteringDecision && !routeEntry.isWaitingForExitingDecision);
         if (
           indexOfNextRouteInNewHistory >= newPageRouteHistory.length ||
@@ -1093,7 +1092,7 @@
   }
 }
 
-/// The default value of [MediaQueryData.routeTraversalEdgeBehavior].
+/// The default value of [Navigator.routeTraversalEdgeBehavior].
 ///
 /// {@macro flutter.widgets.navigator.routeTraversalEdgeBehavior}
 const TraversalEdgeBehavior kDefaultRouteTraversalEdgeBehavior = kIsWeb
@@ -1410,12 +1409,7 @@
     this.requestFocus = true,
     this.restorationScopeId,
     this.routeTraversalEdgeBehavior = kDefaultRouteTraversalEdgeBehavior,
-  }) : assert(pages != null),
-       assert(onGenerateInitialRoutes != null),
-       assert(transitionDelegate != null),
-       assert(observers != null),
-       assert(routeTraversalEdgeBehavior != null),
-       assert(reportsRouteUpdateToEngine != null);
+  });
 
   /// The list of pages with which to populate the history.
   ///
@@ -2108,9 +2102,10 @@
   /// {@macro flutter.widgets.navigator.push}
   ///
   /// {@template flutter.widgets.Navigator.restorablePush}
-  /// The method takes a _static_ [RestorableRouteBuilder] as argument, which
-  /// must instantiate and return a new [Route] object that will be added to
-  /// the navigator. The provided `arguments` object is passed to the
+  /// The method takes a [RestorableRouteBuilder] as argument, which must be a
+  /// _static_ function annotated with `@pragma('vm:entry-point')`. It must
+  /// instantiate and return a new [Route] object that will be added to the
+  /// navigator. The provided `arguments` object is passed to the
   /// `routeBuilder`. The navigator calls the static `routeBuilder` function
   /// again during state restoration to re-create the route object.
   ///
@@ -2841,9 +2836,7 @@
       required _RouteLifecycle initialState,
       required this.pageBased,
       this.restorationInformation,
-    }) : assert(route != null),
-         assert(!pageBased || route.settings is Page),
-         assert(initialState != null),
+    }) : assert(!pageBased || route.settings is Page),
          assert(
            initialState == _RouteLifecycle.staging ||
            initialState == _RouteLifecycle.add ||
@@ -2893,7 +2886,6 @@
 
   void handleAdd({ required NavigatorState navigator, required Route<dynamic>? previousPresent }) {
     assert(currentState == _RouteLifecycle.add);
-    assert(navigator != null);
     assert(navigator._debugLocked);
     assert(route._navigator == null);
     route._navigator = navigator;
@@ -2907,7 +2899,6 @@
 
   void handlePush({ required NavigatorState navigator, required bool isNewFirst, required Route<dynamic>? previous, required Route<dynamic>? previousPresent }) {
     assert(currentState == _RouteLifecycle.push || currentState == _RouteLifecycle.pushReplace || currentState == _RouteLifecycle.replace);
-    assert(navigator != null);
     assert(navigator._debugLocked);
     assert(
       route._navigator == null,
@@ -2964,7 +2955,6 @@
   /// Returns true if the route is popped; otherwise, returns false if the route
   /// refuses to be popped.
   bool handlePop({ required NavigatorState navigator, required Route<dynamic>? previousPresent }) {
-    assert(navigator != null);
     assert(navigator._debugLocked);
     assert(route._navigator == navigator);
     currentState = _RouteLifecycle.popping;
@@ -2991,7 +2981,6 @@
   }
 
   void handleRemoval({ required NavigatorState navigator, required Route<dynamic>? previousPresent }) {
-    assert(navigator != null);
     assert(navigator._debugLocked);
     assert(route._navigator == navigator);
     currentState = _RouteLifecycle.removing;
@@ -3118,7 +3107,6 @@
     // already announced this change by calling didPopNext.
     return !(
       nextRoute == null &&
-        lastAnnouncedPoppedNextRoute != null &&
         lastAnnouncedPoppedNextRoute == lastAnnouncedNextRoute
     );
   }
@@ -3685,7 +3673,7 @@
     _RouteEntry? previousOldPageRouteEntry;
     while (oldEntriesBottom <= oldEntriesTop) {
       final _RouteEntry oldEntry = _history[oldEntriesBottom];
-      assert(oldEntry != null && oldEntry.currentState != _RouteLifecycle.disposed);
+      assert(oldEntry.currentState != _RouteLifecycle.disposed);
       // Records pageless route. The bottom most pageless routes will be
       // stored in key = null.
       if (!oldEntry.pageBased) {
@@ -3711,15 +3699,14 @@
       oldEntriesBottom += 1;
     }
 
-    int pagelessRoutesToSkip = 0;
+    final List<_RouteEntry> unattachedPagelessRoutes=<_RouteEntry>[];
     // Scans the top of the list until we found a page-based route that cannot be
     // updated.
     while ((oldEntriesBottom <= oldEntriesTop) && (newPagesBottom <= newPagesTop)) {
       final _RouteEntry oldEntry = _history[oldEntriesTop];
-      assert(oldEntry != null && oldEntry.currentState != _RouteLifecycle.disposed);
+      assert(oldEntry.currentState != _RouteLifecycle.disposed);
       if (!oldEntry.pageBased) {
-        // This route might need to be skipped if we can not find a page above.
-        pagelessRoutesToSkip += 1;
+        unattachedPagelessRoutes.add(oldEntry);
         oldEntriesTop -= 1;
         continue;
       }
@@ -3727,14 +3714,22 @@
       if (!oldEntry.canUpdateFrom(newPage)) {
         break;
       }
-      // We found the page for all the consecutive pageless routes below. Those
-      // pageless routes do not need to be skipped.
-      pagelessRoutesToSkip = 0;
+
+      // We found the page for all the consecutive pageless routes below. Attach these
+      // pageless routes to the page.
+      if(unattachedPagelessRoutes.isNotEmpty) {
+         pageRouteToPagelessRoutes.putIfAbsent(
+          oldEntry,
+          () =>  List<_RouteEntry>.from(unattachedPagelessRoutes),
+        );
+        unattachedPagelessRoutes.clear();
+      }
+
       oldEntriesTop -= 1;
       newPagesTop -= 1;
     }
     // Reverts the pageless routes that cannot be updated.
-    oldEntriesTop += pagelessRoutesToSkip;
+    oldEntriesTop += unattachedPagelessRoutes.length;
 
     // Scans middle of the old entries and records the page key to old entry map.
     int oldEntriesBottomToScan = oldEntriesBottom;
@@ -3746,7 +3741,6 @@
       final _RouteEntry oldEntry = _history[oldEntriesBottomToScan];
       oldEntriesBottomToScan += 1;
       assert(
-        oldEntry != null &&
         oldEntry.currentState != _RouteLifecycle.disposed,
       );
       // Pageless routes will be recorded when we update the middle of the old
@@ -3857,7 +3851,7 @@
     // Updates the top of the list.
     while ((oldEntriesBottom <= oldEntriesTop) && (newPagesBottom <= newPagesTop)) {
       final _RouteEntry oldEntry = _history[oldEntriesBottom];
-      assert(oldEntry != null && oldEntry.currentState != _RouteLifecycle.disposed);
+      assert(oldEntry.currentState != _RouteLifecycle.disposed);
       if (!oldEntry.pageBased) {
         assert(previousOldPageRouteEntry != null);
         final List<_RouteEntry> pagelessRoutes = pageRouteToPagelessRoutes
@@ -4138,7 +4132,6 @@
 
   Route<T?>? _routeNamed<T>(String name, { required Object? arguments, bool allowNull = false }) {
     assert(!_debugLocked);
-    assert(name != null);
     if (allowNull && widget.onGenerateRoute == null) {
       return null;
     }
@@ -4246,7 +4239,6 @@
     String routeName, {
     Object? arguments,
   }) {
-    assert(routeName != null);
     assert(debugIsSerializableForRestoration(arguments), 'The arguments object must be serializable via the StandardMessageCodec.');
     final _RouteEntry entry = _RestorationInformation.named(
       name: routeName,
@@ -4319,7 +4311,6 @@
     TO? result,
     Object? arguments,
   }) {
-    assert(routeName != null);
     assert(debugIsSerializableForRestoration(arguments), 'The arguments object must be serializable via the StandardMessageCodec.');
     final _RouteEntry entry = _RestorationInformation.named(
       name: routeName,
@@ -4455,7 +4446,6 @@
     RoutePredicate predicate, {
     Object? arguments,
   }) {
-    assert(newRouteName != null);
     assert(debugIsSerializableForRestoration(arguments), 'The arguments object must be serializable via the StandardMessageCodec.');
     final _RouteEntry entry = _RestorationInformation.named(
       name: newRouteName,
@@ -4524,7 +4514,6 @@
   /// {@end-tool}
   @optionalTypeArgs
   String restorablePush<T extends Object?>(RestorableRouteBuilder<T> routeBuilder, {Object? arguments}) {
-    assert(routeBuilder != null);
     assert(_debugIsStaticCallback(routeBuilder), 'The provided routeBuilder must be a static function.');
     assert(debugIsSerializableForRestoration(arguments), 'The arguments object must be serializable via the StandardMessageCodec.');
     final _RouteEntry entry = _RestorationInformation.anonymous(
@@ -4542,7 +4531,6 @@
       _debugLocked = true;
       return true;
     }());
-    assert(entry.route != null);
     assert(entry.route._navigator == null);
     assert(entry.currentState == _RouteLifecycle.push);
     _history.add(entry);
@@ -4622,7 +4610,6 @@
   ///    be restored during state restoration.
   @optionalTypeArgs
   Future<T?> pushReplacement<T extends Object?, TO extends Object?>(Route<T> newRoute, { TO? result }) {
-    assert(newRoute != null);
     assert(newRoute._navigator == null);
     _pushReplacementEntry(_RouteEntry(newRoute, pageBased: false, initialState: _RouteLifecycle.pushReplace), result);
     return newRoute.popped;
@@ -4647,7 +4634,6 @@
   /// {@end-tool}
   @optionalTypeArgs
   String restorablePushReplacement<T extends Object?, TO extends Object?>(RestorableRouteBuilder<T> routeBuilder, { TO? result, Object? arguments }) {
-    assert(routeBuilder != null);
     assert(_debugIsStaticCallback(routeBuilder), 'The provided routeBuilder must be a static function.');
     assert(debugIsSerializableForRestoration(arguments), 'The arguments object must be serializable via the StandardMessageCodec.');
     final _RouteEntry entry = _RestorationInformation.anonymous(
@@ -4665,7 +4651,6 @@
       _debugLocked = true;
       return true;
     }());
-    assert(entry.route != null);
     assert(entry.route._navigator == null);
     assert(_history.isNotEmpty);
     assert(_history.any(_RouteEntry.isPresentPredicate), 'Navigator has no active routes to replace.');
@@ -4708,7 +4693,6 @@
   ///    restored during state restoration.
   @optionalTypeArgs
   Future<T?> pushAndRemoveUntil<T extends Object?>(Route<T> newRoute, RoutePredicate predicate) {
-    assert(newRoute != null);
     assert(newRoute._navigator == null);
     assert(newRoute.overlayEntries.isEmpty);
     _pushEntryAndRemoveUntil(_RouteEntry(newRoute, pageBased: false, initialState: _RouteLifecycle.push), predicate);
@@ -4733,7 +4717,6 @@
   /// {@end-tool}
   @optionalTypeArgs
   String restorablePushAndRemoveUntil<T extends Object?>(RestorableRouteBuilder<T> newRouteBuilder, RoutePredicate predicate, {Object? arguments}) {
-    assert(newRouteBuilder != null);
     assert(_debugIsStaticCallback(newRouteBuilder), 'The provided routeBuilder must be a static function.');
     assert(debugIsSerializableForRestoration(arguments), 'The arguments object must be serializable via the StandardMessageCodec.');
     final _RouteEntry entry = _RestorationInformation.anonymous(
@@ -4751,10 +4734,8 @@
       _debugLocked = true;
       return true;
     }());
-    assert(entry.route != null);
     assert(entry.route._navigator == null);
     assert(entry.route.overlayEntries.isEmpty);
-    assert(predicate != null);
     assert(entry.currentState == _RouteLifecycle.push);
     int index = _history.length - 1;
     _history.add(entry);
@@ -4786,9 +4767,7 @@
   @optionalTypeArgs
   void replace<T extends Object?>({ required Route<dynamic> oldRoute, required Route<T> newRoute }) {
     assert(!_debugLocked);
-    assert(oldRoute != null);
     assert(oldRoute._navigator == this);
-    assert(newRoute != null);
     _replaceEntry(_RouteEntry(newRoute, pageBased: false, initialState: _RouteLifecycle.replace), oldRoute);
   }
 
@@ -4803,12 +4782,9 @@
   /// {@macro flutter.widgets.Navigator.restorablePushNamed.returnValue}
   @optionalTypeArgs
   String restorableReplace<T extends Object?>({ required Route<dynamic> oldRoute, required RestorableRouteBuilder<T> newRouteBuilder, Object? arguments }) {
-    assert(oldRoute != null);
     assert(oldRoute._navigator == this);
-    assert(newRouteBuilder != null);
     assert(_debugIsStaticCallback(newRouteBuilder), 'The provided routeBuilder must be a static function.');
     assert(debugIsSerializableForRestoration(arguments), 'The arguments object must be serializable via the StandardMessageCodec.');
-    assert(oldRoute != null);
     final _RouteEntry entry = _RestorationInformation.anonymous(
       routeBuilder: newRouteBuilder,
       arguments: arguments,
@@ -4858,9 +4834,7 @@
   ///    be restored during state restoration.
   @optionalTypeArgs
   void replaceRouteBelow<T extends Object?>({ required Route<dynamic> anchorRoute, required Route<T> newRoute }) {
-    assert(newRoute != null);
     assert(newRoute._navigator == null);
-    assert(anchorRoute != null);
     assert(anchorRoute._navigator == this);
     _replaceEntryBelow(_RouteEntry(newRoute, pageBased: false, initialState: _RouteLifecycle.replace), anchorRoute);
   }
@@ -4877,12 +4851,9 @@
   /// {@macro flutter.widgets.Navigator.restorablePushNamed.returnValue}
   @optionalTypeArgs
   String restorableReplaceRouteBelow<T extends Object?>({ required Route<dynamic> anchorRoute, required RestorableRouteBuilder<T> newRouteBuilder, Object? arguments }) {
-    assert(anchorRoute != null);
     assert(anchorRoute._navigator == this);
-    assert(newRouteBuilder != null);
     assert(_debugIsStaticCallback(newRouteBuilder), 'The provided routeBuilder must be a static function.');
     assert(debugIsSerializableForRestoration(arguments), 'The arguments object must be serializable via the StandardMessageCodec.');
-    assert(anchorRoute != null);
     final _RouteEntry entry = _RestorationInformation.anonymous(
       routeBuilder: newRouteBuilder,
       arguments: arguments,
@@ -4960,7 +4931,6 @@
     }
     assert(lastEntry.route._navigator == this);
     final RoutePopDisposition disposition = await lastEntry.route.willPop(); // this is asynchronous
-    assert(disposition != null);
     if (!mounted) {
       // Forget about this pop, we were disposed in the meantime.
       return true;
@@ -5072,7 +5042,6 @@
   ///
   /// {@macro flutter.widgets.navigator.removeRoute}
   void removeRoute(Route<dynamic> route) {
-    assert(route != null);
     assert(!_debugLocked);
     assert(() {
       _debugLocked = true;
@@ -5081,7 +5050,6 @@
     assert(route._navigator == this);
     final bool wasCurrent = route.isCurrent;
     final _RouteEntry entry = _history.firstWhere(_RouteEntry.isRoutePredicate(route));
-    assert(entry != null);
     entry.remove();
     _flushHistoryUpdates(rearrangeOverlay: false);
     assert(() {
@@ -5108,7 +5076,6 @@
       _debugLocked = true;
       return true;
     }());
-    assert(anchorRoute != null);
     assert(anchorRoute._navigator == this);
     final int anchorIndex = _history.indexWhere(_RouteEntry.isRoutePredicate(anchorRoute));
     assert(anchorIndex >= 0, 'This Navigator does not contain the specified anchorRoute.');
@@ -5168,7 +5135,6 @@
 
   @optionalTypeArgs
   Route<T>? _getRouteById<T>(String id) {
-    assert(id != null);
     return _history.cast<_RouteEntry?>().firstWhere(
       (_RouteEntry? entry) => entry!.restorationId == id,
       orElse: () => null,
@@ -5206,7 +5172,6 @@
         _history.length - 1,
         _RouteEntry.willBePresentPredicate,
       );
-      assert(routeIndex != null);
       final Route<dynamic> route = _history[routeIndex].route;
       Route<dynamic>? previousRoute;
       if (!route.willHandlePopInternally && routeIndex > 0) {
@@ -5304,7 +5269,7 @@
 }
 
 abstract class _RestorationInformation {
-  _RestorationInformation(this.type) : assert(type != null);
+  _RestorationInformation(this.type);
   factory _RestorationInformation.named({
     required String name,
     required Object? arguments,
@@ -5317,11 +5282,9 @@
   }) = _AnonymousRestorationInformation;
 
   factory _RestorationInformation.fromSerializableData(Object data) {
-    assert(data != null);
     final List<Object?> casted = data as List<Object?>;
     assert(casted.isNotEmpty);
     final _RouteRestorationType type = _RouteRestorationType.values[casted[0]! as int];
-    assert(type != null);
     switch (type) {
       case _RouteRestorationType.named:
         return _NamedRestorationInformation.fromSerializableData(casted.sublist(1));
@@ -5350,10 +5313,7 @@
   Route<dynamic> createRoute(NavigatorState navigator);
 
   _RouteEntry toRouteEntry(NavigatorState navigator, {_RouteLifecycle initialState = _RouteLifecycle.add}) {
-    assert(navigator != null);
-    assert(initialState != null);
     final Route<Object?> route = createRoute(navigator);
-    assert(route != null);
     return _RouteEntry(
       route,
       pageBased: false,
@@ -5368,7 +5328,7 @@
     required this.name,
     required this.arguments,
     required this.restorationScopeId,
-  }) : assert(name != null), super(_RouteRestorationType.named);
+  }) : super(_RouteRestorationType.named);
 
   factory _NamedRestorationInformation.fromSerializableData(List<Object?> data) {
     assert(data.length >= 2);
@@ -5397,7 +5357,6 @@
   @override
   Route<dynamic> createRoute(NavigatorState navigator) {
     final Route<dynamic> route = navigator._routeNamed<dynamic>(name, arguments: arguments)!;
-    assert(route != null);
     return route;
   }
 }
@@ -5407,7 +5366,7 @@
     required this.routeBuilder,
     required this.arguments,
     required this.restorationScopeId,
-  }) : assert(routeBuilder != null), super(_RouteRestorationType.anonymous);
+  }) : super(_RouteRestorationType.anonymous);
 
   factory _AnonymousRestorationInformation.fromSerializableData(List<Object?> data) {
     assert(data.length > 1);
@@ -5444,7 +5403,6 @@
   @override
   Route<dynamic> createRoute(NavigatorState navigator) {
     final Route<dynamic> result = routeBuilder(navigator.context, arguments);
-    assert(result != null);
     return result;
   }
 }
@@ -5526,9 +5484,8 @@
     Set<String?> pagesToRemove,
   ) {
     assert(page == null || page.pageBased);
-    assert(pageToRoutes != null);
     assert(!pageToRoutes.containsKey(page?.restorationId));
-    if (routes != null && routes.isNotEmpty) {
+    if (routes.isNotEmpty) {
       assert(page == null || page.restorationId != null);
       final String? restorationId = page?.restorationId;
       pageToRoutes[restorationId] = routes;
@@ -5672,7 +5629,7 @@
     this.navigatorFinder = _defaultNavigatorFinder,
     required this.onPresent,
     this.onComplete,
-  }) : assert(onPresent != null), assert(navigatorFinder != null);
+  });
 
   /// A callback that given the [BuildContext] of the [State] object to which
   /// this property is registered returns the [NavigatorState] of the navigator
@@ -5707,7 +5664,6 @@
     assert(!isPresent);
     assert(isRegistered);
     final String routeId = onPresent(_navigator, arguments);
-    assert(routeId != null);
     _hookOntoRouteFuture(routeId);
     notifyListeners();
   }
@@ -5760,12 +5716,10 @@
 
   NavigatorState get _navigator {
     final NavigatorState navigator = navigatorFinder(state.context);
-    assert(navigator != null);
     return navigator;
   }
 
   void _hookOntoRouteFuture(String id) {
-    assert(id != null);
     _route = _navigator._getRouteById<T>(id);
     assert(_route != null);
     route!.restorationScopeId.addListener(notifyListeners);
diff --git a/framework/lib/src/widgets/nested_scroll_view.dart b/framework/lib/src/widgets/nested_scroll_view.dart
index 5c6d988..01eab7b 100644
--- a/framework/lib/src/widgets/nested_scroll_view.dart
+++ b/framework/lib/src/widgets/nested_scroll_view.dart
@@ -190,12 +190,7 @@
     this.clipBehavior = Clip.hardEdge,
     this.restorationId,
     this.scrollBehavior,
-  }) : assert(scrollDirection != null),
-       assert(reverse != null),
-       assert(headerSliverBuilder != null),
-       assert(body != null),
-       assert(floatHeaderSlivers != null),
-       assert(clipBehavior != null);
+  });
 
   /// An object that can be used to control the position to which the outer
   /// scroll view is scrolled.
@@ -510,8 +505,7 @@
   const _InheritedNestedScrollView({
     required this.state,
     required super.child,
-  }) : assert(state != null),
-       assert(child != null);
+  });
 
   final NestedScrollViewState state;
 
@@ -526,6 +520,7 @@
     required super.pixels,
     required super.viewportDimension,
     required super.axisDirection,
+    required super.devicePixelRatio,
     required this.minRange,
     required this.maxRange,
     required this.correctionOffset,
@@ -538,6 +533,7 @@
     double? pixels,
     double? viewportDimension,
     AxisDirection? axisDirection,
+    double? devicePixelRatio,
     double? minRange,
     double? maxRange,
     double? correctionOffset,
@@ -548,6 +544,7 @@
       pixels: pixels ?? (hasPixels ? this.pixels : null),
       viewportDimension: viewportDimension ?? (hasViewportDimension ? this.viewportDimension : null),
       axisDirection: axisDirection ?? this.axisDirection,
+      devicePixelRatio: devicePixelRatio ?? this.devicePixelRatio,
       minRange: minRange ?? this.minRange,
       maxRange: maxRange ?? this.maxRange,
       correctionOffset: correctionOffset ?? this.correctionOffset,
@@ -630,7 +627,6 @@
   ScrollDirection _userScrollDirection = ScrollDirection.idle;
 
   void updateUserScrollDirection(ScrollDirection value) {
-    assert(value != null);
     if (userScrollDirection == value) {
       return;
     }
@@ -749,7 +745,6 @@
   }
 
   _NestedScrollMetrics _getMetrics(_NestedScrollPosition innerPosition, double velocity) {
-    assert(innerPosition != null);
     double pixels, minRange, maxRange, correctionOffset;
     double extra = 0.0;
     if (innerPosition.pixels == innerPosition.minScrollExtent) {
@@ -815,6 +810,7 @@
       minRange: minRange,
       maxRange: maxRange,
       correctionOffset: correctionOffset,
+      devicePixelRatio: _outerPosition!.devicePixelRatio,
     );
   }
 
@@ -903,7 +899,10 @@
     // If an update is made to pointer scrolling here, consider if the same
     // (or similar) change should be made in
     // ScrollPositionWithSingleContext.pointerScroll.
-    assert(delta != 0.0);
+    if (delta == 0.0) {
+      goBallistic(0.0);
+      return;
+    }
 
     goIdle();
     updateUserScrollDirection(
@@ -1180,7 +1179,7 @@
     super.debugLabel,
     required this.coordinator,
   }) {
-    if (!hasPixels && initialPixels != null) {
+    if (!hasPixels) {
       correctPixels(initialPixels);
     }
     if (activity == null) {
@@ -1369,7 +1368,6 @@
     if (simulation == null) {
       return IdleScrollActivity(this);
     }
-    assert(mode != null);
     switch (mode) {
       case _NestedBallisticScrollActivityMode.outer:
         assert(metrics != null);
@@ -1670,8 +1668,7 @@
     super.key,
     required this.handle,
     Widget? sliver,
-  }) : assert(handle != null),
-      super(child: sliver);
+  }) : super(child: sliver);
 
   /// The object in which the absorbed overlap is recorded.
   ///
@@ -1715,8 +1712,7 @@
   RenderSliverOverlapAbsorber({
     required SliverOverlapAbsorberHandle handle,
     RenderSliver? sliver,
-  }) : assert(handle != null),
-       _handle = handle {
+  }) : _handle = handle {
     child = sliver;
   }
 
@@ -1727,7 +1723,6 @@
   SliverOverlapAbsorberHandle get handle => _handle;
   SliverOverlapAbsorberHandle _handle;
   set handle(SliverOverlapAbsorberHandle value) {
-    assert(value != null);
     if (handle == value) {
       return;
     }
@@ -1832,8 +1827,7 @@
     super.key,
     required this.handle,
     Widget? sliver,
-  }) : assert(handle != null),
-       super(child: sliver);
+  }) : super(child: sliver);
 
   /// The handle to the [SliverOverlapAbsorber] that is feeding this injector.
   ///
@@ -1873,8 +1867,7 @@
   /// The [handle] must not be null.
   RenderSliverOverlapInjector({
     required SliverOverlapAbsorberHandle handle,
-  }) : assert(handle != null),
-       _handle = handle;
+  }) : _handle = handle;
 
   double? _currentLayoutExtent;
   double? _currentMaxExtent;
@@ -1887,7 +1880,6 @@
   SliverOverlapAbsorberHandle get handle => _handle;
   SliverOverlapAbsorberHandle _handle;
   set handle(SliverOverlapAbsorberHandle value) {
-    assert(value != null);
     if (handle == value) {
       return;
     }
@@ -1998,7 +1990,7 @@
     super.slivers,
     required this.handle,
     super.clipBehavior,
-  }) : assert(handle != null);
+  });
 
   /// The handle to the [SliverOverlapAbsorber] that is feeding this injector.
   final SliverOverlapAbsorberHandle handle;
@@ -2057,15 +2049,13 @@
     super.center,
     required SliverOverlapAbsorberHandle handle,
     super.clipBehavior,
-  }) : assert(handle != null),
-       _handle = handle;
+  }) : _handle = handle;
 
   /// The object to notify when [markNeedsLayout] is called.
   SliverOverlapAbsorberHandle get handle => _handle;
   SliverOverlapAbsorberHandle _handle;
   /// Setting this will trigger notifications on the new object.
   set handle(SliverOverlapAbsorberHandle value) {
-    assert(value != null);
     if (handle == value) {
       return;
     }
diff --git a/framework/lib/src/widgets/orientation_builder.dart b/framework/lib/src/widgets/orientation_builder.dart
index 189a621..6289374 100644
--- a/framework/lib/src/widgets/orientation_builder.dart
+++ b/framework/lib/src/widgets/orientation_builder.dart
@@ -31,11 +31,11 @@
   const OrientationBuilder({
     super.key,
     required this.builder,
-  }) : assert(builder != null);
+  });
 
   /// Builds the widgets below this widget given this widget's orientation.
   ///
-  /// A widget's orientation is simply a factor of its width relative to its
+  /// A widget's orientation is a factor of its width relative to its
   /// height. For example, a [Column] widget will have a landscape orientation
   /// if its width exceeds its height, even though it displays its children in
   /// a vertical array.
diff --git a/framework/lib/src/widgets/overflow_bar.dart b/framework/lib/src/widgets/overflow_bar.dart
index 4b35bb8..dad0549 100644
--- a/framework/lib/src/widgets/overflow_bar.dart
+++ b/framework/lib/src/widgets/overflow_bar.dart
@@ -59,7 +59,7 @@
   /// [overflowDirection], and [clipBehavior] parameters must not be
   /// null. The [children] argument must not be null and must not contain
   /// any null objects.
-  OverflowBar({
+  const OverflowBar({
     super.key,
     this.spacing = 0.0,
     this.alignment,
@@ -69,11 +69,7 @@
     this.textDirection,
     this.clipBehavior = Clip.none,
     super.children,
-  }) : assert(spacing != null),
-       assert(overflowSpacing != null),
-       assert(overflowAlignment != null),
-       assert(overflowDirection != null),
-       assert(clipBehavior != null);
+  });
 
   /// The width of the gap between [children] for the default
   /// horizontal layout.
@@ -257,12 +253,7 @@
     VerticalDirection overflowDirection = VerticalDirection.down,
     required TextDirection textDirection,
     Clip clipBehavior = Clip.none,
-  }) : assert(spacing != null),
-       assert(overflowSpacing != null),
-       assert(overflowAlignment != null),
-       assert(textDirection != null),
-       assert(clipBehavior != null),
-       _spacing = spacing,
+  }) : _spacing = spacing,
        _alignment = alignment,
        _overflowSpacing = overflowSpacing,
        _overflowAlignment = overflowAlignment,
@@ -275,7 +266,6 @@
   double get spacing => _spacing;
   double _spacing;
   set spacing (double value) {
-    assert(value != null);
     if (_spacing == value) {
       return;
     }
@@ -296,7 +286,6 @@
   double get overflowSpacing => _overflowSpacing;
   double _overflowSpacing;
   set overflowSpacing (double value) {
-    assert(value != null);
     if (_overflowSpacing == value) {
       return;
     }
@@ -307,7 +296,6 @@
   OverflowBarAlignment get overflowAlignment => _overflowAlignment;
   OverflowBarAlignment _overflowAlignment;
   set overflowAlignment (OverflowBarAlignment value) {
-    assert(value != null);
     if (_overflowAlignment == value) {
       return;
     }
@@ -318,7 +306,6 @@
   VerticalDirection get overflowDirection => _overflowDirection;
   VerticalDirection _overflowDirection;
   set overflowDirection (VerticalDirection value) {
-    assert(value != null);
     if (_overflowDirection == value) {
       return;
     }
@@ -339,7 +326,6 @@
   Clip get clipBehavior => _clipBehavior;
   Clip _clipBehavior = Clip.none;
   set clipBehavior(Clip value) {
-    assert(value != null);
     if (value == _clipBehavior) {
       return;
     }
@@ -519,7 +505,6 @@
             x = rtl ? 0 : constraints.maxWidth - child.size.width;
             break;
         }
-        assert(x != null);
         childParentData.offset = Offset(x, y);
         y += child.size.height + overflowSpacing;
         child = nextChild();
diff --git a/framework/lib/src/widgets/overlay.dart b/framework/lib/src/widgets/overlay.dart
index b4aa248..d3cd0de 100644
--- a/framework/lib/src/widgets/overlay.dart
+++ b/framework/lib/src/widgets/overlay.dart
@@ -11,6 +11,7 @@
 
 import 'basic.dart';
 import 'framework.dart';
+import 'lookup_boundary.dart';
 import 'ticker_provider.dart';
 
 // Examples can assume:
@@ -71,10 +72,7 @@
     required this.builder,
     bool opaque = false,
     bool maintainState = false,
-  }) : assert(builder != null),
-       assert(opaque != null),
-       assert(maintainState != null),
-       _opaque = opaque,
+  }) : _opaque = opaque,
        _maintainState = maintainState;
 
   /// This entry will include the widget built by this builder in the overlay at
@@ -118,7 +116,6 @@
   bool _maintainState;
   set maintainState(bool value) {
     assert(!_disposedByOwner);
-    assert(_maintainState != null);
     if (_maintainState == value) {
       return;
     }
@@ -226,10 +223,7 @@
     required Key key,
     required this.entry,
     this.tickerEnabled = true,
-  }) : assert(key != null),
-       assert(entry != null),
-       assert(tickerEnabled != null),
-       super(key: key);
+  }) : super(key: key);
 
   final OverlayEntry entry;
   final bool tickerEnabled;
@@ -280,7 +274,7 @@
 /// The [Overlay] widget uses a custom stack implementation, which is very
 /// similar to the [Stack] widget. The main use case of [Overlay] is related to
 /// navigation and being able to insert widgets on top of the pages in an app.
-/// To simply display a stack of widgets, consider using [Stack] instead.
+/// For layout purposes unrelated to navigation, consider using [Stack] instead.
 ///
 /// An [Overlay] widget requires a [Directionality] widget to be in scope, so
 /// that it can resolve direction-sensitive coordinates of any
@@ -314,8 +308,7 @@
     super.key,
     this.initialEntries = const <OverlayEntry>[],
     this.clipBehavior = Clip.hardEdge,
-  }) : assert(initialEntries != null),
-       assert(clipBehavior != null);
+  });
 
   /// The entries to include in the overlay initially.
   ///
@@ -338,7 +331,8 @@
   final Clip clipBehavior;
 
   /// The [OverlayState] from the closest instance of [Overlay] that encloses
-  /// the given context, and, in debug mode, will throw if one is not found.
+  /// the given context within the closest [LookupBoundary], and, in debug mode,
+  /// will throw if one is not found.
   ///
   /// In debug mode, if the `debugRequiredFor` argument is provided and an
   /// overlay isn't found, then this function will throw an exception containing
@@ -372,8 +366,13 @@
     final OverlayState? result = maybeOf(context, rootOverlay: rootOverlay);
     assert(() {
       if (result == null) {
+        final bool hiddenByBoundary = LookupBoundary.debugIsHidingAncestorStateOfType<OverlayState>(context);
         final List<DiagnosticsNode> information = <DiagnosticsNode>[
-          ErrorSummary('No Overlay widget found.'),
+          ErrorSummary('No Overlay widget found${hiddenByBoundary ? ' within the closest LookupBoundary' : ''}.'),
+          if (hiddenByBoundary)
+            ErrorDescription(
+                'There is an ancestor Overlay widget, but it is hidden by a LookupBoundary.'
+            ),
           ErrorDescription('${debugRequiredFor?.runtimeType ?? 'Some'} widgets require an Overlay widget ancestor for correct operation.'),
           ErrorHint('The most common way to add an Overlay to an application is to include a MaterialApp, CupertinoApp or Navigator widget in the runApp() call.'),
           if (debugRequiredFor != null) DiagnosticsProperty<Widget>('The specific widget that failed to find an overlay was', debugRequiredFor, style: DiagnosticsTreeStyle.errorProperty),
@@ -389,7 +388,7 @@
   }
 
   /// The [OverlayState] from the closest instance of [Overlay] that encloses
-  /// the given context, if any.
+  /// the given context within the closest [LookupBoundary], if any.
   ///
   /// Typical usage is as follows:
   ///
@@ -413,8 +412,8 @@
     bool rootOverlay = false,
   }) {
     return rootOverlay
-        ? context.findRootAncestorStateOfType<OverlayState>()
-        : context.findAncestorStateOfType<OverlayState>();
+        ? LookupBoundary.findRootAncestorStateOfType<OverlayState>(context)
+        : LookupBoundary.findAncestorStateOfType<OverlayState>(context);
   }
 
   @override
@@ -638,15 +637,12 @@
 ///
 /// The first [skipCount] children are considered "offstage".
 class _Theatre extends MultiChildRenderObjectWidget {
-  _Theatre({
+  const _Theatre({
     this.skipCount = 0,
     this.clipBehavior = Clip.hardEdge,
     super.children,
-  }) : assert(skipCount != null),
-       assert(skipCount >= 0),
-       assert(children != null),
-       assert(children.length >= skipCount),
-       assert(clipBehavior != null);
+  }) : assert(skipCount >= 0),
+       assert(children.length >= skipCount);
 
   final int skipCount;
 
@@ -699,10 +695,7 @@
     required TextDirection textDirection,
     int skipCount = 0,
     Clip clipBehavior = Clip.hardEdge,
-  }) : assert(skipCount != null),
-       assert(skipCount >= 0),
-       assert(textDirection != null),
-       assert(clipBehavior != null),
+  }) : assert(skipCount >= 0),
        _textDirection = textDirection,
        _skipCount = skipCount,
        _clipBehavior = clipBehavior {
@@ -745,7 +738,6 @@
   int get skipCount => _skipCount;
   int _skipCount;
   set skipCount(int value) {
-    assert(value != null);
     if (_skipCount != value) {
       _skipCount = value;
       markNeedsLayout();
@@ -758,7 +750,6 @@
   Clip get clipBehavior => _clipBehavior;
   Clip _clipBehavior = Clip.hardEdge;
   set clipBehavior(Clip value) {
-    assert(value != null);
     if (value != _clipBehavior) {
       _clipBehavior = value;
       markNeedsPaint();
diff --git a/framework/lib/src/widgets/overscroll_indicator.dart b/framework/lib/src/widgets/overscroll_indicator.dart
index 84783f7..eae45a3 100644
--- a/framework/lib/src/widgets/overscroll_indicator.dart
+++ b/framework/lib/src/widgets/overscroll_indicator.dart
@@ -90,11 +90,7 @@
     required this.color,
     this.notificationPredicate = defaultScrollNotificationPredicate,
     this.child,
-  }) : assert(showLeading != null),
-       assert(showTrailing != null),
-       assert(axisDirection != null),
-       assert(color != null),
-       assert(notificationPredicate != null);
+  });
 
   /// Whether to show the overscroll glow on the side with negative scroll
   /// offsets.
@@ -248,9 +244,7 @@
         } else {
           assert(notification.overscroll != 0.0);
           if (notification.dragDetails != null) {
-            assert(notification.dragDetails!.globalPosition != null);
             final RenderBox renderer = notification.context!.findRenderObject()! as RenderBox;
-            assert(renderer != null);
             assert(renderer.hasSize);
             final Size size = renderer.size;
             final Offset position = renderer.globalToLocal(notification.dragDetails!.globalPosition);
@@ -317,10 +311,7 @@
     required TickerProvider vsync,
     required Color color,
     required Axis axis,
-  }) : assert(vsync != null),
-       assert(color != null),
-       assert(axis != null),
-       _color = color,
+  }) : _color = color,
        _axis = axis {
     _glowController = AnimationController(vsync: vsync)
       ..addStatusListener(_changePhase);
@@ -358,7 +349,6 @@
   Color get color => _color;
   Color _color;
   set color(Color value) {
-    assert(color != null);
     if (color == value) {
       return;
     }
@@ -369,7 +359,6 @@
   Axis get axis => _axis;
   Axis _axis;
   set axis(Axis value) {
-    assert(axis != null);
     if (axis == value) {
       return;
     }
@@ -655,9 +644,7 @@
     this.notificationPredicate = defaultScrollNotificationPredicate,
     this.clipBehavior = Clip.hardEdge,
     this.child,
-  }) : assert(axisDirection != null),
-       assert(notificationPredicate != null),
-       assert(clipBehavior != null);
+  });
 
   /// {@macro flutter.overscroll.axisDirection}
   final AxisDirection axisDirection;
@@ -774,7 +761,7 @@
 
   @override
   Widget build(BuildContext context) {
-    final Size size = MediaQuery.of(context).size;
+    final Size size = MediaQuery.sizeOf(context);
     double mainAxisSize;
     return NotificationListener<ScrollNotification>(
       onNotification: _handleScrollNotification,
diff --git a/framework/lib/src/widgets/page_storage.dart b/framework/lib/src/widgets/page_storage.dart
index e400819..c52bb85 100644
--- a/framework/lib/src/widgets/page_storage.dart
+++ b/framework/lib/src/widgets/page_storage.dart
@@ -27,8 +27,7 @@
 
 @immutable
 class _StorageEntryIdentifier {
-  const _StorageEntryIdentifier(this.keys)
-    : assert(keys != null);
+  const _StorageEntryIdentifier(this.keys);
 
   final List<PageStorageKey<dynamic>> keys;
 
@@ -164,7 +163,7 @@
     super.key,
     required this.bucket,
     required this.child,
-  }) : assert(bucket != null);
+  });
 
   /// The widget below this widget in the tree.
   ///
diff --git a/framework/lib/src/widgets/page_view.dart b/framework/lib/src/widgets/page_view.dart
index 15dbd4a..1be9d91 100644
--- a/framework/lib/src/widgets/page_view.dart
+++ b/framework/lib/src/widgets/page_view.dart
@@ -68,7 +68,7 @@
 ///         body: PageView(
 ///           controller: _pageController,
 ///           children: <Widget>[
-///             Container(
+///             ColoredBox(
 ///               color: Colors.red,
 ///               child: Center(
 ///                 child: ElevatedButton(
@@ -85,7 +85,7 @@
 ///                 ),
 ///               ),
 ///             ),
-///             Container(
+///             ColoredBox(
 ///               color: Colors.blue,
 ///               child: Center(
 ///                 child: ElevatedButton(
@@ -118,10 +118,7 @@
     this.initialPage = 0,
     this.keepPage = true,
     this.viewportFraction = 1.0,
-  }) : assert(initialPage != null),
-       assert(keepPage != null),
-       assert(viewportFraction != null),
-       assert(viewportFraction > 0.0);
+  }) : assert(viewportFraction > 0.0);
 
   /// The page to show when first creating the [PageView].
   final int initialPage;
@@ -273,6 +270,7 @@
     required super.viewportDimension,
     required super.axisDirection,
     required this.viewportFraction,
+    required super.devicePixelRatio,
   });
 
   @override
@@ -283,6 +281,7 @@
     double? viewportDimension,
     AxisDirection? axisDirection,
     double? viewportFraction,
+    double? devicePixelRatio,
   }) {
     return PageMetrics(
       minScrollExtent: minScrollExtent ?? (hasContentDimensions ? this.minScrollExtent : null),
@@ -291,6 +290,7 @@
       viewportDimension: viewportDimension ?? (hasViewportDimension ? this.viewportDimension : null),
       axisDirection: axisDirection ?? this.axisDirection,
       viewportFraction: viewportFraction ?? this.viewportFraction,
+      devicePixelRatio: devicePixelRatio ?? this.devicePixelRatio,
     );
   }
 
@@ -314,10 +314,7 @@
     bool keepPage = true,
     double viewportFraction = 1.0,
     super.oldPosition,
-  }) : assert(initialPage != null),
-       assert(keepPage != null),
-       assert(viewportFraction != null),
-       assert(viewportFraction > 0.0),
+  }) : assert(viewportFraction > 0.0),
        _viewportFraction = viewportFraction,
        _pageToUseOnStartup = initialPage.toDouble(),
        super(
@@ -423,8 +420,6 @@
 
   @override
   void restoreOffset(double offset, {bool initialRestore = false}) {
-    assert(initialRestore != null);
-    assert(offset != null);
     if (initialRestore) {
       _pageToUseOnStartup = offset;
     } else {
@@ -493,6 +488,7 @@
     double? viewportDimension,
     AxisDirection? axisDirection,
     double? viewportFraction,
+    double? devicePixelRatio,
   }) {
     return PageMetrics(
       minScrollExtent: minScrollExtent ?? (hasContentDimensions ? this.minScrollExtent : null),
@@ -501,6 +497,7 @@
       viewportDimension: viewportDimension ?? (hasViewportDimension ? this.viewportDimension : null),
       axisDirection: axisDirection ?? this.axisDirection,
       viewportFraction: viewportFraction ?? this.viewportFraction,
+      devicePixelRatio: devicePixelRatio ?? this.devicePixelRatio,
     );
   }
 }
@@ -509,7 +506,7 @@
   const _ForceImplicitScrollPhysics({
     required this.allowImplicitScrolling,
     super.parent,
-  }) : assert(allowImplicitScrolling != null);
+  });
 
   @override
   _ForceImplicitScrollPhysics applyTo(ScrollPhysics? ancestor) {
@@ -573,7 +570,7 @@
         (velocity >= 0.0 && position.pixels >= position.maxScrollExtent)) {
       return super.createBallisticSimulation(position, velocity);
     }
-    final Tolerance tolerance = this.tolerance;
+    final Tolerance tolerance = toleranceFor(position);
     final double target = _getTargetPixels(position, tolerance, velocity);
     if (target != position.pixels) {
       return ScrollSpringSimulation(spring, position.pixels, target, velocity, tolerance: tolerance);
@@ -657,9 +654,7 @@
     this.clipBehavior = Clip.hardEdge,
     this.scrollBehavior,
     this.padEnds = true,
-  }) : assert(allowImplicitScrolling != null),
-       assert(clipBehavior != null),
-       controller = controller ?? _defaultPageController,
+  }) : controller = controller ?? _defaultPageController,
        childrenDelegate = SliverChildListDelegate(children);
 
   /// Creates a scrollable list that works page by page using widgets that are
@@ -704,9 +699,7 @@
     this.clipBehavior = Clip.hardEdge,
     this.scrollBehavior,
     this.padEnds = true,
-  }) : assert(allowImplicitScrolling != null),
-       assert(clipBehavior != null),
-       controller = controller ?? _defaultPageController,
+  }) : controller = controller ?? _defaultPageController,
        childrenDelegate = SliverChildBuilderDelegate(
          itemBuilder,
          findChildIndexCallback: findChildIndexCallback,
@@ -812,10 +805,7 @@
     this.clipBehavior = Clip.hardEdge,
     this.scrollBehavior,
     this.padEnds = true,
-  }) : assert(childrenDelegate != null),
-       assert(allowImplicitScrolling != null),
-       assert(clipBehavior != null),
-       controller = controller ?? _defaultPageController;
+  }) : controller = controller ?? _defaultPageController;
 
   /// Controls whether the widget's pages will respond to
   /// [RenderObject.showOnScreen], which will allow for implicit accessibility
diff --git a/framework/lib/src/widgets/pages.dart b/framework/lib/src/widgets/pages.dart
index cc726c7..de3ff66 100644
--- a/framework/lib/src/widgets/pages.dart
+++ b/framework/lib/src/widgets/pages.dart
@@ -81,12 +81,7 @@
     this.maintainState = true,
     super.fullscreenDialog,
     super.allowSnapshotting = true,
-  }) : assert(pageBuilder != null),
-       assert(transitionsBuilder != null),
-       assert(opaque != null),
-       assert(barrierDismissible != null),
-       assert(maintainState != null),
-       assert(fullscreenDialog != null);
+  });
 
   /// {@template flutter.widgets.pageRouteBuilder.pageBuilder}
   /// Used build the route's primary contents.
diff --git a/framework/lib/src/widgets/performance_overlay.dart b/framework/lib/src/widgets/performance_overlay.dart
index 6ab0b9c..853d610 100644
--- a/framework/lib/src/widgets/performance_overlay.dart
+++ b/framework/lib/src/widgets/performance_overlay.dart
@@ -59,7 +59,7 @@
   /// For example, if you want a trace of all pictures that could not be
   /// rendered by the rasterizer within the frame boundary (and hence caused
   /// jank), specify 1. Specifying 2 will trace all pictures that took more
-  /// more than 2 frame intervals to render. Adjust this value to only capture
+  /// than 2 frame intervals to render. Adjust this value to only capture
   /// the particularly expensive pictures while skipping the others. Specifying
   /// 0 disables all capture.
   ///
diff --git a/framework/lib/src/widgets/platform_menu_bar.dart b/framework/lib/src/widgets/platform_menu_bar.dart
index 9d1288e..9127760 100644
--- a/framework/lib/src/widgets/platform_menu_bar.dart
+++ b/framework/lib/src/widgets/platform_menu_bar.dart
@@ -954,7 +954,7 @@
   ///
   /// On macOS, this is the `terminate` default menu.
   ///
-  /// This menu item will simply exit the application when activated.
+  /// This menu item will exit the application when activated.
   quit,
 
   /// The system provided "Services" submenu.
diff --git a/framework/lib/src/widgets/platform_view.dart b/framework/lib/src/widgets/platform_view.dart
index fa99120..a18f7cc 100644
--- a/framework/lib/src/widgets/platform_view.dart
+++ b/framework/lib/src/widgets/platform_view.dart
@@ -78,10 +78,7 @@
     this.creationParams,
     this.creationParamsCodec,
     this.clipBehavior = Clip.hardEdge,
-  }) : assert(viewType != null),
-       assert(hitTestBehavior != null),
-       assert(creationParams == null || creationParamsCodec != null),
-       assert(clipBehavior != null);
+  }) : assert(creationParams == null || creationParamsCodec != null);
 
   /// The unique identifier for Android view type to be embedded by this widget.
   ///
@@ -202,8 +199,6 @@
 // TODO(ychris): remove the documentation for conic path not supported once https://github.com/flutter/flutter/issues/35062 is resolved.
 /// Embeds an iOS view in the Widget hierarchy.
 ///
-/// {@macro flutter.rendering.RenderUiKitView}
-///
 /// Embedding iOS views is an expensive operation and should be avoided when a Flutter
 /// equivalent is possible.
 ///
@@ -216,6 +211,7 @@
 /// Construction of UIViews is done asynchronously, before the UIView is ready this widget paints
 /// nothing while maintaining the same layout constraints.
 ///
+/// Clipping operations on a UiKitView can result slow performance.
 /// If a conic path clipping is applied to a UIKitView,
 /// a quad path is used to approximate the clip due to limitation of Quartz.
 class UiKitView extends StatefulWidget {
@@ -231,9 +227,7 @@
     this.creationParams,
     this.creationParamsCodec,
     this.gestureRecognizers,
-  }) : assert(viewType != null),
-       assert(hitTestBehavior != null),
-       assert(creationParams == null || creationParamsCodec != null);
+  }) : assert(creationParams == null || creationParamsCodec != null);
 
   // TODO(amirh): reference the iOS API doc once available.
   /// The unique identifier for iOS view type to be embedded by this widget.
@@ -351,8 +345,7 @@
     super.key,
     required this.viewType,
     this.onPlatformViewCreated,
-  }) : assert(viewType != null),
-       assert(kIsWeb, 'HtmlElementView is only available on Flutter Web.');
+  }) : assert(kIsWeb, 'HtmlElementView is only available on Flutter Web.');
 
   /// The unique identifier for the HTML view type to be embedded by this widget.
   ///
@@ -687,10 +680,7 @@
     required this.hitTestBehavior,
     required this.gestureRecognizers,
     this.clipBehavior = Clip.hardEdge,
-  }) : assert(controller != null),
-       assert(hitTestBehavior != null),
-       assert(gestureRecognizers != null),
-       assert(clipBehavior != null);
+  });
 
   final AndroidViewController controller;
   final PlatformViewHitTestBehavior hitTestBehavior;
@@ -720,9 +710,7 @@
     required this.controller,
     required this.hitTestBehavior,
     required this.gestureRecognizers,
-  }) : assert(controller != null),
-       assert(hitTestBehavior != null),
-       assert(gestureRecognizers != null);
+  });
 
   final UiKitViewController controller;
   final PlatformViewHitTestBehavior hitTestBehavior;
@@ -757,8 +745,7 @@
     required this.viewType,
     required this.onPlatformViewCreated,
     required this.onFocusChanged,
-  }) : assert(id != null),
-       assert(onPlatformViewCreated != null);
+  });
 
   /// The unique identifier for the new platform view.
   ///
@@ -845,10 +832,7 @@
     required PlatformViewSurfaceFactory surfaceFactory,
     required CreatePlatformViewCallback onCreatePlatformView,
     required this.viewType,
-    }) : assert(surfaceFactory != null),
-         assert(onCreatePlatformView != null),
-         assert(viewType != null),
-         _surfaceFactory = surfaceFactory,
+    }) : _surfaceFactory = surfaceFactory,
          _onCreatePlatformView = onCreatePlatformView;
 
 
@@ -983,9 +967,7 @@
     required this.controller,
     required this.hitTestBehavior,
     required this.gestureRecognizers,
-  }) : assert(controller != null),
-       assert(hitTestBehavior != null),
-       assert(gestureRecognizers != null);
+  });
 
   /// The controller for the platform view integrated by this [PlatformViewSurface].
   ///
@@ -1078,9 +1060,7 @@
     required this.controller,
     required this.hitTestBehavior,
     required this.gestureRecognizers,
-  }) : assert(controller != null),
-       assert(hitTestBehavior != null),
-       assert(gestureRecognizers != null);
+  });
 
   /// The controller for the platform view integrated by this [AndroidViewSurface].
   ///
@@ -1147,9 +1127,7 @@
     required AndroidViewController super.controller,
     required super.hitTestBehavior,
     required super.gestureRecognizers,
-  }) : assert(controller != null),
-       assert(hitTestBehavior != null),
-       assert(gestureRecognizers != null);
+  });
 
   @override
   RenderObject createRenderObject(BuildContext context) {
@@ -1172,9 +1150,7 @@
     required AndroidViewController super.controller,
     required super.hitTestBehavior,
     required super.gestureRecognizers,
-  }) : assert(controller != null),
-       assert(hitTestBehavior != null),
-       assert(gestureRecognizers != null);
+  });
 
   @override
   RenderObject createRenderObject(BuildContext context) {
diff --git a/framework/lib/src/widgets/primary_scroll_controller.dart b/framework/lib/src/widgets/primary_scroll_controller.dart
index 91a754c..825cab2 100644
--- a/framework/lib/src/widgets/primary_scroll_controller.dart
+++ b/framework/lib/src/widgets/primary_scroll_controller.dart
@@ -17,6 +17,8 @@
 
 /// Associates a [ScrollController] with a subtree.
 ///
+/// {@youtube 560 315 https://www.youtube.com/watch?v=33_0ABjFJUU}
+///
 /// When a [ScrollView] has [ScrollView.primary] set to true, the [ScrollView]
 /// uses [of] to inherit the [PrimaryScrollController] associated with its
 /// subtree.
@@ -51,7 +53,7 @@
     this.automaticallyInheritForPlatforms = _kMobilePlatforms,
     this.scrollDirection = Axis.vertical,
     required super.child,
-  }) : assert(controller != null);
+  });
 
   /// Creates a subtree without an associated [ScrollController].
   const PrimaryScrollController.none({
diff --git a/framework/lib/src/widgets/raw_keyboard_listener.dart b/framework/lib/src/widgets/raw_keyboard_listener.dart
index 9a6d189..fec0b6c 100644
--- a/framework/lib/src/widgets/raw_keyboard_listener.dart
+++ b/framework/lib/src/widgets/raw_keyboard_listener.dart
@@ -47,10 +47,7 @@
     this.includeSemantics = true,
     this.onKey,
     required this.child,
-  }) : assert(focusNode != null),
-       assert(autofocus != null),
-       assert(includeSemantics != null),
-       assert(child != null);
+  });
 
   /// Controls whether this widget has keyboard focus.
   final FocusNode focusNode;
diff --git a/framework/lib/src/widgets/reorderable_list.dart b/framework/lib/src/widgets/reorderable_list.dart
index f3ace70..9cc6dcf 100644
--- a/framework/lib/src/widgets/reorderable_list.dart
+++ b/framework/lib/src/widgets/reorderable_list.dart
@@ -268,7 +268,6 @@
   ///  * [maybeOf], a similar function that will return null if no
   ///    [ReorderableList] ancestor is found.
   static ReorderableListState of(BuildContext context) {
-    assert(context != null);
     final ReorderableListState? result = context.findAncestorStateOfType<ReorderableListState>();
     assert(() {
       if (result == null) {
@@ -307,7 +306,6 @@
   ///  * [of], a similar function that will throw if no [ReorderableList] ancestor
   ///    is found.
   static ReorderableListState? maybeOf(BuildContext context) {
-    assert(context != null);
     return context.findAncestorStateOfType<ReorderableListState>();
   }
 
@@ -496,7 +494,6 @@
   ///  * [maybeOf], a similar function that will return null if no
   ///    [SliverReorderableList] ancestor is found.
   static SliverReorderableListState of(BuildContext context) {
-    assert(context != null);
     final SliverReorderableListState? result = context.findAncestorStateOfType<SliverReorderableListState>();
     assert(() {
       if (result == null) {
@@ -537,7 +534,6 @@
   ///  * [of], a similar function that will throw if no [SliverReorderableList]
   ///    ancestor is found.
   static SliverReorderableListState? maybeOf(BuildContext context) {
-    assert(context != null);
     return context.findAncestorStateOfType<SliverReorderableListState>();
   }
 }
@@ -1163,7 +1159,7 @@
   }
 
   void _startDragging(BuildContext context, PointerDownEvent event) {
-    final DeviceGestureSettings? gestureSettings = MediaQuery.maybeOf(context)?.gestureSettings;
+    final DeviceGestureSettings? gestureSettings = MediaQuery.maybeGestureSettingsOf(context);
     final SliverReorderableListState? list = SliverReorderableList.maybeOf(context);
     list?.startItemDragReorder(
       index: index,
diff --git a/framework/lib/src/widgets/restoration.dart b/framework/lib/src/widgets/restoration.dart
index 56f2a2c..67f3714 100644
--- a/framework/lib/src/widgets/restoration.dart
+++ b/framework/lib/src/widgets/restoration.dart
@@ -60,7 +60,7 @@
     super.key,
     required this.restorationId,
     required this.child,
-  }) : assert(child != null);
+  });
 
   /// Returns the [RestorationBucket] inserted into the widget tree by the
   /// closest ancestor [RestorationScope] of `context`.
@@ -194,7 +194,7 @@
     super.key,
     this.bucket,
     required super.child,
-  }) : assert(child != null);
+  });
 
   /// The [RestorationBucket] that this widget will insert into the widget tree.
   ///
@@ -268,7 +268,7 @@
     super.key,
     required this.restorationId,
     required this.child,
-  }) : assert(child != null);
+  });
 
   /// The widget below this widget in the tree.
   ///
@@ -529,8 +529,6 @@
   RestorationMixin? _owner;
   void _register(String restorationId, RestorationMixin owner) {
     assert(ChangeNotifier.debugAssertNotDisposed(this));
-    assert(restorationId != null);
-    assert(owner != null);
     _restorationId = restorationId;
     _owner = owner;
   }
@@ -768,8 +766,6 @@
   /// unless it has been unregistered with [unregisterFromRestoration].
   @protected
   void registerForRestoration(RestorableProperty<Object?> property, String restorationId) {
-    assert(property != null);
-    assert(restorationId != null);
     assert(property._restorationId == null || (_debugDoingRestore && property._restorationId == restorationId),
            'Property is already registered under ${property._restorationId}.',
     );
@@ -822,7 +818,6 @@
   /// restoration data by calling this method.
   @protected
   void unregisterFromRestoration(RestorableProperty<Object?> property) {
-    assert(property != null);
     assert(property._owner == this);
     _bucket?.remove<Object?>(property._restorationId!);
     _unregister(property);
@@ -949,10 +944,8 @@
       return didReplace;
     }
     assert(restorationId != null);
-    assert(parent != null);
     if (restorePending || _bucket == null) {
       final RestorationBucket newBucket = parent.claimChild(restorationId!, debugOwner: this);
-      assert(newBucket != null);
       final bool didReplace = _setNewBucketIfNecessary(newBucket: newBucket, restorePending: restorePending);
       assert(_bucket == newBucket);
       return didReplace;
diff --git a/framework/lib/src/widgets/restoration_properties.dart b/framework/lib/src/widgets/restoration_properties.dart
index c925654..9f3731d 100644
--- a/framework/lib/src/widgets/restoration_properties.dart
+++ b/framework/lib/src/widgets/restoration_properties.dart
@@ -136,12 +136,10 @@
 // See [_RestorablePrimitiveValueN] for the nullable version of this class.
 class _RestorablePrimitiveValue<T extends Object> extends _RestorablePrimitiveValueN<T> {
   _RestorablePrimitiveValue(super.defaultValue)
-    : assert(defaultValue != null),
-      assert(debugIsSerializableForRestoration(defaultValue));
+    : assert(debugIsSerializableForRestoration(defaultValue));
 
   @override
   set value(T value) {
-    assert(value != null);
     super.value = value;
   }
 
@@ -153,7 +151,6 @@
 
   @override
   Object toPrimitives() {
-    assert(value != null);
     return super.toPrimitives()!;
   }
 }
@@ -183,7 +180,7 @@
   /// If no restoration data is available to restore the value in this property
   /// from, the property will be initialized with the provided `defaultValue`.
   /// {@endtemplate}
-  RestorableNum(super.defaultValue) : assert(defaultValue != null);
+  RestorableNum(super.defaultValue);
 }
 
 /// A [RestorableProperty] that knows how to store and restore a [double].
@@ -197,7 +194,7 @@
   /// Creates a [RestorableDouble].
   ///
   /// {@macro flutter.widgets.RestorableNum.constructor}
-  RestorableDouble(super.defaultValue) : assert(defaultValue != null);
+  RestorableDouble(super.defaultValue);
 }
 
 /// A [RestorableProperty] that knows how to store and restore an [int].
@@ -211,7 +208,7 @@
   /// Creates a [RestorableInt].
   ///
   /// {@macro flutter.widgets.RestorableNum.constructor}
-  RestorableInt(super.defaultValue) : assert(defaultValue != null);
+  RestorableInt(super.defaultValue);
 }
 
 /// A [RestorableProperty] that knows how to store and restore a [String].
@@ -225,7 +222,7 @@
   /// Creates a [RestorableString].
   ///
   /// {@macro flutter.widgets.RestorableNum.constructor}
-  RestorableString(super.defaultValue) : assert(defaultValue != null);
+  RestorableString(super.defaultValue);
 }
 
 /// A [RestorableProperty] that knows how to store and restore a [bool].
@@ -239,7 +236,7 @@
   /// Creates a [RestorableBool].
   ///
   /// {@macro flutter.widgets.RestorableNum.constructor}
-  RestorableBool(super.defaultValue) : assert(defaultValue != null);
+  RestorableBool(super.defaultValue);
 }
 
 /// A [RestorableProperty] that knows how to store and restore a [bool] that is
@@ -406,7 +403,6 @@
 
   @override
   void initWithValue(T value) {
-    assert(value != null);
     _value?.removeListener(notifyListeners);
     _value = value;
     _value!.addListener(notifyListeners);
diff --git a/framework/lib/src/widgets/router.dart b/framework/lib/src/widgets/router.dart
index b3ea4f3..2a9050e 100644
--- a/framework/lib/src/widgets/router.dart
+++ b/framework/lib/src/widgets/router.dart
@@ -329,8 +329,7 @@
   }) : assert(
          routeInformationProvider == null || routeInformationParser != null,
          'A routeInformationParser must be provided when a routeInformationProvider is specified.',
-       ),
-       assert(routerDelegate != null);
+       );
 
   /// Creates a router with a [RouterConfig].
   ///
@@ -620,7 +619,6 @@
     RouteInformationReportingType status,
     VoidCallback fn,
   ) {
-    assert(status != null);
     assert(status.index >= RouteInformationReportingType.neglect.index);
     assert(() {
       if (_currentIntentionToReport != null &&
@@ -721,7 +719,6 @@
   }
 
   void _handleRouteInformationProviderNotification() {
-    assert(widget.routeInformationProvider!.value != null);
     _routeParsePending = true;
     _processRouteInformation(widget.routeInformationProvider!.value, () => widget.routerDelegate.setNewRoutePath);
   }
@@ -786,9 +783,7 @@
     required this.routerDelegate,
     required this.routerState,
     required super.child,
-  })  : assert(routeInformationProvider == null || routeInformationParser != null),
-        assert(routerDelegate != null),
-        assert(routerState != null);
+  })  : assert(routeInformationProvider == null || routeInformationParser != null);
 
   final ValueListenable<RouteInformation?>? routeInformationProvider;
   final BackButtonDispatcher? backButtonDispatcher;
@@ -1050,7 +1045,7 @@
   /// Creates a back button dispatcher that acts as the child of another.
   ///
   /// The [parent] must not be null.
-  ChildBackButtonDispatcher(this.parent) : assert(parent != null);
+  ChildBackButtonDispatcher(this.parent);
 
   /// The back button dispatcher that this object will attempt to take priority
   /// over when [takePriority] is called.
diff --git a/framework/lib/src/widgets/routes.dart b/framework/lib/src/widgets/routes.dart
index ab94622..c63403b 100644
--- a/framework/lib/src/widgets/routes.dart
+++ b/framework/lib/src/widgets/routes.dart
@@ -204,7 +204,7 @@
     assert(!_transitionCompleter.isCompleted, 'Cannot reuse a $runtimeType after disposing it.');
     final Duration duration = transitionDuration;
     final Duration reverseDuration = reverseTransitionDuration;
-    assert(duration != null && duration >= Duration.zero);
+    assert(duration >= Duration.zero);
     return AnimationController(
       duration: duration,
       reverseDuration: reverseDuration,
@@ -692,7 +692,6 @@
   /// The entry's [LocalHistoryEntry.onRemove] callback, if any, will be called
   /// synchronously.
   void removeLocalHistoryEntry(LocalHistoryEntry entry) {
-    assert(entry != null);
     assert(entry._owner == this);
     assert(_localHistory!.contains(entry));
     bool internalStateChanged = false;
@@ -775,10 +774,7 @@
     required this.impliesAppBarDismissal,
     required this.route,
     required super.child,
-  }) : assert(isCurrent != null),
-       assert(canPop != null),
-       assert(route != null),
-       assert(child != null);
+  });
 
   final bool isCurrent;
   final bool canPop;
@@ -1004,9 +1000,10 @@
   /// [BackdropFilter]. This allows blur effects, for example.
   final ui.ImageFilter? filter;
 
-  /// {@macro flutter.focus.TraversalEdgeBehavior}
+  /// Controls the transfer of focus beyond the first and the last items of a
+  /// [FocusScopeNode].
   ///
-  /// If set to null, [MediaQueryData.routeTraversalEdgeBehavior] is used.
+  /// If set to null, [Navigator.routeTraversalEdgeBehavior] is used.
   final TraversalEdgeBehavior? traversalEdgeBehavior;
 
   // The API for general users of this class
@@ -1681,6 +1678,37 @@
   // one of the builders
   late OverlayEntry _modalBarrier;
   Widget _buildModalBarrier(BuildContext context) {
+    Widget barrier = buildModalBarrier();
+    if (filter != null) {
+      barrier = BackdropFilter(
+        filter: filter!,
+        child: barrier,
+      );
+    }
+    barrier = IgnorePointer(
+      ignoring: animation!.status == AnimationStatus.reverse || // changedInternalState is called when animation.status updates
+                animation!.status == AnimationStatus.dismissed, // dismissed is possible when doing a manual pop gesture
+      child: barrier,
+    );
+    if (semanticsDismissible && barrierDismissible) {
+      // To be sorted after the _modalScope.
+      barrier = Semantics(
+        sortKey: const OrdinalSortKey(1.0),
+        child: barrier,
+      );
+    }
+    return barrier;
+  }
+
+  /// Build the barrier for this [ModalRoute], subclasses can override
+  /// this method to create their own barrier with customized features such as
+  /// color or accessibility focus size.
+  ///
+  /// See also:
+  /// * [ModalBarrier], which is typically used to build a barrier.
+  /// * [ModalBottomSheetRoute], which overrides this method to build a
+  ///   customized barrier.
+  Widget buildModalBarrier() {
     Widget barrier;
     if (barrierColor != null && barrierColor!.alpha != 0 && !offstage) { // changedInternalState is called if barrierColor or offstage updates
       assert(barrierColor != barrierColor!.withOpacity(0.0));
@@ -1703,24 +1731,7 @@
         barrierSemanticsDismissible: semanticsDismissible,
       );
     }
-    if (filter != null) {
-      barrier = BackdropFilter(
-        filter: filter!,
-        child: barrier,
-      );
-    }
-    barrier = IgnorePointer(
-      ignoring: animation!.status == AnimationStatus.reverse || // changedInternalState is called when animation.status updates
-                animation!.status == AnimationStatus.dismissed, // dismissed is possible when doing a manual pop gesture
-      child: barrier,
-    );
-    if (semanticsDismissible && barrierDismissible) {
-      // To be sorted after the _modalScope.
-      barrier = Semantics(
-        sortKey: const OrdinalSortKey(1.0),
-        child: barrier,
-      );
-    }
+
     return barrier;
   }
 
@@ -1886,8 +1897,6 @@
   /// to [route], e.g. when [route] is covered by another route or when [route]
   /// is popped off the [Navigator] stack.
   void subscribe(RouteAware routeAware, R route) {
-    assert(routeAware != null);
-    assert(route != null);
     final Set<RouteAware> subscribers = _listeners.putIfAbsent(route, () => <RouteAware>{});
     if (subscribers.add(routeAware)) {
       routeAware.didPush();
@@ -1899,7 +1908,6 @@
   /// [routeAware] is no longer informed about changes to its route. If the given argument was
   /// subscribed to multiple types, this will unregister it (once) from each type.
   void unsubscribe(RouteAware routeAware) {
-    assert(routeAware != null);
     final List<R> routes = _listeners.keys.toList();
     for (final R route in routes) {
       final Set<RouteAware>? subscribers = _listeners[route];
@@ -2023,8 +2031,7 @@
     super.settings,
     this.anchorPoint,
     super.traversalEdgeBehavior,
-  }) : assert(barrierDismissible != null),
-       _pageBuilder = pageBuilder,
+  }) : _pageBuilder = pageBuilder,
        _barrierDismissible = barrierDismissible,
        _barrierLabel = barrierLabel,
        _barrierColor = barrierColor,
@@ -2171,8 +2178,6 @@
   RouteSettings? routeSettings,
   Offset? anchorPoint,
 }) {
-  assert(pageBuilder != null);
-  assert(useRootNavigator != null);
   assert(!barrierDismissible || barrierLabel != null);
   return Navigator.of(context, rootNavigator: useRootNavigator).push<T>(RawDialogRoute<T>(
     pageBuilder: pageBuilder,
diff --git a/framework/lib/src/widgets/safe_area.dart b/framework/lib/src/widgets/safe_area.dart
index f4a82da..cb384f3 100644
--- a/framework/lib/src/widgets/safe_area.dart
+++ b/framework/lib/src/widgets/safe_area.dart
@@ -47,10 +47,7 @@
     this.minimum = EdgeInsets.zero,
     this.maintainBottomViewPadding = false,
     required this.child,
-  }) : assert(left != null),
-       assert(top != null),
-       assert(right != null),
-       assert(bottom != null);
+  });
 
   /// Whether to avoid system intrusions on the left.
   final bool left;
@@ -93,11 +90,10 @@
   @override
   Widget build(BuildContext context) {
     assert(debugCheckHasMediaQuery(context));
-    final MediaQueryData data = MediaQuery.of(context);
-    EdgeInsets padding = data.padding;
+    EdgeInsets padding = MediaQuery.paddingOf(context);
     // Bottom padding has been consumed - i.e. by the keyboard
     if (maintainBottomViewPadding) {
-      padding = padding.copyWith(bottom: data.viewPadding.bottom);
+      padding = padding.copyWith(bottom: MediaQuery.viewPaddingOf(context).bottom);
     }
 
     return Padding(
@@ -160,10 +156,7 @@
     this.bottom = true,
     this.minimum = EdgeInsets.zero,
     required this.sliver,
-  }) : assert(left != null),
-       assert(top != null),
-       assert(right != null),
-       assert(bottom != null);
+  });
 
   /// Whether to avoid system intrusions on the left.
   final bool left;
@@ -192,7 +185,7 @@
   @override
   Widget build(BuildContext context) {
     assert(debugCheckHasMediaQuery(context));
-    final EdgeInsets padding = MediaQuery.of(context).padding;
+    final EdgeInsets padding = MediaQuery.paddingOf(context);
     return SliverPadding(
       padding: EdgeInsets.only(
         left: math.max(left ? padding.left : 0.0, minimum.left),
diff --git a/framework/lib/src/widgets/scroll_activity.dart b/framework/lib/src/widgets/scroll_activity.dart
index 2e41dd1..f555a35 100644
--- a/framework/lib/src/widgets/scroll_activity.dart
+++ b/framework/lib/src/widgets/scroll_activity.dart
@@ -234,9 +234,7 @@
     this.onDragCanceled,
     this.carriedVelocity,
     this.motionStartDistanceThreshold,
-  }) : assert(delegate != null),
-       assert(details != null),
-       assert(
+  }) : assert(
          motionStartDistanceThreshold == null || motionStartDistanceThreshold > 0.0,
          'motionStartDistanceThreshold must be a positive number or null',
        ),
@@ -621,11 +619,7 @@
     required Duration duration,
     required Curve curve,
     required TickerProvider vsync,
-  }) : assert(from != null),
-       assert(to != null),
-       assert(duration != null),
-       assert(duration > Duration.zero),
-       assert(curve != null) {
+  }) : assert(duration > Duration.zero) {
     _completer = Completer<void>();
     _controller = AnimationController.unbounded(
       value: from,
diff --git a/framework/lib/src/widgets/scroll_aware_image_provider.dart b/framework/lib/src/widgets/scroll_aware_image_provider.dart
index 4524705..958cb1a 100644
--- a/framework/lib/src/widgets/scroll_aware_image_provider.dart
+++ b/framework/lib/src/widgets/scroll_aware_image_provider.dart
@@ -54,8 +54,7 @@
   const ScrollAwareImageProvider({
     required this.context,
     required this.imageProvider,
-  }) : assert(context != null),
-       assert(imageProvider != null);
+  });
 
   /// The context that may or may not be enclosed by a [Scrollable].
   ///
@@ -113,5 +112,8 @@
   ImageStreamCompleter loadBuffer(T key, DecoderBufferCallback decode) => imageProvider.loadBuffer(key, decode);
 
   @override
+  ImageStreamCompleter loadImage(T key, ImageDecoderCallback decode) => imageProvider.loadImage(key, decode);
+
+  @override
   Future<T> obtainKey(ImageConfiguration configuration) => imageProvider.obtainKey(configuration);
 }
diff --git a/framework/lib/src/widgets/scroll_configuration.dart b/framework/lib/src/widgets/scroll_configuration.dart
index 5b2b6b4..b7aca90 100644
--- a/framework/lib/src/widgets/scroll_configuration.dart
+++ b/framework/lib/src/widgets/scroll_configuration.dart
@@ -5,6 +5,7 @@
 import 'package:flute/foundation.dart';
 import 'package:flute/gestures.dart';
 import 'package:flute/rendering.dart';
+import 'package:flute/services.dart' show LogicalKeyboardKey;
 
 import 'framework.dart';
 import 'overscroll_indicator.dart';
@@ -100,6 +101,7 @@
     bool? scrollbars,
     bool? overscroll,
     Set<PointerDeviceKind>? dragDevices,
+    Set<LogicalKeyboardKey>? pointerAxisModifiers,
     ScrollPhysics? physics,
     TargetPlatform? platform,
     @Deprecated(
@@ -112,9 +114,10 @@
       delegate: this,
       scrollbars: scrollbars ?? true,
       overscroll: overscroll ?? true,
+      dragDevices: dragDevices,
+      pointerAxisModifiers: pointerAxisModifiers,
       physics: physics,
       platform: platform,
-      dragDevices: dragDevices,
       androidOverscrollIndicator: androidOverscrollIndicator
     );
   }
@@ -132,6 +135,25 @@
   /// impossible to select text in scrollable containers and is not recommended.
   Set<PointerDeviceKind> get dragDevices => _kTouchLikeDeviceTypes;
 
+  /// A set of [LogicalKeyboardKey]s that, when any or all are pressed in
+  /// combination with a [PointerDeviceKind.mouse] pointer scroll event, will
+  /// flip the axes of the scroll input.
+  ///
+  /// This will for example, result in the input of a vertical mouse wheel, to
+  /// move the [ScrollPosition] of a [ScrollView] with an [Axis.horizontal]
+  /// scroll direction.
+  ///
+  /// If other keys exclusive of this set are pressed during a scroll event, in
+  /// conjunction with keys from this set, the scroll input will still be
+  /// flipped.
+  ///
+  /// Defaults to [LogicalKeyboardKey.shiftLeft],
+  /// [LogicalKeyboardKey.shiftRight].
+  Set<LogicalKeyboardKey> get pointerAxisModifiers => <LogicalKeyboardKey>{
+    LogicalKeyboardKey.shiftLeft,
+    LogicalKeyboardKey.shiftRight,
+  };
+
   /// Applies a [RawScrollbar] to the child widget on desktop platforms.
   Widget buildScrollbar(BuildContext context, Widget child, ScrollableDetails details) {
     // When modifying this function, consider modifying the implementation in
@@ -261,12 +283,14 @@
     required this.delegate,
     this.scrollbars = true,
     this.overscroll = true,
+    Set<PointerDeviceKind>? dragDevices,
+    Set<LogicalKeyboardKey>? pointerAxisModifiers,
     this.physics,
     this.platform,
-    Set<PointerDeviceKind>? dragDevices,
     AndroidOverscrollIndicator? androidOverscrollIndicator,
   }) : _androidOverscrollIndicator = androidOverscrollIndicator,
-       _dragDevices = dragDevices;
+       _dragDevices = dragDevices,
+       _pointerAxisModifiers = pointerAxisModifiers;
 
   final ScrollBehavior delegate;
   final bool scrollbars;
@@ -274,6 +298,7 @@
   final ScrollPhysics? physics;
   final TargetPlatform? platform;
   final Set<PointerDeviceKind>? _dragDevices;
+  final Set<LogicalKeyboardKey>? _pointerAxisModifiers;
   @override
   final AndroidOverscrollIndicator? _androidOverscrollIndicator;
 
@@ -281,6 +306,9 @@
   Set<PointerDeviceKind> get dragDevices => _dragDevices ?? delegate.dragDevices;
 
   @override
+  Set<LogicalKeyboardKey> get pointerAxisModifiers => _pointerAxisModifiers ?? delegate.pointerAxisModifiers;
+
+  @override
   AndroidOverscrollIndicator get androidOverscrollIndicator => _androidOverscrollIndicator ?? delegate.androidOverscrollIndicator;
 
   @override
@@ -303,17 +331,19 @@
   ScrollBehavior copyWith({
     bool? scrollbars,
     bool? overscroll,
+    Set<PointerDeviceKind>? dragDevices,
+    Set<LogicalKeyboardKey>? pointerAxisModifiers,
     ScrollPhysics? physics,
     TargetPlatform? platform,
-    Set<PointerDeviceKind>? dragDevices,
     AndroidOverscrollIndicator? androidOverscrollIndicator
   }) {
     return delegate.copyWith(
       scrollbars: scrollbars ?? this.scrollbars,
       overscroll: overscroll ?? this.overscroll,
+      dragDevices: dragDevices ?? this.dragDevices,
+      pointerAxisModifiers: pointerAxisModifiers ?? this.pointerAxisModifiers,
       physics: physics ?? this.physics,
       platform: platform ?? this.platform,
-      dragDevices: dragDevices ?? this.dragDevices,
       androidOverscrollIndicator: androidOverscrollIndicator ?? this.androidOverscrollIndicator,
     );
   }
@@ -333,9 +363,10 @@
     return oldDelegate.delegate.runtimeType != delegate.runtimeType
         || oldDelegate.scrollbars != scrollbars
         || oldDelegate.overscroll != overscroll
+        || !setEquals<PointerDeviceKind>(oldDelegate.dragDevices, dragDevices)
+        || !setEquals<LogicalKeyboardKey>(oldDelegate.pointerAxisModifiers, pointerAxisModifiers)
         || oldDelegate.physics != physics
         || oldDelegate.platform != platform
-        || !setEquals<PointerDeviceKind>(oldDelegate.dragDevices, dragDevices)
         || delegate.shouldNotify(oldDelegate.delegate);
   }
 
@@ -376,7 +407,6 @@
 
   @override
   bool updateShouldNotify(ScrollConfiguration oldWidget) {
-    assert(behavior != null);
     return behavior.runtimeType != oldWidget.behavior.runtimeType
         || (behavior != oldWidget.behavior && behavior.shouldNotify(oldWidget.behavior));
   }
diff --git a/framework/lib/src/widgets/scroll_controller.dart b/framework/lib/src/widgets/scroll_controller.dart
index 0315a28..fb7a7a3 100644
--- a/framework/lib/src/widgets/scroll_controller.dart
+++ b/framework/lib/src/widgets/scroll_controller.dart
@@ -49,9 +49,7 @@
     double initialScrollOffset = 0.0,
     this.keepScrollOffset = true,
     this.debugLabel,
-  }) : assert(initialScrollOffset != null),
-       assert(keepScrollOffset != null),
-       _initialScrollOffset = initialScrollOffset;
+  }) : _initialScrollOffset = initialScrollOffset;
 
   /// The initial value to use for [offset].
   ///
diff --git a/framework/lib/src/widgets/scroll_metrics.dart b/framework/lib/src/widgets/scroll_metrics.dart
index ef02b85..e637e0f 100644
--- a/framework/lib/src/widgets/scroll_metrics.dart
+++ b/framework/lib/src/widgets/scroll_metrics.dart
@@ -46,6 +46,7 @@
     double? pixels,
     double? viewportDimension,
     AxisDirection? axisDirection,
+    double? devicePixelRatio,
   }) {
     return FixedScrollMetrics(
       minScrollExtent: minScrollExtent ?? (hasContentDimensions ? this.minScrollExtent : null),
@@ -53,6 +54,7 @@
       pixels: pixels ?? (hasPixels ? this.pixels : null),
       viewportDimension: viewportDimension ?? (hasViewportDimension ? this.viewportDimension : null),
       axisDirection: axisDirection ?? this.axisDirection,
+      devicePixelRatio: devicePixelRatio ?? this.devicePixelRatio,
     );
   }
 
@@ -124,6 +126,10 @@
   /// The quantity of content conceptually "below" the viewport in the scrollable.
   /// This is the content below the content described by [extentInside].
   double get extentAfter => math.max(maxScrollExtent - pixels, 0.0);
+
+  /// The [FlutterView.devicePixelRatio] of the view that the [Scrollable]
+  /// associated with this metrics object is drawn into.
+  double get devicePixelRatio;
 }
 
 /// An immutable snapshot of values associated with a [Scrollable] viewport.
@@ -137,6 +143,7 @@
     required double? pixels,
     required double? viewportDimension,
     required this.axisDirection,
+    required this.devicePixelRatio,
   }) : _minScrollExtent = minScrollExtent,
        _maxScrollExtent = maxScrollExtent,
        _pixels = pixels,
@@ -171,6 +178,9 @@
   final AxisDirection axisDirection;
 
   @override
+  final double devicePixelRatio;
+
+  @override
   String toString() {
     return '${objectRuntimeType(this, 'FixedScrollMetrics')}(${extentBefore.toStringAsFixed(1)}..[${extentInside.toStringAsFixed(1)}]..${extentAfter.toStringAsFixed(1)})';
   }
diff --git a/framework/lib/src/widgets/scroll_notification.dart b/framework/lib/src/widgets/scroll_notification.dart
index 1df53a6..3abe9e7 100644
--- a/framework/lib/src/widgets/scroll_notification.dart
+++ b/framework/lib/src/widgets/scroll_notification.dart
@@ -198,10 +198,8 @@
     this.dragDetails,
     required this.overscroll,
     this.velocity = 0.0,
-  }) : assert(overscroll != null),
-       assert(overscroll.isFinite),
-       assert(overscroll != 0.0),
-       assert(velocity != null);
+  }) : assert(overscroll.isFinite),
+       assert(overscroll != 0.0);
 
   /// If the [Scrollable] overscrolled because of a drag, the details about that
   /// drag update.
diff --git a/framework/lib/src/widgets/scroll_notification_observer.dart b/framework/lib/src/widgets/scroll_notification_observer.dart
index 4f49736..4b46042 100644
--- a/framework/lib/src/widgets/scroll_notification_observer.dart
+++ b/framework/lib/src/widgets/scroll_notification_observer.dart
@@ -78,7 +78,7 @@
   const ScrollNotificationObserver({
     super.key,
     required this.child,
-  }) : assert(child != null);
+  });
 
   /// The subtree below this widget.
   final Widget child;
diff --git a/framework/lib/src/widgets/scroll_physics.dart b/framework/lib/src/widgets/scroll_physics.dart
index c2425cc..350a298 100644
--- a/framework/lib/src/widgets/scroll_physics.dart
+++ b/framework/lib/src/widgets/scroll_physics.dart
@@ -6,6 +6,7 @@
 
 import 'package:flute/foundation.dart';
 import 'package:flute/gestures.dart';
+import 'package:flute/painting.dart' show AxisDirection;
 import 'package:flute/physics.dart';
 
 import 'binding.dart' show WidgetsBinding;
@@ -13,6 +14,7 @@
 import 'overscroll_indicator.dart';
 import 'scroll_metrics.dart';
 import 'scroll_simulation.dart';
+import 'view.dart';
 
 export 'package:flute/physics.dart' show ScrollSpringSimulation, Simulation, Tolerance;
 
@@ -236,8 +238,8 @@
   /// the overall scale between the global screen and local scrollable
   /// coordinate systems.
   ///
-  /// The default implementation is stateless, and simply provides a point-in-
-  /// time decision about how fast the scrollable is scrolling. It would always
+  /// The default implementation is stateless, and provides a point-in-time
+  /// decision about how fast the scrollable is scrolling. It would always
   /// return true for a scrollable that is animating back and forth at high
   /// velocity in a loop. It is assumed that callers will handle such
   /// a case, or that a custom stateful implementation would be written that
@@ -247,11 +249,8 @@
   /// is great enough that expensive operations impacting the UI should be
   /// deferred.
   bool recommendDeferredLoading(double velocity, ScrollMetrics metrics, BuildContext context) {
-    assert(velocity != null);
-    assert(metrics != null);
-    assert(context != null);
     if (parent == null) {
-      final double maxPhysicalPixels = WidgetsBinding.instance.window.physicalSize.longestSide;
+      final double maxPhysicalPixels = View.of(context).physicalSize.longestSide;
       return velocity.abs() > maxPhysicalPixels;
     }
     return parent!.recommendDeferredLoading(velocity, metrics, context);
@@ -382,19 +381,32 @@
   /// The spring to use for ballistic simulations.
   SpringDescription get spring => parent?.spring ?? _kDefaultSpring;
 
-  /// The default accuracy to which scrolling is computed.
-  static final Tolerance _kDefaultTolerance = Tolerance(
-    // TODO(ianh): Handle the case of the device pixel ratio changing.
-    // TODO(ianh): Get this from the local MediaQuery not dart:ui's window object.
-    velocity: 1.0 / (0.050 * WidgetsBinding.instance.window.devicePixelRatio), // logical pixels per second
-    distance: 1.0 / WidgetsBinding.instance.window.devicePixelRatio, // logical pixels
-  );
+  /// Deprecated. Call [toleranceFor] instead.
+  @Deprecated(
+    'Call toleranceFor instead. '
+    'This feature was deprecated after v3.7.0-13.0.pre.',
+  )
+  Tolerance get tolerance {
+    return toleranceFor(FixedScrollMetrics(
+      minScrollExtent: null,
+      maxScrollExtent: null,
+      pixels: null,
+      viewportDimension: null,
+      axisDirection: AxisDirection.down,
+      devicePixelRatio: WidgetsBinding.instance.window.devicePixelRatio,
+    ));
+  }
 
   /// The tolerance to use for ballistic simulations.
-  Tolerance get tolerance => parent?.tolerance ?? _kDefaultTolerance;
+  Tolerance toleranceFor(ScrollMetrics metrics) {
+    return parent?.toleranceFor(metrics) ?? Tolerance(
+      velocity: 1.0 / (0.050 * metrics.devicePixelRatio), // logical pixels per second
+      distance: 1.0 / metrics.devicePixelRatio, // logical pixels
+    );
+  }
 
-  /// The minimum distance an input pointer drag must have moved to
-  /// to be considered a scroll fling gesture.
+  /// The minimum distance an input pointer drag must have moved to be
+  /// considered a scroll fling gesture.
   ///
   /// This value is typically compared with the distance traveled along the
   /// scrolling axis.
@@ -695,7 +707,7 @@
 
   @override
   Simulation? createBallisticSimulation(ScrollMetrics position, double velocity) {
-    final Tolerance tolerance = this.tolerance;
+    final Tolerance tolerance = toleranceFor(position);
     if (velocity.abs() >= tolerance.velocity || position.outOfRange) {
       double constantDeceleration;
       switch (decelerationRate) {
@@ -839,7 +851,7 @@
 
   @override
   Simulation? createBallisticSimulation(ScrollMetrics position, double velocity) {
-    final Tolerance tolerance = this.tolerance;
+    final Tolerance tolerance = toleranceFor(position);
     if (position.outOfRange) {
       double? end;
       if (position.pixels > position.maxScrollExtent) {
diff --git a/framework/lib/src/widgets/scroll_position.dart b/framework/lib/src/widgets/scroll_position.dart
index 1982720..99420ae 100644
--- a/framework/lib/src/widgets/scroll_position.dart
+++ b/framework/lib/src/widgets/scroll_position.dart
@@ -12,6 +12,7 @@
 
 import 'basic.dart';
 import 'framework.dart';
+import 'media_query.dart';
 import 'notification_listener.dart';
 import 'page_storage.dart';
 import 'scroll_activity.dart';
@@ -19,6 +20,7 @@
 import 'scroll_metrics.dart';
 import 'scroll_notification.dart';
 import 'scroll_physics.dart';
+import 'view.dart';
 
 export 'scroll_activity.dart' show ScrollHoldController;
 
@@ -98,10 +100,7 @@
     this.keepScrollOffset = true,
     ScrollPosition? oldPosition,
     this.debugLabel,
-  }) : assert(physics != null),
-       assert(context != null),
-       assert(context.vsync != null),
-       assert(keepScrollOffset != null) {
+  }) {
     if (oldPosition != null) {
       absorb(oldPosition);
     }
@@ -217,7 +216,6 @@
   @protected
   @mustCallSuper
   void absorb(ScrollPosition other) {
-    assert(other != null);
     assert(other.context == context);
     assert(_pixels == null);
     if (other.hasContentDimensions) {
@@ -242,6 +240,9 @@
     isScrollingNotifier.value = activity!.isScrolling;
   }
 
+  @override
+  double get devicePixelRatio => MediaQuery.maybeDevicePixelRatioOf(context.storageContext) ?? View.of(context.storageContext).devicePixelRatio;
+
   /// Update the scroll position ([pixels]) to a given pixel value.
   ///
   /// This should only be called by the current [ScrollActivity], either during
@@ -378,7 +379,6 @@
   @protected
   void forcePixels(double value) {
     assert(hasPixels);
-    assert(value != null);
     _impliedVelocity = value - pixels;
     _pixels = value;
     notifyListeners();
@@ -445,8 +445,6 @@
   /// The method may be called multiple times in the lifecycle of a
   /// [ScrollPosition] to restore it to different scroll offsets.
   void restoreOffset(double offset, {bool initialRestore = false}) {
-    assert(initialRestore != null);
-    assert(offset != null);
     if (initialRestore) {
       correctPixels(offset);
     } else {
@@ -531,15 +529,11 @@
 
   @override
   bool applyContentDimensions(double minScrollExtent, double maxScrollExtent) {
-    assert(minScrollExtent != null);
-    assert(maxScrollExtent != null);
     assert(haveDimensions == (_lastMetrics != null));
     if (!nearEqual(_minScrollExtent, minScrollExtent, Tolerance.defaultTolerance.distance) ||
         !nearEqual(_maxScrollExtent, maxScrollExtent, Tolerance.defaultTolerance.distance) ||
         _didChangeViewportDimensionOrReceiveCorrection ||
         _lastAxis != axis) {
-      assert(minScrollExtent != null);
-      assert(maxScrollExtent != null);
       assert(minScrollExtent <= maxScrollExtent);
       _minScrollExtent = minScrollExtent;
       _maxScrollExtent = maxScrollExtent;
@@ -701,10 +695,8 @@
     ScrollPositionAlignmentPolicy alignmentPolicy = ScrollPositionAlignmentPolicy.explicit,
     RenderObject? targetRenderObject,
   }) {
-    assert(alignmentPolicy != null);
     assert(object.attached);
     final RenderAbstractViewport viewport = RenderAbstractViewport.of(object);
-    assert(viewport != null);
 
     Rect? targetRect;
     if (targetRenderObject != null && targetRenderObject != object) {
@@ -826,7 +818,6 @@
     Curve? curve,
     bool? clamp = true,
   }) {
-    assert(to != null);
     assert(clamp != null);
 
     if (clamp!) {
@@ -959,10 +950,7 @@
   /// evaluate the current scroll velocity to be great enough that expensive
   /// operations impacting the UI should be deferred.
   bool recommendDeferredLoading(BuildContext context) {
-    assert(context != null);
     assert(activity != null);
-    assert(activity!.velocity != null);
-    assert(_impliedVelocity != null);
     return physics.recommendDeferredLoading(
       activity!.velocity + _impliedVelocity,
       copyWith(),
diff --git a/framework/lib/src/widgets/scroll_position_with_single_context.dart b/framework/lib/src/widgets/scroll_position_with_single_context.dart
index 66d6af0..44a6e27 100644
--- a/framework/lib/src/widgets/scroll_position_with_single_context.dart
+++ b/framework/lib/src/widgets/scroll_position_with_single_context.dart
@@ -162,7 +162,6 @@
   @protected
   @visibleForTesting
   void updateUserScrollDirection(ScrollDirection value) {
-    assert(value != null);
     if (userScrollDirection == value) {
       return;
     }
@@ -176,7 +175,7 @@
     required Duration duration,
     required Curve curve,
   }) {
-    if (nearEqual(to, pixels, physics.tolerance.distance)) {
+    if (nearEqual(to, pixels, physics.toleranceFor(this).distance)) {
       // Skip the animation, go straight to the position as we are already close.
       jumpTo(to);
       return Future<void>.value();
@@ -212,7 +211,10 @@
     // If an update is made to pointer scrolling here, consider if the same
     // (or similar) change should be made in
     // _NestedScrollCoordinator.pointerScroll.
-    assert(delta != 0.0);
+    if (delta == 0.0) {
+      goBallistic(0.0);
+      return;
+    }
 
     final double targetPixels =
         math.min(math.max(pixels + delta, minScrollExtent), maxScrollExtent);
diff --git a/framework/lib/src/widgets/scroll_simulation.dart b/framework/lib/src/widgets/scroll_simulation.dart
index c1d3290..5e384c4 100644
--- a/framework/lib/src/widgets/scroll_simulation.dart
+++ b/framework/lib/src/widgets/scroll_simulation.dart
@@ -36,12 +36,7 @@
     required this.spring,
     double constantDeceleration = 0,
     super.tolerance,
-  }) : assert(position != null),
-       assert(velocity != null),
-       assert(leadingExtent != null),
-       assert(trailingExtent != null),
-       assert(leadingExtent <= trailingExtent),
-       assert(spring != null) {
+  }) : assert(leadingExtent <= trailingExtent) {
     if (position < leadingExtent) {
       _springSimulation = _underscrollSimulation(position, velocity);
       _springTime = double.negativeInfinity;
@@ -71,7 +66,6 @@
         _springTime = double.infinity;
       }
     }
-    assert(_springTime != null);
   }
 
   /// The maximum velocity that can be transferred from the inertia of a ballistic
diff --git a/framework/lib/src/widgets/scroll_view.dart b/framework/lib/src/widgets/scroll_view.dart
index 26cd8d8..653a04e 100644
--- a/framework/lib/src/widgets/scroll_view.dart
+++ b/framework/lib/src/widgets/scroll_view.dart
@@ -99,19 +99,13 @@
     this.keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
     this.restorationId,
     this.clipBehavior = Clip.hardEdge,
-  }) : assert(scrollDirection != null),
-       assert(reverse != null),
-       assert(shrinkWrap != null),
-       assert(dragStartBehavior != null),
-       assert(clipBehavior != null),
-       assert(
+  }) : assert(
          !(controller != null && (primary ?? false)),
          'Primary ScrollViews obtain their ScrollController via inheritance '
          'from a PrimaryScrollController widget. You cannot both set primary to '
          'true and pass an explicit controller.',
        ),
        assert(!shrinkWrap || center == null),
-       assert(anchor != null),
        assert(anchor >= 0.0 && anchor <= 1.0),
        assert(semanticChildCount == null || semanticChildCount >= 0),
        physics = physics ?? ((primary ?? false) || (primary == null && controller == null && identical(scrollDirection, Axis.vertical)) ? const AlwaysScrollableScrollPhysics() : null);
@@ -186,6 +180,12 @@
   /// [TargetPlatformVariant.mobile] for ScrollViews in the [Axis.vertical]
   /// scroll direction. Adding another to your app will override the
   /// PrimaryScrollController above it.
+  ///
+  /// The following video contains more information about scroll controllers,
+  /// the PrimaryScrollController widget, and their impact on your apps:
+  ///
+  /// {@youtube 560 315 https://www.youtube.com/watch?v=33_0ABjFJUU}
+  ///
   /// {@endtemplate}
   final bool? primary;
 
@@ -1251,34 +1251,23 @@
     super.keyboardDismissBehavior,
     super.restorationId,
     super.clipBehavior,
-  }) : assert(itemBuilder != null),
-       assert(separatorBuilder != null),
-       assert(itemCount != null && itemCount >= 0),
+  }) : assert(itemCount >= 0),
        itemExtent = null,
        prototypeItem = null,
        childrenDelegate = SliverChildBuilderDelegate(
          (BuildContext context, int index) {
            final int itemIndex = index ~/ 2;
-           final Widget? widget;
            if (index.isEven) {
-             widget = itemBuilder(context, itemIndex);
-           } else {
-             widget = separatorBuilder(context, itemIndex);
-             assert(() {
-               if (widget == null) {
-                 throw FlutterError('separatorBuilder cannot return null.');
-               }
-               return true;
-             }());
+             return itemBuilder(context, itemIndex);
            }
-           return widget;
+           return separatorBuilder(context, itemIndex);
          },
          findChildIndexCallback: findChildIndexCallback,
          childCount: _computeActualChildCount(itemCount),
          addAutomaticKeepAlives: addAutomaticKeepAlives,
          addRepaintBoundaries: addRepaintBoundaries,
          addSemanticIndexes: addSemanticIndexes,
-         semanticIndexCallback: (Widget _, int index) {
+         semanticIndexCallback: (Widget widget, int index) {
            return index.isEven ? index ~/ 2 : null;
          },
        ),
@@ -1391,8 +1380,7 @@
     super.keyboardDismissBehavior,
     super.restorationId,
     super.clipBehavior,
-  }) : assert(childrenDelegate != null),
-       assert(
+  }) : assert(
          itemExtent == null || prototypeItem == null,
          'You can only pass itemExtent or prototypeItem, not both',
        );
@@ -1728,8 +1716,7 @@
     super.clipBehavior,
     super.keyboardDismissBehavior,
     super.restorationId,
-  }) : assert(gridDelegate != null),
-       childrenDelegate = SliverChildListDelegate(
+  }) : childrenDelegate = SliverChildListDelegate(
          children,
          addAutomaticKeepAlives: addAutomaticKeepAlives,
          addRepaintBoundaries: addRepaintBoundaries,
@@ -1785,8 +1772,7 @@
     super.keyboardDismissBehavior,
     super.restorationId,
     super.clipBehavior,
-  }) : assert(gridDelegate != null),
-       childrenDelegate = SliverChildBuilderDelegate(
+  }) : childrenDelegate = SliverChildBuilderDelegate(
          itemBuilder,
          findChildIndexCallback: findChildIndexCallback,
          childCount: itemCount,
@@ -1822,8 +1808,7 @@
     super.keyboardDismissBehavior,
     super.restorationId,
     super.clipBehavior,
-  }) : assert(gridDelegate != null),
-       assert(childrenDelegate != null);
+  });
 
   /// Creates a scrollable, 2D array of widgets with a fixed number of tiles in
   /// the cross axis.
diff --git a/framework/lib/src/widgets/scrollable.dart b/framework/lib/src/widgets/scrollable.dart
index 130776a..9a3ae7c 100644
--- a/framework/lib/src/widgets/scrollable.dart
+++ b/framework/lib/src/widgets/scrollable.dart
@@ -100,11 +100,7 @@
     this.restorationId,
     this.scrollBehavior,
     this.clipBehavior = Clip.hardEdge,
-  }) : assert(axisDirection != null),
-       assert(dragStartBehavior != null),
-       assert(viewportBuilder != null),
-       assert(excludeFromSemantics != null),
-       assert(semanticChildCount == null || semanticChildCount >= 0);
+  }) : assert(semanticChildCount == null || semanticChildCount >= 0);
 
   /// The direction in which this widget scrolls.
   ///
@@ -421,8 +417,7 @@
     required this.scrollable,
     required this.position,
     required super.child,
-  }) : assert(scrollable != null),
-       assert(child != null);
+  });
 
   final ScrollableState scrollable;
   final ScrollPosition position;
@@ -461,7 +456,7 @@
   late ScrollBehavior _configuration;
   ScrollPhysics? _physics;
   ScrollController? _fallbackScrollController;
-  MediaQueryData? _mediaQueryData;
+  DeviceGestureSettings? _mediaQueryGestureSettings;
 
   ScrollController get _effectiveScrollController => widget.controller ?? _fallbackScrollController!;
 
@@ -516,7 +511,7 @@
 
   @override
   void didChangeDependencies() {
-    _mediaQueryData = MediaQuery.maybeOf(context);
+    _mediaQueryGestureSettings = MediaQuery.maybeGestureSettingsOf(context);
     _updatePosition();
     super.didChangeDependencies();
   }
@@ -635,7 +630,7 @@
                   ..maxFlingVelocity = _physics?.maxFlingVelocity
                   ..velocityTrackerBuilder = _configuration.velocityTrackerBuilder(context)
                   ..dragStartBehavior = widget.dragStartBehavior
-                  ..gestureSettings = _mediaQueryData?.gestureSettings;
+                  ..gestureSettings = _mediaQueryGestureSettings;
               },
             ),
           };
@@ -656,7 +651,7 @@
                   ..maxFlingVelocity = _physics?.maxFlingVelocity
                   ..velocityTrackerBuilder = _configuration.velocityTrackerBuilder(context)
                   ..dragStartBehavior = widget.dragStartBehavior
-                  ..gestureSettings = _mediaQueryData?.gestureSettings;
+                  ..gestureSettings = _mediaQueryGestureSettings;
               },
             ),
           };
@@ -727,6 +722,12 @@
   }
 
   void _handleDragCancel() {
+    if (_gestureDetectorKey.currentContext == null) {
+      // The cancel was caused by the GestureDetector getting disposed, which
+      // means we will get disposed momentarily as well and shouldn't do
+      // any work.
+      return;
+    }
     // _hold might be null if the drag started.
     // _drag might be null if the drag activity ended and called _disposeDrag.
     assert(_hold == null || _drag == null);
@@ -755,12 +756,32 @@
     );
   }
 
-  // Returns the delta that should result from applying [event] with axis and
-  // direction taken into account.
+  // Returns the delta that should result from applying [event] with axis,
+  // direction, and any modifiers specified by the ScrollBehavior taken into
+  // account.
   double _pointerSignalEventDelta(PointerScrollEvent event) {
-    double delta = widget.axis == Axis.horizontal
-      ? event.scrollDelta.dx
-      : event.scrollDelta.dy;
+    late double delta;
+    final Set<LogicalKeyboardKey> pressed = HardwareKeyboard.instance.logicalKeysPressed;
+    final bool flipAxes = pressed.any(_configuration.pointerAxisModifiers.contains) &&
+      // Axes are only flipped for physical mouse wheel input.
+      // On some platforms, like web, trackpad input is handled through pointer
+      // signals, but should not be included in this axis modifying behavior.
+      // This is because on a trackpad, all directional axes are available to
+      // the user, while mouse scroll wheels typically are restricted to one
+      // axis.
+      event.kind == PointerDeviceKind.mouse;
+
+    switch (widget.axis) {
+      case Axis.horizontal:
+        delta = flipAxes
+          ? event.scrollDelta.dy
+          : event.scrollDelta.dx;
+        break;
+      case Axis.vertical:
+        delta = flipAxes
+          ? event.scrollDelta.dx
+          : event.scrollDelta.dy;
+    }
 
     if (axisDirectionIsReversed(widget.axisDirection)) {
       delta *= -1;
@@ -780,7 +801,7 @@
         GestureBinding.instance.pointerSignalResolver.register(event, _handlePointerScroll);
       }
     } else if (event is PointerScrollInertiaCancelEvent) {
-      position.jumpTo(position.pixels);
+      position.pointerScroll(0);
       // Don't use the pointer signal resolver, all hit-tested scrollables should stop.
     }
   }
@@ -1546,8 +1567,7 @@
     required this.allowImplicitScrolling,
     required this.semanticChildCount,
     super.child,
-  }) : assert(position != null),
-       assert(semanticChildCount == null || semanticChildCount >= 0);
+  }) : assert(semanticChildCount == null || semanticChildCount >= 0);
 
   final ScrollPosition position;
   final bool allowImplicitScrolling;
@@ -1580,7 +1600,6 @@
   }) : _position = position,
        _allowImplicitScrolling = allowImplicitScrolling,
        _semanticChildCount = semanticChildCount,
-       assert(position != null),
        super(child) {
     position.addListener(markNeedsSemanticsUpdate);
   }
@@ -1589,7 +1608,6 @@
   ScrollPosition get position => _position;
   ScrollPosition _position;
   set position(ScrollPosition value) {
-    assert(value != null);
     if (value == _position) {
       return;
     }
@@ -1726,8 +1744,7 @@
   const ScrollIncrementDetails({
     required this.type,
     required this.metrics,
-  })  : assert(type != null),
-        assert(metrics != null);
+  });
 
   /// The type of scroll this is (e.g. line, page, etc.).
   ///
@@ -1750,8 +1767,7 @@
   const ScrollIntent({
     required this.direction,
     this.type = ScrollIncrementType.line,
-  })  : assert(direction != null),
-        assert(type != null);
+  });
 
   /// The direction in which to scroll the scrollable containing the focused
   /// widget.
@@ -1797,12 +1813,7 @@
   /// null. The type and state arguments must not be null, and the widget must
   /// have already been laid out so that the position fields are valid.
   static double _calculateScrollIncrement(ScrollableState state, { ScrollIncrementType type = ScrollIncrementType.line }) {
-    assert(type != null);
-    assert(state.position != null);
     assert(state.position.hasPixels);
-    assert(state.position.viewportDimension != null);
-    assert(state.position.maxScrollExtent != null);
-    assert(state.position.minScrollExtent != null);
     assert(state._physics == null || state._physics!.shouldAcceptUserOffset(state.position));
     if (state.widget.incrementCalculator != null) {
       return state.widget.incrementCalculator!(
@@ -1904,9 +1915,6 @@
     }
     assert(state != null, '$ScrollAction was invoked on a context that has no scrollable parent');
     assert(state!.position.hasPixels, 'Scrollable must be laid out before it can be scrolled via a ScrollAction');
-    assert(state!.position.viewportDimension != null);
-    assert(state!.position.maxScrollExtent != null);
-    assert(state!.position.minScrollExtent != null);
 
     // Don't do anything if the user isn't allowed to scroll.
     if (state!._physics != null && !state._physics!.shouldAcceptUserOffset(state.position)) {
diff --git a/framework/lib/src/widgets/scrollbar.dart b/framework/lib/src/widgets/scrollbar.dart
index a80c9f7..65ad29c 100644
--- a/framework/lib/src/widgets/scrollbar.dart
+++ b/framework/lib/src/widgets/scrollbar.dart
@@ -92,21 +92,11 @@
     double? minOverscrollLength,
     ScrollbarOrientation? scrollbarOrientation,
     bool ignorePointer = false,
-  }) : assert(color != null),
-       assert(radius == null || shape == null),
-       assert(thickness != null),
-       assert(fadeoutOpacityAnimation != null),
-       assert(mainAxisMargin != null),
-       assert(crossAxisMargin != null),
-       assert(minLength != null),
+  }) : assert(radius == null || shape == null),
        assert(minLength >= 0),
        assert(minOverscrollLength == null || minOverscrollLength <= minLength),
        assert(minOverscrollLength == null || minOverscrollLength >= 0),
-       assert(padding != null),
        assert(padding.isNonNegative),
-       assert(trackColor != null),
-       assert(trackBorderColor != null),
-       assert(ignorePointer != null),
        _color = color,
        _textDirection = textDirection,
        _thickness = thickness,
@@ -129,7 +119,6 @@
   Color get color => _color;
   Color _color;
   set color(Color value) {
-    assert(value != null);
     if (color == value) {
       return;
     }
@@ -142,7 +131,6 @@
   Color get trackColor => _trackColor;
   Color _trackColor;
   set trackColor(Color value) {
-    assert(value != null);
     if (trackColor == value) {
       return;
     }
@@ -155,7 +143,6 @@
   Color get trackBorderColor => _trackBorderColor;
   Color _trackBorderColor;
   set trackBorderColor(Color value) {
-    assert(value != null);
     if (trackBorderColor == value) {
       return;
     }
@@ -197,7 +184,6 @@
   double get thickness => _thickness;
   double _thickness;
   set thickness(double value) {
-    assert(value != null);
     if (thickness == value) {
       return;
     }
@@ -220,7 +206,6 @@
   double get mainAxisMargin => _mainAxisMargin;
   double _mainAxisMargin;
   set mainAxisMargin(double value) {
-    assert(value != null);
     if (mainAxisMargin == value) {
       return;
     }
@@ -238,7 +223,6 @@
   double get crossAxisMargin => _crossAxisMargin;
   double _crossAxisMargin;
   set crossAxisMargin(double value) {
-    assert(value != null);
     if (crossAxisMargin == value) {
       return;
     }
@@ -296,7 +280,6 @@
   EdgeInsets get padding => _padding;
   EdgeInsets _padding;
   set padding(EdgeInsets value) {
-    assert(value != null);
     if (padding == value) {
       return;
     }
@@ -319,7 +302,6 @@
   double get minLength => _minLength;
   double _minLength;
   set minLength(double value) {
-    assert(value != null);
     if (minLength == value) {
       return;
     }
@@ -341,7 +323,6 @@
   double get minOverscrollLength => _minOverscrollLength;
   double _minOverscrollLength;
   set minOverscrollLength(double value) {
-    assert(value != null);
     if (minOverscrollLength == value) {
       return;
     }
@@ -679,7 +660,6 @@
   ///
   /// thumbOffsetLocal is a position in the thumb track. Cannot be null.
   double getTrackToScroll(double thumbOffsetLocal) {
-    assert(thumbOffsetLocal != null);
     final double scrollableExtent = _lastMetrics!.maxScrollExtent - _lastMetrics!.minScrollExtent;
     final double thumbMovableExtent = _traversableTrackExtent - _thumbExtent;
 
@@ -1012,8 +992,7 @@
       'This feature was deprecated after v2.9.0-1.0.pre.',
     )
     this.isAlwaysShown,
-  }) : assert(child != null),
-       assert(
+  }) : assert(
          thumbVisibility == null || isAlwaysShown == null,
          'Scrollbar thumb appearance should only be controlled with thumbVisibility, '
          'isAlwaysShown is deprecated.'
@@ -1022,16 +1001,10 @@
          !((thumbVisibility == false || isAlwaysShown == false) && (trackVisibility ?? false)),
          'A scrollbar track cannot be drawn without a scrollbar thumb.'
        ),
-       assert(minThumbLength != null),
        assert(minThumbLength >= 0),
        assert(minOverscrollLength == null || minOverscrollLength <= minThumbLength),
        assert(minOverscrollLength == null || minOverscrollLength >= 0),
-       assert(fadeDuration != null),
-       assert(radius == null || shape == null),
-       assert(timeToFade != null),
-       assert(pressDuration != null),
-       assert(mainAxisMargin != null),
-       assert(crossAxisMargin != null);
+       assert(radius == null || shape == null);
 
   /// {@template flutter.widgets.Scrollbar.child}
   /// The widget below this widget in the tree.
@@ -1675,7 +1648,7 @@
       ..textDirection = Directionality.of(context)
       ..thickness = widget.thickness ?? _kScrollbarThickness
       ..radius = widget.radius
-      ..padding = widget.padding ?? MediaQuery.of(context).padding
+      ..padding = widget.padding ?? MediaQuery.paddingOf(context)
       ..scrollbarOrientation = widget.scrollbarOrientation
       ..mainAxisMargin = widget.mainAxisMargin
       ..shape = widget.shape
@@ -2136,7 +2109,7 @@
         _cachedController!.hasClients &&
         (!_thumbDragging || kIsWeb)) {
       final ScrollPosition position = _cachedController!.position;
-      if (event is PointerScrollEvent && position != null) {
+      if (event is PointerScrollEvent) {
         if (!position.physics.shouldAcceptUserOffset(position)) {
           return;
         }
diff --git a/framework/lib/src/widgets/selectable_region.dart b/framework/lib/src/widgets/selectable_region.dart
index 289e9bc..fb99008 100644
--- a/framework/lib/src/widgets/selectable_region.dart
+++ b/framework/lib/src/widgets/selectable_region.dart
@@ -350,7 +350,7 @@
     }
 
     // Hide the text selection toolbar on mobile when orientation changes.
-    final Orientation orientation = MediaQuery.of(context).orientation;
+    final Orientation orientation = MediaQuery.orientationOf(context);
     if (_lastOrientation == null) {
       _lastOrientation = orientation;
       return;
@@ -506,7 +506,7 @@
   /// is not pending or users end their gestures.
   void _triggerSelectionEndEdgeUpdate() {
     // This method can be called when the drag is not in progress. This can
-    // happen if the the child scrollable returns SelectionResult.pending, and
+    // happen if the child scrollable returns SelectionResult.pending, and
     // the selection area scheduled a selection update for the next frame, but
     // the drag is lifted before the scheduled selection update is run.
     if (_scheduledSelectionEndEdgeUpdate || !_userDraggingSelectionEnd) {
@@ -559,7 +559,7 @@
   /// is not pending or users end their gestures.
   void _triggerSelectionStartEdgeUpdate() {
     // This method can be called when the drag is not in progress. This can
-    // happen if the the child scrollable returns SelectionResult.pending, and
+    // happen if the child scrollable returns SelectionResult.pending, and
     // the selection area scheduled a selection update for the next frame, but
     // the drag is lifted before the scheduled selection update is run.
     if (_scheduledSelectionStartEdgeUpdate || !_userDraggingSelectionStart) {
@@ -1771,11 +1771,11 @@
     LayerLink? effectiveStartHandle = _startHandleLayer;
     LayerLink? effectiveEndHandle = _endHandleLayer;
     if (effectiveStartHandle != null || effectiveEndHandle != null) {
-      final Rect drawableArea = Rect
+      final Rect? drawableArea = hasSize ? Rect
         .fromLTWH(0, 0, containerSize.width, containerSize.height)
-        .inflate(_kSelectionHandleDrawableAreaPadding);
-      final bool hideStartHandle = value.startSelectionPoint == null || !drawableArea.contains(value.startSelectionPoint!.localPosition);
-      final bool hideEndHandle = value.endSelectionPoint == null || !drawableArea.contains(value.endSelectionPoint!.localPosition);
+        .inflate(_kSelectionHandleDrawableAreaPadding) : null;
+      final bool hideStartHandle = value.startSelectionPoint == null || drawableArea ==  null || !drawableArea.contains(value.startSelectionPoint!.localPosition);
+      final bool hideEndHandle = value.endSelectionPoint == null || drawableArea ==  null|| !drawableArea.contains(value.endSelectionPoint!.localPosition);
       effectiveStartHandle = hideStartHandle ? null : _startHandleLayer;
       effectiveEndHandle = hideEndHandle ? null : _endHandleLayer;
     }
diff --git a/framework/lib/src/widgets/selection_container.dart b/framework/lib/src/widgets/selection_container.dart
index 4114690..8714607 100644
--- a/framework/lib/src/widgets/selection_container.dart
+++ b/framework/lib/src/widgets/selection_container.dart
@@ -48,8 +48,7 @@
     this.registrar,
     required SelectionContainerDelegate this.delegate,
     required this.child,
-  }) : assert(delegate != null),
-       assert(child != null);
+  });
 
   /// Creates a selection container that disables selection for the
   /// subtree.
@@ -240,7 +239,7 @@
     super.key,
     required SelectionRegistrar this.registrar,
     required super.child,
-  }) : assert(registrar != null);
+  });
 
   /// Creates a selection registrar scope that disables selection for the
   /// subtree.
@@ -299,12 +298,26 @@
     return box.getTransformTo(ancestor);
   }
 
+  /// Whether the [SelectionContainer] has undergone layout and has a size.
+  ///
+  /// See also:
+  ///
+  ///  * [RenderBox.hasSize], which is used internally by this method.
+  bool get hasSize {
+    assert(
+    _selectionContainerContext?.findRenderObject() != null,
+    'The _selectionContainerContext must have a renderObject, such as after the first build has completed.',
+    );
+    final RenderBox box = _selectionContainerContext!.findRenderObject()! as RenderBox;
+    return box.hasSize;
+  }
+
   /// Gets the size of the [SelectionContainer] of this delegate.
   ///
   /// Can only be called after [SelectionContainer] is laid out.
   Size get containerSize {
     assert(
-      _selectionContainerContext?.findRenderObject() != null,
+      hasSize,
       'containerSize cannot be called before SelectionContainer is laid out.',
     );
     final RenderBox box = _selectionContainerContext!.findRenderObject()! as RenderBox;
diff --git a/framework/lib/src/widgets/semantics_debugger.dart b/framework/lib/src/widgets/semantics_debugger.dart
index e1a1200..a5c8552 100644
--- a/framework/lib/src/widgets/semantics_debugger.dart
+++ b/framework/lib/src/widgets/semantics_debugger.dart
@@ -12,6 +12,7 @@
 import 'binding.dart';
 import 'framework.dart';
 import 'gesture_detector.dart';
+import 'view.dart';
 
 /// A widget that visualizes the semantics for the child.
 ///
@@ -31,8 +32,7 @@
       fontSize: 10.0,
       height: 0.8,
     ),
-  }) : assert(child != null),
-       assert(labelStyle != null);
+  });
 
   /// The widget below this widget in the tree.
   ///
@@ -96,7 +96,7 @@
   Offset? _lastPointerDownLocation;
   void _handlePointerDown(PointerDownEvent event) {
     setState(() {
-      _lastPointerDownLocation = event.position * WidgetsBinding.instance.window.devicePixelRatio;
+      _lastPointerDownLocation = event.position * View.of(context).devicePixelRatio;
     });
     // TODO(ianh): Use a gesture recognizer so that we can reset the
     // _lastPointerDownLocation when none of the other gesture recognizers win.
@@ -159,7 +159,7 @@
         _pipelineOwner,
         _client.generation,
         _lastPointerDownLocation, // in physical pixels
-        WidgetsBinding.instance.window.devicePixelRatio,
+        View.of(context).devicePixelRatio,
         widget.labelStyle,
       ),
       child: GestureDetector(
@@ -286,12 +286,15 @@
       annotations.add('adjustable');
     }
 
-    assert(data.attributedLabel != null);
     final String message;
+    // Android will avoid pronouncing duplicating tooltip and label.
+    // Therefore, having two identical strings is the same as having a single
+    // string.
+    final bool shouldIgnoreDuplicatedLabel = defaultTargetPlatform == TargetPlatform.android && data.attributedLabel.string == data.tooltip;
     final String tooltipAndLabel = <String>[
       if (data.tooltip.isNotEmpty)
         data.tooltip,
-      if (data.attributedLabel.string.isNotEmpty)
+      if (data.attributedLabel.string.isNotEmpty && !shouldIgnoreDuplicatedLabel)
         data.attributedLabel.string,
     ].join('\n');
     if (tooltipAndLabel.isEmpty) {
diff --git a/framework/lib/src/widgets/shortcuts.dart b/framework/lib/src/widgets/shortcuts.dart
index 37cc234..b4f461d 100644
--- a/framework/lib/src/widgets/shortcuts.dart
+++ b/framework/lib/src/widgets/shortcuts.dart
@@ -39,8 +39,7 @@
     T? key2,
     T? key3,
     T? key4,
-  ])  : assert(key1 != null),
-        _keys = HashSet<T>()..add(key1) {
+  ])  : _keys = HashSet<T>()..add(key1) {
     int count = 1;
     if (key2 != null) {
       _keys.add(key2);
@@ -72,8 +71,7 @@
   ///
   /// The `keys` set must not be empty.
   KeySet.fromSet(Set<T> keys)
-      : assert(keys != null),
-        assert(keys.isNotEmpty),
+      : assert(keys.isNotEmpty),
         assert(!keys.contains(null)),
         _keys = HashSet<T>.of(keys);
 
@@ -90,7 +88,6 @@
         && setEquals<T>(other._keys, _keys);
   }
 
-
   // Cached hash code value. Improves [hashCode] performance by 27%-900%,
   // depending on key set size and read/write ratio.
   @override
@@ -334,8 +331,8 @@
   }
 }
 
-/// A [DiagnosticsProperty] which handles formatting a `Map<LogicalKeySet,
-/// Intent>` (the same type as the [Shortcuts.shortcuts] property) so that its
+/// A [DiagnosticsProperty] which handles formatting a `Map<LogicalKeySet, Intent>`
+/// (the same type as the [Shortcuts.shortcuts] property) so that its
 /// diagnostic output is human-readable.
 class ShortcutMapProperty extends DiagnosticsProperty<Map<ShortcutActivator, Intent>> {
   /// Create a diagnostics property for `Map<ShortcutActivator, Intent>` objects,
@@ -347,8 +344,7 @@
     Object super.defaultValue,
     super.level,
     super.description,
-  }) : assert(showName != null),
-       assert(level != null);
+  });
 
   @override
   Map<ShortcutActivator, Intent> get value => super.value!;
@@ -751,8 +747,7 @@
   ShortcutManager({
     Map<ShortcutActivator, Intent> shortcuts = const <ShortcutActivator, Intent>{},
     this.modal = false,
-  })  : assert(shortcuts != null),
-        _shortcuts = shortcuts;
+  })  : _shortcuts = shortcuts;
 
   /// True if the [ShortcutManager] should not pass on keys that it doesn't
   /// handle to any key-handling widgets that are ancestors to this one.
@@ -775,7 +770,6 @@
   Map<ShortcutActivator, Intent> get shortcuts => _shortcuts;
   Map<ShortcutActivator, Intent> _shortcuts = <ShortcutActivator, Intent>{};
   set shortcuts(Map<ShortcutActivator, Intent> value) {
-    assert(value != null);
     if (!mapEquals<ShortcutActivator, Intent>(_shortcuts, value)) {
       _shortcuts = value;
       _indexedShortcutsCache = null;
@@ -841,7 +835,6 @@
   /// must be mapped to an [Action], and the [Action] must be enabled.
   @protected
   KeyEventResult handleKeypress(BuildContext context, RawKeyEvent event) {
-    assert(context != null);
     final Intent? matchedIntent = _find(event, RawKeyboard.instance);
     if (matchedIntent != null) {
       final BuildContext? primaryContext = primaryFocus?.context;
@@ -851,8 +844,8 @@
           intent: matchedIntent,
         );
         if (action != null && action.isEnabled(matchedIntent)) {
-          final Object? actionResult = Actions.of(primaryContext).invokeAction(action, matchedIntent, primaryContext);
-          return action.toKeyEventResult(matchedIntent, actionResult);
+          final Object? invokeResult = Actions.of(primaryContext).invokeAction(action, matchedIntent, primaryContext);
+          return action.toKeyEventResult(matchedIntent, invokeResult);
         }
       }
     }
@@ -934,9 +927,7 @@
     required this.child,
     this.debugLabel,
   }) : _shortcuts = shortcuts,
-       manager = null,
-       assert(shortcuts != null),
-       assert(child != null);
+       manager = null;
 
   /// Creates a const [Shortcuts] widget that uses the [manager] to
   /// manage the map of shortcuts.
@@ -950,9 +941,7 @@
     required ShortcutManager this.manager,
     required this.child,
     this.debugLabel,
-  }) : _shortcuts = const <ShortcutActivator, Intent>{},
-       assert(manager != null),
-       assert(child != null);
+  }) : _shortcuts = const <ShortcutActivator, Intent>{};
 
   /// The [ShortcutManager] that will manage the mapping between key
   /// combinations and [Action]s.
@@ -1183,7 +1172,7 @@
 ///
 /// The registry may be listened to (with [addListener]/[removeListener]) for
 /// change notifications when the registered shortcuts change. Change
-/// notifications take place after the the current frame is drawn, so that
+/// notifications take place after the current frame is drawn, so that
 /// widgets that are not descendants of the registry can listen to it (e.g. in
 /// overlays).
 class ShortcutRegistry with ChangeNotifier {
@@ -1276,9 +1265,8 @@
   ///  * [maybeOf], which is similar to this function, but will return null if
   ///    it doesn't find a [ShortcutRegistrar] ancestor.
   static ShortcutRegistry of(BuildContext context) {
-    assert(context != null);
-    final _ShortcutRegistrarMarker? inherited =
-      context.dependOnInheritedWidgetOfExactType<_ShortcutRegistrarMarker>();
+    final _ShortcutRegistrarScope? inherited =
+      context.dependOnInheritedWidgetOfExactType<_ShortcutRegistrarScope>();
     assert(() {
       if (inherited == null) {
         throw FlutterError(
@@ -1312,9 +1300,8 @@
   ///    result, and will throw an exception if it doesn't find a
   ///    [ShortcutRegistrar] ancestor.
   static ShortcutRegistry? maybeOf(BuildContext context) {
-    assert(context != null);
-    final _ShortcutRegistrarMarker? inherited =
-      context.dependOnInheritedWidgetOfExactType<_ShortcutRegistrarMarker>();
+    final _ShortcutRegistrarScope? inherited =
+      context.dependOnInheritedWidgetOfExactType<_ShortcutRegistrarScope>();
     return inherited?.registry;
   }
 
@@ -1431,7 +1418,7 @@
 
   @override
   Widget build(BuildContext context) {
-    return _ShortcutRegistrarMarker(
+    return _ShortcutRegistrarScope(
       registry: registry,
       child: Shortcuts.manager(
         manager: manager,
@@ -1441,8 +1428,8 @@
   }
 }
 
-class _ShortcutRegistrarMarker extends InheritedWidget {
-  const _ShortcutRegistrarMarker({
+class _ShortcutRegistrarScope extends InheritedWidget {
+  const _ShortcutRegistrarScope({
     required this.registry,
     required super.child,
   });
@@ -1450,7 +1437,7 @@
   final ShortcutRegistry registry;
 
   @override
-  bool updateShouldNotify(covariant _ShortcutRegistrarMarker oldWidget) {
+  bool updateShouldNotify(covariant _ShortcutRegistrarScope oldWidget) {
     return registry != oldWidget.registry;
   }
 }
diff --git a/framework/lib/src/widgets/single_child_scroll_view.dart b/framework/lib/src/widgets/single_child_scroll_view.dart
index 1d4eb04..68f4aa6 100644
--- a/framework/lib/src/widgets/single_child_scroll_view.dart
+++ b/framework/lib/src/widgets/single_child_scroll_view.dart
@@ -48,17 +48,18 @@
 /// small window in split-screen mode. In any case, as a result, it might
 /// make sense to wrap the layout in a [SingleChildScrollView].
 ///
-/// Simply doing so, however, usually results in a conflict between the [Column],
+/// Doing so, however, usually results in a conflict between the [Column],
 /// which typically tries to grow as big as it can, and the [SingleChildScrollView],
 /// which provides its children with an infinite amount of space.
 ///
 /// To resolve this apparent conflict, there are a couple of techniques, as
 /// discussed below. These techniques should only be used when the content is
-/// normally expected to fit on the screen, so that the lazy instantiation of
-/// a sliver-based [ListView] or [CustomScrollView] is not expected to provide
-/// any performance benefit. If the viewport is expected to usually contain
-/// content beyond the dimensions of the screen, then [SingleChildScrollView]
-/// would be very expensive.
+/// normally expected to fit on the screen, so that the lazy instantiation of a
+/// sliver-based [ListView] or [CustomScrollView] is not expected to provide any
+/// performance benefit. If the viewport is expected to usually contain content
+/// beyond the dimensions of the screen, then [SingleChildScrollView] would be
+/// very expensive (in which case [ListView] may be a better choice than
+/// [Column]).
 ///
 /// ### Centering, spacing, or aligning fixed-height content
 ///
@@ -150,10 +151,7 @@
     this.clipBehavior = Clip.hardEdge,
     this.restorationId,
     this.keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
-  }) : assert(scrollDirection != null),
-       assert(dragStartBehavior != null),
-       assert(clipBehavior != null),
-       assert(
+  }) : assert(
          !(controller != null && (primary ?? false)),
          'Primary ScrollViews obtain their ScrollController via inheritance '
          'from a PrimaryScrollController widget. You cannot both set primary to '
@@ -287,8 +285,7 @@
     required this.offset,
     super.child,
     required this.clipBehavior,
-  }) : assert(axisDirection != null),
-       assert(clipBehavior != null);
+  });
 
   final AxisDirection axisDirection;
   final ViewportOffset offset;
@@ -328,10 +325,7 @@
     required ViewportOffset offset,
     RenderBox? child,
     required Clip clipBehavior,
-  }) : assert(axisDirection != null),
-       assert(offset != null),
-       assert(clipBehavior != null),
-       _axisDirection = axisDirection,
+  }) : _axisDirection = axisDirection,
        _offset = offset,
        _clipBehavior = clipBehavior {
     this.child = child;
@@ -340,7 +334,6 @@
   AxisDirection get axisDirection => _axisDirection;
   AxisDirection _axisDirection;
   set axisDirection(AxisDirection value) {
-    assert(value != null);
     if (value == _axisDirection) {
       return;
     }
@@ -353,7 +346,6 @@
   ViewportOffset get offset => _offset;
   ViewportOffset _offset;
   set offset(ViewportOffset value) {
-    assert(value != null);
     if (value == _offset) {
       return;
     }
@@ -373,7 +365,6 @@
   Clip get clipBehavior => _clipBehavior;
   Clip _clipBehavior = Clip.none;
   set clipBehavior(Clip value) {
-    assert(value != null);
     if (value != _clipBehavior) {
       _clipBehavior = value;
       markNeedsPaint();
@@ -510,7 +501,6 @@
   Offset get _paintOffset => _paintOffsetForPosition(offset.pixels);
 
   Offset _paintOffsetForPosition(double position) {
-    assert(axisDirection != null);
     switch (axisDirection) {
       case AxisDirection.up:
         return Offset(0.0, position - child!.size.height + size.height);
@@ -616,7 +606,6 @@
     final double targetMainAxisExtent;
     final double mainAxisExtent;
 
-    assert(axisDirection != null);
     switch (axisDirection) {
       case AxisDirection.up:
         mainAxisExtent = size.height;
@@ -684,7 +673,6 @@
 
   @override
   Rect describeSemanticsClip(RenderObject child) {
-    assert(axis != null);
     final double remainingOffset = _maxScrollExtent - offset.pixels;
     switch (axisDirection) {
       case AxisDirection.up:
diff --git a/framework/lib/src/widgets/size_changed_layout_notifier.dart b/framework/lib/src/widgets/size_changed_layout_notifier.dart
index 916a06d..2947c99 100644
--- a/framework/lib/src/widgets/size_changed_layout_notifier.dart
+++ b/framework/lib/src/widgets/size_changed_layout_notifier.dart
@@ -74,8 +74,7 @@
   _RenderSizeChangedWithCallback({
     RenderBox? child,
     required this.onLayoutChangedCallback,
-  }) : assert(onLayoutChangedCallback != null),
-       super(child);
+  }) : super(child);
 
   // There's a 1:1 relationship between the _RenderSizeChangedWithCallback and
   // the `context` that is captured by the closure created by createRenderObject
diff --git a/framework/lib/src/widgets/sliver.dart b/framework/lib/src/widgets/sliver.dart
index e5c595f..eafff4b 100644
--- a/framework/lib/src/widgets/sliver.dart
+++ b/framework/lib/src/widgets/sliver.dart
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 import 'dart:collection' show HashMap, SplayTreeMap;
+import 'dart:math' as math;
 
 import 'package:flute/foundation.dart';
 import 'package:flute/rendering.dart';
@@ -227,7 +228,7 @@
 }
 
 class _SaltedValueKey extends ValueKey<Key> {
-  const _SaltedValueKey(super.key): assert(key != null);
+  const _SaltedValueKey(super.key);
 }
 
 /// Called to find the new index of a child based on its `key` in case of
@@ -363,11 +364,7 @@
     this.addSemanticIndexes = true,
     this.semanticIndexCallback = _kDefaultSemanticIndexCallback,
     this.semanticIndexOffset = 0,
-  }) : assert(builder != null),
-       assert(addAutomaticKeepAlives != null),
-       assert(addRepaintBoundaries != null),
-       assert(addSemanticIndexes != null),
-       assert(semanticIndexCallback != null);
+  });
 
   /// Called to build children for the sliver.
   ///
@@ -417,7 +414,7 @@
   /// boundaries so that they do not need to be repainted as the list scrolls.
   /// If the children are easy to repaint (e.g., solid color blocks or a short
   /// snippet of text), it might be more efficient to not add a repaint boundary
-  /// and simply repaint the children during scrolling.
+  /// and instead always repaint the children during scrolling.
   ///
   /// Defaults to true.
   final bool addRepaintBoundaries;
@@ -464,7 +461,6 @@
     if (findChildIndexCallback == null) {
       return null;
     }
-    assert(key != null);
     final Key childKey;
     if (key is _SaltedValueKey) {
       final _SaltedValueKey saltedValueKey = key;
@@ -478,7 +474,6 @@
   @override
   @pragma('vm:notify-debugger-on-exception')
   Widget? build(BuildContext context, int index) {
-    assert(builder != null);
     if (index < 0 || (childCount != null && index >= childCount!)) {
       return null;
     }
@@ -582,12 +577,7 @@
     this.addSemanticIndexes = true,
     this.semanticIndexCallback = _kDefaultSemanticIndexCallback,
     this.semanticIndexOffset = 0,
-  }) : assert(children != null),
-       assert(addAutomaticKeepAlives != null),
-       assert(addRepaintBoundaries != null),
-       assert(addSemanticIndexes != null),
-       assert(semanticIndexCallback != null),
-       _keyToIndex = <Key?, int>{null: 0};
+  }) : _keyToIndex = <Key?, int>{null: 0};
 
   /// Creates a constant version of the delegate that supplies children for
   /// slivers using the given list.
@@ -605,12 +595,7 @@
     this.addSemanticIndexes = true,
     this.semanticIndexCallback = _kDefaultSemanticIndexCallback,
     this.semanticIndexOffset = 0,
-  }) : assert(children != null),
-       assert(addAutomaticKeepAlives != null),
-       assert(addRepaintBoundaries != null),
-       assert(addSemanticIndexes != null),
-       assert(semanticIndexCallback != null),
-       _keyToIndex = null;
+  }) : _keyToIndex = null;
 
   /// Whether to wrap each child in an [AutomaticKeepAlive].
   ///
@@ -632,7 +617,7 @@
   /// boundaries so that they do not need to be repainted as the list scrolls.
   /// If the children are easy to repaint (e.g., solid color blocks or a short
   /// snippet of text), it might be more efficient to not add a repaint boundary
-  /// and simply repaint the children during scrolling.
+  /// and instead always repaint the children during scrolling.
   ///
   /// Defaults to true.
   final bool addRepaintBoundaries;
@@ -734,7 +719,6 @@
 
   @override
   int? findIndexByKey(Key key) {
-    assert(key != null);
     final Key childKey;
     if (key is _SaltedValueKey) {
       final _SaltedValueKey saltedValueKey = key;
@@ -747,16 +731,11 @@
 
   @override
   Widget? build(BuildContext context, int index) {
-    assert(children != null);
     if (index < 0 || index >= children.length) {
       return null;
     }
     Widget child = children[index];
     final Key? key = child.key != null? _SaltedValueKey(child.key!) : null;
-    assert(
-      child != null,
-      "The sliver's children must not contain null values, but a null value was found at index $index",
-    );
     if (addRepaintBoundaries) {
       child = RepaintBoundary(child: child);
     }
@@ -930,7 +909,7 @@
   const SliverMultiBoxAdaptorWidget({
     super.key,
     required this.delegate,
-  }) : assert(delegate != null);
+  });
 
   /// {@template flutter.widgets.SliverMultiBoxAdaptorWidget.delegate}
   /// The delegate that provides the children for this widget.
@@ -1035,6 +1014,182 @@
     required super.delegate,
   });
 
+  /// A sliver that places multiple box children in a linear array along the main
+  /// axis.
+  ///
+  /// This constructor is appropriate for sliver lists with a large (or
+  /// infinite) number of children because the builder is called only for those
+  /// children that are actually visible.
+  ///
+  /// Providing a non-null `itemCount` improves the ability of the [SliverGrid]
+  /// to estimate the maximum scroll extent.
+  ///
+  /// `itemBuilder` will be called only with indices greater than or equal to
+  /// zero and less than `itemCount`.
+  ///
+  /// {@macro flutter.widgets.ListView.builder.itemBuilder}
+  ///
+  /// {@macro flutter.widgets.PageView.findChildIndexCallback}
+  ///
+  /// The `addAutomaticKeepAlives` argument corresponds to the
+  /// [SliverChildBuilderDelegate.addAutomaticKeepAlives] property. The
+  /// `addRepaintBoundaries` argument corresponds to the
+  /// [SliverChildBuilderDelegate.addRepaintBoundaries] property. The
+  /// `addSemanticIndexes` argument corresponds to the
+  /// [SliverChildBuilderDelegate.addSemanticIndexes] property.
+  ///
+  /// {@tool snippet}
+  /// This example, which would be inserted into a [CustomScrollView.slivers]
+  /// list, shows an infinite number of items in varying shades of blue:
+  ///
+  /// ```dart
+  /// SliverList.builder(
+  ///   itemBuilder: (BuildContext context, int index) {
+  ///     return Container(
+  ///       alignment: Alignment.center,
+  ///       color: Colors.lightBlue[100 * (index % 9)],
+  ///       child: Text('list item $index'),
+  ///     );
+  ///   },
+  /// )
+  /// ```
+  /// {@end-tool}
+  SliverList.builder({
+    super.key,
+    required NullableIndexedWidgetBuilder itemBuilder,
+    ChildIndexGetter? findChildIndexCallback,
+    int? itemCount,
+    bool addAutomaticKeepAlives = true,
+    bool addRepaintBoundaries = true,
+    bool addSemanticIndexes = true,
+  }) : super(delegate: SliverChildBuilderDelegate(
+         itemBuilder,
+         findChildIndexCallback: findChildIndexCallback,
+         childCount: itemCount,
+         addAutomaticKeepAlives: addAutomaticKeepAlives,
+         addRepaintBoundaries: addRepaintBoundaries,
+         addSemanticIndexes: addSemanticIndexes,
+       ));
+
+  /// A sliver that places multiple box children, separated by box widgets, in a linear array along the main
+  /// axis.
+  ///
+  /// This constructor is appropriate for sliver lists with a large (or
+  /// infinite) number of children because the builder is called only for those
+  /// children that are actually visible.
+  ///
+  /// Providing a non-null `itemCount` improves the ability of the [SliverGrid]
+  /// to estimate the maximum scroll extent.
+  ///
+  /// `itemBuilder` will be called only with indices greater than or equal to
+  /// zero and less than `itemCount`.
+  ///
+  /// {@macro flutter.widgets.ListView.builder.itemBuilder}
+  ///
+  /// {@macro flutter.widgets.PageView.findChildIndexCallback}
+  ///
+  ///
+  /// The `separatorBuilder` is similar to `itemBuilder`, except it is the widget
+  /// that gets placed between itemBuilder(context, index) and itemBuilder(context, index + 1).
+  ///
+  /// The `addAutomaticKeepAlives` argument corresponds to the
+  /// [SliverChildBuilderDelegate.addAutomaticKeepAlives] property. The
+  /// `addRepaintBoundaries` argument corresponds to the
+  /// [SliverChildBuilderDelegate.addRepaintBoundaries] property. The
+  /// `addSemanticIndexes` argument corresponds to the
+  /// [SliverChildBuilderDelegate.addSemanticIndexes] property.
+  /// {@tool snippet}
+  ///
+  /// This example shows how to create a [SliverList] whose [Container] items
+  /// are separated by [Divider]s.
+  ///
+  /// ```dart
+  /// SliverList.separated(
+  ///   itemBuilder: (BuildContext context, int index) {
+  ///     return Container(
+  ///       alignment: Alignment.center,
+  ///       color: Colors.lightBlue[100 * (index % 9)],
+  ///       child: Text('list item $index'),
+  ///     );
+  ///   },
+  ///   separatorBuilder: (BuildContext context, int index) => const Divider(),
+  /// )
+  /// ```
+  /// {@end-tool}
+  SliverList.separated({
+    super.key,
+    required NullableIndexedWidgetBuilder itemBuilder,
+    ChildIndexGetter? findChildIndexCallback,
+    required NullableIndexedWidgetBuilder separatorBuilder,
+    int? itemCount,
+    bool addAutomaticKeepAlives = true,
+    bool addRepaintBoundaries = true,
+    bool addSemanticIndexes = true,
+  }) : super(delegate: SliverChildBuilderDelegate(
+         (BuildContext context, int index) {
+           final int itemIndex = index ~/ 2;
+           final Widget? widget;
+           if (index.isEven) {
+             widget = itemBuilder(context, itemIndex);
+           } else {
+             widget = separatorBuilder(context, itemIndex);
+             assert(() {
+               if (widget == null) {
+                 throw FlutterError('separatorBuilder cannot return null.');
+               }
+               return true;
+             }());
+           }
+           return widget;
+         },
+         findChildIndexCallback: findChildIndexCallback,
+         childCount: itemCount == null ? null : math.max(0, itemCount * 2 - 1),
+         addAutomaticKeepAlives: addAutomaticKeepAlives,
+         addRepaintBoundaries: addRepaintBoundaries,
+         addSemanticIndexes: addSemanticIndexes,
+         semanticIndexCallback: (Widget _, int index) {
+           return index.isEven ? index ~/ 2 : null;
+         },
+       ));
+
+  /// A sliver that places multiple box children in a linear array along the main
+  /// axis.
+  ///
+  /// This constructor uses a list of [Widget]s to build the sliver.
+  ///
+  /// The `addAutomaticKeepAlives` argument corresponds to the
+  /// [SliverChildBuilderDelegate.addAutomaticKeepAlives] property. The
+  /// `addRepaintBoundaries` argument corresponds to the
+  /// [SliverChildBuilderDelegate.addRepaintBoundaries] property. The
+  /// `addSemanticIndexes` argument corresponds to the
+  /// [SliverChildBuilderDelegate.addSemanticIndexes] property.
+  ///
+  /// {@tool snippet}
+  /// This example, which would be inserted into a [CustomScrollView.slivers]
+  /// list, shows an infinite number of items in varying shades of blue:
+  ///
+  /// ```dart
+  /// SliverList.list(
+  ///   children: const <Widget>[
+  ///     Text('Hello'),
+  ///     Text('World!'),
+  ///   ],
+  /// );
+  /// ```
+  /// {@end-tool}
+  SliverList.list({
+    super.key,
+    required List<Widget> children,
+    bool addAutomaticKeepAlives = true,
+    bool addRepaintBoundaries = true,
+    bool addSemanticIndexes = true,
+  }) : super(delegate: SliverChildListDelegate(
+         children,
+         addAutomaticKeepAlives: addAutomaticKeepAlives,
+         addRepaintBoundaries: addRepaintBoundaries,
+         addSemanticIndexes: addSemanticIndexes,
+       ));
+
   @override
   SliverMultiBoxAdaptorElement createElement() => SliverMultiBoxAdaptorElement(this, replaceMovedChildren: true);
 
@@ -1098,6 +1253,116 @@
     required this.itemExtent,
   });
 
+  /// A sliver that places multiple box children in a linear array along the main
+  /// axis.
+  ///
+  /// [SliverFixedExtentList] places its children in a linear array along the main
+  /// axis starting at offset zero and without gaps. Each child is forced to have
+  /// the [itemExtent] in the main axis and the
+  /// [SliverConstraints.crossAxisExtent] in the cross axis.
+  ///
+  /// This constructor is appropriate for sliver lists with a large (or
+  /// infinite) number of children whose extent is already determined.
+  ///
+  /// Providing a non-null `itemCount` improves the ability of the [SliverGrid]
+  /// to estimate the maximum scroll extent.
+  ///
+  /// `itemBuilder` will be called only with indices greater than or equal to
+  /// zero and less than `itemCount`.
+  ///
+  /// {@macro flutter.widgets.ListView.builder.itemBuilder}
+  ///
+  /// The `itemExtent` argument is the extent of each item.
+  ///
+  /// {@macro flutter.widgets.PageView.findChildIndexCallback}
+  ///
+  /// The `addAutomaticKeepAlives` argument corresponds to the
+  /// [SliverChildBuilderDelegate.addAutomaticKeepAlives] property. The
+  /// `addRepaintBoundaries` argument corresponds to the
+  /// [SliverChildBuilderDelegate.addRepaintBoundaries] property. The
+  /// `addSemanticIndexes` argument corresponds to the
+  /// [SliverChildBuilderDelegate.addSemanticIndexes] property.
+  /// {@tool snippet}
+  ///
+  /// This example, which would be inserted into a [CustomScrollView.slivers]
+  /// list, shows an infinite number of items in varying shades of blue:
+  ///
+  /// ```dart
+  /// SliverFixedExtentList.builder(
+  ///   itemExtent: 50.0,
+  ///   itemBuilder: (BuildContext context, int index) {
+  ///     return Container(
+  ///       alignment: Alignment.center,
+  ///       color: Colors.lightBlue[100 * (index % 9)],
+  ///       child: Text('list item $index'),
+  ///     );
+  ///   },
+  /// )
+  /// ```
+  /// {@end-tool}
+  SliverFixedExtentList.builder({
+    super.key,
+    required NullableIndexedWidgetBuilder itemBuilder,
+    required this.itemExtent,
+    ChildIndexGetter? findChildIndexCallback,
+    int? itemCount,
+    bool addAutomaticKeepAlives = true,
+    bool addRepaintBoundaries = true,
+    bool addSemanticIndexes = true,
+  }) : super(delegate: SliverChildBuilderDelegate(
+         itemBuilder,
+         findChildIndexCallback: findChildIndexCallback,
+         childCount: itemCount,
+         addAutomaticKeepAlives: addAutomaticKeepAlives,
+         addRepaintBoundaries: addRepaintBoundaries,
+         addSemanticIndexes: addSemanticIndexes,
+       ));
+
+  /// A sliver that places multiple box children in a linear array along the main
+  /// axis.
+  ///
+  /// [SliverFixedExtentList] places its children in a linear array along the main
+  /// axis starting at offset zero and without gaps. Each child is forced to have
+  /// the [itemExtent] in the main axis and the
+  /// [SliverConstraints.crossAxisExtent] in the cross axis.
+  ///
+  /// This constructor uses a list of [Widget]s to build the sliver.
+  ///
+  /// The `addAutomaticKeepAlives` argument corresponds to the
+  /// [SliverChildBuilderDelegate.addAutomaticKeepAlives] property. The
+  /// `addRepaintBoundaries` argument corresponds to the
+  /// [SliverChildBuilderDelegate.addRepaintBoundaries] property. The
+  /// `addSemanticIndexes` argument corresponds to the
+  /// [SliverChildBuilderDelegate.addSemanticIndexes] property.
+  ///
+  /// {@tool snippet}
+  /// This example, which would be inserted into a [CustomScrollView.slivers]
+  /// list, shows an infinite number of items in varying shades of blue:
+  ///
+  /// ```dart
+  /// SliverFixedExtentList.list(
+  ///   itemExtent: 50.0,
+  ///   children: const <Widget>[
+  ///     Text('Hello'),
+  ///     Text('World!'),
+  ///   ],
+  /// );
+  /// ```
+  /// {@end-tool}
+  SliverFixedExtentList.list({
+    super.key,
+    required List<Widget> children,
+    required this.itemExtent,
+    bool addAutomaticKeepAlives = true,
+    bool addRepaintBoundaries = true,
+    bool addSemanticIndexes = true,
+  }) : super(delegate: SliverChildListDelegate(
+         children,
+         addAutomaticKeepAlives: addAutomaticKeepAlives,
+         addRepaintBoundaries: addRepaintBoundaries,
+         addSemanticIndexes: addSemanticIndexes,
+       ));
+
   /// The extent the children are forced to have in the main axis.
   final double itemExtent;
 
@@ -1203,8 +1468,7 @@
     bool addAutomaticKeepAlives = true,
     bool addRepaintBoundaries = true,
     bool addSemanticIndexes = true,
-  }) : assert(gridDelegate != null),
-       super(delegate: SliverChildBuilderDelegate(
+  }) : super(delegate: SliverChildBuilderDelegate(
          itemBuilder,
          findChildIndexCallback: findChildIndexCallback,
          childCount: itemCount,
@@ -1460,7 +1724,6 @@
 
   @override
   void forgetChild(Element child) {
-    assert(child != null);
     assert(child.slot != null);
     assert(_childElements.containsKey(child.slot));
     _childElements.remove(child.slot);
@@ -1621,7 +1884,6 @@
 
   @override
   void insertRenderObjectChild(covariant RenderObject child, int slot) {
-    assert(slot != null);
     assert(_currentlyUpdatingChildIndex == slot);
     assert(renderObject.debugValidateChild(child));
     renderObject.insert(child as RenderBox, after: _currentBeforeChild);
@@ -1634,7 +1896,6 @@
 
   @override
   void moveRenderObjectChild(covariant RenderObject child, int oldSlot, int newSlot) {
-    assert(newSlot != null);
     assert(_currentlyUpdatingChildIndex == newSlot);
     renderObject.move(child as RenderBox, after: _currentBeforeChild);
   }
@@ -1681,7 +1942,7 @@
 ///
 /// For values of opacity other than 0.0 and 1.0, this class is relatively
 /// expensive because it requires painting the sliver child into an intermediate
-/// buffer. For the value 0.0, the sliver child is simply not painted at all.
+/// buffer. For the value 0.0, the sliver child is not painted at all.
 /// For the value 1.0, the sliver child is painted immediately without an
 /// intermediate buffer.
 ///
@@ -1718,8 +1979,7 @@
     required this.opacity,
     this.alwaysIncludeSemantics = false,
     Widget? sliver,
-  }) : assert(opacity != null && opacity >= 0.0 && opacity <= 1.0),
-       assert(alwaysIncludeSemantics != null),
+  }) : assert(opacity >= 0.0 && opacity <= 1.0),
        super(child: sliver);
 
   /// The fraction to scale the sliver child's alpha value.
@@ -1791,8 +2051,7 @@
     this.ignoring = true,
     this.ignoringSemantics,
     Widget? sliver,
-  }) : assert(ignoring != null),
-       super(child: sliver);
+  }) : super(child: sliver);
 
   /// Whether this sliver is ignored during hit testing.
   ///
@@ -1848,8 +2107,7 @@
     super.key,
     this.offstage = true,
     Widget? sliver,
-  }) : assert(offstage != null),
-       super(child: sliver);
+  }) : super(child: sliver);
 
   /// Whether the sliver child is hidden from the rest of the tree.
   ///
@@ -1917,8 +2175,7 @@
     super.key,
     required this.keepAlive,
     required super.child,
-  }) : assert(child != null),
-       assert(keepAlive != null);
+  });
 
   /// Whether to keep the child alive.
   ///
diff --git a/framework/lib/src/widgets/sliver_fill.dart b/framework/lib/src/widgets/sliver_fill.dart
index 0334551..e7bef9e 100644
--- a/framework/lib/src/widgets/sliver_fill.dart
+++ b/framework/lib/src/widgets/sliver_fill.dart
@@ -30,9 +30,7 @@
     required this.delegate,
     this.viewportFraction = 1.0,
     this.padEnds = true,
-  }) : assert(viewportFraction != null),
-       assert(viewportFraction > 0.0),
-       assert(padEnds != null);
+  }) : assert(viewportFraction > 0.0);
 
   /// The fraction of the viewport that each child should fill in the main axis.
   ///
@@ -73,8 +71,7 @@
   const _SliverFillViewportRenderObjectWidget({
     required super.delegate,
     this.viewportFraction = 1.0,
-  }) : assert(viewportFraction != null),
-      assert(viewportFraction > 0.0);
+  }) : assert(viewportFraction > 0.0);
 
   final double viewportFraction;
 
@@ -94,8 +91,7 @@
   const _SliverFractionalPadding({
     this.viewportFraction = 0,
     Widget? sliver,
-  }) : assert(viewportFraction != null),
-      assert(viewportFraction >= 0),
+  }) : assert(viewportFraction >= 0),
       assert(viewportFraction <= 0.5),
       super(child: sliver);
 
@@ -113,8 +109,7 @@
 class _RenderSliverFractionalPadding extends RenderSliverEdgeInsetsPadding {
   _RenderSliverFractionalPadding({
     double viewportFraction = 0,
-  }) : assert(viewportFraction != null),
-      assert(viewportFraction <= 0.5),
+  }) : assert(viewportFraction <= 0.5),
       assert(viewportFraction >= 0),
       _viewportFraction = viewportFraction;
 
@@ -123,7 +118,6 @@
   double get viewportFraction => _viewportFraction;
   double _viewportFraction;
   set viewportFraction(double newValue) {
-    assert(newValue != null);
     if (_viewportFraction == newValue) {
       return;
     }
@@ -145,7 +139,6 @@
       return;
     }
 
-    assert(constraints.axis != null);
     final double paddingValue = constraints.viewportMainAxisExtent * viewportFraction;
     _lastResolvedConstraints = constraints;
     switch (constraints.axis) {
@@ -264,8 +257,7 @@
     this.child,
     this.hasScrollBody = true,
     this.fillOverscroll = false,
-  }) : assert(hasScrollBody != null),
-       assert(fillOverscroll != null);
+  });
 
   /// Box child widget that fills the remaining space in the viewport.
   ///
diff --git a/framework/lib/src/widgets/sliver_layout_builder.dart b/framework/lib/src/widgets/sliver_layout_builder.dart
index 358e841..3b1e78a 100644
--- a/framework/lib/src/widgets/sliver_layout_builder.dart
+++ b/framework/lib/src/widgets/sliver_layout_builder.dart
@@ -39,7 +39,6 @@
 class _RenderSliverLayoutBuilder extends RenderSliver with RenderObjectWithChildMixin<RenderSliver>, RenderConstrainedLayoutBuilder<SliverConstraints, RenderSliver> {
   @override
   double childMainAxisPosition(RenderObject child) {
-    assert(child != null);
     assert(child == this.child);
     return 0;
   }
@@ -53,7 +52,6 @@
 
   @override
   void applyPaintTransform(RenderObject child, Matrix4 transform) {
-    assert(child != null);
     assert(child == this.child);
     // child's offset is always (0, 0), transform.translate(0, 0) does not mutate the transform.
   }
diff --git a/framework/lib/src/widgets/sliver_persistent_header.dart b/framework/lib/src/widgets/sliver_persistent_header.dart
index ea39712..edd3aae 100644
--- a/framework/lib/src/widgets/sliver_persistent_header.dart
+++ b/framework/lib/src/widgets/sliver_persistent_header.dart
@@ -124,9 +124,7 @@
     required this.delegate,
     this.pinned = false,
     this.floating = false,
-  }) : assert(delegate != null),
-       assert(pinned != null),
-       assert(floating != null);
+  });
 
   /// Configuration for the sliver's layout.
   ///
@@ -255,7 +253,7 @@
   _SliverPersistentHeaderElement(
     _SliverPersistentHeaderRenderObjectWidget super.widget, {
     this.floating = false,
-  }) : assert(floating != null);
+  });
 
   final bool floating;
 
@@ -346,8 +344,7 @@
   const _SliverPersistentHeaderRenderObjectWidget({
     required this.delegate,
     this.floating = false,
-  }) : assert(delegate != null),
-       assert(floating != null);
+  });
 
   final SliverPersistentHeaderDelegate delegate;
   final bool floating;
diff --git a/framework/lib/src/widgets/sliver_prototype_extent_list.dart b/framework/lib/src/widgets/sliver_prototype_extent_list.dart
index f5e3f33..c6d847c 100644
--- a/framework/lib/src/widgets/sliver_prototype_extent_list.dart
+++ b/framework/lib/src/widgets/sliver_prototype_extent_list.dart
@@ -38,7 +38,110 @@
     super.key,
     required super.delegate,
     required this.prototypeItem,
-  }) : assert(prototypeItem != null);
+  });
+
+  /// A sliver that places its box children in a linear array and constrains them
+  /// to have the same extent as a prototype item along the main axis.
+  ///
+  /// This constructor is appropriate for sliver lists with a large (or
+  /// infinite) number of children whose extent is already determined.
+  ///
+  /// Providing a non-null `itemCount` improves the ability of the [SliverGrid]
+  /// to estimate the maximum scroll extent.
+  ///
+  /// `itemBuilder` will be called only with indices greater than or equal to
+  /// zero and less than `itemCount`.
+  ///
+  /// {@macro flutter.widgets.ListView.builder.itemBuilder}
+  ///
+  /// The `prototypeItem` argument is used to determine the extent of each item.
+  ///
+  /// {@macro flutter.widgets.PageView.findChildIndexCallback}
+  ///
+  /// The `addAutomaticKeepAlives` argument corresponds to the
+  /// [SliverChildBuilderDelegate.addAutomaticKeepAlives] property. The
+  /// `addRepaintBoundaries` argument corresponds to the
+  /// [SliverChildBuilderDelegate.addRepaintBoundaries] property. The
+  /// `addSemanticIndexes` argument corresponds to the
+  /// [SliverChildBuilderDelegate.addSemanticIndexes] property.
+  ///
+  /// {@tool snippet}
+  /// This example, which would be inserted into a [CustomScrollView.slivers]
+  /// list, shows an infinite number of items in varying shades of blue:
+  ///
+  /// ```dart
+  /// SliverPrototypeExtentList.builder(
+  ///   prototypeItem: Container(
+  ///     alignment: Alignment.center,
+  ///     child: const Text('list item prototype'),
+  ///   ),
+  ///   itemBuilder: (BuildContext context, int index) {
+  ///     return Container(
+  ///       alignment: Alignment.center,
+  ///       color: Colors.lightBlue[100 * (index % 9)],
+  ///       child: Text('list item $index'),
+  ///     );
+  ///   },
+  /// )
+  /// ```
+  /// {@end-tool}
+  SliverPrototypeExtentList.builder({
+    super.key,
+    required NullableIndexedWidgetBuilder itemBuilder,
+    required this.prototypeItem,
+    ChildIndexGetter? findChildIndexCallback,
+    int? itemCount,
+    bool addAutomaticKeepAlives = true,
+    bool addRepaintBoundaries = true,
+    bool addSemanticIndexes = true,
+  }) : super(delegate: SliverChildBuilderDelegate(
+         itemBuilder,
+         findChildIndexCallback: findChildIndexCallback,
+         childCount: itemCount,
+         addAutomaticKeepAlives: addAutomaticKeepAlives,
+         addRepaintBoundaries: addRepaintBoundaries,
+         addSemanticIndexes: addSemanticIndexes,
+       ));
+
+  /// A sliver that places multiple box children in a linear array along the main
+  /// axis.
+  ///
+  /// This constructor uses a list of [Widget]s to build the sliver.
+  ///
+  /// The `addAutomaticKeepAlives` argument corresponds to the
+  /// [SliverChildBuilderDelegate.addAutomaticKeepAlives] property. The
+  /// `addRepaintBoundaries` argument corresponds to the
+  /// [SliverChildBuilderDelegate.addRepaintBoundaries] property. The
+  /// `addSemanticIndexes` argument corresponds to the
+  /// [SliverChildBuilderDelegate.addSemanticIndexes] property.
+  ///
+  /// {@tool snippet}
+  /// This example, which would be inserted into a [CustomScrollView.slivers]
+  /// list, shows an infinite number of items in varying shades of blue:
+  ///
+  /// ```dart
+  /// SliverPrototypeExtentList.list(
+  ///   prototypeItem: const Text('Hello'),
+  ///   children: const <Widget>[
+  ///     Text('Hello'),
+  ///     Text('World!'),
+  ///   ],
+  /// );
+  /// ```
+  /// {@end-tool}
+  SliverPrototypeExtentList.list({
+    super.key,
+    required List<Widget> children,
+    required this.prototypeItem,
+    bool addAutomaticKeepAlives = true,
+    bool addRepaintBoundaries = true,
+    bool addSemanticIndexes = true,
+  }) : super(delegate: SliverChildListDelegate(
+         children,
+         addAutomaticKeepAlives: addAutomaticKeepAlives,
+         addRepaintBoundaries: addRepaintBoundaries,
+         addSemanticIndexes: addSemanticIndexes,
+       ));
 
   /// Defines the main axis extent of all of this sliver's children.
   ///
diff --git a/framework/lib/src/widgets/slotted_render_object_widget.dart b/framework/lib/src/widgets/slotted_render_object_widget.dart
index 64c18a1..201ae45 100644
--- a/framework/lib/src/widgets/slotted_render_object_widget.dart
+++ b/framework/lib/src/widgets/slotted_render_object_widget.dart
@@ -116,7 +116,7 @@
   /// currently occupied by a child to obtain a name for that slot for debug
   /// outputs.
   ///
-  /// The default implementation calls [EnumName.name] on `slot` it it is an
+  /// The default implementation calls [EnumName.name] on `slot` if it is an
   /// [Enum] value and `toString` if it is not.
   @protected
   String debugNameForSlot(S slot) {
diff --git a/framework/lib/src/widgets/snapshot_widget.dart b/framework/lib/src/widgets/snapshot_widget.dart
index 2407df2..dfa4d3f 100644
--- a/framework/lib/src/widgets/snapshot_widget.dart
+++ b/framework/lib/src/widgets/snapshot_widget.dart
@@ -141,7 +141,7 @@
     return _RenderSnapshotWidget(
       controller: controller,
       mode: mode,
-      devicePixelRatio: MediaQuery.of(context).devicePixelRatio,
+      devicePixelRatio: MediaQuery.devicePixelRatioOf(context),
       painter: painter,
       autoresize: autoresize,
     );
@@ -153,7 +153,7 @@
     (renderObject as _RenderSnapshotWidget)
       ..controller = controller
       ..mode = mode
-      ..devicePixelRatio = MediaQuery.of(context).devicePixelRatio
+      ..devicePixelRatio = MediaQuery.devicePixelRatioOf(context)
       ..painter = painter
       ..autoresize = autoresize;
   }
diff --git a/framework/lib/src/widgets/spacer.dart b/framework/lib/src/widgets/spacer.dart
index 3f719a5..b84aac8 100644
--- a/framework/lib/src/widgets/spacer.dart
+++ b/framework/lib/src/widgets/spacer.dart
@@ -18,8 +18,8 @@
 /// {@tool snippet}
 ///
 /// ```dart
-/// Row(
-///   children: const <Widget>[
+/// const Row(
+///   children: <Widget>[
 ///     Text('Begin'),
 ///     Spacer(), // Defaults to a flex of one.
 ///     Text('Middle'),
@@ -43,8 +43,7 @@
   ///
   /// The [flex] parameter may not be null or less than one.
   const Spacer({super.key, this.flex = 1})
-    : assert(flex != null),
-      assert(flex > 0);
+    : assert(flex > 0);
 
   /// The flex factor to use in determining how much space to take up.
   ///
diff --git a/framework/lib/src/widgets/spell_check.dart b/framework/lib/src/widgets/spell_check.dart
index 142746e..63e95b6 100644
--- a/framework/lib/src/widgets/spell_check.dart
+++ b/framework/lib/src/widgets/spell_check.dart
@@ -2,11 +2,13 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import 'package:flute/foundation.dart';
 import 'package:flute/painting.dart';
 import 'package:flute/services.dart'
     show SpellCheckResults, SpellCheckService, SuggestionSpan, TextEditingValue;
 
+import 'editable_text.dart' show EditableTextContextMenuBuilder;
+import 'framework.dart' show immutable;
+
 /// Controls how spell check is performed for text input.
 ///
 /// This configuration determines the [SpellCheckService] used to fetch the
@@ -19,12 +21,14 @@
   const SpellCheckConfiguration({
     this.spellCheckService,
     this.misspelledTextStyle,
+    this.spellCheckSuggestionsToolbarBuilder,
   }) : _spellCheckEnabled = true;
 
   /// Creates a configuration that disables spell check.
   const SpellCheckConfiguration.disabled()
     :  _spellCheckEnabled = false,
        spellCheckService = null,
+       spellCheckSuggestionsToolbarBuilder = null,
        misspelledTextStyle = null;
 
   /// The service used to fetch spell check results for text input.
@@ -38,6 +42,10 @@
   /// assertion error.
   final TextStyle? misspelledTextStyle;
 
+  /// Builds the toolbar used to display spell check suggestions for misspelled
+  /// words.
+  final EditableTextContextMenuBuilder? spellCheckSuggestionsToolbarBuilder;
+
   final bool _spellCheckEnabled;
 
   /// Whether or not the configuration should enable or disable spell check.
@@ -47,7 +55,8 @@
   /// specified overrides.
   SpellCheckConfiguration copyWith({
     SpellCheckService? spellCheckService,
-    TextStyle? misspelledTextStyle}) {
+    TextStyle? misspelledTextStyle,
+    EditableTextContextMenuBuilder? spellCheckSuggestionsToolbarBuilder}) {
     if (!_spellCheckEnabled) {
       // A new configuration should be constructed to enable spell check.
       return const SpellCheckConfiguration.disabled();
@@ -56,6 +65,7 @@
     return SpellCheckConfiguration(
       spellCheckService: spellCheckService ?? this.spellCheckService,
       misspelledTextStyle: misspelledTextStyle ?? this.misspelledTextStyle,
+      spellCheckSuggestionsToolbarBuilder : spellCheckSuggestionsToolbarBuilder ?? this.spellCheckSuggestionsToolbarBuilder,
     );
   }
 
@@ -65,6 +75,7 @@
   spell check enabled   : $_spellCheckEnabled
   spell check service   : $spellCheckService
   misspelled text style : $misspelledTextStyle
+  spell check suggesstions toolbar builder: $spellCheckSuggestionsToolbarBuilder
 '''
         .trim();
   }
@@ -78,11 +89,12 @@
     return other is SpellCheckConfiguration
       && other.spellCheckService == spellCheckService
       && other.misspelledTextStyle == misspelledTextStyle
+      && other.spellCheckSuggestionsToolbarBuilder == spellCheckSuggestionsToolbarBuilder
       && other._spellCheckEnabled == _spellCheckEnabled;
   }
 
   @override
-  int get hashCode => Object.hash(spellCheckService, misspelledTextStyle, _spellCheckEnabled);
+  int get hashCode => Object.hash(spellCheckService, misspelledTextStyle, spellCheckSuggestionsToolbarBuilder, _spellCheckEnabled);
 }
 
 // Methods for displaying spell check results:
diff --git a/framework/lib/src/widgets/status_transitions.dart b/framework/lib/src/widgets/status_transitions.dart
index d2b2dde..b79bd0d 100644
--- a/framework/lib/src/widgets/status_transitions.dart
+++ b/framework/lib/src/widgets/status_transitions.dart
@@ -13,7 +13,7 @@
   const StatusTransitionWidget({
     super.key,
     required this.animation,
-  }) : assert(animation != null);
+  });
 
   /// The animation to which this widget is listening.
   final Animation<double> animation;
diff --git a/framework/lib/src/widgets/table.dart b/framework/lib/src/widgets/table.dart
index 1e4c214..8d81642 100644
--- a/framework/lib/src/widgets/table.dart
+++ b/framework/lib/src/widgets/table.dart
@@ -32,7 +32,7 @@
 @immutable
 class TableRow {
   /// Creates a row in a [Table].
-  const TableRow({ this.key, this.decoration, this.children });
+  const TableRow({ this.key, this.decoration, this.children = const <Widget>[]});
 
   /// An identifier for the row.
   final LocalKey? key;
@@ -49,7 +49,7 @@
   /// Children may be wrapped in [TableCell] widgets to provide per-cell
   /// configuration to the [Table], but children are not required to be wrapped
   /// in [TableCell] widgets.
-  final List<Widget>? children;
+  final List<Widget> children;
 
   @override
   String toString() {
@@ -61,9 +61,7 @@
     if (decoration != null) {
       result.write('$decoration, ');
     }
-    if (children == null) {
-      result.write('child list is null');
-    } else if (children!.isEmpty) {
+    if (children.isEmpty) {
       result.write('no children');
     } else {
       result.write('$children');
@@ -126,28 +124,7 @@
     this.border,
     this.defaultVerticalAlignment = TableCellVerticalAlignment.top,
     this.textBaseline, // NO DEFAULT: we don't know what the text's baseline should be
-  }) : assert(children != null),
-       assert(defaultColumnWidth != null),
-       assert(defaultVerticalAlignment != null),
-       assert(defaultVerticalAlignment != TableCellVerticalAlignment.baseline || textBaseline != null, 'textBaseline is required if you specify the defaultVerticalAlignment with TableCellVerticalAlignment.baseline'),
-       assert(() {
-         if (children.any((TableRow row) => row.children == null)) {
-           throw FlutterError(
-             'One of the rows of the table had null children.\n'
-             'The children property of TableRow must not be null.',
-           );
-         }
-         return true;
-       }()),
-       assert(() {
-         if (children.any((TableRow row) => row.children!.any((Widget cell) => cell == null))) {
-           throw FlutterError(
-             'One of the children of one of the rows of the table was null.\n'
-             'The children of a TableRow must not be null.',
-           );
-         }
-         return true;
-       }()),
+  }) : assert(defaultVerticalAlignment != TableCellVerticalAlignment.baseline || textBaseline != null, 'textBaseline is required if you specify the defaultVerticalAlignment with TableCellVerticalAlignment.baseline'),
        assert(() {
          if (children.any((TableRow row1) => row1.key != null && children.any((TableRow row2) => row1 != row2 && row1.key == row2.key))) {
            throw FlutterError(
@@ -159,8 +136,8 @@
        }()),
        assert(() {
          if (children.isNotEmpty) {
-           final int cellCount = children.first.children!.length;
-           if (children.any((TableRow row) => row.children!.length != cellCount)) {
+           final int cellCount = children.first.children.length;
+           if (children.any((TableRow row) => row.children.length != cellCount)) {
              throw FlutterError(
                'Table contains irregular row lengths.\n'
                'Every TableRow in a Table must have the same number of children, so that every cell is filled. '
@@ -174,7 +151,7 @@
                               ? children.map<Decoration?>((TableRow row) => row.decoration).toList(growable: false)
                               : null {
     assert(() {
-      final List<Widget> flatChildren = children.expand<Widget>((TableRow row) => row.children!).toList(growable: false);
+      final List<Widget> flatChildren = children.expand<Widget>((TableRow row) => row.children).toList(growable: false);
       return !debugChildrenHaveDuplicateKeys(this, flatChildren, message:
         'Two or more cells in this Table contain widgets with the same key.\n'
         'Every widget child of every TableRow in a Table must have different keys. The cells of a Table are '
@@ -250,7 +227,7 @@
   RenderTable createRenderObject(BuildContext context) {
     assert(debugCheckHasDirectionality(context));
     return RenderTable(
-      columns: children.isNotEmpty ? children[0].children!.length : 0,
+      columns: children.isNotEmpty ? children[0].children.length : 0,
       rows: children.length,
       columnWidths: columnWidths,
       defaultColumnWidth: defaultColumnWidth,
@@ -266,7 +243,7 @@
   @override
   void updateRenderObject(BuildContext context, RenderTable renderObject) {
     assert(debugCheckHasDirectionality(context));
-    assert(renderObject.columns == (children.isNotEmpty ? children[0].children!.length : 0));
+    assert(renderObject.columns == (children.isNotEmpty ? children[0].children.length : 0));
     assert(renderObject.rows == children.length);
     renderObject
       ..columnWidths = columnWidths
@@ -301,8 +278,7 @@
       rowIndex += 1;
       return _TableElementRow(
         key: row.key,
-        children: row.children!.map<Element>((Widget child) {
-          assert(child != null);
+        children: row.children.map<Element>((Widget child) {
           return inflateWidget(child, _TableSlot(columnIndex++, rowIndex));
         }).toList(growable: false),
       );
@@ -360,12 +336,12 @@
         oldChildren = const <Element>[];
       }
       final List<_TableSlot> slots = List<_TableSlot>.generate(
-        row.children!.length,
+        row.children.length,
         (int columnIndex) => _TableSlot(columnIndex, rowIndex),
       );
       newChildren.add(_TableElementRow(
         key: row.key,
-        children: updateChildren(oldChildren, row.children!, forgottenChildren: _forgottenChildren, slots: slots),
+        children: updateChildren(oldChildren, row.children, forgottenChildren: _forgottenChildren, slots: slots),
       ));
     }
     while (oldUnkeyedRows.moveNext()) {
@@ -385,7 +361,6 @@
   }
 
   void _updateRenderObjectChildren() {
-    assert(renderObject != null);
     renderObject.setFlatChildren(
       _children.isNotEmpty ? _children[0].children.length : 0,
       _children.expand<RenderBox>((_TableElementRow row) {
diff --git a/framework/lib/src/widgets/tap_and_drag_gestures.dart b/framework/lib/src/widgets/tap_and_drag_gestures.dart
new file mode 100644
index 0000000..6e99d1d
--- /dev/null
+++ b/framework/lib/src/widgets/tap_and_drag_gestures.dart
@@ -0,0 +1,1292 @@
+// 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:flute/foundation.dart';
+import 'package:flute/gestures.dart';
+import 'package:flute/services.dart' show HardwareKeyboard, LogicalKeyboardKey;
+
+double _getGlobalDistance(PointerEvent event, OffsetPair? originPosition) {
+  assert(originPosition != null);
+  final Offset offset = event.position - originPosition!.global;
+  return offset.distance;
+}
+
+// The possible states of a [TapAndDragGestureRecognizer].
+//
+// The recognizer advances from [ready] to [possible] when it starts tracking
+// a pointer in [TapAndDragGestureRecognizer.addAllowedPointer]. Where it advances
+// from there depends on the sequence of pointer events that is tracked by the
+// recognizer, following the initial [PointerDownEvent]:
+//
+// * If a [PointerUpEvent] has not been tracked, the recognizer stays in the [possible]
+//   state as long as it continues to track a pointer.
+// * If a [PointerMoveEvent] is tracked that has moved a sufficient global distance
+//   from the initial [PointerDownEvent] and it came before a [PointerUpEvent], then
+//   when this recognizer wins the arena, it will move from the [possible] state to [accepted].
+// * If a [PointerUpEvent] is tracked before the pointer has moved a sufficient global
+//   distance to be considered a drag, then this recognizer moves from the [possible]
+//   state to [ready].
+// * If a [PointerCancelEvent] is tracked then this recognizer moves from its current
+//   state to [ready].
+//
+// Once the recognizer has stopped tracking any remaining pointers, the recognizer
+// returns to the [ready] state.
+enum _DragState {
+  // The recognizer is ready to start recognizing a drag.
+  ready,
+
+  // The sequence of pointer events seen thus far is consistent with a drag but
+  // it has not been accepted definitively.
+  possible,
+
+  // The sequence of pointer events has been accepted definitively as a drag.
+  accepted,
+}
+
+/// {@macro flutter.gestures.tap.GestureTapDownCallback}
+///
+/// The consecutive tap count at the time the pointer contacted the
+/// screen is given by [TapDragDownDetails.consecutiveTapCount].
+///
+/// Used by [TapAndDragGestureRecognizer.onTapDown].
+typedef GestureTapDragDownCallback  = void Function(TapDragDownDetails details);
+
+/// Details for [GestureTapDragDownCallback], such as the number of
+/// consecutive taps.
+///
+/// See also:
+///
+///  * [TapAndDragGestureRecognizer], which passes this information to its
+///    [TapAndDragGestureRecognizer.onTapDown] callback.
+///  * [TapDragUpDetails], the details for [GestureTapDragUpCallback].
+///  * [TapDragStartDetails], the details for [GestureTapDragStartCallback].
+///  * [TapDragUpdateDetails], the details for [GestureTapDragUpdateCallback].
+///  * [TapDragEndDetails], the details for [GestureTapDragEndCallback].
+class TapDragDownDetails with Diagnosticable {
+  /// Creates details for a [GestureTapDragDownCallback].
+  ///
+  /// The [globalPosition], [localPosition], [consecutiveTapCount], and
+  /// [keysPressedOnDown] arguments must be provided and must not be null.
+  TapDragDownDetails({
+    required this.globalPosition,
+    required this.localPosition,
+    this.kind,
+    required this.consecutiveTapCount,
+    required this.keysPressedOnDown,
+  });
+
+  /// The global position at which the pointer contacted the screen.
+  final Offset globalPosition;
+
+  /// The local position at which the pointer contacted the screen.
+  final Offset localPosition;
+
+  /// The kind of the device that initiated the event.
+  final PointerDeviceKind? kind;
+
+  /// If this tap is in a series of taps, then this value represents
+  /// the number in the series this tap is.
+  final int consecutiveTapCount;
+
+  /// The keys that were pressed when the most recent [PointerDownEvent] occurred.
+  final Set<LogicalKeyboardKey> keysPressedOnDown;
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<Offset>('globalPosition', globalPosition));
+    properties.add(DiagnosticsProperty<Offset>('localPosition', localPosition));
+    properties.add(DiagnosticsProperty<PointerDeviceKind?>('kind', kind));
+    properties.add(DiagnosticsProperty<int>('consecutiveTapCount', consecutiveTapCount));
+    properties.add(DiagnosticsProperty<Set<LogicalKeyboardKey>>('keysPressedOnDown', keysPressedOnDown));
+  }
+}
+
+/// {@macro flutter.gestures.tap.GestureTapUpCallback}
+///
+/// The consecutive tap count at the time the pointer contacted the
+/// screen is given by [TapDragUpDetails.consecutiveTapCount].
+///
+/// Used by [TapAndDragGestureRecognizer.onTapUp].
+typedef GestureTapDragUpCallback  = void Function(TapDragUpDetails details);
+
+/// Details for [GestureTapDragUpCallback], such as the number of
+/// consecutive taps.
+///
+/// See also:
+///
+///  * [TapAndDragGestureRecognizer], which passes this information to its
+///    [TapAndDragGestureRecognizer.onTapUp] callback.
+///  * [TapDragDownDetails], the details for [GestureTapDragDownCallback].
+///  * [TapDragStartDetails], the details for [GestureTapDragStartCallback].
+///  * [TapDragUpdateDetails], the details for [GestureTapDragUpdateCallback].
+///  * [TapDragEndDetails], the details for [GestureTapDragEndCallback].
+class TapDragUpDetails with Diagnosticable {
+  /// Creates details for a [GestureTapDragUpCallback].
+  ///
+  /// The [kind], [globalPosition], [localPosition], [consecutiveTapCount], and
+  /// [keysPressedOnDown] arguments must be provided and must not be null.
+  TapDragUpDetails({
+    required this.kind,
+    required this.globalPosition,
+    required this.localPosition,
+    required this.consecutiveTapCount,
+    required this.keysPressedOnDown,
+  });
+
+  /// The global position at which the pointer contacted the screen.
+  final Offset globalPosition;
+
+  /// The local position at which the pointer contacted the screen.
+  final Offset localPosition;
+
+  /// The kind of the device that initiated the event.
+  final PointerDeviceKind kind;
+
+  /// If this tap is in a series of taps, then this value represents
+  /// the number in the series this tap is.
+  final int consecutiveTapCount;
+
+  /// The keys that were pressed when the most recent [PointerDownEvent] occurred.
+  final Set<LogicalKeyboardKey> keysPressedOnDown;
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<Offset>('globalPosition', globalPosition));
+    properties.add(DiagnosticsProperty<Offset>('localPosition', localPosition));
+    properties.add(DiagnosticsProperty<PointerDeviceKind?>('kind', kind));
+    properties.add(DiagnosticsProperty<int>('consecutiveTapCount', consecutiveTapCount));
+    properties.add(DiagnosticsProperty<Set<LogicalKeyboardKey>>('keysPressedOnDown', keysPressedOnDown));
+  }
+}
+
+/// {@macro flutter.gestures.dragdetails.GestureDragStartCallback}
+///
+/// The consecutive tap count at the time the pointer contacted the
+/// screen is given by [TapDragStartDetails.consecutiveTapCount].
+///
+/// Used by [TapAndDragGestureRecognizer.onDragStart].
+typedef GestureTapDragStartCallback = void Function(TapDragStartDetails details);
+
+/// Details for [GestureTapDragStartCallback], such as the number of
+/// consecutive taps.
+///
+/// See also:
+///
+///  * [TapAndDragGestureRecognizer], which passes this information to its
+///    [TapAndDragGestureRecognizer.onDragStart] callback.
+///  * [TapDragDownDetails], the details for [GestureTapDragDownCallback].
+///  * [TapDragUpDetails], the details for [GestureTapDragUpCallback].
+///  * [TapDragUpdateDetails], the details for [GestureTapDragUpdateCallback].
+///  * [TapDragEndDetails], the details for [GestureTapDragEndCallback].
+class TapDragStartDetails with Diagnosticable {
+  /// Creates details for a [GestureTapDragStartCallback].
+  ///
+  /// The [globalPosition], [localPosition], [consecutiveTapCount], and
+  /// [keysPressedOnDown] arguments must be provided and must not be null.
+  TapDragStartDetails({
+    this.sourceTimeStamp,
+    required this.globalPosition,
+    required this.localPosition,
+    this.kind,
+    required this.consecutiveTapCount,
+    required this.keysPressedOnDown,
+  });
+
+  /// Recorded timestamp of the source pointer event that triggered the drag
+  /// event.
+  ///
+  /// Could be null if triggered from proxied events such as accessibility.
+  final Duration? sourceTimeStamp;
+
+  /// The global position at which the pointer contacted the screen.
+  ///
+  /// See also:
+  ///
+  ///  * [localPosition], which is the [globalPosition] transformed to the
+  ///    coordinate space of the event receiver.
+  final Offset globalPosition;
+
+  /// The local position in the coordinate system of the event receiver at
+  /// which the pointer contacted the screen.
+  final Offset localPosition;
+
+  /// The kind of the device that initiated the event.
+  final PointerDeviceKind? kind;
+
+  /// If this tap is in a series of taps, then this value represents
+  /// the number in the series this tap is.
+  final int consecutiveTapCount;
+
+  /// The keys that were pressed when the most recent [PointerDownEvent] occurred.
+  final Set<LogicalKeyboardKey> keysPressedOnDown;
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<Duration?>('sourceTimeStamp', sourceTimeStamp));
+    properties.add(DiagnosticsProperty<Offset>('globalPosition', globalPosition));
+    properties.add(DiagnosticsProperty<Offset>('localPosition', localPosition));
+    properties.add(DiagnosticsProperty<PointerDeviceKind?>('kind', kind));
+    properties.add(DiagnosticsProperty<int>('consecutiveTapCount', consecutiveTapCount));
+    properties.add(DiagnosticsProperty<Set<LogicalKeyboardKey>>('keysPressedOnDown', keysPressedOnDown));
+  }
+}
+
+/// {@macro flutter.gestures.dragdetails.GestureDragUpdateCallback}
+///
+/// The consecutive tap count at the time the pointer contacted the
+/// screen is given by [TapDragUpdateDetails.consecutiveTapCount].
+///
+/// Used by [TapAndDragGestureRecognizer.onDragUpdate].
+typedef GestureTapDragUpdateCallback = void Function(TapDragUpdateDetails details);
+
+/// Details for [GestureTapDragUpdateCallback], such as the number of
+/// consecutive taps.
+///
+/// See also:
+///
+///  * [TapAndDragGestureRecognizer], which passes this information to its
+///    [TapAndDragGestureRecognizer.onDragUpdate] callback.
+///  * [TapDragDownDetails], the details for [GestureTapDragDownCallback].
+///  * [TapDragUpDetails], the details for [GestureTapDragUpCallback].
+///  * [TapDragStartDetails], the details for [GestureTapDragStartCallback].
+///  * [TapDragEndDetails], the details for [GestureTapDragEndCallback].
+class TapDragUpdateDetails with Diagnosticable {
+  /// Creates details for a [GestureTapDragUpdateCallback].
+  ///
+  /// The [delta] argument must not be null.
+  ///
+  /// If [primaryDelta] is non-null, then its value must match one of the
+  /// coordinates of [delta] and the other coordinate must be zero.
+  ///
+  /// The [globalPosition], [localPosition], [offsetFromOrigin], [localOffsetFromOrigin],
+  /// [consecutiveTapCount], and [keysPressedOnDown] arguments must be provided and must
+  /// not be null.
+  TapDragUpdateDetails({
+    this.sourceTimeStamp,
+    this.delta = Offset.zero,
+    this.primaryDelta,
+    required this.globalPosition,
+    this.kind,
+    required this.localPosition,
+    required this.offsetFromOrigin,
+    required this.localOffsetFromOrigin,
+    required this.consecutiveTapCount,
+    required this.keysPressedOnDown,
+  }) : assert(
+         primaryDelta == null
+           || (primaryDelta == delta.dx && delta.dy == 0.0)
+           || (primaryDelta == delta.dy && delta.dx == 0.0),
+       );
+
+  /// Recorded timestamp of the source pointer event that triggered the drag
+  /// event.
+  ///
+  /// Could be null if triggered from proxied events such as accessibility.
+  final Duration? sourceTimeStamp;
+
+  /// The amount the pointer has moved in the coordinate space of the event
+  /// receiver since the previous update.
+  ///
+  /// If the [GestureTapDragUpdateCallback] is for a one-dimensional drag (e.g.,
+  /// a horizontal or vertical drag), then this offset contains only the delta
+  /// in that direction (i.e., the coordinate in the other direction is zero).
+  ///
+  /// Defaults to zero if not specified in the constructor.
+  final Offset delta;
+
+  /// The amount the pointer has moved along the primary axis in the coordinate
+  /// space of the event receiver since the previous
+  /// update.
+  ///
+  /// If the [GestureTapDragUpdateCallback] is for a one-dimensional drag (e.g.,
+  /// a horizontal or vertical drag), then this value contains the component of
+  /// [delta] along the primary axis (e.g., horizontal or vertical,
+  /// respectively). Otherwise, if the [GestureTapDragUpdateCallback] is for a
+  /// two-dimensional drag (e.g., a pan), then this value is null.
+  ///
+  /// Defaults to null if not specified in the constructor.
+  final double? primaryDelta;
+
+  /// The pointer's global position when it triggered this update.
+  ///
+  /// See also:
+  ///
+  ///  * [localPosition], which is the [globalPosition] transformed to the
+  ///    coordinate space of the event receiver.
+  final Offset globalPosition;
+
+  /// The local position in the coordinate system of the event receiver at
+  /// which the pointer contacted the screen.
+  ///
+  /// Defaults to [globalPosition] if not specified in the constructor.
+  final Offset localPosition;
+
+  /// The kind of the device that initiated the event.
+  final PointerDeviceKind? kind;
+
+  /// A delta offset from the point where the drag initially contacted
+  /// the screen to the point where the pointer is currently located in global
+  /// coordinates (the present [globalPosition]) when this callback is triggered.
+  ///
+  /// When considering a [GestureRecognizer] that tracks the number of consecutive taps,
+  /// this offset is associated with the most recent [PointerDownEvent] that occured.
+  final Offset offsetFromOrigin;
+
+  /// A local delta offset from the point where the drag initially contacted
+  /// the screen to the point where the pointer is currently located in local
+  /// coordinates (the present [localPosition]) when this callback is triggered.
+  ///
+  /// When considering a [GestureRecognizer] that tracks the number of consecutive taps,
+  /// this offset is associated with the most recent [PointerDownEvent] that occured.
+  final Offset localOffsetFromOrigin;
+
+  /// If this tap is in a series of taps, then this value represents
+  /// the number in the series this tap is.
+  final int consecutiveTapCount;
+
+  /// The keys that were pressed when the most recent [PointerDownEvent] occurred.
+  final Set<LogicalKeyboardKey> keysPressedOnDown;
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<Duration?>('sourceTimeStamp', sourceTimeStamp));
+    properties.add(DiagnosticsProperty<Offset>('delta', delta));
+    properties.add(DiagnosticsProperty<double?>('primaryDelta', primaryDelta));
+    properties.add(DiagnosticsProperty<Offset>('globalPosition', globalPosition));
+    properties.add(DiagnosticsProperty<Offset>('localPosition', localPosition));
+    properties.add(DiagnosticsProperty<PointerDeviceKind?>('kind', kind));
+    properties.add(DiagnosticsProperty<Offset>('offsetFromOrigin', offsetFromOrigin));
+    properties.add(DiagnosticsProperty<Offset>('localOffsetFromOrigin', localOffsetFromOrigin));
+    properties.add(DiagnosticsProperty<int>('consecutiveTapCount', consecutiveTapCount));
+    properties.add(DiagnosticsProperty<Set<LogicalKeyboardKey>>('keysPressedOnDown', keysPressedOnDown));
+  }
+}
+
+/// {@macro flutter.gestures.monodrag.GestureDragEndCallback}
+///
+/// The consecutive tap count at the time the pointer contacted the
+/// screen is given by [TapDragEndDetails.consecutiveTapCount].
+///
+/// Used by [TapAndDragGestureRecognizer.onDragEnd].
+typedef GestureTapDragEndCallback = void Function(TapDragEndDetails endDetails);
+
+/// Details for [GestureTapDragEndCallback], such as the number of
+/// consecutive taps.
+///
+/// See also:
+///
+///  * [TapAndDragGestureRecognizer], which passes this information to its
+///    [TapAndDragGestureRecognizer.onDragEnd] callback.
+///  * [TapDragDownDetails], the details for [GestureTapDragDownCallback].
+///  * [TapDragUpDetails], the details for [GestureTapDragUpCallback].
+///  * [TapDragStartDetails], the details for [GestureTapDragStartCallback].
+///  * [TapDragUpdateDetails], the details for [GestureTapDragUpdateCallback].
+class TapDragEndDetails with Diagnosticable {
+  /// Creates details for a [GestureTapDragEndCallback].
+  ///
+  /// The [velocity] argument must not be null.
+  ///
+  /// The [consecutiveTapCount], and [keysPressedOnDown] arguments must
+  /// be provided and must not be null.
+  TapDragEndDetails({
+    this.velocity = Velocity.zero,
+    this.primaryVelocity,
+    required this.consecutiveTapCount,
+    required this.keysPressedOnDown,
+  }) : assert(
+         primaryVelocity == null
+           || primaryVelocity == velocity.pixelsPerSecond.dx
+           || primaryVelocity == velocity.pixelsPerSecond.dy,
+       );
+
+  /// The velocity the pointer was moving when it stopped contacting the screen.
+  ///
+  /// Defaults to zero if not specified in the constructor.
+  final Velocity velocity;
+
+  /// The velocity the pointer was moving along the primary axis when it stopped
+  /// contacting the screen, in logical pixels per second.
+  ///
+  /// If the [GestureTapDragEndCallback] is for a one-dimensional drag (e.g., a
+  /// horizontal or vertical drag), then this value contains the component of
+  /// [velocity] along the primary axis (e.g., horizontal or vertical,
+  /// respectively). Otherwise, if the [GestureTapDragEndCallback] is for a
+  /// two-dimensional drag (e.g., a pan), then this value is null.
+  ///
+  /// Defaults to null if not specified in the constructor.
+  final double? primaryVelocity;
+
+  /// If this tap is in a series of taps, then this value represents
+  /// the number in the series this tap is.
+  final int consecutiveTapCount;
+
+  /// The keys that were pressed when the most recent [PointerDownEvent] occurred.
+  final Set<LogicalKeyboardKey> keysPressedOnDown;
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(DiagnosticsProperty<Velocity>('velocity', velocity));
+    properties.add(DiagnosticsProperty<double?>('primaryVelocity', primaryVelocity));
+    properties.add(DiagnosticsProperty<int>('consecutiveTapCount', consecutiveTapCount));
+    properties.add(DiagnosticsProperty<Set<LogicalKeyboardKey>>('keysPressedOnDown', keysPressedOnDown));
+  }
+}
+
+/// Signature for when the pointer that previously triggered a
+/// [GestureTapDragDownCallback] did not complete.
+///
+/// Used by [TapAndDragGestureRecognizer.onCancel].
+typedef GestureCancelCallback = void Function();
+
+// A mixin for [OneSequenceGestureRecognizer] that tracks the number of taps
+// that occur in a series of [PointerEvent]s and the most recent set of
+// [LogicalKeyboardKey]s pressed on the most recent tap down.
+//
+// A tap is tracked as part of a series of taps if:
+//
+// 1. The elapsed time between when a [PointerUpEvent] and the subsequent
+// [PointerDownEvent] does not exceed [kDoubleTapTimeout].
+// 2. The delta between the position tapped in the global coordinate system
+// and the position that was tapped previously must be less than or equal
+// to [kDoubleTapSlop].
+//
+// This mixin's state, i.e. the series of taps being tracked is reset when
+// a tap is tracked that does not meet any of the specifications stated above.
+mixin _TapStatusTrackerMixin on OneSequenceGestureRecognizer {
+  // Public state available to [OneSequenceGestureRecognizer].
+
+  // The [PointerDownEvent] that was most recently tracked in [addAllowedPointer].
+  //
+  // This value will be null if a [PointerDownEvent] has not been tracked yet in
+  // [addAllowedPointer] or the timer between two taps has elapsed.
+  //
+  // This value is only reset when the timer between a [PointerUpEvent] and the
+  // [PointerDownEvent] times out or when a new [PointerDownEvent] is tracked in
+  // [addAllowedPointer].
+  PointerDownEvent? get currentDown => _down;
+
+  // The [PointerUpEvent] that was most recently tracked in [handleEvent].
+  //
+  // This value will be null if a [PointerUpEvent] has not been tracked yet in
+  // [handleEvent] or the timer between two taps has elapsed.
+  //
+  // This value is only reset when the timer between a [PointerUpEvent] and the
+  // [PointerDownEvent] times out or when a new [PointerDownEvent] is tracked in
+  // [addAllowedPointer].
+  PointerUpEvent? get currentUp => _up;
+
+  // The number of consecutive taps that the most recently tracked [PointerDownEvent]
+  // in [currentDown] represents.
+  //
+  // This value defaults to zero, meaning a tap series is not currently being tracked.
+  //
+  // When this value is greater than zero it means [addAllowedPointer] has run
+  // and at least one [PointerDownEvent] belongs to the current series of taps
+  // being tracked.
+  //
+  // [addAllowedPointer] will either increment this value by `1` or set the value to `1`
+  // depending if the new [PointerDownEvent] is determined to be in the same series as the
+  // tap that preceded it. If too much time has elapsed between two taps, the recognizer has lost
+  // in the arena, the gesture has been cancelled, or the recognizer is being disposed then
+  // this value will be set to `0`, and a new series will begin.
+  int get consecutiveTapCount => _consecutiveTapCount;
+
+  // The set of [LogicalKeyboardKey]s pressed when the most recent [PointerDownEvent]
+  // was tracked in [addAllowedPointer].
+  //
+  // This value defaults to an empty set.
+  //
+  // When the timer between two taps elapses, the recognizer loses the arena, the gesture is cancelled
+  // or the recognizer is disposed of then this value is reset.
+  Set<LogicalKeyboardKey> get keysPressedOnDown => _keysPressedOnDown ?? <LogicalKeyboardKey>{};
+
+  // The upper limit for the [consecutiveTapCount]. When this limit is reached
+  // all tap related state is reset and a new tap series is tracked.
+  //
+  // If this value is null, [consecutiveTapCount] can grow infinitely large.
+  int? get maxConsecutiveTap;
+
+  // The maximum distance in logical pixels the gesture is allowed to drift
+  // from the initial touch down position before the [consecutiveTapCount]
+  // and [keysPressedOnDown] are frozen and the remaining tracker state is
+  // reset. These values remain frozen until the next [PointerDownEvent] is
+  // tracked in [addAllowedPointer].
+  double? get slopTolerance;
+
+  // Private tap state tracked.
+  PointerDownEvent? _down;
+  PointerUpEvent? _up;
+  int _consecutiveTapCount = 0;
+  Set<LogicalKeyboardKey>? _keysPressedOnDown;
+
+  OffsetPair? _originPosition;
+  int? _previousButtons;
+
+  // For timing taps.
+  Timer? _consecutiveTapTimer;
+  Offset? _lastTapOffset;
+
+  // When tracking a tap, the [consecutiveTapCount] is incremented if the given tap
+  // falls under the tolerance specifications and reset to 1 if not.
+  @override
+  void addAllowedPointer(PointerDownEvent event) {
+    super.addAllowedPointer(event);
+    if (maxConsecutiveTap == _consecutiveTapCount) {
+      _tapTrackerReset();
+    }
+    _up = null;
+    if (_down != null && !_representsSameSeries(event)) {
+      // The given tap does not match the specifications of the series of taps being tracked,
+      // reset the tap count and related state.
+      _consecutiveTapCount = 1;
+    } else {
+      _consecutiveTapCount += 1;
+    }
+    _consecutiveTapTimerStop();
+    // `_down` must be assigned in this method instead of [handleEvent],
+    // because [acceptGesture] might be called before [handleEvent],
+    // which may rely on `_down` to initiate a callback.
+    _trackTap(event);
+  }
+
+  @override
+  void handleEvent(PointerEvent event) {
+    if (event is PointerMoveEvent) {
+      final bool isSlopPastTolerance = slopTolerance != null && _getGlobalDistance(event, _originPosition) > slopTolerance!;
+
+      if (isSlopPastTolerance) {
+        _consecutiveTapTimerStop();
+        _previousButtons = null;
+        _lastTapOffset = null;
+      }
+    } else if (event is PointerUpEvent) {
+      _up = event;
+      if (_down != null) {
+        _consecutiveTapTimerStop();
+        _consecutiveTapTimerStart();
+      }
+    } else if (event is PointerCancelEvent) {
+      _tapTrackerReset();
+    }
+  }
+
+  @override
+  void rejectGesture(int pointer) {
+    _tapTrackerReset();
+  }
+
+  @override
+  void dispose() {
+    _tapTrackerReset();
+    super.dispose();
+  }
+
+  void _trackTap(PointerDownEvent event) {
+    _down = event;
+    _keysPressedOnDown = HardwareKeyboard.instance.logicalKeysPressed;
+    _previousButtons = event.buttons;
+    _lastTapOffset = event.position;
+    _originPosition = OffsetPair(local: event.localPosition, global: event.position);
+  }
+
+  bool _hasSameButton(int buttons) {
+    assert(_previousButtons != null);
+    if (buttons == _previousButtons!) {
+      return true;
+    } else {
+      return false;
+    }
+  }
+
+  bool _isWithinConsecutiveTapTolerance(Offset secondTapOffset) {
+    if (_lastTapOffset == null) {
+      return false;
+    }
+
+    final Offset difference = secondTapOffset - _lastTapOffset!;
+    return difference.distance <= kDoubleTapSlop;
+  }
+
+  bool _representsSameSeries(PointerDownEvent event) {
+    return _consecutiveTapTimer != null
+        && _isWithinConsecutiveTapTolerance(event.position)
+        && _hasSameButton(event.buttons);
+  }
+
+  void _consecutiveTapTimerStart() {
+    _consecutiveTapTimer ??= Timer(kDoubleTapTimeout, _tapTrackerReset);
+  }
+
+  void _consecutiveTapTimerStop() {
+    if (_consecutiveTapTimer != null) {
+      _consecutiveTapTimer!.cancel();
+      _consecutiveTapTimer = null;
+    }
+  }
+
+  void _tapTrackerReset() {
+    // The timer has timed out, i.e. the time between a [PointerUpEvent] and the subsequent
+    // [PointerDownEvent] exceeded the duration of [kDoubleTapTimeout], so the tap belonging
+    // to the [PointerDownEvent] cannot be considered part of the same tap series as the
+    // previous [PointerUpEvent].
+    _consecutiveTapTimerStop();
+    _previousButtons = null;
+    _originPosition = null;
+    _lastTapOffset = null;
+    _consecutiveTapCount = 0;
+    _keysPressedOnDown = null;
+    _down = null;
+    _up = null;
+  }
+}
+
+/// Recognizes taps and movements.
+///
+/// Takes on the responsibilities of [TapGestureRecognizer] and
+/// [DragGestureRecognizer] in one [GestureRecognizer].
+///
+/// ### Gesture arena behavior
+///
+/// [TapAndDragGestureRecognizer] competes on the pointer events of
+/// [kPrimaryButton] only when it has at least one non-null `onTap*`
+/// or `onDrag*` callback.
+///
+/// It will declare defeat if it determines that a gesture is not a
+/// tap (e.g. if the pointer is dragged too far while it's contacting the
+/// screen) or a drag (e.g. if the pointer was not dragged far enough to
+/// be considered a drag.
+///
+/// This recognizer will not immediately declare victory for every tap or drag that it
+/// recognizes.
+///
+/// The recognizer will declare victory when all other recognizer's in
+/// the arena have lost, if the timer of [kPressTimeout] elapses and a tap
+/// series greater than 1 is being tracked.
+///
+/// If this recognizer loses the arena (either by declaring defeat or by
+/// another recognizer declaring victory) while the pointer is contacting the
+/// screen, it will fire [onCancel] instead of [onTapUp] or [onDragEnd].
+///
+/// ### When competing with `TapGestureRecognizer` and `DragGestureRecognizer`
+///
+/// Similar to [TapGestureRecognizer] and [DragGestureRecognizer],
+/// [TapAndDragGestureRecognizer] will not aggresively declare victory when it detects
+/// a tap, so when it is competing with those gesture recognizers and others it has a chance
+/// of losing.
+///
+/// When competing against [TapGestureRecognizer], if the pointer does not move past the tap
+/// tolerance, then the recognizer that entered the arena first will win. In this case the
+/// gesture detected is a tap. If the pointer does travel past the tap tolerance then this
+/// recognizer will be declared winner by default. The gesture detected in this case is a drag.
+///
+/// When competing against [DragGestureRecognizer], if the pointer does not move a sufficient
+/// global distance to be considered a drag, the recognizers will tie in the arena. If the
+/// pointer does travel enough distance then the [TapAndDragGestureRecognizer] will lose because
+/// the [DragGestureRecognizer] will declare self-victory when the drag threshold is met.
+class TapAndDragGestureRecognizer extends OneSequenceGestureRecognizer with _TapStatusTrackerMixin {
+  /// Creates a tap and drag gesture recognizer.
+  ///
+  /// {@macro flutter.gestures.GestureRecognizer.supportedDevices}
+  TapAndDragGestureRecognizer({
+    super.debugOwner,
+    super.supportedDevices,
+    super.allowedButtonsFilter,
+  }) : _deadline = kPressTimeout,
+      dragStartBehavior = DragStartBehavior.start,
+      slopTolerance = kTouchSlop;
+
+  /// Configure the behavior of offsets passed to [onDragStart].
+  ///
+  /// If set to [DragStartBehavior.start], the [onDragStart] callback will be called
+  /// with the position of the pointer at the time this gesture recognizer won
+  /// the arena. If [DragStartBehavior.down], [onDragStart] will be called with
+  /// the position of the first detected down event for the pointer. When there
+  /// are no other gestures competing with this gesture in the arena, there's
+  /// no difference in behavior between the two settings.
+  ///
+  /// For more information about the gesture arena:
+  /// https://flutter.dev/docs/development/ui/advanced/gestures#gesture-disambiguation
+  ///
+  /// By default, the drag start behavior is [DragStartBehavior.start].
+  ///
+  /// See also:
+  ///
+  ///  * [DragGestureRecognizer.dragStartBehavior], which includes more details and an example.
+  DragStartBehavior dragStartBehavior;
+
+  /// The frequency at which the [onDragUpdate] callback is called.
+  ///
+  /// The value defaults to null, meaning there is no delay for [onDragUpdate] callback.
+  ///
+  /// See also:
+  ///   * [TextSelectionGestureDetector], which uses this parameter to avoid excessive updates
+  ///     text layouts in text fields.
+  Duration? dragUpdateThrottleFrequency;
+
+  /// An upper bound for the amount of taps that can belong to one tap series.
+  ///
+  /// When this limit is reached the series of taps being tracked by this
+  /// recognizer will be reset.
+  @override
+  int? maxConsecutiveTap;
+
+  // The maximum distance in logical pixels the gesture is allowed to drift
+  // to still be considered a tap.
+  //
+  // Drifting past the allowed slop amount causes the recognizer to reset
+  // the tap series it is currently tracking, stopping the consecutive tap
+  // count from increasing. The consecutive tap count and the set of hardware
+  // keys that were pressed on tap down will retain their pre-past slop
+  // tolerance values until the next [PointerDownEvent] is tracked.
+  //
+  // If the gesture exceeds this value, then it can only be accepted as a drag
+  // gesture.
+  //
+  // Can be null to indicate that the gesture can drift for any distance.
+  // Defaults to 18 logical pixels.
+  @override
+  final double? slopTolerance;
+
+  /// {@macro flutter.gestures.tap.TapGestureRecognizer.onTapDown}
+  ///
+  /// This triggers after the down event, once a short timeout ([kPressTimeout]) has
+  /// elapsed, or once the gestures has won the arena, whichever comes first.
+  ///
+  /// The position of the pointer is provided in the callback's `details`
+  /// argument, which is a [TapDragDownDetails] object.
+  ///
+  /// {@template flutter.gestures.selectionrecognizers.TapAndDragGestureRecognizer.tapStatusTrackerData}
+  /// The number of consecutive taps, and the keys that were pressed on tap down
+  /// are also provided in the callback's `details` argument.
+  /// {@endtemplate}
+  ///
+  /// See also:
+  ///
+  ///  * [kPrimaryButton], the button this callback responds to.
+  ///  * [TapDragDownDetails], which is passed as an argument to this callback.
+  GestureTapDragDownCallback? onTapDown;
+
+  /// {@macro flutter.gestures.tap.TapGestureRecognizer.onTapUp}
+  ///
+  /// This triggers on the up event, if the recognizer wins the arena with it
+  /// or has previously won.
+  ///
+  /// The position of the pointer is provided in the callback's `details`
+  /// argument, which is a [TapDragUpDetails] object.
+  ///
+  /// {@macro flutter.gestures.selectionrecognizers.TapAndDragGestureRecognizer.tapStatusTrackerData}
+  ///
+  /// See also:
+  ///
+  ///  * [kPrimaryButton], the button this callback responds to.
+  ///  * [TapDragUpDetails], which is passed as an argument to this callback.
+  GestureTapDragUpCallback? onTapUp;
+
+  /// {@macro flutter.gestures.monodrag.DragGestureRecognizer.onStart}
+  ///
+  /// The position of the pointer is provided in the callback's `details`
+  /// argument, which is a [TapDragStartDetails] object. The [dragStartBehavior]
+  /// determines this position.
+  ///
+  /// {@macro flutter.gestures.selectionrecognizers.TapAndDragGestureRecognizer.tapStatusTrackerData}
+  ///
+  /// See also:
+  ///
+  ///  * [kPrimaryButton], the button this callback responds to.
+  ///  * [TapDragStartDetails], which is passed as an argument to this callback.
+  GestureTapDragStartCallback? onDragStart;
+
+  /// {@macro flutter.gestures.monodrag.DragGestureRecognizer.onUpdate}
+  ///
+  /// The distance traveled by the pointer since the last update is provided in
+  /// the callback's `details` argument, which is a [TapDragUpdateDetails] object.
+  ///
+  /// {@macro flutter.gestures.selectionrecognizers.TapAndDragGestureRecognizer.tapStatusTrackerData}
+  ///
+  /// See also:
+  ///
+  ///  * [kPrimaryButton], the button this callback responds to.
+  ///  * [TapDragUpdateDetails], which is passed as an argument to this callback.
+  GestureTapDragUpdateCallback? onDragUpdate;
+
+  /// {@macro flutter.gestures.monodrag.DragGestureRecognizer.onEnd}
+  ///
+  /// The velocity is provided in the callback's `details` argument, which is a
+  /// [TapDragEndDetails] object.
+  ///
+  /// {@macro flutter.gestures.selectionrecognizers.TapAndDragGestureRecognizer.tapStatusTrackerData}
+  ///
+  /// See also:
+  ///
+  ///  * [kPrimaryButton], the button this callback responds to.
+  ///  * [TapDragEndDetails], which is passed as an argument to this callback.
+  GestureTapDragEndCallback? onDragEnd;
+
+  /// The pointer that previously triggered [onTapDown] did not complete.
+  ///
+  /// This is called when a [PointerCancelEvent] is tracked when the [onTapDown] callback
+  /// was previously called.
+  ///
+  /// It may also be called if a [PointerUpEvent] is tracked after the pointer has moved
+  /// past the tap tolerance but not past the drag tolerance, and the recognizer has not
+  /// yet won the arena.
+  ///
+  /// See also:
+  ///
+  ///  * [kPrimaryButton], the button this callback responds to.
+  GestureCancelCallback? onCancel;
+
+  // Tap related state.
+  bool _pastSlopTolerance = false;
+  bool _sentTapDown = false;
+  bool _wonArenaForPrimaryPointer = false;
+
+  // Primary pointer being tracked by this recognizer.
+  int? _primaryPointer;
+  Timer? _deadlineTimer;
+  // The recognizer will call [onTapDown] after this amount of time has elapsed
+  // since starting to track the primary pointer.
+  //
+  // [onTapDown] will not be called if the primary pointer is
+  // accepted, rejected, or all pointers are up or canceled before [_deadline].
+  final Duration _deadline;
+
+  // Drag related state.
+  _DragState _dragState = _DragState.ready;
+  PointerEvent? _start;
+  late OffsetPair _initialPosition;
+  late double _globalDistanceMoved;
+  OffsetPair? _correctedPosition;
+
+  // For drag update throttle.
+  TapDragUpdateDetails? _lastDragUpdateDetails;
+  Timer? _dragUpdateThrottleTimer;
+
+  final Set<int> _acceptedActivePointers = <int>{};
+
+  bool _hasSufficientGlobalDistanceToAccept(PointerDeviceKind pointerDeviceKind, double? deviceTouchSlop) {
+    return _globalDistanceMoved.abs() > computePanSlop(pointerDeviceKind, gestureSettings);
+  }
+
+  // Drag updates may require throttling to avoid excessive updating, such as for text layouts in text
+  // fields. The frequency of invocations is controlled by the [dragUpdateThrottleFrequency].
+  //
+  // Once the drag gesture ends, any pending drag update will be fired
+  // immediately. See [_checkDragEnd].
+  void _handleDragUpdateThrottled() {
+    assert(_lastDragUpdateDetails != null);
+    if (onDragUpdate != null) {
+      invokeCallback<void>('onDragUpdate', () => onDragUpdate!(_lastDragUpdateDetails!));
+    }
+    _dragUpdateThrottleTimer = null;
+    _lastDragUpdateDetails = null;
+  }
+
+  @override
+  bool isPointerAllowed(PointerEvent event) {
+    if (_primaryPointer == null) {
+      switch (event.buttons) {
+        case kPrimaryButton:
+          if (onTapDown == null &&
+              onDragStart == null &&
+              onDragUpdate == null &&
+              onDragEnd == null &&
+              onTapUp == null &&
+              onCancel == null) {
+            return false;
+          }
+          break;
+        default:
+          return false;
+      }
+    } else {
+      if (event.pointer != _primaryPointer) {
+        return false;
+      }
+    }
+
+    return super.isPointerAllowed(event as PointerDownEvent);
+  }
+
+  @override
+  void addAllowedPointer(PointerDownEvent event) {
+    if (_dragState == _DragState.ready) {
+      super.addAllowedPointer(event);
+      _primaryPointer = event.pointer;
+      _globalDistanceMoved = 0.0;
+      _dragState = _DragState.possible;
+      _initialPosition = OffsetPair(global: event.position, local: event.localPosition);
+      _deadlineTimer = Timer(_deadline, () => _didExceedDeadlineWithEvent(event));
+    }
+  }
+
+  @override
+  void handleNonAllowedPointer(PointerDownEvent event) {
+    // There can be multiple drags simultaneously. Their effects are combined.
+    if (event.buttons != kPrimaryButton) {
+      if (!_wonArenaForPrimaryPointer) {
+        super.handleNonAllowedPointer(event);
+      }
+    }
+  }
+
+  @override
+  void acceptGesture(int pointer) {
+    if (pointer != _primaryPointer) {
+      return;
+    }
+
+    _stopDeadlineTimer();
+
+    assert(!_acceptedActivePointers.contains(pointer));
+    _acceptedActivePointers.add(pointer);
+
+    // Called when this recognizer is accepted by the [GestureArena].
+    if (currentDown != null) {
+      _checkTapDown(currentDown!);
+    }
+
+    _wonArenaForPrimaryPointer = true;
+
+    if (_start != null) {
+      _acceptDrag(_start!);
+    }
+
+    if (currentUp != null) {
+      _checkTapUp(currentUp!);
+    }
+  }
+
+  @override
+  void didStopTrackingLastPointer(int pointer) {
+    switch (_dragState) {
+      case _DragState.ready:
+        _checkCancel();
+        resolve(GestureDisposition.rejected);
+        break;
+
+      case _DragState.possible:
+        if (_pastSlopTolerance) {
+          // This means the pointer was not accepted as a tap.
+          if (_wonArenaForPrimaryPointer) {
+            // If the recognizer has already won the arena for the primary pointer being tracked
+            // but the pointer has exceeded the tap tolerance, then the pointer is accepted as a
+            // drag gesture.
+            if (currentDown != null) {
+              _acceptDrag(currentDown!);
+              _checkDragEnd();
+            }
+          } else {
+            _checkCancel();
+            resolve(GestureDisposition.rejected);
+          }
+        } else {
+          // The pointer is accepted as a tap.
+          if (currentUp != null) {
+            _checkTapUp(currentUp!);
+          }
+        }
+        break;
+
+      case _DragState.accepted:
+        // For the case when the pointer has been accepted as a drag.
+        // Meaning [_checkTapDown] and [_checkDragStart] have already ran.
+        _checkDragEnd();
+        break;
+    }
+
+    _stopDeadlineTimer();
+    _dragState = _DragState.ready;
+    _pastSlopTolerance = false;
+  }
+
+  @override
+  void handleEvent(PointerEvent event) {
+    if (event.pointer != _primaryPointer) {
+      return;
+    }
+    super.handleEvent(event);
+    if (event is PointerMoveEvent) {
+      // Receiving a [PointerMoveEvent], does not automatically mean the pointer
+      // being tracked is doing a drag gesture. There is some drift that can happen
+      // between the initial [PointerDownEvent] and subsequent [PointerMoveEvent]s.
+      // Accessing [_pastSlopTolerance] lets us know if our tap has moved past the
+      // acceptable tolerance. If the pointer does not move past this tolerance than
+      // it is not considered a drag.
+      //
+      // To be recognized as a drag, the [PointerMoveEvent] must also have moved
+      // a sufficient global distance from the initial [PointerDownEvent] to be
+      // accepted as a drag. This logic is handled in [_hasSufficientGlobalDistanceToAccept].
+      //
+      // The recognizer will also detect the gesture as a drag when the pointer
+      // has been accepted and it has moved past the [slopTolerance] but has not moved
+      // a sufficient global distance from the initial position to be considered a drag.
+      // In this case since the gesture cannot be a tap, it defaults to a drag.
+
+      _pastSlopTolerance = _pastSlopTolerance || slopTolerance != null && _getGlobalDistance(event, _initialPosition) > slopTolerance!;
+
+      if (_dragState == _DragState.accepted) {
+        _checkDragUpdate(event);
+      } else if (_dragState == _DragState.possible) {
+        if (_start == null) {
+          // Only check for a drag if the start of a drag was not already identified.
+          _checkDrag(event);
+        }
+
+        // This can occur when the recognizer is accepted before a [PointerMoveEvent] has been
+        // received that moves the pointer a sufficient global distance to be considered a drag.
+        if (_start != null) {
+          _acceptDrag(_start!);
+        }
+      }
+    } else if (event is PointerUpEvent) {
+      if (_dragState == _DragState.possible) {
+        // The drag has not been accepted before a [PointerUpEvent], therefore the recognizer
+        // attempts to recognize a tap.
+        stopTrackingIfPointerNoLongerDown(event);
+      } else if (_dragState == _DragState.accepted) {
+        _giveUpPointer(event.pointer);
+      }
+    } else if (event is PointerCancelEvent) {
+      _dragState = _DragState.ready;
+      _giveUpPointer(event.pointer);
+    }
+  }
+
+  @override
+  void rejectGesture(int pointer) {
+    if (pointer != _primaryPointer) {
+      return;
+    }
+    super.rejectGesture(pointer);
+
+    _stopDeadlineTimer();
+    _giveUpPointer(pointer);
+    _resetTaps();
+    _resetDragUpdateThrottle();
+  }
+
+  @override
+  void dispose() {
+    _stopDeadlineTimer();
+    _resetDragUpdateThrottle();
+    super.dispose();
+  }
+
+  @override
+  String get debugDescription => 'tap_and_drag';
+
+  void _acceptDrag(PointerEvent event) {
+    if (!_wonArenaForPrimaryPointer) {
+      return;
+    }
+    _dragState = _DragState.accepted;
+    if (dragStartBehavior == DragStartBehavior.start) {
+      _initialPosition = _initialPosition + OffsetPair(global: event.delta, local: event.localDelta);
+    }
+    _checkDragStart(event);
+    if (event.localDelta != Offset.zero) {
+      final Matrix4? localToGlobal = event.transform != null ? Matrix4.tryInvert(event.transform!) : null;
+      final Offset correctedLocalPosition = _initialPosition.local + event.localDelta;
+      final Offset globalUpdateDelta = PointerEvent.transformDeltaViaPositions(
+        untransformedEndPosition: correctedLocalPosition,
+        untransformedDelta: event.localDelta,
+        transform: localToGlobal,
+      );
+      final OffsetPair updateDelta = OffsetPair(local: event.localDelta, global: globalUpdateDelta);
+      _correctedPosition = _initialPosition + updateDelta; // Only adds delta for down behaviour
+      _checkDragUpdate(event);
+      _correctedPosition = null;
+    }
+  }
+
+  void _checkDrag(PointerMoveEvent event) {
+    final Matrix4? localToGlobalTransform = event.transform == null ? null : Matrix4.tryInvert(event.transform!);
+    _globalDistanceMoved += PointerEvent.transformDeltaViaPositions(
+      transform: localToGlobalTransform,
+      untransformedDelta: event.localDelta,
+      untransformedEndPosition: event.localPosition
+    ).distance * 1.sign;
+    if (_hasSufficientGlobalDistanceToAccept(event.kind, gestureSettings?.touchSlop)) {
+      _start = event;
+    }
+  }
+
+  void _checkTapDown(PointerDownEvent event) {
+    if (_sentTapDown) {
+      return;
+    }
+
+    final TapDragDownDetails details = TapDragDownDetails(
+      globalPosition: event.position,
+      localPosition: event.localPosition,
+      kind: getKindForPointer(event.pointer),
+      consecutiveTapCount: consecutiveTapCount,
+      keysPressedOnDown: keysPressedOnDown,
+    );
+
+    if (onTapDown != null) {
+      invokeCallback('onTapDown', () => onTapDown!(details));
+    }
+
+    _sentTapDown = true;
+  }
+
+  void _checkTapUp(PointerUpEvent event) {
+    if (!_wonArenaForPrimaryPointer) {
+      return;
+    }
+
+    final TapDragUpDetails upDetails = TapDragUpDetails(
+      kind: event.kind,
+      globalPosition: event.position,
+      localPosition: event.localPosition,
+      consecutiveTapCount: consecutiveTapCount,
+      keysPressedOnDown: keysPressedOnDown,
+    );
+
+    if (onTapUp != null) {
+      invokeCallback('onTapUp', () => onTapUp!(upDetails));
+    }
+
+    _resetTaps();
+    if (!_acceptedActivePointers.remove(event.pointer)) {
+      resolvePointer(event.pointer, GestureDisposition.rejected);
+    }
+  }
+
+  void _checkDragStart(PointerEvent event) {
+    if (onDragStart != null) {
+      final TapDragStartDetails details = TapDragStartDetails(
+        sourceTimeStamp: event.timeStamp,
+        globalPosition: _initialPosition.global,
+        localPosition: _initialPosition.local,
+        kind: getKindForPointer(event.pointer),
+        consecutiveTapCount: consecutiveTapCount,
+        keysPressedOnDown: keysPressedOnDown,
+      );
+
+      invokeCallback<void>('onDragStart', () => onDragStart!(details));
+    }
+
+    _start = null;
+  }
+
+  void _checkDragUpdate(PointerEvent event) {
+    final Offset globalPosition = _correctedPosition != null ? _correctedPosition!.global : event.position;
+    final Offset localPosition = _correctedPosition != null ? _correctedPosition!.local : event.localPosition;
+
+    final TapDragUpdateDetails details =  TapDragUpdateDetails(
+      sourceTimeStamp: event.timeStamp,
+      delta: event.localDelta,
+      globalPosition: globalPosition,
+      kind: getKindForPointer(event.pointer),
+      localPosition: localPosition,
+      offsetFromOrigin: globalPosition - _initialPosition.global,
+      localOffsetFromOrigin: localPosition - _initialPosition.local,
+      consecutiveTapCount: consecutiveTapCount,
+      keysPressedOnDown: keysPressedOnDown,
+    );
+
+    if (dragUpdateThrottleFrequency != null) {
+      _lastDragUpdateDetails = details;
+      // Only schedule a new timer if there's not one pending.
+      _dragUpdateThrottleTimer ??= Timer(dragUpdateThrottleFrequency!, _handleDragUpdateThrottled);
+    } else {
+      if (onDragUpdate != null) {
+        invokeCallback<void>('onDragUpdate', () => onDragUpdate!(details));
+      }
+    }
+  }
+
+  void _checkDragEnd() {
+    if (_dragUpdateThrottleTimer != null) {
+      // If there's already an update scheduled, trigger it immediately and
+      // cancel the timer.
+      _dragUpdateThrottleTimer!.cancel();
+      _handleDragUpdateThrottled();
+    }
+
+    final TapDragEndDetails endDetails =
+      TapDragEndDetails(
+        primaryVelocity: 0.0,
+        consecutiveTapCount: consecutiveTapCount,
+        keysPressedOnDown: keysPressedOnDown,
+      );
+
+    invokeCallback<void>('onDragEnd', () => onDragEnd!(endDetails));
+
+    _resetTaps();
+    _resetDragUpdateThrottle();
+  }
+
+  void _checkCancel() {
+    if (!_sentTapDown) {
+      // Do not fire tap cancel if [onTapDown] was never called.
+      return;
+    }
+    if (onCancel != null) {
+      invokeCallback('onCancel', onCancel!);
+    }
+    _resetDragUpdateThrottle();
+    _resetTaps();
+  }
+
+  void _didExceedDeadlineWithEvent(PointerDownEvent event) {
+    _didExceedDeadline();
+  }
+
+  void _didExceedDeadline() {
+    if (currentDown != null) {
+      _checkTapDown(currentDown!);
+
+      if (consecutiveTapCount > 1) {
+        // If our consecutive tap count is greater than 1, i.e. is a double tap or greater,
+        // then this recognizer declares victory to prevent the [LongPressGestureRecognizer]
+        // from declaring itself the winner if a double tap is held for too long.
+        resolve(GestureDisposition.accepted);
+      }
+    }
+  }
+
+  void _giveUpPointer(int pointer) {
+    stopTrackingPointer(pointer);
+    // If the pointer was never accepted, then it is rejected since this recognizer is no longer
+    // interested in winning the gesture arena for it.
+    if (!_acceptedActivePointers.remove(pointer)) {
+      resolvePointer(pointer, GestureDisposition.rejected);
+    }
+  }
+
+  void _resetTaps() {
+    _sentTapDown = false;
+    _wonArenaForPrimaryPointer = false;
+    _primaryPointer = null;
+  }
+
+  void _resetDragUpdateThrottle() {
+    if (dragUpdateThrottleFrequency == null) {
+      return;
+    }
+    _lastDragUpdateDetails = null;
+    if (_dragUpdateThrottleTimer != null) {
+      _dragUpdateThrottleTimer!.cancel();
+      _dragUpdateThrottleTimer = null;
+    }
+  }
+
+  void _stopDeadlineTimer() {
+    if (_deadlineTimer != null) {
+      _deadlineTimer!.cancel();
+      _deadlineTimer = null;
+    }
+  }
+}
diff --git a/framework/lib/src/widgets/text.dart b/framework/lib/src/widgets/text.dart
index 7669a55..8115435 100644
--- a/framework/lib/src/widgets/text.dart
+++ b/framework/lib/src/widgets/text.dart
@@ -49,12 +49,7 @@
     this.textWidthBasis = TextWidthBasis.parent,
     this.textHeightBehavior,
     required super.child,
-  }) : assert(style != null),
-       assert(softWrap != null),
-       assert(overflow != null),
-       assert(maxLines == null || maxLines > 0),
-       assert(child != null),
-       assert(textWidthBasis != null);
+  }) : assert(maxLines == null || maxLines > 0);
 
   /// A const-constructable default text style that provides fallback values.
   ///
@@ -98,7 +93,6 @@
     TextWidthBasis? textWidthBasis,
     required Widget child,
   }) {
-    assert(child != null);
     return Builder(
       builder: (BuildContext context) {
         final DefaultTextStyle parent = DefaultTextStyle.of(context);
@@ -239,8 +233,7 @@
     super.key,
     required this.textHeightBehavior,
     required super.child,
-  }) :  assert(textHeightBehavior != null),
-        assert(child != null);
+  });
 
   /// {@macro dart.ui.textHeightBehavior}
   final TextHeightBehavior textHeightBehavior;
@@ -440,11 +433,7 @@
     this.textWidthBasis,
     this.textHeightBehavior,
     this.selectionColor,
-  }) : assert(
-         data != null,
-         'A non-null String must be provided to a Text widget.',
-       ),
-       textSpan = null;
+  }) : textSpan = null;
 
   /// Creates a text widget with a [InlineSpan].
   ///
@@ -472,11 +461,7 @@
     this.textWidthBasis,
     this.textHeightBehavior,
     this.selectionColor,
-  }) : assert(
-         textSpan != null,
-         'A non-null TextSpan must be provided to a Text.rich widget.',
-       ),
-       data = null;
+  }) : data = null;
 
   /// The text to display.
   ///
@@ -598,7 +583,7 @@
     if (style == null || style!.inherit) {
       effectiveTextStyle = defaultTextStyle.style.merge(style);
     }
-    if (MediaQuery.boldTextOverride(context)) {
+    if (MediaQuery.boldTextOf(context)) {
       effectiveTextStyle = effectiveTextStyle!.merge(const TextStyle(fontWeight: FontWeight.bold));
     }
     final SelectionRegistrar? registrar = SelectionContainer.maybeOf(context);
diff --git a/framework/lib/src/widgets/text_editing_intents.dart b/framework/lib/src/widgets/text_editing_intents.dart
index eb12b41..80b2588 100644
--- a/framework/lib/src/widgets/text_editing_intents.dart
+++ b/framework/lib/src/widgets/text_editing_intents.dart
@@ -137,11 +137,11 @@
 /// reverse.
 ///
 /// This is typically only used on MacOS.
-class ExtendSelectionToNextWordBoundaryOrCaretLocationIntent extends DirectionalTextEditingIntent {
+class ExtendSelectionToNextWordBoundaryOrCaretLocationIntent extends DirectionalCaretMovementIntent {
   /// Creates an [ExtendSelectionToNextWordBoundaryOrCaretLocationIntent].
   const ExtendSelectionToNextWordBoundaryOrCaretLocationIntent({
     required bool forward,
-  }) : super(forward);
+  }) : super(forward, false, true);
 }
 
 /// Expands the current selection to the document boundary in the direction
@@ -154,11 +154,11 @@
 ///
 ///   [ExtendSelectionToDocumentBoundaryIntent], which is similar but always
 ///   moves the extent.
-class ExpandSelectionToDocumentBoundaryIntent extends DirectionalTextEditingIntent {
+class ExpandSelectionToDocumentBoundaryIntent extends DirectionalCaretMovementIntent {
   /// Creates an [ExpandSelectionToDocumentBoundaryIntent].
   const ExpandSelectionToDocumentBoundaryIntent({
     required bool forward,
-  }) : super(forward);
+  }) : super(forward, false);
 }
 
 /// Expands the current selection to the closest line break in the direction
@@ -173,11 +173,11 @@
 ///
 ///   [ExtendSelectionToLineBreakIntent], which is similar but always moves the
 ///   extent.
-class ExpandSelectionToLineBreakIntent extends DirectionalTextEditingIntent {
+class ExpandSelectionToLineBreakIntent extends DirectionalCaretMovementIntent {
   /// Creates an [ExpandSelectionToLineBreakIntent].
   const ExpandSelectionToLineBreakIntent({
     required bool forward,
-  }) : super(forward);
+  }) : super(forward, false);
 }
 
 /// Extends, or moves the current selection from the current
@@ -222,6 +222,21 @@
 }
 
 /// Extends, or moves the current selection from the current
+/// [TextSelection.extent] position to the previous or the next paragraph
+/// boundary depending on the [forward] parameter.
+///
+/// This [Intent] collapses the selection when the order of [TextSelection.base]
+/// and [TextSelection.extent] would reverse.
+///
+/// This is typically only used on MacOS.
+class ExtendSelectionToNextParagraphBoundaryOrCaretLocationIntent extends DirectionalCaretMovementIntent {
+  /// Creates an [ExtendSelectionToNextParagraphBoundaryOrCaretLocationIntent].
+  const ExtendSelectionToNextParagraphBoundaryOrCaretLocationIntent({
+    required bool forward,
+  }) : super(forward, false, true);
+}
+
+/// Extends, or moves the current selection from the current
 /// [TextSelection.extent] position to the start or the end of the document.
 ///
 /// See also:
diff --git a/framework/lib/src/widgets/text_selection.dart b/framework/lib/src/widgets/text_selection.dart
index fc5eb5b..72d971b 100644
--- a/framework/lib/src/widgets/text_selection.dart
+++ b/framework/lib/src/widgets/text_selection.dart
@@ -24,6 +24,7 @@
 import 'magnifier.dart';
 import 'overlay.dart';
 import 'scrollable.dart';
+import 'tap_and_drag_gestures.dart';
 import 'tap_region.dart';
 import 'ticker_provider.dart';
 import 'transitions.dart';
@@ -35,19 +36,6 @@
 /// called.
 const Duration _kDragSelectionUpdateThrottle = Duration(milliseconds: 50);
 
-/// Signature for when a pointer that's dragging to select text has moved again.
-///
-/// The first argument [startDetails] contains the details of the event that
-/// initiated the dragging.
-///
-/// The second argument [updateDetails] contains the details of the current
-/// pointer movement. It's the same as the one passed to [DragGestureRecognizer.onUpdate].
-///
-/// This signature is different from [GestureDragUpdateCallback] to make it
-/// easier for various text fields to use [TextSelectionGestureDetector] without
-/// having to store the start position.
-typedef DragSelectionUpdateCallback = void Function(DragStartDetails startDetails, DragUpdateDetails updateDetails);
-
 /// The type for a Function that builds a toolbar's container with the given
 /// child.
 ///
@@ -332,10 +320,7 @@
     ClipboardStatusNotifier? clipboardStatus,
     this.contextMenuBuilder,
     required TextMagnifierConfiguration magnifierConfiguration,
-  }) : assert(value != null),
-       assert(context != null),
-       assert(handlesVisible != null),
-       _handlesVisible = handlesVisible,
+  }) : _handlesVisible = handlesVisible,
        _value = value {
     renderObject.selectionStartInViewport.addListener(_updateTextSelectionOverlayVisibilities);
     renderObject.selectionEndInViewport.addListener(_updateTextSelectionOverlayVisibilities);
@@ -431,7 +416,6 @@
   bool get handlesVisible => _handlesVisible;
   bool _handlesVisible = false;
   set handlesVisible(bool visible) {
-    assert(visible != null);
     if (_handlesVisible == visible) {
       return;
     }
@@ -469,6 +453,20 @@
     return;
   }
 
+  /// Shows toolbar with spell check suggestions of misspelled words that are
+  /// available for click-and-replace.
+  void showSpellCheckSuggestionsToolbar(
+    WidgetBuilder spellCheckSuggestionsToolbarBuilder
+  ) {
+    _updateSelectionOverlay();
+    assert(context.mounted);
+    _selectionOverlay
+      .showSpellCheckSuggestionsToolbar(
+        context: context,
+        builder: spellCheckSuggestionsToolbarBuilder,
+    );
+  }
+
   /// {@macro flutter.widgets.SelectionOverlay.showMagnifier}
   void showMagnifier(Offset positionToShow) {
     final TextPosition position = renderObject.getPositionForPoint(positionToShow);
@@ -595,7 +593,7 @@
     // widget.renderObject.getRectForComposingRange might fail. In cases where
     // the current frame is different from the previous we fall back to
     // renderObject.preferredLineHeight.
-    if (renderObject.plainText == currText && _selection != null && _selection.isValid && !_selection.isCollapsed) {
+    if (renderObject.plainText == currText && _selection.isValid && !_selection.isCollapsed) {
       final String selectedGraphemes = _selection.textInside(currText);
       firstSelectedGraphemeExtent = selectedGraphemes.characters.first.length;
       startHandleRect = renderObject.getRectForComposingRange(TextRange(start: _selection.start, end: _selection.start + firstSelectedGraphemeExtent));
@@ -608,7 +606,7 @@
     final int lastSelectedGraphemeExtent;
     Rect? endHandleRect;
     // See the explanation in _getStartGlyphHeight.
-    if (renderObject.plainText == currText && _selection != null && _selection.isValid && !_selection.isCollapsed) {
+    if (renderObject.plainText == currText && _selection.isValid && !_selection.isCollapsed) {
       final String selectedGraphemes = _selection.textInside(currText);
       lastSelectedGraphemeExtent = selectedGraphemes.characters.last.length;
       endHandleRect = renderObject.getRectForComposingRange(TextRange(start: _selection.end - lastSelectedGraphemeExtent, end: _selection.end));
@@ -894,7 +892,6 @@
       return TextSelectionHandleType.collapsed;
     }
 
-    assert(textDirection != null);
     switch (textDirection) {
       case TextDirection.ltr:
         return ltrType;
@@ -1347,6 +1344,29 @@
     );
   }
 
+  /// Shows toolbar with spell check suggestions of misspelled words that are
+  /// available for click-and-replace.
+  void showSpellCheckSuggestionsToolbar({
+    BuildContext? context,
+    required WidgetBuilder builder,
+  }) {
+    if (context == null) {
+      return;
+    }
+
+    final RenderBox renderBox = context.findRenderObject()! as RenderBox;
+    _contextMenuController.show(
+      context: context,
+      contextMenuBuilder: (BuildContext context) {
+        return _SelectionToolbarWrapper(
+          layerLink: toolbarLayerLink,
+          offset: -renderBox.localToGlobal(Offset.zero),
+          child: builder(context),
+        );
+      },
+    );
+  }
+
   bool _buildScheduled = false;
 
   /// Rebuilds the selection toolbar or handles if they are present.
@@ -1554,9 +1574,7 @@
     required this.layerLink,
     required this.offset,
     required this.child,
-  }) : assert(layerLink != null),
-       assert(offset != null),
-       assert(child != null);
+  });
 
   final Widget child;
   final Offset offset;
@@ -1825,7 +1843,7 @@
   /// The [delegate] must not be null.
   TextSelectionGestureDetectorBuilder({
     required this.delegate,
-  }) : assert(delegate != null);
+  });
 
   /// The delegate for this [TextSelectionGestureDetectorBuilder].
   ///
@@ -1870,6 +1888,23 @@
         && selection.end >= textPosition.offset;
   }
 
+  /// Returns true if position was on selection.
+  bool _positionOnSelection(Offset position, TextSelection? targetSelection) {
+    if (targetSelection == null) {
+      return false;
+    }
+
+    final TextPosition textPosition = renderEditable.getPositionForPoint(position);
+
+    return targetSelection.start <= textPosition.offset
+        && targetSelection.end >= textPosition.offset;
+  }
+
+  /// Returns true if shift left or right is contained in the given set.
+  static bool _containsShift(Set<LogicalKeyboardKey> keysPressed) {
+    return keysPressed.any(<LogicalKeyboardKey>{ LogicalKeyboardKey.shiftLeft, LogicalKeyboardKey.shiftRight }.contains);
+  }
+
   // Expand the selection to the given global position.
   //
   // Either base or extent will be moved to the last tapped position, whichever
@@ -1883,8 +1918,6 @@
   //   * [_extendSelection], which is similar but pivots the selection around
   //     the base.
   void _expandSelection(Offset offset, SelectionChangedCause cause, [TextSelection? fromSelection]) {
-    assert(cause != null);
-    assert(offset != null);
     assert(renderEditable.selection?.baseOffset != null);
 
     final TextPosition tappedPosition = renderEditable.getPositionForPoint(offset);
@@ -1914,8 +1947,6 @@
   //   * [_expandSelection], which is similar but always increases the size of
   //     the selection.
   void _extendSelection(Offset offset, SelectionChangedCause cause) {
-    assert(cause != null);
-    assert(offset != null);
     assert(renderEditable.selection?.baseOffset != null);
 
     final TextPosition tappedPosition = renderEditable.getPositionForPoint(offset);
@@ -1957,15 +1988,6 @@
   /// The viewport offset pixels of the [RenderEditable] at the last drag start.
   double _dragStartViewportOffset = 0.0;
 
-  // Returns true iff either shift key is currently down.
-  bool get _isShiftPressed {
-    return HardwareKeyboard.instance.logicalKeysPressed
-      .any(<LogicalKeyboardKey>{
-        LogicalKeyboardKey.shiftLeft,
-        LogicalKeyboardKey.shiftRight,
-      }.contains);
-  }
-
   double get _scrollPosition {
     final ScrollableState? scrollableState =
         delegate.editableTextKey.currentContext == null
@@ -1976,13 +1998,19 @@
         : scrollableState.position.pixels;
   }
 
-  // True iff a tap + shift has been detected but the tap has not yet come up.
-  bool _isShiftTapping = false;
-
   // For a shift + tap + drag gesture, the TextSelection at the point of the
   // tap. Mac uses this value to reset to the original selection when an
   // inversion of the base and offset happens.
-  TextSelection? _shiftTapDragSelection;
+  TextSelection? _dragStartSelection;
+
+  // For tap + drag gesture on iOS, whether the position where the drag started
+  // was on the previous TextSelection. iOS uses this value to determine if
+  // the cursor should move on drag update.
+  //
+  // If the drag started on the previous selection then the cursor will move on
+  // drag update. If the drag did not start on the previous selection then the
+  // cursor will not move on drag update.
+  bool? _dragBeganOnPreviousSelection;
 
   /// Handler for [TextSelectionGestureDetector.onTapDown].
   ///
@@ -1993,11 +2021,17 @@
   ///
   ///  * [TextSelectionGestureDetector.onTapDown], which triggers this callback.
   @protected
-  void onTapDown(TapDownDetails details) {
+  void onTapDown(TapDragDownDetails details) {
     if (!delegate.selectionEnabled) {
       return;
     }
-    renderEditable.handleTapDown(details);
+    // TODO(Renzo-Olivares): Migrate text selection gestures away from saving state
+    // in renderEditable. The gesture callbacks can use the details objects directly
+    // in callbacks variants that provide them [TapGestureRecognizer.onSecondaryTap]
+    // vs [TapGestureRecognizer.onSecondaryTapUp] instead of having to track state in
+    // renderEditable. When this migration is complete we should remove this hack.
+    // See https://github.com/flutter/flutter/issues/115130.
+    renderEditable.handleTapDown(TapDownDetails(globalPosition: details.globalPosition));
     // The selection overlay should only be shown when the user is interacting
     // through a touch screen (via either a finger or a stylus). A mouse shouldn't
     // trigger the selection overlay.
@@ -2011,21 +2045,20 @@
       || kind == PointerDeviceKind.stylus;
 
     // Handle shift + click selection if needed.
-    final bool isShiftPressedValid = _isShiftPressed && renderEditable.selection?.baseOffset != null;
+    final bool isShiftPressed = _containsShift(details.keysPressedOnDown);
+    // It is impossible to extend the selection when the shift key is pressed, if the
+    // renderEditable.selection is invalid.
+    final bool isShiftPressedValid = isShiftPressed && renderEditable.selection?.baseOffset != null;
     switch (defaultTargetPlatform) {
       case TargetPlatform.android:
       case TargetPlatform.fuchsia:
       case TargetPlatform.iOS:
         // On mobile platforms the selection is set on tap up.
-        if (_isShiftTapping) {
-          _isShiftTapping = false;
-        }
         break;
       case TargetPlatform.macOS:
         // On macOS, a shift-tapped unfocused field expands from 0, not from the
         // previous selection.
         if (isShiftPressedValid) {
-          _isShiftTapping = true;
           final TextSelection? fromSelection = renderEditable.hasFocus
               ? null
               : const TextSelection.collapsed(offset: 0);
@@ -2045,7 +2078,6 @@
       case TargetPlatform.linux:
       case TargetPlatform.windows:
         if (isShiftPressedValid) {
-          _isShiftTapping = true;
           _extendSelection(details.globalPosition, SelectionChangedCause.tap);
           return;
         }
@@ -2109,25 +2141,32 @@
   ///  * [TextSelectionGestureDetector.onSingleTapUp], which triggers
   ///    this callback.
   @protected
-  void onSingleTapUp(TapUpDetails details) {
+  void onSingleTapUp(TapDragUpDetails details) {
     if (delegate.selectionEnabled) {
       // Handle shift + click selection if needed.
-      final bool isShiftPressedValid = _isShiftPressed && renderEditable.selection?.baseOffset != null;
+      final bool isShiftPressed = _containsShift(details.keysPressedOnDown);
+      // It is impossible to extend the selection when the shift key is pressed, if the
+      // renderEditable.selection is invalid.
+      final bool isShiftPressedValid = isShiftPressed && renderEditable.selection?.baseOffset != null;
       switch (defaultTargetPlatform) {
         case TargetPlatform.linux:
         case TargetPlatform.macOS:
         case TargetPlatform.windows:
           editableText.hideToolbar();
           // On desktop platforms the selection is set on tap down.
-          if (_isShiftTapping) {
-            _isShiftTapping = false;
-          }
           break;
         case TargetPlatform.android:
+          editableText.hideToolbar();
+          editableText.showSpellCheckSuggestionsToolbar();
+          if (isShiftPressedValid) {
+            _extendSelection(details.globalPosition, SelectionChangedCause.tap);
+            return;
+          }
+          renderEditable.selectPosition(cause: SelectionChangedCause.tap);
+          break;
         case TargetPlatform.fuchsia:
           editableText.hideToolbar();
           if (isShiftPressedValid) {
-            _isShiftTapping = true;
             _extendSelection(details.globalPosition, SelectionChangedCause.tap);
             return;
           }
@@ -2137,7 +2176,6 @@
           if (isShiftPressedValid) {
             // On iOS, a shift-tapped unfocused field expands from 0, not from
             // the previous selection.
-            _isShiftTapping = true;
             final TextSelection? fromSelection = renderEditable.hasFocus
                 ? null
                 : const TextSelection.collapsed(offset: 0);
@@ -2200,7 +2238,7 @@
   ///  * [TextSelectionGestureDetector.onSingleTapCancel], which triggers
   ///    this callback.
   @protected
-  void onSingleTapCancel() {/* Subclass should override this method if needed. */}
+  void onSingleTapCancel() { /* Subclass should override this method if needed. */ }
 
   /// Handler for [TextSelectionGestureDetector.onSingleLongTapStart].
   ///
@@ -2370,7 +2408,13 @@
   ///  * [onSecondaryTap], which is typically called after this.
   @protected
   void onSecondaryTapDown(TapDownDetails details) {
-    renderEditable.handleSecondaryTapDown(details);
+    // TODO(Renzo-Olivares): Migrate text selection gestures away from saving state
+    // in renderEditable. The gesture callbacks can use the details objects directly
+    // in callbacks variants that provide them [TapGestureRecognizer.onSecondaryTap]
+    // vs [TapGestureRecognizer.onSecondaryTapUp] instead of having to track state in
+    // renderEditable. When this migration is complete we should remove this hack.
+    // See https://github.com/flutter/flutter/issues/115130.
+    renderEditable.handleSecondaryTapDown(TapDownDetails(globalPosition: details.globalPosition));
     _shouldShowSelectionToolbar = true;
   }
 
@@ -2384,7 +2428,7 @@
   ///  * [TextSelectionGestureDetector.onDoubleTapDown], which triggers this
   ///    callback.
   @protected
-  void onDoubleTapDown(TapDownDetails details) {
+  void onDoubleTapDown(TapDragDownDetails details) {
     if (delegate.selectionEnabled) {
       renderEditable.selectWord(cause: SelectionChangedCause.tap);
       if (shouldShowSelectionToolbar) {
@@ -2402,7 +2446,7 @@
   ///  * [TextSelectionGestureDetector.onDragSelectionStart], which triggers
   ///    this callback.
   @protected
-  void onDragSelectionStart(DragStartDetails details) {
+  void onDragSelectionStart(TapDragStartDetails details) {
     if (!delegate.selectionEnabled) {
       return;
     }
@@ -2411,8 +2455,18 @@
       || kind == PointerDeviceKind.touch
       || kind == PointerDeviceKind.stylus;
 
-    if (_isShiftPressed && renderEditable.selection != null && renderEditable.selection!.isValid) {
-      _isShiftTapping = true;
+    _dragStartSelection = renderEditable.selection;
+    _dragStartScrollOffset = _scrollPosition;
+    _dragStartViewportOffset = renderEditable.offset.pixels;
+
+    if (details.consecutiveTapCount > 1) {
+      // Do not set the selection on a consecutive tap and drag.
+      return;
+    }
+
+    final bool isShiftPressed = _containsShift(details.keysPressedOnDown);
+
+    if (isShiftPressed && renderEditable.selection != null && renderEditable.selection!.isValid) {
       switch (defaultTargetPlatform) {
         case TargetPlatform.iOS:
         case TargetPlatform.macOS:
@@ -2425,16 +2479,46 @@
           _extendSelection(details.globalPosition, SelectionChangedCause.drag);
           break;
       }
-      _shiftTapDragSelection = renderEditable.selection;
     } else {
-      renderEditable.selectPositionAt(
-        from: details.globalPosition,
-        cause: SelectionChangedCause.drag,
-      );
+      switch (defaultTargetPlatform) {
+        case TargetPlatform.iOS:
+        case TargetPlatform.android:
+        case TargetPlatform.fuchsia:
+          switch (details.kind) {
+            case PointerDeviceKind.mouse:
+            case PointerDeviceKind.trackpad:
+            case PointerDeviceKind.stylus:
+            case PointerDeviceKind.invertedStylus:
+              renderEditable.selectPositionAt(
+                from: details.globalPosition,
+                cause: SelectionChangedCause.drag,
+              );
+              break;
+            case PointerDeviceKind.touch:
+            case PointerDeviceKind.unknown:
+              // For Android, Fucshia, and iOS platforms, a touch drag
+              // does not initiate unless the editable has focus.
+              if (renderEditable.hasFocus) {
+                renderEditable.selectPositionAt(
+                  from: details.globalPosition,
+                  cause: SelectionChangedCause.drag,
+                );
+              }
+              break;
+            case null:
+              break;
+          }
+          break;
+        case TargetPlatform.linux:
+        case TargetPlatform.macOS:
+        case TargetPlatform.windows:
+          renderEditable.selectPositionAt(
+            from: details.globalPosition,
+            cause: SelectionChangedCause.drag,
+          );
+          break;
+      }
     }
-
-    _dragStartScrollOffset = _scrollPosition;
-    _dragStartViewportOffset = renderEditable.offset.pixels;
   }
 
   /// Handler for [TextSelectionGestureDetector.onDragSelectionUpdate].
@@ -2447,12 +2531,14 @@
   ///  * [TextSelectionGestureDetector.onDragSelectionUpdate], which triggers
   ///    this callback./lib/src/material/text_field.dart
   @protected
-  void onDragSelectionUpdate(DragStartDetails startDetails, DragUpdateDetails updateDetails) {
+  void onDragSelectionUpdate(TapDragUpdateDetails details) {
     if (!delegate.selectionEnabled) {
       return;
     }
 
-    if (!_isShiftTapping) {
+    final bool isShiftPressed = _containsShift(details.keysPressedOnDown);
+
+    if (!isShiftPressed) {
       // Adjust the drag start offset for possible viewport offset changes.
       final Offset editableOffset = renderEditable.maxLines == 1
           ? Offset(renderEditable.offset.pixels - _dragStartViewportOffset, 0.0)
@@ -2461,58 +2547,137 @@
         0.0,
         _scrollPosition - _dragStartScrollOffset,
       );
-      return renderEditable.selectPositionAt(
-        from: startDetails.globalPosition - editableOffset - scrollableOffset,
-        to: updateDetails.globalPosition,
-        cause: SelectionChangedCause.drag,
-      );
+      final Offset dragStartGlobalPosition = details.globalPosition - details.offsetFromOrigin;
+
+      // Select word by word.
+      if (details.consecutiveTapCount == 2) {
+        return renderEditable.selectWordsInRange(
+          from: dragStartGlobalPosition - editableOffset - scrollableOffset,
+          to: details.globalPosition,
+          cause: SelectionChangedCause.drag,
+        );
+      }
+
+      switch (defaultTargetPlatform) {
+        case TargetPlatform.iOS:
+          // With a touch device, nothing should happen, unless there was a double tap, or
+          // there was a collapsed selection, and the tap/drag position is at the collapsed selection.
+          // In that case the caret should move with the drag position.
+          //
+          // With a mouse device, a drag should select the range from the origin of the drag
+          // to the current position of the drag.
+          switch (details.kind) {
+            case PointerDeviceKind.mouse:
+            case PointerDeviceKind.trackpad:
+              return renderEditable.selectPositionAt(
+                from: dragStartGlobalPosition - editableOffset - scrollableOffset,
+                to: details.globalPosition,
+                cause: SelectionChangedCause.drag,
+              );
+            case PointerDeviceKind.stylus:
+            case PointerDeviceKind.invertedStylus:
+            case PointerDeviceKind.touch:
+            case PointerDeviceKind.unknown:
+              _dragBeganOnPreviousSelection ??= _positionOnSelection(dragStartGlobalPosition, _dragStartSelection);
+              assert(_dragBeganOnPreviousSelection != null);
+              if (renderEditable.hasFocus
+                  && _dragStartSelection!.isCollapsed
+                  && _dragBeganOnPreviousSelection!
+              ) {
+                return renderEditable.selectPositionAt(
+                  from: details.globalPosition,
+                  cause: SelectionChangedCause.drag,
+                );
+              }
+              break;
+            case null:
+              break;
+          }
+          return;
+        case TargetPlatform.android:
+        case TargetPlatform.fuchsia:
+          // With a precise pointer device, such as a mouse, trackpad, or stylus,
+          // the drag will select the text spanning the origin of the drag to the end of the drag.
+          // With a touch device, the cursor should move with the drag.
+          switch (details.kind) {
+            case PointerDeviceKind.mouse:
+            case PointerDeviceKind.trackpad:
+            case PointerDeviceKind.stylus:
+            case PointerDeviceKind.invertedStylus:
+              return renderEditable.selectPositionAt(
+                from: dragStartGlobalPosition - editableOffset - scrollableOffset,
+                to: details.globalPosition,
+                cause: SelectionChangedCause.drag,
+              );
+            case PointerDeviceKind.touch:
+            case PointerDeviceKind.unknown:
+              if (renderEditable.hasFocus) {
+                return renderEditable.selectPositionAt(
+                  from: details.globalPosition,
+                  cause: SelectionChangedCause.drag,
+                );
+              }
+              break;
+            case null:
+              break;
+          }
+          return;
+        case TargetPlatform.macOS:
+        case TargetPlatform.linux:
+        case TargetPlatform.windows:
+          return renderEditable.selectPositionAt(
+            from: dragStartGlobalPosition - editableOffset - scrollableOffset,
+            to: details.globalPosition,
+            cause: SelectionChangedCause.drag,
+          );
+      }
     }
 
-    if (_shiftTapDragSelection!.isCollapsed
+    if (_dragStartSelection!.isCollapsed
         || (defaultTargetPlatform != TargetPlatform.iOS
             && defaultTargetPlatform != TargetPlatform.macOS)) {
-      return _extendSelection(updateDetails.globalPosition, SelectionChangedCause.drag);
+      return _extendSelection(details.globalPosition, SelectionChangedCause.drag);
     }
 
     // If the drag inverts the selection, Mac and iOS revert to the initial
     // selection.
     final TextSelection selection = editableText.textEditingValue.selection;
-    final TextPosition nextExtent = renderEditable.getPositionForPoint(updateDetails.globalPosition);
+    final TextPosition nextExtent = renderEditable.getPositionForPoint(details.globalPosition);
     final bool isShiftTapDragSelectionForward =
-        _shiftTapDragSelection!.baseOffset < _shiftTapDragSelection!.extentOffset;
+        _dragStartSelection!.baseOffset < _dragStartSelection!.extentOffset;
     final bool isInverted = isShiftTapDragSelectionForward
-        ? nextExtent.offset < _shiftTapDragSelection!.baseOffset
-        : nextExtent.offset > _shiftTapDragSelection!.baseOffset;
-    if (isInverted && selection.baseOffset == _shiftTapDragSelection!.baseOffset) {
+        ? nextExtent.offset < _dragStartSelection!.baseOffset
+        : nextExtent.offset > _dragStartSelection!.baseOffset;
+    if (isInverted && selection.baseOffset == _dragStartSelection!.baseOffset) {
       editableText.userUpdateTextEditingValue(
         editableText.textEditingValue.copyWith(
           selection: TextSelection(
-            baseOffset: _shiftTapDragSelection!.extentOffset,
+            baseOffset: _dragStartSelection!.extentOffset,
             extentOffset: nextExtent.offset,
           ),
         ),
         SelectionChangedCause.drag,
       );
     } else if (!isInverted
-        && nextExtent.offset != _shiftTapDragSelection!.baseOffset
-        && selection.baseOffset != _shiftTapDragSelection!.baseOffset) {
+        && nextExtent.offset != _dragStartSelection!.baseOffset
+        && selection.baseOffset != _dragStartSelection!.baseOffset) {
       editableText.userUpdateTextEditingValue(
         editableText.textEditingValue.copyWith(
           selection: TextSelection(
-            baseOffset: _shiftTapDragSelection!.baseOffset,
+            baseOffset: _dragStartSelection!.baseOffset,
             extentOffset: nextExtent.offset,
           ),
         ),
         SelectionChangedCause.drag,
       );
     } else {
-      _extendSelection(updateDetails.globalPosition, SelectionChangedCause.drag);
+      _extendSelection(details.globalPosition, SelectionChangedCause.drag);
     }
   }
 
   /// Handler for [TextSelectionGestureDetector.onDragSelectionEnd].
   ///
-  /// By default, it simply cleans up the state used for handling certain
+  /// By default, it cleans up the state used for handling certain
   /// built-in behaviors.
   ///
   /// See also:
@@ -2520,10 +2685,12 @@
   ///  * [TextSelectionGestureDetector.onDragSelectionEnd], which triggers this
   ///    callback.
   @protected
-  void onDragSelectionEnd(DragEndDetails details) {
-    if (_isShiftTapping) {
-      _isShiftTapping = false;
-      _shiftTapDragSelection = null;
+  void onDragSelectionEnd(TapDragEndDetails details) {
+    final bool isShiftPressed = _containsShift(details.keysPressedOnDown);
+    _dragBeganOnPreviousSelection = null;
+
+    if (isShiftPressed) {
+      _dragStartSelection = null;
     }
   }
 
@@ -2562,8 +2729,8 @@
 ///
 /// An ordinary [GestureDetector] configured to handle events like tap and
 /// double tap will only recognize one or the other. This widget detects both:
-/// first the tap and then, if another tap down occurs within a time limit, the
-/// double tap.
+/// the first tap and then any subsequent taps that occurs within a time limit
+/// after the first.
 ///
 /// See also:
 ///
@@ -2593,12 +2760,12 @@
     this.onDragSelectionEnd,
     this.behavior,
     required this.child,
-  }) : assert(child != null);
+  });
 
   /// Called for every tap down including every tap down that's part of a
   /// double click or a long press, except touches that include enough movement
   /// to not qualify as taps (e.g. pans and flings).
-  final GestureTapDownCallback? onTapDown;
+  final GestureTapDragDownCallback? onTapDown;
 
   /// Called when a pointer has tapped down and the force of the pointer has
   /// just become greater than [ForcePressGestureRecognizer.startPressure].
@@ -2614,16 +2781,18 @@
   /// Called for a tap down event with the secondary mouse button.
   final GestureTapDownCallback? onSecondaryTapDown;
 
-  /// Called for each distinct tap except for every second tap of a double tap.
+  /// Called for the first tap in a series of taps, consecutive taps do not call
+  /// this method.
+  ///
   /// For example, if the detector was configured with [onTapDown] and
   /// [onDoubleTapDown], three quick taps would be recognized as a single tap
-  /// down, followed by a double tap down, followed by a single tap down.
-  final GestureTapUpCallback? onSingleTapUp;
+  /// down, followed by a tap up, then a double tap down, followed by a single tap down.
+  final GestureTapDragUpCallback? onSingleTapUp;
 
   /// Called for each touch that becomes recognized as a gesture that is not a
   /// short tap, such as a long tap or drag. It is called at the moment when
   /// another gesture from the touch is recognized.
-  final GestureTapCancelCallback? onSingleTapCancel;
+  final GestureCancelCallback? onSingleTapCancel;
 
   /// Called for a single long tap that's sustained for longer than
   /// [kLongPressTimeout] but not necessarily lifted. Not called for a
@@ -2638,20 +2807,20 @@
 
   /// Called after a momentary hold or a short tap that is close in space and
   /// time (within [kDoubleTapTimeout]) to a previous short tap.
-  final GestureTapDownCallback? onDoubleTapDown;
+  final GestureTapDragDownCallback? onDoubleTapDown;
 
   /// Called when a mouse starts dragging to select text.
-  final GestureDragStartCallback? onDragSelectionStart;
+  final GestureTapDragStartCallback? onDragSelectionStart;
 
   /// Called repeatedly as a mouse moves while dragging.
   ///
   /// The frequency of calls is throttled to avoid excessive text layout
   /// operations in text fields. The throttling is controlled by the constant
   /// [_kDragSelectionUpdateThrottle].
-  final DragSelectionUpdateCallback? onDragSelectionUpdate;
+  final GestureTapDragUpdateCallback? onDragSelectionUpdate;
 
   /// Called when a mouse that was previously dragging is released.
-  final GestureDragEndCallback? onDragSelectionEnd;
+  final GestureTapDragEndCallback? onDragSelectionEnd;
 
   /// How this gesture detector should behave during hit testing.
   ///
@@ -2666,100 +2835,50 @@
 }
 
 class _TextSelectionGestureDetectorState extends State<TextSelectionGestureDetector> {
-  // Counts down for a short duration after a previous tap. Null otherwise.
-  Timer? _doubleTapTimer;
-  Offset? _lastTapOffset;
-  // True if a second tap down of a double tap is detected. Used to discard
-  // subsequent tap up / tap hold of the same tap.
-  bool _isDoubleTap = false;
+  static int? _getDefaultMaxConsecutiveTap() => 2;
 
   @override
   void dispose() {
-    _doubleTapTimer?.cancel();
-    _dragUpdateThrottleTimer?.cancel();
     super.dispose();
   }
 
   // The down handler is force-run on success of a single tap and optimistically
   // run before a long press success.
-  void _handleTapDown(TapDownDetails details) {
+  void _handleTapDown(TapDragDownDetails details) {
     widget.onTapDown?.call(details);
     // This isn't detected as a double tap gesture in the gesture recognizer
     // because it's 2 single taps, each of which may do different things depending
     // on whether it's a single tap, the first tap of a double tap, the second
     // tap held down, a clean double tap etc.
-    if (_doubleTapTimer != null && _isWithinDoubleTapTolerance(details.globalPosition)) {
-      // If there was already a previous tap, the second down hold/tap is a
-      // double tap down.
-      widget.onDoubleTapDown?.call(details);
 
-      _doubleTapTimer!.cancel();
-      _doubleTapTimeout();
-      _isDoubleTap = true;
+    if (details.consecutiveTapCount == 2) {
+      widget.onDoubleTapDown?.call(details);
     }
   }
 
-  void _handleTapUp(TapUpDetails details) {
-    if (!_isDoubleTap) {
+  void _handleTapUp(TapDragUpDetails details) {
+    if (details.consecutiveTapCount == 1) {
       widget.onSingleTapUp?.call(details);
-      _lastTapOffset = details.globalPosition;
-      _doubleTapTimer?.cancel();
-      _doubleTapTimer = Timer(kDoubleTapTimeout, _doubleTapTimeout);
     }
-    _isDoubleTap = false;
   }
 
   void _handleTapCancel() {
     widget.onSingleTapCancel?.call();
   }
 
-  DragStartDetails? _lastDragStartDetails;
-  DragUpdateDetails? _lastDragUpdateDetails;
-  Timer? _dragUpdateThrottleTimer;
-
-  void _handleDragStart(DragStartDetails details) {
-    assert(_lastDragStartDetails == null);
-    _lastDragStartDetails = details;
+  void _handleDragStart(TapDragStartDetails details) {
     widget.onDragSelectionStart?.call(details);
   }
 
-  void _handleDragUpdate(DragUpdateDetails details) {
-    _lastDragUpdateDetails = details;
-    // Only schedule a new timer if there's no one pending.
-    _dragUpdateThrottleTimer ??= Timer(_kDragSelectionUpdateThrottle, _handleDragUpdateThrottled);
+  void _handleDragUpdate(TapDragUpdateDetails details) {
+    widget.onDragSelectionUpdate?.call(details);
   }
 
-  /// Drag updates are being throttled to avoid excessive text layouts in text
-  /// fields. The frequency of invocations is controlled by the constant
-  /// [_kDragSelectionUpdateThrottle].
-  ///
-  /// Once the drag gesture ends, any pending drag update will be fired
-  /// immediately. See [_handleDragEnd].
-  void _handleDragUpdateThrottled() {
-    assert(_lastDragStartDetails != null);
-    assert(_lastDragUpdateDetails != null);
-    widget.onDragSelectionUpdate?.call(_lastDragStartDetails!, _lastDragUpdateDetails!);
-    _dragUpdateThrottleTimer = null;
-    _lastDragUpdateDetails = null;
-  }
-
-  void _handleDragEnd(DragEndDetails details) {
-    assert(_lastDragStartDetails != null);
-    if (_dragUpdateThrottleTimer != null) {
-      // If there's already an update scheduled, trigger it immediately and
-      // cancel the timer.
-      _dragUpdateThrottleTimer!.cancel();
-      _handleDragUpdateThrottled();
-    }
+  void _handleDragEnd(TapDragEndDetails details) {
     widget.onDragSelectionEnd?.call(details);
-    _dragUpdateThrottleTimer = null;
-    _lastDragStartDetails = null;
-    _lastDragUpdateDetails = null;
   }
 
   void _forcePressStarted(ForcePressDetails details) {
-    _doubleTapTimer?.cancel();
-    _doubleTapTimer = null;
     widget.onForcePressStart?.call(details);
   }
 
@@ -2768,37 +2887,21 @@
   }
 
   void _handleLongPressStart(LongPressStartDetails details) {
-    if (!_isDoubleTap && widget.onSingleLongTapStart != null) {
+    if (widget.onSingleLongTapStart != null) {
       widget.onSingleLongTapStart!(details);
     }
   }
 
   void _handleLongPressMoveUpdate(LongPressMoveUpdateDetails details) {
-    if (!_isDoubleTap && widget.onSingleLongTapMoveUpdate != null) {
+    if (widget.onSingleLongTapMoveUpdate != null) {
       widget.onSingleLongTapMoveUpdate!(details);
     }
   }
 
   void _handleLongPressEnd(LongPressEndDetails details) {
-    if (!_isDoubleTap && widget.onSingleLongTapEnd != null) {
+    if (widget.onSingleLongTapEnd != null) {
       widget.onSingleLongTapEnd!(details);
     }
-    _isDoubleTap = false;
-  }
-
-  void _doubleTapTimeout() {
-    _doubleTapTimer = null;
-    _lastTapOffset = null;
-  }
-
-  bool _isWithinDoubleTapTolerance(Offset secondTapOffset) {
-    assert(secondTapOffset != null);
-    if (_lastTapOffset == null) {
-      return false;
-    }
-
-    final Offset difference = secondTapOffset - _lastTapOffset!;
-    return difference.distance <= kDoubleTapSlop;
   }
 
   @override
@@ -2810,10 +2913,7 @@
       (TapGestureRecognizer instance) {
         instance
           ..onSecondaryTap = widget.onSecondaryTap
-          ..onSecondaryTapDown = widget.onSecondaryTapDown
-          ..onTapDown = _handleTapDown
-          ..onTapUp = _handleTapUp
-          ..onTapCancel = _handleTapCancel;
+          ..onSecondaryTapDown = widget.onSecondaryTapDown;
       },
     );
 
@@ -2821,7 +2921,7 @@
         widget.onSingleLongTapMoveUpdate != null ||
         widget.onSingleLongTapEnd != null) {
       gestures[LongPressGestureRecognizer] = GestureRecognizerFactoryWithHandlers<LongPressGestureRecognizer>(
-        () => LongPressGestureRecognizer(debugOwner: this, kind: PointerDeviceKind.touch),
+        () => LongPressGestureRecognizer(debugOwner: this, supportedDevices: <PointerDeviceKind>{ PointerDeviceKind.touch }),
         (LongPressGestureRecognizer instance) {
           instance
             ..onLongPressStart = _handleLongPressStart
@@ -2834,16 +2934,21 @@
     if (widget.onDragSelectionStart != null ||
         widget.onDragSelectionUpdate != null ||
         widget.onDragSelectionEnd != null) {
-      gestures[PanGestureRecognizer] = GestureRecognizerFactoryWithHandlers<PanGestureRecognizer>(
-        () => PanGestureRecognizer(debugOwner: this, supportedDevices: <PointerDeviceKind>{ PointerDeviceKind.mouse }),
-        (PanGestureRecognizer instance) {
+      gestures[TapAndDragGestureRecognizer] = GestureRecognizerFactoryWithHandlers<TapAndDragGestureRecognizer>(
+        () => TapAndDragGestureRecognizer(debugOwner: this),
+        (TapAndDragGestureRecognizer instance) {
           instance
             // Text selection should start from the position of the first pointer
             // down event.
             ..dragStartBehavior = DragStartBehavior.down
-            ..onStart = _handleDragStart
-            ..onUpdate = _handleDragUpdate
-            ..onEnd = _handleDragEnd;
+            ..dragUpdateThrottleFrequency = _kDragSelectionUpdateThrottle
+            ..maxConsecutiveTap = _getDefaultMaxConsecutiveTap()
+            ..onTapDown = _handleTapDown
+            ..onDragStart = _handleDragStart
+            ..onDragUpdate = _handleDragUpdate
+            ..onDragEnd = _handleDragEnd
+            ..onTapUp = _handleTapUp
+            ..onCancel = _handleTapCancel;
         },
       );
     }
diff --git a/framework/lib/src/widgets/text_selection_toolbar_layout_delegate.dart b/framework/lib/src/widgets/text_selection_toolbar_layout_delegate.dart
index 7778f96..02ae050 100644
--- a/framework/lib/src/widgets/text_selection_toolbar_layout_delegate.dart
+++ b/framework/lib/src/widgets/text_selection_toolbar_layout_delegate.dart
@@ -41,9 +41,9 @@
   /// If not provided, it will be calculated.
   final bool? fitsAbove;
 
-  // Return the value that centers width as closely as possible to position
-  // while fitting inside of min and max.
-  static double _centerOn(double position, double width, double max) {
+  /// Return the value that centers width as closely as possible to position
+  /// while fitting inside of min and max.
+  static double centerOn(double position, double width, double max) {
     // If it overflows on the left, put it as far left as possible.
     if (position - width / 2.0 < 0.0) {
       return 0.0;
@@ -69,7 +69,7 @@
     final Offset anchor = fitsAbove ? anchorAbove : anchorBelow;
 
     return Offset(
-      _centerOn(
+      centerOn(
         anchor.dx,
         childSize.width,
         size.width,
diff --git a/framework/lib/src/widgets/texture.dart b/framework/lib/src/widgets/texture.dart
index caa4f8c..546f4f4 100644
--- a/framework/lib/src/widgets/texture.dart
+++ b/framework/lib/src/widgets/texture.dart
@@ -40,7 +40,7 @@
     required this.textureId,
     this.freeze = false,
     this.filterQuality = FilterQuality.low,
-  }) : assert(textureId != null);
+  });
 
   /// The identity of the backend texture.
   final int textureId;
diff --git a/framework/lib/src/widgets/ticker_provider.dart b/framework/lib/src/widgets/ticker_provider.dart
index 00f43d0..7f87844 100644
--- a/framework/lib/src/widgets/ticker_provider.dart
+++ b/framework/lib/src/widgets/ticker_provider.dart
@@ -26,7 +26,7 @@
     super.key,
     required this.enabled,
     required this.child,
-  }) : assert(enabled != null);
+  });
 
   /// The requested ticker mode for this subtree.
   ///
@@ -151,7 +151,7 @@
     required this.enabled,
     required this.notifier,
     required super.child,
-  }) : assert(enabled != null);
+  });
 
   final bool enabled;
   final ValueNotifier<bool> notifier;
diff --git a/framework/lib/src/widgets/title.dart b/framework/lib/src/widgets/title.dart
index dce6028..088cfce 100644
--- a/framework/lib/src/widgets/title.dart
+++ b/framework/lib/src/widgets/title.dart
@@ -20,8 +20,7 @@
     this.title = '',
     required this.color,
     required this.child,
-  }) : assert(title != null),
-       assert(color != null && color.alpha == 0xFF);
+  }) : assert(color.alpha == 0xFF);
 
   /// A one-line description of this app for use in the window manager.
   /// Must not be null.
diff --git a/framework/lib/src/widgets/transitions.dart b/framework/lib/src/widgets/transitions.dart
index c5616dc..dfcc5d3 100644
--- a/framework/lib/src/widgets/transitions.dart
+++ b/framework/lib/src/widgets/transitions.dart
@@ -22,7 +22,7 @@
 /// [ChangeNotifier] and [ValueNotifier].
 ///
 /// [AnimatedWidget] is most useful for widgets that are otherwise stateless. To
-/// use [AnimatedWidget], simply subclass it and implement the build function.
+/// use [AnimatedWidget], subclass it and implement the build function.
 ///
 /// {@tool dartpad}
 /// This code defines a widget called `Spinner` that spins a green square
@@ -32,7 +32,7 @@
 /// {@end-tool}
 ///
 /// For more complex case involving additional state, consider using
-/// [AnimatedBuilder].
+/// [AnimatedBuilder] or [ListenableBuilder].
 ///
 /// ## Relationship to [ImplicitlyAnimatedWidget]s
 ///
@@ -55,8 +55,10 @@
 /// with subclasses of [ImplicitlyAnimatedWidget] (see above), which are usually
 /// named `AnimatedFoo`. Commonly used animated widgets include:
 ///
-///  * [AnimatedBuilder], which is useful for complex animation use cases and a
-///    notable exception to the naming scheme of [AnimatedWidget] subclasses.
+///  * [ListenableBuilder], which uses a builder pattern that is useful for
+///    complex [Listenable] use cases.
+///  * [AnimatedBuilder], which uses a builder pattern that is useful for
+///    complex [Animation] use cases.
 ///  * [AlignTransition], which is an animated version of [Align].
 ///  * [DecoratedBoxTransition], which is an animated version of [DecoratedBox].
 ///  * [DefaultTextStyleTransition], which is an animated version of
@@ -78,7 +80,7 @@
   const AnimatedWidget({
     super.key,
     required this.listenable,
-  }) : assert(listenable != null);
+  });
 
   /// The [Listenable] to which this widget is listening.
   ///
@@ -97,7 +99,7 @@
   @override
   void debugFillProperties(DiagnosticPropertiesBuilder properties) {
     super.debugFillProperties(properties);
-    properties.add(DiagnosticsProperty<Listenable>('animation', listenable));
+    properties.add(DiagnosticsProperty<Listenable>('listenable', listenable));
   }
 }
 
@@ -174,8 +176,7 @@
     this.transformHitTests = true,
     this.textDirection,
     this.child,
-  }) : assert(position != null),
-       super(listenable: position);
+  }) : super(listenable: position);
 
   /// The animation that controls the position of the child.
   ///
@@ -256,8 +257,7 @@
     this.alignment = Alignment.center,
     this.filterQuality,
     this.child,
-  }) : assert(scale != null),
-       super(listenable: scale);
+  }) : super(listenable: scale);
 
   /// The animation that controls the scale of the child.
   ///
@@ -340,8 +340,7 @@
     this.alignment = Alignment.center,
     this.filterQuality,
     this.child,
-  }) : assert(turns != null),
-       super(listenable: turns);
+  }) : super(listenable: turns);
 
   /// The animation that controls the rotation of the child.
   ///
@@ -442,10 +441,7 @@
     required Animation<double> sizeFactor,
     this.axisAlignment = 0.0,
     this.child,
-  }) : assert(axis != null),
-       assert(sizeFactor != null),
-       assert(axisAlignment != null),
-       super(listenable: sizeFactor);
+  }) : super(listenable: sizeFactor);
 
   /// [Axis.horizontal] if [sizeFactor] modifies the width, otherwise
   /// [Axis.vertical].
@@ -529,7 +525,7 @@
     required this.opacity,
     this.alwaysIncludeSemantics = false,
     super.child,
-  }) : assert(opacity != null);
+  });
 
   /// The animation that controls the opacity of the child.
   ///
@@ -599,8 +595,7 @@
     required this.opacity,
     this.alwaysIncludeSemantics = false,
     Widget? sliver,
-  }) : assert(opacity != null),
-      super(child: sliver);
+  }) : super(child: sliver);
 
   /// The animation that controls the opacity of the sliver child.
   ///
@@ -700,8 +695,7 @@
     super.key,
     required Animation<RelativeRect> rect,
     required this.child,
-  }) : assert(rect != null),
-       super(listenable: rect);
+  }) : super(listenable: rect);
 
   /// The animation that controls the child's size and position.
   Animation<RelativeRect> get rect => listenable as Animation<RelativeRect>;
@@ -760,10 +754,7 @@
     required Animation<Rect?> rect,
     required this.size,
     required this.child,
-  }) : assert(rect != null),
-       assert(size != null),
-       assert(child != null),
-       super(listenable: rect);
+  }) : super(listenable: rect);
 
   /// The animation that controls the child's size and position.
   ///
@@ -830,9 +821,7 @@
     required this.decoration,
     this.position = DecorationPosition.background,
     required this.child,
-  }) : assert(decoration != null),
-       assert(child != null),
-       super(listenable: decoration);
+  }) : super(listenable: decoration);
 
   /// Animation of the decoration to paint.
   ///
@@ -898,9 +887,7 @@
     required this.child,
     this.widthFactor,
     this.heightFactor,
-  }) : assert(alignment != null),
-       assert(child != null),
-       super(listenable: alignment);
+  }) : super(listenable: alignment);
 
   /// The animation that controls the child's alignment.
   Animation<AlignmentGeometry> get alignment => listenable as Animation<AlignmentGeometry>;
@@ -954,9 +941,7 @@
     this.softWrap = true,
     this.overflow = TextOverflow.clip,
     this.maxLines,
-  }) : assert(style != null),
-       assert(child != null),
-       super(listenable: style);
+  }) : super(listenable: style);
 
   /// The animation that controls the descendants' text style.
   Animation<TextStyle> get style => listenable as Animation<TextStyle>;
@@ -996,11 +981,109 @@
   }
 }
 
+/// A general-purpose widget for building a widget subtree when a [Listenable]
+/// changes.
+///
+/// [ListenableBuilder] is useful for more complex widgets that wish to listen
+/// to changes in other objects as part of a larger build function. To use
+/// [ListenableBuilder], construct the widget and pass it a [builder]
+/// function.
+///
+/// Any subtype of [Listenable] (such as a [ChangeNotifier], [ValueNotifier], or
+/// [Animation]) can be used with a [ListenableBuilder] to rebuild only certain
+/// parts of a widget when the [Listenable] notifies its listeners. Although
+/// they have identical implementations, if an [Animation] is being listened to,
+/// consider using an [AnimatedBuilder] instead for better readability.
+///
+/// ## Performance optimizations
+///
+/// {@template flutter.widgets.transitions.ListenableBuilder.optimizations}
+/// If the [builder] function contains a subtree that does not depend on the
+/// [listenable], it's often more efficient to build that subtree once instead
+/// of rebuilding it on every change of the listenable.
+///
+/// If a pre-built subtree is passed as the [child] parameter, the
+/// [ListenableBuilder] will pass it back to the [builder] function so that it
+/// can be incorporated into the build.
+///
+/// Using this pre-built [child] is entirely optional, but can improve
+/// performance significantly in some cases and is therefore a good practice.
+/// {@endtemplate}
+///
+/// {@tool dartpad}
+/// This example shows how a [ListenableBuilder] can be used to listen to a
+/// [FocusNode] (which is also a [ChangeNotifier]) to see when a subtree has
+/// focus, and modify a decoration when its focus state changes.
+///
+/// ** See code in examples/api/lib/widgets/transitions/listenable_builder.0.dart **
+/// {@end-tool}
+///
+/// {@template flutter.flutter.ListenableBuilder.ChangeNotifier.rebuild}
+/// ## Improve rebuild performance
+///
+/// Performance can be improved by specifying any widgets that don't need to
+/// change as a result of changes in the listener as the prebuilt
+/// [ListenableBuilder.child] attribute.
+///
+/// {@tool dartpad}
+/// The following example implements a simple counter that utilizes a
+/// [ListenableBuilder] to limit rebuilds to only the [Text] widget containing
+/// the count. The current count is stored in a [ValueNotifier], which rebuilds
+/// the [ListenableBuilder]'s contents when its value is changed.
+///
+/// ** See code in examples/api/lib/widgets/transitions/listenable_builder.1.dart **
+/// {@end-tool}
+/// {@endtemplate}
+///
+/// See also:
+///
+/// * [AnimatedBuilder], which has the same functionality, but is named more
+///   appropriately for a builder triggered by [Animation]s.
+class ListenableBuilder extends AnimatedWidget {
+  /// Creates a builder that responds to changes in [listenable].
+  ///
+  /// The [listenable] and [builder] arguments must not be null.
+  const ListenableBuilder({
+    super.key,
+    required super.listenable,
+    required this.builder,
+    this.child,
+  });
+
+  // Overridden getter to replace with documentation tailored to
+  // ListenableBuilder.
+
+  /// The [Listenable] supplied to the constructor.
+  ///
+  /// Also accessible through the [listenable] getter.
+  ///
+  /// See also:
+  ///
+  /// * [AnimatedBuilder], a widget with an identical functionality commonly
+  ///   used with [Animation] [Listenable]s for better readability.
+  @override
+  Listenable get listenable => super.listenable;
+
+  /// Called every time the [listenable] notifies about a change.
+  ///
+  /// The child given to the builder should typically be part of the returned
+  /// widget tree.
+  final TransitionBuilder builder;
+
+  /// The child widget to pass to the [builder].
+  ///
+  /// {@macro flutter.widgets.transitions.ListenableBuilder.optimizations}
+  final Widget? child;
+
+  @override
+  Widget build(BuildContext context) => builder(context, child);
+}
+
 /// A general-purpose widget for building animations.
 ///
-/// AnimatedBuilder is useful for more complex widgets that wish to include
-/// an animation as part of a larger build function. To use AnimatedBuilder,
-/// simply construct the widget and pass it a builder function.
+/// [AnimatedBuilder] is useful for more complex widgets that wish to include
+/// an animation as part of a larger build function. To use [AnimatedBuilder],
+/// construct the widget and pass it a builder function.
 ///
 /// For simple cases without additional state, consider using
 /// [AnimatedWidget].
@@ -1009,16 +1092,18 @@
 ///
 /// ## Performance optimizations
 ///
-/// If your [builder] function contains a subtree that does not depend on the
-/// animation, it's more efficient to build that subtree once instead of
-/// rebuilding it on every animation tick.
+/// {@template flutter.widgets.transitions.AnimatedBuilder.optimizations}
+/// If the [builder] function contains a subtree that does not depend on the
+/// animation passed to the constructor, it's more efficient to build that
+/// subtree once instead of rebuilding it on every animation tick.
 ///
-/// If you pass the pre-built subtree as the [child] parameter, the
-/// [AnimatedBuilder] will pass it back to your builder function so that you
-/// can incorporate it into your build.
+/// If a pre-built subtree is passed as the [child] parameter, the
+/// [AnimatedBuilder] will pass it back to the [builder] function so that it can
+/// be incorporated into the build.
 ///
 /// Using this pre-built child is entirely optional, but can improve
 /// performance significantly in some cases and is therefore a good practice.
+/// {@endtemplate}
 ///
 /// {@tool dartpad}
 /// This code defines a widget that spins a green square continually. It is
@@ -1028,61 +1113,64 @@
 /// ** See code in examples/api/lib/widgets/transitions/animated_builder.0.dart **
 /// {@end-tool}
 ///
-/// {@template flutter.flutter.animatedbuilder_changenotifier.rebuild}
-/// ## Improve rebuilds performance using AnimatedBuilder
+/// ## Improve rebuild performance
 ///
-/// Despite the name, [AnimatedBuilder] is not limited to [Animation]s. Any subtype
-/// of [Listenable] (such as [ChangeNotifier] and [ValueNotifier]) can be used with
-/// an [AnimatedBuilder] to rebuild only certain parts of a widget when the
-/// [Listenable] notifies its listeners. This technique is a performance improvement
-/// that allows rebuilding only specific widgets leaving others untouched.
+/// Despite the name, [AnimatedBuilder] is not limited to [Animation]s, any
+/// subtype of [Listenable] (such as [ChangeNotifier] or [ValueNotifier]) can be
+/// used to trigger rebuilds. Although they have identical implementations, if
+/// an [Animation] is not being listened to, consider using a
+/// [ListenableBuilder] for better readability.
+///
+/// You can use an [AnimatedBuilder] or [ListenableBuilder] to rebuild only
+/// certain parts of a widget when the [Listenable] notifies its listeners. You
+/// can improve performance by specifying any widgets that don't need to change
+/// as a result of changes in the listener as the prebuilt [child] attribute.
 ///
 /// {@tool dartpad}
 /// The following example implements a simple counter that utilizes an
-/// [AnimatedBuilder] to limit rebuilds to only the [Text] widget. The current count
-/// is stored in a [ValueNotifier], which rebuilds the [AnimatedBuilder]'s contents
-/// when its value is changed.
+/// [AnimatedBuilder] to limit rebuilds to only the [Text] widget. The current
+/// count is stored in a [ValueNotifier], which rebuilds the [AnimatedBuilder]'s
+/// contents when its value is changed.
 ///
-/// ** See code in examples/api/lib/foundation/change_notifier/change_notifier.0.dart **
+/// ** See code in examples/api/lib/widgets/transitions/listenable_builder.1.dart **
 /// {@end-tool}
-/// {@endtemplate}
 ///
 /// See also:
 ///
-///  * [TweenAnimationBuilder], which animates a property to a target value
-///    without requiring manual management of an [AnimationController].
-class AnimatedBuilder extends AnimatedWidget {
+/// * [ListenableBuilder], a widget with similar functionality, but is named
+///   more appropriately for a builder triggered on changes in [Listenable]s
+///   that aren't [Animation]s.
+/// * [TweenAnimationBuilder], which animates a property to a target value
+///   without requiring manual management of an [AnimationController].
+class AnimatedBuilder extends ListenableBuilder {
   /// Creates an animated builder.
   ///
-  /// The [animation] and [builder] arguments must not be null.
+  /// The [animation] and [builder] arguments are required.
   const AnimatedBuilder({
     super.key,
     required Listenable animation,
-    required this.builder,
-    this.child,
-  }) : assert(animation != null),
-       assert(builder != null),
-       super(listenable: animation);
+    required super.builder,
+    super.child,
+  }) : super(listenable: animation);
 
-  /// Called every time the animation changes value.
-  final TransitionBuilder builder;
+  /// The [Listenable] supplied to the constructor (typically an [Animation]).
+  ///
+  /// Also accessible through the [listenable] getter.
+  ///
+  /// See also:
+  ///
+  /// * [ListenableBuilder], a widget with similar functionality commonly used
+  ///   with [Listenable]s (such as [ChangeNotifier]) for better readability
+  ///   when the [animation] isn't an [Animation].
+  Listenable get animation => super.listenable;
 
-  /// The child widget to pass to the [builder].
-  ///
-  /// If a [builder] callback's return value contains a subtree that does not
-  /// depend on the animation, it's more efficient to build that subtree once
-  /// instead of rebuilding it on every animation tick.
-  ///
-  /// If the pre-built subtree is passed as the [child] parameter, the
-  /// [AnimatedBuilder] will pass it back to the [builder] function so that it
-  /// can be incorporated into the build.
-  ///
-  /// Using this pre-built child is entirely optional, but can improve
-  /// performance significantly in some cases and is therefore a good practice.
-  final Widget? child;
+  // Overridden getter to replace with documentation tailored to
+  // AnimatedBuilder.
 
+  /// Called every time the [animation] notifies about a change.
+  ///
+  /// The child given to the builder should typically be part of the returned
+  /// widget tree.
   @override
-  Widget build(BuildContext context) {
-    return builder(context, child);
-  }
+  TransitionBuilder get builder => super.builder;
 }
diff --git a/framework/lib/src/widgets/tween_animation_builder.dart b/framework/lib/src/widgets/tween_animation_builder.dart
index 4d3a428..d660c84 100644
--- a/framework/lib/src/widgets/tween_animation_builder.dart
+++ b/framework/lib/src/widgets/tween_animation_builder.dart
@@ -108,9 +108,7 @@
     required this.builder,
     super.onEnd,
     this.child,
-  }) : assert(tween != null),
-       assert(curve != null),
-       assert(builder != null);
+  });
 
   /// Defines the target value for the animation.
   ///
diff --git a/framework/lib/src/widgets/unique_widget.dart b/framework/lib/src/widgets/unique_widget.dart
index ee90cb5..fa6c078 100644
--- a/framework/lib/src/widgets/unique_widget.dart
+++ b/framework/lib/src/widgets/unique_widget.dart
@@ -24,8 +24,7 @@
   /// inflated instance of this widget.
   const UniqueWidget({
     required GlobalKey<T> key,
-  }) : assert(key != null),
-       super(key: key);
+  }) : super(key: key);
 
   @override
   T createState();
diff --git a/framework/lib/src/widgets/value_listenable_builder.dart b/framework/lib/src/widgets/value_listenable_builder.dart
index 0ff24fb..4b5a54d 100644
--- a/framework/lib/src/widgets/value_listenable_builder.dart
+++ b/framework/lib/src/widgets/value_listenable_builder.dart
@@ -119,8 +119,7 @@
     required this.valueListenable,
     required this.builder,
     this.child,
-  }) : assert(valueListenable != null),
-       assert(builder != null);
+  });
 
   /// The [ValueListenable] whose value you depend on in order to build.
   ///
@@ -141,10 +140,11 @@
 
   /// A [valueListenable]-independent widget which is passed back to the [builder].
   ///
-  /// This argument is optional and can be null if the entire widget subtree
-  /// the [builder] builds depends on the value of the [valueListenable]. For
-  /// example, if the [valueListenable] is a [String] and the [builder] simply
-  /// returns a [Text] widget with the [String] value.
+  /// This argument is optional and can be null if the entire widget subtree the
+  /// [builder] builds depends on the value of the [valueListenable]. For
+  /// example, in the case where the [valueListenable] is a [String] and the
+  /// [builder] returns a [Text] widget with the current [String] value, there
+  /// would be no useful [child].
   final Widget? child;
 
   @override
diff --git a/framework/lib/src/widgets/view.dart b/framework/lib/src/widgets/view.dart
new file mode 100644
index 0000000..4e73d22
--- /dev/null
+++ b/framework/lib/src/widgets/view.dart
@@ -0,0 +1,118 @@
+// 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:engine/ui.dart' show FlutterView;
+
+import 'framework.dart';
+import 'lookup_boundary.dart';
+import 'media_query.dart';
+
+/// Injects a [FlutterView] into the tree and makes it available to descendants
+/// within the same [LookupBoundary] via [View.of] and [View.maybeOf].
+///
+/// The provided [child] is wrapped in a [MediaQuery] constructed from the given
+/// [view].
+///
+/// In a future version of Flutter, the functionality of this widget will be
+/// extended to actually bootstrap the render tree that is going to be rendered
+/// into the provided [view]. This will enable rendering content into multiple
+/// [FlutterView]s from a single widget tree.
+///
+/// Each [FlutterView] can be associated with at most one [View] widget in the
+/// widget tree. Two or more [View] widgets configured with the same
+/// [FlutterView] must never exist within the same widget tree at the same time.
+/// Internally, this limitation is enforced by a [GlobalObjectKey] that derives
+/// its identity from the [view] provided to this widget.
+class View extends StatelessWidget {
+  /// Injects the provided [view] into the widget tree.
+  View({required this.view, required this.child}) : super(key: GlobalObjectKey(view));
+
+  /// The [FlutterView] to be injected into the tree.
+  final FlutterView view;
+
+  /// {@macro flutter.widgets.ProxyWidget.child}
+  final Widget child;
+
+  @override
+  Widget build(BuildContext context) {
+    return _ViewScope(
+      view: view,
+      child: MediaQuery.fromView(
+        view: view,
+        child: child,
+      ),
+    );
+  }
+
+  /// Returns the [FlutterView] that the provided `context` will render into.
+  ///
+  /// Returns null if the `context` is not associated with a [FlutterView].
+  ///
+  /// The method creates a dependency on the `context`, which will be informed
+  /// when the identity of the [FlutterView] changes (i.e. the `context` is
+  /// moved to render into a different [FlutterView] then before). The context
+  /// will not be informed when the properties on the [FlutterView] itself
+  /// change their values. To access the property values of a [FlutterView] it
+  /// is best practise to use [MediaQuery.maybeOf] instead, which will ensure
+  /// that the `context` is informed when the view properties change.
+  ///
+  /// See also:
+  ///
+  ///  * [View.of], which throws instead of returning null if no [FlutterView]
+  ///    is found.
+  static FlutterView? maybeOf(BuildContext context) {
+    return LookupBoundary.dependOnInheritedWidgetOfExactType<_ViewScope>(context)?.view;
+  }
+
+  /// Returns the [FlutterView] that the provided `context` will render into.
+  ///
+  /// Throws if the `context` is not associated with a [FlutterView].
+  ///
+  /// The method creates a dependency on the `context`, which will be informed
+  /// when the identity of the [FlutterView] changes (i.e. the `context` is
+  /// moved to render into a different [FlutterView] then before). The context
+  /// will not be informed when the properties on the [FlutterView] itself
+  /// change their values. To access the property values of a [FlutterView] it
+  /// is best practise to use [MediaQuery.of] instead, which will ensure that
+  /// the `context` is informed when the view properties change.
+  ///
+  /// See also:
+  ///
+  ///  * [View.maybeOf], which throws instead of returning null if no
+  ///    [FlutterView] is found.
+  static FlutterView of(BuildContext context) {
+    final FlutterView? result = maybeOf(context);
+    assert(() {
+      if (result == null) {
+        final bool hiddenByBoundary = LookupBoundary.debugIsHidingAncestorWidgetOfExactType<_ViewScope>(context);
+        final List<DiagnosticsNode> information = <DiagnosticsNode>[
+          if (hiddenByBoundary) ...<DiagnosticsNode>[
+            ErrorSummary('View.of() was called with a context that does not have access to a View widget.'),
+            ErrorDescription('The context provided to View.of() does have a View widget ancestor, but it is hidden by a LookupBoundary.'),
+          ] else ...<DiagnosticsNode>[
+            ErrorSummary('View.of() was called with a context that does not contain a View widget.'),
+            ErrorDescription('No View widget ancestor could be found starting from the context that was passed to View.of().'),
+          ],
+          ErrorDescription(
+            'The context used was:\n'
+            '  $context',
+          ),
+          ErrorHint('This usually means that the provided context is not associated with a View.'),
+        ];
+        throw FlutterError.fromParts(information);
+      }
+      return true;
+    }());
+    return result!;
+  }
+}
+
+class _ViewScope extends InheritedWidget {
+  const _ViewScope({required this.view, required super.child});
+
+  final FlutterView view;
+
+  @override
+  bool updateShouldNotify(_ViewScope oldWidget) => view != oldWidget.view;
+}
diff --git a/framework/lib/src/widgets/viewport.dart b/framework/lib/src/widgets/viewport.dart
index 6b1494c..c0127de 100644
--- a/framework/lib/src/widgets/viewport.dart
+++ b/framework/lib/src/widgets/viewport.dart
@@ -67,12 +67,8 @@
     this.cacheExtentStyle = CacheExtentStyle.pixel,
     this.clipBehavior = Clip.hardEdge,
     List<Widget> slivers = const <Widget>[],
-  }) : assert(offset != null),
-       assert(slivers != null),
-       assert(center == null || slivers.where((Widget child) => child.key == center).length == 1),
-       assert(cacheExtentStyle != null),
+  }) : assert(center == null || slivers.where((Widget child) => child.key == center).length == 1),
        assert(cacheExtentStyle != CacheExtentStyle.viewport || cacheExtent != null),
-       assert(clipBehavior != null),
        super(children: slivers);
 
   /// The direction in which the [offset]'s [ViewportOffset.pixels] increases.
@@ -142,7 +138,6 @@
   /// This depends on the [Directionality] if the `axisDirection` is vertical;
   /// otherwise, the default cross axis direction is downwards.
   static AxisDirection getDefaultCrossAxisDirection(BuildContext context, AxisDirection axisDirection) {
-    assert(axisDirection != null);
     switch (axisDirection) {
       case AxisDirection.up:
         assert(debugCheckHasDirectionality(
@@ -329,15 +324,14 @@
   /// rebuild this widget when the [offset] changes.
   ///
   /// The [offset] argument must not be null.
-  ShrinkWrappingViewport({
+  const ShrinkWrappingViewport({
     super.key,
     this.axisDirection = AxisDirection.down,
     this.crossAxisDirection,
     required this.offset,
     this.clipBehavior = Clip.hardEdge,
     List<Widget> slivers = const <Widget>[],
-  }) : assert(offset != null),
-       super(children: slivers);
+  }) : super(children: slivers);
 
   /// The direction in which the [offset]'s [ViewportOffset.pixels] increases.
   ///
diff --git a/framework/lib/src/widgets/visibility.dart b/framework/lib/src/widgets/visibility.dart
index 2be5cdf..d914049 100644
--- a/framework/lib/src/widgets/visibility.dart
+++ b/framework/lib/src/widgets/visibility.dart
@@ -62,13 +62,7 @@
     this.maintainSize = false,
     this.maintainSemantics = false,
     this.maintainInteractivity = false,
-  }) : assert(child != null),
-       assert(replacement != null),
-       assert(visible != null),
-       assert(maintainState != null),
-       assert(maintainAnimation != null),
-       assert(maintainSize != null),
-       assert(
+  }) : assert(
          maintainState == true || maintainAnimation == false,
          'Cannot maintain animations if the state is not also maintained.',
        ),
@@ -98,10 +92,7 @@
     required this.child,
     this.replacement = const SizedBox.shrink(),
     this.visible = true,
-  }) :  assert(child != null),
-        assert(replacement != null),
-        assert(visible != null),
-        maintainState = true,
+  }) :  maintainState = true,
         maintainAnimation = true,
         maintainSize = true,
         maintainSemantics = true,
@@ -329,15 +320,7 @@
     this.maintainSize = false,
     this.maintainSemantics = false,
     this.maintainInteractivity = false,
-  }) : assert(sliver != null),
-       assert(replacementSliver != null),
-       assert(visible != null),
-       assert(maintainState != null),
-       assert(maintainAnimation != null),
-       assert(maintainSize != null),
-       assert(maintainSemantics != null),
-       assert(maintainInteractivity != null),
-       assert(
+  }) : assert(
          maintainState == true || maintainAnimation == false,
          'Cannot maintain animations if the state is not also maintained.',
        ),
@@ -367,10 +350,7 @@
     required this.sliver,
     this.replacementSliver = const SliverToBoxAdapter(),
     this.visible = true,
-  }) :  assert(sliver != null),
-        assert(replacementSliver != null),
-        assert(visible != null),
-        maintainState = true,
+  }) :  maintainState = true,
         maintainAnimation = true,
         maintainSize = true,
         maintainSemantics = true,
diff --git a/framework/lib/src/widgets/widget_inspector.dart b/framework/lib/src/widgets/widget_inspector.dart
index 7124150..ee4922e 100644
--- a/framework/lib/src/widgets/widget_inspector.dart
+++ b/framework/lib/src/widgets/widget_inspector.dart
@@ -10,6 +10,7 @@
 import 'package:engine/ui.dart' as ui
     show
         ClipOp,
+        FlutterView,
         Image,
         ImageByteFormat,
         Paragraph,
@@ -30,6 +31,7 @@
 import 'framework.dart';
 import 'gesture_detector.dart';
 import 'service_extensions.dart';
+import 'view.dart';
 
 /// Signature for the builder callback used by
 /// [WidgetInspector.selectButtonBuilder].
@@ -77,9 +79,7 @@
   _MulticastCanvas({
     required Canvas main,
     required Canvas screenshot,
-  }) : assert(main != null),
-       assert(screenshot != null),
-       _main = main,
+  }) : _main = main,
        _screenshot = screenshot;
 
   final Canvas _main;
@@ -335,8 +335,7 @@
 class _ScreenshotData {
   _ScreenshotData({
     required this.target,
-  }) : assert(target != null),
-       containerLayer = _ScreenshotContainerLayer();
+  }) : containerLayer = _ScreenshotContainerLayer();
 
   /// Target to take a screenshot of.
   final RenderObject target;
@@ -552,10 +551,9 @@
     bool debugPaint = false,
   }) {
     RenderObject repaintBoundary = renderObject;
-    while (repaintBoundary != null && !repaintBoundary.isRepaintBoundary) {
+    while (!repaintBoundary.isRepaintBoundary) {
       repaintBoundary = repaintBoundary.parent! as RenderObject;
     }
-    assert(repaintBoundary != null);
     final _ScreenshotData data = _ScreenshotData(target: renderObject);
     final _ScreenshotPaintingContext context = _ScreenshotPaintingContext(
       containerLayer: repaintBoundary.debugLayer!,
@@ -623,8 +621,7 @@
     required this.node,
     required this.children,
     this.childIndex,
-  }) : assert(node != null),
-       assert(children != null);
+  });
 
   /// Node at the point in the path this [_DiagnosticsPathNode] is describing.
   final DiagnosticsNode node;
@@ -827,9 +824,6 @@
     required AsyncValueGetter<bool> getter,
     required AsyncValueSetter<bool> setter,
   }) {
-    assert(name != null);
-    assert(getter != null);
-    assert(setter != null);
     registerServiceExtension(
       name: name,
       callback: (Map<String, String> parameters) async {
@@ -1331,8 +1325,7 @@
   /// appropriate to display the Widget tree in the inspector.
   @protected
   bool isWidgetTreeReady([ String? groupName ]) {
-    return WidgetsBinding.instance != null &&
-           WidgetsBinding.instance.debugDidSendFirstFrameEvent;
+    return WidgetsBinding.instance.debugDidSendFirstFrameEvent;
   }
 
   /// Returns the Dart object associated with a reference id.
@@ -1498,13 +1491,13 @@
           return false;
         }
         selection.currentElement = object;
-        developer.inspect(selection.currentElement);
+        _sendInspectEvent(selection.currentElement);
       } else {
         if (object == selection.current) {
           return false;
         }
         selection.current = object! as RenderObject;
-        developer.inspect(selection.current);
+        _sendInspectEvent(selection.current);
       }
       if (selectionChangedCallback != null) {
         if (SchedulerBinding.instance.schedulerPhase == SchedulerPhase.idle) {
@@ -1523,6 +1516,25 @@
     return false;
   }
 
+  /// Notify attached tools to navigate to an object's source location.
+  void _sendInspectEvent(Object? object){
+    inspect(object);
+
+    final _Location? location = _getSelectedSummaryWidgetLocation(null);
+    if (location != null) {
+      postEvent(
+        'navigate',
+        <String, Object>{
+          'fileUri': location.file, // URI file path of the location.
+          'line': location.line, // 1-based line number.
+          'column': location.column, // 1-based column number.
+          'source': 'flutter.inspector',
+        },
+        stream: 'ToolEvent',
+      );
+    }
+  }
+
   /// Returns a DevTools uri linking to a specific element on the inspector page.
   String? _devToolsInspectorUriForElement(Element element) {
     if (activeDevToolsServerAddress != null && connectedVmServiceUri != null) {
@@ -1977,7 +1989,6 @@
 
     if (renderObject.debugNeedsLayout) {
       final PipelineOwner owner = renderObject.owner!;
-      assert(owner != null);
       assert(!owner.debugDoingLayout);
       owner
         ..flushLayout()
@@ -2212,9 +2223,16 @@
   }
 
   Map<String, Object?>? _getSelectedWidget(String? previousSelectionId, String groupName) {
+    return _nodeToJson(
+      _getSelectedWidgetDiagnosticsNode(previousSelectionId),
+      InspectorSerializationDelegate(groupName: groupName, service: this),
+    );
+  }
+
+  DiagnosticsNode? _getSelectedWidgetDiagnosticsNode(String? previousSelectionId) {
     final DiagnosticsNode? previousSelection = toObject(previousSelectionId) as DiagnosticsNode?;
     final Element? current = selection.currentElement;
-    return _nodeToJson(current == previousSelection?.value ? previousSelection : current?.toDiagnosticsNode(), InspectorSerializationDelegate(groupName: groupName, service: this));
+    return current == previousSelection?.value ? previousSelection : current?.toDiagnosticsNode();
   }
 
   /// Returns a [DiagnosticsNode] representing the currently selected [Element]
@@ -2229,9 +2247,13 @@
     return _safeJsonEncode(_getSelectedSummaryWidget(previousSelectionId, groupName));
   }
 
-  Map<String, Object?>? _getSelectedSummaryWidget(String? previousSelectionId, String groupName) {
+  _Location? _getSelectedSummaryWidgetLocation(String? previousSelectionId) {
+     return _getCreationLocation(_getSelectedSummaryDiagnosticsNode(previousSelectionId)?.value);
+  }
+
+  DiagnosticsNode? _getSelectedSummaryDiagnosticsNode(String? previousSelectionId) {
     if (!isWidgetCreationTracked()) {
-      return _getSelectedWidget(previousSelectionId, groupName);
+      return _getSelectedWidgetDiagnosticsNode(previousSelectionId);
     }
     final DiagnosticsNode? previousSelection = toObject(previousSelectionId) as DiagnosticsNode?;
     Element? current = selection.currentElement;
@@ -2245,7 +2267,11 @@
       }
       current = firstLocal;
     }
-    return _nodeToJson(current == previousSelection?.value ? previousSelection : current?.toDiagnosticsNode(), InspectorSerializationDelegate(groupName: groupName, service: this));
+    return current == previousSelection?.value ? previousSelection : current?.toDiagnosticsNode();
+  }
+
+  Map<String, Object?>? _getSelectedSummaryWidget(String? previousSelectionId, String groupName) {
+    return _nodeToJson(_getSelectedSummaryDiagnosticsNode(previousSelectionId), InspectorSerializationDelegate(groupName: groupName, service: this));
   }
 
   /// Returns whether [Widget] creation locations are available.
@@ -2279,12 +2305,27 @@
   }
 
   /// All events dispatched by a [WidgetInspectorService] use this method
-  /// instead of calling [developer.postEvent] directly so that tests for
-  /// [WidgetInspectorService] can track which events were dispatched by
-  /// overriding this method.
+  /// instead of calling [developer.postEvent] directly.
+  ///
+  /// This allows tests for [WidgetInspectorService] to track which events were
+  /// dispatched by overriding this method.
   @protected
-  void postEvent(String eventKind, Map<Object, Object?> eventData) {
-    developer.postEvent(eventKind, eventData);
+  void postEvent(
+    String eventKind,
+    Map<Object, Object?> eventData, {
+    String stream = 'Extension',
+  }) {
+    developer.postEvent(eventKind, eventData, stream: stream);
+  }
+
+  /// All events dispatched by a [WidgetInspectorService] use this method
+  /// instead of calling [developer.inspect].
+  ///
+  /// This allows tests for [WidgetInspectorService] to track which events were
+  /// dispatched by overriding this method.
+  @protected
+  void inspect(Object? object) {
+    developer.inspect(object);
   }
 
   final _ElementLocationStatsTracker _rebuildStats = _ElementLocationStatsTracker();
@@ -2559,7 +2600,7 @@
     super.key,
     required this.child,
     required this.selectButtonBuilder,
-  }) : assert(child != null);
+  });
 
   /// The widget that is being inspected.
   final Widget child;
@@ -2637,7 +2678,6 @@
     final List<DiagnosticsNode> children = object.debugDescribeChildren();
     for (int i = children.length - 1; i >= 0; i -= 1) {
       final DiagnosticsNode diagnostics = children[i];
-      assert(diagnostics != null);
       if (diagnostics.style == DiagnosticsTreeStyle.offstage ||
           diagnostics.value is! RenderObject) {
         continue;
@@ -2726,7 +2766,8 @@
     // on the edge of the display. If the pointer is being dragged off the edge
     // of the display we do not want to select anything. A user can still select
     // a widget that is only at the exact screen margin by tapping.
-    final Rect bounds = (Offset.zero & (WidgetsBinding.instance.window.physicalSize / WidgetsBinding.instance.window.devicePixelRatio)).deflate(_kOffScreenMargin);
+    final ui.FlutterView view = View.of(context);
+    final Rect bounds = (Offset.zero & (view.physicalSize / view.devicePixelRatio)).deflate(_kOffScreenMargin);
     if (!bounds.contains(_lastPointerLocation!)) {
       setState(() {
         selection.clear();
@@ -2740,9 +2781,7 @@
     }
     if (_lastPointerLocation != null) {
       _inspectAt(_lastPointerLocation!);
-
-      // Notify debuggers to open an inspector on the object.
-      developer.inspect(selection.current);
+      WidgetInspectorService.instance._sendInspectEvent(selection.current);
     }
     setState(() {
       // Only exit select mode if there is a button to return to select mode.
@@ -2890,8 +2929,7 @@
 class _RenderInspectorOverlay extends RenderBox {
   /// The arguments must not be null.
   _RenderInspectorOverlay({ required InspectorSelection selection })
-    : _selection = selection,
-      assert(selection != null);
+    : _selection = selection;
 
   InspectorSelection get selection => _selection;
   InspectorSelection _selection;
@@ -2999,8 +3037,7 @@
     required this.overlayRect,
     required this.selection,
     required this.rootRenderObject,
-  }) : assert(overlayRect != null),
-       assert(selection != null) {
+  }) {
     bool inDebugMode = false;
     assert(() {
       inDebugMode = true;
@@ -3440,9 +3477,7 @@
   ///
   /// The `description` and `url` arguments must not be null.
   DevToolsDeepLinkProperty(String description, String url)
-    : assert(description != null),
-      assert(url != null),
-      super('', url, description: description, level: DiagnosticLevel.info);
+    : super('', url, description: description, level: DiagnosticLevel.info);
 }
 
 /// Returns if an object is user created.
diff --git a/framework/lib/src/widgets/widget_span.dart b/framework/lib/src/widgets/widget_span.dart
index 3b37366..87620ef 100644
--- a/framework/lib/src/widgets/widget_span.dart
+++ b/framework/lib/src/widgets/widget_span.dart
@@ -77,8 +77,7 @@
     super.alignment,
     super.baseline,
     super.style,
-  }) : assert(child != null),
-       assert(
+  }) : assert(
          baseline != null || !(
           identical(alignment, ui.PlaceholderAlignment.aboveBaseline) ||
           identical(alignment, ui.PlaceholderAlignment.belowBaseline) ||
diff --git a/framework/lib/src/widgets/will_pop_scope.dart b/framework/lib/src/widgets/will_pop_scope.dart
index d620939..ab90c7f 100644
--- a/framework/lib/src/widgets/will_pop_scope.dart
+++ b/framework/lib/src/widgets/will_pop_scope.dart
@@ -33,7 +33,7 @@
     super.key,
     required this.child,
     required this.onWillPop,
-  }) : assert(child != null);
+  });
 
   /// The widget below this widget in the tree.
   ///
diff --git a/framework/lib/src/widgets/window.dart b/framework/lib/src/widgets/window.dart
new file mode 100644
index 0000000..873df8f
--- /dev/null
+++ b/framework/lib/src/widgets/window.dart
@@ -0,0 +1,8 @@
+// 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.
+
+/// Placeholder to be used in a future version of Flutter.
+abstract class Window {
+  const Window._();
+}
diff --git a/framework/lib/widgets.dart b/framework/lib/widgets.dart
index 03fe927..27b8865 100644
--- a/framework/lib/widgets.dart
+++ b/framework/lib/widgets.dart
@@ -72,6 +72,7 @@
 export 'src/widgets/layout_builder.dart';
 export 'src/widgets/list_wheel_scroll_view.dart';
 export 'src/widgets/localizations.dart';
+export 'src/widgets/lookup_boundary.dart';
 export 'src/widgets/magnifier.dart';
 export 'src/widgets/media_query.dart';
 export 'src/widgets/modal_barrier.dart';
@@ -134,6 +135,7 @@
 export 'src/widgets/spell_check.dart';
 export 'src/widgets/status_transitions.dart';
 export 'src/widgets/table.dart';
+export 'src/widgets/tap_and_drag_gestures.dart';
 export 'src/widgets/tap_region.dart';
 export 'src/widgets/text.dart';
 export 'src/widgets/text_editing_intents.dart';
@@ -147,6 +149,7 @@
 export 'src/widgets/tween_animation_builder.dart';
 export 'src/widgets/unique_widget.dart';
 export 'src/widgets/value_listenable_builder.dart';
+export 'src/widgets/view.dart';
 export 'src/widgets/viewport.dart';
 export 'src/widgets/visibility.dart';
 export 'src/widgets/widget_inspector.dart';
diff --git a/script/bin/sync.dart b/script/bin/sync.dart
index 12a1bd0..673d371 100644
--- a/script/bin/sync.dart
+++ b/script/bin/sync.dart
@@ -1,3 +1,5 @@
+import 'dart:io' as io;
+
 import 'package:file/file.dart';
 import 'package:file/local.dart';
 import 'package:path/path.dart' as p;
@@ -39,18 +41,27 @@
     String source = file.readAsStringSync();
     source = source.replaceAll("'package:flutter/", "'package:flute/");
     source = source.replaceAll('exception is NullThrownError', 'false');
-    if (relPath == r'src\material\dialog.dart' ||
-        relPath == r'src\material\navigation_rail.dart' ||
-        relPath == r'src\material\switch.dart' ||
-        relPath == r'src\widgets\icon.dart') {
+    if (relPath == p.join('src', 'material', 'dialog.dart') ||
+        relPath == p.join('src', 'material', 'navigation_rail.dart') ||
+        relPath == p.join('src', 'material', 'switch.dart') ||
+        relPath == p.join('src', 'widgets', 'icon.dart') ||
+        relPath == p.join('src', 'material', 'time_picker.dart') ||
+        relPath == p.join('src', 'material', 'time_picker_theme.dart')) {
       source = source.replaceAll("'dart:ui'", "'package:engine/ui.dart' hide TextStyle");
     } else {
       source = source.replaceAll("'dart:ui'", "'package:engine/ui.dart'");
     }
 
-    if (relPath != r'src\foundation\bitfield.dart' &&
-        relPath != r'src\foundation\platform.dart' &&
-        relPath != r'src\services\platform_channel.dart') {
+    if (relPath == p.join('src', 'foundation', 'math.dart')) {
+      source = source.split('\n').where((String line) {
+        return line.trim().isEmpty || line.trim().startsWith('//');
+      }).join('\n');
+      source += "\nexport 'package:engine/ui.dart' show clampDouble;";
+    }
+
+    if (relPath != p.join('src', 'foundation', 'bitfield.dart') &&
+        relPath != p.join('src', 'foundation', 'platform.dart') &&
+        relPath != p.join('src', 'services', 'platform_channel.dart')) {
       source = source.split('\n').map<String>((String line) {
         final int indexOfConditionalImport = line.indexOf(r'if (dart.library');
         if (indexOfConditionalImport == -1) {
@@ -67,7 +78,7 @@
       }).join('\n');
     }
 
-    if (relPath == r'src\foundation\_platform_web.dart') {
+    if (relPath == p.join('src', 'foundation', '_platform_web.dart')) {
       source = source.replaceAll('if (ui.debugEmulateFlutterTesterEnvironment as bool)', 'if (true)');
     }
 
@@ -90,7 +101,9 @@
 }
 
 Future<Directory> _findFlutterRepo() async {
-  final String where = (await pm.run(<String>['where', 'flutter'])).stdout as String;
+  final String whereOrWhich = io.Platform.operatingSystem == 'window'
+    ? 'where' : 'which';
+  final String where = (await pm.run(<String>[whereOrWhich, 'flutter'])).stdout as String;
   final String pathToFlutterBin = where.split('\n').first;
   final Directory bin = fs.directory(p.dirname(pathToFlutterBin));
   return bin.parent;