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;
|
final DragTargetWillAcceptWithDetails<T>? onWillAcceptWithDetails;
|
||||||
|
|
||||||
/// Called when an acceptable piece of data was dropped over this drag target.
|
/// 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.
|
/// Equivalent to [onAcceptWithDetails], but only includes the data.
|
||||||
final DragTargetAccept<T>? onAccept;
|
final DragTargetAccept<T>? onAccept;
|
||||||
|
|
||||||
/// Called when an acceptable piece of data was dropped over this drag target.
|
/// 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
|
/// Equivalent to [onAccept], but with information, including the data, in a
|
||||||
/// [DragTargetDetails].
|
/// [DragTargetDetails].
|
||||||
@ -666,7 +668,8 @@ class DragTarget<T extends Object> extends StatefulWidget {
|
|||||||
/// the target.
|
/// the target.
|
||||||
final DragTargetLeave<T>? onLeave;
|
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.
|
/// This includes entering and leaving the target.
|
||||||
final DragTargetMove<T>? onMove;
|
final DragTargetMove<T>? onMove;
|
||||||
@ -707,6 +710,7 @@ class _DragTargetState<T extends Object> extends State<DragTarget<T>> {
|
|||||||
(widget.onWillAccept != null &&
|
(widget.onWillAccept != null &&
|
||||||
widget.onWillAccept!(avatar.data as T?)) ||
|
widget.onWillAccept!(avatar.data as T?)) ||
|
||||||
(widget.onWillAcceptWithDetails != null &&
|
(widget.onWillAcceptWithDetails != null &&
|
||||||
|
avatar.data != null &&
|
||||||
widget.onWillAcceptWithDetails!(DragTargetDetails<T>(data: avatar.data! as T, offset: avatar._lastOffset!)));
|
widget.onWillAcceptWithDetails!(DragTargetDetails<T>(data: avatar.data! as T, offset: avatar._lastOffset!)));
|
||||||
if (resolvedWillAccept) {
|
if (resolvedWillAccept) {
|
||||||
setState(() {
|
setState(() {
|
||||||
@ -741,12 +745,14 @@ class _DragTargetState<T extends Object> extends State<DragTarget<T>> {
|
|||||||
setState(() {
|
setState(() {
|
||||||
_candidateAvatars.remove(avatar);
|
_candidateAvatars.remove(avatar);
|
||||||
});
|
});
|
||||||
|
if (avatar.data != null) {
|
||||||
widget.onAccept?.call(avatar.data! as T);
|
widget.onAccept?.call(avatar.data! as T);
|
||||||
widget.onAcceptWithDetails?.call(DragTargetDetails<T>(data: avatar.data! as T, offset: avatar._lastOffset!));
|
widget.onAcceptWithDetails?.call(DragTargetDetails<T>(data: avatar.data! as T, offset: avatar._lastOffset!));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void didMove(_DragAvatar<Object> avatar) {
|
void didMove(_DragAvatar<Object> avatar) {
|
||||||
if (!mounted) {
|
if (!mounted || avatar.data == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
widget.onMove?.call(DragTargetDetails<T>(data: avatar.data! as T, offset: avatar._lastOffset!));
|
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));
|
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 {
|
testWidgetsWithLeakTracking('Drag and drop - dragging over button', (WidgetTester tester) async {
|
||||||
final List<String> events = <String>[];
|
final List<String> events = <String>[];
|
||||||
Offset firstLocation, secondLocation;
|
Offset firstLocation, secondLocation;
|
||||||
@ -2392,6 +2433,68 @@ void main() {
|
|||||||
expect(find.text('Target'), findsOneWidget);
|
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 {
|
testWidgetsWithLeakTracking('Draggable disposes recognizer', (WidgetTester tester) async {
|
||||||
late final OverlayEntry entry;
|
late final OverlayEntry entry;
|
||||||
addTearDown(() => entry..remove()..dispose());
|
addTearDown(() => entry..remove()..dispose());
|
||||||
|
Loading…
x
Reference in New Issue
Block a user