前言

之前的章节我们基本上把Flutter中基础部分的东西都做了简单的讲解,通过前面章节的循序学习读者也基本能完成一些简单的UI绘制并能利用Flutter处理一些简单的用户交互,读者可能也留意到,我们之前的章节中所学习到的内容并没有涉及到数据存储方面的操作,或者说,我们到现在为止并不知道在Flutter中数据应该怎么存,存在哪。本篇博文中笔者将会为大家解决这一疑惑。

关于Flutter中的数据存储

相信做过原生Android开发的读者对数据存储并不陌生,在原生Android中我们会把一些轻量级的数据(如用户信息、APP配置信息等)写入 SharedPreferences做存储,把需要长期存储的数据写入本地文件或者 Sqlite3,当然Flutter中也同样用一套完整的本地数据存储体系,下面我们就一直来了解下上述提到的这3中本地存储方式在Flutter中使用。

1.SharedPreferences

在Flutter中本身并没有内置SharedPreferences存储,但是官方给我们提供了第三方的组件来实现这一存储方式。我们可以通过pubspec.yaml文件引入,关于pubspec.yaml的使用我们在Flutter入门进阶之旅(五)Image Widget,这一章节提到过,只不过在Image使用中我们引入的是assets文件依赖。

如下我们在dependencies节点下引入SharedPreferences的依赖,读者在pubspec.yaml引入依赖时一定要注意代码缩进格式,否则在在执行flutter packages get时很可能会报错

  1. dependencies:
  2. flutter:
  3. sdk: flutter
  4. # 添加sharedPreference依赖
  5. shared_preferences: ^0.5.0
  6. dev_dependencies:
  7. flutter_test:
  8. sdk: flutter
  9. # 引入本地资源图片
  10. assets:
  11. - images/a.png
  12. - images/aaa.png

然后命令行执行 flutter packages get把远程依赖同步到本地,在此笔者写文章的时候sharedPreference的最新版本是0.5.0,读者可自行去https://pub.dartlang.org/flutter上获取最新版本,同时也可以在上面找到其他需要引入的资源依赖包。

笔者的话

啰里啰嗦的准备工作总算是讲完了,主要是今天的课程涉及到了包依赖管理,可能对于有些初学者有点懵,所以我就借助sharedPreference把依赖引入废话扯了一大通,如果读者已经掌握了上述操作,可跳过准备工作直接到下面的部分。

继续上面的内容,我们先来体验一下sharedPreference,贴个图大家放松一下。
12- Flutter 数据存储 - 图1

从上图中我们看到我们使用 sharedPreference做了简单存储跟获取的操作,其实 sharedPreference好像也就这么点左右,不是存就是取。读者在自行操作时一定不要忘记导入 sharedPreference的包

  1. import 'package:shared_preferences/shared_preferences.dart';

存数据

跟原生Android一样,Flutter中操作sp也是通过key-value的方式存取数据

  1. Future saveString() async {
  2. SharedPreferences sharedPreferences = await SharedPreferences.getInstance();
  3. sharedPreferences.setString(
  4. STORAGE_KEY, _textFieldController.value.text.toString());
  5. }

SharedPreferences中为我们提供了String、bool、Double、Int、StringList数据类型的存取。
12- Flutter 数据存储 - 图2

取数据

  1. Future getString() async {
  2. SharedPreferences sharedPreferences = await SharedPreferences.getInstance();
  3. setState(() {
  4. _storageString = sharedPreferences.get(STORAGE_KEY);
  5. });
  6. }

上述操作逻辑中我们通过 _textFieldController获取在 TextField中的值,在按下存储按钮的同时我们把数据写入sp中,当按下获取值的时候我们通过 setState把从sp中获取的值同步更新到下面的Text上显示。

完整代码:

  1. import 'package:flutter/material.dart';
  2. import 'package:shared_preferences/shared_preferences.dart';
  3. class StoragePage extends StatefulWidget {
  4. @override
  5. State<StatefulWidget> createState() => StorageState();
  6. }
  7. class StorageState extends State {
  8. var _textFieldController = new TextEditingController();
  9. var _storageString = '';
  10. final STORAGE_KEY = 'storage_key';
  11. Future saveString() async {
  12. SharedPreferences sharedPreferences = await SharedPreferences.getInstance();
  13. sharedPreferences.setString(
  14. STORAGE_KEY, _textFieldController.value.text.toString());
  15. }
  16. Future getString() async {
  17. SharedPreferences sharedPreferences = await SharedPreferences.getInstance();
  18. setState(() {
  19. _storageString = sharedPreferences.get(STORAGE_KEY);
  20. });
  21. }
  22. @override
  23. Widget build(BuildContext context) {
  24. return new Scaffold(
  25. appBar: new AppBar(
  26. title: new Text('数据存储'),
  27. ),
  28. body: new Column(
  29. children: <Widget>[
  30. Text("shared_preferences存储", textAlign: TextAlign.center),
  31. TextField(
  32. controller: _textFieldController,
  33. ),
  34. MaterialButton(
  35. onPressed: saveString,
  36. child: new Text("存储"),
  37. color: Colors.pink,
  38. ),
  39. MaterialButton(
  40. onPressed: getString,
  41. child: new Text("获取"),
  42. color: Colors.lightGreen,
  43. ),
  44. Text('shared_preferences存储的值为 $_storageString'),
  45. ],
  46. ),
  47. );
  48. }
  49. }

2.文件存储

虽然我们今天内容是Flutter的数据存储,尴尬的是Flutter本身都没有内置提到的这三种存储方式,不过好在官方给我们提供了三方的支持库,不知道后续的Flutter版本中会不会对此做改进。操作文件同样我们也需要像SharedPreferences一样,需要在pubspec.yaml引入。在 Flutter 里实现文件读写,需要使用 path_provider 和 dart 的 io 模块。path_provider 负责查找 iOS/Android 的目录文件,IO 模块负责对文件进行读写

  1. # 添加文件依赖
  2. path_provider: ^0.5.0

笔者在此引入的最新版本是0.5.0,读者可自行去https://pub.dartlang.org/flutter上获取最新版本。

由于整个操作演示逻辑跟SharedPreferences一样,我就不详细讲解文件存储中关于存取数据的具体操作了,稍微我贴上源代码,读者自行查阅代码对比即可,关于文件存储的三个获取文件路径的方法我这里说明一下,做过原生Android开发的读者可能对此不陌生,但是ios或者初学者可能并不了解这个概念,所以我想提出来说明一下。

在path_provider中有三个获取文件路径的方法:

  • getTemporaryDirectory()//获取应用缓存目录,等同IOS的NSTemporaryDirectory()和Android的getCacheDir() 方法
  • getApplicationDocumentsDirectory()获取应用文件目录类似于Ios的NSDocumentDirectory和Android上的 AppData目录
  • getExternalStorageDirectory()//这个是存储卡,仅仅在Android平台可以使用

来看下操作文件的效果图
12- Flutter 数据存储 - 图3

借用了SharedPreferences存储的逻辑,只是把存储的代码放在了 file.text中,代码里有详尽的注释,我就不多做解释说明了,读者可自行尝试对比跟 SharedPreferences的差别

样例代码

  1. import 'package:flutter/material.dart';
  2. import 'package:path_provider/path_provider.dart';
  3. import 'dart:io';
  4. class StoragePage extends StatefulWidget {
  5. @override
  6. State<StatefulWidget> createState() => StorageState();
  7. }
  8. class StorageState extends State {
  9. var _textFieldController = new TextEditingController();
  10. var _storageString = '';
  11. saveString() async {
  12. final file = await getFile('file.text');
  13. file.writeAsString(_textFieldController.value.text.toString());
  14. }
  15. Future getString() async {
  16. final file = await getFile('file.text');
  17. var filePath = file.path;
  18. setState(() {
  19. file.readAsString().then((String value) {
  20. _storageString = value +'\n文件存储路径:'+filePath;
  21. });
  22. });
  23. }
  24. Future<File> getFile(String fileName) async {
  25. final fileDirectory = await getApplicationDocumentsDirectory();
  26. final filePath = fileDirectory.path;
  27. return new File(filePath + "/"+fileName);
  28. }
  29. @override
  30. Widget build(BuildContext context) {
  31. return new Scaffold(
  32. appBar: new AppBar(
  33. title: new Text('数据存储'),
  34. ),
  35. body: new Column(
  36. mainAxisAlignment: MainAxisAlignment.center,
  37. children: <Widget>[
  38. Text("文件存储", textAlign: TextAlign.center),
  39. TextField(
  40. controller: _textFieldController,
  41. ),
  42. MaterialButton(
  43. onPressed: saveString,
  44. child: new Text("存储"),
  45. color: Colors.cyan,
  46. ),
  47. MaterialButton(
  48. onPressed: getString,
  49. child: new Text("获取"),
  50. color: Colors.deepOrange,
  51. ),
  52. Text('从文件存储中获取的值为 $_storageString'),
  53. ],
  54. ),
  55. );
  56. }
  57. }

3.Sqflite

在Flutter中的数据库叫 Sqflite跟原生安卓的 Sqlite叫法不一样。我们来看下 Sqflite官方对它的解释说明:

  1. SQLite plugin for Flutter. Supports both iOS and Android.
  2. Support transactions and batches
  3. Automatic version managment during open
  4. Helpers for insert/query/update/delete queries
  5. DB operation executed in a background thread on iOS and Android

通过上面的描述,我们了解到 Sqflite是一个同时支持Android跟Ios平台的数据库,并且支持标准的 CURD操作,下面我们还是用上面操作文件跟sp的代码逻辑是一块体验一下 Sqflite

同样需要引入依赖:

  1. #添加Sqflite依赖
  2. sqflite: ^1.0.0

模拟场景:

利用Sqflite创建一张user表,其中user表中id设置为主键id,且为自增长,name字段为text类型,用户按下存储按钮后,把TextFile输入框里的内容插入到user表中,当按下获取按钮时,取出数据库中最后一条数据显示在下方Text上,并且显示出当前数据库中一共有多少条数据,以及数据库的存储路径。

效果图
12- Flutter 数据存储 - 图4
上述描述样式代码

  1. import 'package:flutter/material.dart';
  2. import 'package:path_provider/path_provider.dart';
  3. import 'dart:io';
  4. import 'package:sqflite/sqflite.dart';
  5. class StoragePage extends StatefulWidget {
  6. @override
  7. State<StatefulWidget> createState() => StorageState();
  8. }
  9. class StorageState extends State {
  10. var _textFieldController = new TextEditingController();
  11. var _storageString = '';
  12. saveString() async {
  13. final db = await getDataBase('my_db.db');
  14. db.transaction((trx) {
  15. trx.rawInsert(
  16. 'INSERT INTO user(name) VALUES("${_textFieldController.value.text.toString()}")');
  17. });
  18. }
  19. Future getString() async {
  20. final db = await getDataBase('my_db.db');
  21. var dbPath = db.path;
  22. setState(() {
  23. db.rawQuery('SELECT * FROM user').then((List<Map> lists) {
  24. print('----------------$lists');
  25. var listSize = lists.length;
  26. _storageString = lists[listSize - 1]['name'] +
  27. "\n现在数据库中一共有${listSize}条数据" +
  28. "\n数据库的存储路径为${dbPath}";
  29. });
  30. });
  31. }
  32. Future<Database> getDataBase(String dbName) async {
  33. final fileDirectory = await getApplicationDocumentsDirectory();
  34. final dbPath = fileDirectory.path;
  35. Database database = await openDatabase(dbPath + "/" + dbName, version: 1,
  36. onCreate: (Database db, int version) async {
  37. await db.execute("CREATE TABLE user (id INTEGER PRIMARY KEY, name TEXT)");
  38. });
  39. return database;
  40. }
  41. @override
  42. Widget build(BuildContext context) {
  43. return new Scaffold(
  44. appBar: new AppBar(
  45. title: new Text('数据存储'),
  46. ),
  47. body: new Column(
  48. mainAxisAlignment: MainAxisAlignment.center,
  49. children: <Widget>[
  50. Text("Sqflite数据库存储", textAlign: TextAlign.center),
  51. TextField(
  52. controller: _textFieldController,
  53. ),
  54. MaterialButton(
  55. onPressed: saveString,
  56. child: new Text("存储"),
  57. color: Colors.cyan,
  58. ),
  59. MaterialButton(
  60. onPressed: getString,
  61. child: new Text("获取"),
  62. color: Colors.deepOrange,
  63. ),
  64. Text('从Sqflite数据库中获取的值为 $_storageString'),
  65. ],
  66. ),
  67. );
  68. }
  69. }

至此,关于Flutter的本地存储相关的内容就全部讲解完了,在本文章中,我为了清晰代码结构跟业务逻辑,复用的都是同一个存储业务逻辑跟UI便于大家结合代码做对比,读者可结合代码自行对比三种存储方式的细节差别。