• 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步骤:

    1. 连接到WebSocket服务器

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

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

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

    final channel = new IOWebSocketChannel.connect('ws://echo.websocket.org');
    2. 监听来自服务器的消息

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

    我们如何收取消息并显示它们?在这个例子中,我们将使用一个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重新构建界面。
    3. 将数据发送到服务器

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

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

    StreamSink类提供了给数据源同步或异步添加事件的一般方法。
    4. 关闭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。只需启动一次观察器,然后它就会在后台运行,这是安全的。

  • 相关阅读:
    Git代码行数统计命令
    JPA访问数据库的几种方式
    爱码小士丨代码一敲十年,收入虽高前途摇摆
    “肉瘾”女孩从软件测试工程师到主管的成长感悟
    华为测试大牛Python+Django接口自动化怎么写的?
    携程大牛的单元测试是怎么样写的?
    Jmeter参数的AES加密使用
    弄啥嘞?热爱你的Bug
    “进腾讯工作一个月,我想辞职了”
    我在华为,软件测试人员在工作中如何运用Linux?
  • 原文地址:https://www.cnblogs.com/SLchuck/p/10336435.html
Copyright © 2020-2023  润新知