Flutter入门进阶之旅(十二)Flutter 数据存储

前言

之前的章节我们基本上把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时很可能会报错


dependencies:
  flutter:
    sdk: flutter
    
  # 添加sharedPreference依赖
  shared_preferences: ^0.5.0

dev_dependencies:
  flutter_test:
    sdk: flutter
    
  # 引入本地资源图片
  assets:
     - images/a.png
     - images/aaa.png

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

笔者的话

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

继续上面的内容,我们先来体验一下sharedPreference,贴个图大家放松一下。


sharedPreference

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

import 'package:shared_preferences/shared_preferences.dart';

存数据

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

/**
   * 利用SharedPreferences存储数据
   */
  Future saveString() async {
    SharedPreferences sharedPreferences = await SharedPreferences.getInstance();
    sharedPreferences.setString(
        STORAGE_KEY, _textFieldController.value.text.toString());
  }

SharedPreferences中为我们提供了String、bool、Double、Int、StringList数据类型的存取。

SharedPreferences

取数据

/**
   * 获取存在SharedPreferences中的数据
   */
  Future getString() async {
    SharedPreferences sharedPreferences = await SharedPreferences.getInstance();
    setState(() {
      _storageString = sharedPreferences.get(STORAGE_KEY);
    });
  }

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

完整代码:

import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';

class StoragePage extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => StorageState();
}

class StorageState extends State {
  var _textFieldController = new TextEditingController();
  var _storageString = '';
  final STORAGE_KEY = 'storage_key';

  /**
   * 利用SharedPreferences存储数据
   */
  Future saveString() async {
    SharedPreferences sharedPreferences = await SharedPreferences.getInstance();
    sharedPreferences.setString(
        STORAGE_KEY, _textFieldController.value.text.toString());
  }

  /**
   * 获取存在SharedPreferences中的数据
   */
  Future getString() async {
    SharedPreferences sharedPreferences = await SharedPreferences.getInstance();
    setState(() {
      _storageString = sharedPreferences.get(STORAGE_KEY);
    });
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text('数据存储'),
      ),
      body: new Column(
        children: <Widget>[
          Text("shared_preferences存储", textAlign: TextAlign.center),
          TextField(
            controller: _textFieldController,
          ),
          MaterialButton(
            onPressed: saveString,
            child: new Text("存储"),
            color: Colors.pink,
          ),
          MaterialButton(
            onPressed: getString,
            child: new Text("获取"),
            color: Colors.lightGreen,
          ),
          Text('shared_preferences存储的值为  $_storageString'),


        ],
      ),
    );
  }
}

2.文件存储

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

  # 添加文件依赖
  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平台可以使用

来看下操作文件的效果图

文件存储

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

样例代码

import 'package:flutter/material.dart';
import 'package:path_provider/path_provider.dart';
import 'dart:io';

class StoragePage extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => StorageState();
}

class StorageState extends State {
  var _textFieldController = new TextEditingController();
  var _storageString = '';

  /**
   * 利用文件存储数据
   */
  saveString() async {
    final file = await getFile('file.text');
    //写入字符串
    file.writeAsString(_textFieldController.value.text.toString());
  }

  /**
   * 获取存在文件中的数据
   */
  Future getString() async {
    final file = await getFile('file.text');
    var filePath  = file.path;
    setState(() {
      file.readAsString().then((String value) {
        _storageString = value +'\n文件存储路径:'+filePath;
      });
    });
  }

  /**
   * 初始化文件路径
   */
  Future<File> getFile(String fileName) async {
    //获取应用文件目录类似于Ios的NSDocumentDirectory和Android上的 AppData目录
    final fileDirectory = await getApplicationDocumentsDirectory();

    //获取存储路径
    final filePath = fileDirectory.path;

    //或者file对象(操作文件记得导入import 'dart:io')
    return new File(filePath + "/"+fileName);
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text('数据存储'),
      ),
      body: new Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          Text("文件存储", textAlign: TextAlign.center),
          TextField(
            controller: _textFieldController,
          ),
          MaterialButton(
            onPressed: saveString,
            child: new Text("存储"),
            color: Colors.cyan,
          ),
          MaterialButton(
            onPressed: getString,
            child: new Text("获取"),
            color: Colors.deepOrange,
          ),
          Text('从文件存储中获取的值为  $_storageString'),
        ],
      ),
    );
  }
}

3.Sqflite

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

SQLite plugin for Flutter. Supports both iOS and Android.

 Support transactions and batches
 Automatic version managment during open
 Helpers for insert/query/update/delete queries
 DB operation executed in a background thread on iOS and Android

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

同样需要引入依赖:

  #添加Sqflite依赖
  sqflite: ^1.0.0

模拟场景:

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

效果图

Sqflite

上述描述样式代码

import 'package:flutter/material.dart';
import 'package:path_provider/path_provider.dart';
import 'dart:io';
import 'package:sqflite/sqflite.dart';

class StoragePage extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => StorageState();
}

class StorageState extends State {
  var _textFieldController = new TextEditingController();
  var _storageString = '';

  /**
   * 利用Sqflite数据库存储数据
   */
  saveString() async {
    final db = await getDataBase('my_db.db');
    //写入字符串
    db.transaction((trx) {
      trx.rawInsert(
          'INSERT INTO user(name) VALUES("${_textFieldController.value.text.toString()}")');
    });
  }

  /**
   * 获取存在Sqflite数据库中的数据
   */
  Future getString() async {
    final db = await getDataBase('my_db.db');
    var dbPath = db.path;
    setState(() {
      db.rawQuery('SELECT * FROM user').then((List<Map> lists) {
        print('----------------$lists');
        var listSize = lists.length;
        //获取数据库中的最后一条数据
        _storageString = lists[listSize - 1]['name'] +
            "\n现在数据库中一共有${listSize}条数据" +
            "\n数据库的存储路径为${dbPath}";
      });
    });
  }

  /**
   * 初始化数据库存储路径
   */
  Future<Database> getDataBase(String dbName) async {
    //获取应用文件目录类似于Ios的NSDocumentDirectory和Android上的 AppData目录
    final fileDirectory = await getApplicationDocumentsDirectory();

    //获取存储路径
    final dbPath = fileDirectory.path;

    //构建数据库对象
    Database database = await openDatabase(dbPath + "/" + dbName, version: 1,
        onCreate: (Database db, int version) async {
      await db.execute("CREATE TABLE user (id INTEGER PRIMARY KEY, name TEXT)");
    });

    return database;
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text('数据存储'),
      ),
      body: new Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          Text("Sqflite数据库存储", textAlign: TextAlign.center),
          TextField(
            controller: _textFieldController,
          ),
          MaterialButton(
            onPressed: saveString,
            child: new Text("存储"),
            color: Colors.cyan,
          ),
          MaterialButton(
            onPressed: getString,
            child: new Text("获取"),
            color: Colors.deepOrange,
          ),
          Text('从Sqflite数据库中获取的值为  $_storageString'),
        ],
      ),
    );
  }
}

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

下一篇:Flutter入门进阶之旅(十三)Flutter 路由

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270

推荐阅读更多精彩内容