Nesting reorderable lists?

Viewed 513

I have a ReorderableList consisting of ListTiles and ExpansionTiles, and would like the children of the ExpansionTiles to be reorderable. The children are recursive in that they are also a mix of ListTiles and ExpansionTiles. This is my current code:

Widget _buildList() {
  return Scrollbar(
    child: ReorderableListView(
      padding: const EdgeInsets.all(25),
      children: topLevelOrder.map<Widget>(_buildItem).toList(),
      onReorder: (oldIndex, newIndex) {
      // ...
      },
    ),
  );
}

Widget _buildItem(String id) {
  Map item = data[id];
  List children = item["children"];
  if (children.length > 0) {
    List<Widget> childWidgets = [];
    for (var child in children)
      childWidgets.add(_buildItem(child));
    return ExpansionTile(
      key: Key(id),
      initiallyExpanded: true,
      title: Text(id),
      children: [
        ReorderableListView(
          children: childWidgets,
          onReorder: (oldIndex, newIndex) {
            // ...
          },
        )
      ],
    );
  }
  return ListTile(
    key: Key(id),
    title: Text(id)
  );
}

where data is a hardcoded dictionary of test data (only two levels deep). However, this throws a BoxConstraints forces an infinite height error. How do I fix this or otherwise achieve a nested reorderable list?

2 Answers

i am working on such implementation. The trick is to add

shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),

for nested reorderable lists. Try to avoid Expanded() outside the list, and check for widgets inside the lists, first of all the third part libraries, some of them can break the reorderable list onReorder behavior.

i have tried multiple nesting too, which works fine. The only limitation is that reordering works on same level only.

Ive tried similar thing, but I just don't see it working this way, (maybe it works, I'm not sure), instead I've come with solution where multiple ReorderableListView (can also ReorderableListView.builder) widgets are nested into SingleChildScrollView widget, its also possible to nest inside other widgets as well, as you can see from example. And it seems to me that this would also work perfect for op question as well

have fun Fluttering around

import 'package:flutter/material.dart';

/// This is the main application widget.

/// This is the stateful widget that the main application instantiates.
class MyStatefulWidget extends StatefulWidget {
  const MyStatefulWidget({Key? key}) : super(key: key);

  @override
  State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}

/// This is the private State class that goes with MyStatefulWidget.
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
  final List<int> _items = List<int>.generate(10, (int index) => index);
  final List<int> _items2 = List<int>.generate(15, (int index2) => index2);

  @override
  Widget build(BuildContext context) {
    final ColorScheme colorScheme = Theme.of(context).colorScheme;
    final Color oddItemColor = colorScheme.primary.withOpacity(0.05);
    final Color evenItemColor = colorScheme.primary.withOpacity(0.15);
    int index = 0;
    int index2 = 0;

    return Scaffold(
      appBar: AppBar(
        title: Text('Example'),
      ),
      body: SingleChildScrollView(
        child: Column(
          children: <Widget>[
            ListTile(title: Text('test 1')),
            ListTile(title: Text('test 2')),
            ReorderableListView(
              physics: ClampingScrollPhysics(),
              shrinkWrap: true,
              padding: const EdgeInsets.symmetric(horizontal: 40),
              children: <Widget>[
                for (index = 0; index < _items.length; index++)
                  ListTile(
                    key: Key('$index'),
                    tileColor:
                        _items[index].isOdd ? oddItemColor : evenItemColor,
                    title: Text('Item ${_items[index]}'),
                  ),
              ],
              onReorder: (int oldIndex, int newIndex) {
                setState(() {
                  if (oldIndex < newIndex) {
                    newIndex -= 1;
                  }
                  final int item = _items.removeAt(oldIndex);
                  _items.insert(newIndex, item);
                });
              },
            ),
            ListTile(title: Text('test 3')),
            ListTile(title: Text('test 4')),
            ReorderableListView(
              physics: ClampingScrollPhysics(),
              shrinkWrap: true,
              padding: const EdgeInsets.symmetric(horizontal: 40),
              children: <Widget>[
                for (index2 = 0; index2 < _items2.length; index2++)
                  ListTile(
                    key: Key('$index2'),
                    tileColor:
                        _items2[index2].isOdd ? oddItemColor : evenItemColor,
                    title: Text('Item ${_items2[index2]}'),
                  ),
              ],
              onReorder: (int oldIndex2, int newIndex2) {
                setState(() {
                  if (oldIndex2 < newIndex2) {
                    newIndex2 -= 1;
                  }
                  final int item = _items2.removeAt(oldIndex2);
                  _items2.insert(newIndex2, item);
                });
              },
            ),
            ListTile(title: Text('test 5')),
            ListTile(title: Text('test 6')),
          ],
        ),
      ),
    );
  }
}
Related