Flutter 数据库moor的正确使用姿势

1,145 阅读3分钟

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);

                    },

                  );

                },

              ),

            ),

          ),

        ],

      ),

    );

  }

}

查看数据库