欢迎点赞,转载请注明出处
本节示例项目源代码下载点击这里 flutter_http
一个有价值的应用离不开与网络数据的交换。
Android设备进行网络通信时,需要在AndroidManifest.xml文件里增加网络使用权限android.permission.INTERNET。
<manifest xmlns:android...>
...
<uses-permission android:name="android.permission.INTERNET" />
<application ...
</manifest>
AndroidManifest.xml文件一般存在于android/app/src/main目录之下。
获取网络数据
http插件为提供了获取网络数据最简单的方法,使用前在pubspec.yaml中需要增加依赖http: 0.12.0+4。我们对下面官方的示例代码进行解析说明。

- 第35行 Future<Album> futureAlbum定义了一个Future类型变量,它表示将来要获取的一个Album对象值或将来的某个错误。
- 第40行 futureAlbum = fetchAlbum()是一个异步调用,因为我们需要获取网络数据,这个过程往往会比较耗时,如果是同步等则导致界面不能及时绘制呈现。
- 第6行 final response = await http.get('jsonplaceholder.typicode.com/albums/1'),同步等待一个HTTP GET请求响应。response的类型是Future<http.Response>,http.Response包含成功的http请求接收到的数据。我们可以在浏览器直接输入示例地址,观察到请求和响应细节:
请求响应返回的是application/json类型的对象:
{
"userId": 1,
"id": 1,
"title": "quidem molestiae enim"
}
- 第9行 Album.fromJson(json.decode(response.body)),dart:convert包的json.decode函数负责把json对象转换成对应的类型,本例是转换为Map类型。然后再通过Album.fromJson命名构造函数,将Map对象转换为Album Dart类对象。注意Album.fromJson是factory类型,Map的值类型为dynamic。
- 第54行到第65行定义了一个FutureBuilder。FutureBuilder也是一个Widget,它的future参数作为要绘制的子Widget的异步数据源,build参数包含了是要绘制的子Widget和Future对象的快照信息。如果你熟悉前面一章的StreamBuilder,则对这种使用形式并不陌生。Future和Stream区别在于Future 只能返回一个单独的异步响应,而 Stream 类可以随着时间的推移传递很多事件。
- 第63行 CircularProgressIndicator是一个圆形动画进度指示器的Material Design,它通过循环旋转来表示应用的繁忙状态。
- 为了进一步理解网络请求的过程,我们特意在代码的第7,第38和第57增加了print语句,可以看到如下输出:
flutter: initState
flutter: AsyncSnapshot<Album>(ConnectionState.waiting, null, null)
flutter: AsyncSnapshot<Album>(ConnectionState.waiting, null, null)
flutter: http response completed
flutter: AsyncSnapshot<Album>(ConnectionState.done, Instance of 'Album', null)
snapshot为AsyncSnapshot类型,AsyncSnapshot的ConnectionState是一个枚举类,定义如下:
enum ConnectionState {
// 当前没有异步任务,比如[FutureBuilder]的[future]为null时
none,
// 异步任务处于等待状态,例如获取网络数据等待中
waiting,
// Stream处于激活状态(流上已经有数据传递了),只对StreamBuilder有效。
active,
// 异步任务已经终止,例如成功获取了网络数据
done,
}
第一个AsyncSnapshot(ConnectionState.waiting, null, null)输出是由initState事件触发的FutureBuilder组件构建引起的,之所以是waiting状态,是因为要等待第6行代码的返回。
第二个AsyncSnapshot(ConnectionState.waiting, null, null)输出跟Flutter组件底层绘制机制相关,它会在适当地时机调用drawFrame,从而触发FutureBuilder组件的构建。这行输出并不是总存在的。
AsyncSnapshot(ConnectionState.done, Instance of 'Album', null)输出是当Future有值或出错时,即第6行执行返回后。ConnectionState状态会改为done,且通过执行setState函数会又一次触发FutureBuilder组件的构建。这一点可以在FutureBuilder源代码的第614行和第616行观察到:

发送网络数据
Restful是一种网络应用程序的设计风格和开发方式,基于HTTP,可以使用XML格式定义或JSON格式定义。HTTP Get方法用于读取,Post或Put用于数据插入或更新。基于前面的http.get示例,下面http.post的示例也比较容易理解。

- 第15 行 response.statusCode == 201,在HTTP协议中,响应状态码201是一个代表成功的应答状态码,表示请求已经被成功处理,并且创建了新的资源。
- 第60行 _futureAlbum == null 为真时,显示的是上图设备的UI;第72行执行完毕后,_futureAlbum不为null(此时_futureAlbum的_state为0,表示Uncompleted状态),则显示下图设备的UI。第82行_futureAlbum时的_state为4,表示Completed状态。
JSON数据解析
JSON(JavaScript Object Notation, JS 对象简谱) 是一种轻量级的数据交换格式。它基于 ECMAScript 的一个子集,采用完全独立于编程语言的文本格式来存储和表示数据。前面2个示例中我们已经接触过JSON对象,以及JSON对象和Dart类之间相互转换的过程,我们把这个过程称为序列化和反序列化,或编码和解码。我们再回顾下dart:convert编码和解码JSON的方法。
class User {
final String name;
final String email;
User(this.name, this.email);
User.fromJson(Map<String, dynamic> json)
: name = json['name'],
email = json['email'];
Map<String, dynamic> toJson() =>
{
'name': name,
'email': email,
};
}
main(){
String jsonString='''{
"name": "groupones",
"email": "groupones@gmail.com"
}''';
Map userMap = jsonDecode(jsonString);
var user = User.fromJson(userMap);
String json = jsonEncode(user);
}
对于复杂的对象使用dart:convert编解码效率会很低,我们可以使用json_serializable插件。我们在pubspec.yaml配置如下:
dependencies:
json_annotation: ^3.0.0
dev_dependencies:
build_runner: ^1.0.0
json_serializable: ^3.2.0
如果只是开发阶段使用的包依赖,如test,example等,这些依赖项可以放在dev_dependencie中,这样可以依赖树更小,pub运行更快。
使用json_serializable插件重写前面的示例并保存在文件user.dart中。
import 'dart:convert';
import 'package:json_annotation/json_annotation.dart';
part 'user.g.dart';
@JsonSerializable()
class User {
User(this.name, this.email);
String name;
String email;
factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
Map<String, dynamic> toJson() => _$UserToJson(this);
}
main(){
String jsonString='''{
"name": "groupones",
"email": "groupones@gmail.com"
}''';
Map userMap = jsonDecode(jsonString);
var user = User.fromJson(userMap);
print('Happy birthdy, ${user.name}!');
print('We sent you a surprise to ${user.email}.');
String json = jsonEncode(user);
print('User json string is $json');
}
- part 'user.g.dart' part指令表示可以把各个功能分解到各个dart文件中,但part of所在文件不能包括import、library等关键字,这样有利于库的维护和复用。
- user.g.dart文件是用json_serializable插件自动生成的,这需要在项目的根目录下执行flutter pub run build_runner build或flutter pub run build_runner watch命令,后者是监听模式,每当user.dart文件更新保存后,user.g.dart文件会同步更新。生成的user.g.dart文件如下:
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'user.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
User _$UserFromJson(Map<String, dynamic> json) {
return User(
json['name'] as String,
json['email'] as String,
);
}
Map<String, dynamic> _$UserToJson(User instance) => <String, dynamic>{
'name': instance.name,
'email': instance.email,
};
- UserFromJson和UserToJson的代码逻辑你不用自己再写了,user.g.dart文件里已经自动生成了。我们只要按照json_serializable插件命名规则进行引用即可。
- 使用Dart Command Line App的方式运行user.dart,可以得到如下输出结果:
Happy birthdy, groupones!
We sent you a surprise to groupones@gmail.com.
User json string is {"name":"groupones","email":"groupones@gmail.com"}
如果只是上述这些功能就使用json_serializable代替dart:convert,也未免小题大作了。json_serializable还提供@JsonKey等注解和注解选项,以支持对JSON转换时需要处理的多种场景。如果你有过Java Spring或C#.NET MVC等方面的编程经验,对此处注解的使用会感到非常地熟悉。
使用json_serializable插件,User类的构造函数,属性name和email还是需要人工输入,你可以使用IDEA的插件将这一过程更加自动化,如FlutterJsonBeanFactory,有兴趣的同学自行研究。

dio插件
dio插件是一个比http插件更强大的Dart Http请求库,支持Restful API、FormData、拦截器、请求取消、Cookie管理、文件上传/下载、超时、自定义适配器等。
使用dio重写第一个http get示例,pubspec.yaml配置dio: ^3.0.9,完整代码如下:
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:dio/dio.dart';
Future<Album> fetchAlbum() async {
final response = await Dio().get('https://jsonplaceholder.typicode.com/albums/1');
if (response.statusCode == 200) {
return Album.fromJson(response.data);
} else {
throw Exception('Failed to load album');
}
}
class Album {
final int userId;
final int id;
final String title;
Album({this.userId, this.id, this.title});
factory Album.fromJson(Map<String, dynamic> json) {
return Album(
userId: json['userId'],
id: json['id'],
title: json['title'],
);
}
}
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
MyApp({Key key}) : super(key: key);
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
Future<Album> futureAlbum;
@override
void initState() {
super.initState();
futureAlbum = fetchAlbum();
}
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Fetch Data Example',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(
appBar: AppBar(
title: Text('Fetch Data Example'),
),
body: Center(
child: FutureBuilder<Album>(
future: futureAlbum,
builder: (context, snapshot) {
if (snapshot.hasData) {
return Text(snapshot.data.title);
} else if (snapshot.hasError) {
return Text("${snapshot.error}");
}
return CircularProgressIndicator();
},
),
),
),
);
}
}
对比2个项目代码,我们只做了2处修改:fetchAlbum()函数里http.get修改为Dio().get,json.decode(response.body)修改为response.data,这里的response.data已经是Map类型,所以不需要JSON解码。
继续使用dio库重写http post示例,这次我们不使用Dio().post的方式,使用Dio核心API request的写法:
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:dio/dio.dart';
Future<Album> createAlbum(String title) async {
Dio dio = Dio(); // 使用默认配置
dio.options.baseUrl = "https://jsonplaceholder.typicode.com/";
dio.options.connectTimeout = 5000;
dio.options.receiveTimeout = 3000;
final response = await dio.request(
"/albums",
data: {'title': title},
options: Options(method: "POST"),
);
if (response.statusCode == 201) {
return Album.fromJson(response.data);
} else {
throw Exception('Failed to create album.');
}
}
class Album {
final int id;
final String title;
Album({this.id, this.title});
factory Album.fromJson(Map<String, dynamic> json) {
return Album(
id: json['id'],
title: json['title'],
);
}
}
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
MyApp({Key key}) : super(key: key);
@override
_MyAppState createState() {
return _MyAppState();
}
}
class _MyAppState extends State<MyApp> {
final TextEditingController _controller = TextEditingController();
Future<Album> _futureAlbum;
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Create Data Example',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(
appBar: AppBar(
title: Text('Create Data Example'),
),
body: Container(
alignment: Alignment.center,
padding: const EdgeInsets.all(8.0),
child: (_futureAlbum == null)
? Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
TextField(
controller: _controller,
decoration: InputDecoration(hintText: 'Enter Title'),
),
RaisedButton(
child: Text('Create Data'),
onPressed: () {
setState(() {
_futureAlbum = createAlbum(_controller.text);
});
},
),
],
)
: FutureBuilder<Album>(
future: _futureAlbum,
builder: (context, snapshot) {
if (snapshot.hasData) {
return Text(snapshot.data.title);
} else if (snapshot.hasError) {
return Text("${snapshot.error}");
}
return CircularProgressIndicator();
},
),
),
),
);
}
}
具体的差异都在createAlbum()函数里,难度不大,大家可以自行分析。
关于Dio的详细使用说明和示例,可以参考这里。
拓展
除了普通的HTTP请求,还可以通过WebSockets来连接服务器,WebSockets可以以非轮询的方式与服务器进行双向通信。我们并不准备在这里介绍WebSocket通信编程,有兴趣的可以戳这里进行了解。
实验十一
在实验九的基础上实现以下2个功能:
- 通过获取远程数据库网络数据,判断登录账号和密码的正确性;
- 实现注册页面,发送网络数据存入远程数据库。
提示:后端接口可以通过BaaS(后端即服务:Backend as a Service)模拟,如API工厂,Bomb等。
上一篇 Widget状态和应用数据管理 下一篇 Flutter应用发布