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

Flutter/Hive内存List修改未保存却跨页面留存问题

Flutter Hive 异常问题:未保存的内存修改意外保留

问题现象

在第一个页面从Hive数据库获取DataCategory列表后,仅修改内存中的List对象,未执行Hive的put保存操作就关闭页面;打开第二个页面重新从Hive获取该列表时,发现之前未保存的修改仍然存在。

相关代码

GeneralBox 工具类

import 'dart:convert';

import 'package:hive/hive.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:mg_planner_app/data/data_default_category.dart';
import 'package:mg_planner_app/data/data_general.dart';
import 'package:mg_planner_app/setting/model/model_country.dart';


class GeneralBox {
  static const String data_general = "data_general";
  static const String data_category = "data_category";
  static const String data_sub_category = "data_sub_category";
  static const String data_item = "data_item";
  static const String data_setting = "data_setting";

  static Box<DataGeneral> GetGeneral() => Hive.box(data_general);

  static Box<DataCategory> GetCategory() => Hive.box(data_category);

  static Box<DataSubCategory> GetExpense() => Hive.box(data_sub_category);

  static Box<DataItem> GetAccounts() => Hive.box(data_item);

  static Box<DataSetting> GetDataSetting() => Hive.box(data_setting);

  static DataSetting GetDataSettingObject() {
    var box_setting = GetDataSetting();
    if(box_setting.values.length > 0) {
      return box_setting.values.toList().cast<DataSetting>()[0];
    }
    else {
      final dataSetting = DataSetting()
        ..key = "0"
        ..user_name = "Guest"
        ..due_term = "7"
        ..number_formate_index = "3"
        ..number_formate_char = ","
        ..date_format = "dd/MM/yyyy"
        ..time_format = "hh:mm a"
        ..uuid = "uuid.v1()"
        ..modelCountry = jsonEncode(ModelCountry.countries[185])
        ..pass_code = false
        ..is_show_board = true
        ..is_purchased = false
        ..currency_format = 4
        ..password = "";

      box_setting.put("0", dataSetting);
      return dataSetting;
    }
  }

  static void saveDataSetting(DataSetting dataSetting) {
    var box_setting = GetDataSetting();
    box_setting.put("0", dataSetting);
  }

  static GetHiveFunction() async {
    await Hive.initFlutter();
    if(!Hive.isAdapterRegistered(0)) {
      Hive.registerAdapter(DataGeneralAdapter());
    }
    if(!Hive.isAdapterRegistered(1)) {
      Hive.registerAdapter(DataCategoryAdapter());
    }
    if(!Hive.isAdapterRegistered(2)) {
      Hive.registerAdapter(DataSubCategoryAdapter());
    }
    if(!Hive.isAdapterRegistered(3)) {
      Hive.registerAdapter(DataItemAdapter());
    }
    if(!Hive.isAdapterRegistered(4)) {
      Hive.registerAdapter(DataSettingAdapter());
    }

    if(!Hive.isBoxOpen(data_general)) {
      await Hive.openBox<DataGeneral>(data_general);
    }
    if(!Hive.isBoxOpen(data_category)) {
      await Hive.openBox<DataCategory>(data_category);
    }
    if(!Hive.isBoxOpen(data_sub_category)) {
      await Hive.openBox<DataSubCategory>(data_sub_category);
    }
    if(!Hive.isBoxOpen(data_item)) {
      await Hive.openBox<DataItem>(data_item);
    }
    if(!Hive.isBoxOpen(data_setting)) {
      await Hive.openBox<DataSetting>(data_setting);
    }
  }

  static String getNewKey() {
    return DateTime.now().millisecondsSinceEpoch.toString();
  }

  static List<DataCategory> ListDefaultCategory() {
    List<DataCategory> list = [];

    list.add(dataCategory1);
    list.add(dataCategory2);
    list.add(dataCategory3);
    list.add(dataCategory4);
    list.add(dataCategory5);
    list.add(dataCategory6);
    list.add(dataCategory7);
    list.add(dataCategory8);
    list.add(dataCategory9);
    list.add(dataCategory10);
    list.add(dataCategory11);
    list.add(dataCategory12);
    list.add(dataCategory13);
    return list;
  }

  static DataCategory AddNewCategory(String category_type, String category_name) {
    return DataCategory()
      ..key = getNewKey()
      ..type = category_type
      ..name = category_name
      ..list_item = []
      ..sub_category = [];
  }

  static DataSubCategory AddNewSubCategory(String category_type, String category_name) {
    return DataSubCategory()
      ..key = getNewKey()
      ..type = category_type
      ..name = category_name
      ..list_item = [];
  }
}

页面中获取列表的代码

ValueListenableBuilder<Box<DataCategory>>(
  valueListenable: GeneralBox.GetCategory().listenable(),
  builder: (context, box, _) {
    List<DataCategory> list_category = box.values.toList().cast<DataCategory>();
    // 此处修改list_category或其中的DataCategory对象
    // ...
  },
)

问题原因

Hive中的对象是引用类型,当你从Box中获取对象列表时,得到的是内存中对象的直接引用,而非副本。由于你的GeneralBox全局复用打开的Box实例,只要Box保持打开状态,后续从Box中获取的对象都是同一个内存实例。因此,即使未调用put将修改持久化到磁盘,内存中的对象修改仍会被保留,直到Box关闭或应用重启。

解决方案

1. 修改对象前创建副本

修改内存对象时,先创建对象的深拷贝,避免直接修改Hive持有的原对象:

// 为DataCategory创建副本方法
DataCategory copyCategory(DataCategory original) {
  return DataCategory()
    ..key = original.key
    ..type = original.type
    ..name = original.name
    ..list_item = List.from(original.list_item)
    ..sub_category = List.from(original.sub_category);
}

// 使用时生成副本列表
List<DataCategory> list_category = box.values.toList().cast<DataCategory>().map(copyCategory).toList();

2. 规范修改流程

仅在需要持久化时更新原对象,修改后立即调用put保存:

// 修改后立即保存到Hive
void updateCategory(DataCategory updatedCategory) {
  final box = GeneralBox.GetCategory();
  box.put(updatedCategory.key, updatedCategory);
}

3. 页面销毁时重置内存对象(可选)

若不需要保留页面临时修改,可在页面销毁时用磁盘数据覆盖内存对象:

@override
void dispose() {
  final box = GeneralBox.GetCategory();
  // 遍历所有key,用磁盘数据重新覆盖内存对象
  for (var key in box.keys) {
    final diskData = box.get(key);
    box.put(key, diskData);
  }
  super.dispose();
}

内容的提问来源于stack exchange,提问作者sifat ullah shah

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 03:29:55