【Flutter】不想用build_runner自动生成代码?用这些插件工具更快速的实现吧
那些替代 build_runner 的插件
前言
尽管 build_runner 在许多项目中被视为不可或缺,它仍然存在一些限制,例如编译时间可能较长,以及配置过程可能相对复杂,导致效率反倒不如一些编辑器插件高。
通过使用编辑器插件和一些工具,开发者们不仅可以期待更快的构建和配置时间,还能享受到更为直观和简化的开发工作流程。特别是在一些需要快速原型开发的项目中,能够迅速迭代并实时看到更改的效果,无疑是一个巨大的优势。
本文的目标是向大家介绍一系列可以替代 build_runner 的编辑器插件和工具。不论是你正在寻求加快开发速度,还是希望简化项目的构建配置,这些工具都可能会是你的有力助手。
接下来我就以之前文章中讲到过的那些配合 build_runner 使用的插件中各常用功能使用编辑器插件与工具来替代。
一、 资源管理
之前我们介绍过 flutter_gen_runner 这个插件,配合 build_runner 可以生成对应的资源代码,并且支持图片,字体,颜色,其他资源等。
确实是很方便,但是我们平常用的最多的也就是图片,我们可以通过 Flutter Assets Generator 这个编辑器插件快速的实现图片资源的生成。
我们可以在 AS 的插件市场中搜索并安装:
使用起来也很方便:
点击自动配置,在 yaml 文件中就可以看到,此时已经是自动生成图片资源了:
如果有错误,我们可以把生成的文件删除重新生成:
默认生成的文件是在 generated 文件夹中的,如果你想自定义可以在配置中修改:
这个插件主要是管理图片比较好用,如果是字体还是需要使用 Flutter 原始的方案:
flutter:
fonts:
- family: MyCustomFont
fonts:
- asset: fonts/MyCustomFont.ttf
使用:
Text(
'This is a custom font text',
style: TextStyle(fontFamily: 'MyCustomFont'),
)
至于颜色也是需要自己管理不方便使用配置文件了。因为并没有涉及到 XML 的解析与格式化对应的颜色常量,此时就需要我们用 Dart 类常量去管理对应的颜色了。
二、Json对象序列与反序列化
之前我们介绍过 json_annotation 这个插件,配合 build_runner 可以生成对应的序列化与反序列化代码。
但是我们需要写一些模板代码:
import 'package:json_annotation/json_annotation.dart';
part 'user.g.dart'; // 1. 这个 part 声明是必须的,它指向生成的文件
@JsonSerializable() // 2. 这个注解告诉 json_serializable 包要为这个类生成代码
class User {
final String name;
final String email;
User({required this.name, required this.email});
// 3. 工厂构造函数,从 JSON 创建一个新的实例
factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
// 4. `toJson` 是一个方法,将 User 实例转化为一个 map
Map<String, dynamic> toJson() => _$UserToJson(this);
}
例如 part 的依赖,文件名匹配,类名匹配,我们可能还需要使用 Live Template 这种模板来生成,然后再运行 build_runner 生成代码,我们先抛开编译时长这个痛点不谈,就是这个过程和步骤就显得比较复杂。
当然我们可以使用第三方的工具网站或者插件来生成对应的模板代码,但总的来说步骤也是挺多的。
如果使用编辑器插件来生成:
安装完插件之后,使用的时候直接 new 一个文件,选择使用插件提供的入口:
然后在面板中输入类名和对应的Json字符串就可以生成对应的实体了:
此时会自动生成对应的序列化与反序列化方法:
@JsonSerializable()
class ServerTime {
String? status;
ServerTimeData? data;
int? code;
String? message;
ServerTime();
factory ServerTime.fromJson(Map<String, dynamic> json) => $ServerTimeFromJson(json);
Map<String, dynamic> toJson() => $ServerTimeToJson(this);
@override
String toString() {
return jsonEncode(this);
}
}
@JsonSerializable()
class ServerTimeData {
int? timestamps;
ServerTimeData();
factory ServerTimeData.fromJson(Map<String, dynamic> json) => $ServerTimeDataFromJson(json);
Map<String, dynamic> toJson() => $ServerTimeDataToJson(this);
@override
String toString() {
return jsonEncode(this);
}
}
如果你想要添加字段也不需要重新生成,只需要添加对应的属性,然后重新 build 即可
重新生成之后可以看到生成的代码中已经有我们加上的属性了:
是不是比 json_annotation + build_runner 更加方便呢?
三、dataClass数据类
在Dart中,data class(数据类)是一种特殊类型的类,它主要用于存储数据。数据类通常包含一些属性,以及用于创建、比较和复制这些类的对象的方法。数据类使得对象之间的比较和复制变得更加简单,因为这些操作通常是基于类的属性进行的。并且在 Bloc 或者其他状态管理框架的时候,数据类可以作为状态的载体,使得状态的传递和处理更加清晰和高效。
如何生成数据类,之前我们介绍过 freezed 这个插件,配合 build_runner 可以生成对应的数据类与不可变类,特别适应于 Bloc 框架。
总的来说,和 json_annotation 的定义方式类似,需要使用一些模板:
import 'package:freezed_annotation/freezed_annotation.dart';
part '$FILENAME$.freezed.dart';
part '$FILENAME$.g.dart';
@freezed
class $NAME$ with _$$$NAME$ {
const factory $NAME$() = _$NAME$;
factory $NAME$.fromJson(Map<String, dynamic> json) => _$$$NAME$FromJson(json);
}
需要 part 指定文件名,需要指定类名,需要开发者手动的填写一下,本来想偷懒但有没有完全偷懒...
那如果我们不想使用 freezed 这种插件,不想用 build_runner 这种自动生成的方案,我们之前也介绍过了 Equatable 的一些用法。确实是很多开发者选择的方式。
如果我不想导入依赖库,那么我们可以使用 Dart Data Class 插件来自动生成 Data Class 的代码,其实 Equatable 本质就是把这些代码封装隐藏起来了,看起来比较简单而已。并且 Equatable 也不是完全的自动化,也需要开发者指定属性。
而使用编辑器插件 Dart Data Class ,我们在生成的时候就可以选择那些属性进行对比。
如何使用?先下载一个插件:
使用的时候,我们先定义属性,然后 Alt + Insert 直接选择对应的需求即可:
例如我选择 Data Class 之后,就是生成全部的代码:
需要哪些属性最对比,勾选上然后就自动生成了对应的代码:
class ScheduleState {
String? name;
int? age;
//<editor-fold desc="Data Methods">
ScheduleState({
this.name,
this.age,
});
@override
bool operator ==(Object other) =>
identical(this, other) || (other is ScheduleState && runtimeType == other.runtimeType && name == other.name && age == other.age);
@override
int get hashCode => name.hashCode ^ age.hashCode;
@override
String toString() {
return 'ScheduleState{' + ' name: $name,' + ' age: $age,' + '}';
}
ScheduleState copyWith({
String? name,
int? age,
}) {
return ScheduleState(
name: name ?? this.name,
age: age ?? this.age,
);
}
Map<String, dynamic> toMap() {
return {
'name': this.name,
'age': this.age,
};
}
factory ScheduleState.fromMap(Map<String, dynamic> map) {
return ScheduleState(
name: map['name'] as String,
age: map['age'] as int,
);
}
//</editor-fold>
}
作者甚至都帮我们做好了折叠的选项,在 AS 中可以直接折叠生成的代码
是不是比 Equatable 还要方便呢?
四、依赖注入
之前我们介绍过使用 build_runner + get_it + injectable 实现依赖注入的自动生成。
总的来说,比较方便,个人感觉比 freezed 和 json_annotation 这种要方便一点,不需要额外定义模板。
用了 injectable 之后,先创建初始化代码
import 'package:get_it/get_it.dart';
import 'package:injectable/injectable.dart';
import 'injection.config.dart';
final getIt = GetIt.instance;
@InjectableInit(
initializerName: 'init', // default
preferRelativeImports: true, // default
asExtension: true, // default
)
void configureDependencies() => getIt.init();
普通的Factory注入:
import 'package:injectable/injectable.dart';
@injectable
class MyService{
void sayHello(){
print("Hello MyService");
}
}
单例类注入:
import 'package:injectable/injectable.dart';
@singleton
class MySingleService{
void sayHello(){
print("Hello MySingleService");
}
}
确实比较方便,当然如果你不想使用 build_runner 去执行命令生成代码,那么直接使用 get_it 也是可以的。
import 'package:get_it/get_it.dart';
final getIt = GetIt.instance;
class UserRepository {
// 用户仓库的实现
}
class UserBloc {
final UserRepository userRepository;
UserBloc({required this.userRepository});
}
void main() {
// 手动注册依赖
getIt.registerSingleton<UserRepository>(UserRepository());
// 获取依赖
final userBloc = getIt.get<UserBloc>(param1: UserRepository());
// 使用userBloc
}
当然如果你的项目中不需要使用依赖注入框架,或者不想使用依赖注入框架也是可以的,这个也不是说一定要有,你可以通过创建单例类或使用静态方法来自己控制依赖的创建和管理。
class UserRepository {
// 用户仓库的实现
}
class UserBloc {
static final UserBloc _instance = UserBloc._internal();
factory UserBloc() {
return _instance;
}
UserBloc._internal() {
_userRepository = UserRepository();
}
late UserRepository _userRepository;
UserRepository get userRepository => _userRepository;
}
void main() {
// 使用单例的UserBloc
final userBloc = UserBloc();
// 使用userBloc
}
我们一样可以自己实现单例类来自行管理。或者我们可以使用状态管理框架,如 Bloc 的全局Provider 或者 Getx 的 GetxService 全局的单例。
五、路由管理
我们之前介绍过使用 auto_route + build_runner 实现的自动创建路由配置。总体体验下来是和 freezed 和 json_annotation 这种方式类似的,需要额外的处理模板,不过比他们好一点的是只需要处理初始化的模板。
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
part 'app_router.gr.dart';
@AutoRouterConfig(replaceInRouteName: 'Page|Screen,PageRoute')
class AppRouter extends _$AppRouter {
@override
List<AutoRoute> get routes => [
];
}
之后我们就只需要在路由页面添加注解就能自动生成对应的配置了,但是每次创建一个页面都要生成一次,要知道我们的 build_runner 也是耗时的,导致效率反倒不是那么高。
个人的推荐如果不想全面拥抱 build_runner 自动化的,可以考虑使用 go_router 这种声明式的路由管理。并且它使用起来比 auto_router 更加的简单方便,高阶功能也都支持。
import 'package:go_router/go_router.dart';
final router = GoRouter(
routes: [
GoRoute(
path: '/',
builder: (context, state) => HomePage(),
),
GoRoute(
path: '/detail/:id',
builder: (context, state) => DetailPage(state.params['id']),
),
],
);
使用声明的路由表
void main() {
runApp(MaterialApp.router(
title: 'My App',
theme: ThemeData(primarySwatch: Colors.blue),
routerDelegate: router.routerDelegate,
routeInformationParser: router.routeInformationParser,
));
}
在不使用 build_runner 自动生成代码的情况下,您可以选择手动编写路由配置,或者使用如go_router或fluro这样的路由库来管理路由。这些方法都允许您完全控制路由的定义和行为,而不依赖于代码生成。选择哪种方法取决于您的项目需求和个人偏好。
六、其他状态管理的插件
除了以上跟状态管理无关的插件,我们在使用状态管理插件也是可以通过编辑器插件快速的生成对应的模板代码,也是非常实用的,可以大大的提高开发效率。
Provider:
Provider 是Flutter社区中广泛使用的状态管理解决方案之一。它基于InheritedWidget,使得状态管理更加简单和直观。
Flutter Provider Snippets: 这个插件提供了Provider的代码片段,可以快速插入常用的Provider代码结构。
Flutter Provider :这个插件可以提供 Provider 的模板代码。
Riverpod
Riverpod 是由Provider的作者之一推出的,它旨在解决Provider的一些限制,并提供更多的灵活性和可测试性。
Android Studio插件:
Flutter Riverpod Snippets: 提供Riverpod的代码片段,帮助开发者快速开始。
目前只有这种方案可以生成对应的代码,不过现在的 RiverPod 都是结合 Hooks 使用,这就有点一言难尽了。
GetX
GetX 是一个功能丰富的状态管理库,它不仅提供了状态管理,还包括路由管理和依赖注入等功能。
这两个插件都可以实现,我个人比较喜欢呆呆大神的模板,如果对生成的文件后缀有要求,可以在设置中修改
Bloc Bloc 是另一个流行的状态管理库,它遵循单向数据流的原则,非常适合复杂的应用状态管理。
这里也推荐两个插件,一个是官方的一个是呆呆大神的,官方的只是生成 Bloc 相关类,呆呆大神可以生成页面与对应的使用,相对来说更方便。
总结
本文我们介绍了一些基础功能和状态管理框架的插件生成代码方式。
在Flutter开发中,自动化代码生成工具如同一把锋利的剑,助开发者披荆斩棘。然而,build_runner 虽强大,却非唯一之选,也有它的劣势与缺点。我们探索了另一条路径——编辑器插件与AS插件,它们如同隐匿于林间的捷径,让我们的开发之旅更加迅捷与愉悦。
无论是资源的生成、依赖的注入、Json的序列化与反序列化,还是不可变对象的创建与路由的自动生成,这些插件以其独特的优势,为我们提供了更为便捷的解决方案。它们与IDE的深度融合,不仅减少了编译等待的时间,更在代码编写过程中即时反馈,极大地提升了开发效率。
通过本文的探索与实践,我们不难发现,这些插件如同开发者的得力助手,它们以直观的方式简化了复杂的操作,让代码的生成不再是繁琐的重复劳动,而是变成了一个高效、愉悦的过程。它们的存在,不仅是对build_runner的有力补充,更是对Flutter生态的丰富与完善。
我们开发者可以自由的组合,可以全部使用编辑器插件生成,也可以部分使用 build_runner 配合一些 Flutter 插件使用自动化生成代码都是可以的。
文章到处就告一段落了,如果你有其他的更多的更好的实现方式,也希望大家能评论区交流一起学习进步。如果我的文章有错别字,不通顺的,或者代码、注释、有错漏的地方,同学们都可以指出修正。
如果感觉本文对你有一点的启发和帮助,还望你能点赞
支持一下,你的支持对我真的很重要。
Ok,这一期就此完结。
转载自:https://juejin.cn/post/7382416827568504867