原理
因为 tree shaking,Flutter 禁用了 Dart 的反射,Flutter 禁用了 Dart 的反射,Flutter 禁用了 Dart 的反射,重要的事情说三遍,所以 Flutter 无法用 JSON decode 泛型 model,解决方法是使用 json_serializable 把响应基类先 decode,并把响应数据类型 decode 为 Map<String, dynamic>
,在需要使用响应数据类型的地方再调用实际类型的 fromJson
序列化。
HTTP 接口
接口请求参数样例为,请求方式为 POST Request Body:{"countryCode": "86", "telNo": "13227753101"}
接口响应参数样例为:{"code": 0, "msg": "ok", "data": {"registered": true}}
Flutter 解析
依赖 pubspec.yaml
:
...
dependencies:
...
http: ^0.12.2
json_annotation: ^3.1.1
dev_dependencies:
...
build_runner: ^1.11.0
json_serializable: ^3.5.1
...
编译前置脚本 build.sh
:
#!/bin/bash
############################################################################
## 项目编译前置脚本
## 使用:chmod +x build.sh && ./build.sh
############################################################################
echo 'Start build music_hub_frontend'
# 拉取依赖
flutter pub get
# 生成 model 类
cd lib/model/
flutter packages pub run build_runner build
cd -
echo 'Build success'
所有 model 类均放置在 lib/model
下。
响应基类 lib/model/resp.dart
:
import 'package:json_annotation/json_annotation.dart';
part 'resp.g.dart';
/// 接口响应模型基类
/// json_serializable 不支持泛型
/// 所以只能把实际数据丢成 Map<String, dynamic> 在实际使用的地方再解析一遍
///
/// @author seliote
/// @since 2021-01-25
@JsonSerializable()
class Resp {
// 请求响应状态码,不可为 null
@JsonKey(name: "code")
int code;
// 请求响应状态码描述,不可为 null
@JsonKey(name: "msg")
String msg;
// 请求响应实际数据,可能为 null
@JsonKey(name: "data")
Map<String, dynamic> data;
Resp(this.code, this.msg, this.data);
factory Resp.fromJson(Map<String, dynamic> json) => _$RespFromJson(json);
Map<String, dynamic> toJson() => _$RespToJson(this);
/// 判断响应对应的操作是否成功
bool isSuccess() => code == 0;
/// 获取响应的显示信息
String displayMsg() {
// 没用国际化
switch (code) {
case 0:
return "成功";
case -1000:
return "未知异常";
case -1001:
return "请求地址异常";
case -1002:
return "请求参数异常";
default:
return "信息未配置";
}
}
}
响应数据类型 lib/model/user/registered_status.dart
:
import 'dart:core';
import 'package:json_annotation/json_annotation.dart';
part 'registered_status.g.dart';
/// /user/registered_status 实体类
///
/// @author seliote
/// @since 2021-01-23
/// /user/registered_status 请求实体类
@JsonSerializable()
class RegisteredStatusReq {
@JsonKey(name: "country_code")
String countryCode;
@JsonKey(name: "tel_no")
String telNo;
RegisteredStatusReq(this.countryCode, this.telNo);
factory RegisteredStatusReq.fromJson(Map<String, dynamic> json) =>
_$RegisteredStatusReqFromJson(json);
Map<String, dynamic> toJson() => _$RegisteredStatusReqToJson(this);
}
/// /user/registered_status 响应实体类
@JsonSerializable()
class RegisteredStatusResp {
@JsonKey(name: "registered")
bool registered;
RegisteredStatusResp(this.registered);
factory RegisteredStatusResp.fromJson(Map<String, dynamic> json) =>
_$RegisteredStatusRespFromJson(json);
Map<String, dynamic> toJson() => _$RegisteredStatusRespToJson(this);
}
项目根目录下执行编译前置脚本:chmod +x build.sh && ./build.sh
,此时 JSON 解析格式已经完成,接下来是使用部分。
HTTP 请求封装 lib/util/backend_utils.dart
:
import 'dart:convert';
import 'package:frontend/model/resp.dart';
import 'package:http/http.dart' as http;
/// 后台相关工具
///
/// @author seliote
/// @since 2021-01-23
const String SCHEME = "http";
// 服务器应用部署 URL
const String SERVER_URL = "localhost";
// 服务器端口
const int SERVER_PORT = 80;
/// Post 方式发送 request body
///
/// [path] URL 文件路径部分
/// [reqBody] POST 请求实体部分
Future<Resp> post<T>(String path, Object reqBody) async {
Map<String, String> headers = {"Content-Type": "application/json"};
var url = "$SCHEME://$SERVER_URL:$SERVER_PORT$path";
var resp = await http.post(url, headers: headers, body: json.encode(reqBody));
var respModel = Resp.fromJson(json.decode(resp.body));
return respModel;
}
实际请求代码:
...
var next = FlatButton(
onPressed: () async {
var resp = await post<RegisteredStatusResp>(
"/user/registered_status",
RegisteredStatusReq(
countryCode.data, textEditingController.text));
if (resp.isSuccess()) {
print("is registered: "
"${RegisteredStatusResp.fromJson(resp.data).registered}");
} else {
print(resp.displayMsg());
}
},
...