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

Flutter FutureBuilder空值检查运算符使用错误求助

解决Flutter FutureBuilder空指针错误:Null check operator used on a null value

错误根源

你的代码里FutureBuilder的条件判断逻辑完全颠倒了:

  • 当snapshot.hasData为false(数据未加载完成/加载失败)时,你试图渲染需要依赖snapshot.data的PieChart和数据列表,还通过!强制解包空值,直接触发空指针错误。
  • 当数据加载完成(snapshot.hasData为true)时,反而显示加载动画,完全不符合预期逻辑。

修复方案

1. 修正条件判断逻辑

把if (!snapshot.hasData)和else的内容互换,确保只有当数据存在时才渲染业务UI,否则显示加载状态。

2. 处理可空字段的安全访问

由于WorldStatsModel的所有字段都是可空类型,直接调用toString()可能会得到"null"字符串,建议用**空合并运算符??**给字段设置默认值,避免显示不友好的内容。

3. 补充错误状态处理(推荐)

增加对snapshot.hasError的判断,当API请求失败时显示错误提示,提升用户体验。

修正后的完整FutureBuilder代码

FutureBuilder(
  future: statsServices.fetchWorldStatesRecords(),
  builder: (context, AsyncSnapshot<WorldStatsModel> snapshot) {
    // 数据加载完成,渲染业务UI
    if (snapshot.hasData) {
      final stats = snapshot.data!;
      return Column(
        children: [
          PieChart(
            dataMap: {
              'cases': double.parse(stats.cases?.toString() ?? "0"),
              'recovered': double.parse(stats.recovered?.toString() ?? "0"),
              'deaths': double.parse(stats.deaths?.toString() ?? "0"),
            },
            chartValuesOptions: const ChartValuesOptions(
                showChartValuesInPercentage: true),
            chartRadius: MediaQuery.of(context).size.width / 3.2,
            legendOptions: const LegendOptions(
                legendPosition: LegendPosition.left),
            animationDuration: const Duration(seconds: 3),
            chartType: ChartType.ring,
            colorList: colorlist,
          ),
          const SizedBox(height: 15),
          Card(
            child: Column(children: [
              ReusableRow(title: 'updated', value: stats.updated?.toString() ?? "0"),
              ReusableRow(title: 'cases', value: stats.cases?.toString() ?? "0"),
              ReusableRow(title: 'todayCases', value: stats.todayCases?.toString() ?? "0"),
              ReusableRow(title: 'deaths', value: stats.deaths?.toString() ?? "0"),
              ReusableRow(title: 'todayDeaths', value: stats.todayDeaths?.toString() ?? "0"),
              ReusableRow(title: 'recovered', value: stats.recovered?.toString() ?? "0"),
              ReusableRow(title: 'todayRecovered', value: stats.todayRecovered?.toString() ?? "0"),
              ReusableRow(title: 'active', value: stats.active?.toString() ?? "0"),
              ReusableRow(title: 'critical', value: stats.critical?.toString() ?? "0"),
              ReusableRow(title: 'casesPerOneMillion', value: stats.casesPerOneMillion?.toString() ?? "0"),
              ReusableRow(title: 'deathsPerOneMillion', value: stats.deathsPerOneMillion?.toString() ?? "0.0"),
              ReusableRow(title: 'tests', value: stats.tests?.toString() ?? "0"),
              ReusableRow(title: 'testsPerOneMillion', value: stats.testsPerOneMillion?.toString() ?? "0.0"),
              ReusableRow(title: 'population', value: stats.population?.toString() ?? "0"),
              ReusableRow(title: 'affectedCountries', value: stats.affectedCountries?.toString() ?? "0"),
              ReusableRow(title: 'recoveredPerOneMillion', value: stats.recoveredPerOneMillion?.toString() ?? "0.0"),
            ]),
          ),
          const SizedBox(height: 20),
          GestureDetector(
            onTap: () {
              Navigator.push(
                context,
                MaterialPageRoute(builder: (context) => const CountryList()),
              );
            },
            child: Container(
              height: 50,
              width: 300,
              decoration: BoxDecoration(
                  color: const Color(0xff1aa260),
                  borderRadius: BorderRadius.circular(10),
                  boxShadow: const [
                    BoxShadow(color: Colors.black, blurRadius: 2)
                  ]),
              child: const Center(
                child: Text(
                  'Track Country',
                  style: TextStyle(
                      fontSize: 15,
                      fontWeight: FontWeight.bold,
                      color: Colors.black),
                ),
              ),
            ),
          ),
          const SizedBox(height: 10),
        ],
      );
    } 
    // 数据加载中,显示加载动画
    else if (snapshot.connectionState == ConnectionState.waiting) {
      return Center(
        child: SpinKitFadingCircle(
          color: Colors.black,
          size: 50,
          controller: controller,
        ),
      );
    } 
    // 请求出错,显示错误提示
    else {
      return const Center(child: Text("加载数据失败,请稍后重试"));
    }
  },
)

关键修改说明

  1. 颠倒条件判断:将原有的if (!snapshot.hasData)改为if (snapshot.hasData),确保数据存在时才渲染业务UI。
  2. 提取变量简化代码:用final stats = snapshot.data!;提取数据对象,避免重复写snapshot.data!。
  3. 空安全处理:对每个可空字段使用?.toString() ?? "默认值",防止空值导致的异常,同时优化显示效果。
  4. 细化状态判断:增加ConnectionState.waiting的判断,更精准处理加载中状态;补充错误状态的提示UI。

内容的提问来源于stack exchange,提问作者Asher

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.17 21:01:01