Flutter-数据持久化的两种方式
相信做过原生开发对数据存储并不陌生,在原生 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'),
        ],
      ),
    );
  }
}
实际效果

示例代码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 还有一些其他的用法,可以根据业务需求去扩展。
转载自:https://juejin.cn/post/7040986659533357087




