Flutter ObjectBox中如何编写带关联的可订阅查询
问题根因
你观察到的异常触发是ObjectBox watch()方法的默认设计导致的:Query.watch()返回的流属于实体级变更监听,只要监听的实体类型(此处为Runner)发生任何增删改操作,无论变更的实体是否匹配当前查询的过滤条件,都会立即触发事件,不会判断变更是否影响当前查询的结果集,这和你测试时的现象完全吻合。
你的查询过滤逻辑本身没有问题,只是监听的粒度不符合预期。
正确实现方案
不要直接使用裸的watch()流,改用ObjectBox内置的带结果校验的结果流,有两种落地方式:
- 优先使用
Query.stream()(推荐)
构建完查询后直接调用stream()方法,这个方法返回Stream<List<T>>类型的结果流,内部自动做两层优化:- 仅当和当前查询条件关联的实体发生变更时才触发重查(包括匹配条件的
Runner实体变更、关联的Class实体变更、Runner与Class的关联关系变更),非关联实体变更不会触发重查 - 自动比对前后两次查询的结果集,只有结果的成员列表、实体属性确实发生变化时,才会向监听者下发事件,完全避免无效触发
- 仅当和当前查询条件关联的实体发生变更时才触发重查(包括匹配条件的
- 手动加结果比对(不推荐)
如果你要保留现有watch()的写法,可以在监听回调中自行缓存上一次的查询结果,每次触发查询后和缓存做比对,只有结果不一致时再执行重绘逻辑。但这种方式在无关实体变更时仍会执行一次全量查询,性能不如内置流方案。
修正后的代码示例
// 持久层代码 late Stream<List<Runner>> easyGroupRunnersStream; // 记得持有Query实例,在不需要时调用close()释放资源 late final Query<Runner> easyGroupQuery; // 初始化查询逻辑 void initEasyGroupQuery() { final queryBuilder = runnerBox.query(); queryBuilder.link(Runner_.runnerClass, Class_.name.equals("Easy")); easyGroupQuery = queryBuilder.build(); // 获取带变更校验的结果流 easyGroupRunnersStream = easyGroupQuery.stream(); } // 监听层代码 final sub = db.easyGroupRunnersStream.listen((List<Runner> easyRunners) { // 仅当Easy组跑者确实发生增删、属性修改时才会进入回调 print("Easy组数据更新,当前人数:${easyRunners.length}"); // 执行组件重绘逻辑 }); // 组件销毁时记得释放资源 @override void dispose() { sub.cancel(); db.easyGroupQuery.close(); super.dispose(); }
补充说明
stream()方法默认会同时监听查询关联的实体变更,如果你后续需要响应组别本身的属性修改(比如"Easy"组改名、组状态变更),不需要额外添加监听,流会自动捕获关联变更并触发更新。
内容的提问来源于stack exchange,提问作者Albert Henmyr
相关产品推荐
相关产品推荐

