dev_dependencies: flutter_test: sdk: flutter # ... floor_generator: ^1.4.1 build_runner: ^2.3.3接下来我们就以之前的备忘录为例,来看看使用 floor 后的改善。
// 堆代码 duidaima.com @entity class Memo { @PrimaryKey(autoGenerate: true) final int? id; String title; String content; @ColumnInfo(name: 'created_time') DateTime createdTime; @ColumnInfo(name: 'modified_time') DateTime modifiedTime; List<String> tags; Memo({ this.id, required this.title, required this.content, required this.createdTime, required this.modifiedTime, required this.tags, }); }这里说明一下常见的注解:
@ignore:忽略某个成员属性,即该属性不产生相应的数据表字段。注意,通过 get 方法产生的计算属性默认就会被忽略,例如长方形面积 double get area => width * height。
@dao abstract class MemoDao { @Query('SELECT * FROM Memo ORDER BY modified_time DESC') Future<List<Memo>> findAllMemos(); @Query( 'SELECT * FROM Memo WHERE title LIKE :searchKey OR content LIKE :searchKey ORDER BY modified_time DESC') Future<List<Memo>> findMemoWithSearchKey(String searchKey); @Query('SELECT * FROM Memo WHERE id = :id') Stream<Memo?> findMemoById(int id); @insert Future<void> insertMemo(Memo memo); @Update(onConflict: OnConflictStrategy.replace) Future<void> updateMemo(Memo memo); @delete Future<void> deleteMemo(Memo memo); }
class StringListConverter extends TypeConverter<List<String>, String> { @override List<String> decode(String databaseValue) { return databaseValue.isNotEmpty ? databaseValue.split('|') : []; } @override String encode(List<String> value) { return value.join('|'); } } class DateTimeConverter extends TypeConverter<DateTime, int> { @override DateTime decode(int databaseValue) { return DateTime.fromMillisecondsSinceEpoch(databaseValue); } @override int encode(DateTime value) { return value.millisecondsSinceEpoch; } }使用转换器只需要在定义数据库FloorDatabase 的抽象类的时候引入到注解@TypeConverters就可以了。
@TypeConverters([StringListConverter, DateTimeConverter]) @Database(version: 1, entities: [Memo]) abstract class MemoDatabase extends FloorDatabase { MemoDao get memoDao; }代码改造
Future<void> main() async { WidgetsFlutterBinding.ensureInitialized(); final database = await $FloorMemoDatabase.databaseBuilder('app_database.db').build(); final dao = database.memoDao; getIt.registerSingleton<MemoDao>(dao, signalsReady: true); runApp(const MyApp()); }这里调用ensureInitialized这个方法是保证 Flutter 和原生交互的部分已经完成,因为在 sqflite 中需要使用原生的文件存储。备忘录列表的代码改造涉及数据操作的有两处,分别是列表刷新和删除备忘录。列表模糊搜索时需要自己组装模糊搜索的字符,比如我们这里使用了百分号将搜索关键词包裹实现任意匹配。删除备忘录需要根据是否有搜索调用不同的方法,这是因为对应的 SQL 不同。
void _refreshMemoList({String? searchKey}) async { List<Memo> memoList = searchKey == null ? await GetIt.I<MemoDao>().findAllMemos() : await GetIt.I<MemoDao>().findMemoWithSearchKey('%$searchKey%'); setState(() { _memoList = memoList; }); }删除就非常简单了,直接调用删除方法就好了。
void _deleteMemo(Memo memo) async { final confirmed = await _showDeleteConfirmationDialog(memo); if (confirmed != null && confirmed) { await GetIt.I<MemoDao>().deleteMemo(memo); _refreshMemoList(); if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar(SnackBar( content: Text('已删除 "${memo.title}"'), duration: const Duration(seconds: 2), )); } }添加备忘录的页面只需要更改保存备忘录的方法,而且因为不需要再对时间做转换,方法更为简洁。
Future<void> _saveMemo(BuildContext context) async { var memo = Memo( title: _title, content: _content, createdTime: DateTime.now(), modifiedTime: DateTime.now(), tags: _tags); // 保存备忘录 await GetIt.I<MemoDao>().insertMemo(memo); }编辑备忘录页面也类似,调用 updateMemo 方法即可完成保存。
Future<void> _saveMemo(BuildContext context) async { widget.memo.title = _title; widget.memo.content = _content; widget.memo.modifiedTime = DateTime.now(); // 保存备忘录 await GetIt.I<MemoDao>().updateMemo(widget.memo); }