Flutter代码生成插件jugger_generator的使用

发布于 1周前 作者 vueper 来自 Flutter

Flutter代码生成插件jugger_generator的使用

Index

如何使用

安装

要使用此插件,您需要在 pubspec.yaml 文件中添加 juggerjugger_generator 作为依赖项。

dependencies:
  jugger: any

dev_dependencies:
  build_runner: any
  jugger_generator: any

运行生成器

有两种方法可以运行代码生成器:

  • 如果您的包依赖于 Flutter:
    flutter pub run build_runner build
    
  • 如果您的包不依赖于 Flutter:
    dart pub run build_runner build
    

特性

语法

基本概念

以下示例展示了如何使用 jugger:

import 'package:example/main.jugger.dart';
import 'package:jugger/jugger.dart';

void main() {
  final MyComponent myComponent = JuggerMyComponent.create();
  print(myComponent.getString());
}

@Component(
  modules: <Type>[MyModule],
)
abstract class MyComponent {
  String getString();
}

@module
abstract class MyModule {
  @provides
  static String provideSting() => 'hello!';
}

组件

组件是连接模块和依赖方之间的桥梁。当我们需要某个对象时,我们会向组件请求。组件知道哪个模块可以创建所需的对象,并将其返回给依赖方。

一个组件可以有 modules 和其他需要请求依赖的 components

@Component(
  modules: <Type>[...],
  dependencies: <Type>[...]
)

子组件

子组件是继承并扩展父组件对象图的组件。您可以使用它们来将应用程序的对象图划分为子图,以便将应用的不同部分相互隔离。

声明子组件

与顶级组件一样,您通过编写一个声明抽象方法的类来创建子组件。这些方法返回应用程序关心的类型。与 @Component 不同,您用 @Subcomponent 来注解子组件,并安装 @Modules

@Subcomponent(
  modules: <Type>[MySubcomponentModule],
)
abstract class MySubcomponent {
  String getString();
}

@module
abstract class MySubcomponentModule {
  @provides
  static String provideString() => '';
}
添加子组件到父组件

要将子组件添加到父组件中,需要添加一个返回子组件类型的抽象方法。别忘了使用 @subcomponentFactory 注解该方法。

import 'package:jugger/jugger.dart';

@Component()
abstract class MyComponent {
  @subcomponentFactory
  MySubcomponent createMySubcomponent();
}
子组件与范围

一个常见的原因是将应用程序的组件拆分为子组件是为了使用范围。对于普通的无作用域绑定,每个使用注入类型的用户可能会得到一个新的独立实例。但如果绑定是作用域化的,则在此作用域生命周期内所有使用该绑定的用户都将获得相同的绑定类型实例。

标准的作用域是 @singleton。使用单例作用域绑定的所有用户都会获得相同的实例。

一个组件可以通过注解为 @scope 的组件关联一个作用域。在这种情况下,组件实现会持有所有作用域对象的引用,以便重复使用。带有 @provides 方法注解为作用域的方法只能安装到同样注解为相同作用域的组件中。

没有子组件可以与任何祖先组件使用相同的作用域。然而,两个不可互达的子组件可以使用相同的作用域,因为不存在关于存储作用域对象的歧义。(即使使用相同的作用域注解,这两个子组件实际上也有不同的作用域实例。)

import 'package:jugger/jugger.dart';

@Component()
@singleton
abstract class MyComponent {
  @subcomponentFactory
  MySubcomponent createMySubcomponent();

  @subcomponentFactory
  MySubcomponent2 createMySubcomponent2();
}

@Subcomponent()
@MyScope()
abstract class MySubcomponent {}

@Subcomponent()
@MyScope()
abstract class MySubcomponent2 {}
具有构建器的子组件

子组件可以有一个构建器。这也在 @Subcomponent 注解中指定。在父组件的方法中,您需要将它指定为唯一参数。

import 'package:jugger/jugger.dart';

@Component()
abstract class MyComponent {
  @subcomponentFactory
  MySubcomponent createMySubcomponent(MySubcomponentBuilder builder);
}

@Subcomponent(
  builder: MySubcomponentBuilder,
)
abstract class MySubcomponent {
  String getString();
}

@componentBuilder
abstract class MySubcomponentBuilder {
  MySubcomponentBuilder setString(String s);

  MySubcomponent build();
}

创建组件实例的方式如下:

final MyComponent myComponent = JuggerMyComponent.create();
MySubcomponent mySubcomponent = myComponent.createMySubcomponent(
  JuggerSubcomponent$MySubcomponentBuilder().setString("Hello"),
);
print(mySubcomponent.getString());

范围

您可以定义自己的作用域。它们的目标是在单个组件内重用对象实例。

@scope
class MyScope {
  const MyScope._();
}

const MyScope myScope = MyScope._();

组件构建器

组件可能需要外部对象来使用依赖图。为此,您需要使用 组件构建器。声明一个带 @componentBuilder 注解的抽象类。它必须包含一个带有组件返回类型的必需 build() 方法。对于每个外部依赖,您需要声明一个具有构建器返回类型的带有单个参数的方法。

import 'package:example/main.jugger.dart';
import 'package:jugger/jugger.dart';

void main() {
  final MyComponent myComponent =
      JuggerMyComponentBuilder().helloString('hello').build();

  print(myComponent.getString());
}

@Component(
  builder: MyComponentBuilder,
)
abstract class MyComponent {
  String getString();
}

@componentBuilder
abstract class MyComponentBuilder {
  MyComponentBuilder helloString(String s);

  MyComponent build();
}

构建器提供的依赖项将在依赖图中用于构建其他依赖项。

...
@componentBuilder
abstract class MyComponentBuilder {
  MyComponentBuilder setDouble(double d);

  MyComponent build();
}

@module
abstract class MyModule {
  @provides
  @singleton
  static int provideInteger() => 0;

  @provides
  static String provideSting(
    int i, // used from this module
    double d, // user from component builder
  ) =>
      '$i, $d';
}

组件作为依赖

组件可以依赖其他组件以使用其返回的对象作为依赖。

import 'package:example/main.jugger.dart';
import 'package:jugger/jugger.dart';

void main() {
  // 创建第一个组件
  final FirstComponent firstComponent = JuggerFirstComponent.create();

  final SecondComponent secondComponent = JuggerSecondComponentBuilder()
      // 传递第一个组件的实例
      .setFirstComponent(firstComponent)
      .build();

  print(secondComponent.getString());
}

@Component(
  modules: <Type>[FirstModule],
)
@singleton
abstract class FirstComponent {
  // 重要!为了第二个组件能够使用它来构建对象,
  // 您需要添加一个返回它的方法。
  int getInt();
}

@module
abstract class FirstModule {
  @provides
  @singleton
  static int provideInteger() => 0;
}

@Component(
  // 指定使用第一个组件作为依赖
  dependencies: <Type>[FirstComponent],
  modules: <Type>[SecondModule],
  builder: SecondComponentBuilder,
)
@singleton
abstract class SecondComponent {
  String getString();
}

// 如果使用组件作为依赖,需要组件构建器。
@componentBuilder
abstract class SecondComponentBuilder {
  // 设置第一个组件
  SecondComponentBuilder setFirstComponent(FirstComponent component);

  SecondComponent build();
}

@module
abstract class SecondModule {
  @provides
  @singleton
  static double provideDouble() => 0.0;

  @provides
  static String provideSting(
    int i, // 从第一个组件使用
    double d, // 从这个模块使用
  ) =>
      '$i, $d';
}

模块

模块是一个简单的类,包含创建对象的逻辑。模块只包含提供依赖的方法。通常,每个模块包括与应用程序逻辑相关的对象。

@module
abstract class <ModuleName> {
  ...
  提供和绑定方法
  ...
}

模块必须是抽象的,并且只包含静态或抽象方法。

包含的模块

includes 中的模块及其递归包含的附加模块贡献的所有内容都将贡献到对象图。

@Module(includes: <Type>[Module2, Module3])
abstract class Module1 {
...

提供方法

带有 @provides 注解的方法返回将用于依赖图中的类实例。

@provides
static String provideSting() => 'hello';

一个方法可以包含构造其返回对象的参数。“依赖项”也必须在同一个或另一个模块中提供。

@provides
static int provideInteger() => 0;

@provides
static double provideDouble() => 0.0;
  
@provides
static String provideSting(int i, double d) => '$i, $d';

绑定方法

@binds 方法与 @provides 相同,但它将接口绑定到实现。该方法必须是抽象的,并且有一个实现返回类型的参数。

abstract class MyInterface { }

class MyImplementation implements MyInterface {
  @inject
  const MyImplementation();
}

@module
abstract class MyModule {
  @binds
  MyInterface bindMyClass(MyImplementation impl);
}

单例

此注解用于指示仅创建依赖对象的一个实例。 注意:此作用域应用于每个模块单独!

它可以应用于模块中的方法和类构造函数。

@provides
@singleton // 告诉图中只有一个实例。
static int provideInteger() => 0;
@singleton // 如果没有提供者,将使用此类。
class MyClass {
  @inject
  const MyClass();
}

注入

注解告诉 jugger 是否在构建图时使用该注解。

注入构造函数

注解告诉 jugger 在构建图时是否使用给定的类。

class MyClass {
  @inject
  const MyClass();
}

对于这样的类,您不能在模块中声明提供者方法,jugger 将自行理解并生成它。

import 'package:example/main.jugger.dart';
import 'package:jugger/jugger.dart';

void main() {
  final MyComponent firstComponent = JuggerMyComponent.create();
  print(firstComponent.getStringProvider().getString());
}

@Component(
  modules: <Type>[MyModule],
)
abstract class MyComponent {
  StringProvider getStringProvider();
}

@module
abstract class MyModule {
  @provides
  static int provideInteger() => 0;

  @provides
  static double provideDouble() => 0.0;
}

class StringProvider {
  // 注入构造函数,jugger 本身会创建一个提供者类
  @inject
  // 将使用该模块中的依赖项
  const StringProvider(this.d, this.i);

  final int i;
  final double d;

  String getString() => '$i, $d';
}

注入方法

方法也可以被注入。当 jugger 创建类的实例时,它会被调用。

import 'package:example/main.jugger.dart';
import 'package:jugger/jugger.dart';

void main() {
  final MyComponent firstComponent = JuggerMyComponent.create();
  print(firstComponent.getStringProvider().getString());
}

@Component(
  modules: <Type>[MyModule],
)
abstract class MyComponent {
  StringProvider getStringProvider();
}

@module
abstract class MyModule {
  @provides
  static int provideInteger() => 0;

  @provides
  static double provideDouble() => 0.0;
}

class StringProvider {
  @inject
  StringProvider(this.d);

  final double d;

  String? _s;

  // 注入方法
  @inject
  // 将使用该模块中的依赖项
  void init(int i) {
    _s = '$i, $d';
  }

  String getString() => _s ?? '';
}

限定符

@Qualifier 注解用于区分相同类型但不同实例的对象。 命名限定符示例:

@module
abstract class AppModule {
  @provides
  @Named('dev')
  static AppConfig provideDevAppConfig() {
    return const AppConfig('https://dev.com/');
  }

  @provides
  @Named('release')
  static AppConfig provideReleaseAppConfig() {
    return const AppConfig('https://dev.com/');
  }

  @provides
  @singleton
  static AppConfig provideAppConfig(
    AppEnvironment environment,
    @Named('dev') AppConfig dev,
    @Named('release') AppConfig release,
  ) {
    switch (environment) {
      case AppEnvironment.dev:
        return dev;
      case AppEnvironment.release:
        return release;
    }
  }
}

您还可以声明自定义限定符:

@qualifier
class Release {
  const Release();
}

const Release release = Release();

@qualifier
class Dev {
  const Dev();
}

const Dev dev = Dev();

并且使用方式如下:

@provides
@dev
static AppConfig provideDevAppConfig() {
  return const AppConfig('https://dev.com/');
}

可处置组件

生命周期与组件相同的对象可以被处置。通常它们是单例对象。要处置此类对象,您需要向组件添加一个方法:

@Component()
abstract class AppComponent {
  Future<void> dispose();
}

对象本身必须是作用域化的(单例):

@singleton
@disposable
class MySingleton {
  @inject
  const MySingleton();
}

或者在模块中:

@Module
abstract class AppModule {
  @provides
  @singleton
  @disposable
  static MySingleton provideMySingleton() => MySingleton();
}

可处置对象必须有一个 dispose 方法:

class MySingleton {
  @inject
  const MySingleton();

  void dispose() { }
  // 或
  Future<void> dispose() async {}
}

如果不可能声明一个 dispose 方法,您可以分配另一个方法来进行处置。

为此,您需要指定 disposable 注解的委托策略:

@provides
@singleton
@Disposable(strategy: DisposalStrategy.delegated)
static MySingleton provideMySingleton() => MySingleton();

并为模块添加一个方法来处置对象:

@disposalHandler
static Future<void> disposeMySingleton(MySingleton mySingleton) async {
  await mySingleton.close();
}

可处置对象看起来像这样:

class MySingleton {
  @inject
  const MySingleton();

  Future<void> close() async {}
}

要处置组件对象,只需调用 dispose。之后,组件将无法使用。操作将是幂等的。

多绑定

Jugger 允许您将多个对象绑定到集合中,即使这些对象是在不同的模块中绑定的。Jugger 将这些集合组装在一起,以便应用程序代码可以注入而无需直接依赖于各个绑定。

集合多绑定

为了将一个元素贡献给可注入的多绑定集合,向您的模块方法添加 @IntoSet 注解:

@Module
abstract class Module {
  @provides
  @intoSet
  static String provideString1() => '1';

  @provides
  @intoSet
  static String provideString2() => '2';
}

现在组件可以提供集合:

@Component(modules: <Type>[Module])
abstract class AppComponent {
  Set<String> get strings;
}

或者组件中的绑定可以依赖于集合:

@provides
static int provideCount(Set<String> strings) => strings.length;
映射多绑定

Jugger 允许您使用多绑定将条目贡献到可注入的映射中,只要映射键在编译时已知。

要向多绑定映射贡献一个条目,向模块添加一个返回值的方法,并使用 @IntoMap 和另一个指定映射键的自定义注解。

对于键为字符串或包装的原语的映射,使用其中一个标准注解:

@Module
abstract class Module {
  @provides
  @intoMap
  [@StringKey](/user/StringKey)('b')
  static int provideInt1() => 1;

  @provides
  @intoMap
  [@StringKey](/user/StringKey)('a')
  static int provideInt2() => 2;
}

@Component(modules: <Type>[Module])
abstract class AppComponent {
  Map<String, int> get ints;
}

支持的键类型有:

  • String
  • int
  • double
  • bool
  • Type
  • Enum

Jugger 已经有几个预构建的注解:

但是,您也可以创建自定义注解:

@mapKey
class MyKey {
  const MyKey(this.value);

  final double value;
}

延迟初始化

有时依赖项需要稍后而不是立即初始化。您可以使用 Lazy 来实现这一点。这对于不初始化不会使用的依赖项非常有用。例如,这是一个选择在提供者方法中使用哪个依赖项的例子:

[@j](/user/j).provides
static Repository provideRepository(
  ILazy<OfflineRepository> offline, 
  ILazy<OnlineRepository> online,
  bool isOnline,
) {
    if (isOnline) {
      return online.get();
    } else {
      return offline.get();
    }
}

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

1 回复

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


当然,下面是一个关于如何在Flutter项目中使用jugger_generator插件的代码案例。jugger_generator是一个代码生成工具,旨在简化Flutter项目的开发过程,通过自动生成模板代码来提高效率。

前提条件

  1. 确保你已经安装了Flutter和Dart。
  2. 确保你的项目已经初始化为一个Flutter项目。

步骤一:添加依赖

首先,你需要在pubspec.yaml文件中添加jugger_generator的依赖。

dependencies:
  flutter:
    sdk: flutter
  jugger_generator: ^最新版本号 # 请替换为最新版本号

dev_dependencies:
  build_runner: ^最新版本号 # build_runner用于运行代码生成命令

然后运行flutter pub get来安装依赖。

步骤二:配置build.yaml

在项目的根目录下创建一个build.yaml文件,用于配置jugger_generator的行为。以下是一个简单的配置示例:

targets:
  $default:
    builders:
      jugger_generator:
        enabled: true

步骤三:创建输入文件

jugger_generator通常需要一些输入文件来生成代码。这些输入文件可以是YAML、JSON或其他格式,具体取决于插件的配置和用途。

假设我们有一个简单的YAML文件models.yaml,内容如下:

models:
  - name: User
    fields:
      - {name: id, type: int, primaryKey: true}
      - {name: name, type: String, nullable: false}
      - {name: email, type: String, nullable: true}

步骤四:运行代码生成器

在项目根目录下运行以下命令来生成代码:

flutter pub run build_runner build

这将根据models.yaml文件生成相应的Dart模型文件。假设生成的模型文件名为user_model.dart,内容可能如下所示:

import 'package:json_annotation/json_annotation.dart';

part 'user_model.g.dart';

@JsonSerializable()
class User {
  int? id;
  String name;
  String? email;

  User({
    this.id,
    required this.name,
    this.email,
  });

  factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);

  Map<String, dynamic> toJson() => _$UserToJson(this);
}

注意:生成的代码文件(如user_model.g.dart)通常是由插件自动管理的,不需要手动编辑。

步骤五:使用生成的代码

现在你可以在你的Flutter项目中使用生成的模型类了。例如:

import 'package:flutter/material.dart';
import 'user_model.dart'; // 导入生成的模型文件

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Flutter Demo'),
        ),
        body: Center(
          child: Text('Hello, ${User(name: 'John Doe', email: 'john.doe@example.com').name}!'),
        ),
      ),
    );
  }
}

总结

通过上述步骤,你已经成功地在Flutter项目中使用了jugger_generator插件来生成代码。这只是一个简单的示例,jugger_generator插件可能支持更多复杂的配置和输入格式,具体请参考其官方文档以获取更多信息。

回到顶部