flutter GetX

2,001 阅读10分钟

在 Flutter 中,您可能遇到过 BLoc、Provider、RiverPod、ChangeNotifier、MobX 等多种状态管理技术。GetX 是一个快速、轻量级且稳定的状态管理包,它还提供了许多附加功能。在本文中,我们将深入探讨在 Flutter 中使用 GetX。

Flutter 中的 GetX 简介

GetX 是 Flutter 中状态管理问题的超轻量级和强大的解决方案。它包含更少的样板代码、高效的状态管理和简单的路由。 GetX 有 3 个基本原则:

  • 性能:GetX 专注于最少的资源使用和高效的应用程序。
  • 生产力:GetX 减少了 Flutter 中的样板代码。此外,GetX 会在不使用时自动释放控制器,从而减少处理实例的麻烦。
  • 组织:GetX 遵循视图、表示逻辑和业务逻辑完全解耦的模型。这导致易于理解的项目和易于管理。

GetX 的三大支柱

GetX主要解决三个问题。这些是:

  • 状态管理:GetX 有两种方式来管理状态。一种是简单的方式,另一种是反应性的方式。我们将在下一节中详细讨论这一点。
  • 路线管理:如果我们想导航到另一个页面,显示小吃店,显示对话框等,我们可以使用 GetX 轻松完成所有这些,而无需关心上下文。
  • 依赖管理:Get 有一个强大的依赖管理器,它允许你在任何你想要的地方检索你的控制器。

现在我们对 GetX 的目标有了一个简要的概述。让我们讨论一下它是如何实现这种反应式和动态的状态管理的。为此,我们将首先简要讨论 GetX 中的 GetxController,因为一切都围绕着这些 GetxController!

探索 GetxController

GetxController 类负责表示部分和后端部分。我们将所有变量、控制器和后端逻辑保存在控制器类中。 GetxController 类中的 update() 方法负责重建 GetBuilder widget 。

我们创建一个Controller类如下:

import 'package:get/get.dart';

class HomeController extends GetxController {
  
}

现在,让我们看看计数器应用程序的控制器是什么样的:

import 'package:get/get.dart';

class HomeController extends GetxController{
  int counter = 0;

  void increment(){
    counter++;
    // Re-runs the builder function of GetBuilder widget
    update();
  }
}
import 'package:get/get.dart';

class HomeController extends GetxController {
  // We will discuss .obs later in the State Mangement Section
  var count = 0.obs;
  increment() => count++;
}

要创建 HomeController 类的实例,我们有两种方法:

Simple Instance:此实例仅在当前 widget 安装之前可用。就像任何其他变量一样,它受限于 widget 的生命周期。

HomeController controller = HomeController();

Persistent Instance:这个实例是使用 Get.put() 创建的,一旦创建就可以在任何地方访问。您可以拥有许多不同页面的控制器,并在创建后在任何地方访问它们。我们使用 Get.find< T >() 访问控制器。

HomeController controller = Get.put(HomeController());

GetxController 还为我们提供了我们可以实现的应用程序生命周期方法,以便我们可以独立于 UI 部分处理和启动对象:

import 'package:get/get.dart';

class HomeController extends GetxController {
  late int count ;

  increment() {
    count++;
    update();
  }

  @override
  void onInit() {
    // Init variables and subscribe to streams here
    count = 0;
    super.onInit();
  }

  @override
  void onReady() {
    // Called 1 Frame after onInit
    super.onReady();
  }

  @override
  void onClose() {
    // Dispose Controllers or remove listeners to streams here
    super.onClose();
  }
}

image.png

依赖管理

当我们说依赖管理时,我们将控制器称为依赖。使用 GetX,我们可以使我们的依赖关系持久化。这里的持久意味着不受 widget 生命周期的限制。如果我们希望我们的控制器可以在整个应用程序中访问,或者在两个或多个屏幕上访问和共享同一个控制器,使用 GetX 很容易实现。

现在我们将讨论创建持久依赖的方法:

  1. Get.put & Get.putAsync

这两个函数基本相同,只是 putAsync 提供了一个异步函数来保存实例。这些函数立即将实例初始化为 Singleton 实例到内存中并返回对象。一旦我们使用 put 或 putAsync,GetX 就会在内部调用 Get.find() 并返回对象。两者都有一个负责提供实例的位置参数。

S put<S>(
  S dependency, {
  String? tag, 
  bool permanent = false,
  S Function()? builder,
})

Future<S> putAsync<S>(
  Future<S> Function() builder, {
  String? tag, 
  bool permanent = false,
})

这些函数,还提供了两个命名参数 tag 和 permanent 。假设我们要保存同一类类型的多个实例,在这种情况下,我们使用 tag 参数区分实例。

如果删除使用 Get.put 保存依赖项的路由,则依赖项将被释放。但是,如果希望依赖项在应用程序的生命周期内持续存在,我们将 permanent 参数设置为 true:

HomeController controller = Get.put(HomeController(), permanent: true);

现在,这个特定实例将在应用程序的整个生命周期中可用。 putAsync 的语法也类似:

HomeController controller = Get.putAsync(
      () async {
    // await any task here
    return HomeController();
  },
  permanent: true,
);
  1. get.create

它创建依赖项,类似于 Get.put 但这里的永久参数默认为 true。这里要注意的重要一点是,每次使用 Get.find() 时,它都会运行 builder 方法并每次都返回一个新实例。它不遵循单例模式。此外,它不会像 put 方法那样立即创建实例并返回对象。稍后我们可以使用 Get.find() 获取控制器的新实例。

void create<S>(
S Function() builder, {
String? tag,
    bool permanent = true,
})

你可能不像 put() 那样频繁地使用这个函数,因为每次我们使用 Get.find() 时它都会创建一个新实例。虽然在大多数情况下,我们想要一个跨多个屏幕的实例。

  1. 获取.lazyPut

当我们想要延迟注入依赖项时,我们会使用它。仅当我们使用 Get.find() 搜索依赖项时,才会创建依赖项但将其加载到内存中。

Get.lazyPut(() => HomeController(), fenix: true);

与 Get.put 中的 permanent 参数一样,fenix 属性确保依赖项可以在应用程序的生命周期内存在。

依赖注入的另一种方法是使用 GetX 中的 Binding 类。

在 Flutter 中使用 GetX 进行状态管理

GetX 不像其他状态管理技术那样使用 Streams 或 Value Notifier。这些问题是我们需要手动订阅和取消订阅。 GetX 在内部使用 GetValue 和 GetStream,它们在使用更少资源的同时保持性能。 GetX 有两种状态管理器:

  • 使用 GetBuilder 的简单方法
  • 使用 GetX 和 Obx 的反应式方式

探索 GetBuilder

GetBuilder 是一个 widget ,用于使用 GetxController 类实例。每当在控制器中调用 update() 时,GetBuilder 都会重新构建。 GetBuilder 是对 update() 函数的补充。 GetBuilder 是一个功能强大的 widget ,它提供了有状态 widget 的 init state、dispose 和其他属性。因此,我们在使用 GetX 时甚至不需要使用 Stateful Widget。

GetBuilder<HomeController>(
  init: HomeController(),
  initState: (controller) {},
  dispose: (controller) {},
  builder: (controller) {},
),

如果在 init 参数中提及依赖项,当移动到另一个页面时,它会自动处理掉。如果已经在路由中注入了依赖项,则 init 参数不是必需的。

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

class HomePage extends StatelessWidget {
  Get.put(HomeController());

  @override
  Widget build(context) {
    return Scaffold(
      body: GetBuilder<HomeController>(
        builder: (HomeController controller) {

          // Automatically gets the controller
          return Container();
        },
      ),
    );
  }
}

GetBuilder 还支持 tag 参数,您可以使用该参数指定要使用的依赖项。

GetBuilder<HomeController>(
  tag: "tagForController",
  builder: (HomeController controller){
    return Container();
  },
),

如果不想在控制器类中定义生命周期方法,可以在 GetBuilder 中将它们定义为参数:

GetBuilder<HomeController>(
  initState: (controller) => controller.getData(),
  dispose: (controller) => controller.closeStreams(),
  builder: (controller) => Container()),
),

稍后我们将通过一个完整的示例看到如何使用 GetBuilder 的示例。现在,让我们看看如何使用 GetX 在 Flutter 中创建反应变量。

声明反应变量

反应变量是一种能够在其值发生变化时自动通知侦听器的变量。我们有 3 种方法可以在 GetX 中将变量声明为反应性变量:

使用 Rx{Type}

final counter = RxInt(0);
final message = RxString("BrewYourTech");
final list = RxList<int>([]);

使用 Rx< Type >

final counter = Rx<Int>(0);
final message = Rx<String>("BrewYourTech");
final list = Rx<List<int>>([]);

// Can be used on custom classes too
final auth = Rx<Auth>();

使用 .obx(最喜欢)

final counter = 0.obs;
final message = "BrewYourTech".obs;
final list = [].obs;

// Custom Class
final auth = Auth().obs;

探索 GetX 和 Obx

当想要更新 UI 时,GetBuilder 会得到更新,但是,只要值发生变化,GetX 和 Obx 就会得到更新。当我们使用 Getx 和 Obx 时,我们说我们正在使用响应式方法。这意味着我们正在使用一个数据流来更改值并更新所有侦听器。我们不需要在这里使用 update() 手动更新通知器。

GetX

GetBuilder 快速高效,但其行为更像一个 ChangeNotifier,因为它仅在手动通知时才会收到通知。 GetX widget 用于实现反应性,它像数据流一样接收事件。

GetX 仍然比任何其他反应式状态管理器更经济,但它比 GetBuilder 消耗更多的 RAM。

GetX 语法与 GetBuilder 非常相似,只是在绝对反应性方面有所不同。您可以假设它像带有一些语法糖和优化的 StreamBuilder:

class HomeController extends GetxController {
  var counter = 0.obs;
  increase() => counter.value++;
}

// GetX

GetX<HomeController>(
  init: HomeController(),
  builder: (val) => Text(
  '${val.counter.value}',
  ),
),

在 GetX widget 中你也不需要提供 init 参数,你可以预先初始化依赖,它会自动使用 Get.find() 来获取依赖。 GetX 非常优化,并确保如果值相同,则跳过构建。

Obx

Obx 与 GetX 非常相似,但它只有一个参数。它只有一个位置参数方法,该方法返回要构建的 widget 。每次响应变量更改时,Obx 构建器都会重新构建。

Obx(() => Widget);

Obx 不需要任何类型,例如 GetBuilder 或 GetX。因此我们可以在 Obx widget 中使用多个控制器。它也不提供任何生命周期方法,例如 GetBuilder 或 GetX widget 。

Obx(() => Text(controller1.id.value + controller2.name.value);

它比 GetX 更经济,但输给了 GetBuilder,这是意料之中的,因为它是反应式的,而且 GetBuilder 具有现有的最简单的方法。

现在我们已经讨论了 GetX 中的所有状态管理技术,让我们看看计数器应用程序在所有状态管理技术中的实现是什么样的。

import 'package:flutter/material.dart';

class CounterApp extends StatefulWidget {
  const CounterApp({Key? key}) : super(key: key);

  @override
  State<CounterApp> createState() => _CounterAppState();
}

class _CounterAppState extends State<CounterApp> {
  int counter = 0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          counter++;
          setState(() {});
        },
        child: const Icon(Icons.add),
      ),
      body: Center(
        child: Text(
          counter.toString(),
        ),
      ),
    );
  }
}
import 'package:flutter/material.dart';
import 'package:get/get.dart';

class HomeController extends GetxController {
  var count = 0;
  increment() {
    count++;
    update();
  }
}

class HomePage extends StatelessWidget {
  const HomePage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return GetBuilder<HomeController>(
      builder: (controller) {
        return Scaffold(
          floatingActionButton: FloatingActionButton(
            onPressed: controller.increment,
            child: const Icon(Icons.add),
          ),
          body: Center(
            child: Text(
              controller.count.toString(),
            ),
          ),
        );
      },
    );
  }
}
import 'package:flutter/material.dart';
import 'package:get/get.dart';

class HomeController extends GetxController {
  var count = 0.obs;
  increment() => count.value++;
}

class HomePage extends StatelessWidget {
  const HomePage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    final controller = Get.put(HomeController());
    return Scaffold(
      floatingActionButton: FloatingActionButton(
        onPressed: controller.increment,
        child: const Icon(Icons.add),
      ),
      body: Center(
        child: Obx(()=> Text(controller.count.toString()),
        ),
      ),
    );
  }
}
import 'package:flutter/material.dart';
import 'package:get/get.dart';

class HomeController extends GetxController {
  var count = 0.obs;
  increment() => count.value++;
}

class HomePage extends StatelessWidget {
  const HomePage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    final controller = Get.put(HomeController());
    return Scaffold(
      floatingActionButton: FloatingActionButton(
        onPressed: controller.increment,
        child: const Icon(Icons.add),
      ),
      body: Center(
          child: GetX<HomeController>(
            builder: (_) => Text(controller.count.toString()),
          )),
    );
  }
}

GetX 提供了一些额外的小部件,但我们不会在这里讨论这些 widget 。要使用这些 widget ,我们必须了解绑定。

在 Flutter 中使用 GetX 进行路由管理

GetX 简化了 Flutter 中的路由,使用 GetX 您无需上下文即可路由到不同的页面、显示小吃条、显示对话框、显示底部表格等。要使用 GetX 路由功能,必须将您的 MaterialApp 替换为 GetMaterialApp :

GetMaterialApp(
  home: HomePage(),
)

导航功能

要导航到新屏幕:

Get.to(HomePage());
// OR
Get.toNamed("/HomePage");

关闭工作表、dialogs、snack bars 等。替换 Navigator.pop(context) :

Get.back();

要转到没有返回选项的下一个屏幕,替换 Navigator.pushReplacement():

Get.off(HomePage());
// OR
Get.offNamed("/HomePage");

转到下一个屏幕并清除路由堆栈,替换 Navigator.pushAndRemoveUntil() :

Get.offAll(HomePage());
// OR
Get.offAllNamed("/HomePage");

定义命名路由

我们可以使用 getPages 参数在 GetMaterialApp 中轻松定义命名路由,如下所示:

GetMaterialApp(
  getPages: [
      GetPage(name: "/", page: ()=> const SplashPage()),
      GetPage(name: "/HomePage", page: () => const HomePage()),
  ],
);

要处理未知路由情况,请执行以下操作:

GetMaterialApp(
  unknownRoute: GetPage(name: '/notfound', page: () => UnknownRoutePage()),
);

还可以在命名路由中传递参数,Get 接受所有数据类型,包括自定义类。只需这样做:

Get.toNamed("/HomePage", arguments: "Any Argument type");

要接收类或类控制器中的参数,只需:

var args = Get.arguments;

Snackbar、Dialog 和 Sheets

为了在 Flutter 中显示 Snackbar、Dialog 和 Sheets,我们需要上下文,有时还需要一个与 Scaffold 相连的 GlobalKey。然而,当使用 GetX 时,我们可以很容易地在没有任何上下文的情况下显示其中的任何一个。

让我们看看如何使用 GetX 显示 Snackbar:

final snackBar = SnackBar(
  content: Text('Content of Snackbar'),
  action: SnackBarAction(
      label: 'Ok',
      onPressed: (){}
  ),
);
// Find the right context or use a Global Key attached to Scaffold
Scaffold.of(context).showSnackBar(snackBar);
Get.snackBar("Title", "Content");

// This by default is not so appealing, So you may use

Get.showSnackBar();
// Or
Get.rawSnackBar();

现在让我们看看如何使用 GetX 在 Flutter 中显示 Dialog:

showDialog(
  context: context,
  builder: (_) => YourDialog,
);
Get.dialog(YourDialog);

让我们看看如何使用 GetX 在 Flutter 中显示Sheets:

showModalBottomSheet(
  context: context,
  builder: (context) => YourSheetWidget,
);
Get.bottomSheet(YourSheetWidget);