Flutter笔记:getX库中的GetView中间件

Flutter笔记
getX库中的GetView中间件

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


flutter-ljc

如果你对一些原生 Flutter 和 GetX 中的概念不熟悉,如状态提升、控制器、改变通知、GetX控制器、GetX服务,可以在阅读本文前先阅读《状态模式、控制器模式、GetX控制器和服务
。本文假设你已经有这些基础的情况下,进而介绍针对于 GetX 状态实例采用单例模式的实践,以及 依赖注入 在基于 GetXFlutter 项目中的应用。

另请参考 《状态模式、控制器模式、GetX控制器和服务》,地址:https://blog.csdn.net/qq_28550263/article/details/134350949


1. GetPage简介

GetViewGetX 库中的一个用于构建视图的组件。它与一个注册的 Controller 关联,并通过 getter 方法提供对该 Controller 的访问。用人话说就是,GetView 简化了 GetX 中对 控制器的访问。

2. 控制器模式思想的简要回顾

2.1 状态提升模式的缺陷

当我们做状态管理的时候,一个常用的手段就是 状态提升(State Lifting)。状态提升是一种在 ReactFlutter 等前端和跨端框架中都十分常见的设计模式,用于管理组件之间共享状态的方法。这种模式通常用于处理以下情况:

  • 共享数据: 当多个组件需要访问和共享相同的数据时,将状态提升到这些组件的共同祖先组件中,以便它们可以共享数据。

  • 状态同步: 当某个状态需要被多个组件 修改 时,将这个状态提升到共同的父组件,由父组件负责管理和更新状态,然后将状态传递给子组件。

首先,如果一个状态需要被很多组件访问,那么这个状态就需要被提升到很高的层级,这会使得组件树变得复杂。其次,状态提升模式使得状态和UI紧密耦合,这可能会导致代码难以维护和测试。另外一个方面,状态提升在Flutter中将很容易导致扩大刷新范围,浪费性能。因此需要一种有效的解决方案来弥补状态提升的不足。这个解决方案就是所谓的 控制器模式

2.2 控制器模式

在原生Flutter中,控制器模式的基本思想是通过创建一个独立的控制器类,将与业务逻辑相关的状态和方法都封装在这个控制器中。控制器类负责管理状态、处理逻辑,而界面组件则负责展示UI,并通过控制器来获取或更新状态。

  1. 独立控制器类: 创建一个独立的控制器类,用于管理相关的状态和逻辑。

  2. 状态封装: 将与业务逻辑相关的状态封装在控制器中,使得控制器成为状态的唯一管理者。

  3. 界面组件简化: 界面组件专注于展示UI,通过控制器获取状态和调用方法,避免在组件内部处理过多的业务逻辑。

  4. 解耦和复用: 通过使用控制器,实现组件的状态和行为的解耦,提高代码的复用性和可维护性。

3. 使用单例模式控制GetX实例数量

3.1 不使用 工厂构造方法 单例模式

基于 Dart 对于面向对象语法中相关功能的支持,为控制器或者服务实现单例可以按照下面的三个步骤进行:

  1. 供一个私有静态属性用于存储唯一的控制器实例;
  2. 创建用于内部静态构造的构造器,尽量避免提供外部可访问的构造方法;
  3. 提供一个外部访问的访问器接口,在该接口中:
  • 如果还没有创建过控制器,则内部构建数以该类的唯一构造器实例后返回;
  • 如果存储的构造器已经非空,则返回该之前创建过的属于该类的唯一构造器实例。

基于以上步骤,一个计数器控制器增加单例控制的面向对象实现如下:

import 'package:get/get.dart';

/// 计数器控制器类
class CounterController extends GetxController {
  // 提供一个私有静态属性用于存储唯一的控制器实例
  static CounterController? _instance;

  // 仅提供一个私有构造器防止外部创建实例
  CounterController._();

  // 提供一个外部访问的访问器接口
  static CounterController? get to {
    // 表示仅仅当 _instance 为 null 时,内部构造该控制器实例
    _instance ??= CounterController._();
    return _instance;
  }

  // 下面表示一些状态变量个状态相关的内容...
  int counter = 0;
  void increment() {
    counter++;
    update();
  }
}

3.2 使用 工厂构造方法 实现单例模式

在Dart中,我们可以使用工厂构造函数来实现单例模式。工厂构造函数与普通构造函数不同,它不一定每次都会创建一个新的实例。相反,工厂构造函数可以从缓存中返回一个已存在的实例,或者返回一个子类型的实例。

以下是如何使用工厂构造函数来实现CounterController的单例:

import 'package:get/get.dart';

class CounterController extends GetxController {
  // 提供一个私有静态属性用于存储唯一的控制器实例
  static CounterController? _instance;

  // 使用工厂构造函数来实现单例
  factory CounterController() {
    if (_instance == null) {
      _instance = CounterController._internal();
    }
    return _instance!;
  }

  // 私有构造函数
  CounterController._internal();

  // 下面表示一些状态变量个状态相关的内容...
  int counter = 0;
  void increment() {
    counter++;
    update();
  }
}

在这个例子中,我们首先定义了一个私有的静态属性_instance来存储单例对象。然后,我们定义了一个工厂构造函数CounterController(),它会检查_instance是否为null,如果为null,就创建一个新的CounterController实例并赋值给_instance,否则就直接返回_instance。这样,我们就可以确保CounterController的单例性。

然后,我们定义了一个私有的构造函数CounterController._internal(),这个构造函数只能在类的内部被调用,这样可以防止外部代码创建CounterController的新实例。

最后,我们定义了一些状态变量和状态相关的方法,这些与单例模式无关,只是CounterController的业务逻辑。

4. GetX 依赖注入

4.1 依赖注入的基本概念

依赖注入(Dependency Injection,简称DI)是一种设计模式,它的主要目标是实现松耦合。在依赖注入中,一个对象不需要直接创建或管理它的依赖项,而是通过外部的方式(例如构造函数、属性或方法)来接收这些依赖项。这样,对象就可以专注于自己的业务逻辑,而不需要关心如何创建和管理依赖项。

依赖注入的主要优点是提高了代码的可测试性和可重用性。由于对象不再直接创建和管理依赖项,所以我们可以在测试时轻松地替换依赖项,例如使用模拟对象(Mock Object)来代替真实的依赖项。此外,由于依赖项是通过外部的方式来提供的,所以我们可以在不同的场景下使用不同的依赖项,这增加了代码的可重用性。

4.2 GetX中的依赖注入原理

GetX 的依赖注入原理主要基于 Dart 的映射(Map)数据结构和 GetX 的依赖管理容器。在 GetX 中,依赖管理容器是一个全局的映射表,键是依赖项的类型,值是依赖项的实例。

当你调用 Get.put()Get.lazyPut()Get.putAsync()Get.create() 等方法时,GetX 会首先检查依赖管理容器中是否已经存在相同类型的依赖项。如果不存在,GetX 会创建一个新的依赖项,并将它的类型和实例添加到依赖管理容器中。如果已经存在,GetX 会根据具体的方法来决定是否替换已存在的依赖项。

当你调用 Get.find() 方法时,GetX 会根据指定的类型从依赖管理容器中查找对应的依赖项。如果找到了,就返回这个依赖项;如果没有找到,就抛出一个异常。

当你调用 Get.delete() 方法时,GetX 会根据指定的类型从依赖管理容器中删除对应的依赖项。如果这个依赖项是一个GetxControllerGetxServiceGetX还会调用它的 onClose()方法。

通过这种方式,GetX 可以实现依赖项的自动管理,包括创建、查找、删除和生命周期管理。这大大简化了依赖管理的复杂性,使得开发者可以更专注于业务逻辑的实现。

4.3 基于GetX依赖注入使用单例

注入依赖

基于GetX依赖注入使用单例时,需要调用 Get.put()、Get.lazyPut()、Get.putAsync()、Get.create() 创建一个新的依赖项,然后将它注册到依赖管理容器中。每个依赖项都有一个对应的类型,GetX会根据这个类型来管理和查找依赖项。

例如:

void main() {
  Get.put(CounterController());
  runApp(MyApp());
}

由于往往需要创建使用多个依赖,这种情况下,我个人习惯于在项目中创建一个 app_injections.dart 文件用于存放这些依赖项。例如:

class DependencyInjection {
  static void init() {
    Get.lazyPut(() => AuthService(AuthProvider())); 
    Get.lazyPut(() => ChatService()); 
    // 继续添加其它依赖项
  }
}

另外,使用 GetX 时,则顶层组件使用的是其包装的 GetMaterialApp 以替代 MaterialApp。你可以选择在 onInit 中完成一些初始化工作, 比如:

GetMaterialApp(
  //...省略其它内容
  onInit: () async {
    await initialization(context); // 等待初始化完成
  },
)

其中,为了代码简洁,可以单独写一个 initialization 方法:

Future<void> initialization(BuildContext context) async {
  // 可以在这里完成依赖项初始化
  DependencyInjection.init();
}

获取依赖

你调用Get.find()方法时,GetX会根据指定的类型从依赖管理容器中查找对应的依赖项。如果找到了,就返回这个依赖项;如果没有找到,就抛出一个异常,因此请确保你的依赖被正确注入。

final counterController = Get.find<CounterController>();

删除依赖项

当你调用Get.delete()方法时,GetX会根据指定的类型从依赖管理容器中删除对应的依赖项。如果这个依赖项是一个GetxController或GetxService,GetX还会调用它的onClose()方法。如:

Get.delete<CounterController>();

生命周期管理

如果一个依赖项是一个 GetxControllerGetX 会自动管理它的生命周期。当这个依赖项被创建时,GetX会调用它的 onInit() 和 onReady()方法;当这个依赖项被删除时,GetX 会调用它的 onClose() 方法。

相比之下GetxService 的实例在整个应用生命周期中都是可用的,它们不会被自动回收。这是因为GetxService通常用于提供全局的、需要长期存在的服务,例如用户认证、数据库操作、网络请求等。

作用域管理

GetX支持作用域管理,你可以使用Get.createScope()和Get.deleteScope()方法来创建和删除作用域。每个作用域都有一个唯一的标识符,你可以在创建和获取依赖项时指定作用域。

Get.createScope('myScope');
Get.put(CounterController(), tag: 'myScope');
final counterController = Get.find<CounterController>(tag: 'myScope');
Get.deleteScope('myScope');

4.4 GetX 控制器内置的一种简化的单例获取方法

在GetX中,除了使用Get.find()方法来获取控制器的单例实例外,还可以使用.to的方式来获取。这是GetX为我们提供的一种简化的获取单例的方式。

当你创建一个继承自GetxController的类时,你可以在类的内部定义一个静态的to属性,这个属性会自动获取这个类的单例实例。这样,你就可以直接通过.to的方式来获取这个类的单例实例,而不需要每次都调用Get.find()方法。

例如:

class CounterController extends GetxController {
  static CounterController get to => Get.find();

  var count = 0.obs;

  void increment() {
    count.value++;
  }
}

在这个例子中,我们定义了一个静态的to属性,它会自动调用Get.find()方法来获取CounterController的单例实例。然后,我们就可以在其他地方通过CounterController.to的方式来获取这个单例实例:

CounterController.to.increment();

显而易见的是,这种方式的好处是,它可以让代码看起来更简洁,而且更符合面向对象的设计原则。你心动了吗?赶紧试一试修改你大量的 find 吧。

5. GetView中间件

在你想着要修改 find 使用 4.4 小节 的方式时,有时候可能使用更简单的 GetView 就可以完成你的需求。

由于有时候在 Flutter 中,我们经常需要在不同的组件中访问和操作同一个控制器实例。为了避免在每个组件中都写一遍 Get.find()GetX 库提供了一个 GetView 中间件。

GetView 是一个抽象类,它继承自 StatelessWidget,并提供了一个 controller 属性,这个属性会自动获取对应类型的控制器实例,因此相比于在 StatelessWidget 中自己获取单例, 使用起GetView 来会更加地方便。

GetView 的主要功能是简化控制器的获取。当你创建一个继承自 GetView 的组件时,你只需要指定控制器的类型,然后你就可以在组件中直接使用 controller 属性来访问控制器实例。

例如,还是哪个计数器地例子,我们再一次进行改进。我们创建一个 CounterController 类,它继承自 GetxController 并有一个 count 状态和一个 increment 方法:

class CounterController extends GetxController {
  var count = 0.obs;

  void increment() {
    count.value++;
  }
}

然后,我们创建一个继承自GetView的组件:

class CounterPage extends GetView<CounterController> {
  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Counter')),
      body: Center(
        child: Obx(() => Text('Count: ${controller.count.value}')),
      ),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.add),
        onPressed: controller.increment,
      ),
    );
  }
}

在这个例子中,GetView 会自动获取一个 CounterController 的实例,并将它赋值给 controller 属性。然后,我们就可以在组件中直接使用 controller 属性来访问和操作 CounterController 的实例。这样,我们就不需要在每个组件中都写一遍 Get.find() 了。

  • 6
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: 在使用 GetX 库中的 Obx 方法时,可以使用 async 和 await 实现异步操作。 例如,在一个 Obx 函数中调用一个异步函数,并使用 await 关键字等待返回结果: ``` final myValue = Obx(() async { var value = await someAsyncFunction(); return value; }); ``` 在这个例子中,someAsyncFunction() 是一个异步函数,Obx 包装了它并返回一个 Obx 对象。 当 someAsyncFunction() 返回时,Obx 会自动刷新并通知所有依赖它的 widgets 进行更新。 需要注意,在 Obx 中使用 await 的情况,需要确保 Obx 函数是 async 的。 ### 回答2: 在Flutter的GetX库中,我们可以使用`Obx`方法来实现响应式更新。`Obx`方法接受一个返回`Rx`对象的回调函数作为参数,并在该回调函数中监听数据的变化。在`Obx`回调函数中,我们可以使用`async`和`await`来执行异步操作,以实现更复杂的逻辑。 要在`Obx`方法中使用`async`和`await`,首先定义一个`Rx`对象,例如`RxInt`、`RxString`或`RxList`等,作为需要监听和更新的数据。 接下来,我们可以在`Obx`回调函数中使用`async`关键字来标记该函数为异步函数。然后,可以使用`await`关键字来等待异步操作的结果。 例如,我们可以在`Obx`回调函数中调用一个异步函数,等待其执行完成后更新数据: ``` final count = RxInt(0); void fetchData() async { // 模拟一个耗时的异步操作 await Future.delayed(Duration(seconds: 2)); // 更新count的值 count.value = 10; } Obx(() { return Text('Count: ${count.value}'); }); ``` 在上面的例子中,`count`是一个`RxInt`对象,用来表示需要更新的数据。`fetchData`是一个异步函数,模拟一个耗时的操作,并在操作完成后更新`count`的值。在`Obx`回调函数中,使用`Text`组件显示`count`的值。 当调用`fetchData`函数时,由于其内部包含`await`关键字,程序会等待异步操作完成后再进行下一步的处理。一旦异步操作完成,`count`的值会发生变化,从而触发`Obx`回调函数的执行,更新界面上展示的`Text`组件的内容。 总结来说,在GetX库中,我们可以在`Obx`方法中使用`async`和`await`来执行异步操作,从而实现动态更新界面的效果。 ### 回答3: 在Flutter的GetX库中,Obx方法并不能直接使用async和await关键字。Obx是GetX库中的一个观察者小部件,用于侦听并响应任何被观察对象(例如Rx变量)的更改。但是,可以通过使用RxStream或RxWorker来实现类似于async和await的功能。 要在Obx中实现异步操作,可以使用RxStream。首先,创建一个RxStream对象,并使用StreamController将异步操作包装成一个流。然后,在Obx方法内部,使用Obx(() {})方法来订阅RxStream。每当这个流的数据发生变化时,Obx方法将被调用,可以在其中更新UI。 以下是一个示例代码: ```dart final count = RxInt(0); void incrementCount() async { await Future.delayed(Duration(seconds: 1)); count.value++; } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text('Async Operation')), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Obx(() => Text('Count: ${count.value}')), ElevatedButton( child: Text('Increment'), onPressed: incrementCount, ), ], ), ), ); } ``` 在这个示例中,我们使用RxInt来管理一个计数器,名为count。在incrementCount方法中,我们使用async和await关键字模拟了一个异步操作,通过延迟1秒钟来增加计数。在UI中,我们使用Obx(() {})方法订阅了count。每当count发生变化时,Obx内部的匿名函数就会被触发,并在Text小部件中更新显示。 总结来说,尽管Obx方法本身不能直接使用async和await关键字,但可以结合使用RxStream或RxWorker来实现类似的异步操作。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

jcLee95

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值