相信做过原生开发对数据存储并不陌生,在原生 Android 中会把一些轻量级的数据(如用户登录信息、APP配置信息等)写入 SharedPreferences 做存储,在 iOS 中使用 NSUserDefaults 做存储。而对有大批量数据增、删、改、查的需求时,则会选择经过 Sqlite 进行完成。而在 Flutter 中也有官方维护的插件能够完成这些功用。

简单数据耐久化

保存数据到本地磁盘是应用程序常用功用之一,比如保存用户登录信息、用户配置信息等。而保存这些信息通常使用 shared_preferences,它保存数据的方式为 Key-Value(键值对),支撑 Android 和 iOS。shared_preferences 是一个第三方插件,在 iOS 中使用 NSUserDefaults,在 Android 中使用 SharedPreferences

增加依靠

在项目的pubspec.yaml文件中增加依靠:

dependencies:
 shared_preferences: ^2.0.10

执行命令:

flutter pub get

shared_preferences 支撑的数据类型有 int、double、bool、string、stringList

示例代码1

import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
class SaveDataPage extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => SaveDataState();
}
class SaveDataState extends State {
  final _textFieldController = TextEditingController();
  var _storageString = '';
  final SAVE_KEY = 'storage_key';
  //使用SharedPreferences存储数据
  Future saveString() async {
    SharedPreferences sharedPreferences = await SharedPreferences.getInstance();
    sharedPreferences.setString(
        SAVE_KEY, _textFieldController.value.text.toString());
  }
  //获取存在SharedPreferences中的数据
  Future getString() async {
    SharedPreferences sharedPreferences = await SharedPreferences.getInstance();
    setState(() {
      if(sharedPreferences.getString(SAVE_KEY) != null) {
        _storageString = sharedPreferences.getString(SAVE_KEY)!;
      }
    });
  }
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('数据存储'),
      ),
      body: Column(
        children: <Widget>[
          const Text("shared_preferences存储", textAlign: TextAlign.center),
          TextField(
            controller: _textFieldController,
          ),
          MaterialButton(
            onPressed: saveString,
            child: const Text("存储"),
            color: Colors.pink,
          ),
          MaterialButton(
            onPressed: getString,
            child: const Text("获取"),
            color: Colors.lightGreen,
          ),
          Text('shared_preferences存储的值为  $_storageString'),
        ],
      ),
    );
  }
}

实际效果

Flutter-数据持久化的两种方式

示例代码2

这儿是封装一个类进行数据存储,包括保存、获取、删去、清理功用

import 'package:shared_preferences/shared_preferences.dart';
class Storage {
  static Future<void> setString(key, value) async {
    SharedPreferences sp = await SharedPreferences.getInstance();
    sp.setString(key, value);
  }
  static Future<String?> getString(key) async{
    SharedPreferences sp = await SharedPreferences.getInstance();
    sp.getString(key);
  }
  static Future<void> remove(key) async {
    SharedPreferences sp = await SharedPreferences.getInstance();
    sp.remove(key);
  }
  static Future<void> clear() async {
    SharedPreferences sp = await SharedPreferences.getInstance();
    sp.clear();
  }
}

这儿使用这个封装的类,详细包括是这样的功用:

1、获取本地存储里边的数据 (searchList)

2、判别本地存储是否有数据

2.1、如果有数据
     1、读取本地存储的数据
     2、判别本地存储中有没有当时数据,
        如果有不做操作、如果没有当时数据,本地存储的数据和当时数据拼接后从头写入    
2.2、如果没有数据

    直接把当时数据放在数组中写入到本地存储
import 'dart:convert';
import 'storage.dart';
class SearchServices {
  static setHistoryData(keywords) async {
    String? searchList = await Storage.getString('searchList');
    if(searchList != null){
      List searchListData = json.decode(searchList);
      var hasData = searchListData.any((value){
        return value == keywords;
      });
      if(!hasData){
        searchListData.add(keywords);
        await Storage.setString('searchList', json.encode(searchListData));
      }
    } else {
      List tempList = [];
      tempList.add(keywords);
      await Storage.setString('searchList', json.encode(tempList));
    }
  }
  static getHistoryList() async {
    String? searchList = await Storage.getString('searchList');
    if(searchList != null){
      List searchListData = json.decode(searchList);
      return searchListData;
    }
    return [];
  }
  static clearHistoryList() async {
    await Storage.remove('searchList');
  }
  static removeHistoryData(keywords) async {
    String? searchList = await Storage.getString('searchList');
    if(searchList != null){
      List searchListData = json.decode(searchList);
      searchListData.remove(keywords);
      await Storage.setString('searchList', searchList);
    }
  }
}

大量杂乱数据耐久化

对有大批量数据增、删、改、查的需求时,我们就想到了数据库 Sqlite。在Flutter中的数据库叫 Sqflite 跟原生的 Sqlite 叫法不一样,Sqflite 是一个一起支撑 Android 跟 iOS 平台的数据库。

增加依靠

sqflite: ^2.0.1

执行命令:

flutter pub get

使用办法介绍

刺进数据

刺进数据有两种办法能够完成:

Future<int> insert(String table, Map<String, Object?> values,
    {String? nullColumnHack, ConflictAlgorithm? conflictAlgorithm});
Future<int> rawInsert(String sql, [List<Object?>? arguments]);
  • insert 办法第一个参数为操作的表名,第二个参数map中是想要增加的字段名和对应字段值,第三个参数是发生冲突时解决方案。官方给的一个刺进的示例:
var value = {
  'age': 18,
  'name': 'Candy'
};
int id = await db.insert(
  'table',
  value,
  conflictAlgorithm: ConflictAlgorithm.replace,
);
  • rawInsert 办法第一个参数为一条刺进 sql 句子,第二个参数表明填充数据。官方给的一个刺进的示例:
int id1 = await database.rawInsert('INSERT INTO Test(name, value, num) VALUES("some name", 1234, 456.789)');

查询数据

查询数据提供了两种办法:

Future<List<Map<String, Object?>>> query(String table,
    {bool? distinct,
    List<String>? columns,
    String? where,
    List<Object?>? whereArgs,
    String? groupBy,
    String? having,
    String? orderBy,
    int? limit,
    int? offset});
Future<List<Map<String, Object?>>> rawQuery(String sql,
    [List<Object?>? arguments]);
  • query 办法第一个参数为操作的表名,后边的可选参数顺次表明是否去重、查询字段、where子句、where子句占位符参数值、怎么分组、包括哪些行组、怎么排序、查询的条数、查询的偏移位。除了表名和查询字段其他都对错必传的。官方给出的查询示例:
List<Map> maps = await db.query(tableTodo,
  columns: ['columnId', 'columnDone', 'columnTitle'], 
  where: 'columnId = ?', 
  whereArgs: [id]);
  • rawQuery 办法第一个参数是一条查询sql句子。官方给出的查询示例:
List<Map> list = await database.rawQuery('SELECT * FROM Test');

更新数据

更新数据库中的数据,返回修改了的数量,这儿也是提供了两种办法,

Future<int> rawUpdate(String sql, [List<Object?>? arguments]);
Future<int> update(String table, Map<String, Object?> values,
    {String? where,
    List<Object?>? whereArgs,
    ConflictAlgorithm? conflictAlgorithm});
  • rawUpdate办法第一个参数为一条更新sql句子,第二个参数表明更新的数据。官方给出的查询示例:
int count = await database.rawUpdate(
    'UPDATE Test SET name = ?, value = ? WHERE name = ?', 
    ['updated name', '9876', 'some name']);
  • update办法第一个参数为操作的表名,第二个参数为修改的字段和对应值,后边的参数顺次是 where 句子,where 子句占位符参数值,冲突的解决方案。官方给出的查询示例:
int count = await db.update(tableTodo, todo.toMap(),
where: '$columnId = ?', whereArgs: [todo.id]);

删去数据

删去数据也有两种办法,返回删去的数量。

Future<int> rawDelete(String sql, [List<Object?>? arguments]);
Future<int> delete(String table, {String? where, List<Object?>? whereArgs});
  • rawDelete办法第一个参数为一条删去sql句子,第二个参数表明填充数据。官方给出的查询示例:
int count = await database.rawDelete('DELETE FROM Test WHERE name = ?', ['another name']);
  • delete办法第一个参数为操作的表名,后边的可选参数顺次表明 where 子句、where 子句占位符参数值。官方给出的查询示例:
int count = await db.delete(tableTodo, where: 'columnId = ?', whereArgs: [id]);

举个例子

界说了一个 Person 目标,经过封装数据库界说一个类 DBProvider 完成增、删、改、查。

单例模式创立 SQLite

//创立单例模式SQLite
static final DBProvider _singleton = DBProvider._internal();
factory DBProvider() {
  return _singleton;
}
DBProvider._internal();

完好的创立数据库,包括增、删、改、查的代码

import 'dart:io';
import 'package:path_provider/path_provider.dart';
import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart';
class DBProvider {
  //创立单例模式SQLite
  static final DBProvider _singleton = DBProvider._internal();
  factory DBProvider() {
    return _singleton;
  }
  DBProvider._internal();
  static Database? _db;
  Future<Database> get db async {
    if (_db != null) {
      return _db!;
    }
    _db = await _initDB();
    return _db!;
  }
  //初始化数据库
  Future<Database> _initDB() async {
    // 获取数据库文件的存储途径
    Directory documentsDirectory = await getApplicationDocumentsDirectory();
    String path = join(documentsDirectory.path, 'demo.db');
    //界说了数据库的版本
    return await openDatabase(path,
      version: 1, onCreate: _onCreate);
  }
  //创立数据库表
  Future _onCreate(Database db, int version) async {
    return await db.execute('''
          CREATE TABLE $tablePerson (
            $columnId INTEGER PRIMARY KEY,
            $columnName TEXT,
            $columnSex TEXT,
            $columnAge INTEGER,
          ''');
  }
  // 刺进人员信息
  Future<Person> insert(Person person) async {
    person.id = await _db!.insert(tablePerson, person.toMap());
    return person;
  }
  // 查找所有人员信息
  Future<List<Person>?> queryAll() async {
    List<Map> maps = await _db!.query(tablePerson, columns: [
      columnId,
      columnName,
      columnSex,
      columnAge
    ]);
    if (maps.isEmpty) {
      return null;
    }
    List<Person> books = [];
    for (int i = 0; i < maps.length; i++) {
      books.add(Person.fromMap(maps[i]));
    }
    return books;
  }
  // 依据ID查找个人信息
  Future<Person?> getBook(int id) async {
    List<Map> maps = await _db!.query(tablePerson,
        columns: [
          columnId,
          columnName,
          columnSex,
          columnAge
        ],
        where: '$columnId = ?',
        whereArgs: [id]);
    if (maps.isNotEmpty) {
      return Person.fromMap(maps.first);
    }
    return null;
  }
  // 依据ID删去个人信息
  Future<int> delete(int id) async {
    return await _db!.delete(tablePerson, where: '$columnId = ?', whereArgs: [id]);
  }
  // 更新个人信息
  Future<int> update(Person person) async {
    return await _db!.update(tablePerson, person.toMap(),
        where: '$columnId = ?', whereArgs: [person.id]);
  }
}

Person类的代码

const String tablePerson = 'person';
const String columnId = '_id';
const String columnName = 'name';
const String columnSex = 'sex';
const String columnAge = 'age';
class Person {
  int? id;
  String? name;
  String? sex;
  int? age;
  Person({required this.id, required this.name, required this.sex, required this.age});
  Map<String, dynamic> toMap() {
    var map = <String, dynamic>{
      columnName: name,
      columnSex: sex,
      columnAge: age
    };
    map[columnId] = id;
    return map;
  }
  Person.fromMap(Map<dynamic, dynamic> map) {
    id = map[columnId];
    name = map[columnName];
    sex = map[columnSex];
    age = map[columnAge];
  }
}

上面介绍了 SQLite 的基本用法,数据的增删改查是使用频率比较高的,SQLite 还有一些其他的用法,能够依据业务需求去扩展。