Minor adjustments on 2D APIs (#131358)
These tweaks came from https://github.com/flutter/packages/pull/4536
- The TwoDimensionalChildBuilderDelegate asserts that maxXIndex and maxYIndex are null or >= 0
- The TwoDimensionalChildDelegate setter in RenderTwoDimensionalViewport has a covariant to allow type safety for subclasses of RenderTwoDimensionalViewport implementing with other subclasses of TwoDimensionalChildDelegate
I'd like to cherry pick this so https://github.com/flutter/packages/pull/4536 will not have to wait for it to reach stable.
diff --git a/packages/flutter/lib/src/widgets/scroll_delegate.dart b/packages/flutter/lib/src/widgets/scroll_delegate.dart
index 28f9e28..765bf31 100644
--- a/packages/flutter/lib/src/widgets/scroll_delegate.dart
+++ b/packages/flutter/lib/src/widgets/scroll_delegate.dart
@@ -933,7 +933,9 @@
required this.builder,
int? maxXIndex,
int? maxYIndex,
- }) : _maxYIndex = maxYIndex,
+ }) : assert(maxYIndex == null || maxYIndex >= 0),
+ assert(maxXIndex == null || maxXIndex >= 0),
+ _maxYIndex = maxYIndex,
_maxXIndex = maxXIndex;
/// Called to build children on demand.
@@ -974,6 +976,8 @@
/// [TwoDimensionalViewport] subclass to learn how this value is applied in
/// the specific use case.
///
+ /// If not null, the value must be non-negative.
+ ///
/// If the value changes, the delegate will call [notifyListeners]. This
/// informs the [RenderTwoDimensionalViewport] that any cached information
/// from the delegate is invalid.
@@ -993,6 +997,7 @@
if (value == maxXIndex) {
return;
}
+ assert(value == null || value >= 0);
_maxXIndex = value;
notifyListeners();
}
@@ -1015,6 +1020,7 @@
if (maxYIndex == value) {
return;
}
+ assert(value == null || value >= 0);
_maxYIndex = value;
notifyListeners();
}
diff --git a/packages/flutter/lib/src/widgets/two_dimensional_viewport.dart b/packages/flutter/lib/src/widgets/two_dimensional_viewport.dart
index 69976eb..82befbc 100644
--- a/packages/flutter/lib/src/widgets/two_dimensional_viewport.dart
+++ b/packages/flutter/lib/src/widgets/two_dimensional_viewport.dart
@@ -612,7 +612,7 @@
/// Supplies children for layout in the viewport.
TwoDimensionalChildDelegate get delegate => _delegate;
TwoDimensionalChildDelegate _delegate;
- set delegate(TwoDimensionalChildDelegate value) {
+ set delegate(covariant TwoDimensionalChildDelegate value) {
if (_delegate == value) {
return;
}
diff --git a/packages/flutter/test/widgets/two_dimensional_viewport_test.dart b/packages/flutter/test/widgets/two_dimensional_viewport_test.dart
index b44fac3..a445d34 100644
--- a/packages/flutter/test/widgets/two_dimensional_viewport_test.dart
+++ b/packages/flutter/test/widgets/two_dimensional_viewport_test.dart
@@ -111,6 +111,78 @@
);
}, variant: TargetPlatformVariant.all());
+ test('maxXIndex and maxYIndex assertions', () {
+ final TwoDimensionalChildBuilderDelegate delegate = TwoDimensionalChildBuilderDelegate(
+ maxXIndex: 0,
+ maxYIndex: 0,
+ builder: (BuildContext context, ChildVicinity vicinity) {
+ return const SizedBox.shrink();
+ }
+ );
+ // Update
+ expect(
+ () {
+ delegate.maxXIndex = -1;
+ },
+ throwsA(
+ isA<AssertionError>().having(
+ (AssertionError error) => error.toString(),
+ 'description',
+ contains('value == null || value >= 0'),
+ ),
+ ),
+ );
+ expect(
+ () {
+ delegate.maxYIndex = -1;
+ },
+ throwsA(
+ isA<AssertionError>().having(
+ (AssertionError error) => error.toString(),
+ 'description',
+ contains('value == null || value >= 0'),
+ ),
+ ),
+ );
+ // Constructor
+ expect(
+ () {
+ TwoDimensionalChildBuilderDelegate(
+ maxXIndex: -1,
+ maxYIndex: 0,
+ builder: (BuildContext context, ChildVicinity vicinity) {
+ return const SizedBox.shrink();
+ }
+ );
+ },
+ throwsA(
+ isA<AssertionError>().having(
+ (AssertionError error) => error.toString(),
+ 'description',
+ contains('maxXIndex == null || maxXIndex >= 0'),
+ ),
+ ),
+ );
+ expect(
+ () {
+ TwoDimensionalChildBuilderDelegate(
+ maxXIndex: 0,
+ maxYIndex: -1,
+ builder: (BuildContext context, ChildVicinity vicinity) {
+ return const SizedBox.shrink();
+ }
+ );
+ },
+ throwsA(
+ isA<AssertionError>().having(
+ (AssertionError error) => error.toString(),
+ 'description',
+ contains('maxYIndex == null || maxYIndex >= 0'),
+ ),
+ ),
+ );
+ });
+
testWidgets('throws an error when builder throws', (WidgetTester tester) async {
final List<Object> exceptions = <Object>[];
final FlutterExceptionHandler? oldHandler = FlutterError.onError;
@@ -1822,3 +1894,28 @@
100.0,
);
}
+
+// Validates covariant through analysis.
+mixin _SomeDelegateMixin on TwoDimensionalChildDelegate {}
+
+class _SomeRenderTwoDimensionalViewport extends RenderTwoDimensionalViewport { // ignore: unused_element
+ _SomeRenderTwoDimensionalViewport({
+ required super.horizontalOffset,
+ required super.horizontalAxisDirection,
+ required super.verticalOffset,
+ required super.verticalAxisDirection,
+ required _SomeDelegateMixin super.delegate,
+ required super.mainAxis,
+ required super.childManager,
+ });
+
+ @override
+ _SomeDelegateMixin get delegate => super.delegate as _SomeDelegateMixin;
+ @override
+ set delegate(_SomeDelegateMixin value) { // Analysis would fail without covariant
+ super.delegate = value;
+ }
+
+ @override
+ void layoutChildSequence() {}
+}