mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
Fixes DragTarget
crash if Draggable.data
is null
(#133136)
Makes the `data` parameter of `Draggable` non-nullable. Fixes https://github.com/flutter/flutter/issues/84816 *If you had to change anything in the [flutter/tests] repo, include a link to the migration guide as per the [breaking change policy].*
This commit is contained in:
parent
7646430c25
commit
f15f2313b9
@ -652,11 +652,13 @@ class DragTarget<T extends Object> extends StatefulWidget {
|
||||
final DragTargetWillAcceptWithDetails<T>? onWillAcceptWithDetails;
|
||||
|
||||
/// Called when an acceptable piece of data was dropped over this drag target.
|
||||
/// It will not be called if `data` is `null`.
|
||||
///
|
||||
/// Equivalent to [onAcceptWithDetails], but only includes the data.
|
||||
final DragTargetAccept<T>? onAccept;
|
||||
|
||||
/// Called when an acceptable piece of data was dropped over this drag target.
|
||||
/// It will not be called if `data` is `null`.
|
||||
///
|
||||
/// Equivalent to [onAccept], but with information, including the data, in a
|
||||
/// [DragTargetDetails].
|
||||
@ -666,7 +668,8 @@ class DragTarget<T extends Object> extends StatefulWidget {
|
||||
/// the target.
|
||||
final DragTargetLeave<T>? onLeave;
|
||||
|
||||
/// Called when a [Draggable] moves within this [DragTarget].
|
||||
/// Called when a [Draggable] moves within this [DragTarget]. It will not be
|
||||
/// called if `data` is `null`.
|
||||
///
|
||||
/// This includes entering and leaving the target.
|
||||
final DragTargetMove<T>? onMove;
|
||||
@ -707,6 +710,7 @@ class _DragTargetState<T extends Object> extends State<DragTarget<T>> {
|
||||
(widget.onWillAccept != null &&
|
||||
widget.onWillAccept!(avatar.data as T?)) ||
|
||||
(widget.onWillAcceptWithDetails != null &&
|
||||
avatar.data != null &&
|
||||
widget.onWillAcceptWithDetails!(DragTargetDetails<T>(data: avatar.data! as T, offset: avatar._lastOffset!)));
|
||||
if (resolvedWillAccept) {
|
||||
setState(() {
|
||||
@ -741,12 +745,14 @@ class _DragTargetState<T extends Object> extends State<DragTarget<T>> {
|
||||
setState(() {
|
||||
_candidateAvatars.remove(avatar);
|
||||
});
|
||||
widget.onAccept?.call(avatar.data! as T);
|
||||
widget.onAcceptWithDetails?.call(DragTargetDetails<T>(data: avatar.data! as T, offset: avatar._lastOffset!));
|
||||
if (avatar.data != null) {
|
||||
widget.onAccept?.call(avatar.data! as T);
|
||||
widget.onAcceptWithDetails?.call(DragTargetDetails<T>(data: avatar.data! as T, offset: avatar._lastOffset!));
|
||||
}
|
||||
}
|
||||
|
||||
void didMove(_DragAvatar<Object> avatar) {
|
||||
if (!mounted) {
|
||||
if (!mounted || avatar.data == null) {
|
||||
return;
|
||||
}
|
||||
widget.onMove?.call(DragTargetDetails<T>(data: avatar.data! as T, offset: avatar._lastOffset!));
|
||||
|
@ -395,6 +395,47 @@ void main() {
|
||||
expect(targetMoveCount['Target 2'], equals(1));
|
||||
});
|
||||
|
||||
testWidgetsWithLeakTracking('Drag and drop - onMove is not called if moved with null data', (WidgetTester tester) async {
|
||||
bool onMoveCalled = false;
|
||||
|
||||
await tester.pumpWidget(MaterialApp(
|
||||
home: Column(
|
||||
children: <Widget>[
|
||||
const Draggable<int>(
|
||||
feedback: Text('Dragging'),
|
||||
child: Text('Source'),
|
||||
),
|
||||
DragTarget<int>(
|
||||
builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
|
||||
return const SizedBox(height: 100.0, child: Text('Target'));
|
||||
},
|
||||
onMove: (DragTargetDetails<dynamic> details) {
|
||||
onMoveCalled = true;
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
));
|
||||
|
||||
expect(onMoveCalled, isFalse);
|
||||
|
||||
final Offset firstLocation = tester.getCenter(find.text('Source'));
|
||||
final TestGesture gesture = await tester.startGesture(firstLocation, pointer: 7);
|
||||
await tester.pump();
|
||||
|
||||
expect(onMoveCalled, isFalse);
|
||||
|
||||
final Offset secondLocation = tester.getCenter(find.text('Target'));
|
||||
await gesture.moveTo(secondLocation);
|
||||
await tester.pump();
|
||||
|
||||
expect(onMoveCalled, isFalse);
|
||||
await gesture.up();
|
||||
await tester.pump();
|
||||
|
||||
expect(onMoveCalled, isFalse);
|
||||
});
|
||||
|
||||
testWidgetsWithLeakTracking('Drag and drop - dragging over button', (WidgetTester tester) async {
|
||||
final List<String> events = <String>[];
|
||||
Offset firstLocation, secondLocation;
|
||||
@ -2392,6 +2433,68 @@ void main() {
|
||||
expect(find.text('Target'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgetsWithLeakTracking('Drag and drop - onAccept is not called if dropped with null data', (WidgetTester tester) async {
|
||||
bool onAcceptCalled = false;
|
||||
bool onAcceptWithDetailsCalled = false;
|
||||
|
||||
await tester.pumpWidget(MaterialApp(
|
||||
home: Column(
|
||||
children: <Widget>[
|
||||
const Draggable<int>(
|
||||
feedback: Text('Dragging'),
|
||||
child: Text('Source'),
|
||||
),
|
||||
DragTarget<int>(
|
||||
builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
|
||||
return const SizedBox(height: 100.0, child: Text('Target'));
|
||||
},
|
||||
onAccept: (int data) {
|
||||
onAcceptCalled = true;
|
||||
},
|
||||
onAcceptWithDetails: (DragTargetDetails<int> details) {
|
||||
onAcceptWithDetailsCalled =true;
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
));
|
||||
|
||||
expect(onAcceptCalled, isFalse);
|
||||
expect(onAcceptWithDetailsCalled, isFalse);
|
||||
expect(find.text('Source'), findsOneWidget);
|
||||
expect(find.text('Dragging'), findsNothing);
|
||||
expect(find.text('Target'), findsOneWidget);
|
||||
|
||||
final Offset firstLocation = tester.getCenter(find.text('Source'));
|
||||
final TestGesture gesture = await tester.startGesture(firstLocation, pointer: 7);
|
||||
await tester.pump();
|
||||
|
||||
expect(onAcceptCalled, isFalse);
|
||||
expect(onAcceptWithDetailsCalled, isFalse);
|
||||
expect(find.text('Source'), findsOneWidget);
|
||||
expect(find.text('Dragging'), findsOneWidget);
|
||||
expect(find.text('Target'), findsOneWidget);
|
||||
|
||||
final Offset secondLocation = tester.getCenter(find.text('Target'));
|
||||
await gesture.moveTo(secondLocation);
|
||||
await tester.pump();
|
||||
|
||||
expect(onAcceptCalled, isFalse);
|
||||
expect(onAcceptWithDetailsCalled, isFalse);
|
||||
expect(find.text('Source'), findsOneWidget);
|
||||
expect(find.text('Dragging'), findsOneWidget);
|
||||
expect(find.text('Target'), findsOneWidget);
|
||||
|
||||
await gesture.up();
|
||||
await tester.pump();
|
||||
|
||||
expect(onAcceptCalled, isFalse, reason: 'onAccept should not be called when data is null');
|
||||
expect(onAcceptWithDetailsCalled, isFalse, reason: 'onAcceptWithDetails should not be called when data is null');
|
||||
expect(find.text('Source'), findsOneWidget);
|
||||
expect(find.text('Dragging'), findsNothing);
|
||||
expect(find.text('Target'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgetsWithLeakTracking('Draggable disposes recognizer', (WidgetTester tester) async {
|
||||
late final OverlayEntry entry;
|
||||
addTearDown(() => entry..remove()..dispose());
|
||||
|
Loading…
Reference in New Issue
Block a user