Skip to content

ReorderableListView in a Drawer: ListTile stuck during dragging #105010

@epentesi

Description

@epentesi

Steps to Reproduce

  1. In a Drawer create a ReorderableListView with 10 ListTiles
  2. Open the Drawer
  3. Drag any ListTile («Item 5» in my demo video) with one finger (DON’T RELEASE IT)
  4. With another finger tap outside the Drawer

Expected results:
The dragged ListTile should disappear together with the closing Drawer.

Actual results:
The ListTile doesn’t disappear, remains stuck on the screen and can’t be removed. See the attached demo video ⬇️

Code sample
import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'ReorderableListView in a Drawer'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key, required this.title}) : super(key: key);
  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  List itemList = [
    {'name': 'Item 1'},
    {'name': 'Item 2'},
    {'name': 'Item 3'},
    {'name': 'Item 4'},
    {'name': 'Item 5'},
    {'name': 'Item 6'},
    {'name': 'Item 7'},
    {'name': 'Item 8'},
    {'name': 'Item 9'},
    {'name': 'Item 10'},
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      backgroundColor: Colors.grey.shade700,
      body: SafeArea(
        child: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Text(
                'Open the drawer on the left',
                style: Theme.of(context)
                    .textTheme
                    .bodyLarge
                    ?.copyWith(color: Colors.white),
              ),
            ],
          ),
        ),
      ),
      drawer: Drawer(
        child: SafeArea(
          child: Column(
            children: [
              DrawerHeader(
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.stretch,
                  children: [
                    Text(
                      'ReorderableListView',
                      style: Theme.of(context).textTheme.headlineSmall,
                    ),
                    const SizedBox(
                      height: 16,
                    ),
                    Text(
                      'With one finger drag an item\nand at the same time, with another finger,\ntap outside the drawer',
                      style: Theme.of(context).textTheme.bodySmall,
                    ),
                  ],
                ),
              ),
              Expanded(
                child: ReorderableListView(
                  padding: EdgeInsets.zero,
                  scrollDirection: Axis.vertical,
                  shrinkWrap: false,
                  buildDefaultDragHandles: true,
                  onReorder: (int oldIndex, int newIndex) {
                    setState(
                      () {
                        if (oldIndex < newIndex) {
                          newIndex -= 1;
                        }
                        final item = itemList.removeAt(oldIndex);
                        itemList.insert(newIndex, item);
                      },
                    );
                  },
                  children: [
                    for (var i in itemList)
                      ListTile(
                        tileColor: Colors.transparent,
                        key: ValueKey(i['name']),
                        leading: const Icon(Icons.dashboard_rounded),
                        title: Text(
                          i['name'],
                        ),
                        trailing: const Icon(Icons.drag_handle),
                        dense: false,
                      ),
                  ],
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}
Error logs
======== Exception caught by gesture library =======================================================
The following assertion was thrown while routing a pointer event:
setState() called after dispose(): SliverReorderableListState#4da7d(lifecycle state: defunct, not mounted, tickers: tracking 0 tickers)

This error happens if you call setState() on a State object for a widget that no longer appears in the widget tree (e.g., whose parent widget no longer includes the widget in its build). This error can occur when code calls setState() from a timer or an animation callback.

The preferred solution is to cancel the timer or stop listening to the animation in the dispose() callback. Another solution is to check the "mounted" property of this object before calling setState() to ensure the object is still in the tree.
This error might indicate a memory leak if setState() is being called because another object is retaining a reference to this State object after it has been removed from the tree. To avoid memory leaks, consider breaking the reference to this object during dispose().

When the exception was thrown, this was the stack: 
#0      State.setState.<anonymous closure> (package:flutter/src/widgets/framework.dart:1073:9)
#1      State.setState (package:flutter/src/widgets/framework.dart:1108:6)
#2      SliverReorderableListState._dragUpdate (package:flutter/src/widgets/reorderable_list.dart:722:5)
#3      _DragInfo.update (package:flutter/src/widgets/reorderable_list.dart:1384:15)
#4      MultiDragPointerState._move (package:flutter/src/gestures/multidrag.dart:86:16)
#5      MultiDragGestureRecognizer._handleEvent (package:flutter/src/gestures/multidrag.dart:259:13)
#6      PointerRouter._dispatch (package:flutter/src/gestures/pointer_router.dart:94:12)
#7      PointerRouter._dispatchEventToRoutes.<anonymous closure> (package:flutter/src/gestures/pointer_router.dart:139:9)
#8      _LinkedHashMapMixin.forEach (dart:collection-patch/compact_hash.dart:614:13)
#9      PointerRouter._dispatchEventToRoutes (package:flutter/src/gestures/pointer_router.dart:137:18)
#10     PointerRouter.route (package:flutter/src/gestures/pointer_router.dart:123:7)
#11     GestureBinding.handleEvent (package:flutter/src/gestures/binding.dart:445:19)
#12     GestureBinding.dispatchEvent (package:flutter/src/gestures/binding.dart:425:22)
#13     RendererBinding.dispatchEvent (package:flutter/src/rendering/binding.dart:329:11)
#14     GestureBinding._handlePointerEventImmediately (package:flutter/src/gestures/binding.dart:380:7)
#15     GestureBinding.handlePointerEvent (package:flutter/src/gestures/binding.dart:344:5)
#16     GestureBinding._flushPointerEventQueue (package:flutter/src/gestures/binding.dart:302:7)
#17     GestureBinding._handlePointerDataPacket (package:flutter/src/gestures/binding.dart:285:7)
#21     _invoke1 (dart:ui/hooks.dart:170:10)
#22     PlatformDispatcher._dispatchPointerDataPacket (dart:ui/platform_dispatcher.dart:331:7)
#23     _dispatchPointerDataPacket (dart:ui/hooks.dart:94:31)
(elided 3 frames from dart:async)
====================================================================================================
Demo Video
ReorderableListView-in-a-Drawer.MP4

Metadata

Metadata

Assignees

Labels

c: crashStack traces logged to the consolef: material designflutter/packages/flutter/material repository.f: scrollingViewports, list views, slivers, etc.found in release: 3.0Found to occur in 3.0found in release: 3.1Found to occur in 3.1frameworkflutter/packages/flutter repository. See also f: labels.has reproducible stepsThe issue has been confirmed reproducible and is ready to work onr: fixedIssue is closed as already fixed in a newer version

Type

No type

Projects

Status

Done (PR merged)

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions