Flutter FutureBuilder报错:initState中使用Riverpod引发依赖问题
解决Flutter FutureBuilder snapshot.data为null及Riverpod初始化错误问题
核心问题分析
- 初始化错误根源:在
initState()中调用fetchAccounts()时使用了ref.watch(),此时ConsumerState的依赖(UncontrolledProviderScope)还未完成初始化,导致请求可能获取到无效的Provider值,或Future执行异常,最终FutureBuilder无法拿到有效数据。 - 额外问题:ListView嵌套
SingleChildScrollView引发滚动冲突;搜索过滤逻辑效率低下,每个列表项单独判断过滤条件。
解决方案
方案1:调整Future初始化逻辑(兼容现有代码结构)
修改_AccountFinderState,避免在initState()中使用ref.watch(),改用ref.read()并将Future初始化移至didChangeDependencies(),同时修复滚动和过滤问题:
class _AccountFinderState extends ConsumerState<AccountFinder> { late Future<List<Account>> accounts; String searchString = ""; bool _isInitialized = false; // 标记是否已完成初始化,避免重复执行请求 // 将Provider值作为参数传入,不在方法内调用ref.watch Future<List<Account>> fetchAccounts(String userId, String companyId) async { final url = Uri.parse( 'http://api_adress/GetAllActs?CID=$companyId', ); final headers = {'Content-Type': 'application/json'}; final response = await http.post( url, headers: headers, body: json.encode({ "id": userId, "userName": "string", "password": "string", "loggedIn": true, "userActive": true, "userDeleteable": true }), ); if (response.statusCode == 200) { List<dynamic> listAccountsJson = jsonDecode(response.body); return listAccountsJson .map((account) => Account.fromJson(account)) .toList(); } else { throw Exception('Failed to load items'); } } @override void didChangeDependencies() { super.didChangeDependencies(); // 仅初始化一次,避免依赖变化时重复请求 if (!_isInitialized) { final userID = ref.read(userStateProvider).id; final companyID = ref.read(companyStateProvider).comID; accounts = fetchAccounts(userID, companyID); _isInitialized = true; } } // 刷新数据时获取最新的Provider值 void _refreshAccounts() { final userID = ref.read(userStateProvider).id; final companyID = ref.read(companyStateProvider).comID; setState(() { accounts = fetchAccounts(userID, companyID); }); } @override Widget build(BuildContext context) { return Column( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.center, children: [ // 搜索框部分保持不变 SafeArea( child: Container( height: 40, decoration: BoxDecoration( borderRadius: BorderRadius.circular(10), color: Theme.of(context) .colorScheme .onBackground .withOpacity(0.1)), margin: const EdgeInsets.symmetric(horizontal: 10, vertical: 20), child: TextField( onTapOutside: (event) => FocusManager.instance.primaryFocus?.unfocus(), onChanged: (value) { setState(() { searchString = value.toLowerCase(); }); }, style: Theme.of(context).textTheme.bodyMedium!, decoration: InputDecoration( border: InputBorder.none, hintText: AppLocalizations.of(context)!.search, hintStyle: Theme.of(context).textTheme.bodyMedium!.copyWith( fontSize: 16, fontWeight: FontWeight.w300, color: Theme.of(context) .colorScheme .onBackground .withOpacity(0.7)), prefixIcon: const Icon(Icons.search)), ), ), ), // 修改为自定义刷新方法 IconButton(onPressed: _refreshAccounts, icon: const Icon(Icons.refresh)), const SizedBox(height: 10), Expanded( child: FutureBuilder( future: accounts, builder: (context, AsyncSnapshot<List<Account>> snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { return const Center(child: CircularProgressIndicator()); } else if (snapshot.hasError) { return Center(child: Text('Error: ${snapshot.error}')); } else if (!snapshot.hasData || snapshot.data!.isEmpty) { return const Center(child: Text('No data available')); } else { // 先过滤整个列表再渲染,提升性能 final filteredAccounts = snapshot.data! .where((account) => account.actName!.toLowerCase().contains(searchString)) .toList(); return ListView.separated( shrinkWrap: true, // 解决滚动冲突,让ListView自适应高度 padding: const EdgeInsets.all(8), itemCount: filteredAccounts.length, itemBuilder: (BuildContext context, int index) { final account = filteredAccounts[index]; return Card( elevation: 3, child: ListTile( title: Text(account.actName!), subtitle: Text('${account.actMobileNo}\n${account.actPhoneNo}\n'), ), ); }, separatorBuilder: (BuildContext context, int index) { return const Divider(); }, ); } }, ), ), ], ); } }
方案2:改用FutureProvider(Riverpod最佳实践)
将异步请求封装为FutureProvider,自动管理请求状态,无需手动维护Future对象:
1. 定义FutureProvider
final accountsProvider = FutureProvider<List<Account>>((ref) async { final user = ref.watch(userStateProvider); final company = ref.watch(companyStateProvider); final url = Uri.parse('http://api_adress/GetAllActs?CID=${company.comID}'); final headers = {'Content-Type': 'application/json'}; final response = await http.post( url, headers: headers, body: json.encode({ "id": user.id, "userName": "string", "password": "string", "loggedIn": true, "userActive": true, "userDeleteable": true }), ); if (response.statusCode == 200) { List<dynamic> listAccountsJson = jsonDecode(response.body); return listAccountsJson .map((account) => Account.fromJson(account)) .toList(); } else { throw Exception('Failed to load items'); } });
2. 修改AccountFinder页面
class AccountFinder extends ConsumerStatefulWidget { const AccountFinder({super.key}); @override ConsumerState<AccountFinder> createState() => _AccountFinderState(); } class _AccountFinderState extends ConsumerState<AccountFinder> { String searchString = ""; @override Widget build(BuildContext context) { // 监听FutureProvider的状态变化 final accountsAsyncValue = ref.watch(accountsProvider); return Column( mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.center, children: [ // 搜索框部分保持不变 SafeArea( child: Container( height: 40, decoration: BoxDecoration( borderRadius: BorderRadius.circular(10), color: Theme.of(context) .colorScheme .onBackground .withOpacity(0.1)), margin: const EdgeInsets.symmetric(horizontal: 10, vertical: 20), child: TextField( onTapOutside: (event) => FocusManager.instance.primaryFocus?.unfocus(), onChanged: (value) { setState(() { searchString = value.toLowerCase(); }); }, style: Theme.of(context).textTheme.bodyMedium!, decoration: InputDecoration( border: InputBorder.none, hintText: AppLocalizations.of(context)!.search, hintStyle: Theme.of(context).textTheme.bodyMedium!.copyWith( fontSize: 16, fontWeight: FontWeight.w300, color: Theme.of(context) .colorScheme .onBackground .withOpacity(0.7)), prefixIcon: const Icon(Icons.search)), ), ), ), // 通过invalidate触发重新请求 IconButton( onPressed: () => ref.invalidate(accountsProvider), icon: const Icon(Icons.refresh) ), const SizedBox(height: 10), Expanded( child: accountsAsyncValue.when( loading: () => const Center(child: CircularProgressIndicator()), error: (error, stack) => Center(child: Text('Error: $error')), data: (accounts) { final filteredAccounts = accounts .where((account) => account.actName!.toLowerCase().contains(searchString)) .toList(); if (filteredAccounts.isEmpty) { return const Center(child: Text('No data available')); } return ListView.separated( shrinkWrap: true, padding: const EdgeInsets.all(8), itemCount: filteredAccounts.length, itemBuilder: (context, index) { final account = filteredAccounts[index]; return Card( elevation: 3, child: ListTile( title: Text(account.actName!), subtitle: Text('${account.actMobileNo}\n${account.actPhoneNo}\n'), ), ); }, separatorBuilder: (context, index) => const Divider(), ); }, ), ), ], ); } }
关键注意事项
- 禁止在
initState()、dispose()或类构造函数中使用ref.watch(),这些生命周期方法中只能用ref.read(),因为此时组件依赖未完成初始化。 - 使用
FutureProvider能更优雅地管理异步状态,自动处理加载、错误、数据更新场景,减少手动维护代码。
内容的提问来源于stack exchange,提问作者Jad Zahreddine
相关产品推荐
相关产品推荐

