Flutter 系列之GetX的学习(1) --> 状态管理

1. 介绍

GetX 是 Flutter 的超轻量级强大解决方案。它快速、实用地结合了高性能状态管理、智能依赖注入和路由管理。

状态管理: GetX 的旗舰功能之一是其直观的状态管理功能。GetX 中的状态管理几乎不需要样板代码即可实现。

路线管理: GetX 提供了用于在 Flutter 应用程序内导航的 API。此 API 非常简单,所需代码较少。

依赖管理: GetX 提供了一种智能方法来管理 Flutter 应用程序中的依赖项,例如视图控制器。GetX 将从内存中删除任何当前未使用的控制器。这是您作为开发人员必须手动完成的任务,但 GetX 可以自动为您完成。

2. 展示使用 [状态管理]

下面我们将创建一个项目, 演示Getx的使用

创建项目+启动项目

shell 复制代码
flutter create project_name
cd /project_name
flutter run

如果使用vscode 可以直接在左边侧边栏进行调试启动, Android Studio 可以点击右上角的启动按钮 进行启动

2.1 基本页面搭建

创建一个目录

counter

--> state

counter_screen.dart

main.dart

dart 复制代码
import 'package:flutter/material.dart';
import 'package:getx_study/counter/counter_screen.dart';

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

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home:   CounterScreen(),
    );
  }
}

counter_screen.dart

里面编写页面结构,代码里面用到的widget,将不会讲解,本文主要针对Getx的使用讲解,不明白的组件可以去浏览器搜一下. 另外代码里面也有注解.

dart 复制代码
class CounterScreen extends StatelessWidget {
  const CounterScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Gex 学习'),
      ),
      body: Center(
        child: Container(
          width: MediaQuery.of(context).size.width, // 盒子的宽度 为设备的宽度
          height: MediaQuery.of(context).size.height, // 盒子的高度 为设备的高度
          decoration: BoxDecoration(color: Colors.grey.withOpacity(0.3), ), // 盒子的背景色
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            crossAxisAlignment: CrossAxisAlignment.center,
            children: [
              GestureDetector(onTap: () {}, child: _buildContainer()),
              GestureDetector(onTap: () {}, child: _buildContainer()),
              GestureDetector(onTap: () {}, child: _buildContainer()),
              GestureDetector(onTap: () {}, child: _buildContainer()),
            ],
          ),
        ),
      ),
    );
  }
}

接着完成了我们抽离出来的_buildContainer组件,该方法 返回一个Container组件

dart 复制代码
_buildContainer() {
  return Container(
    decoration: BoxDecoration(
        color: const Color.fromARGB(255, 243, 207, 219), 
        borderRadius: BorderRadius.circular(10.0), // 圆角
        boxShadow: [const BoxShadow(color: Colors.black12,offset: Offset(6.0, 2.0),blurRadius: 10.0,spreadRadius: 4.0),]), // 盒子阴影
    alignment: Alignment.center,
    width: 100.0,
    height: 80.0,
    margin: const EdgeInsets.all(10),
    child: const Text("点击++"),
  );
}

此时基本页面搭建好了

2.2 数据状态(state定义)

打开根目录下的pubspec.yaml文件,并填入以下内容

yaml 复制代码
dependencies:
  get: ^4.6.6

vscode 会自动执行pub get 命令 获取最新的依赖

counter_controller.dart 文件中写入

dart 复制代码
import 'package:get/get_state_manager/src/simple/get_controllers.dart';

class CounterController extends GetxController {
  int _x=0;
  int get x=>_x;

  void increment(){_x++}
}

GetxControllerGetX 的控制器,它帮助管理状态的更新。继承 GetxController 意味着你可以在这个类中使用 GetX 的各种功能,如依赖注入和状态管理。

在这个文件里面我们定义一个_私有变量 x , 并且提供了get方法来获取这个私有变量. 同时定义一个一个increment方法去增加私有变量_x的值.

  • 状态(State) :在 CounterController 中,状态由私有变量 _x 表示。
  • 读取状态 :通过 x 这个 getter 公开了 _x 的值,允许外部读取。
  • 修改状态 :通过 increment() 方法,状态可以被修改(即 _x 的值递增)。

2.3 依赖注入

下一步是进行依赖注入 。依赖注入(Dependency Injection,简称 DI)是 GetX 提供的一项功能,用于将控制器(如 CounterController)注入到视图层,使得它们可以在不同的地方方便地被获取和使用。

dart 复制代码
  Widget build(BuildContext context) {
    // 注入控制器
    CounterController controller = Get.put(CounterController());

在下面的交互里面,调用controller身上的属性/方法

dart 复制代码
GestureDetector(onTap: () {controller.increment();}, child: _buildContainer()),

控制输出

我们的控制器已经创建并且初始化完毕

shell 复制代码
[GETX] Instance "CounterController" has been created

[GETX] Instance "CounterController" has been initialized

下面我们接着在控制器CounterController 的方法increment中进行print 打印,查看x的值的变化

dart 复制代码
  void increment(){
    print("x的值为${_x}");
    _x++;
  }

我们可以看到_x的值开始发生了变化

2.4 视图更新

目前我们已经定义好我们的控制器, 并且在UI部分已经实例化控制器,能够调用控制器的方法, 那么下面我们将控制器里面的数据进行一个UI展示,并且希望数据更新的时候UI也能得到改变, 就和react里面使用setState,vue里面的ref一样. 下面就看看如何实现吧

2.4.1 GetBuilder

GetBuilder 是 GetX 框架中的一种用于手动控制状态更新的工具. 我们先看下如何使用吧

dart 复制代码
              GetBuilder<CounterController>(builder: (_) => Text(
                  controller.x.toString(),
                  style: const TextStyle(fontSize: 22, color: Colors.black),
                ),
              ),
              GestureDetector(onTap: () {controller.increment();},child: _buildContainer()),

但是此时我们发现点击之后并没有得到更新, 这是因为GetBuilder在状态改变时,你需要手动调用 controller.update(),这样才会触发 GetBuilder 重建包裹的 UI。

所以我们需要在我们的counter_controller.dart文件里面在increase方法的后面添加update() 方法

dart 复制代码
class CounterController extends GetxController {
  int _x = 0;
  int get x => _x;

  void increment() {
    print("x的值为${_x}");
    _x++;
    update();
  }
}

此时点击之后就ui也跟着发生变化.

总结一下:

  1. 使用 Get.put() 注入控制器
  • 你可以在需要使用 GetBuilder 的地方,通过 Get.put() 来注入控制器,确保在视图中可以访问它。
  1. 包裹需要更新的 UI
  • 将需要根据状态更新的部分 UI 包裹在 GetBuilder 中,指定控制器类型。
  1. 手动调用 controller.update() 更新 UI
  • 当状态改变时,你需要手动调用 controller.update(),这将触发 GetBuilder 重建包裹的 UI。

2.4.2 Obx

第一步 声明响应式变量

声明响应式变量(状态) , 即把一个变量变成是 "可观察的"。

1 - 第一种是使用 Rx{Type}

dart 复制代码
// 建议使用初始值,但不是强制性的
final name = RxString('');
final isLogged = RxBool(false);
final count = RxInt(0);
final balance = RxDouble(0.0);
final items = RxList<String>([]);
final myMap = RxMap<String, int>({});

2 - 第二种是使用 Rx ,规定泛型 Rx<Type>

dart 复制代码
final name = Rx<String>('');
final isLogged = Rx<Bool>(false);
final count = Rx<Int>(0);
final balance = Rx<Double>(0.0);
final number = Rx<Num>(0)
final items = Rx<List<String>>([]);
final myMap = Rx<Map<String, int>>({});

// 自定义类 - 可以是任何类
final user = Rx<User>();

3 - 第三种更实用、更简单、更可取的方法,只需添加 .obs 作为value的属性。

dart 复制代码
final name = ''.obs;
final isLogged = false.obs;
final count = 0.obs;
final balance = 0.0.obs;
final number = 0.obs;
final items = <String>[].obs;
final myMap = <String, int>{}.obs;

// 自定义类 - 可以是任何类
final user = User().obs;
第二步 在ui中使用响应式变量

Obx 概念 Obx 是 GetX 中用于实现响应式 UI 的小部件。它的主要作用是监听可观察变量(如 .obs 创建的变量)的变化,并在变化时自动更新其子树。

工作原理

  1. 可观察变量 :通过将变量定义为 .obs,GetX 会将其转化为可观察对象。例如,var count = 0.obs;
  2. 自动重建 :当可观察变量发生变化时,Obx 会自动重新构建其内部的 UI。这意味着你不需要手动调用 setState 来更新界面。
  3. 简化代码 :使用 Obx 可以减少样板代码,使得状态管理更加直观。

不过这里说一句,Get.find()方法, 之前我们都是通过注入依赖.但是如果我们每个文件需要使用都注册一遍的话, 可能会导致错误或不必要的性能开销。Get.put() 会每次创建一个新的实例,

dart 复制代码
 // 注入控制器
    CounterController controller = Get.put(CounterController());

所以我们使用Get.find() 方法,GetX 提供的依赖注入机制的一部分,允许你在需要的地方轻松访问已创建的控制器。

对于使用到响应式变量的widget,我们需要使用Obx 进行一个包裹, 这样才会在变量更新的时候,该widget -> rebuild.

3. controller的生命周期钩子

如果你想在控制器第一次被调用的那一刻启动一个方法,你不需要为此使用构造函数,使用像Get这样面向性能的包,这样做反而是糟糕的做法,

因为它偏离了控制器被创建或分配的逻辑(如果你创建了这个控制器的实例,构造函数会立即被调用,你会在控制器还没有被使用之前就填充了一个控制器,你在没有被使用的情况下就分配了内存,这绝对违背这个库的原则)。

onInit();和onClose();方法就是为此而创建的,它们会在Controller被创建,或者第一次使用时被调用,这取决于你是否使用Get.lazyPut。例如,如果你想调用你的API来填充数据,你可以忘掉老式的initState/dispose方法,只需在onInit中开始调用api,如果你需要执行任何命令,如关闭流,使用onClose()来实现。

4. 总结

  1. 只更新需要的小部件。

  2. 不使用changeNotifier,状态管理器使用较少的内存(接近0mb)。

  3. 忘掉StatefulWidget! 使用Get你永远不会需要它。对于其他的状态管理器,你可能需要使用StatefulWidget来获取你的Provider、BLoC、MobX控制器等的实例。但是你有没有停下来想一想,你的appBar,你的脚手架,以及你的类中的大部分widget都是无状态的?那么如果你只能保存有状态的Widget的状态,为什么要保存整个类的状态呢?Get也解决了这个问题。创建一个无状态类,让一切都成为无状态。如果你需要更新单个组件,就用GetBuilder把它包起来,它的状态就会被维护。

  4. 真正的解耦你的项目! 控制器一定不要在你的UI中,把你的TextEditController,或者你使用的任何控制器放在你的Controller类中。

  5. 你是否需要触发一个事件来更新一个widget,一旦它被渲染?GetBuilder有一个属性 "initState",就像StatefulWidget一样,你可以从你的控制器中调用事件,直接从控制器中调用,不需要再在你的initState中放置事件。

  6. 你是否需要触发一个动作,比如关闭流、定时器等?GetBuilder也有dispose属性,只要该widget被销毁,你就可以调用事件。

  7. 仅在必要时使用流。你可以在你的控制器里面正常使用你的StreamControllers,也可以正常使用StreamBuilder,但是请记住,一个流消耗合理的内存,响应式编程很美,但是你不应该滥用它。30个流同时打开会比changeNotifier更糟糕(而且changeNotifier非常糟糕)。

  8. 更新widgets而不需要为此花费ram。Get只存储GetBuilder的创建者ID,必要时更新该GetBuilder。get ID存储在内存中的消耗非常低,即使是成千上万的GetBuilders。当你创建一个新的GetBuilder时,你实际上是在共享拥有创建者ID的GetBuilder的状态。不会为每个GetBuilder创建一个新的状态,这为大型应用节省了大量的内存。基本上你的应用程序将是完全无状态的,而少数有状态的Widgets(在GetBuilder内)将有一个单一的状态,因此更新一个状态将更新所有的状态。状态只是一个。

  9. Get是全知全能的,在大多数情况下,它很清楚地知道从内存中取出一个控制器的时机,你不需要担心什么时候移除一个控制器,Get知道最佳的时机。

相关推荐
LinXunFeng9 小时前
Flutter - iOS编译加速
flutter·xcode·apple
pengyu11 小时前
系统化掌握Flutter开发之隐式动画(一):筑基之旅
android·flutter·dart
AntG14 小时前
flutter webview crash 问题
flutter
勤劳打代码1 天前
烽火连营——爆杀 Jank 闪烁卡顿
flutter·面试·性能优化
书弋江山2 天前
Flutter 调用原生IOS接口
flutter·ios·cocoa
怀君2 天前
Flutter——最详细原生交互(MethodChannel、EventChannel、BasicMessageChannel)使用教程
flutter·交互·flutter与原生交互
星海拾遗2 天前
debug_unpack_ios failed: Exception: Failed to codesign 解决方案(亲测有效)
flutter·ios
爱学习的大牛1232 天前
flutter环境最新踩坑
flutter·androidstdio
bst@微胖子2 天前
Flutter管理项目实战
android·flutter
B.-2 天前
Flutter 实现消息推送的方法
android·学习·flutter·macos·ios·cocoa