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
相关产品推荐
相关产品推荐

