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

Flutter从API获取JSON遇空值错误,请求排查模型及代码问题

Flutter API数据获取与轮播展示问题

我在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.16 19:55:18