Updates:
See latest updates at the bottom.
Original
I have flutter Draggable and DragTarget widgets that are working except that after I place the DragTarget I get to a particular situation where I want to rebuild the DragTarget and move the Draggable back to its original position.
When I check the Flutter inspector it shows my widgets where I expect them to be but when I call my Provider for the next set of information, the Draggables remain in their dropped position rather than returning to their starting position.
As seen in the last frame of the image, the three target frames still contain a reference to the draggable widgets but I am rebuilding both the Draggable and DragTarget widgets on each click of the red IconButton.
Expectations:
When I reset my consumer data I want to have both Tiles and Targets redrawn in their original positions with the new data.
@override
Widget build(BuildContext context) {
return DragTarget(
builder:
(context, List<String> candidateData, List<dynamic> rejectedData) {
return (isSuccessful) //&& !widget.isPuzzleComplete
? Tile(
title: widget.data,
)
: TargetPlaceholder(
widget: widget,
);
},
onWillAccept: (data) {
return widget.data == data;
},
onAccept: (data) {
setState(() {
Provider.of<GameController>(
context,
listen: false,
).checkPuzzleSolved();
//activate the animation, may not require setState
isSuccessful = !widget.isPuzzleComplete;
});
},
onLeave: (data) {
print('Draggable object left the target area containing data of $data');
},
);
}
}
@override
Widget build(BuildContext context) {
return Draggable(
data: widget.title,
// dragAnchor: DragAnchor.pointer,
child: isSuccessful ? Container() : TileContent(widget: widget),
childWhenDragging: Container(),
feedback: Opacity(
opacity: 0.7,
child: TileContent(
widget: widget,
),
),
onDragCompleted: () {
setState(() {
Transform(
transform: Matrix4.translation(_shake()),
child: TileContent(widget: widget));
});
},
onDragEnd: (details) {
setState(() {
isSuccessful = details.wasAccepted;
});
},
);
}
}
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
for (var item in targetPattern)
Consumer<GameController>(builder: (context, gc, _) {
return Target(
data: item,
isPuzzleComplete: gc.isPuzzleComplete,
);
})
],
),
SizedBox(
height: 36,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
for (var data in scrambledPattern)
Consumer<GameController>(builder: (context, gc, _) {
return Tile(
title: data,
isPuzzleComplete: gc.isPuzzleComplete,
);
})
],
),
See the note below and the second animated gif to compare and contrast the code changes and new behavior with the original behavior above.
After adding a UniqueKey to both the Targets and Tiles - The Tiles are incorrectly shown in their original location, The Targets are correctly shown.
What I am seeking to do is:
Note - Applied the following code to both the Tile and Target widget.
key: (gc.isPuzzleComplete == true) ? UniqueKey() : null,
Thanks to @LoVe's reminder on using UniqueKey and a review of the Flutter Team's video on Keys; I was able to resolve the issue by conditionally adding the UniqueKey to the enclosing Row containing both the Tiles and the Target.
From within each of these widgets I was able to update the appearance of the widgets using (isSuccessful || widget.isPuzzleComplete)
. I was also able to remove the Consumer logic related to grabbing the isPuzzleComplete logic from the Tile/Target widgets and from the setState call.
Currently, the behavior is 100% correct.
//mod to parent row of Tiles
Row(
key: widget.isPuzzleComplete ? UniqueKey() : null,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
for (var data in scrambledPattern)
Tile( title: data,
isPuzzleComplete: widget.isPuzzleComplete,
),
],
),
//mod to draggable Tile
Draggable(
key: (widget.isPuzzleComplete == true) ? UniqueKey() : null,
data: widget.title,
child: (isSuccessful || widget.isPuzzleComplete)
? Container()
: TileContent(widget: widget),
//mod to drag target
return DragTarget(
key: UniqueKey(),
builder:
(context, List<String> candidateData, List<dynamic> rejectedData) {
return (widget.isPuzzleComplete || isSuccessful)
? Tile(
title: widget.data,
Flutter Team Video on use of Keys (link)
You will have to use keys, something like this:
Tile( key:UniqueKey(), title: data, isPuzzleComplete: gc.isPuzzleComplete, ); }
Whenever you are using a Row
or Column
or List
of widgets of the same type and you are updating them frequently, then you must use Key
s so the framework knows when to and when to not update the widgets of the same type,
more about using keys here