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

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

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


【简介】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运行一个持久的构建服务器,监视文件系统的编辑并在必要时进行重建
servewatch 相同,但同时运行一个开发服务器。
默认情况下,它在 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.1dev_dependencies:build_runner: ^2.4.6json_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';()
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 HANDpart 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框架》,地址:https://jclee95.blog.csdn.net/article/details/133377191

首先,你需要在pubspec.yaml文件中添加floor,floor_generator和build_runner的依赖。floor是运行时依赖,floor_generator和build_runner是开发时依赖。你的pubspec.yaml文件应该像这样:

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

然后,运行flutter pub get命令来获取这些依赖。

接着创建实体:定义一个类,使用 @entity 注解标记这个类,这个类将会映射到数据库的一个表。使用 @primaryKey 注解标记主键。例如:

// person_entity.dart
import 'package:floor/floor.dart';
class PersonEntity {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';
abstract class PersonDao {('SELECT * FROM Person')Future<List<PersonEntity>> findAllPeople();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'; // 生成的代码会在那里(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 HANDpart 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);}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;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));}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();

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/140074.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

npm 换源

你可以通过以下命令来将 npm 的源切换到阿里源&#xff1a; bashnpm config set registry https://registry.npm.taobao.org 这个命令会将 npm 的源设置为阿里的镜像源&#xff0c;这样你在安装 npm 包时&#xff0c;就会从阿里的镜像源下载&#xff0c;速度会更快。 如果你…

海康Visionmaster-Qt+VS 二次开发环境如何配置?

1 新建 Qt 工程&#xff0c;添加 Qt 模块 Core、GUI、Active Qt 和 Container Widgets 2 拷贝 DLL:VM\VisionMaster4.0.0\Development\V4.0.0\ComControl\bin\x64 下的所有拷贝到项目工程输出目录下&#xff0c;如下图所示&#xff0c;项目的输出路径是 Dll 文件夹。 3 第一…

JavaWeb Day10 案例 准备工作

目录 一、需求说明 二、环境搭建 &#xff08;一&#xff09;数据库 &#xff08;二&#xff09;后端 ①controller层 1.DeptController.java 2.EmpController.java ②mapper层 1.DeptMapper.java 2.EmpMapper.java ③pojo层 1.Dept.java 2.Emp.java 3.Result.ja…

STM32一

0.前言 在B站经常看见有人用stm32做出了有趣的电子小玩艺儿&#xff0c;感到很羡慕&#xff0c;于是想了解一下。 1.什么是stm32 STM32 是一系列由STMicroelectronics&#xff08;意法半导体&#xff09;公司设计和制造的32位ARM Cortex-M微控制器。这一系列的微控制器广泛用…

GetSimple CMS忘记密码

GetSimple CMS是一个超简单的 CMS&#xff0c;适合建立个人网站等只需要极少数页面的网站。在站上百科上&#xff0c;是这么说的&#xff1a; GetSimple是一款基于XML存储数据的开源内容管理系统&#xff0c;且易于安装和定制&#xff0c;无需MySQL支持。提供撤销保护和备份功能…

skynet学习笔记03— 服务

01、API newservice(name, ...)&#xff1a; 阻塞的形势启动一个名为 name 的新服务&#xff0c;待start函数执行完后会返回这个服务的地址。uniqueservice(name, ...)&#xff1a;针对于当前节点&#xff0c;启动一个唯一服务&#xff08;相当于单例&#xff09;&#xff0c;…

stm32 - Cortex

stm32 - Cortex 概念Cortex-M4 的工作模式和工作状态寄存器 概念 Cortex-M4 的工作模式和工作状态 处理模式 当处理器发生了异常或者中断&#xff0c;则进入处理模式进行处理&#xff0c;处理完成后返回到线程模式 权限大&#xff0c;访问处理器中所有的资源 线程模式 芯片复…

深度解剖Linux权限的概念

> 作者简介&#xff1a;დ旧言~&#xff0c;目前大二&#xff0c;现在学习Java&#xff0c;c&#xff0c;c&#xff0c;Python等 > 座右铭&#xff1a;松树千年终是朽&#xff0c;槿花一日自为荣。 > 目标&#xff1a;牢记Linux权限的概念。 > 毒鸡汤&#xff1a;你…

upload 文件自动上传写法,前后端 下载流文件流

<el-uploadv-model:file-list"fileList":action"app.api/student/student/import":headers"{// Content-Type: multipart/form-data;boundary----split-boundary, 此处切记不要加&#xff0c;否则会造成后端报错 Required request part file is…

HashMap的使用,以及内置方法

HashMap 是 Java 中常用的集合类之一&#xff0c;它实现了 Map 接口&#xff0c;基于哈希表实现。HashMap 允许存储键值对&#xff0c;其中键和值都可以是任意类型的对象。 1. 创建 HashMap import java.util.HashMap; import java.util.Map;public class HashMapExample {pu…

C语言--输入10个数字,要求输出其中值最大的元素和该数字是第几个数

今天小编带大家了解一下什么是“打擂台”算法。 一.思路分析 可以定义一个数组arr&#xff0c;长度为10&#xff0c;用来存放10个数字&#xff0c;设计一个函数Max&#xff0c;用来求两个数中的较大值&#xff0c; 定义一个临时变量tmparr[0],保存临时最大的值&#xff0c;下标…

Hive 常用存储、压缩格式

1. Hive常用的存储格式 TEXTFI textfile为默认存储格式 存储方式&#xff1a;行存储 磁盘开销大 数据解析开销大 压缩的text文件 hive 无法进行合拆分 SEQUENCEFILE sequencefile二进制文件&#xff0c;以<key,value>的形式序列到文件中 存储方式&#xff1a;行存储 可…

洛谷 NOIP 2023 模拟赛 P9836 种树

洛谷 NOIP 2023 模拟赛 P9836 种树 文章目录 洛谷 NOIP 2023 模拟赛 P9836 种树题目大意思路code 题目大意 路边有 n n n 棵树&#xff0c;每棵树的 高度 均为正整数&#xff0c;记作 p 1 , p 2 … p n p_1, p_2 \dots p_n p1​,p2​…pn​。 定义一棵树的 宽度 为它高度的…

vagrant安装k8s集群

目录 概述前期准备安装virtualbox安装vagrant安装gitbash 集群架构集群安装集群初始化集群测试 概述 使用vagrant、virtualbox创建。 前期准备 安装virtualbox 访问官网安装&#xff0c;版本7.0.10 安装vagrant 访问官网安装&#xff0c;版本2.3.7 安装gitbash 访问官网…

Gogs安装和部署教程-centos上

0、什么是 Gogs? Gogs 是一款极易搭建的自助 Git 服务。 Gogs 的目标是打造一个最简单、最快速和最轻松的方式搭建自助 Git 服务。使用 Go 语言开发使得 Gogs 能够通过独立的二进制分发&#xff0c;并且支持 Go 语言支持的 所有平台&#xff0c;包括 Linux、Mac OS X、Windo…

记录一次某某虚拟机的逆向

导语 学了一段时间的XPosed&#xff0c;发现XPosed真的好强&#xff0c;只要技术强&#xff0c;什么操作都能实现... 这次主要记录一下我对这款应用的逆向思路 apk检查 使用MT管理器检查apk的加壳情况 发现是某数字的免费版本 直接使用frida-dexdump 脱下来后备用 应用分…

Maven内网开发使用离线仓库

Maven内网开发使用离线仓库 离线或者内网环境开发与外网不通&#xff0c;中央仓库连不上&#xff0c;使用 Maven 管理项目会遇到很多问题。 比如&#xff1a;依赖包缺失&#xff0c;内网的Nexus私服的包老旧&#xff0c;很久没有维护&#xff0c;项目无法运行打包&#xff0c;…

C++语言的广泛应用领域

目录 1. 系统级编程 2. 游戏开发 3. 嵌入式系统 4. 大数据处理 5. 金融和量化分析 6. 人工智能和机器学习 7. 网络和通信 结语 C是一种多范式编程语言&#xff0c;具有高性能、中级抽象能力和面向对象的特性。由Bjarne Stroustrup于1979年首次设计并实现&#xff0c;C在…

基于蜉蝣算法优化概率神经网络PNN的分类预测 - 附代码

基于蜉蝣算法优化概率神经网络PNN的分类预测 - 附代码 文章目录 基于蜉蝣算法优化概率神经网络PNN的分类预测 - 附代码1.PNN网络概述2.变压器故障诊街系统相关背景2.1 模型建立 3.基于蜉蝣优化的PNN网络5.测试结果6.参考文献7.Matlab代码 摘要&#xff1a;针对PNN神经网络的光滑…

关于react输入框回显问题

绑定表单元素的值到组件状态中。例如&#xff0c;对于一个文本框&#xff0c;可以使用onChange事件将用户输入的值绑定到组件状态中。 创建一个处理表单提交的函数。这个函数通常会使用组件状态中的值来更新页面上的数据。 在handleSubmit函数中&#xff0c;防止默认表单提交…