Flutter Hive筛选器无法重置 求技术解决方案
Flutter+Hive 筛选器无法重置问题排查与解决
在基于Flutter和Hive开发的项目中,遇到筛选器无法重置的异常:即便未将筛选器数据存入Hive Box,旧的筛选状态仍会被保留。尝试过调用setState、box.compact、Navigator.pop、重新打开Box,甚至清理应用缓存、重启真机和iPhone模拟器,所有方法均无效。
相关代码
class ClientsFilterBottomSheet extends StatefulWidget { final TransactionsBloc transactionsBloc; const ClientsFilterBottomSheet({Key? key, required this.transactionsBloc}) : super(key: key); @override State<ClientsFilterBottomSheet> createState() => _ClientsFilterBottomSheetState(); } class _ClientsFilterBottomSheetState extends State<ClientsFilterBottomSheet> { List<dynamic> servers = []; @override void initState() { super.initState(); servers = box.get('servers', defaultValue: []); } void _resetFilter() async { setState(() { servers = box.get('servers', defaultValue: []); }); } @override void dispose() { super.dispose(); } @override Widget build(BuildContext context) { return SingleChildScrollView( physics: const NeverScrollableScrollPhysics(), child: SizedBox( height: MediaQuery.of(context).size.height * 0.7, child: Column( children: [ Container( height: 4, width: 48, margin: const EdgeInsets.only(top: 10), decoration: BoxDecoration( color: const Color(0xffE0E0E0), borderRadius: BorderRadius.circular(10), ), ), const SizedBox( height: 10, ), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const SizedBox( width: 48, ), Container(margin: const EdgeInsets.only(top: 13, bottom: 13), child: Text('filter_shops'.tr()).roboto(fontSize: 18, fontWeight: FontWeight.w500)), Container( margin: const EdgeInsets.only(right: 24), child: animatedDownloadButton(onUserPressed: () async { await Future.delayed(const Duration(seconds: 5)); }), ) ], ), const SizedBox( height: 10, ), const Divider( color: UIColors.greyColor2, thickness: 0.1, height: 0, ), SizedBox( height: MediaQuery.of(context).size.height * 0.48, child: SingleChildScrollView( child: ListView.builder( shrinkWrap: true, itemCount: servers.length, physics: const NeverScrollableScrollPhysics(), itemBuilder: (context, index) { List<dynamic> clients = servers[index].clients; return clients.isEmpty ? const SizedBox() : Column( children: [ ListTileTheme( dense: true, horizontalTitleGap: 0.0, minLeadingWidth: 0, child: ExpansionTile( controlAffinity: ListTileControlAffinity.leading, initiallyExpanded: true, title: CheckboxListTile( value: servers[index].isSelected, tristate: false, onChanged: (value) { servers[index].isSelected = value!; servers[index].clients.forEach((element) { element.isSelected = value; }); setState(() {}); }, title: GestureDetector( onTap: () {}, child: Text(servers[index].name ?? '').capitalize().roboto(fontSize: 16, fontWeight: FontWeight.w500, color: Colors.black87), ), ), children: [ ListView.builder( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), itemCount: clients.length, itemBuilder: (context, index2) { return Padding( padding: const EdgeInsets.only(right: 14.0, left: 30.0, top: 0, bottom: 0), child: ListTileTheme( dense: true, child: CheckboxListTile( value: servers[index].isSelected ? (clients[index2].isSelected) : false, checkColor: UIColors.mainBackgroundColor, onChanged: (value) { clients[index2].isSelected = value!; if (clients.any((element) => element.isSelected)) { servers[index].isSelected = true; } else if (clients.every((element) => !element.isSelected)) { servers[index].isSelected = false; } setState(() {}); }, title: Padding( padding: const EdgeInsets.only(left: 8.0), child: Text(clients[index2].name ?? '').capitalize().roboto(fontSize: 14, fontWeight: FontWeight.w400), ), ), ), ); }, ), ], ), ), ], ); }, ), ), ), Expanded( child: Container( padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), decoration: BoxDecoration( borderRadius: BorderRadius.circular(10), color: Colors.white, boxShadow: [ BoxShadow( color: Colors.grey.withOpacity(0.2), spreadRadius: 1, blurRadius: 1, offset: const Offset(0, 1), // changes position of shadow ), ], ), child: Row( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ Expanded( child: ElevatedButton( onPressed: _resetFilter, style: ElevatedButton.styleFrom( elevation: 0, backgroundColor: const Color(0xffF0F2F6), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10), ), padding: const EdgeInsets.symmetric(vertical: 20), ), child: Text('reset'.tr().toUpperCase()).roboto(fontSize: 16, fontWeight: FontWeight.w500, color: const Color(0xff009C35)), ), ), const SizedBox( width: 8, ), Expanded( child: ElevatedButton( onPressed: () async { // await box.put('servers', servers); if (mounted) Navigator.pop(context); }, style: ElevatedButton.styleFrom( elevation: 0, backgroundColor: const Color(0xff009C35), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10), ), padding: const EdgeInsets.symmetric(vertical: 20), ), child: Text('apply'.tr().toUpperCase()).roboto(fontSize: 16, fontWeight: FontWeight.w500, color: Colors.white), ), ), //reset button ], ), ), ), ], ), ), ); } }
问题根源
Hive存储的对象是引用类型,你在initState中直接将box.get('servers')赋值给servers变量后,后续修改servers[index].isSelected或客户端选中状态时,实际上是直接修改了Hive Box中存储对象的内存引用,即便没有调用box.put,这些修改也会被Hive自动同步到磁盘(因为Hive的可变对象会实时跟踪修改)。重置时重新调用box.get拿到的还是被修改后的对象引用,所以旧状态无法清除。
解决方案
方案1:深拷贝切断引用关联
每次从Hive获取数据时,对数据进行深拷贝,避免直接引用Hive中的原始对象。可以通过序列化反序列化实现通用深拷贝:
import 'dart:convert'; // 通用深拷贝方法 List<dynamic> deepCopyList(List<dynamic> list) { return jsonDecode(jsonEncode(list)).cast<dynamic>(); } class _ClientsFilterBottomSheetState extends State<ClientsFilterBottomSheet> { List<dynamic> servers = []; @override void initState() { super.initState(); // 初始化时深拷贝 servers = deepCopyList(box.get('servers', defaultValue: [])); } void _resetFilter() { setState(() { // 重置时重新深拷贝原始数据 servers = deepCopyList(box.get('servers', defaultValue: [])); }); } // ... 其他代码不变 }
如果有自定义模型类,推荐用模型类的fromJson/toJson方法实现更可靠的深拷贝,避免json序列化的局限性。
方案2:使用不可变对象(推荐)
将Server和Client定义为不可变类,修改状态时必须创建新对象,从根源上避免修改Hive原始数据的问题。可以使用freezed库快速实现不可变类:
- 添加依赖到
pubspec.yaml:
dependencies: freezed_annotation: ^2.4.1 hive: ^2.2.3 hive_flutter: ^1.1.0 dev_dependencies: build_runner: ^2.4.8 freezed: ^2.4.6 hive_generator: ^1.1.5
- 定义不可变模型类:
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:hive/hive.dart'; part 'server.freezed.dart'; part 'server.g.dart'; @HiveType(typeId: 0) @freezed class Server with _$Server { const factory Server({ @HiveField(0) required String name, @HiveField(1) required bool isSelected, @HiveField(2) required List<Client> clients, }) = _Server; factory Server.fromJson(Map<String, dynamic> json) => _$ServerFromJson(json); } @HiveType(typeId: 1) @freezed class Client with _$Client { const factory Client({ @HiveField(0) required String name, @HiveField(1) required bool isSelected, }) = _Client; factory Client.fromJson(Map<String, dynamic> json) => _$ClientFromJson(json); }
- 修改状态更新逻辑,创建新对象而非直接修改属性:
// 服务器全选/取消逻辑 onChanged: (value) { setState(() { servers = servers.map((server) { if (server == servers[index]) { return server.copyWith( isSelected: value!, clients: server.clients.map((client) => client.copyWith(isSelected: value!)).toList(), ); } return server; }).toList(); }); }, // 单个客户端选中逻辑 onChanged: (value) { setState(() { servers = servers.map((server) { if (server == servers[index]) { final updatedClients = server.clients.map((client) { if (client == clients[index2]) { return client.copyWith(isSelected: value!); } return client; }).toList(); // 更新服务器选中状态 final serverIsSelected = updatedClients.any((client) => client.isSelected); return server.copyWith( isSelected: serverIsSelected, clients: updatedClients, ); } return server; }).toList(); }); },
内容的提问来源于stack exchange,提问作者Ismoil A'zamov
相关产品推荐
相关产品推荐

