Flutter中如何基于页面参数通过Riverpod正确初始化FilterProvider?
利用Widget参数初始化Riverpod Provider的最佳方案
我正在用Riverpod和AutoRoute开发Flutter应用,包含HomePage和SearchPage两个页面。筛选状态由filterProvider管理,SearchPage的搜索功能通过searchControllerProvider调用这个筛选器。现在需要实现的是:从HomePage导航到SearchPage时,把传递的初始Filter对象设置为filterProvider的初始状态。
相关代码
@MaterialAutoRouter( replaceInRouteName: 'Page,Route', routes: <AutoRoute>[ AutoRoute(page: HomePage, initial: true), AutoRoute(page: SearchPage), ], ) class $AppRouter {} class Filter { final bool isFiltered; Filter({required this.isFiltered}); } final filterProvider = StateProvider<Filter>((ref) { return Filter(isFiltered: false); }); class HomePage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Home Page'), ), body: Center( child: ElevatedButton( onPressed: () { context.pushRoute(SearchRoute(filter: Filter(isFiltered: true))); }, child: Text('Go to Search'), ), ), ); } } final searchControllerProvider = Provider<List<String>>((ref) { final filter = ref.watch(filterProvider).state; if (filter.isFiltered) { return ['True']; } else { return ['False']; } }); class SearchPage extends ConsumerStatefulWidget { final Filter initialFilter; SearchPage({Key? key, required this.initialFilter}) : super(key: key); @override _SearchPageState createState() => _SearchPageState(); } class _SearchPageState extends ConsumerState<SearchPage> { @override Widget build(BuildContext context) { final results = ref.watch(searchControllerProvider); return Scaffold( appBar: AppBar( title: Text('Search Page'), ), body: Expanded( child: ListView.builder( itemCount: results.length, itemBuilder: (context, index) { return ListTile( title: Text(results[index]), ); }, ), ), ); } }
尝试过的方法及问题
方法一:在initState或build中修改filterProvider
@override void initState() { super.initState(); ref.read(filterProvider.notifier).state = widget.initialFilter; }
触发错误:
══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞════════════════════════ Tried to modify a provider while the widget tree was building. If you are encountering this error, chances are you tried to modify a provider in a widget life-cycle, such as but not limited to: - build - initState - dispose - didUpdateWidget - didChangeDependencies
方法二:在HomePage跳转前设置filterProvider值
onPressed: () { ref.read(filterProvider.notifier).state = Filter(isFiltered: true); context.pushRoute(SearchRoute(filter: Filter(isFiltered: true))); }
日志显示filterProvider在SearchPage加载前已被销毁:
Provider filterProvider was initialized with Filter(isFiltered: false) Provider filterProvider updated from Filter(isFiltered: true)) Provider filterProvider was disposed Provider filterProvider was initialized with Filter(isFiltered: false)
用KeepAlive会导致状态跨页面保留,不符合需求。
方法三:使用AutoRouteWrapper与ProviderScope
class SearchPage extends ConsumerStatefulWidget implements AutoRouteWrapper { final Filter initialFilter; SearchPage({Key? key, required this.initialFilter}) : super(key: key); @override Widget wrappedRoute(BuildContext context) { return ProviderScope( overrides: [ filterProvider.overrideWith(() => initialFilter), ], child: this, ); } @override _SearchPageState createState() => _SearchPageState(); }
触发LateInitializationError异常:
══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞════════════════════════ The following LateError was thrown building SearchScreen(dirty, dependencies: [UncontrolledProviderScope], state: SearchScreenState#5931a): LateInitializationError: Field '_element@119511105' has not been initialized.
方法四:使用Future.microtask或addPostFrameCallback
Future.microtask(() { ref.read(filterProvider.notifier).state = widget.initialFilter; }); // 或者 WidgetsBinding.instance.addPostFrameCallback((_) { ref.read(filterProvider.notifier).state = widget.initialFilter; });
该方法可行,但会导致searchControllerProvider调用两次,分别使用默认筛选器和正确值。
最佳解决方案
方案一:用FamilyProvider(推荐)
这是最符合Riverpod设计理念的方式,FamilyProvider允许你传递参数给Provider的创建函数,天然支持初始化参数传递,且每个页面实例的状态相互隔离:
- 修改
filterProvider为StateProvider.family:
final filterProvider = StateProvider.family<Filter, Filter>((ref, initialFilter) { return initialFilter; });
- 修改
searchControllerProvider为Provider.family,接收初始化参数:
final searchControllerProvider = Provider.family<List<String>, Filter>((ref, initialFilter) { final filter = ref.watch(filterProvider(initialFilter)).state; return filter.isFiltered ? ['True'] : ['False']; });
- 在SearchPage中使用带参数的Provider:
class _SearchPageState extends ConsumerState<SearchPage> { @override Widget build(BuildContext context) { final results = ref.watch(searchControllerProvider(widget.initialFilter)); return Scaffold( appBar: AppBar(title: const Text('Search Page')), body: ListView.builder( itemCount: results.length, itemBuilder: (context, index) => ListTile(title: Text(results[index])), ), ); } }
方案二:修复AutoRouteWrapper的ProviderScope用法
如果坚持使用全局StateProvider,需要避免直接将this作为ProviderScope的child,改用内部Widget或Builder延迟构建,解决element未初始化的问题:
class SearchPage extends ConsumerStatefulWidget implements AutoRouteWrapper { final Filter initialFilter; const SearchPage({Key? key, required this.initialFilter}) : super(key: key); @override Widget wrappedRoute(BuildContext context) { return ProviderScope( overrides: [ filterProvider.overrideWith((ref) => widget.initialFilter), ], // 用Builder延迟构建,避免element未初始化 child: Builder(builder: (context) => this), ); } @override _SearchPageState createState() => _SearchPageState(); } class _SearchPageState extends ConsumerState<SearchPage> { @override Widget build(BuildContext context) { final results = ref.watch(searchControllerProvider); return Scaffold( appBar: AppBar(title: const Text('Search Page')), body: ListView.builder( itemCount: results.length, itemBuilder: (context, index) => ListTile(title: Text(results[index])), ), ); } }
总结
- 优先选择FamilyProvider,它完美适配“带初始化参数的Provider”场景,状态隔离清晰,符合Riverpod的设计逻辑。
- 若使用全局StateProvider配合AutoRouteWrapper,需通过Builder或内部Widget延迟构建,避免LateInitializationError。
内容的提问来源于stack exchange,提问作者Guilherme Bernardino
相关产品推荐
相关产品推荐

