09-文件和网络请求
Posted slchuck
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了09-文件和网络请求相关的知识,希望对你有一定的参考价值。
09-文件和网络请求
文件操作
Dart的IO库包含了文件读写的相关类,它属于Dart语法标准的一部分,所以通过Dart IO库,无论是Dart VM下的脚本还是Flutter,都是通过Dart IO库来操作文件的。
APP目录
android和ios的应用存储目录不同,PathProvider 插件提供了一种平台透明的方式来访问设备文件系统上的常用位置。该类当前支持访问两个文件系统位置:
- 临时目录: 可以使用 getTemporaryDirectory() 来获取临时目录; 系统可随时清除的临时目录(缓存)。在iOS上,这对应于NSTemporaryDirectory() 返回的值。在Android上,这是getCacheDir())返回的值。
- 文档目录: 可以使用getApplicationDocumentsDirectory()来获取应用程序的文档目录,该目录用于存储只有自己可以访问的文件。只有当应用程序被卸载时,系统才会清除该目录。在iOS上,这对应于NSDocumentDirectory。在Android上,这是AppData目录。
引入PathProvider插件,
在pubspec.yaml文件中添加如下声明:dependencies: path_provider: ^0.4.1
添加后,执行flutter packages get 获取一下。
import 'dart:io';
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:path_provider/path_provider.dart';
class FileOperationRoute extends StatefulWidget {
FileOperationRoute({Key key}) : super(key: key);
@override
_FileOperationRouteState createState() => new _FileOperationRouteState();
}
class _FileOperationRouteState extends State<FileOperationRoute> {
int _counter;
@override
void initState() {
super.initState();
//从文件读取点击次数
_readCounter().then((int value) {
setState(() {
_counter = value;
});
});
}
Future<File> _getLocalFile() async {
// 获取应用目录
String dir = (await getApplicationDocumentsDirectory()).path;
return new File('$dir/counter.txt');
}
Future<int> _readCounter() async {
try {
File file = await _getLocalFile();
// 读取点击次数(以字符串)
String contents = await file.readAsString();
return int.parse(contents);
} on FileSystemException {
return 0;
}
}
Future<Null> _incrementCounter() async {
setState(() {
_counter++;
});
// 将点击次数以字符串类型写到文件中
await (await _getLocalFile()).writeAsString('$_counter');
}
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(title: new Text('文件操作')),
body: new Center(
child: new Text('点击了 $_counter 次'),
),
floatingActionButton: new FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: new Icon(Icons.add),
),
);
}
}
通过HttpClient发起HTTP请求
Dart IO库中提供了Http请求的一些类,我们可以直接使用HttpClient来发起请求。使用HttpClient发起请求分为五步:
1. 创建一个HttpClient
HttpClient httpClient = new HttpClient();
2.打开Http连接,设置请求头
HttpClientRequest request = await httpClient.getUrl(uri);
这一步可以使用任意Http method.
如果包含Query参数,可以在构建uri时添加,如:
Uri uri=Uri(scheme: "https", host: "flutterchina.club", queryParameters: {
"xx":"xx",
"yy":"dd"
});
通过HttpClientRequest可以设置请求header,如:
request.headers.add("user-agent", "test");
如果是post或put等可以携带请求体方法,可以通过HttpClientRequest对象发送request body,如:
String payload="...";
request.add(utf8.encode(payload));
3.等待连接服务器
HttpClientResponse response = await request.close();
这一步完成后,请求信息就已经发送给服务器了,返回一个HttpClientResponse对象,它包含响应头(header)和响应流(响应体的Stream)。
4.读取响应内容
String responseBody = await response.transform(utf8.decoder).join();
5.请求结束,关闭HttpClient
httpClient.close();
Dio http库
dio是一个强大的Dart Http请求库,支持Restful API、FormData、拦截器、请求取消、Cookie管理、文件上传/下载、超时等。
引入dio,
在pubspec.yaml文件中添加如下声明:dependencies: dio: ^x.x.x
添加后,执行flutter packages get 获取一下。
一个dio实例可以发起多个http请求,一般来说,APP只有一个http数据源时,dio应该使用单例模式。
示例
import 'package:dio/dio.dart';
Dio dio = new Dio();
//发起 GET 请求 :
Response response;
response=await dio.get("/test?id=12&name=wendu")
print(response.data.toString());
//发起一个 POST 请求:
response=await dio.post("/test",data:{"id":12,"name":"wendu"})
//发起多个并发请求:
response= await Future.wait([dio.post("/info"),dio.get("/token")]);
//下载文件:
response=await dio.download("https://www.google.com/",_savePath);
//发送 FormData:
FormData formData = new FormData.from({
"name": "wendux",
"age": 25,
});
response = await dio.post("/info", data: formData)
//通过FormData上传多个文件:
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主页
使用WebSockets
Http协议是无状态的,只能由客户端主动发起,服务端再被动响应,服务端无法向客户端主动推送内容,并且一旦服务器响应结束,链接就会断开(见注解部分),所以无法进行实时通信。WebSocket协议正是为解决客户端与服务端实时通信而产生的技术,现在已经被主流浏览器支持,所以对于Web开发者来说应该比较熟悉了,Flutter也提供了专门的包来支持WebSocket协议。
发起WebSockets步骤:
- 连接到WebSocket服务器
web_socket_channel package 提供了我们需要连接到WebSocket服务器的工具.
该package提供了一个WebSocketChannel允许我们既可以监听来自服务器的消息,又可以将消息发送到服务器的方法。
在Flutter中,我们可以创建一个WebSocketChannel连接到一台服务器:
final channel = new IOWebSocketChannel.connect(‘ws://echo.websocket.org‘);
- 监听来自服务器的消息
现在我们建立了连接,我们可以监听来自服务器的消息,在我们发送消息给测试服务器之后,它会返回相同的消息。
我们如何收取消息并显示它们?在这个例子中,我们将使用一个StreamBuilder Widget来监听新消息, 并用一个Text Widget来显示它们。
new StreamBuilder(
stream: widget.channel.stream,
builder: (context, snapshot) {
return new Text(snapshot.hasData ? ‘${snapshot.data}‘ : ‘‘);
},
);
工作原理
WebSocketChannel提供了一个来自服务器的消息Stream 。
该Stream类是dart:async包中的一个基础类。它提供了一种方法来监听来自数据源的异步事件。与Future返回单个异步响应不同,Stream类可以随着时间推移传递很多事件。
该StreamBuilder Widget将连接到一个Stream, 并在每次收到消息时通知Flutter重新构建界面。
- 将数据发送到服务器
为了将数据发送到服务器,我们会add消息给WebSocketChannel提供的sink。
channel.sink.add(‘Hello!‘);
工作原理
WebSocketChannel提供了一个StreamSink,它将消息发给服务器。
StreamSink类提供了给数据源同步或异步添加事件的一般方法。
- 关闭WebSocket连接
在我们使用WebSocket后,要关闭连接:
channel.sink.close();
import 'package:flutter/material.dart';
import 'package:web_socket_channel/io.dart';
class WebSocketRoute extends StatefulWidget {
@override
_WebSocketRouteState createState() => new _WebSocketRouteState();
}
class _WebSocketRouteState extends State<WebSocketRoute> {
TextEditingController _controller = new TextEditingController();
IOWebSocketChannel channel;
String _text = "";
@override
void initState() {
//创建websocket连接
channel = new IOWebSocketChannel.connect('ws://echo.websocket.org');
}
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text("WebSocket(内容回显)"),
),
body: new Padding(
padding: const EdgeInsets.all(20.0),
child: new Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
new Form(
child: new TextFormField(
controller: _controller,
decoration: new InputDecoration(labelText: 'Send a message'),
),
),
new StreamBuilder(
stream: channel.stream,
builder: (context, snapshot) {
//网络不通会走到这
if (snapshot.hasError) {
_text = "网络不通...";
} else if (snapshot.hasData) {
_text = "echo: "+snapshot.data;
}
return new Padding(
padding: const EdgeInsets.symmetric(vertical: 24.0),
child: new Text(_text),
);
},
)
],
),
),
floatingActionButton: new FloatingActionButton(
onPressed: _sendMessage,
tooltip: 'Send message',
child: new Icon(Icons.send),
),
);
}
void _sendMessage() {
if (_controller.text.isNotEmpty) {
channel.sink.add(_controller.text);
}
}
@override
void dispose() {
channel.sink.close();
super.dispose();
}
}
Json Model
由于Flutter中禁用了Dart的反射功能,而正因如此也就无法实现Json动态转化Model的功能。具体做法就是,通过预定义一些与Json结构对应的Model类,然后在请求到数据后再动态根据数据创建出Model类的实例。
例如,我们可以通过引入一个简单的模型类(Model class)来解决前面提到的问题,我们称之为User。在User类内部,我们有:
一个User.fromJson 构造函数, 用于从一个map构造出一个 User实例 map structure
一个toJson 方法, 将 User 实例转化为一个map.
user.dart
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() =>
<String, dynamic>{
'name': name,
'email': email,
};
}
自动生成Model
尽管还有其他库可用,介绍一下官方推荐的json_serializable package包。 它是一个自动化的源代码生成器,可以在开发阶段为我们生成JSON序列化模板,这样一来,由于序列化代码不再由我们手写和维护,我们将运行时产生JSON序列化异常的风险降至最低。
在项目中设置json_serializable
要包含json_serializable到我们的项目中,我们需要一个常规和两个开发依赖项。简而言之,开发依赖项是不包含在我们的应用程序源代码中的依赖项,它是开发过程中的一些辅助工具、脚本,和node中的开发依赖项相似。
pubspec.yaml
dependencies:
# Your other regular dependencies here
json_annotation: ^2.0.0
dev_dependencies:
# Your other dev_dependencies here
build_runner: ^1.0.0
json_serializable: ^2.0.0
在您的项目根文件夹中运行 flutter packages get (或者在编辑器中点击 “Packages Get”) 以在项目中使用这些新的依赖项.
以json_serializable的方式创建model类
让我们看看如何将我们的User类转换为一个json_serializable。为了简单起见,我们使用前面示例中的简化JSON model。
user.dart
import 'package:json_annotation/json_annotation.dart';
// user.g.dart 将在我们运行生成命令后自动生成
part 'user.g.dart';
///这个标注是告诉生成器,这个类是需要生成Model类的
@JsonSerializable()
class User{
User(this.name, this.email);
String name;
String email;
//不同的类使用不同的mixin即可
factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
Map<String, dynamic> toJson() => _$UserToJson(this);
}
有两种运行代码生成器的方法:
一次性生成
通过在我们的项目根目录下运行:
flutter packages pub run build_runner build
这触发了一次性构建,我们可以在需要时为我们的Model生成json序列化代码,它通过我们的源文件,找出需要生成Model类的源文件(包含@JsonSerializable标注的)来生成对应的.g.dart文件。一个好的建议是将所有Model类放在一个单独的目录下,然后在该目录下执行命令。
持续生成
使用watcher可以使我们的源代码生成的过程更加方便。它会监视我们项目中文件的变化,并在需要时自动构建必要的文件,我们可以通过
flutter packages pub run build_runner watch
在项目根目录下运行来启动watcher。只需启动一次观察器,然后它就会在后台运行,这是安全的。
以上是关于09-文件和网络请求的主要内容,如果未能解决你的问题,请参考以下文章