Flutter Riverpod分页:如何正确调用init()避免构建报错?
问题
我尝试用Riverpod结合Firestore实现列表分页功能,视图页面和Provider代码如下,但在调用init()函数时出现错误:Unhandled Exception: Tried to modify a provider while the widget tree was building.,请问如何正确实现分页?
视图页面代码
import 'package:enue/community/gyeongin/addposting/add_posting.dart'; import 'package:enue/provider/posting/posting_provider.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; class PostingHome extends ConsumerStatefulWidget { const PostingHome({super.key}); @override ConsumerState<ConsumerStatefulWidget> createState() => _PostingHomeState(); } class _PostingHomeState extends ConsumerState<PostingHome> { @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.white, floatingActionButton: FloatingActionButton( onPressed: () { Navigator.push( context, MaterialPageRoute( builder: (context) { return const AddPosting(); }, ), ); }, backgroundColor: const Color(0xFFc54245), child: const Icon(Icons.create, color: Colors.white), ), body: Consumer( builder: (context, ref, widget) { final postingHomeState = ref.watch(postingHomeProvider); return ListView.builder( controller: postingHomeState.scrollController, itemCount: postingHomeState.list.length + 1, itemBuilder: (context, index) { if (index < postingHomeState.list.length) { final item = postingHomeState.list[index]; return ListTile( title: Text(item['title']), subtitle: Text(item['subtitle']), ); } else if (postingHomeState.isMore) { return const Center(child: CircularProgressIndicator()); } else { return Container(); } }, ); }, ), ); } }
Provider代码
import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:cloud_firestore/cloud_firestore.dart'; final postingHomeProvider = StateNotifierProvider.autoDispose< PostingHomeStateNotifier, PostingHomeState>((ref) => PostingHomeStateNotifier()); class PostingHomeState { const PostingHomeState({ this.isMore = true, this.isLoading = false, this.list = const [], required this.scrollController, }); final bool isMore; final bool isLoading; final List<Map<String, dynamic>> list; final ScrollController scrollController; PostingHomeState copyWith({ bool? isMore, bool? isLoading, List<Map<String, dynamic>>? list, ScrollController? scrollController, }) { return PostingHomeState( isMore: isMore ?? this.isMore, isLoading: isLoading ?? this.isLoading, list: list ?? this.list, scrollController: scrollController ?? this.scrollController, ); } } class PostingHomeStateNotifier extends StateNotifier<PostingHomeState> { PostingHomeStateNotifier() : super(PostingHomeState( scrollController: ScrollController(), list: const [], isLoading: false, isMore: true)); DocumentSnapshot? lastDocument; Future<void> paginatedData() async { if (state.isMore) { state = state.copyWith(isLoading: true); final collectionReference = FirebaseFirestore.instance .collection("경인교대(게시글)") .orderBy("createdAt", descending: true); late QuerySnapshot<Map<String, dynamic>> querySnapshot; if (lastDocument == null) { querySnapshot = await collectionReference.limit(10).get(); } else { querySnapshot = await collectionReference .limit(10) .startAfterDocument(lastDocument!) .get(); } lastDocument = querySnapshot.docs.isEmpty ? null : querySnapshot.docs.last; state = state.copyWith( list: [...state.list, ...querySnapshot.docs.map((e) => e.data())], isLoading: false); if (querySnapshot.docs.length < 10) { state = state.copyWith(isMore: false); } } } void onScroll() { if (state.scrollController.position.pixels == state.scrollController.position.maxScrollExtent) { paginatedData(); } } void init() { paginatedData(); state.scrollController.addListener(onScroll); } @override void dispose() { state.scrollController.dispose(); super.dispose(); } }
解决方案
1. 错误原因
你在Widget的构建阶段(build方法里)调用了init(),而这个方法会触发Provider状态修改(比如paginatedData里更新list和isLoading),Flutter禁止在构建过程中修改状态,否则会导致渲染异常。
2. 正确初始化方式
方式一:在initState中调用初始化
把init()放到ConsumerState的initState方法里,这里是Widget初始化的安全阶段,不会触发构建时的状态修改错误:
class _PostingHomeState extends ConsumerState<PostingHome> { @override void initState() { super.initState(); // 获取notifier并调用初始化 ref.read(postingHomeProvider.notifier).init(); } @override Widget build(BuildContext context) { // 原build代码保持不变 return Scaffold( // ... 省略原有代码 ); } }
方式二:Provider内部自动初始化
如果不想在视图层手动调用init(),可以把初始化逻辑放到StateNotifier的构造函数里,用Future.microtask延迟执行,避免同步修改状态:
class PostingHomeStateNotifier extends StateNotifier<PostingHomeState> { PostingHomeStateNotifier() : super(PostingHomeState( scrollController: ScrollController(), list: const [], isLoading: false, isMore: true)) { // 用微任务延迟执行,避免初始化时同步修改状态 Future.microtask(() { paginatedData(); state.scrollController.addListener(onScroll); }); } // 省略其他已有方法... }
这样Provider创建时会自动完成初始化,不需要在视图层额外调用。
3. 分页逻辑优化建议
- 防止重复请求:在
paginatedData开头加判断,避免加载中或无更多数据时重复触发请求:
Future<void> paginatedData() async { // 新增判断:加载中或无更多数据时直接返回 if (state.isLoading || !state.isMore) return; state = state.copyWith(isLoading: true); // 省略原有请求逻辑... }
- 滚动监听优化:可以用
NotificationListener替代ScrollController的监听,或者在onScroll里加防抖处理,避免快速滚动时多次触发请求。
内容的提问来源于stack exchange,提问作者박진혁
相关产品推荐
相关产品推荐

