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

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的创建函数,天然支持初始化参数传递,且每个页面实例的状态相互隔离:

  1. 修改filterProvider为StateProvider.family:
final filterProvider = StateProvider.family<Filter, Filter>((ref, initialFilter) {
  return initialFilter;
});
  1. 修改searchControllerProvider为Provider.family,接收初始化参数:
final searchControllerProvider = Provider.family<List<String>, Filter>((ref, initialFilter) {
  final filter = ref.watch(filterProvider(initialFilter)).state;
  return filter.isFiltered ? ['True'] : ['False'];
});
  1. 在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 14:40:11