You need to enable JavaScript to run this app. Flutter请求API时拼接Token到URL出现空值报错及模型解析异常如何解决
优惠活动
大模型
产品
解决方案
定价
更多

Flutter请求API时拼接Token到URL出现空值报错及模型解析异常如何解决

代码问题拆解与修正方案

现有代码的核心疏漏

  • Token读取逻辑空转:APIService.get方法里调用了storage.read(key: "api_token"),但读取结果没有赋值给任何变量,完全没有参与请求构造,白读了一次存储。
  • 存储渠道不统一:APIService里用FlutterSecureStorage读Token,页面端又用SharedPreferences读同一个key的Token,两个存储引擎的存储区域完全隔离,就算存了Token也读不到对方写入的内容。
  • Token携带方式不规范:尝试把Token直接拼到URL里,既不符合绝大多数API的校验规范,还容易因为URL编码问题、日志泄漏引发报错,行业通用做法是把Token放在请求头的Authorization字段传递。
  • 接口返回值无兜底:get方法仅在HTTP状态码为200时返回解析后的JSON,其余状态仅打印日志、不返回任何值,调用方拿到的是null值,把null传给模型的fromJson方法时,就会直接触发「无法对空值执行调用操作」的空指针错误。
  • 模型可空性不匹配:模型里定义的所有字段都是非空String类型,但实际接口返回的字段大概率存在空值(比如用户未填写的简介、性别字段),一旦解析到空值就会触发类型不匹配报错。

修正后的参考实现

修正后的APIService

class APIService {
  String baseurl = "https://b2all.live/api";
  var log = Logger();
  FlutterSecureStorage storage = const FlutterSecureStorage();

  // 统一封装GET请求,自动携带身份Token
  Future<dynamic> get(String url) async {
    // 读取存储的Token,赋值给变量参与请求构造
    final String? apiToken = await storage.read(key: "api_token");
    final String fullUrl = formater(url);
    // 构造通用请求头,携带身份凭证
    final Map<String, String> requestHeaders = {
      'Content-Type': 'application/json',
      if (apiToken != null) 'Authorization': 'Bearer $apiToken',
    };

    try {
      final response = await http.get(
        Uri.parse(fullUrl),
        headers: requestHeaders,
      );
      log.i("请求地址:$fullUrl, 状态码:${response.statusCode}, 返回内容:${response.body}");
      // 200状态正常返回解析后的JSON
      if (response.statusCode == 200) {
        return jsonDecode(response.body);
      }
      // 非200状态主动抛出可捕获的异常,避免静默返回null
      throw Exception("请求失败,状态码:${response.statusCode}, 错误信息:${response.body}");
    } catch (e) {
      log.e("请求异常:$e");
      rethrow;
    }
  }

  String formater(String url) {
    return baseurl + url;
  }
}

修正后的页面请求逻辑

@override
void initState() {
  super.initState();
  fetchData();
}

void fetchData() async {
  try {
    // 不需要在页面端单独读取Token,统一交给APIService层处理
    final response = await networkHandler.get(
        "/profile/DvIXUN1CAgYMQzKri0dx3XVxfcABDjbPEg1QyHVD4vu1pQM8pRF56OjLgVW8");
    setState(() {
      // 确认response为有效对象后再做模型解析
      profileModelFix = ProfileModelFix.fromJson(response);
      circular = false;
    });
  } catch (e) {
    // 捕获全链路异常,避免页面无响应、空值传参
    setState(() {
      circular = false;
    });
    log.e("数据加载失败:$e");
  }
}

修正后的数据模型

import 'package:flutter/foundation.dart';
import 'package:json_annotation/json_annotation.dart';

part 'profileModelFix.g.dart';

@JsonSerializable()
class ProfileModelFix {
  // 所有字段标记为可空,匹配接口返回空值的常见场景
  String? name;
  String? user_name;
  String? dob;
  String? phone;
  String? gender;
  String? bio;
  String? api_token;

  ProfileModelFix({
    this.name, 
    this.user_name, 
    this.dob, 
    this.phone, 
    this.gender, 
    this.bio, 
    this.api_token,
  });

  factory ProfileModelFix.fromJson(Map<String, dynamic> json) =>
      _$ProfileModelFixFromJson(json);
  Map<String, dynamic> toJson() => _$ProfileModelFixToJson(this);
}

排查注意事项

  • 统一Token存储渠道:二选一使用FlutterSecureStorage或SharedPreferences存储Token,不要两边重复读写,避免出现存储值不匹配的问题。
  • 模型代码修改完成后,重新执行flutter pub run build_runner build重新生成.g.dart解析文件,否则旧的解析逻辑还是会触发空值报错。
  • 如果仍出现解析错误,先打印接口实际返回的JSON内容,对比字段名和模型定义是否完全一致,避免因为字段拼写不匹配导致解析出null值。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.11.10 04:15:03