skip to Main Content

I have a view which switches between a ListView and a ReorderableListView.

Widget _buildList(
  BuildContext context,
  List<ExerciseTemplate> exerciseTemplates, 
  EditWorkoutModel dao,
) {
  if (_isInEditingMode) {
    return ReorderableListView(
      key: ObjectKey('reordeableListView'),
      onReorder: ((oldIndex, newIndex) {
        dao.reorderIndexes(
          oldIndex,
          (oldIndex < newIndex) ? newIndex - 1 : newIndex,
        );
      }),
      padding: const EdgeInsets.only(bottom: 120),
      children: [
        for (var exerciseTemplate in exerciseTemplates)
          Provider(
            key: ObjectKey('${exerciseTemplate.id}_compactExerciseTemplateRow_provider'),
            create: (context) => EditExerciseModel(exerciseTemplate),
            child: CompactExerciseTemplateRow(
              key: ObjectKey('${exerciseTemplate.id}_compactExerciseTemplateRow'),
            ),
          ),
        ],
      );
    } else {
      return ListView.builder(
        key: ObjectKey('listView'),
        itemCount: exerciseTemplates.length,
        padding: const EdgeInsets.only(bottom: 120),
        itemBuilder: (BuildContext context, int index) {
          final exerciseTemplate = exerciseTemplates[index];
          return Provider(
            // Key is needed here to properly handle deleted rows in the ui.
            // Without the key, deleted rows are being shown.
            key: ObjectKey(
                '${exerciseTemplate.id}_exerciseTemplateRow_provider'),
            create: (context) => EditExerciseModel(exerciseTemplate),
            child: ExerciseTemplateRow(
              key: ObjectKey('${exerciseTemplate.id}_exerciseTemplateRow'),
              onDelete: () async {
                await dao.deleteExercise(exerciseTemplate);
                return true;
              },
            ),
          );
        },
      );
    }
  }

Both lists show the same data, but tapping a button, switches to a ReorderableListView which shows the data with different widgets. Tapping the button again switches back to the ListView.

However, switching forth and back results that I am not able to interact with elements within the row of the ListView. This issue appeared after I added a globalKey for each element in the ListView. I need this key, to properly handle deleting rows, so I can not just remove the key again.

How can I make it work, that I can interact with widgets within the row after I switched to the ReorderableListView and back to the ListView?

2

Answers


  1. Chosen as BEST ANSWER

    So the issue was that I was using ObjectKey instead of ValueKey.

    The difference between those two is that ObjectKey checks if the identity (the instance) is the same. ValueKey checks the underlying value with the == operator.

    My guess here is that by using ObjectKey in my case, flutter is not able to properly replace the old widget with the new one, since the new widget always have a different key. By using ValueKey flutter can distinguish between old and new widgets. Widgets will be in my case replaced after I switch between the lists, because the row widget won't be visible and therefor disposed.

    Because the widgets were not properly replaced, somehow the old widgets are still being rendered, but all gesture listeners were already disposed. Therefor no interaction was possible anymore.

    These are just my assumption, let me know if I am completely wrong here.


  2. Copied from Provider document:
    https://pub.dev/packages/provider

    DON’T create your object from variables that can change over time.

    In such a situation, your object would never update when the value changes.

    int count;
    
    Provider(
      create: (_) => MyModel(count),
      child: ...
    )
    

    If you want to pass variables that can change over time to your object, consider using ProxyProvider:

    int count;
    
    ProxyProvider0(
      update: (_, __) => MyModel(count),
      child: ...
    )
    

    It’s ok to use Global key and switch between ListView and ReorderableListView, see example below:
    https://dartpad.dev/?id=fd39a89b67448d86e682dd2c5ec77453

    
    void main() {
      runApp(const MyApp());
    }
    
    class MyApp extends StatelessWidget {
      const MyApp({super.key});
      @override
      Widget build(BuildContext context) {
        return const MaterialApp(
          title: 'Flutter Demo',
          home: MyHomePage(),
        );
      }
    }
    
    class MyHomePage extends StatefulWidget {
      const MyHomePage({super.key});
    
      @override
      State<MyHomePage> createState() => _MyHomePageState();
    }
    
    class _MyHomePageState extends State<MyHomePage> {
      bool reOrder = false;
      @override
      Widget build(BuildContext context) {
        return Scaffold(
            appBar: AppBar(
              title: Text(reOrder ? "ReoderableListView" : "ListView"),
            ),
            floatingActionButton: FloatingActionButton(onPressed: () {
              setState(() {
                reOrder = !reOrder;
              });
            }),
            body: MyListView(reOrder));
      }
    }
    
    final data = List.generate(10, (index) => {"title": 'item $index', "value": false});
    
    class MyListView extends StatefulWidget {
      final bool reOrder;
      const MyListView(this.reOrder, {super.key});
    
      @override
      State<MyListView> createState() => _MyListViewState();
    }
    
    class _MyListViewState extends State<MyListView> {
      @override
      Widget build(BuildContext context) {
        if (widget.reOrder) {
          return ReorderableListView(
            key: const ObjectKey('reordeableListView'),
            onReorder: (int oldIndex, int newIndex) {
              setState(() {
                if (oldIndex < newIndex) {
                  newIndex -= 1;
                }
                final item = data.removeAt(oldIndex);
                data.insert(newIndex, item);
              });
            },
            children: [
              for (var item in data)
                ListTile(
                  key: ObjectKey('${item["title"]}_compactExerciseTemplateRow_provider'),
                  title: Text(item["title"] as String),
                  trailing: Text((item["value"] as bool).toString()),
                ),
            ],
          );
        } else {
          return ListView.builder(
            key: const ObjectKey('listView'),
            itemCount: data.length,
            padding: const EdgeInsets.only(bottom: 120),
            itemBuilder: (BuildContext context, int index) {
              return CheckboxListTile(
                key: ObjectKey('${data[index]["title"]}_exerciseTemplateRow_provider'),
                title: Text(data[index]["title"] as String),
                value: (data[index]["value"] as bool),
                onChanged: (bool? value) {
                  setState(() => data[index]["value"] = value!);
                },
              );
            },
          );
        }
      }
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search