Norbert515 / flutter_list_drag_and_drop

An implementation of drag and drop for lists

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Swiping to dismiss?

michelleran opened this issue · comments

I'm trying Flutter's sample swipe-to-dismiss code with DragAndDropList, and though I am able to swipe items away, the program promptly complains about a Dismissible not being removed from the widget tree.

This is my build function (inside a State):

  List<String> _items = ['Item 1', 'Item 2', 'Item 3'];

  @override
  Widget build(BuildContext context) {
    print('Hey! $_items');
    return new Scaffold(
      appBar: new AppBar(
        title: new Text(widget.title),
      ),
      body: new DragAndDropList<String>(
        _items,
        itemBuilder: (BuildContext context, item) {
          print('Trying to build a widget for item $item');
          return new Dismissible(
            // Each Dismissible must contain a unique Key.
            key: new Key(item),
            onDismissed: (direction) {
              setState(() {
                _items.remove(item);
                print('Hmmm... $_items');
              });
            },
            // Show a red background as the item is swiped away
            background: new Container(color: Colors.red),
            child: new ListTile(
              leading: new Text((_items.indexOf(item) + 1).toString()),
              title: new Text(item),
            ),
          );
        },
        onDragFinish: (before, after) {
          String data = _items[before];
          _items.removeAt(before);
          _items.insert(after, data);
        },
        canBeDraggedTo: (one, two) => true,
        dragElevation: 8.0,
      ),
    );
  }

which, after swiping Item 2 away, gives the output

Hey! [Item 1, Item 2, Item 3]
Trying to build a widget for item Item 1
Trying to build a widget for item Item 2
Trying to build a widget for item Item 3
Hmmm... [Item 1, Item 3]
Hey! [Item 1, Item 3]
Trying to build a widget for item Item 1
Trying to build a widget for item Item 2
══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════
The following assertion was thrown building Dismissible-[<'Item 2'>](dirty, state:
_DismissibleState#07bef(tickers: tracking 2 tickers)):
A dismissed Dismissible widget is still part of the tree.
Make sure to implement the onDismissed handler and to immediately remove the Dismissible
widget from the application once that handler has fired.

When the exception was thrown, this was the stack:
#0      _DismissibleState.build.<anonymous closure> (package:flutter/src/widgets/dismissible.dart:471:11)
#1      _DismissibleState.build (package:flutter/src/widgets/dismissible.dart:478:8)
#2      StatefulElement.build (package:flutter/src/widgets/framework.dart:3713:27)
#3      ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:3625:15)
#4      Element.rebuild (package:flutter/src/widgets/framework.dart:3478:5)
#5      StatefulElement.update (package:flutter/src/widgets/framework.dart:3760:5)
#6      Element.updateChild (package:flutter/src/widgets/framework.dart:2682:15)
#7      RenderObjectElement.updateChildren (package:flutter/src/widgets/framework.dart:4340:32)
#8      MultiChildRenderObjectElement.update (package:flutter/src/widgets/framework.dart:4726:17)
#9      Element.updateChild (package:flutter/src/widgets/framework.dart:2682:15)
#10     SingleChildRenderObjectElement.update (package:flutter/src/widgets/framework.dart:4618:14)
#11     Element.updateChild (package:flutter/src/widgets/framework.dart:2682:15)
#12     ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:3636:16)
#13     Element.rebuild (package:flutter/src/widgets/framework.dart:3478:5)
#14     StatefulElement.update (package:flutter/src/widgets/framework.dart:3760:5)
#15     Element.updateChild (package:flutter/src/widgets/framework.dart:2682:15)
#16     SingleChildRenderObjectElement.update (package:flutter/src/widgets/framework.dart:4618:14)
#17     Element.updateChild (package:flutter/src/widgets/framework.dart:2682:15)
#18     ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:3636:16)
#19     Element.rebuild (package:flutter/src/widgets/framework.dart:3478:5)
#20     StatefulElement.update (package:flutter/src/widgets/framework.dart:3760:5)
#21     Element.updateChild (package:flutter/src/widgets/framework.dart:2682:15)
#22     ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:3636:16)
#23     Element.rebuild (package:flutter/src/widgets/framework.dart:3478:5)
#24     StatelessElement.update (package:flutter/src/widgets/framework.dart:3685:5)
#25     Element.updateChild (package:flutter/src/widgets/framework.dart:2682:15)
#26     SingleChildRenderObjectElement.update (package:flutter/src/widgets/framework.dart:4618:14)
#27     Element.updateChild (package:flutter/src/widgets/framework.dart:2682:15)
#28     ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:3636:16)
#29     Element.rebuild (package:flutter/src/widgets/framework.dart:3478:5)
#30     StatelessElement.update (package:flutter/src/widgets/framework.dart:3685:5)
#31     Element.updateChild (package:flutter/src/widgets/framework.dart:2682:15)
#32     ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:3636:16)
#33     Element.rebuild (package:flutter/src/widgets/framework.dart:3478:5)
#34     ProxyElement.update (package:flutter/src/widgets/framework.dart:3870:5)
#35     Element.updateChild (package:flutter/src/widgets/framework.dart:2682:15)
#36     ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:3636:16)
#37     Element.rebuild (package:flutter/src/widgets/framework.dart:3478:5)
#38     StatefulElement.update (package:flutter/src/widgets/framework.dart:3760:5)
#39     Element.updateChild (package:flutter/src/widgets/framework.dart:2682:15)
#40     SliverMultiBoxAdaptorElement.performRebuild (package:flutter/src/widgets/sliver.dart:702:34)
#41     SliverMultiBoxAdaptorElement.update (package:flutter/src/widgets/sliver.dart:671:7)
#42     Element.updateChild (package:flutter/src/widgets/framework.dart:2682:15)
#43     SingleChildRenderObjectElement.update (package:flutter/src/widgets/framework.dart:4618:14)
#44     Element.updateChild (package:flutter/src/widgets/framework.dart:2682:15)
#45     RenderObjectElement.updateChildren (package:flutter/src/widgets/framework.dart:4340:32)
#46     MultiChildRenderObjectElement.update (package:flutter/src/widgets/framework.dart:4726:17)
#47     _ViewportElement.update (package:flutter/src/widgets/viewport.dart:187:17)
#48     Element.updateChild (package:flutter/src/widgets/framework.dart:2682:15)
#49     ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:3636:16)
#50     Element.rebuild (package:flutter/src/widgets/framework.dart:3478:5)
#51     ProxyElement.update (package:flutter/src/widgets/framework.dart:3870:5)
#52     Element.updateChild (package:flutter/src/widgets/framework.dart:2682:15)
#53     SingleChildRenderObjectElement.update (package:flutter/src/widgets/framework.dart:4618:14)
#54     Element.updateChild (package:flutter/src/widgets/framework.dart:2682:15)
#55     SingleChildRenderObjectElement.update (package:flutter/src/widgets/framework.dart:4618:14)
#56     Element.updateChild (package:flutter/src/widgets/framework.dart:2682:15)
#57     SingleChildRenderObjectElement.update (package:flutter/src/widgets/framework.dart:4618:14)
#58     Element.updateChild (package:flutter/src/widgets/framework.dart:2682:15)
#59     SingleChildRenderObjectElement.update (package:flutter/src/widgets/framework.dart:4618:14)
#60     Element.updateChild (package:flutter/src/widgets/framework.dart:2682:15)
#61     ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:3636:16)
#62     Element.rebuild (package:flutter/src/widgets/framework.dart:3478:5)
#63     StatefulElement.update (package:flutter/src/widgets/framework.dart:3760:5)
#64     Element.updateChild (package:flutter/src/widgets/framework.dart:2682:15)
#65     SingleChildRenderObjectElement.update (package:flutter/src/widgets/framework.dart:4618:14)
#66     Element.updateChild (package:flutter/src/widgets/framework.dart:2682:15)
#67     ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:3636:16)
#68     Element.rebuild (package:flutter/src/widgets/framework.dart:3478:5)
#69     StatefulElement.update (package:flutter/src/widgets/framework.dart:3760:5)
#70     Element.updateChild (package:flutter/src/widgets/framework.dart:2682:15)
#71     ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:3636:16)
#72     Element.rebuild (package:flutter/src/widgets/framework.dart:3478:5)
#73     StatelessElement.update (package:flutter/src/widgets/framework.dart:3685:5)
#74     Element.updateChild (package:flutter/src/widgets/framework.dart:2682:15)
#75     _LayoutBuilderElement._layout.<anonymous closure> (package:flutter/src/widgets/layout_builder.dart:118:18)
#76     BuildOwner.buildScope (package:flutter/src/widgets/framework.dart:2205:19)
#77     _LayoutBuilderElement._layout (package:flutter/src/widgets/layout_builder.dart:107:11)
#78     RenderObject.invokeLayoutCallback.<anonymous closure> (package:flutter/src/rendering/object.dart:1666:58)
#79     PipelineOwner._enableMutationsToDirtySubtrees (package:flutter/src/rendering/object.dart:740:15)
#80     RenderObject.invokeLayoutCallback (package:flutter/src/rendering/object.dart:1666:13)
#81     _RenderLayoutBuilder.performLayout (package:flutter/src/widgets/layout_builder.dart:205:5)
#82     RenderObject.layout (package:flutter/src/rendering/object.dart:1570:7)
#83     MultiChildLayoutDelegate.layoutChild (package:flutter/src/rendering/custom_layout.dart:124:11)
#84     _ScaffoldLayout.performLayout (package:flutter/src/material/scaffold.dart:256:7)
#85     MultiChildLayoutDelegate._callPerformLayout (package:flutter/src/rendering/custom_layout.dart:194:7)
#86     RenderCustomMultiChildLayoutBox.performLayout (package:flutter/src/rendering/custom_layout.dart:338:14)
#87     RenderObject._layoutWithoutResize (package:flutter/src/rendering/object.dart:1445:7)
#88     PipelineOwner.flushLayout (package:flutter/src/rendering/object.dart:709:18)
#89     BindingBase&GestureBinding&ServicesBinding&SchedulerBinding&PaintingBinding&RendererBinding.drawFrame (package:flutter/src/rendering/binding.dart:270:19)
#90     BindingBase&GestureBinding&ServicesBinding&SchedulerBinding&PaintingBinding&RendererBinding&WidgetsBinding.drawFrame (package:flutter/src/widgets/binding.dart:622:22)
#91     BindingBase&GestureBinding&ServicesBinding&SchedulerBinding&PaintingBinding&RendererBinding._handlePersistentFrameCallback (package:flutter/src/rendering/binding.dart:208:5)
#92     BindingBase&GestureBinding&ServicesBinding&SchedulerBinding._invokeFrameCallback (package:flutter/src/scheduler/binding.dart:990:15)
#93     BindingBase&GestureBinding&ServicesBinding&SchedulerBinding.handleDrawFrame (package:flutter/src/scheduler/binding.dart:930:9)
#94     BindingBase&GestureBinding&ServicesBinding&SchedulerBinding._handleDrawFrame (package:flutter/src/scheduler/binding.dart:842:5)
#95     _invoke (file:///b/build/slave/Mac_Engine/build/src/flutter/lib/ui/hooks.dart:120)
#96     _drawFrame (file:///b/build/slave/Mac_Engine/build/src/flutter/lib/ui/hooks.dart:109)
════════════════════════════════════════════════════════════════════════════════════════════════════
Trying to build a widget for item Item 3

Seems like itemBuilder is ignoring changes to _items. Any thoughts on how to fix this?

Fixed by modifying the build function in DragAndDropListState like so:

  @override
  Widget build(BuildContext context) {
    return new LayoutBuilder(
      builder: (BuildContext context3, constr) {
        rows = (widget.rowsData).map((it) => new Data<T>(it)).toList(); // added this line
        return new ListView.builder(
          itemBuilder: (BuildContext context2, int index) {
            return _getDraggableListItem(context2, index, context3);
          },
          controller: scrollController,
          itemCount: rows.length,
          padding: const EdgeInsets.all(0.0),
        );
      },
    );
  }

I'm guessing widget.rowsData was updated but not rows, since that was only set when the state is initialized.

Actually, on further investigation, this seems to break dragging to reorder...

Fixed by adding in a boolean flag itemWasDismissed and checking for it in build; if true, then reset the value of rows.

On even further investigation, this seems to have slightly broken the animation. It works just fine up until after you dismiss an item; then it no longer shows the original list moving/reordering alongside the item you're dragging until you drop it. The animation also breaks after showing and dismissing a dialog.

Here are my modifications to drag_and_drop_list.dart (actually not a lot):

// same; omitted

class DragAndDropList<T> extends StatefulWidget {
  
  // same; omitted

  bool listWasChanged; // added

  DragAndDropList(this.rowsData,
      {Key key, @required this.itemBuilder,
        this.onDragFinish,
        @required this.canBeDraggedTo,
        this.dragElevation = 0.0,
        this.tilt = 0.0,
        this.listWasChanged = false}) // added
      : providesOwnDraggable = false,
        itemBuilderCustom = null, super(key: key);

  // same; omitted

}

class _DragAndDropListState<T> extends State<DragAndDropList> {

  // same; omitted

  @override
  Widget build(BuildContext context) {
    return new LayoutBuilder(
      builder: (BuildContext context3, constr) {

        if (widget.listWasChanged) // added
          rows = (widget.rowsData).map((it) => new Data<T>(it)).toList(); // added

        return new ListView.builder(
          itemBuilder: (BuildContext context2, int index) {
            return _getDraggableListItem(context2, index, context3);
          },
          controller: scrollController,
          itemCount: rows.length,
          padding: const EdgeInsets.all(0.0),
        );
      },
    );
  }

  // same; omitted

}

Any thoughts?

This should be related to this PR.
The _DragAndDropListState caches the widgets. There is a method called didUpdateWidget which is called when the widget of the state is rebuilt. For some reason I removed refreshing the data there, in an earlier commit.
Adding rows = (widget.rowsData).map((it) => new Data<T>(it)).toList(); there should work. Could you try that out?
If that works, I'll do some additional testing and update the code.

Yes, adding that (and removing my changes) appears to fix it - thanks!