Flutter中持久化存储数据有多种方案, 一般常用的有 shared_preferences 和 sqfite
shared_preferences: 包含NSUserDefaults(在iOS上)和SharedPreferences(在Android上),为简单数据提供持久存储。数据以异步方式持久保存到磁盘。
sqflite: 是一款轻量级的关系型数据库,类似SQLite. 支持iOS和Android。适用于存储数据库 , 表类型的数据.
作者所用版本为1.1.3
dependencies:
...
sqflite: ^1.1.3
先贴一下代码 ,然后我们逐行分析:
/*
* author: Created by 李卓原 on 2019/3/12.
* email: [email protected]
*
*/
import 'dart:async';
import 'package:path/path.dart';
import 'package:sale_aggregator_app/models/video.dart';
import 'package:sqflite/sqflite.dart';
class DatabaseHelper {
static final DatabaseHelper _instance = new DatabaseHelper.internal();
factory DatabaseHelper() => _instance;
final String tableVideo = 'VideoTable';
final String columnId = 'id';
final String image = 'image';
final String url = 'url';
final String duration = 'duration';
final String title = 'title';
final String favoriteStatus = 'favorite_status';
static Database _db;
DatabaseHelper.internal();
Future<Database> get db async {
if (_db != null) {
return _db;
}
_db = await initDb();
return _db;
}
initDb() async {
String databasesPath = await getDatabasesPath();
String path = join(databasesPath, 'flashgo.db');
var db = await openDatabase(path, version: 1, onCreate: _onCreate);
return db;
}
void _onCreate(Database db, int newVersion) async {
await db.execute(
'CREATE TABLE $tableVideo($columnId INTEGER PRIMARY KEY, $image TEXT, $url TEXT, $duration INTEGER, $title TEXT, $favoriteStatus TEXT)');
}
Future<int> insertVideo(Video video) async {
var dbClient = await db;
var result = await dbClient.insert(tableVideo, video.toJson());
return result;
}
Future<List> selectVideos({int limit, int offset}) async {
var dbClient = await db;
var result = await dbClient.query(
tableVideo,
columns: [columnId, image, url, duration, title, favoriteStatus],
limit: limit,
offset: offset,
);
List<Video> videos = [];
result.forEach((item) => videos.add(Video.fromSql(item)));
return videos;
}
Future<int> getCount() async {
var dbClient = await db;
return Sqflite.firstIntValue(
await dbClient.rawQuery('SELECT COUNT(*) FROM $tableVideo'));
}
Future<Video> getVideo(int id) async {
var dbClient = await db;
List<Map> result = await dbClient.query(tableVideo,
columns: [columnId, image, url, duration, title, favoriteStatus],
where: '$id = ?',
whereArgs: [id]);
if (result.length > 0) {
return Video.fromSql(result.first);
}
return null;
}
Future<int> deleteNote(String images) async {
var dbClient = await db;
return await dbClient
.delete(tableVideo, where: '$image = ?', whereArgs: [images]);
}
Future<int> updateNote(Video video) async {
var dbClient = await db;
return await dbClient.update(tableVideo, video.toJson(),
where: "$columnId = ?", whereArgs: [video.id]);
}
Future close() async {
var dbClient = await db;
return dbClient.close();
}
}
可以看到我们在执行相关方法的时候都会先获得db, db会执行一个initDb方法,用于创建数据库和表
initDb() async {
String databasesPath = await getDatabasesPath();
String path = join(databasesPath, 'flashgo.db');
var db = await openDatabase(path, version: 1, onCreate: _onCreate);
return db;
}
void _onCreate(Database db, int newVersion) async {
await db.execute(
'CREATE TABLE $tableVideo($columnId INTEGER PRIMARY KEY, $image TEXT, $url TEXT, $duration INTEGER, $title TEXT, $favoriteStatus TEXT)');
}
然后是我定义的一个实体类,看一下代码:
class Video {
int id;
String image;
String url;
int duration;
String title;
bool favoriteStatus;
Video(
{this.id,
this.image,
this.url,
this.duration,
this.title,
this.favoriteStatus});
Video.fromJson(Map json) {
id = json['id'];
image = json['image'];
url = json['url'];
duration = json['duration'];
title = json['title'];
favoriteStatus = json['favorite_status'];
}
Video.fromSql(Map json) {
id = json['id'];
image = json['image'];
url = json['url'];
duration = json['duration'];
title = json['title'];
favoriteStatus = json['favorite_status'] == 'true';
}
Map toJson() {
final Map data = new Map();
data['id'] = this.id;
data['image'] = this.image;
data['url'] = this.url;
data['duration'] = this.duration;
data['title'] = this.title;
data['favorite_status'] = this.favoriteStatus;
return data;
}
}
细心的同学可能看到了一个与众不同的方法: fromSql
,
其中的json['favorite_status']
是字符串类型, 为什么不依然用bool型呢, 因为sqlite不支持bool型.
上文中,CREATE TABLE $tableVideo($columnId INTEGER PRIMARY KEY, $image TEXT, $url TEXT, $duration INTEGER, $title TEXT, $favoriteStatus TEXT)
这个建表方法可以看到
id用的是integer, 其他都是用的text.
我们看一下sqlite都支持哪些数据类型吧:
每个存储在 SQLite 数据库中的值都具有以下存储类之一:
存储类 | 描述 |
---|---|
NULL | 值是一个 NULL 值。 |
INTEGER | 值是一个带符号的整数,根据值的大小存储在 1、2、3、4、6 或 8 字节中。 |
REAL | 值是一个浮点值,存储为 8 字节的 IEEE 浮点数字。 |
TEXT | 值是一个文本字符串,使用数据库编码(UTF-8、UTF-16BE 或 UTF-16LE)存储。 |
BLOB | 值是一个 blob 数据,完全根据它的输入存储。 |
数据库和表已经准备就绪了,那么该看一看它的增删改查了
Future insertVideo(Video video) async {
var dbClient = await db;
var result = await dbClient.insert(tableVideo, video.toJson());
return result;
}
这里是我封装的一个插入数据的方法,参数是video的对象, 但是可以看到insert
方法第二个参数是一个json数据,所以其实也可以直接传递json数据.而不是传一个对象再转成json.
Future selectVideos({int limit, int offset}) async {
var dbClient = await db;
var result = await dbClient.query(
tableVideo,
columns: [columnId, image, url, duration, title, favoriteStatus],
limit: limit,
offset: offset,
);
List
主要是调用了query
方法,先看一下源码:
Future>> query(String table,
{bool distinct,
List columns,
String where,
List whereArgs,
String groupBy,
String having,
String orderBy,
int limit,
int offset});
有一个必传参数是表名,
然后有很多修饰,
比如
limit : 是要查询多少条数据,
offset :是从哪里开始查.
columns: 是要查询哪几列
where: 是查询条件,这里我是查询所有的所以没有设置.
limit 和offset 这两个也是最常用的属性,所以我封装方法的时候允许设置这两个参数.
如果你有多张表,多个列需要查询,我建议各自封装方法,不然的话,需要传入的参数过于复杂便失去了封装的意义.
这里的查询方法返回的是json数据,且要记住,是只有integer和text类型的,所以想要bool一定要自己处理
List
所以这里,我新建了一个fromSql的方法,把查询出来的json数据转成我想要的类型的对象.
Future
思路同上,只是要多了一个where,即查询条件,这里我是根据id来查 所以只传入了一个id参数.
返回查询到的结果(json类型) .
如果查询不到,则返回一个null.
Future updateVideo(Video video) async {
var dbClient = await db;
return await dbClient.update(tableVideo, video.toJson(),
where: "$columnId = ?", whereArgs: [video.id]);
}
这个代码的逻辑是
Future deleteVideo(String images) async {
var dbClient = await db;
return await dbClient
.delete(tableVideo, where: '$image = ?', whereArgs: [images]);
}
其实逻辑和查询是一样的,我这是根据image来查找并删除.也可以用id或者其他数据.
Future getCount() async {
var dbClient = await db;
return Sqflite.firstIntValue(
await dbClient.rawQuery('SELECT COUNT(*) FROM $tableVideo'));
}
此方法用来查询表中有多少条数据.
这我用了rawQuery
方法, 它是支持直接使用sql语句进行查询的.
因为该结果返回一个列表,所以使用Sqflite.firstIntValue
来获取其中的第一个值.
Future close() async {
var dbClient = await db;
return dbClient.close();
}
在操作执行完毕后 , 记得关闭数据库.关闭之后无法再访问数据库.
以上是对代码的分析,下面看一下实际的使用:
//把视频列表存到数据库以备用
void saveVideos(List
相关代码尽在github