RangeError(RangeError(索引):无效值:有效值范围为空:0)从警报对话框的列表中删除项目时出错

发布于 2025-01-15 15:01:11 字数 5133 浏览 3 评论 0原文

我对 flutter 还很陌生,正在学习一门课程,但遇到了这个错误:

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

背景

我正在编写的用于学习 flutter 的简单应用程序是一个任务管理应用程序,我们有一个任务列表,您可以单击按钮将新任务添加到列表中。

查看任务的主列表时,您可以单击“查看”,然后弹出一个警报对话框,在警报对话框中显示任务“标题”和任务“描述”。

在警报对话框中,我有一个“保存”和“删除”按钮。保存工作正常,我在删除按钮方面遇到问题。

代码

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),
    );
  }
}

当我单击删除按钮时,错误发生在第 74 行:

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

我认为发生的情况是,当我单击“删除”按钮并且它在按下的函数上运行时:

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

我认为正在破坏的代码因为警报对话框中的“标题”来自我刚刚删除的索引...所以在运行弹出窗口之前,警报对话框有一瞬间没有任何内容可显示...

我尝试移动 Navigator.of (context).pop(); 在前面删除电话,但这也不起作用。如果我继续执行代码,任务确实会被删除,所以它可以工作,但不确定如何修复弹出的错误。

删除任务函数是:

  void deleteTask({required int index}) {
    tasks.removeAt(index);
    notifyListeners();
  }

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();
  }

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

痴者 2025-01-22 15:01:11

如果这是你的情况,对话框正在使用你正在删除的任务,你可以在两者之间添加一个微小的延迟 - 你甚至可以弹出另一个小对话框,上面写着“删除任务”,就像一个很好的用户体验。

我会做类似的事情:

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

作为一个很好的东西(以下内容是可选的):

在显示旋转的圆形进度指示器和 后,我会立即显示一个小对话框文本小部件只是说“删除任务”或类似的内容。

我将按照上面的方式重构它,但现在我传递一个回调,如下所示:

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

onPressed 中,我将执行相同的操作,但在弹出主警报后,我将启动“删除任务”,在延迟完成并且回调执行它时,再次弹出导航器以将其关闭:

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();
  }, 

这是 Gist 详细介绍了该方法(在 DartPad.dev 上运行并查看)。您应该看到以下输出:

在此处输入图像描述

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

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文