09-文件和网络请求

09-文件和网络请求

文件操做

Dart的IO库包含了文件读写的相关类,它属于Dart语法标准的一部分,因此经过Dart IO库,不管是Dart VM下的脚本仍是Flutter,都是经过Dart IO库来操做文件的。node

APP目录

Android和iOS的应用存储目录不一样,PathProvider 插件提供了一种平台透明的方式来访问设备文件系统上的经常使用位置。该类当前支持访问两个文件系统位置:git

  • 临时目录: 可使用 getTemporaryDirectory() 来获取临时目录; 系统可随时清除的临时目录(缓存)。在iOS上,这对应于NSTemporaryDirectory() 返回的值。在Android上,这是getCacheDir())返回的值。
  • 文档目录: 可使用getApplicationDocumentsDirectory()来获取应用程序的文档目录,该目录用于存储只有本身能够访问的文件。只有当应用程序被卸载时,系统才会清除该目录。在iOS上,这对应于NSDocumentDirectory。在Android上,这是AppData目录。

引入PathProvider插件,
在pubspec.yaml文件中添加以下声明:github

dependencies:
  path_provider: ^0.4.1

添加后,执行flutter packages get 获取一下。web

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发起请求分为五步:json

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。只需启动一次观察器,而后它就会在后台运行,这是安全的。

相关文章
相关标签/搜索