Flutter数据库生成插件sqflite_gen的使用

Flutter数据库生成插件sqflite_gen的使用

Sqflite Gen

开源代码生成器,用于统一的SQLite数据库访问,适用于Flutter和Dart。

概述

此包的目标是通过处理sqflite包重复的数据库访问层重写任务来支持开发者。它解析SQL创建脚本并生成模型、提供器和常量,以便访问所有表。它还创建了一个数据库访问层,并在应用程序首次运行时自动创建所有表。

它自动处理布尔值、Uint8List和DateTime类型的自动类型映射。

生成的源遵循由sqflite提供的最佳原则(参见“SQL助手”部分)。


安装

❗ 要开始使用Sqflite Gen,必须在机器上安装Dart SDK。

通过dart pub add安装:

dart pub add sqflite_gen

然后运行:

dart pub get

flutter pub get

现在SqfliteGen将通过运行以下命令为您生成数据库访问层文件:

dart run build_runner build

配置

为了让包生成所需的源代码,请在应用的资源目录中放置一个以.sql结尾的文件。该文件必须包括数据库的所有create table语句。

所有生成的文件将放在/下的lib/db目录中。


概述

支持自增主键。

对于每个表,将在lib/db/tables下创建一个子目录。该包为每个表创建一个数据模型、表访问提供器和用于统一访问表和字段的常量。

模型文件与数据库表同名。它表示一个表,包含该表的每一列的属性。它还包含将映射到Map和从Map转换的方法,以及一个copyWith方法来创建一个新的克隆。

提供器类与数据库表同名,并带有后缀Provider。它通过提供CRUD操作的基本访问方法允许对表的基本访问:insertgetupdatedelete。为了方便起见,它还提供了一个返回表中所有记录的getAll方法。

进一步的常量被创建用于封装表名和所有列名。


使用示例

导入文件以获取数据库访问权限(替换example_app为您的应用引用)

import 'package:example_app/db/database.dart';
import 'package:example_app/db/db.dart';

打开数据库

打开数据库也包含一个基本的迁移机制。这目前仅用于在第一次打开数据库时动态创建所有表。之后数据库结构不再改变。

const dbName = 'test.db';
late Database database;

.
.
.

database = await openDatabaseWithMigration(dbName);

许多应用程序只使用一个数据库,且永远不会关闭它(当应用程序终止时会自动关闭)。如果您想释放资源,可以关闭数据库。

await database.close();

访问数据库表

每个表都由一个模型和一个提供器类表示。它们以类型安全的方式封装访问。

对于以下示例,我们假设数据库表如下所示:

CREATE TABLE Test(
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  text VARCHAR NOT NULL
);

插入

final record = Test(text: 'This is a test');
final table = TestProvider(database);

final insertedRecord = await table.insert(record);
log(insertedRecord.id.toString());

请注意,insert方法自动处理自增列id(当未显式提供值时)。insert还返回包含数据库给定的自增列id值的原始record的副本。

查询

get方法期望一个主键值。

final table = TestProvider(database);

final record = await table.get(1);
log(insertedRecord.text);

更新

final table = TestProvider(database);
final record = await table.get(1);

final changedRecord = record.copyWith(text: 'Changed text');
await table.update(changedRecord);

删除

delete方法期望一个主键值。

final table = TestProvider(database);

final success = await table.delete(1);
log(success ? 'Successfully deleted' : 'Failing deleting record');

处理空值

标记为可为空的列在模型类中表示为可为空的字段。

CREATE TABLE Test(
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  text VARCHAR
);

这对copyWith方法来说是一个问题,因为给定的值现在可以代表三种不同的动作:

  • 设置值为新值
  • 将值设置为null
  • 保持值不变

为了支持这一点,可为空的字段在copyWith方法中被包装:

// 初始化模型
final record = Test(text: 'Original text');
log(record.text); // 打印 Original text

// 更改可为空的列 text 为不同的字符串
final changedRecord = record.copyWith(text: Wrapped.value('New text'));
log(record.text); // 打印 New text

// 保持列 text 的值
final otherRecord = record.copyWith(); // 相当于: record.copyWith(text: null)
log(record.text); // 打印 Original text

// 将可为空的列 text 更改为 null 值
final nulledRecord = record.copyWith(text: Wrapped.value(null));
log(record.text); // 打印 <null>

支持的SQLite类型

目前尚未对值进行有效性检查,因此请避免使用不支持的类型 SQLite文档

示例类型名 生成的Dart字段类型
INT, INTEGER, TINYINT, SMALLINT, MEDIUMINT, BIGINT, UNSIGNED BIG INT, INT2, INT8 int
CHARACTER, VARCHAR, VARYING CHARACTER, NCHAR, NATIVE CHARACTER, NVARCHAR, TEXT, CLOB, STRING String
BLOB Uint8List
BOOL bool
DATE, DATETIME double
REAL, DOUBLE, DOUBLE PRECISION, FLOAT, NUMERIC, DECIMAL DateTime

更多关于支持类型的信息 这里


持续集成

Sqflite Gen 使用由 Very Good Workflows 提供的 GitHub Actions 工作流。

默认情况下,在每次拉取请求和推送时,CI 会格式化、检查和测试代码。这确保了随着添加功能或更改代码,代码保持一致且行为正确。该项目使用 Very Good Analysis 作为严格的分析选项。使用 Very Good Workflows 来强制执行代码覆盖率。


运行测试

要运行所有单元测试:

dart pub global activate coverage 1.2.0
dart test --coverage=coverage
dart pub global run coverage:format_coverage --lcov --in=coverage --out=coverage/lcov.info

要查看生成的覆盖率报告,您可以使用 lcov。

# 生成覆盖率报告
genhtml coverage/lcov.info -o coverage/

# 打开覆盖率报告
open coverage/index.html

示例代码

import 'dart:typed_data';

import 'package:example_app/db/database.dart';
import 'package:example_app/db/db.dart';
import 'package:example_app/my_list_item.dart';
import 'package:flutter/material.dart';
import 'package:uuid/uuid.dart';
import 'package:sqflite/sqflite.dart';

const dbName = 'test.db';
late Database database;

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Sqflite_Gen Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const MyHomePage(title: 'Sqflite_Gen Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  [@override](/user/override)
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  List<Test> _list = [];
  final uuid = const Uuid();

  [@override](/user/override)
  void initState() {
    super.initState();
    _initDatabase();
  }

  void _initDatabase() async {
    database = await openDatabaseWithMigration(dbName);
    final table = TestProvider(database);
    _updateList(table);
  }

  void _addRecord() async {
    final guid = uuid.v4();
    final newRecord = Test(
      text: guid,
      number: _list.length,
      numeric: _list.length / 2,
      date: DateTime.now(),
      isChecked: _list.length % 2 != 0,
      anything: Uint8List.fromList(guid.codeUnits),
    );

    final table = TestProvider(database);
    await table.insert(newRecord);
    _updateList(table);
  }

  void _updateRecord(int id) async {
    final record = _list.firstWhere((item) => item.id == id);
    final table = TestProvider(database);
    await table.update(record.copyWith(date: DateTime.now()));
    _showSnackBar('$id updated');
    _updateList(table);
  }

  void _deleteRecord(int id) async {
    final table = TestProvider(database);
    await table.delete(id);
    _showSnackBar('$id deleted');
    _updateList(table);
  }

  void _updateList(TestProvider table) async {
    final allRecords = await table.getAll();
    setState(() {
      _list = allRecords;
    });
  }

  void _showSnackBar(String text) {
    if (context.mounted) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(
          content: Text(text),
        ),
      );
    }
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text(widget.title),
      ),
      body: ListView.separated(
        separatorBuilder: (_, __) => const Divider(
          height: 1,
          indent: 10,
          endIndent: 10,
        ),
        itemCount: _list.length,
        scrollDirection: Axis.vertical,
        itemBuilder: (context, index) => MyListItem(
          key: ValueKey(_list[index].id),
          record: _list[index],
          onDelete: _deleteRecord,
          onUpdate: _updateRecord,
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _addRecord,
        tooltip: 'Add record to database',
        child: const Icon(Icons.add),
      ),
    );
  }
}

更多关于Flutter数据库生成插件sqflite_gen的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter数据库生成插件sqflite_gen的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


sqflite_gen 是一个用于 Flutter 的代码生成插件,它可以帮助你自动生成 SQLite 数据库的代码,从而简化数据库操作。它基于 sqflite 库,提供了类型安全和更简洁的代码生成功能。

以下是使用 sqflite_gen 的基本步骤:

1. 添加依赖

首先,你需要在 pubspec.yaml 文件中添加 sqflite_genbuild_runner 依赖:

dependencies:
  flutter:
    sdk: flutter
  sqflite: ^2.0.0+4

dev_dependencies:
  build_runner: ^2.1.0
  sqflite_gen: ^1.0.0

然后运行 flutter pub get 来获取依赖。

2. 创建数据库模型

接下来,你需要定义数据库的表和模型。你可以使用 @Database@Table 注解来定义数据库和表。

import 'package:sqflite_gen/sqflite_gen.dart';

@Database(name: 'my_database.db', version: 1)
class MyDatabase {
  @Table(name: 'users')
  class User {
    @Column(primaryKey: true, autoIncrement: true)
    int id;

    @Column(type: ColumnType.text)
    String name;

    @Column(type: ColumnType.integer)
    int age;
  }
}

3. 生成代码

运行以下命令来生成数据库代码:

flutter pub run build_runner build

这将会根据你定义的模型生成相应的数据库操作代码。

4. 使用生成的代码

生成的代码会包含一个 MyDatabase 类,你可以使用它来执行数据库操作。

import 'package:flutter/material.dart';
import 'my_database.g.dart'; // 生成的代码

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  final database = MyDatabase();
  await database.open();

  // 插入数据
  await database.users.insert(User()
    ..name = 'John Doe'
    ..age = 30);

  // 查询数据
  final users = await database.users.query();
  for (final user in users) {
    print('User: ${user.name}, Age: ${user.age}');
  }

  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Sqflite Gen Example'),
        ),
        body: Center(
          child: Text('Check the console for database output.'),
        ),
      ),
    );
  }
}

5. 其他操作

生成的代码还支持其他常见的数据库操作,如更新、删除等。你可以通过生成的 MyDatabase 类来访问这些操作。

例如,更新数据:

await database.users.update(user);

删除数据:

await database.users.delete(user);

6. 关闭数据库

当不再需要数据库时,记得关闭它:

await database.close();
回到顶部