You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter ListView出现RangeError,任务无法显示求助

解决Flutter ListView中的RangeError问题

问题原因

你遇到的RangeError (index): Invalid value: Not in inclusive range 0..1: 2错误,核心是任务列表的子元素结构不一致:

  • 新创建的任务是[title, false, paragraph](3个元素)
  • 从SharedPreferences加载的旧任务是[task, isChecked](仅2个元素)
    但在ListView的itemBuilder中,你固定尝试获取第三个元素data[index][2](paragraph),导致加载旧任务时索引越界。

同时,你的saveTasks方法只保存了任务标题,没有保存paragraph,重启App后段落信息会丢失,也加剧了结构不一致的问题。

修复步骤

1. 修复任务的保存与加载逻辑

需要把每个任务的完整信息(标题、勾选状态、段落)序列化后保存,这里用JSON字符串存储完整任务结构:

首先在TaskList文件顶部导入JSON序列化包:

import 'dart:convert';

然后修改saveTasks和loadTask方法:

// 修改saveTasks,保存完整任务结构
void saveTasks() async {
  SharedPreferences prefs = await SharedPreferences.getInstance();
  List<String> tasks = toDoList.map((task) => jsonEncode({
    'title': task[0],
    'isChecked': task[1],
    'paragraph': task.length > 2 ? task[2] : '',
  })).toList();
  prefs.setStringList('tasks', tasks);
  print('Tasks saved: $tasks');
}

// 修改loadTask,解析完整任务结构
Future<void> loadTask() async {
  SharedPreferences prefs = await SharedPreferences.getInstance();
  List<String>? savedTasks = prefs.getStringList('tasks');

  if (savedTasks != null) {
    toDoList.clear(); // 清空旧数据,避免重复加载
    for (var taskStr in savedTasks) {
      Map<String, dynamic> taskMap = jsonDecode(taskStr);
      bool isChecked = await loadCheckBoxState(taskMap['title']);
      // 统一任务结构为3个元素
      toDoList.add([
        taskMap['title'],
        isChecked,
        taskMap['paragraph'] ?? '',
      ]);
    }
  }

  notifyListeners();
}

2. 增加安全访问判断(可选)

在ListView的itemBuilder中添加安全判断,避免极端情况下的索引越界:

itemBuilder: (context, index) {
  String title = data[index][0] ?? '无标题';
  bool isChecked = data[index][1] ?? false;
  // 若子列表不足3个元素,默认给空字符串
  String paragraph = data[index].length > 2 ? data[index][2] : '';

  // 后续代码不变
}

完整修改后的TaskList代码

import 'dart:convert';
import 'package:exam_1/widgets/dialog_box.dart';
import 'package:exam_1/widgets/task_details.dart';
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';

class TaskList extends ChangeNotifier {
  final _title = TextEditingController();
  final _paragraph = TextEditingController();

  bool isChecked = false;
  final String key = 'isChecked';

  List<List<dynamic>> toDoList = [];

  TaskList() {
    loadTask();
  }

  Future<void> loadTask() async {
    SharedPreferences prefs = await SharedPreferences.getInstance();
    List<String>? savedTasks = prefs.getStringList('tasks');

    if (savedTasks != null) {
      toDoList.clear();
      for (var taskStr in savedTasks) {
        Map<String, dynamic> taskMap = jsonDecode(taskStr);
        bool isChecked = await loadCheckBoxState(taskMap['title']);
        toDoList.add([
          taskMap['title'],
          isChecked,
          taskMap['paragraph'] ?? '',
        ]);
      }
    }

    notifyListeners();
  }

  Future<bool> loadCheckBoxState(String task) async {
    SharedPreferences prefs = await SharedPreferences.getInstance();
    return prefs.getBool('$key:$task') ?? false;
  }

  void saveTasks() async {
    SharedPreferences prefs = await SharedPreferences.getInstance();
    List<String> tasks = toDoList.map((task) => jsonEncode({
      'title': task[0],
      'isChecked': task[1],
      'paragraph': task.length > 2 ? task[2] : '',
    })).toList();
    prefs.setStringList('tasks', tasks);
    print('Tasks saved: $tasks');
  }

  void checkBoxChanged(bool? value, int index) async {
    if (value != null) {
      String task = toDoList[index][0] as String;
      toDoList[index][1] = value;
      SharedPreferences prefs = await SharedPreferences.getInstance();
      prefs.setBool('$key:$task', value);

      saveTasks();
      notifyListeners();
    }
  }

  void createNewTask(BuildContext context) {
    showDialog(
      context: context,
      builder: (context) {
        return DialogBox(
          titleController: _title,
          paragraphController: _paragraph,
          onSave: () => saveNewTask(context, _title.text, _paragraph.text),
          onCancel: () {
            Navigator.of(context).pop();
          },
        );
      },
    );
    notifyListeners();
  }

  void saveNewTask(BuildContext context, String title, String paragraph) {
    if (title.isEmpty || paragraph.isEmpty) {
      showDialog(
        context: context,
        builder: (context) {
          return const AlertDialog(
            title: Text('Please Add a note'),
          );
        },
      );
    } else {
      toDoList.add([title, false, paragraph]);
      _title.clear();
      _paragraph.clear();
      saveTasks();
      Navigator.of(context).pop();

      notifyListeners();
    }
  }

  void deleteTask(int index) {
    toDoList.removeAt(index);
    saveTasks();

    notifyListeners();
  }

  void openTaskDetails(BuildContext context, String title, String paragraph) {
    Navigator.push(
      context,
      MaterialPageRoute(
        builder: (context) =>
            TaskDetailsPage(title: title, paragraph: paragraph),
      ),
    );
  }
}

内容的提问来源于stack exchange,提问作者ParzivalDT

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 14:57:44