09-文件和网络请求

Posted slchuck

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了09-文件和网络请求相关的知识,希望对你有一定的参考价值。

09-文件和网络请求

文件操作

Dart的IO库包含了文件读写的相关类,它属于Dart语法标准的一部分,所以通过Dart IO库,无论是Dart VM下的脚本还是Flutter,都是通过Dart IO库来操作文件的。

APP目录

androidios的应用存储目录不同,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步骤:

  1. 连接到WebSocket服务器

web_socket_channel package 提供了我们需要连接到WebSocket服务器的工具.

该package提供了一个WebSocketChannel允许我们既可以监听来自服务器的消息,又可以将消息发送到服务器的方法。

在Flutter中,我们可以创建一个WebSocketChannel连接到一台服务器:

final channel = new IOWebSocketChannel.connect(‘ws://echo.websocket.org‘);

  1. 监听来自服务器的消息

现在我们建立了连接,我们可以监听来自服务器的消息,在我们发送消息给测试服务器之后,它会返回相同的消息。

我们如何收取消息并显示它们?在这个例子中,我们将使用一个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重新构建界面。

  1. 将数据发送到服务器

为了将数据发送到服务器,我们会add消息给WebSocketChannel提供的sink。

channel.sink.add(‘Hello!‘);
工作原理
WebSocketChannel提供了一个StreamSink,它将消息发给服务器。

StreamSink类提供了给数据源同步或异步添加事件的一般方法。

  1. 关闭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-文件和网络请求的主要内容,如果未能解决你的问题,请参考以下文章

VSCode自定义代码片段14——Vue的axios网络请求封装

Python 向 Postman 请求代码片段

09-文件和网络请求

等待文件加载和网络请求完成

简单的 Javascript http 请求片段但不起作用

前端面试题之手写promise