3

What I need is a reorderable list in iOS styled cupertino Flutter app, very much like the list one gets in iPhone's Reminders app.

To be more specific, say we have the following flutter app:

import 'package:flutter/cupertino.dart';

void main() {
  runApp(
    CupertinoApp(
      title: 'Cupertino App',
      theme: const CupertinoThemeData(brightness: Brightness.light),
      home: CupertinoPageScaffold(
        navigationBar: const CupertinoNavigationBar(
          middle: Text('Cupertino List'),
        ),
        child: SafeArea(
          child: CupertinoListSection(
            header: const Text('Cupertino Items'),
            children: [
              for (var i = 1; i < 6; i++)
                CupertinoListTile(
                  key: Key('$i'),
                  title: Text('Item $i'),
                ),
            ],
          ),
        ),
      ),
    ),
  );
}

This will create a static list of 5 items named Item 1 through Item 6. I want to be able to long tap on an item and drag it to its new position - much like what material's ReorderableListView accomplishes.

bagratte
  • 157
  • 2
  • 5

1 Answers1

1

Thanks for making me work on a Cupertino app after a very long time :).

This is the solution I worked on:

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

  @override
  State<ReorderableListIos> createState() => _ReorderableListIosState();
}

class _ReorderableListIosState extends State<ReorderableListIos> {
  final List<int> generatedIntList =
      List<int>.generate(10, (int index) => index);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: ReorderableListView(
        children: [
          for (int currentIndex = 0;
              currentIndex < generatedIntList.length;
              currentIndex += 1)
            ListTile(
                key: Key('$currentIndex'),
                title: Text(' ${generatedIntList[currentIndex]}')),
        ],
        onReorder: (int oldIndex, int newIndex) {
          setState(() {
            if (oldIndex < newIndex) {
              newIndex -= 1;
            }
            final int itemToRemove = generatedIntList.removeAt(oldIndex);
            generatedIntList.insert(newIndex, itemToRemove);
          });
        },
      ),
    );
  }
}

In case you get an error telling you:

No MaterialLocalizations found.

ReorderableListView widgets require MaterialLocalizations to be provided by a Localizations widget ancestor.
The material library uses Localizations to generate messages, labels, and abbreviations.

To introduce a MaterialLocalizations, either use a MaterialApp at the root of your application to include them automatically, or add a Localization widget with a MaterialLocalizations delegate.

Try adding localizations to your CupertinoApp like so:

localizationsDelegates: [
      DefaultMaterialLocalizations.delegate,
      DefaultCupertinoLocalizations.delegate,
      DefaultWidgetsLocalizations.delegate,
 ],

Which turns your main function into this:

void main() {
  runApp(const CupertinoApp(
    home: ReorderableListIos(),
    localizationsDelegates: [
      DefaultMaterialLocalizations.delegate,
      DefaultCupertinoLocalizations.delegate,
      DefaultWidgetsLocalizations.delegate,
    ],
  ));
}

Hope this helps!

Edit: For some creativity let's change the icon of the drag handle: First change the ReorderableListView:

ReorderableListView(
  buildDefaultDragHandles: false,
  ...

Then add a trailing ReorderableDragStartListener in order to change the icon:

ListTile(
  key: Key('$currentIndex'),
  title: Text(' ${generatedIntList[currentIndex]}'),
  trailing: ReorderableDragStartListener(
  key: ValueKey<int>(generatedIntList[currentIndex]),
    index: currentIndex,
    child: const Icon(CupertinoIcons.bars),
  ),
)

Final version: Final looks of the ReorderableList

bqubique
  • 678
  • 6
  • 17