Flutter容器管理插件dart_container的使用

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

Flutter容器管理插件dart_container的使用

描述

该插件为Dart语言提供了依赖注入解决方案,并且包含一个基于[dart_router_extended]的应用服务器。

特性

  • 简单注入:注册对象实例。这基本上会被视为单例对象。每次注入调用都会返回相同的对象。
  • 懒惰注入:注册生成对象的构建器函数。该函数将在第一次执行注入时被调用,然后在后续的注入中返回相同对象。
  • 工厂注入:注册生产对象的工厂函数。
  • 有资格名称的注入:容器支持有资格的注入,因此你可以为你的依赖提供名称。
  • 注入配置文件:你可以为多个配置文件注册某个对象,然后根据选择的配置文件来注入或不注入值。这个功能对于想要以不同的注入配置运行应用程序非常有用。
  • 值注入:将简单的命名值注入到容器中。
  • Web服务器配置
  • Web路由和控制器支持
  • 使用路由保护进行Web路由安全
  • CORS配置支持
  • 计划任务支持
  • 事件支持

使用方法

简单注入

var myObject = MyClass();
var myProperty = "Prop value";

// 注册一个对象到容器
$().generic(object: myObject)
   // 然后注册一个值
   .value("myProperty", myProperty)
   // 然后注册一个有别名的对象。这也可以与构建器和工厂一起使用
   .generic(object: myObject, name: "alias");

// 获取对象
MyClass injectedObject = $().get();
// 获取有别名的对象
MyClass injectedObjectAlias = $().get(name: "alias");

// 如果存在,则获取对象
MyClass? injectedObjectIfPresent = $().getIfPresent();
// 获取有别名的对象
MyClass? injectedAliasObjectIfPresent = $().getIfPresent(name: "alias");

// 也可以使用快捷方法
MyClass injectedObject = $get();
MyClass injectedObjectAlias = $get(name: "alias");
MyClass? injectedObjectIfPresent = $$get();

// 获取值
String property = $().getValue("myProperty");
String? propertyIfPresent = $().getValueIfPresent("myProperty");

// 或者使用快捷方法
String property = $val("myProperty");
String? propertyIfPresent = $$val("myProperty");

构建器和工厂注入

构建器和工厂本质上都是用于构建容器对象的方法,区别在于工厂方法每次从容器中检索对象时都会被调用,而构建器方法只在首次创建容器对象时调用一次,之后每次都返回相同的实例,使其基本上成为一个延迟构建的单例。

class SimpleObj {
    final String timestamp;
    SimpleObj(this.timestamp);
}

// 注册到容器
$()
    // 注册一个只会被调用一次以创建容器对象的构建器函数
    .generic(builder: () => MyClass())
    .generic(factory: () => SimpleObj(DateTime.now().microsecondsSinceEpoch.toString()));

// 获取对象
MyClass injectedObject = $().get();
// 使用注入的工厂生成对象
SimpleObj injectedObjectIfPresent = $().getIfPresent();

// 也可以使用快捷方法
MyClass injectedObject = $get();
SimpleObj injectedObjectIfPresent = $$get();

条件回调

class SimpleObj {
    final String timestamp;
    SimpleObj(this.timestamp);
}

// 注册到容器
$()
    // 注册一个只会被调用一次以创建容器对象的构建器函数
    .generic(builder: () => MyClass())
    .generic(factory: () => SimpleObj(DateTime.now().microsecondsSinceEpoch.toString()));

// 获取对象
MyClass injectedObject = $().get();
// 使用注入的工厂生成对象
SimpleObj? injectedObjectIfPresent = $().getIfPresent();

// 也可以使用快捷方法
MyClass injectedObject = $get();
SimpleObj? injectedObjectIfPresent = $$get();

// 条件回调,仅当容器中存在对象时才调用某些代码
Container().ifPresentThen<MyClass>((MyClass obj) {
    print(obj);
});
// 或者使用快捷方法
$then<MyClass>((MyClass obj) {
    print(obj);
});

// 条件回调。仅当容器中存在值时才调用某些代码
$().ifValuePresentThen("valueKey", (value) {
    print(value);
});
// 或者使用快捷方法
$valThen("valueKey", (value) {
    print(value);
});

// 条件回调,具有多个依赖项。只有当所有依赖项都找到时,容器才会调用回调
$().ifAllPresentThen([
    Lookup.object(MyClass), 
    Lookup.object(SimpleObject), 
    Lookup.value("valueKey")
    ], (list) {
        MyClass? myClass;
        SimpleObject? simpleObject;
        String? value;
        [myClass, simpleObject, value] = list;
});
// 或者调用快捷方法
$allThen([
    $look(MyClass), 
    $look(SimpleObject), 
    $lookVal("valueKey")
    ], (list) {
        MyClass? myClass;
        SimpleObject? simpleObject;
        String? value;
        [myClass, simpleObject, value] = list;
});

使用配置文件

var myObject = MyClass();
var myProperty = "Prop value";

// 注册到容器
$()
    .generic(object: myObject, profiles: ["test", "run"])
    .value("myProperty", myProperty, profiles: ["test", "run"])
    // 设置活动配置文件
    .profile("run");

// 获取对象。注入总是使用活动配置文件来注入任何已注册的对象或提供的值
// 如果对于活动配置文件对象不在容器中,此方法将抛出异常
MyClass injectedObject = $().get();
// 获取对象(如果存在)
// 如果对于活动配置文件对象不在容器中,此方法将返回null
MyClass? injectedObjectIfPresent = $().getIfPresent();

// 获取值。如果在活动配置文件中不存在值,此方法将抛出异常
String property = $().getValue("myProperty");
// 如果在活动配置文件中不存在值,此方法将返回null
String? propertyIfPresent = $().getValueIfPresent("myProperty");

注入接口的对象

class MyInterface {
    void doSomething() {}
}

class MyClass implements MyInterface {
    @override
    void doSomething() {
        print("Something");
    }
}

var myObject = MyClass();

// 为接口而不是类型向容器注册对象
$().typed(MyInterface, object: myObject);

// 如果对于活动配置文件对象不在容器中,此方法将抛出异常
MyInterface injectedObject = $().get();
// 获取对象(如果存在)
// 如果对于活动配置文件对象不在容器中,此方法将返回null
MyInterface? injectedObjectIfPresent = $().getIfPresent();

自启动对象

class AutoStartMock implements AutoStart {
  @override
  void init() {
    print("Init called");
  }

  @override
  void run() {
    print("Run called");
  }
}

$().generic(builder: () => AutoStartMock(), autoStart: true)
  // 一旦调用自启动,AutoStart对象将首先调用init方法,
  // 然后异步调用run方法,以避免阻塞容器和其他功能
  .autoStart();

计划任务

配置调度程序

// 将定时器轮询间隔设置为指定的持续时间
// 轮询间隔对于长时间运行的计划任务很有用。较长的时间间隔会减少CPU负载,但也会降低触发时间的准确性
// 如果未指定,默认值为10秒
$().schedulerPollingInterval(Duration(seconds: 1));

// 将所有任务的开始延迟设置为指定的持续时间
$().schedulerInitialDelay(Duration(seconds: 10));

一次性计划任务

class OneTimeScheduledJob implements ScheduledJob {
  bool hasRun = false;
  @override
  Duration? getDuration() => Duration(seconds: 1);

  @override
  ScheduledJobType getType() => ScheduledJobType.oneTime;

  @override
  void run() {
    hasRun = true;
  }

  @override
  DateTime? getStartTime() => null;
}

// 任务将在1秒后运行,如getDuration实现所提供的
$().schedule(oneTime).autoStart();

周期性计划任务

class PeriodicScheduledJob implements ScheduledJob {
  int runTimes = 0;
  @override
  Duration? getDuration() => Duration(seconds: 1);

  @override
  ScheduledJobType getType() => ScheduledJobType.periodic;

  @override
  DateTime? getStartTime() => null;

  @override
  void run() {
    runTimes++;
  }
}

// 立即运行,然后每秒运行一次,如getDuration实现所提供的
PeriodicScheduledJob periodic = PeriodicScheduledJob();
$().schedule(periodic).autoStart();

指定时间的一次性计划任务

class AtExactTimeScheduledJob implements ScheduledJob {
  bool ran = false;
  @override
  Duration? getDuration() => null;

  @override
  DateTime? getStartTime() => DateTime.now().add(Duration(seconds: 3));

  @override
  ScheduledJobType getType() => ScheduledJobType.atExactTime;

  @override
  void run() {
    ran = true;
  }
}

AtExactTimeScheduledJob atTime = AtExactTimeScheduledJob();

// 任务将在3秒后运行
$().schedulerPollingInterval(Duration(seconds: 1))
  .schedule(atTime)
  .autoStart();

指定时间重复计划任务

class AtExactTimeRepeatingScheduledJob extends ScheduledJob {
  bool ran = false;
  @override
  Duration? getDuration() => Duration(seconds: 2);

  @override
  DateTime? getStartTime() => DateTime.now().add(Duration(seconds: 3));

  @override
  ScheduledJobType getType() => ScheduledJobType.atExactTime;

  @override
  void run() {
    ran = true;
  }
}

AtExactTimeScheduledJob atTime = AtExactTimeScheduledJob();

// 任务将在3秒后运行,然后每隔2秒运行一次
$().schedulerPollingInterval(Duration(seconds: 1))
  .schedule(atTime)
  .autoStart();

Web服务器

class StausController extends Controller {
  StatusController()
      : super(
          // 控制器下的所有路由都挂载在控制器前缀下
          pathPrefix: "/status",
          routes: [
            GetStatusRoute(),
            PostStatusRoute(),
          ],
          // 守卫允许对路由进行安全访问。如果你不想任何人访问路由或控制器,可以实现一个守卫
          guard: StatusGuard(),
        );
}

class StatusGuard extends RouteGuard {
  @override
  bool isSecure(Request request) {
    return true;
  }
}

class GetStatusRoute extends AbstractRoute {
  GetStatusRoute()
      : super(
          // 所有的路由解析完全兼容shelf_router,因为实际使用的库就是它
          ["/<key>"],
          Method.get,
        );
  
  @override
  Function buildHandler() {
    return _respond;
  }

  Response _respond(Request req, String key) {
    return JsonResponse.okJson({key: "requested"});
  }
}

class PostStatusRoute extends AbstractRoute {
  PostStatusRoute()
      : super(
        ["/<key>"],
        Method.post,
      );

  @override
  Function buildHandler() {
    return _respond;
  }

  Response _respond(Request req, String key) async {
    return JsonResponse.ok({key: "posted"});
  }
}

$().webServerConfig(
  // 首先我们需要一个未找到处理器
  (req) => Response.notFound("Route not found for ${req.method}:${req.requestedUri}"),
  // 主机
  'localhost',
  // 端口
  env.httpPort,
  shared: true,
  profiles: ["test", "run"],
)
  // 提供控制器列表
  .controllers([
    StatusController();
  ])
  // 和/或提供路由列表
  .routes([
    GetStatusRoute(),
    PostStatusRoute(),
  ])
  // 设置活动配置文件
  .profile("run")
  // 调用自启动以启动Web服务器
  .autoStart();

  // 如果你不想使用自启动功能但仍想启动Web服务器,你可以
  Future.delayed(Duration.zero, () async => $get&lt;WebServer&gt;().run());
  .

事件

使用dart-container可以构建一个简单的发布/订阅框架,用于传递消息并构建反应式应用。目前支持的是精确匹配主题。

简单主题

// 首先订阅主题
$().subscribe("topic", (topic, event) {
  print("Got message on topic $topic with event value $event");
});

// 然后发布到主题
$().publishEvent(["topic"], "newValue");

// 也可以订阅和发布到多个主题

// 发布给多个订阅者
$().subscribe("topic1", (topic, event) {
  print("Subscriber 1 : Got message on topic $topic with event value $event");
});

$().subscribe("topic2", (topic, event) {
  print("Subscriber 2 : Got message on topic $topic with event value $event");
});

// 或者使用快捷方法
// $sub("topic2", (topic, event) {
//  print("Subscriber 2 : Got message on topic $topic with event value $event");
//});

// 向多个主题发布消息
// 在这种情况下,两个订阅者都将收到新消息
$().publishEvent(["topic1", "topic2"], "newValue");
// 或者使用快捷方法
// $pub(["topic1", "topic2"], "newValue");

通配符和子主题

$().subscribe("topic/*", (topic, event) {
  // 将接收来自两个子主题的消息
});

// 发布到第一个子主题
$().publishEvent(["topic/subtopic1"], "newValue1");
// 发布到第二个子主题
$().publishEvent(["topic/subtopic2"], "newValue2");

$().subscribe("topic/*", (topic, event) {
  // 将接收来自两个子主题的消息
});

// 发布到第一个子主题
$().publishEvent(["topic/subtopic1", "topic/subtopic1"], "newValue1");
// 重要提示:当向多个子主题发布值时,订阅者只会被调用一次,参数的主题值将是发布列表中匹配的第一个主题的值

更多关于Flutter容器管理插件dart_container的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter容器管理插件dart_container的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,dart_container 是一个假设的 Flutter 插件名称,因为实际上 Flutter 并没有一个直接名为 dart_container 的官方或广泛认可的插件。不过,我可以基于 Flutter 中容器管理的一般概念,提供一个示例代码,展示如何在 Flutter 中管理容器(如页面、对话框等)。

在 Flutter 中,容器通常指的是能够包含其他小部件(widgets)的 widget,例如 ScaffoldStackColumnRow 等。为了模拟一个容器管理插件的功能,我们可以创建一个自定义的 Flutter 插件或者功能,用于管理这些容器。

以下是一个简化的示例,展示如何在 Flutter 中使用基本的容器管理概念。在这个例子中,我们将创建一个简单的页面导航管理,使用 Flutter 的 Navigator 来管理不同的页面(容器)。

示例代码

1. 定义主页面(Main Page)

import 'package:flutter/material.dart';
import 'second_page.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Container Management',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Main Page'),
      ),
      body: Center(
        child: ElevatedButton(
          onPressed: () {
            Navigator.push(
              context,
              MaterialPageRoute(builder: (context) => SecondPage()),
            );
          },
          child: Text('Go to Second Page'),
        ),
      ),
    );
  }
}

2. 定义第二个页面(Second Page)

import 'package:flutter/material.dart';

class SecondPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Second Page'),
      ),
      body: Center(
        child: Text('This is the second page!'),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          Navigator.pop(context);
        },
        tooltip: 'Back to Home',
        child: Icon(Icons.arrow_back),
      ),
    );
  }
}

解释

  1. MyApp 类是应用的入口点,它创建了一个 MaterialApp,并设置了应用的主题和主页。
  2. MyHomePage 类是主页面的实现,它包含一个 Scaffold,在 appBar 中显示标题,在 body 中包含一个按钮。点击按钮时,使用 Navigator.push 方法将第二个页面推送到导航堆栈上。
  3. SecondPage 类是第二个页面的实现,它同样包含一个 Scaffold,并在 appBar 中显示标题,在 body 中显示文本。它还包含一个浮动操作按钮(FAB),点击该按钮时,使用 Navigator.pop 方法将当前页面从导航堆栈中弹出,返回到主页面。

这个示例展示了如何在 Flutter 中使用基本的导航功能来管理不同的页面(容器)。虽然这不是一个实际的 dart_container 插件的示例,但它展示了如何在 Flutter 应用中实现类似的容器管理功能。如果你正在寻找一个特定的容器管理插件,你可能需要查看 Flutter 的 Pub.dev 网站,以找到符合你需求的第三方插件。

回到顶部