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
相关产品推荐
相关产品推荐

