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

Flutter中FutureBuilder二次点击按钮时Hive Key为空问题求助

问题根源与修复方案

核心问题分析

  1. 列表项构建时重复初始化状态列表:在itemBuilder中每次构建列表项都会遍历整个数据集,往taskIconCheckList中添加元素,最终列表长度变为数据量×数据量,导致状态与数据索引完全不匹配,触发后续流程异常。
  2. FutureBuilder重复执行异步请求:未缓存showTaskList()的结果,每次组件重建都会重新发起Hive查询,导致新旧数据混杂,点击时可能拿到过时的对象。
  3. Task更新逻辑冗余且易出错:通过where查询目标Task效率低,新建Task对象时丢失原对象的user和taskCat字段,且手动构建对象可能导致Hive的key关联异常。

具体修复步骤

1. 缓存FutureBuilder的异步请求

在页面的initState中缓存异步请求结果,避免每次组件重建都重新查询Hive:

late Future<List<Task>> taskListFuture;

@override
void initState() {
  super.initState();
  taskListFuture = controller.showTaskList();
}

2. 修复状态列表初始化逻辑

将taskIconCheckList的初始化移到FutureBuilder获取数据后,仅在列表为空或长度变化时执行,确保状态与数据一一对应:

FutureBuilder<List<Task>>(
  future: taskListFuture,
  builder: (context, snapshot) {
    switch (snapshot.connectionState) {
      case ConnectionState.waiting:
        return SizedBox(
          height: Get.height,
          child: const Center(child: CircularProgressIndicator()),
        );
      default:
        if (snapshot.hasError) {
          return Text('Error: ${snapshot.error}');
        } else {
          final List<Task> data = snapshot.data ?? [];

          // 仅在必要时初始化状态列表
          if (controller.taskIconCheckList.isEmpty || controller.taskIconCheckList.length != data.length) {
            controller.taskIconCheckList.clear();
            for (final task in data) {
              controller.taskIconCheckList.add(task.status.obs);
            }
          }

          return ListView.separated(
            scrollDirection: Axis.vertical,
            physics: const BouncingScrollPhysics(),
            shrinkWrap: true,
            itemCount: data.length,
            itemBuilder: (context, index) {
              final task = data[index];
              return ListTile(
                leading: Obx(
                  () => PageTransitionSwitcher(
                    transitionBuilder: (child, primaryAnimation, secondaryAnimation) {
                      return SharedAxisTransition(
                        animation: primaryAnimation,
                        secondaryAnimation: secondaryAnimation,
                        transitionType: SharedAxisTransitionType.horizontal,
                        fillColor: Colors.transparent,
                        child: child,
                      );
                    },
                    duration: const Duration(milliseconds: 800),
                    child: controller.taskIconCheckList[index].value
                        ? IconButton(
                            icon: const Icon(Icons.check_circle_rounded, color: Colors.lightGreenAccent),
                            onPressed: () {
                              controller.functionTaskIconCheckList(index);
                              controller.updateStatusTask(index, task.key!);
                            },
                          )
                        : IconButton(
                            icon: const Icon(Icons.radio_button_unchecked_outlined, color: Colors.red),
                            onPressed: () {
                              controller.functionTaskIconCheckList(index);
                              controller.updateStatusTask(index, task.key!);
                            },
                          ),
                  ),
                ),
                title: Text(task.taskText, style: normalTextForCategory),
                subtitle: Text(
                  task.date.toString().substring(0, 10),
                  textDirection: TextDirection.ltr,
                  textAlign: TextAlign.right,
                  style: normalTextForSubtitle,
                ),
                trailing: Row(
                  mainAxisSize: MainAxisSize.min,
                  children: [
                    IconButton(
                      onPressed: () {
                        myDefaultDialog(
                          'هشدار',
                          'آیا از حذف این گزینه اطمینان دارید؟',
                          'بله',
                          'خیر',
                          () {
                            Get.back();
                            mySnakeBar(
                              '',
                              'گزینه مورد نظر با موفقیت حذف شد.',
                              Icons.warning_amber_rounded,
                              Colors.yellow,
                            );
                          },
                        );
                      },
                      icon: const Icon(Icons.delete),
                      color: Colors.redAccent,
                    ),
                    IconButton(
                      onPressed: () {
                        Get.offNamed(Routs.editTaskScreen, arguments: 'edit');
                      },
                      icon: const Icon(Icons.edit_calendar, color: Colors.yellowAccent),
                    ),
                  ],
                ),
              );
            },
            separatorBuilder: (context, index) => const Divider(height: 2, color: Colors.white70),
          );
        }
    }
  },
)

3. 优化Task更新函数

直接获取目标Task并修改状态后保存,无需新建对象,避免key关联异常:

Future<void> updateStatusTask(int index, int taskKey) async {
  final taskBox = await Hive.openBox('task');
  final Task? task = taskBox.get(taskKey) as Task?;
  if (task == null) return;

  // 修改状态并自动同步到Hive
  task.status = taskIconCheckList[index].value;
  await task.save();
}

4. 明确showTaskList的返回类型

指定返回List<Task>,避免类型不确定导致的潜在问题:

Future<List<Task>> showTaskList() async {
  final sharedPreferences = await SharedPreferences.getInstance();
  final userId = sharedPreferences.getInt('key');
  if (userId == null) return [];

  final taskBox = await Hive.openBox('task');
  return taskBox.values.where((task) => task.userId == userId).cast<Task>().toList();
}

修复原理

通过缓存异步请求避免重复查询,确保状态列表与数据一一对应,同时使用HiveObject的原生save()方法更新数据,避免手动构建对象导致的key丢失问题,彻底解决二次点击时key为null的异常。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.11 21:15:32