Flutter中SliverGrid结合FutureBuilder遇snapshot未定义与索引越界问题
问题分析与解决方案
核心问题
- 未定义
snapshot错误:childCount: snapshot.data.length中的snapshot是FutureBuilder内部的局部变量,外部无法直接访问,导致编译报错。 - 索引越界异常:注释
childCount后,SliverChildBuilderDelegate会无限创建列表项,当索引超过实际数据长度时触发RangeError;同时每个列表项单独调用fetchCountries()会重复发起API请求,既浪费资源又可能导致数据不同步。
修复方案
将FutureBuilder移到SliverGrid外层,先一次性获取所有国家数据,再基于拿到的数据构建网格列表,既能正确设置childCount,又避免重复请求API。
修改后的完整代码
class CountryListScreen extends StatelessWidget { final String prefLang; const CountryListScreen({super.key, required this.prefLang}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( leading: BackButton( onPressed: () => Navigator.of(context).pop(), ), title: const Text(AppConstants.appName), elevation: 2), body: SafeArea( child: CustomScrollView( slivers: [ showCountries(), ], ), ), ); } Widget showCountries() { // 将FutureBuilder放在SliverGrid外层,统一获取数据 return FutureBuilder( future: fetchCountries(), builder: (context, AsyncSnapshot<List<Country>> snapshot) { if (!snapshot.hasData) { // 数据加载中显示加载指示器 return const SliverFillRemaining( child: Center(child: CircularProgressIndicator()), ); } else if (snapshot.hasError) { // 加载出错时显示错误提示 return const SliverFillRemaining( child: Center(child: Text('加载数据失败')), ); } else { // 数据加载完成,构建SliverGrid return SliverGrid( gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 3), delegate: SliverChildBuilderDelegate( (BuildContext context, int index) { return _buildCountryFlag(snapshot.data![index], context); }, // 使用获取到的数据长度作为childCount childCount: snapshot.data!.length, ), ); } }, ); } Future<List<Country>> fetchCountries() async { final response = await Utilities().consumeApi('/countries?language=$prefLang'); return (json.decode(response.body) as List) .map((i) => Country.fromJson(i)) .toList(); } Widget _buildCountryFlag(Country country, BuildContext context) { return InkWell( onTap: () { // 点击事件逻辑 }, child: _FlagItem( flag: CountryFlag.fromCountryCode(country.code), countryCode: country.code, countryName: country.name), ); } } class _FlagItem extends StatelessWidget { const _FlagItem( {required this.flag, required this.countryCode, required this.countryName}); final CountryFlag flag; final String countryCode; final String countryName; @override Widget build(BuildContext context) { return Column( children: [ const SizedBox(height: 10), Expanded(child: flag), const SizedBox(height: 8), Expanded( child: Text( countryName, style: Theme.of(context).textTheme.titleMedium, ), ), ], ); } }
关键修改点
- 把
FutureBuilder包裹在SliverGrid外层,确保整个列表仅发起一次API请求。 - 在
FutureBuilder的builder回调中,分别处理加载中、加载错误、加载完成三种状态。 - 加载完成后,用
snapshot.data!.length作为childCount,彻底避免索引越界问题。 - 列表项直接使用已获取的数据,不再单独发起请求,提升性能和数据一致性。
内容的提问来源于stack exchange,提问作者Sundarrajan Rajagopal
相关产品推荐
相关产品推荐

