Flutter中持久化存儲數據有多種方案, 一般常用的有 shared_preferences 和 sqfite
-
shared_preferences: 包含NSUserDefaults(在iOS上)和SharedPreferences(在Android上),爲簡單數據提供持久存儲。數據以異步方式持久保存到磁盤。
-
sqflite: 是一款輕量級的關係型數據庫,類似SQLite. 支持iOS和Android。適用於存儲數據庫 , 表類型的數據.
sqflite的使用
添加依賴:
作者所用版本爲1.1.3
dependencies:
...
sqflite: ^1.1.3
爲了方便,外面創建一個DatabaseHelper來封裝一些數據庫的相關操作:
先貼一下代碼 ,然後我們逐行分析:
/*
* 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)');
}
- getDatabasesPath() : 獲取默認數據庫位置(在Android上,它通常是data/data/<package_name>/databases,在iOS上,它是Documents目錄)
- join(databasesPath, ‘flashgo.db’): 相當於在上述方法獲取到的位置創建了一個名爲flashgo的數據庫.
- openDatabase: 按指定路徑打開數據庫 , 路徑就是上面flash.db的路徑,version爲數據庫版本號,onCreate是創建表的方法
然後是我定義的一個實體類,看一下代碼:
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<String, dynamic> json) {
id = json['id'];
image = json['image'];
url = json['url'];
duration = json['duration'];
title = json['title'];
favoriteStatus = json['favorite_status'];
}
Video.fromSql(Map<String, dynamic> json) {
id = json['id'];
image = json['image'];
url = json['url'];
duration = json['duration'];
title = json['title'];
favoriteStatus = json['favorite_status'] == 'true';
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
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都支持哪些數據類型吧:
sql存儲數據類型
每個存儲在 SQLite 數據庫中的值都具有以下存儲類之一:
存儲類 | 描述 |
---|---|
NULL | 值是一個 NULL 值。 |
INTEGER | 值是一個帶符號的整數,根據值的大小存儲在 1、2、3、4、6 或 8 字節中。 |
REAL | 值是一個浮點值,存儲爲 8 字節的 IEEE 浮點數字。 |
TEXT | 值是一個文本字符串,使用數據庫編碼(UTF-8、UTF-16BE 或 UTF-16LE)存儲。 |
BLOB | 值是一個 blob 數據,完全根據它的輸入存儲。 |
數據庫和表已經準備就緒了,那麼該看一看它的增刪改查了
插入數據
Future<int> insertVideo(Video video) async {
var dbClient = await db;
var result = await dbClient.insert(tableVideo, video.toJson());
return result;
}
這裏是我封裝的一個插入數據的方法,參數是video的對象, 但是可以看到insert
方法第二個參數是一個json數據,所以其實也可以直接傳遞json數據.而不是傳一個對象再轉成json.
查詢方法
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;
}
主要是調用了query
方法,先看一下源碼:
Future<List<Map<String, dynamic>>> query(String table,
{bool distinct,
List<String> columns,
String where,
List<dynamic> whereArgs,
String groupBy,
String having,
String orderBy,
int limit,
int offset});
有一個必傳參數是表名,
然後有很多修飾,
比如
limit : 是要查詢多少條數據,
offset :是從哪裏開始查.
columns: 是要查詢哪幾列
where: 是查詢條件,這裏我是查詢所有的所以沒有設置.
limit 和offset 這兩個也是最常用的屬性,所以我封裝方法的時候允許設置這兩個參數.
如果你有多張表,多個列需要查詢,我建議各自封裝方法,不然的話,需要傳入的參數過於複雜便失去了封裝的意義.
這裏的查詢方法返回的是json數據,且要記住,是隻有integer和text類型的,所以想要bool一定要自己處理
List<Video> videos = [];
result.forEach((item) => videos.add(Video.fromSql(item)));
所以這裏,我新建了一個fromSql的方法,把查詢出來的json數據轉成我想要的類型的對象.
查詢單個
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;
}
思路同上,只是要多了一個where,即查詢條件,這裏我是根據id來查 所以只傳入了一個id參數.
返回查詢到的結果(json類型) .
如果查詢不到,則返回一個null.
更改數據
Future<int> updateVideo(Video video) async {
var dbClient = await db;
return await dbClient.update(tableVideo, video.toJson(),
where: "$columnId = ?", whereArgs: [video.id]);
}
這個代碼的邏輯是
- 傳入更改後的數據
- 根據傳入的數據id找到對應數據
- 更新數據
刪除數據
Future<int> deleteVideo(String images) async {
var dbClient = await db;
return await dbClient
.delete(tableVideo, where: '$image = ?', whereArgs: [images]);
}
其實邏輯和查詢是一樣的,我這是根據image來查找並刪除.也可以用id或者其他數據.
獲取數據的數量
Future<int> 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<Video> videos) async {
var db = DatabaseHelper();
videos.forEach((v) => db.insertVideo(v));
db.close();
}
相關代碼盡在github