Unverified Commit aa8f8613 authored by Michael Goderbauer's avatar Michael Goderbauer Committed by GitHub

Fix crash when disposing nested Scrollables while holding in overscroll position (#27864)

parent 964fab02
......@@ -53,10 +53,12 @@ abstract class Notification {
///
/// The notification will be delivered to any [NotificationListener] widgets
/// with the appropriate type parameters that are ancestors of the given
/// [BuildContext].
/// [BuildContext]. If the [BuildContext] is null, the notification is not
/// dispatched.
void dispatch(BuildContext target) {
assert(target != null); // Only call dispatch if the widget's State is still mounted.
target.visitAncestorElements(visitAncestor);
// The `target` may be null if the subtree the notification is supposed to be
// dispatched in is in the process of being disposed.
target?.visitAncestorElements(visitAncestor);
}
@override
......
......@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/widgets.dart';
......@@ -28,4 +30,68 @@ void main() {
tester.state<FlipWidgetState>(find.byType(FlipWidget)).flip();
await tester.pump(const Duration(hours: 5));
});
testWidgets('Disposing a (nested) Scrollable while holding in overscroll (iOS) does not crash', (WidgetTester tester) async {
// Regression test for https://github.com/flutter/flutter/issues/27707.
debugDefaultTargetPlatformOverride = TargetPlatform.iOS;
final ScrollController controller = ScrollController();
final Key outterContainer = GlobalKey();
await tester.pumpWidget(
MaterialApp(
home: Center(
child: Container(
key: outterContainer,
color: Colors.purple,
width: 400.0,
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Container(
width: 500.0,
child: ListView.builder(
controller: controller,
itemBuilder: (BuildContext context, int index) {
return Container(
color: index % 2 == 0 ? Colors.red : Colors.green,
height: 200.0,
child: Text('Hello $index'),
);
},
),
),
),
),
),
),
);
// Go into overscroll.
double lastScrollOffset;
await tester.fling(find.text('Hello 0'), const Offset(0.0, 1000.0), 1000.0);
await tester.pump(const Duration(milliseconds: 100));
expect(lastScrollOffset = controller.offset, lessThan(0.0));
// Reduce the overscroll a little, but don't let it go back to 0.0.
await tester.pump(const Duration(milliseconds: 100));
expect(controller.offset, greaterThan(lastScrollOffset));
expect(controller.offset, lessThan(0.0));
final double currentOffset = controller.offset;
// Start a hold activity by putting one pointer down.
await tester.startGesture(tester.getTopLeft(find.byKey(outterContainer)) + const Offset(50.0, 50.0));
await tester.pumpAndSettle(); // This shouldn't change the scroll offset because of the down event above.
expect(controller.offset, currentOffset);
// Dispose the scrollables while the finger is still down, this should not crash.
await tester.pumpWidget(
MaterialApp(
home: Container(),
)
);
await tester.pumpAndSettle();
expect(controller.hasClients, isFalse);
debugDefaultTargetPlatformOverride = null;
});
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment