Dart笔记 build_runner 用于 Dart 代码生成和模块化编译的构建体系


作者李俊才 (jcLee95)blog.csdn.net/qq_28550263
邮箱 : 291148484@163.com
本文地址blog.csdn.net/qq_28550263…


【简介】build_runner 库是一个用于主动化代码生成的工具。文章首要解说了build_runner的用处和装置办法,然后详细介绍了其内置指令、选项以及输入和输出的处理办法。文章还经过两个实例(json_serializable和Floor ORM结构)展现了怎么在实践项目中运用build_runner来主动生成代码。

目 录* * *


1. 概述

2.1 build_runner 用于处理什么问题

build_runner库是为了处理DartFlutter中代码生成的问题而创立的,用于生成代码。代码生成是一种常见的编程技术,它能够帮助开发者主动化一些重复或模板化的编程任务,从而进步开发功率和代码质量。

DartFlutter中,有许多场景或许需求运用到代码生成。例如:

  • 序列化反序列化:关于杂乱的数据结构,手动编写序列化和反序列化的代码或许会十分繁琐和容易出错。经过运用代码生成,咱们能够主动地为数据结构生成序列化和反序列化的代码。
  • ORM目标联系映射):在处理数据库时,咱们一般需求将数据库中的表映射到Dart的目标。这个映射过程能够经过代码生成来主动化。
  • 依靠注入:依靠注入是一种常见的规划形式,它能够帮助咱们更好地安排和管理代码中的依靠联系。经过运用代码生成,咱们能够主动地为依靠注入生成所需的代码。

但是,代码生成也有其应战。其间一个首要的应战是 处理文件和依靠联系。例如,当一个文件发生变化时,咱们或许需求从头生成依靠于这个文件的一切其他文件的代码。build_runner 供给一个强壮且灵活的办法来管理和主动化代码生成过程中的文件和依靠联系。

2.2 build_runner 的装置

运用build_runner,你需求在你的pubspec.yaml文件中增加指定版本的build_runner作为开发依靠项。需求指出的是,一般,将它放在pubspec.yaml的dev_dependencies下:

dev_dependencies:
  build_runner:

然后,你能够在指令行中运转build_runner:

dart run build_runner build

这将运转一切装备的生成器,并将生成的代码输出到指定的目录。

注:当供给构建器的包经过 build.yaml 文件装备时,它们被规划为运用生成的构建脚本进行消费。大多数构建器应该需求很少或不需求装备,详细取决于构建器的文档。假如你需求将web代码编译为js,需求在 dev_dependencies 中增加 build_web_compilers。

2. build_runner 的用法介绍

2.1 内置指令和选项

除了build指令外,build_runner还供给了其它的内置指令,如watchserve等。其间:

指令 描绘
build 运转一次构建并退出
watch 运转一个耐久的构建服务器,监视文件体系的编辑并在必要时进行重建
serve watch相同,但同时运转一个开发服务器。 默许情况下,它在80808081端口上别离供给webtest目录
test 运转一次构建,创立一个兼并的输出目录,然后运转dart run test --precompiled <merged-output-dir>

一切这些指令都支撑以下选项:

选项 描绘
--help 打印帮助信息
--delete-conflicting-outputs 假设用户包中的抵触输出来自之前的构建,并跳过一般供给的用户提示
--[no-]fail-on-severe 是否在记载错误时将构建视为失利。 默许情况下,为false
--build-filter 构建过滤器允许你明确挑选要构建的文件,而不是构建整个目录

2.2 输入和输出

输入

在Dart中,一个包(package)是包含一组相关Dart代码的目录。有用的输入遵从一般的Dart包规矩,一个规范的Dart包一般会有如下的目录结构:

  • lib:这个目录包含了包的公共代码。其他包能够导入这个目录下的Dart文件。
  • test:这个目录包含了包的测试代码。
  • example:这个目录包含了包的示例代码。
  • bin:这个目录包含了包的可执行脚本。
    有用的输入遵从一般的Dart包规矩。

build_runner能够从任何包依靠项的顶级lib文件夹下读取任何文件,也能够从当时包读取一切文件。这个映射界说了构建器(Builder)怎么处理输入文件并生成输出文件。一切匹配此映射中任何键的输入源都将作为构建步骤传递给此构建器。只要具有相同基本名和来自此映射值的扩展名的文件才被希望作为输出。

一般来说,最好尽或许详细地指定你的输入集,由于一切匹配的文件都将根据构建器的buildExtensions进行检查。

【注】:buildExtensionsBuilder类build库) 的一个特点,它是一个从输入文件扩展名到输出文件扩展名的映射。 例如,假如你有一个将.txt文件转换为.md和.html文件的构建器,那么你的buildExtensions或许如下:

const buildExtensions = {
 '.txt': ['.md', '.html'],
};

这意味着,关于每一个.txt文件,这个构建器都会生成一个对应的.md文件和一个.html文件。
假如存在一个空的键,那么一切的输入都被视为匹配。构建器的实例有必要始终回来相同的装备。一般,构建器会回来一个常量映射。构建器也能够根据BuilderOptions挑选扩展。

输出

你能够在当时包的任何地方输出文件。构建器不允许覆盖现有文件,只能创立新文件。先前构建的输出不会被视为后续构建的输入。

源代码操控

build_runner会在你的包的顶级目录下创立一个.dart_tool文件夹,这个文件夹不应该提交到你的源代码操控仓库。关于生成的文件,一般最好不要提交它们到源代码操控,但是特定的构建器或许会供给其他的主张。

假如你将生成的文件提交到你的仓库,那么当你切换分支或兼并更改时,你或许会在下一次构建时收到关于已存在的声明输出的警告。这将跟从一个提示删去这些文件的提示。你能够输入l来列出文件,然后假如一切看起来正确,能够输入y来删去它们。假如你认为有什么不对,你能够输入n来放弃构建,而不采纳任何举动。

3. 运用举例:json_serializable

例如有一个用于生成 JSON 序列化代码的构建器。你能够运用 build_runner 来运转这个构建器,生成序列化代码。

Dart中,咱们常常运用json_serializable包来主动生成 JSON 序列化和反序列化的代码。这个包供给了一个构建器,咱们能够运用build_runner来运转这个构建器。

其间:

  • json_serializable供给用于处理 JSON 的Dart 构建体系构建器。
    你需求在你的模型类上增加 @JsonSerializable() 注解,并界说fromJsontoJson办法。然后,json_serializable构建器会主动为你生成这些办法的完成;
  • build_runner用于在Dart项目中运转构建器(能够运用build_runner来运转任何构建器,包含这里的json_serializable供给的构建器,build_runner会找到一切可用的构建器,并依照它们的装备运转它们。)。

也就是说,json_serializable担任界说怎么生成代码,而build_runner担任运转json_serializable构建器并将生成的代码写入到文件中。

接下来看一下详细的过程。首要装置到依靠:

dependencies:
  json_annotation: ^4.8.1
dev_dependencies:
  build_runner: ^2.4.6
  json_serializable: ^6.1.4

然后,你能够界说一个 Model 类(数据模型),并运用 json_serializable 的注解:

import 'package:json_annotation/json_annotation.dart';
// 这行是有必要的,示user_model.dart文件是一个库的一部分
// 而 user_model.g.dart 文件是这个库的另一部分
part 'user_model.g.dart';
@JsonSerializable()
class UserModel {
  final String name;
  final String email;
  UserModel(this.name, this.email);
  factory UserModel.fromJson(Map<String, dynamic> json) =>
      _$UserModelFromJson(json);
  Map<String, dynamic> toJson() => _$UserModelToJson(this);
}

其间, _UserFromJson**和 **_UserToJson是由json_serializable生成的函数,它们用于将 JSON 数据转换为 UserModel 目标,以及将User目标转换为JSON数据。

接着,在项目下运转 build_runner 的 build 指令来生成序列化代码:

dart run build_runner build

这将会生成一个对应的user_model.g文件,这个文件包含了 _UserFromJson**和 **_UserToJson的完成。生成的对应的user_model.g.dart的代码如下:

// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'user_model.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
UserModel _$UserModelFromJson(Map<String, dynamic> json) => UserModel(
      json['name'] as String,
      json['email'] as String,
    );

Map<String, dynamic> _$UserModelToJson(UserModel instance) => <String, dynamic>{
      'name': instance.name,
      'email': instance.email,
    };

4. 运用举例:在 Floor (ORM结构)中主动生成数据库代码

能够参考博文《用于ORM的Floor结构》,地址:jclee95.blog.csdn.net/article/det…

首要,你需求在pubspec.yaml文件中增加floor,floor_generator和build_runner的依靠。floor是运转时依靠,floor_generator和build_runner是开发时依靠。你的pubspec.yaml文件应该像这样:

dependencies:
  flutter:
    sdk: flutter
  floor: ^1.4.2
  sqflite: ^2.3.0
dev_dependencies:
  floor_generator: ^1.4.2
  build_runner: ^2.1.2

然后,运转flutter pub get指令来获取这些依靠。

接着创立实体:界说一个类,运用@entity注解符号这个类,这个类将会映射到数据库的一个表。运用@primaryKey注解符号主键。例如:

// person_entity.dart
import 'package:floor/floor.dart';
@entity
class PersonEntity {
  @primaryKey
  final int id;
  final String name;
  PersonEntity(this.id, this.name);
}

接下来创立DAO文件,界说一个抽象类,运用@dao注解符号这个类。在这个类中,你能够界说查询数据库的办法。运用@Query注解界说查询句子,运用@insert注解界说刺进办法。例如:

// person_dao.dart
import 'package:floor/floor.dart';
import 'person_entity.dart';
@dao
abstract class PersonDao {
  @Query('SELECT * FROM Person')
  Future<List<PersonEntity>> findAllPeople();
  @insert
  Future<void> insertPerson(PersonEntity person);
}

接下来,你需求界说一个数据库类,这个类需求继承 FloorDatabase,并运用@Database注解。在这个类中,你能够界说获取PersonDao的办法。例如:

import 'package:floor/floor.dart';
import 'person_dao.dart';
import 'person_entity.dart';
part 'app_database.g.dart'; // 生成的代码会在那里
@Database(version: 1, entities: [PersonEntity])
abstract class AppDatabase extends FloorDatabase {
  PersonDao get personDao;
}

然后,你能够运转指令来生成数据库操作的代码:

dart run build_runner build

能够看到,在同一个目录下生成了一个名为app_database.g.dart的文件,生成的内容如下:

// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'app_database.dart';
// **************************************************************************
// FloorGenerator
// **************************************************************************
// ignore: avoid_classes_with_only_static_members
class $FloorAppDatabase {
  /// 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 _$AppDatabaseBuilder databaseBuilder(String name) =>
      _$AppDatabaseBuilder(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 _$AppDatabaseBuilder inMemoryDatabaseBuilder() =>
      _$AppDatabaseBuilder(null);
}
class _$AppDatabaseBuilder {
  _$AppDatabaseBuilder(this.name);
  final String? name;
  final List<Migration> _migrations = [];
  Callback? _callback;
  /// Adds migrations to the builder.
  _$AppDatabaseBuilder addMigrations(List<Migration> migrations) {
    _migrations.addAll(migrations);
    return this;
  }
  /// Adds a database [Callback] to the builder.
  _$AppDatabaseBuilder addCallback(Callback callback) {
    _callback = callback;
    return this;
  }
  /// Creates the database and initializes it.
  Future<AppDatabase> build() async {
    final path = name != null
        ? await sqfliteDatabaseFactory.getDatabasePath(name!)
        : ':memory:';
    final database = _$AppDatabase();
    database.database = await database.open(
      path,
      _migrations,
      _callback,
    );
    return database;
  }
}
class _$AppDatabase extends AppDatabase {
  _$AppDatabase([StreamController<String>? listener]) {
    changeListener = listener ?? StreamController<String>.broadcast();
  }
  PersonDao? _personDaoInstance;
  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');
        await callback?.onConfigure?.call(database);
      },
      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 `PersonEntity` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, PRIMARY KEY (`id`))');
        await callback?.onCreate?.call(database, version);
      },
    );
    return sqfliteDatabaseFactory.openDatabase(path, options: databaseOptions);
  }
  @override
  PersonDao get personDao {
    return _personDaoInstance ??= _$PersonDao(database, changeListener);
  }
}
class _$PersonDao extends PersonDao {
  _$PersonDao(
    this.database,
    this.changeListener,
  )   : _queryAdapter = QueryAdapter(database),
        _personEntityInsertionAdapter = InsertionAdapter(
            database,
            'PersonEntity',
            (PersonEntity item) =>
                <String, Object?>{'id': item.id, 'name': item.name});
  final sqflite.DatabaseExecutor database;
  final StreamController<String> changeListener;
  final QueryAdapter _queryAdapter;
  final InsertionAdapter<PersonEntity> _personEntityInsertionAdapter;
  @override
  Future<List<PersonEntity>> findAllPeople() async {
    return _queryAdapter.queryList('SELECT * FROM Person',
        mapper: (Map<String, Object?> row) =>
            PersonEntity(row['id'] as int, row['name'] as String));
  }
  @override
  Future<void> insertPerson(PersonEntity person) async {
    await _personEntityInsertionAdapter.insert(
        person, OnConflictStrategy.abort);
  }
}

能够看出,你或许需求手动在app_database.dart导入一些库,一般根据编辑器提示补上就能够:

import 'dart:async';
import 'package:sqflite/sqflite.dart' as sqflite;

在这个生成的文件中,能够看到:

  • $FloorAppDatabase:这个类供给了创立AppDatabase实例的办法。你能够运用databaseBuilder办法来创立一个耐久化的数据库,或许运用inMemoryDatabaseBuilder办法来创立一个内存数据库。
  • _$AppDatabaseBuilder:这个类用于构建AppDatabase实例。你能够运用addMigrations办法来增加数据库搬迁,运用addCallback办法来增加数据库回调。
  • _$AppDatabase:这个类是AppDatabase类的完成。它包含了一个PersonDao实例,你能够运用这个实例来操作数据库。
  • _$PersonDao:这个类是PersonDao接口的完成。它包含了findAllPeople和insertPerson办法的完成,这些办法用于查询和刺进数据。

最终,就能够运用生成的$FloorAppDatabase类来获取数据库实例,并运用这个实例来操作数据库了。例如:

final database = await $FloorAppDatabase.databaseBuilder('app_database.db').build();
final personDao = database.personDao;
final person = PersonEntity(1, 'Frank');
await personDao.insertPerson(person);
final result = await personDao.findAllPeople();