likes
comments
collection
share

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

作者站长头像
站长
· 阅读数 4

相信做过原生开发对数据存储并不陌生,在原生 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 还有一些其他的用法,可以根据业务需求去扩展。