Flutter Draggable - How to Reset Draggable to Starting Position?

1,920

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 Keys so the framework knows when to and when to not update the widgets of the same type,

more about using keys here

Share:
1,920
Tommie C.
Author by

Tommie C.

One of my favorite sequences in Apollo 13 is when they were trying to resolve the problem of how to load a number of circuits in the correct sequence without the system overloading. That's coding. My questions and answers serve as my resume and skills. Please review thoroughly before contact. Traditional resume available if required. via: three4seven-6nine2-eight8eight9 (leave a message) What I do? My focus has always been a straightforward proposition. How to build something useful to others that would stand the test of time? How to build the app simply (using the laws of simplicity ~ by John Maeda)? How to build a self sustaining solution? My solutions can be found at (https://fnd.io/#/us/software-developer/691659816-tommie-carter). I am a traditionally trained software engineer with experience building solutions for international law firms and other organizations. I care about my products because they bear my name. "I have gone through the fire and I am amazing." For more, please visit www.tommiecarter.com What am I doing on SO? I attempt to leave the site and our world just a little better than I found it, I try to give complete solutions in each question or answer that I put forward. Why I contribute to SO? I believe that each of us has a tiny spark of the divine. It is up to each to make a choice to be better than what others say we must be. We can change our world when we do so. Nota Bene In each of my answers and questions I try to give a complete (non-trivial) solution. Please review these for a comprehensive breakdown of the issue/resolution. Extracting useful information from everyday problems. Current coding flavors include flutter, hivedb, firebase, iOS, android, html, angular and nodejs. But I can code into any language (I dream in code). Quick Start Developer Training A typical student is a beginning programmer with some knowledge in at least one language. Visit Site for Info. Helpful Mac Tools To setup your Mac OSX system to speak Mandarin, Cantonese, and English or write traditional or simplified characters and pinyin, download the updated toolset from Github or get the original zip file (see demo,tldr;) Language Stuff Want to become a mobile developer? I teach online courses using the laws of simplicity and traditional software engineering concepts. For more info; contact [email protected]

Updated on December 01, 2022

Comments

  • Tommie C.
    Tommie C. over 1 year

    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.

    Observations

    • Draggables remain in dropped position even though I am wrapping the Tiles in the parent widget with a Consumer.
    • The Consumer is passing the changed data values.
    • The DragTargets are visible in my widget tree using Flutter
      Inspector.
    • When I get to an event where I have more than three Draggables, the
      dropped draggables are misplaced while the Tiles and DropTargets for the other widgets appear as expected.

    Expectations:

    When I reset my consumer data I want to have both Tiles and Targets redrawn in their original positions with the new data.

    draggable_behavior

    The DragTarget

    @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');
          },
        );
      }
    }
    

    Draggable

    @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;
            });
          },
        );
      }
    }
    

    Parent Widget

     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,
                      );
                    })
                ],
              ),
    

    Updated Behavior

    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:

    • Have the Tiles shown in their dropped position on the Target
    • After a different click event have the Tiles and Targets redrawn in their original positions.

    Note - Applied the following code to both the Tile and Target widget.

    key: (gc.isPuzzleComplete == true) ? UniqueKey() : null,
    

    uniquekey-update

    The Solution

    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,
    

    References

    Flutter Team Video on use of Keys (link)

    • Matthew Trent
      Matthew Trent almost 4 years
      How did you make it so that you can only drag 1 draggable at a time in your app?
    • Tommie C.
      Tommie C. almost 4 years
      @Sniperduel17 - I did nothing except embed the tiles and targets into two different wrap widgets. These act like row widgets except they handle overflow. I got the various animated behaviors w/o any effort at that point. FYI - I can also drag multiple tiles to multiple targets. I did not set any options to allow single drag, everything sort of worked out of the box.