RangeError (RangeError (index): Invalid value: Valid value range is empty: 0) error when deleting an item from a list from an alert dialog

1,196

If that's your case, that the dialog is using the task you are deleting, you could add a tiny delay in between - you could even pop another small dialog that's saying "deleting task" just as a nice user experience.

I'd do something like:

void deleteTask({ required int index }) {
  Future.delayed(const Duration(milliseconds: 500) {
    tasks.removeAt(index);
    notifyListeners();
  }
}

As a nice-to-have (the below stuff is optional):

I'd show a small dialog right after just showing a spinning circular progress indicator and a Text widget just saying "deleting task" or something like that.

I'd refactor it as above, but now I pass a callback, as such:

void deleteTask({ required int index }, Function callback) {
  Future.delayed(const Duration(seconds: 1) {
    tasks.removeAt(index);
    notifyListeners();
    callback();
  }
}

And in the onPressed, i'd perform the same action, but right after popping the main alert, I'd launch the "deleting task" one, and upon the delay being done and the callback execute it, pop the Navigator once again to dismiss it:

onPressed: () {
   Provider.of<Tasks>(context, listen: false)
     .deleteTask(index: index, () {
         // pop the Navigator again since the task is done
         Navigator.of(context).pop();
     });
     
     // yes, pop the Alert here
     Navigator.of(context).pop();

     // launch the other one
     showDialog();
  }, 

Here's the Gist that details pretty much the approach (run it on DartPad.dev and see). You should see the following output:

enter image description here

Share:
1,196
Ten Digit Grid
Author by

Ten Digit Grid

Updated on January 04, 2023

Comments

  • Ten Digit Grid
    Ten Digit Grid over 1 year

    I am pretty new to flutter and am following a course and ran into this error:

    RangeError (RangeError (index): Invalid value: Valid value range is empty: 0)
    

    Background

    The simple app I am writing to learn flutter is a task management app where we have a list of tasks, and you can click a button to add new tasks to the list.

    When viewing the main list of tasks you can click "View" and an Alert Dialog pops up showing the Task "title" and task "description" in an Alert Dialog.

    In the Alert Dialog I have a "save" and "delete" button. The save works fine, I am having issues with the delete button.

    Code

    class Rivers extends StatelessWidget {
      const Rivers({Key? key}) : super(key: key);
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: const Text('Wave a Task'),
          ),
          body: Column(
            children: const [
              CircleAvatar(
                radius: 40,
                backgroundImage: AssetImage('images/wave.png'),
              ),
              RiverBody(),
            ],
          ),
          floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
          floatingActionButton: FloatingActionButton(
            onPressed: () {
              Navigator.of(context).push(
                MaterialPageRoute(
                  builder: (context) => TaskPage(),
                ),
              );
            },
            child: const Icon(Icons.add),
          ),
        );
      }
    }
    
    class RiverBody extends StatelessWidget {
      const RiverBody({Key? key}) : super(key: key);
    
      @override
      Widget build(BuildContext context) {
        return Expanded(
          child: ListView(
            padding: const EdgeInsets.only(top: 10.0),
            children: List.generate(
              Provider.of<Tasks>(context).tasks.length,
              (index) => ListTileCustom(
                index: index,
              ),
            ),
          ),
        );
      }
    }
    
    class ListTileCustom extends StatelessWidget {
      const ListTileCustom({Key? key, required this.index}) : super(key: key);
      final int index;
      @override
      Widget build(BuildContext context) {
        Task currentTask = Provider.of<Tasks>(context).tasks[index];
        return CheckboxListTile(
          subtitle: Align(
            alignment: Alignment.centerLeft,
            child: TextButton(
              onPressed: () {
                showDialog(
                    context: context,
                    builder: (context) {
                      String title = Provider.of<Tasks>(context).tasks[index].title;
                      String description =
                          Provider.of<Tasks>(context).tasks[index].description;
                      TextEditingController titleController =
                          TextEditingController(text: title);
                      TextEditingController descriptionController =
                          TextEditingController(text: description);
                      return AlertDialog(
                        title: TextField(
                          controller: titleController,
                        ),
                        content: TextField(
                          controller: descriptionController,
                        ),
                        actions: [
                          TextButton(
                            style: TextButton.styleFrom(
                              primary: Colors.white,
                              backgroundColor: Colors.red,
                            ),
                            onPressed: () {
                              Provider.of<Tasks>(context, listen: false)
                                  .deleteTask(index: index);
                              Navigator.of(context).pop();
                            },
                            child: const Text('Delete'),
                          ),
                          TextButton(
                            onPressed: () {
                              Provider.of<Tasks>(context, listen: false).save(
                                  index: index,
                                  newTitle: titleController.text,
                                  newDescription: descriptionController.text);
                              Navigator.of(context).pop();
                            },
                            child: const Text('Save'),
                          ),
                        ],
                      );
                    });
              },
              child: const Text('View'),
            ),
          ),
          onChanged: (newbool) {
            Provider.of<Tasks>(context, listen: false)
                .changeDone(index: index, isDone: newbool!);
          },
          value: currentTask.done,
          title: Text(currentTask.title),
        );
      }
    }
    

    When I click the delete button the error happens at line 74:

    String title = Provider.of<Tasks>(context).tasks[index].title;
    

    What I think is happening is that when I click the "delete" button and it runs its on pressed function:

                    onPressed: () {
                      Provider.of<Tasks>(context, listen: false)
                          .deleteTask(index: index);
                      Navigator.of(context).pop();
                    },
                    child: const Text('Delete'),
                  ),
    

    The code I think is breaking because the "title" in the Alert Dialog comes from the index I just deleted... so for a split second the alert dialog has nothing to show right before it runs the pop...

    I tried moving the Navigator.of(context).pop(); in front of the delete call but that didn't work either. If I continue the code the task does get deleted, so it kind of works but not sure how to fix the error popping up.

    The deleteTask function is:

      void deleteTask({required int index}) {
        tasks.removeAt(index);
        notifyListeners();
      }
    
  • Ten Digit Grid
    Ten Digit Grid about 2 years
    I think in your code its missing a ")" but when I put that back in I am still getting an error.
  • Roman Jaquez
    Roman Jaquez about 2 years
    yeah you are correct - it was for you to get the idea, but glad you caught that. I will provide you with a Github Gist with my idea so you can try it.