Moor 注解生成数据库
- 统一规范
- 减少写重复代码
1.配置
dependencies:
flutter:
sdk: flutter
# Dart
cupertino_icons: ^1.0.3
moor: ^4.3.2
sqlite3_flutter_libs: ^0.5.0
path_provider: ^2.0.2
moor_db_viewer: ^4.0.0
dev_dependencies:
moor_generator: ^4.3.1
build_runner: ^2.0.5
2.创建数据库表格实例
import 'package:moor/moor.dart';
// 表格实例
@DataClassName("Todo")
class Todos extends Table {
IntColumn get id => integer().autoIncrement()();
TextColumn get title => text().withLength(min: 1, max: 50)();
TextColumn get content => text().nullable().named('description')();
IntColumn get category => integer().nullable()();
BoolColumn get completed => boolean().withDefault(Constant(false))();
}
3.创建数据库文件
import 'dart:io';
import 'package:moor/ffi.dart';
import 'package:moor/moor.dart';
import 'package:path/path.dart' as p;
import 'package:path_provider/path_provider.dart';
import '../bean/todos.dart';
//记得添加
part 'todo_database.g.dart';
@UseMoor(tables: [Todos])
class TodoDatabase extends _$TodoDatabase {
//创建数据库实例,开启数据库连接
TodoDatabase() : super(_openConnection());
//数据库版本控制
@override int get schemaVersion => 1;
//升级配置
@override MigrationStrategy get migration => MigrationStrategy(
onUpgrade: (migrator, from, to) async {
if (from == 1) {
migrator.deleteTable(todos.actualTableName);
migrator.createTable(todos);
}},
beforeOpen: (details) async {
await customStatement('PRAGMA foreign_keys = ON');
});
}
// 开启数据库连接
LazyDatabase _openConnection() {
return LazyDatabase(() async {
final dbFolder = await getApplicationDocumentsDirectory();
final file = File(p.join(dbFolder.path, 'db.sqlite'));
return VmDatabase(file, logStatements: true);
});
}
4.生成数据库创建的实现类
flutter pub run build_runner build
或者
flutter packages pub run build_runner watch
或者
flutter packages pub run build_runner watch --delete-conflicting-outputs
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'todo_database.dart';
// **************************************************************************
// MoorGenerator
// **************************************************************************
// ignore_for_file: unnecessary_brace_in_string_interps, unnecessary_this
class Todo extends DataClass implements Insertable<Todo> {
final int id;
final String title;
final String? content;
final int? category;
final bool completed;
Todo(
{required this.id,
required this.title,
this.content,
this.category,
required this.completed});
factory Todo.fromData(Map<String, dynamic> data, GeneratedDatabase db,
{String? prefix}) {
final effectivePrefix = prefix ?? '';
return Todo(
id: const IntType()
.mapFromDatabaseResponse(data['${effectivePrefix}id'])!,
title: const StringType()
.mapFromDatabaseResponse(data['${effectivePrefix}title'])!,
content: const StringType()
.mapFromDatabaseResponse(data['${effectivePrefix}description']),
category: const IntType()
.mapFromDatabaseResponse(data['${effectivePrefix}category']),
completed: const BoolType()
.mapFromDatabaseResponse(data['${effectivePrefix}completed'])!,
);
}
@override
Map<String, Expression> toColumns(bool nullToAbsent) {
final map = <String, Expression>{};
map['id'] = Variable<int>(id);
map['title'] = Variable<String>(title);
if (!nullToAbsent || content != null) {
map['description'] = Variable<String?>(content);
}
if (!nullToAbsent || category != null) {
map['category'] = Variable<int?>(category);
}
map['completed'] = Variable<bool>(completed);
return map;
}
TodosCompanion toCompanion(bool nullToAbsent) {
return TodosCompanion(
id: Value(id),
title: Value(title),
content: content == null && nullToAbsent
? const Value.absent()
: Value(content),
category: category == null && nullToAbsent
? const Value.absent()
: Value(category),
completed: Value(completed),
);
}
factory Todo.fromJson(Map<String, dynamic> json,
{ValueSerializer? serializer}) {
serializer ??= moorRuntimeOptions.defaultSerializer;
return Todo(
id: serializer.fromJson<int>(json['id']),
title: serializer.fromJson<String>(json['title']),
content: serializer.fromJson<String?>(json['content']),
category: serializer.fromJson<int?>(json['category']),
completed: serializer.fromJson<bool>(json['completed']),
);
}
@override
Map<String, dynamic> toJson({ValueSerializer? serializer}) {
serializer ??= moorRuntimeOptions.defaultSerializer;
return <String, dynamic>{
'id': serializer.toJson<int>(id),
'title': serializer.toJson<String>(title),
'content': serializer.toJson<String?>(content),
'category': serializer.toJson<int?>(category),
'completed': serializer.toJson<bool>(completed),
};
}
Todo copyWith(
{int? id,
String? title,
String? content,
int? category,
bool? completed}) =>
Todo(
id: id ?? this.id,
title: title ?? this.title,
content: content ?? this.content,
category: category ?? this.category,
completed: completed ?? this.completed,
);
@override
String toString() {
return (StringBuffer('Todo(')
..write('id: $id, ')
..write('title: $title, ')
..write('content: $content, ')
..write('category: $category, ')
..write('completed: $completed')
..write(')'))
.toString();
}
@override
int get hashCode => $mrjf($mrjc(
id.hashCode,
$mrjc(
title.hashCode,
$mrjc(content.hashCode,
$mrjc(category.hashCode, completed.hashCode)))));
@override
bool operator ==(Object other) =>
identical(this, other) ||
(other is Todo &&
other.id == this.id &&
other.title == this.title &&
other.content == this.content &&
other.category == this.category &&
other.completed == this.completed);
}
class TodosCompanion extends UpdateCompanion<Todo> {
final Value<int> id;
final Value<String> title;
final Value<String?> content;
final Value<int?> category;
final Value<bool> completed;
const TodosCompanion({
this.id = const Value.absent(),
this.title = const Value.absent(),
this.content = const Value.absent(),
this.category = const Value.absent(),
this.completed = const Value.absent(),
});
TodosCompanion.insert({
this.id = const Value.absent(),
required String title,
this.content = const Value.absent(),
this.category = const Value.absent(),
this.completed = const Value.absent(),
}) : title = Value(title);
static Insertable<Todo> custom({
Expression<int>? id,
Expression<String>? title,
Expression<String?>? content,
Expression<int?>? category,
Expression<bool>? completed,
}) {
return RawValuesInsertable({
if (id != null) 'id': id,
if (title != null) 'title': title,
if (content != null) 'description': content,
if (category != null) 'category': category,
if (completed != null) 'completed': completed,
});
}
TodosCompanion copyWith(
{Value<int>? id,
Value<String>? title,
Value<String?>? content,
Value<int?>? category,
Value<bool>? completed}) {
return TodosCompanion(
id: id ?? this.id,
title: title ?? this.title,
content: content ?? this.content,
category: category ?? this.category,
completed: completed ?? this.completed,
);
}
@override
Map<String, Expression> toColumns(bool nullToAbsent) {
final map = <String, Expression>{};
if (id.present) {
map['id'] = Variable<int>(id.value);
}
if (title.present) {
map['title'] = Variable<String>(title.value);
}
if (content.present) {
map['description'] = Variable<String?>(content.value);
}
if (category.present) {
map['category'] = Variable<int?>(category.value);
}
if (completed.present) {
map['completed'] = Variable<bool>(completed.value);
}
return map;
}
@override
String toString() {
return (StringBuffer('TodosCompanion(')
..write('id: $id, ')
..write('title: $title, ')
..write('content: $content, ')
..write('category: $category, ')
..write('completed: $completed')
..write(')'))
.toString();
}
}
class $TodosTable extends Todos with TableInfo<$TodosTable, Todo> {
final GeneratedDatabase _db;
final String? _alias;
$TodosTable(this._db, [this._alias]);
final VerificationMeta _idMeta = const VerificationMeta('id');
late final GeneratedColumn<int?> id = GeneratedColumn<int?>(
'id', aliasedName, false,
typeName: 'INTEGER',
requiredDuringInsert: false,
defaultConstraints: 'PRIMARY KEY AUTOINCREMENT');
final VerificationMeta _titleMeta = const VerificationMeta('title');
late final GeneratedColumn<String?> title = GeneratedColumn<String?>(
'title', aliasedName, false,
additionalChecks:
GeneratedColumn.checkTextLength(minTextLength: 1, maxTextLength: 50),
typeName: 'TEXT',
requiredDuringInsert: true);
final VerificationMeta _contentMeta = const VerificationMeta('content');
late final GeneratedColumn<String?> content = GeneratedColumn<String?>(
'description', aliasedName, true,
typeName: 'TEXT', requiredDuringInsert: false);
final VerificationMeta _categoryMeta = const VerificationMeta('category');
late final GeneratedColumn<int?> category = GeneratedColumn<int?>(
'category', aliasedName, true,
typeName: 'INTEGER', requiredDuringInsert: false);
final VerificationMeta _completedMeta = const VerificationMeta('completed');
late final GeneratedColumn<bool?> completed = GeneratedColumn<bool?>(
'completed', aliasedName, false,
typeName: 'INTEGER',
requiredDuringInsert: false,
defaultConstraints: 'CHECK (completed IN (0, 1))',
defaultValue: Constant(false));
@override
List<GeneratedColumn> get $columns =>
[id, title, content, category, completed];
@override
String get aliasedName => _alias ?? 'todos';
@override
String get actualTableName => 'todos';
@override
VerificationContext validateIntegrity(Insertable<Todo> instance,
{bool isInserting = false}) {
final context = VerificationContext();
final data = instance.toColumns(true);
if (data.containsKey('id')) {
context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta));
}
if (data.containsKey('title')) {
context.handle(
_titleMeta, title.isAcceptableOrUnknown(data['title']!, _titleMeta));
} else if (isInserting) {
context.missing(_titleMeta);
}
if (data.containsKey('description')) {
context.handle(_contentMeta,
content.isAcceptableOrUnknown(data['description']!, _contentMeta));
}
if (data.containsKey('category')) {
context.handle(_categoryMeta,
category.isAcceptableOrUnknown(data['category']!, _categoryMeta));
}
if (data.containsKey('completed')) {
context.handle(_completedMeta,
completed.isAcceptableOrUnknown(data['completed']!, _completedMeta));
}
return context;
}
@override
Set<GeneratedColumn> get $primaryKey => {id};
@override
Todo map(Map<String, dynamic> data, {String? tablePrefix}) {
return Todo.fromData(data, _db,
prefix: tablePrefix != null ? '$tablePrefix.' : null);
}
@override
$TodosTable createAlias(String alias) {
return $TodosTable(_db, alias);
}
}
abstract class _$TodoDatabase extends GeneratedDatabase {
_$TodoDatabase(QueryExecutor e) : super(SqlTypeSystem.defaultInstance, e);
late final $TodosTable todos = $TodosTable(this);
@override
Iterable<TableInfo> get allTables => allSchemaEntities.whereType<TableInfo>();
@override
List<DatabaseSchemaEntity> get allSchemaEntities => [todos];
}
注意事项
解决Flutter Conflicting outputs were detected and the build is unable to prompt for permission to...
原因如下:
原因其实日志也说明了,在提交代码的时候把flutter packages pub run build_runner build生成的xxxx.g.dart也提了上去。运行flutter packages pub run build_runner build不光生成了xxxx.g.dart。同时还成了一个dart_tool/build目录,入过程所说的只运行app是没有问题的,之前xxxx.g.dart还是可用的。但是再生成新的xxxx.g.dart因为缺少之前生成dart_tool/build目下的文件就会报错。
flutter packages pub run build_runner clean
flutter packages pub run build_runner build --delete-conflicting-outputs
5.写数据库操作类
import 'package:moor/moor.dart';
import 'package:moor_example/bean/todos.dart';
import 'package:moor_example/database/todo_database.dart';
//记得加,build会自动生成
part 'todos_dao.g.dart';
@UseDao(tables: [Todos])
class TodosDao extends DatabaseAccessor<TodoDatabase> with _$TodosDaoMixin {
// this constructor is required so that the main database can create an instance // of this object.
TodosDao(TodoDatabase db) : super(db);
Future<List<Todo>> getAllTodos() => select(todos).get();
Stream<List<Todo>> watchAllTodos() => select(todos).watch();
Future insertTodo(TodosCompanion todo) => into(todos).insert(todo);
Future updateTodo(Todo todo) => update(todos).replace(todo);
Future deleteTodo(Todo todo) => delete(todos).delete(todo);
}
6.生成数据库操作Dao实现类
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'todos_dao.dart';
// **************************************************************************
// DaoGenerator
// **************************************************************************
mixin _$TodosDaoMixin on DatabaseAccessor<TodoDatabase> {
$TodosTable get todos => attachedDatabase.todos;
}
7.数据库的调用包装类
import 'package:flutter/material.dart';
import 'package:moor/moor.dart';
import 'package:moor_example/dao/todos_dao.dart';
import 'package:moor_example/database/todo_database.dart';
// 操作数据库类
class DatabaseProvider extends ChangeNotifier {
late TodosDao _todosDao;
TodosDao get todosDao => _todosDao;
bool _hideCompleted = false;
bool get hideCompleted => _hideCompleted;
set hideCompleted(bool value) {
_hideCompleted = value;
notifyListeners();
}
DatabaseProvider() {
TodoDatabase database = TodoDatabase();
_todosDao = TodosDao(database);
}
//新增
Future insertNewTodoItem(
String title, String content, int category, bool completed) {
final todo = TodosCompanion(
title: Value(title),
content: Value(content),
category: Value(category),
completed: Value(completed)
);
return todosDao.insertTodo(todo);
}
// 删除
Future deleteTodoItem(Todo todo) {
return todosDao.deleteTodo(todo);
}
//修改
Future updateTodoItem(Todo todo) {
return todosDao.updateTodo(todo);
}
//查询
Future<List<Todo>> queryTodoItems(){
return todosDao.getAllTodos();
}
//查询
Stream<List<Todo>> watchAllTodos(){
return todosDao.watchAllTodos();
}
}
8.数据库的升级
官方文档:drift.simonbinder.eu/docs/advanc…
import 'dart:io';
import 'package:moor/ffi.dart';
import 'package:moor/moor.dart';
import 'package:path/path.dart' as p;
import 'package:path_provider/path_provider.dart';
import '../bean/todos.dart';
part 'todo_database.g.dart';
@UseMoor(tables: [Todos])
class TodoDatabase extends _$TodoDatabase {
//创建数据库实例,开启数据库连接
TodoDatabase() : super(_openConnection());
//数据库版本控制
@override int get schemaVersion => 2;
//升级配置
@override MigrationStrategy get migration => MigrationStrategy(
onUpgrade: (migrator, from, to) async {
if (from == 1) {
// 版本2 表格新增字段 category
await migrator.addColumn(todos, todos.category);
}},
beforeOpen: (details) async {
await customStatement('PRAGMA foreign_keys = ON');
});
}
// 开启数据库连接
LazyDatabase _openConnection() {
return LazyDatabase(() async {
final dbFolder = await getApplicationDocumentsDirectory();
final file = File(p.join(dbFolder.path, 'db.sqlite'));
return VmDatabase(file, logStatements: true);
});
}
9.使用
import 'package:flutter/material.dart';
import 'package:moor_db_viewer/moor_db_viewer.dart';
import 'package:moor_example/database/todo_database.dart';
import 'package:moor_example/todo_item_widget.dart';
import 'package:provider/provider.dart';
import 'database/database_provider.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (_) => DatabaseProvider(),
child: MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
),
);
}
}
class MyHomePage extends StatefulWidget {
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
@override
void initState() {
super.initState();
var databaseProvider = DatabaseProvider();
for(var i=0;i<20;++i){
databaseProvider.insertNewTodoItem("待办事项${i+1}", "待办事项${i+1}", i+1,false);
}
setState(() {
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Todo'),
actions: [
InkWell(child: Padding(
padding: const EdgeInsets.all(8.0),
child: Center(child: Text("查看数据库")),
),onTap: (){
final db = DatabaseProvider().database; //This should be a singleton
Navigator.of(context).push(MaterialPageRoute(builder: (context) => MoorDbViewer(db)));
},)
],
),
body: Column(
children: <Widget>[
Expanded(
child: Consumer<DatabaseProvider>(
builder: (context, databaseProvider, child) => StreamBuilder(
stream: databaseProvider.watchAllTodos(),
builder: (BuildContext context,
AsyncSnapshot<List<Todo>> snapshot) {
final todosWithCategory = snapshot.data ?? [];
return ListView.builder(
itemCount: todosWithCategory.length,
itemBuilder: (BuildContext context, int index) {
final item = todosWithCategory[index];
return TodoItemWidget( item, databaseProvider.todosDao);
},
);
},
),
),
),
],
),
);
}
}
查看数据库