Save the order of the ReorderableListView in Flutter?

553 views Asked by At

I am using the ReorderableListView widget to display a list in my Flutter application..

Everything works fine and the ListTiles easily get dragged and dropped to new positions but the only thing is that they don't stay in the new position after restarting the application.

How do I maintain their positions? I tried using PageStorage but it doesn't maintain the new positions

Any ideas?

Thanks in advance

ReorderableListView(
physics: const ClampingScrollPhysics(),
shrinkWrap: true,
onReorder: (int oldIndex, int newIndex) {
  setState(() {
    if (newIndex > oldIndex) newIndex--;
       final item = tasks.removeAt(oldIndex);
       tasks.insert(newIndex, item);
     });
   },
children: [
for (final item in tasks)
Container(
key: ValueKey(item),
child: ExpansionTile(
title: Text(item['name'])
)])
1

There are 1 answers

0
Yes Dev On

I would suggest creating a Task class and adding an id field that uniquely identifies the task:

class Task{
 final String name;
 final int id;

  Task(this.name, this.id);
}

Then you'll need to use a storage package like shared_preferences to save the index of each task whenever they are reordered:

late SharedPreferences sharedPreferences;

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  sharedPreferences = await SharedPreferences.getInstance();
  runApp(const MyApp());
}

The code is a little complicated and it will only be suitable for small lists. In short, whenever a item in the list moves, you need to save its new index to shared preferences. The trick is that newIndex is always one greater than the actual new index.

You also need to handle forward and backward movement differently. On forward moves, all of the items between the old and new index need to be updated since their order value would have decreased. For backward moves, it is the opposite.

class ReorderExample extends StatefulWidget {
  const ReorderExample({super.key});

  @override
  State<ReorderExample> createState() => _ReorderExampleState();
}

List<Task> tasks = [
  Task('Task one', 4),
  Task('Task two', 5),
  Task('Task three', 6),
];

class _ReorderExampleState extends State<ReorderExample> {
  @override
  void initState() {
    tasks.sort((a, b) {

      int indexA = (sharedPreferences.getInt(a.id.toString()) ?? 0);
      int indexB = (sharedPreferences.getInt(b.id.toString()) ?? 0);
      debugPrint('${a.name}: $indexA');
      debugPrint('${b.name}: $indexB');

      return (indexA > indexB) ? 1 : 0;
    });
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        body: Center(
      child: ReorderableListView(
          physics: const ClampingScrollPhysics(),
          shrinkWrap: true,
          onReorder: (int oldIndex, int newIndex) {
            setState(() {
              debugPrint('newIndex: $newIndex');
              debugPrint('oldIndex: $oldIndex');

              if (newIndex > oldIndex) {
                Task movingTask = tasks[oldIndex];
                tasks.removeAt(oldIndex);
                tasks.insert(newIndex - 1, movingTask);
                sharedPreferences.setInt(movingTask.id.toString(), newIndex - 1);

                for (Task task in tasks.sublist(oldIndex, newIndex-1)) {
                  int index = sharedPreferences.getInt(task.id.toString()) ?? (oldIndex + 1);
                  debugPrint('${task.name}: ${index - 1}');
                  sharedPreferences.setInt(task.id.toString(), index - 1);
                }
              } else {
                Task movingTask = tasks[oldIndex];
                tasks.removeAt(oldIndex);
                tasks.insert(newIndex, movingTask);
                sharedPreferences.setInt(movingTask.id.toString(), newIndex);
                for (Task task in tasks.sublist(newIndex+1)) {
                  int index = sharedPreferences.getInt(task.id.toString()) ?? (oldIndex - 1);
                  debugPrint('${task.name}: ${index + 1}');
                  sharedPreferences.setInt(task.id.toString(), index + 1);
                }
              }
            });
          },
          children: [
            for (final task in tasks)
              Container(
                key: UniqueKey(),
                child: ExpansionTile(
                  title: Text(task.name),
                ),
              ),
          ]),
    ));
  }
}