PS : 博主用Flutter写的玩安卓:https://github.com/codingmancui/flutter_frolo 欢迎star
Floor 数据库简介
Floor库是Flutter 应用的SQLite抽象支持。Floor库提供了轻量的SQLite抽象,在内存对象与数据库之间自动映射,同时可通过SQL完全控制数据库。
Floor 快速入门
1.添加依赖
dependencies:
flutter:
sdk: flutter
floor: ^0.17.0
dev_dependencies:
floor_generator: ^0.17.0
build_runner: ^1.10.3
2.创建一个实体
它将表示一个数据库表以及业务对象的框架,
@Entity 标记这一个类是对应一个数据库表,可以指定表名,如果不指定默认表名实体名。
@primaryKey 用于标记这是数据表的主键,需要是int属性。
@ignore 用于忽略字段
@Entity(tableName:'history')
class Article {
@primaryKey
int id;
List<Tags> tags;
String title;
@ignore
int itemType = 0;
Article(
{this.id,
this.tags,
this.title,
this.itemType});
}
3.创建一个DAO(数据访问对象)
该组件负责管理对底层SQLite数据库的访问,抽象类包含查询数据库的方法签名,这些方法签名必须返回一个Future或Stream
可以通过向方法中添加@Query注释来定义查询,该方法必须返回您正在查询的实体的Future或Stream @insert将方法标记为插入方法。
import 'package:floor/floor.dart';
import 'package:frolo/data/protocol/models.dart';
@dao
abstract class ArticleDao{
@Query('SELECT * FROM HISTORY')
Future<List<Article>> findAllArticles();
@insert
Future<void> insertArticle(Article article);
}
4.创建数据库
它必须是一个继承FloorDatabase的抽象类,此外,需要将@Database()添加到类的签名中,确保将2.创建一个实体这一步创建的实体添加到@Database的entities注解属性中
import 'dart:async';
// required package imports
import 'package:floor/floor.dart';
import 'package:sqflite/sqflite.dart' as sqflite;
import 'package:frolo/data/db/article_dao.dart';
import 'package:frolo/data/protocol/models.dart';
import 'article_tag_converter.dart';
part 'database.g.dart'; // the generated code will be there
@TypeConverters([ArticleTagConverter])
@Database(version: 1, entities: [Article])
abstract class WanAndroidDatabase extends FloorDatabase{
ArticleDao get articleDao;
}
注意:
1.确保添加部分part 'database.g.dart';在这个文件的导入下面,需要注意的是,database必须与数据库定义的文件名进行交换。在本例中,文件名为database.dart,所以part 'database.g.dart';
2.运行以下命令 flutter packages pub run build_runner build,如果需要在文件变动时,自动运行命令使用flutter packages pub run build_runner watch
5.使用生成的代码
为了获得数据库的实例,使用生成的$FloorWanAndroidDatabase类,它允许访问数据库构建器。该名称由$Floor和数据库类名组成传递给databaseBuilder()的字符串将是数据库文件名。
要初始化数据库,请调用build()并使用await确保结果。
final database = await $FloorWanAndroidDatabase.databaseBuilder('wan_android_database.db').build();
final articleDao = database.articleDao;
await articleDao.insertArticle(article);
final result = await articleDao.findAllArticles();
Floor 采坑记
Entity类型存储
1. TypeConverters 类型
由于SQLite只允许存储少数类型的值。当需要存储更复杂的Dart在内存中的对象时,有时需要在Dart和SQLite兼容类型之间进行转换。
在上面的demo中,实体类Article中tags字段为List<Tags>类型,我们如果如果不使用TypeConverters类型,在执行 flutter packages pub run build_runner build时便会报错,列表不支持<Tags*>*列类型
Column type is not supported for List<Tags*>*.
package:frolo/data/protocol/models.dart:63:14
╷
63 │ List<Tags> tags;
│ ^^^^
2. TypeConverters 实现和用法
- 创建实现抽象
TypeConverter的转换器类,并将内存中的对象类型和数据库类型作为参数化类型提供。这个类继承decode()和encode()函数,它们定义从一种类型到另一种类型的转换。在Demo中将List<Tags>类型转化为String进行数据库存储
class ArticleTagConverter extends TypeConverter<List<Tags>, String> {
@override
List<Tags> decode(String databaseValue) {
List list = json.decode(databaseValue);
List<Tags> tags = new List();
list.map((value) {
tags.add(Tags.fromJson(value));
});
return tags;
}
@override
String encode(List<Tags> value) {
String v = json.encode(value);
return v;
}
}
- 通过使用
@TypeConverters注释将创建的类型转换器应用到数据库,并确保在这里额外导入类型转换器的文件。在数据库文件中导入它总是必要的,因为生成的代码将是数据库文件的一部分,并且这是类型转换器实例化的位置。
@TypeConverters([ArticleTagConverter])
@Database(version: 1, entities: [Article])
abstract class WanAndroidDatabase extends FloorDatabase{
ArticleDao get articleDao;
}
生成文件database.g.dart解析
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'database.dart';
// **************************************************************************
// FloorGenerator
// **************************************************************************
class $FloorWanAndroidDatabase {
/// Creates a database builder for a persistent database.
/// Once a database is built, you should keep a reference to it and re-use it.
static _$WanAndroidDatabaseBuilder databaseBuilder(String name) =>
_$WanAndroidDatabaseBuilder(name);
/// Creates a database builder for an in memory database.
/// Information stored in an in memory database disappears when the process is killed.
/// Once a database is built, you should keep a reference to it and re-use it.
static _$WanAndroidDatabaseBuilder inMemoryDatabaseBuilder() =>
_$WanAndroidDatabaseBuilder(null);
}
class _$WanAndroidDatabaseBuilder {
_$WanAndroidDatabaseBuilder(this.name);
final String name;
final List<Migration> _migrations = [];
Callback _callback;
/// Adds migrations to the builder.
_$WanAndroidDatabaseBuilder addMigrations(List<Migration> migrations) {
_migrations.addAll(migrations);
return this;
}
/// Adds a database [Callback] to the builder.
_$WanAndroidDatabaseBuilder addCallback(Callback callback) {
_callback = callback;
return this;
}
/// Creates the database and initializes it.
Future<WanAndroidDatabase> build() async {
final path = name != null
? await sqfliteDatabaseFactory.getDatabasePath(name)
: ':memory:';
final database = _$WanAndroidDatabase();
database.database = await database.open(
path,
_migrations,
_callback,
);
return database;
}
}
class _$WanAndroidDatabase extends WanAndroidDatabase {
_$WanAndroidDatabase([StreamController<String> listener]) {
changeListener = listener ?? StreamController<String>.broadcast();
}
ArticleDao _articleDaoInstance;
Future<sqflite.Database> open(String path, List<Migration> migrations,
[Callback callback]) async {
final databaseOptions = sqflite.OpenDatabaseOptions(
version: 1,//版本号
onConfigure: (database) async {
await database.execute('PRAGMA foreign_keys = ON');
},
onOpen: (database) async {
await callback?.onOpen?.call(database);
},
// 数据库升级
onUpgrade: (database, startVersion, endVersion) async {
await MigrationAdapter.runMigrations(
database, startVersion, endVersion, migrations);
await callback?.onUpgrade?.call(database, startVersion, endVersion);
},
// 数据库创建
onCreate: (database, version) async {
await database.execute(
'CREATE TABLE IF NOT EXISTS `history` (`id` INTEGER, `apkLink` TEXT, `audit` INTEGER, `author` TEXT, `canEdit` INTEGER, `chapterId` INTEGER, `chapterName` TEXT, `collect` INTEGER, `courseId` INTEGER, `desc` TEXT, `descMd` TEXT, `envelopePic` TEXT, `fresh` INTEGER, `link` TEXT, `niceDate` TEXT, `niceShareDate` TEXT, `origin` TEXT, `prefix` TEXT, `projectLink` TEXT, `publishTime` INTEGER, `realSuperChapterId` INTEGER, `selfVisible` INTEGER, `shareDate` INTEGER, `shareUser` TEXT, `superChapterId` INTEGER, `superChapterName` TEXT, `tags` TEXT, `title` TEXT, `type` INTEGER, `userId` INTEGER, `visible` INTEGER, `zan` INTEGER, PRIMARY KEY (`id`))');
await callback?.onCreate?.call(database, version);
},
);
return sqfliteDatabaseFactory.openDatabase(path, options: databaseOptions);
}
@override
ArticleDao get articleDao {
return _articleDaoInstance ??= _$ArticleDao(database, changeListener);
}
}
class _$ArticleDao extends ArticleDao {
_$ArticleDao(this.database, this.changeListener)
: _queryAdapter = QueryAdapter(database),
//1 数据库写入,如果字段使用转换器则调用转换器encode方法
_articleInsertionAdapter = InsertionAdapter(
database,
'history',
(Article item) => <String, dynamic>{
'id': item.id,
'tags': _articleTagConverter.encode(item.tags),
'title': item.title,
});
final sqflite.DatabaseExecutor database;
final StreamController<String> changeListener;
final QueryAdapter _queryAdapter;
final InsertionAdapter<Article> _articleInsertionAdapter;
@override
Future<List<Article>> findAllArticles() async {//数据库读取,该方法默认返回空对象,需要手动实现
return _queryAdapter.queryList('SELECT * FROM history',
mapper: (Map<String, dynamic> row) => Article(
id: row['id'] as int,
tags: _articleTagConverter.decode(row['tags']),
title: row['title'] as String,
));
}
@override
Future<void> insertArticle(Article article) async {
await _articleInsertionAdapter.insert(article, OnConflictStrategy.replace);//可以自定primaryKey冲突策略
}
}
// ignore_for_file: unused_element 转换器创建
final _articleTagConverter = ArticleTagConverter();
- 这里需要注意的是,由于该文件是自动创建,有些是需要手动修改的,在Demo中博主就遇到这个坑,查询到的数据全是空对象,最终问题是自动生成的代码中
findAllArticles方法如下:查询到的row转换为Article时实际创建的是空对象
@override
Future<List<Article>> findAllArticles() async {//数据库读取
return _queryAdapter.queryList('SELECT * FROM history',
mapper: (Map<String, dynamic> row) => Article());
}
- 在数据库insert过程中如果
primaryKey冲突,我们可以指定冲突策略,一共有五种策略,这里Demo中使用的是OnConflictStrategy.replace。这里是不是很眼熟,比较像线程池中的拒绝策略
@override
Future<void> insertArticle(Article article) async {
await _articleInsertionAdapter.insert(article, OnConflictStrategy.replace);
}
PS : 博主用Flutter写的玩安卓:https://github.com/codingmancui/flutter_frolo 欢迎star