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

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库快速实现不可变类:

  1. 添加依赖到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
  1. 定义不可变模型类:
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);
}
  1. 修改状态更新逻辑,创建新对象而非直接修改属性:
// 服务器全选/取消逻辑
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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 23:37:00