Flutter开发07-网络请求与JSON转换

网络请求

Dio

官方https://github.com/flutterchina/dio

添加依赖

1
2
dependencies:
dio: ^3.0.9

导入并创建dio实例:

1
2
import 'package:dio/dio.dart';
Dio dio = Dio();

设置配置参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var dio = Dio(BaseOptions(
baseUrl: "http://httpbin.org/",
connectTimeout: 5000,
receiveTimeout: 100000,
// 5s
headers: {
HttpHeaders.userAgentHeader: "dio",
"api": "1.0.0",
},
contentType: Headers.jsonContentType,
// Transform the response data to a String encoded with UTF8.
// The default value is [ResponseType.JSON].
responseType: ResponseType.plain,
));

示例

发起 GET 请求 :

1
2
3
Response response;
response=await dio.get("/test?id=12&name=wendu")
print(response.data.toString());

对于GET请求我们可以将query参数通过对象来传递,上面的代码等同于:

1
2
response=await dio.get("/test",queryParameters:{"id":12,"name":"wendu"})
print(response);

发起一个 POST 请求:

1
response=await dio.post("/test",data:{"id":12,"name":"wendu"})

发起多个并发请求:

1
response= await Future.wait([dio.post("/info"),dio.get("/token")]);

下载文件:

1
response=await dio.download("https://www.google.com/",_savePath);

发送 FormData:

1
2
3
4
5
FormData formData = new FormData.from({
"name": "wendux",
"age": 25,
});
response = await dio.post("/info", data: formData)

如果发送的数据是FormData,则dio会将请求header的contentType设为“multipart/form-data”。

通过FormData上传多个文件:

1
2
3
4
5
6
7
8
9
10
11
12
FormData formData = new FormData.from({
"name": "wendux",
"age": 25,
"file1": new UploadFileInfo(new File("./upload.txt"), "upload1.txt"),
"file2": new UploadFileInfo(new File("./upload.txt"), "upload2.txt"),
// 支持文件数组上传
"files": [
new UploadFileInfo(new File("./example/upload.txt"), "upload.txt"),
new UploadFileInfo(new File("./example/upload.txt"), "upload.txt")
]
});
response = await dio.post("/info", data: formData)

值得一提的是,dio内部仍然使用HttpClient发起的请求,所以代理、请求认证、证书校验等和HttpClient是相同的,我们可以在onHttpClientCreate回调中设置,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
(dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate = (client) {
//设置代理
client.findProxy = (uri) {
return "PROXY 192.168.1.2:8888";
};
//校验证书
httpClient.badCertificateCallback=(X509Certificate cert, String host, int port){
if(cert.pem==PEM){
return true; //证书一致,则允许发送数据
}
return false;
};
};

注意,onHttpClientCreate会在当前dio实例内部需要创建HttpClient时调用,所以通过此回调配置HttpClient会对整个dio实例生效,如果你想针对某个应用请求单独的代理或证书校验策略,可以创建一个新的dio实例即可。

Retrofit For Dart

官网地址https://pub.flutter-io.cn/packages/retrofit

添加依赖

1
2
3
4
5
6
7
8
dependencies:
retrofit: ^1.3.4
logger: ^0.9.1

dev_dependencies:
retrofit_generator: ^1.3.4+2
build_runner: ^1.10.0
json_serializable: ^3.2.0

注意

json_serializable必须要添加 否则实体类不会自动生成

一般步骤为:

(1) 引入json_annotation,即插入

1
import 'package:json_annotation/json_annotation.dart';

(2) 指定此类的生成代码,

1
part '类名.g.dart';

(3) 添加序列化标注,注意括号

1
@JsonSerializable()

(4) 新建类和属性(字段)以及带参构造方法,注意属性类型。

(5) 添加反序列化和序列化方法fromJsontoJson

1
2
factory 类名.fromJson(Map<String, dynamic> json) =>_$类名FromJson(json);
Map<String, dynamic> toJson() => _$类名ToJson(this);

(6) 生成文件

1
flutter pub run build_runner build

Mac上已生成文件无法覆盖,可以按如下命令操作

1
flutter pub run build_runner build --delete-conflicting-outputs

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import 'package:json_annotation/json_annotation.dart';

part 'ResultVo.g.dart';

@JsonSerializable()
class ResultVo {
int code;
String msg;
dynamic obj;

ResultVo({this.code, this.msg, this.obj});

factory ResultVo.fromJson(Map<String, dynamic> json) => _$ResultVoFromJson(json);
Map<String, dynamic> toJson() => _$ResultVoToJson(this);
}

接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import 'package:retrofit/retrofit.dart';
import 'package:dio/dio.dart';
import 'package:qggj_android/model/ResultVo.dart';

part 'RestClient.g.dart';

@RestApi(baseUrl: "http://192.168.6.200:31199/")
abstract class RestClient {
factory RestClient(Dio dio, {String baseUrl}) = _RestClient;

@POST("/teacherSign/login")
Future<ResultVo> teacher_login(@Field() String account,@Field() String password);
}

final dio = Dio(); // Provide a dio instance
final client = RestClient(dio);
final logger = Logger();

调用

1
2
3
4
5
6
7
8
9
client
.teacher_login(this.username, this.userpwd)
.then((value) => {login(value)})
.catchError((err) => {
ToastHelper.showToast(context, "请求失败!")
})
.whenComplete(
() => LoadingHelper.hideLoading(context));
},

官方示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
@GET("/tasks/{id}")
Future<Task> getTask(@Path("id") String id);

@GET('/demo')
Future<String> queries(@Queries() Map<String, dynamic> queries);

@GET("https://httpbin.org/get")
Future<String> namedExample(
@Query("apikey") String apiKey,
@Query("scope") String scope,
@Query("type") String type,
@Query("from") int from
);

@POST("/tasks")
Future<Task> createTask(@Body() Task task);

@POST("/teacherSign/login")
Future<ResultVo> teacher_login(@Field() String account,@Field() String password);

@POST("http://httpbin.org/post")
Future<void> createNewTaskFromFile(@Part() File file);

@POST("http://httpbin.org/post")
@FormUrlEncoded()
Future<String> postUrlEncodedFormData(@Field() String hello);

@PATCH("/tasks/{id}")
Future<Task> updateTaskPart(@Path() String id, @Body() Map<String, dynamic> map);

@PUT("/tasks/{id}")
Future<Task> updateTask(@Path() String id, @Body() Task task);

@DELETE("/tasks/{id}")
Future<void> deleteTask(@Path() String id);

JSON解析

Dart不支持带有泛型的实体类直接转换。

读取本地JSON文件

比如要读取项目根目录下的assets/person.json

首先要在 pubspec.yaml 中做如下配置:

1
2
3
4
5
flutter:
uses-material-design: true
# 资源文件配置
assets:
- assets/person.json

导入如下几个依赖库:

1
2
3
4
5
6
// 使用该库中的 rootBundle 对象来读取 perosn.json 文件
import 'package:flutter/services.dart';
// json
import 'dart:convert';
// 异步 Future
import 'dart:async';

实体类

1
2
3
4
5
6
7
8
9
10
11
class Person {
String name;
int age;
double height;

Person({this.name, this.age, this.height});

factory Person.fromJson(Map<String, dynamic> json) {
return Person(name: json['name'], age: json['age'], height: json['height']);
}
}

读取转换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import 'package:flutter/services.dart';
import 'dart:convert';
import 'dart:async';
import '../models/person.dart';

// 读取 assets 文件夹中的 person.json 文件
Future<String> _loadPersonJson() async {
return await rootBundle.loadString('assets/person.json');
}

// 将 json 字符串解析为 Person 对象
Future<Person> decodePerson() async {
// 获取本地的 json 字符串
String personJson = await _loadPersonJson();

// 解析 json 字符串,返回的是 Map<String, dynamic> 类型
final jsonMap = json.decode(personJson);
print('jsonMap runType is ${jsonMap.runtimeType}');
Person person = Person.fromJson(jsonMap);
print(
'person name is ${person.name}, age is ${person.age}, height is ${person.height}');
return person;
}

JSON和Map互转

添加引用

1
import 'dart:convert';

JSON字符串转Map

1
2
3
4
Map<String, dynamic> user = JSON.decode(json);

print('Howdy, ${user['name']}!');
print('We sent the verification link to ${user['email']}.');

对象转JSON字符串

1
String json = JSON.encode(user);

JSON字符串转Model类

简单对象转换

json

1
2
3
4
5
{
"name": "jack",
"age": 18,
"height": 175.0
}

实体类

1
2
3
4
5
6
7
8
9
10
11
class Person {
String name;
int age;
double height;

Person({this.name, this.age, this.height});

factory Person.fromJson(Map<String, dynamic> json) {
return Person(name: json['name'], age: json['age'], height: json['height']);
}
}

调用

1
2
3
final jsonMap = json.decode(personJson);
print('jsonMap runType is ${jsonMap.runtimeType}');
Person person = Person.fromJson(jsonMap);

输出如下

1
jsonMap runType is _InternalLinkedHashMap<String, dynamic>

可以看出 json.decode(personJson) 方法返回的类型为 _InternalLinkedHashMap ,意思就是这个 Map 的 key 为 String 类型,而 value 的类型为 dynamic 的,也就是动态的

数组的转换

1
2
3
4
5
6
7
8
9
10
[
{
"id": 1,
"name": "Jack"
},
{
"id": 2,
"name": "Rose"
}
]

实体类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class MemberList {  
List<Member> memberList;

MemberList({this.memberList});

factory MemberList.fromJson(List<dynamic> listJson) {

List<Member> memberList =
listJson.map((value) => Member.fromJson(value)).toList();

return MemberList(memberList: memberList);
}
}

class Member {
int id;
String name;

Member({this.id, this.name});

factory Member.fromJson(Map<String, dynamic> json) {
return Member(id: json['id'], name: json['name']);
}
}

调用

1
2
3
4
List<dynamic> list = json.decode(memberListJson);
MemberList memberList = MemberList.fromJson(list);
memberList.memberList
.forEach((member) => print('member name is ${member.name}'));

带有数组的对象转换

1
2
3
4
5
6
7
{
"name": "China",
"cities": [
"Beijing",
"Shanghai"
]
}

实体类

1
2
3
4
5
6
7
8
9
10
11
12
class Country {  
String name;
List<String> cities;

Country({this.name, this.cities});

factory Country.fromJson(Map<String, dynamic> json) {
var originList = json['cities'];
List<String> cityList = new List<String>.from(originList);
return Country(name: json['name'], cities: cityList);
}
}

调用

1
2
Map<String, dynamic> jsonMap = json.decode(countryJson);
Country country = Country.fromJson(jsonMap);

复杂的对象数组嵌套

json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"id": "0302",
"class_name": "三年二班",
"students": [
{
"name": "叶湘伦",
"sex": "男"
},
{
"name": "路小雨",
"sex": "女"
}
]
}

实体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class ClassInfo {
String id;
String name;
List<Student> studentList;

ClassInfo({this.id, this.name, this.studentList});

factory ClassInfo.fromJson(Map<String, dynamic> json) {
final originList = json['students'] as List;
List<Student> studentList =
originList.map((value) => Student.fromJson(value)).toList();
return ClassInfo(id: json['id'], name: json['class_name'], studentList: studentList);
}
}

class Student {
String name;
String sex;

Student({this.name, this.sex});

factory Student.fromJson(Map<String, dynamic> json) {
return Student(name: json['name'], sex: json['sex']);
}
}

调用

1
2
3
4
5
Map<String, dynamic> jsonMap = json.decode(classInfoJson);

ClassInfo classInfo = ClassInfo.fromJson(jsonMap);
classInfo.studentList
.forEach((student) => print('student name is ${student.name}'));

使用插件生成实体类

FlutterJsonBeanFactory插件

除了上面的方式外,我们还可以使用FlutterJsonBeanFactory插件来辅助生成Bean类。
安装FlutterJsonBeanFactory插件很简单,

以Android Studio为例,

依次选择【Android Studio】->【Settings】->【Plugins】,然后搜索FlutterJsonBeanFactory插件安装即可

使用方式

在要生成文件的文件夹上右键New -> dart bean class File from JSON

该插件转换要求JSON的最外层为对象,不能为数组,为数组时无法转换。

在线生成实体类

https://www.psvmc.cn/zjtools/json_dart/index.html

无法抓包

设置Http代理

默认Flutter网络请求是无法通过抓包软件抓包的,如果需要抓包按如下配置

1
2
3
4
5
6
7
8
9
10
11
12
import 'package:dio/dio.dart';
import 'package:dio/adapter.dart';
...
(dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate = (client) {
// config the http client
client.findProxy = (uri) {
//proxy all request to localhost:8888
return "PROXY localhost:8888";
};
// you can also create a HttpClient to dio
// return HttpClient();
};