Flutter从API获取JSON遇空值错误,请求排查模型及代码问题
我在Flutter中尝试从指定API获取JSON数据并在轮播组件中展示,目前遇到空值问题,报错提示:Null is not subtype of String and nullcheck is used on NULL value。以下是我的代码,请帮忙排查模型是否匹配API结构,以及问题出在哪里。
Phone模型类
class Phone{ int? id; bool? is_new; String? title; String? subtitle; String? picture; bool? is_buy; dynamic best_seller; int? price_without_discount; int? discount_price; Phone({ this.id, this.is_new, this.title, this.subtitle, this.picture, this.is_buy, this.best_seller, this.price_without_discount, this.discount_price }); factory Phone.fromJson(Map <String, dynamic> json) => Phone( id: json[MyPhoneKeys.id], is_new: json[MyPhoneKeys.is_new], title: json[MyPhoneKeys.title], subtitle: json[MyPhoneKeys.subtitle], picture: json[MyPhoneKeys.picture], is_buy: json[MyPhoneKeys.is_buy], best_seller: json[MyPhoneKeys.best_seller], price_without_discount: json[MyPhoneKeys.price_without_discount], discount_price: json[MyPhoneKeys.discount_price] ); }
数据展示页面
class CarouselSliderData extends StatefulWidget{ const CarouselSliderData({super.key}); @override State<CarouselSliderData> createState() => CarouselSliderDataState(); } class CarouselSliderDataState extends State<CarouselSliderData> { int? id; bool? is_new; String? title; String? subtitle; dynamic picture; bool? is_buy; dynamic best_seller; int? price_without_discount; int? discount_price; late Future<dynamic> phoneSpec; @override void initState() { phoneSpec = MyApiService().getDataMocky(); super.initState(); } @override Widget build(BuildContext context) { return FutureBuilder<dynamic>( future: phoneSpec, builder: (context, snapshot) { return Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ Text(snapshot.data!.id), Text(snapshot.data!.is_new), Text(snapshot.data!.title), Text(snapshot.data!.subtitle), Text(snapshot.data!.picture), Text(snapshot.data!.is_buy), Text(snapshot.data!.best_seller), Text(snapshot.data!.price_without_discount), Text(snapshot.data!.discount_price), ], ); } ); } Future<dynamic> getData() async{ return await MyApiService().getDataMocky().then((value) async{ if(value != null){ setState((){ id = value!.id!; is_new = value!.is_new!; title = value!.title!; subtitle = value!.subtitle!; picture = value!.picture!; is_buy = value!.is_buy!; best_seller = value!.best_seller!; price_without_discount = value!.price_without_discount!; discount_price = value!.discount_price!; }); return value; } }, );/*.catchError((_){ throw Exception("Exception is caught"); });*/ } }
API请求服务类
import 'dart:convert'; import 'package:http/http.dart' as http; import 'package:my_work/apiService/model.dart'; class MyApiService{ Future<dynamic> getDataMocky() async { final response = await http.get( Uri.parse('https://run.mocky.io/v3/654bd15e-b121-49ba-a588-960956b15175') ); if(response.statusCode == 200){ return Phone.fromJson(json.decode(response.body)[1]); } return Exception(); } }
问题分析与解决
1. 模型与API结构匹配问题
该API返回数组结构,每个元素的字段类型和你的Phone模型定义是匹配的,但核心问题是**MyPhoneKeys的键名必须和API返回的字段名完全一致**。比如API返回字段为is_new、best_seller,你需要确保MyPhoneKeys.is_new对应字符串"is_new",MyPhoneKeys.best_seller对应字符串"best_seller",否则json[key]会返回null,直接引发空值错误。
2. FutureBuilder状态处理缺失
你的FutureBuilder没有处理三种关键状态:
- 请求加载中:直接访问
snapshot.data!会触发空值错误 - 请求失败:未展示错误UI
- 数据为空:强制解包
!会引发空值异常
3. Text组件类型不匹配
Text组件仅接受String类型,但你直接传入了int、bool类型的值(比如id是数字、is_new是布尔值),这会导致类型转换错误,也就是报错提示的Null is not subtype of String(本质是非String类型无法直接传给Text)。
4. API服务类错误返回逻辑错误
当响应状态码非200时,你直接返回Exception()对象,而非抛出异常,导致FutureBuilder无法正确捕获错误状态。
修复方案
1. 确保MyPhoneKeys键名正确
定义常量类保证键名和API字段完全一致:
class MyPhoneKeys { static const String id = "id"; static const String is_new = "is_new"; static const String title = "title"; static const String subtitle = "subtitle"; static const String picture = "picture"; static const String is_buy = "is_buy"; static const String best_seller = "best_seller"; static const String price_without_discount = "price_without_discount"; static const String discount_price = "discount_price"; }
2. 完善FutureBuilder状态处理
修改build方法中的FutureBuilder:
@override Widget build(BuildContext context) { return FutureBuilder<Phone?>( future: phoneSpec, builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { return const Center(child: CircularProgressIndicator()); } if (snapshot.hasError) { return Center(child: Text('请求失败: ${snapshot.error}')); } final phone = snapshot.data; if (phone == null) { return const Center(child: Text('未获取到数据')); } return Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ Text(phone.id.toString()), Text(phone.is_new.toString()), Text(phone.title ?? '无标题'), Text(phone.subtitle ?? '无副标题'), Text(phone.picture ?? '无图片链接'), Text(phone.is_buy.toString()), Text(phone.best_seller.toString()), Text(phone.price_without_discount.toString()), Text(phone.discount_price.toString()), ], ); }, ); }
3. 修正API服务类错误处理
改为抛出异常,并指定明确的返回类型:
class MyApiService{ Future<Phone?> getDataMocky() async { final response = await http.get( Uri.parse('https://run.mocky.io/v3/654bd15e-b121-49ba-a588-960956b15175') ); if(response.statusCode == 200){ final List<dynamic> dataList = json.decode(response.body); if(dataList.isNotEmpty && dataList.length > 1){ return Phone.fromJson(dataList[1]); } return null; } throw Exception('请求失败,状态码: ${response.statusCode}'); } }
4. 移除冗余方法
getData方法未被使用,且内部强制解包会增加空值风险,直接删除即可。
内容的提问来源于stack exchange,提问作者Edgar A8

