Flutter 笔记 | Flutter 文件IO、网络请求、JSON、日期与国际化

本文详细介绍了在Flutter中进行文件I/O操作,包括获取应用目录、文件读写,以及网络请求的实现,如HttpClient与dio库的使用。同时,讲解了JSON到Dart Model类的转换方法,以及日期和时间的格式化。此外,还深入探讨了Flutter的国际化支持,包括如何设置和监听系统语言切换,以及使用intl包进行本地化资源的管理和生成。
摘要由CSDN通过智能技术生成

文件IO操作

Dart的 IO 库包含了文件读写的相关类,它属于 Dart 语法标准的一部分,所以通过 Dart IO 库,无论是 Dart VM 下的脚本还是 Flutter,都是通过 Dart IO 库来操作文件的,不过和 Dart VM 相比,Flutter 有一个重要差异是文件系统路径不同,这是因为 Dart VM 是运行在 PC 或服务器操作系统下,而 Flutter 是运行在移动操作系统中,他们的文件系统会有一些差异。

APP目录

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

  • 临时目录: 可以使用 getTemporaryDirectory() 来获取临时目录; 系统可随时清除临时目录的文件。在 iOS 上,这对应于NSTemporaryDirectory() 返回的值。在 Android上,这是getCacheDir() 返回的值。
  • 文档目录: 可以使用getApplicationDocumentsDirectory()来获取应用程序的文档目录,该目录用于存储只有自己可以访问的文件。只有当应用程序被卸载时,系统才会清除该目录。在 iOS 上,这对应于NSDocumentDirectory。在 Android 上,这是AppData目录。
  • 外部存储目录:可以使用getExternalStorageDirectory()来获取外部存储目录,如 SD 卡;由于 iOS不支持外部目录,所以在 iOS 下调用该方法会抛出UnsupportedError异常,而在 Android 下结果是Android SDK 中getExternalStorageDirectory的返回值。

一旦你的 Flutter 应用程序有一个文件位置的引用,你可以使用 dart:io API来执行对文件系统的读/写操作。例如:

import 'dart:async';
import 'dart:io';
import 'dart:convert';

// Reading a file as text
// When reading a text file encoded using UTF-8,
// you can read the entire file contents with readAsString().
// When the individual lines are important, you can use readAsLines().
// In both cases, a Future object is returned that provides the contents of the
// file as one or more strings.
Future<void> readFileAsText() async {
   
  var config = File('config.txt');

// Put the whole file in a single string.
  var stringContents = await config.readAsString();
  print('The file is ${
     stringContents.length} characters long.');

// Put each line of the file into its own string.
  var lines = await config.readAsLines();
  print('The file is ${
     lines.length} lines long.');
}

// Reading a file as binary
// The following code reads an entire file as bytes into a list of ints.
// The call to readAsBytes() returns a Future, which provides the result when it’s available.
Future<void> readFileAsBinary() async {
   
  var config = File('config.txt');
  var contents = await config.readAsBytes();
  print('The file is ${
     contents.length} bytes long.');
}

// Handling errors
// To capture errors so they don’t result in uncaught exceptions, you can register
// a catchError handler on the Future, or (in an async function) use try-catch:
Future<void> handlingErrors() async {
   
  var config = File('config.txt');
  try {
   
    var contents = await config.readAsString();
    print(contents);
  } catch (e) {
   
    print(e);
  }
}

// Streaming file contents
// Use a Stream to read a file, a little at a time. You can use either the Stream
// API or await for, part of Dart’s asynchrony support.
Future<void> streamingFileContents() async {
   
  var config = File('config.txt');
  Stream<List<int>> inputStream = config.openRead();

  var lines = utf8.decoder
      .bind(inputStream)
      .transform(const LineSplitter());
  try {
   
    await for (final line in lines) {
   
      print('Got ${
     line.length} characters from stream');
    }
    print('file is now closed');
  } catch (e) {
   
    print(e);
  }
}

// Writing file contents
// You can use an IOSinklaunch to write data to a file. Use the File openWrite()
// method to get an IOSink that you can write to. The default mode, FileMode.write,
// completely overwrites existing data in the file.
Future<void> writingFileContent() async {
   
  var logFile = File('log.txt');
  var sink = logFile.openWrite();
  sink.write('FILE ACCESSED ${
     DateTime.now()}\n');
  // To add to the end of the file, use the optional mode parameter to specify FileMode.append:
  // var sink = logFile.openWrite(mode: FileMode.append);
  await sink.flush();
  await sink.close();
}

// To write binary data, use add(List<int> data).Listing files in a directory
// Finding all files and subdirectories for a directory is an asynchronous operation.
// The list() method returns a Stream that emits an object when a file or directory is encountered.
Future<void> writeBinaryData() async {
   
  var dir = Directory('tmp');
  try {
   
    var dirList = dir.list();
    await for (final FileSystemEntity f in dirList) {
   
      if (f is File) {
   
        print('Found file ${
     f.path}');
      } else if (f is Directory) {
   
        print('Found dir ${
     f.path}');
      }
    }
  } catch (e) {
   
    print(e.toString());
  }
}

更多有关 Dart 进行文件处理的详细内容可以参考 Dart 语言文档,下面我们看一个简单的例子。

示例

我们还是以计数器为例,实现在应用退出重启后可以恢复点击次数。 这里,我们使用文件来保存数据:

  1. 引入PathProvider插件;在pubspec.yaml文件中添加如下声明:
path_provider: ^2.0.15

添加后,执行 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);

  
  _FileOperationRouteState createState() => _FileOperationRouteState();
}

class _FileOperationRouteState extends State<FileOperationRoute> {
   
  int _counter = 0;

  
  void initState() {
   
    super.initState();
    // 从文件读取点击次数
    _readCounter().then((int value) {
   
      setState(() {
   
        _counter = value;
      });
    });
  }

  Future<File> _getLocalFile() async {
   
    // 获取应用目录
    String dir = (await getApplicationDocumentsDirectory()).path;
    return 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;
    }
  }

  _incrementCounter() async {
   
    setState(() {
   
      _counter++;
    });
    // 将点击次数以字符串类型写到文件中
    await (await _getLocalFile()).writeAsString('$_counter');
  }


  
  Widget build(BuildContext context) {
   
    return Scaffold(
      appBar: AppBar(title: Text('文件操作')),
      body: Center(
        child: Text('点击了 $_counter 次'),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  }
}

上面代码比较简单,不再赘述,需要说明的是,本示例只是为了演示文件读写,而在实际开发中,如果要存储一些简单的数据,使用shared_preferences插件会比较简单。

网络请求

通过HttpClient发起HTTP请求

Dart IO库中提供了用于发起Http请求的一些类,我们可以直接使用HttpClient来发起请求。使用HttpClient发起请求分为五步:

  1. 创建一个HttpClient
 HttpClient httpClient = HttpClient();
  1. 打开Http连接,设置请求头:
HttpClientRequest request = await httpClient.getUrl(uri);

这一步可以使用任意Http Method,如httpClient.post(...)httpClient.delete(...)等。如果包含Query参数,可以在构建uri时添加,如:

Uri uri = Uri(scheme: "https", host: "flutterchina.club", queryParameters: {
   
    "xx":"xx",
    "yy":"dd"
  });

通过HttpClientRequest可以设置请求header,如:

request.headers.add("user-agent", "test");

如果是postput等可以携带请求体方法,可以通过HttpClientRequest对象发送请求体,如:

String payload="...";
request.add(utf8.encode(payload)); 
//request.addStream(_inputStream); //可以直接添加输入流
  1. 等待连接服务器:
HttpClientResponse response = await request.close();

这一步完成后,请求信息就已经发送给服务器了,返回一个HttpClientResponse对象,它包含响应头(header)和响应流(响应体的Stream),接下来就可以通过读取响应流来获取响应内容。

  1. 读取响应内容:
String responseBody = await response.transform(utf8.decoder).join();

我们通过读取响应流来获取服务器返回的数据,在读取时我们可以设置编码格式,这里是utf8

  1. 请求结束,关闭HttpClient
httpClient.close();

关闭client后,通过该client发起的所有请求都会终止。

示例

下面代码实现点击按钮后请求百度首页,请求成功后,将返回内容显示出来并在控制台打印响应header,代码如下:

import 'dart:convert';
import 'dart:io';

import 'package:flutter/material.dart';

class HttpTestRoute extends StatefulWidget {
   
  
  _HttpTestRouteState createState() => _HttpTestRouteState();
}

class _HttpTestRouteState extends State<HttpTestRoute> {
   
  bool _loading = false;
  String _text = "";

  
  Widget build(BuildContext context) {
   
    return SingleChildScrollView(
      child: Column(
        children: <Widget>[
          ElevatedButton(
            child: Text("获取百度首页"),
            onPressed: _loading ? null : request,
          ),
          Container(
            width: MediaQuery.of(context).size.width - 50.0,
            child: Text(_text.replaceAll(RegExp(r"\s"), "")),
          )
        ],
      ),
    );
  }

  request() async {
   
    setState(() {
   
      _loading = true;
      _text = "正在请求...";
    });
    try {
   
      //创建一个HttpClient
      HttpClient httpClient = HttpClient();
      //打开Http连接
      HttpClientRequest request =
          await httpClient.getUrl(Uri.parse("https://www.baidu.com"));
      //使用iPhone的UA
      request.headers.add(
        "user-agent",
        "Mozilla/5.0 (iPhone; CPU iPhone OS 10_3_1 like Mac OS X) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/10.0 Mobile/14E304 Safari/602.1",
      );
      //等待连接服务器(会将请求信息发送给服务器)
      HttpClientResponse response = await request.close();
      //读取响应内容
      _text = await response.transform(utf8.decoder).join();
      //输出响应头
      print(response.headers);

      //关闭client后,通过该client发起的所有请求都会终止。
      httpClient.close();
    } catch (e) {
   
      _text = "请求失败:$e";
    } finally {
   
      setState(() {
   
        _loading = false;
      });
    }
  }
}

控制台输出:

I/flutter (18545): connection: Keep-Alive
I/flutter (18545): cache-control: no-cache
I/flutter (18545): set-cookie: ....  //有多个,省略...
I/flutter (18545): transfer-encoding: chunked
I/flutter (18545): date: Tue, 30 Oct 2018 10:00:52 GMT
I/flutter (18545): content-encoding: gzip
I/flutter (18545): vary: Accept-Encoding
I/flutter (18545): strict-transport-security: max-age=172800
I/flutter (18545): content-type: text/html;charset=utf-8
I/flutter (18545): tracecode: 00525262401065761290103018
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

川峰

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值