Debug模式下已移除的Riverpod组件仍执行FamilyProvider选择器
问题:移除列表项时Riverpod FamilyProvider抛出"Bad state: No element"错误
问题描述
状态中包含用户可修改的按钮列表,添加新按钮正常,但移除按钮时,对应按钮组件仍会执行buttonStateFamilyProvider的选择器逻辑,此时目标按钮已从列表中移除,firstWhere因找不到元素抛出Error: Bad state: No element异常。
复现环境
- 仅在Windows、Chrome的Debug构建版本中触发
- Release模式及dartpad.dev中无法复现
复现步骤
- 运行Debug版本应用
- 点击任意按钮将其从列表中移除
- 立即触发上述错误
原因分析
点击按钮移除自身时,AppState更新后,MyHomePage会重建并移除对应的_ButtonWidget,但在组件从树中完全移除前,autoDispose的Family Provider仍会收到AppState更新通知,此时执行firstWhere找不到对应ID的按钮,直接抛出异常。
解决方案
核心修改:给firstWhere添加orElse避免抛出异常
修改Family Provider中的选择器逻辑,添加orElse返回null,同时在组件中处理null情况,避免报错;另外修正ButtonState的Equatable实现,确保状态判断准确。
修改后的完整代码
import 'package:equatable/equatable.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; void main() { runApp(const ProviderScope(child: MyApp())); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, ), home: const MyHomePage(title: 'Flutter Demo Home Page'), ); } } class MyHomePage extends ConsumerWidget { const MyHomePage({super.key, required this.title}); final String title; @override Widget build(BuildContext context, WidgetRef ref) { final buttons = ref.watch(appStateProvider).buttons; print('Rebuild HomePage with $buttons'); return Scaffold( appBar: AppBar( title: Text(title), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ const Text('Click on any of the buttons to remove the widget'), for (final btn in buttons) _ButtonWidget(id: btn.id), ], ), ), ); } } class _ButtonWidget extends ConsumerWidget { final int id; const _ButtonWidget({ required this.id, }); @override Widget build(BuildContext context, WidgetRef ref) { print('Rebuild ButtonWidget $id'); final buttonState = ref.watch(buttonStateFamilyProvider(id)); // 若按钮已被移除,返回空组件避免UI报错 if (buttonState == null) { return const SizedBox.shrink(); } return ElevatedButton( onPressed: () { final appStateController = ref.read(appStateProvider.notifier); final newList = appStateController.state.buttons.toList() ..removeWhere((btn) => btn.id == id); appStateController.state = AppState(newList); }, style: ElevatedButton.styleFrom(backgroundColor: Colors.blueGrey), child: Text(buttonState.name), ); } } final appStateProvider = StateProvider<AppState>((ref) { return const AppState([ ButtonState(1, 'Button 1'), ButtonState(2, 'Button 2'), ]); }); final buttonStateFamilyProvider = Provider.family.autoDispose<ButtonState?, int>( (ref, id) { ref.onCancel(() { print('cancelled buttonStateFamilyProvider($id)'); }); print('Executing buttonStateFamilyProvider($id)'); final result = ref.watch( appStateProvider.select( (state) => state.buttons.firstWhere( (btn) => btn.id == id, orElse: () => null, // 添加orElse避免找不到元素时抛出异常 ), ), ); print('buttonStateFamilyProvider($id) = $result'); return result; }, ); class AppState with EquatableMixin { final List<ButtonState> buttons; const AppState(this.buttons); @override List<Object?> get props => [buttons]; } class ButtonState with EquatableMixin { final String name; final int id; const ButtonState(this.id, this.name); // 修正:将id加入props,确保Equatable能正确判断按钮唯一性 @override List<Object?> get props => [id, name]; }
关键修改说明
- Family Provider返回类型调整:改为
ButtonState?,适配orElse返回null的情况 - 添加orElse参数:在
firstWhere中指定orElse: () => null,避免找不到元素时抛出异常 - 组件空状态处理:在
_ButtonWidget中判断buttonState是否为null,为空时返回空组件 - Equatable逻辑修正:将
ButtonState的id加入props列表,确保不同ID的按钮被正确识别为不同对象
pubspec.yaml(无修改)
name: riverpod_widget_rebuild description: A new Flutter project. publish_to: 'none' version: 1.0.0+1 environment: sdk: '>=2.18.5 <3.0.0' dependencies: flutter: sdk: flutter flutter_riverpod: ^2.3.0 cupertino_icons: ^1.0.2 equatable: ^2.0.5 dev_dependencies: flutter_test: sdk: flutter flutter_lints: ^2.0.0 flutter: uses-material-design: true
内容的提问来源于stack exchange,提问作者Matthiee
相关产品推荐
相关产品推荐

