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

Flutter中SliverGrid结合FutureBuilder遇snapshot未定义与索引越界问题

问题分析与解决方案

核心问题

  1. 未定义snapshot错误:childCount: snapshot.data.length中的snapshot是FutureBuilder内部的局部变量,外部无法直接访问,导致编译报错。
  2. 索引越界异常:注释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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 12:10:56