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("加载数据失败,请稍后重试")); } }, )
关键修改说明
- 颠倒条件判断:将原有的
if (!snapshot.hasData)改为if (snapshot.hasData),确保数据存在时才渲染业务UI。 - 提取变量简化代码:用
final stats = snapshot.data!;提取数据对象,避免重复写snapshot.data!。 - 空安全处理:对每个可空字段使用
?.toString() ?? "默认值",防止空值导致的异常,同时优化显示效果。 - 细化状态判断:增加
ConnectionState.waiting的判断,更精准处理加载中状态;补充错误状态的提示UI。
内容的提问来源于stack exchange,提问作者Asher
相关产品推荐
相关产品推荐

