GetX:状态管理

什么是状态管理

什么是“状态”

在 Flutter 里,状态就是 UI 显示依赖的数据。比如:

  • 一个计数器的数字(count
  • 用户是否登录(isLogin
  • 输入框里的文字(text
  • 接口请求的结果(userProfile

一句话:状态就是 UI 背后的数据

什么是“状态管理”

状态管理包含两件事:

  1. 存储和修改状态(数据)
    • 比如 count = 0;
    • 点击按钮时 count++
  2. 通知 UI 重新渲染
    • 比如文本 "Count: $count" 需要更新
    • 状态变化后 UI 自动刷新

状态管理 = 修改数据 + 更新 UI

Flutter 为什么需要状态管理?

因为 Flutter 的 UI 是声明式的: UI = f(状态)

“声明式”是什么意思?

命令式 UI(比如 Android 原生、Java Swing)里,你要手动告诉界面怎么改:

textView.setText("Count: " + count);

就像你在下“命令”,一步步写清楚:怎么改文字,怎么改颜色。

声明式 UI(Flutter / React)里,你只“声明”UI长什么样,它会根据状态自动渲染:

Text("Count: $count")

你不用写“怎么更新”,只要告诉 Flutter:UI 的样子取决于状态

UI = f(状态) 是什么意思:

这是一个数学表达式的类比:

  • UI(用户界面)是一个函数的结果。
  • 状态是输入参数。
  • f(函数)就是你写的 build() 方法。

换句话说:UI = build(状态)。当状态改变时,Flutter 会重新执行 build(),生成新的 UI。

举个例子:

int count = 0;

@override
Widget build(BuildContext context) {
  return Text("Count: $count");
}
  • 状态count
  • f(状态)Text("Count: $count")
  • UI:界面上显示的数字

count = 5Text("Count: 5")

count = 10Text("Count: 10")


因此只要 count 改了,就得 setState(),Flutter 会重新执行 build,然后 UI 更新。

如果没有“状态管理”,你得到处写 setState,而且数据共享很麻烦。

状态管理的方式

  • 自带的 setState(最简单,局部)

  • InheritedWidget / Provider(跨组件共享)

  • Bloc / Redux(比较重量级)

  • GetX / Riverpod / MobX(轻量又好用)

GetX 的四种状态管理方式

GetX 包
 ├── Obx        (最轻量,只监听 Rx 变量)
 ├── GetX<T>    (监听整个控制器的 Rx 变量)
 ├── GetBuilder (非响应式,手动 update)
 └── ValueBuilder(局部状态,类似 StatefulWidget)
方式 响应方式 是否需要 .obs 是否依赖控制器 更新触发方式 适用场景
Obx 自动更新 ✅ 需要 ❌ 不一定 变量变化自动刷新 小范围UI,频繁更新
GetX 自动更新 ✅ 需要 ✅ 强依赖 控制器内变量变化 监听控制器多个变量
GetBuilder 手动更新 ❌ 不需要 ✅ 强依赖 controller.update() 大范围UI,性能优先
ValueBuilder 手动更新 ❌ 不需要 ❌ 不依赖 update(value) 局部小状态,替代StatefulWidget

Obx

使用

  1. 定义响应式变量:xxx.obs
  2. 使用Obx监听响应式变量
  3. 变量变化之后,会自动更新UI
Future<void> main() async {
  runApp(MyApp());
}

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

  @override
  Widget build(BuildContext context) {
    // GetMaterialApp: getx 里面的组件
    return GetMaterialApp(
      // 不显示 debug banner 这个图标
      debugShowCheckedModeBanner: false,
      
      home: StateObxView(),
    );
  }
}

class StateObxView extends StatelessWidget {
  StateObxView({super.key});
  // 1.定义一个响应式变量:使用 Obx() 来监听状态变化
  final count = 0.obs;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("Obx(...)")),
      body: Center(
        child: Column(
          children: [
            // 2.这个 Text 被 Obx 包裹,监听 count
            Obx(() => Text("count1 -> $count")),
            Obx(() => Text("count2 -> $count")),

            //
            Divider(),
            ElevatedButton(
              onPressed: () {
                // 3.点击按钮时,更新 count 的值,同时会自动更新 UI。
                // 不需要调用 setState()
                count.value++;
              },
              child: Text('add'),
            ),
          ],
        ),
      ),
    );
  }
}

响应式变量

什么是“响应式变量”?

响应式变量(Reactive Variable):就是 能自动通知监听它的 UI 刷新的变量

  • 普通变量:只会存数据,改了不会通知 UI。
  • 响应式变量:一旦数据变化,会“广播消息”,让相关 UI 自动更新。

final count = 0.obs;

  • 0 → 普通整数
  • .obs把普通变量包装成“响应式变量”
  • 等价于:RxInt count = RxInt(0);
  • count 不再是普通的 int,而是一个 RxInt(响应式 Int 类型)

普通类型变成Rx类型

Dart自带的

普通变量 响应式写法 内部类型
int count = 0; var count = 0.obs; RxInt
double price = 0.0; var price = 0.0.obs; RxDouble
String name = "Tom"; var name = "Tom".obs; RxString
bool isLogin = false; var isLogin = false.obs; RxBool
List<String> list = []; var list = <String>[].obs; RxList<String>
Set<int> nums = {}; var nums = <int>{}.obs; RxSet<int>
Map<String, int> map = {}; var map = <String, int>{}.obs; RxMap<String, int>

自定义对象

整个对象响应式

class User {
  String name;
  int age;
  User(this.name, this.age);
}

var user = User("Tom", 20).obs;

user.update((val) {
  val?.age++;
}); // 自动刷新 UI

字段响应式

class User {
  var name = "Tom".obs;
  var age = 20.obs;
}

final user = User();

user.name.value = "Jerry"; // 自动刷新 UI

特殊类型

Rx<T> → 通用写法,可以包装任何类型:

var myData = Rx<MyClass>(MyClass());

Rxn<T> → 可空响应式类型(防止空指针):

Rxn<String> city = Rxn<String>();
city.value = null; // 合法

Obx 包裹的作用

Obx(() => Text("count1 -> $count")),

  1. 监听响应式变量
    • Obx 会监听你在 ()=> 里面用到的所有 .obs 变量。
    • 一旦这些变量发生变化,Obx 会触发 builder,重新构建里面的 UI。
  2. 局部刷新 UI
    • 只会刷新 Obx 包裹的那一小块 UI,而不是整个页面。因此提高了性能。
  3. 自动更新
    • 你不用写 setState(),变量变化 UI 就会自动刷新。

GetX

使用

使用步骤

  1. 定义控制器:class CountController extends GetxController

    • 注意要继承GetxController
    • 定义响应式变量:final _count = 0.obs;
    • 定义操作状态的方法:add() => _count.value++;
  2. 在页面中使用 GetX Widget

    • 创建控制器实例: final controller = CountController();(这个是在手动创建)
    • 绑定实例:
      • 泛型 <CountController> 指定了类型: GetX<CountController>(
      • init 或依赖注入提供了具体实例:init: controller,
      • 创建回调函数:builder: (ctrl) {......}。每次响应式变量发生变化的时候,都会调用builder
  3. 修改状态触发 UI 更新:.obs 变量变化时,所有用到这个变量的 GetX Widget 都会重新 build。

    ElevatedButton(
      // 点击按钮 -> count++
      onPressed: () {
        ctrl.add();
      },
      child: Text('count1'),
    ),
    

代码


class CountController extends GetxController {
  // RxInt 类型,响应式变量
  final _count = 0.obs;
  set count(value) => _count.value = value;
  get count => _count.value;
  // 另一个响应式变量
  final _count2 = 0.obs;
  set count2(value) => _count2.value = value;
  get count2 => _count2.value;

  add() => _count.value++;
  add2() => _count2.value++;
}


class StateGetxView extends StatelessWidget {
  StateGetxView({super.key});
  // 实例化控制器
  // 在实际项目里,更推荐用 Get.put(CountController()) 来全局注入,这样可以跨页面共享。
  final controller = CountController();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("Getx")),
      body: Center(
        child: Column(
          children: [
            // class GetX<T extends DisposableInterface> extends StatefulWidget
            GetX<CountController>(
              // init: controller 表示绑定这个控制器实例。
              init: controller,
              // final void Function(GetXState<T> state)? initState,
              // initState: (_) {} 可以在 widget 初始化时做一些操作,这里没做事。
              initState: (_) {},
              // final GetXControllerBuilder<T> builder;
              // builder 返回一个 Text,显示 count。
              // 每次 count 变化时,都会执行 print("GetX - 1"),并重建 Text。
              builder: (ctrl) {
                print("GetX - 1");
                return Text('value 1 -> ${ctrl.count}');
              },
            ),
            GetX<CountController>(
              init: controller,
              initState: (_) {},
              builder: (ctrl) {
                print("GetX - 2");
                return Text('value 2 -> ${ctrl.count}');
              },
            ),
            Divider(),

            //
            GetX<CountController>(
              init: controller,
              initState: (_) {},
              builder: (ctrl) {
                print("GetX - 3");
                return Column(
                  children: [
                    Text('value 3 -> ${ctrl.count}'),
                    ElevatedButton(
                      // 点击按钮 -> count++
                      onPressed: () {
                        ctrl.add();
                      },
                      child: Text('count1'),
                    ),
                  ],
                );
              },
            ),
            Divider(),

            // count2
            GetX<CountController>(
              init: controller,
              initState: (_) {},
              builder: (ctrl) {
                print("GetX - 4");
                return Text('value 4 -> ${ctrl.count2}');
              },
            ),
            Divider(),

            // 按钮
            ElevatedButton(
              onPressed: () {
                controller.add();
              },
              child: Text('count1'),
            ),

            ElevatedButton(
              onPressed: () {
                controller.add2();
              },
              child: Text('count2'),
            ),
          ],
        ),
      ),
    );
  }
}

执行流程图

flowchart TD
    A["用户点击按钮"] -->|"调用 add() 或 add2()"| B["CountController 方法执行"]

    B -->|"修改 _count 或 _count2"| C["RxInt 变量发生变化"]

    C --> D["GetX<CountController> 监听到变化"]

    D -->|"触发 builder 回调"| E["重新构建对应 Widget"]

    E --> F["UI 更新显示最新的 count / count2 值"]

GetX Widget 如何监听状态

GetX<CountController>(
  builder: (ctrl) {
    return Text('value -> ${ctrl.count}');
  },
)

GetX 内部会 订阅 ctrl.count 的变化

_count.value 改变时,它会触发通知,让所有订阅了它的 builder 被重新执行(重新 build)。

控制器里的 Rx 变量 (.obs)
         │
         │ 变化时发出通知
         ▼
     GetX Widget
         │
         │ 收到通知
         ▼
       builder 被执行
         │
         ▼
       UI 重建

注意:只有 .obs 类型的变量会触发通知。普通变量不会自动刷新 UI。

所以 GetxController 并不是自己去 “监控变量”,而是 依赖 Rx 对象的监听机制 来通知 GetX 重建 UI。

GetxController 的作用

存放状态

页面或者模块的状态(数据)都可以放在控制器里。

可以结合响应式变量 .obs,实现 自动更新 UI

注意:GetxController 并不是自己去 “监控变量”,而是 依赖 Rx 对象的监听机制 来通知 GetX 重建 UI。

class CountController extends GetxController {
  var count = 0.obs;  // 响应式状态
  void increment() => count++;
}

提供操作方法

控制器不仅存状态,还可以写方法来修改状态,封装业务逻辑。

add() => _count.value++;
add2() => _count2.value++;

生命周期管理

GetxController 提供了一些生命周期钩子:

方法 作用
onInit() 控制器创建后执行一次,通常做初始化或 API 请求
onReady() 控制器完全初始化并绑定 UI 后执行
onClose() 控制器销毁前执行,用来释放资源,如取消订阅、关闭 Stream
class CountController extends GetxController {
  @override
  void onInit() {
    super.onInit();
    print("Controller 初始化");
  }

  @override
  void onClose() {
    print("Controller 销毁");
    super.onClose();
  }
}

支持依赖注入

可以通过 Get.put()Get.lazyPut() 注入控制器。

页面或其他控制器可以通过 Get.find<T>() 获取实例。

Get.put(CountController());        // 注入
final ctrl = Get.find<CountController>();  // 获取

如何绑定控制器Controller

手动创建了控制器,并通过 init 绑定:

ctrl 就是我们手动创建的 controller

不依赖全局注入,独立使用。


优点

  1. 控制器只在这个页面有效,不占用全局资源。
  2. 页面销毁时,控制器也会自动销毁(默认行为,可通过 fenix: true 保持)。

缺点

  • 其他页面无法直接获取这个控制器。
  • 页面多次进入时,如果不加 Get.isRegistered 检查,可能会重复创建实例。
class CountController extends GetxController {
  var count = 0.obs;
  void increment() => count++;
}

class MyPage extends StatelessWidget {
  
  @override
  Widget build(BuildContext context) {
    final controller = CountController(); // 手动创建控制器(也会重复创建实例,每次执行build都会)
    return Scaffold(
      body: Center(
        child: GetX<CountController>(
          init: controller,            // 指定使用这个实例
          builder: (ctrl) {
            return Column(
              children: [
                Text('count: ${ctrl.count}'),
                ElevatedButton(
                  onPressed: () => ctrl.increment(),
                  child: Text('增加'),
                ),
              ],
            );
          },
        ),
      ),
    );
  }
}

依赖 GetX 的依赖注入(不使用init

  • 通过 Get.put(CountController()) 全局注入控制器。
  • GetX 内部会自动使用 Get.find<CountController>() 找到全局实例。
    • 我们在创建GetX的时候已经指明了泛型:child: GetX<CountController>(
  • ctrl 就是全局注入的控制器。

优点

  1. 控制器是全局单例,整个应用都能共享。
  2. 生命周期从应用启动开始,到应用结束才销毁。

缺点

  • 如果只是某个页面需要,不需要全局一直存在。
class CountController extends GetxController {
  var count = 0.obs;
  void increment() => count++;
}

void main() {
  Get.put(CountController()); // 全局注入
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(home: MyPage());
  }
}

class MyPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: GetX<CountController>(
          builder: (ctrl) {
            return Column(
              children: [
                Text('count: ${ctrl.count}'),
                ElevatedButton(
                  onPressed: () => ctrl.increment(),
                  child: Text('增加'),
                ),
              ],
            );
          },
        ),
      ),
    );
  }
}

重复创建实例

问题引入

class StateGetxView extends StatelessWidget {
  StateGetxView({super.key});
  final controller = CountController();// 手动创建实例(会重复创建)
  // final controller2 = Get.put(CountController());// 使用依赖创建(会重复创建)

这个字段是在 每次页面实例化的时候执行一次

如果你多次进入这个页面(比如 Navigator.push 多次),每次都会执行 CountController(),也就是 会创建多个控制器实例

对应的 GetX<CountController>(init: controller, ...) 每次都会绑定这个新实例。

如何避免重复创建

用依赖注入和检查是否已注册

final controller = Get.isRegistered<CountController>()
    ? Get.find<CountController>()
    : Get.put(CountController());
  • 如果控制器已经被注册过,就复用已有实例。
  • 如果没有,就创建并注册一个新的实例。

全局注入一次(写在 main()

void main() {
  Get.put(CountController()); // 全局单例
  runApp(MyApp());
}

页面每次进入都会用同一个控制器,不会重复创建。

GetBuilder

使用步骤

  1. 使用的步骤和GetX类似
  2. 定义控制器:class CountController extends GetxController
  3. 在页面中使用 GetX Widget:GetBuilder<CountController>(
  4. 修改状态:会改变count的值,但是不会里面更新UI
  5. 执行controller.update才会更新状态
class CountController extends GetxController {
  // RxInt 类型,响应式变量
  final _count = 0.obs;
  set count(value) => _count.value = value;
  get count => _count.value;
  // 另一个响应式变量
  final _count2 = 0.obs;
  set count2(value) => _count2.value = value;
  get count2 => _count2.value;

  add() => _count.value++;
  add2() => _count2.value++;
}


class StateGetBuilderView extends StatelessWidget {
  StateGetBuilderView({super.key});
  // 这个早期版本可以这么写。但是现在这么写UI是不会跟新的
  // final controller = CountController();
  final CountController controller = Get.put(CountController());

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("GetBuilder")),
      body: Center(
        child: Column(
          children: [
            GetBuilder<CountController>(
              init: controller,
              initState: (_) {},
              builder: (ctrl) {
                print("GetBuilder - 1");
                return Text('value -> ${ctrl.count}');
              },
            ),
            GetBuilder<CountController>(
              init: controller,
              initState: (_) {},
              builder: (ctrl) {
                print("GetBuilder - 2");
                return Text('value -> ${ctrl.count}');
              },
            ),
            Divider(),

            //
            GetBuilder<CountController>(
              init: controller,
              initState: (_) {},
              builder: (ctrl) {
                print("GetBuilder - 3");
                return Column(
                  children: [
                    Text('value -> ${ctrl.count}'),
                    ElevatedButton(
                      onPressed: () {
                        ctrl.add();
                      },
                      child: Text('GetBuilder -> add'),
                    ),
                  ],
                );
              },
            ),
            Divider(),

            // count2
            GetBuilder<CountController>(
              init: controller,
              initState: (_) {},
              builder: (ctrl) {
                print("GetBuilder - 4");
                return Text('value count2 -> ${ctrl.count2}');
              },
            ),
            Divider(),

            // id2
            GetBuilder<CountController>(
              id: "id2",
              init: controller,
              initState: (_) {},
              builder: (ctrl) {
                print("GetBuilder - 4");
                return Text('id2 -> value count2 -> ${ctrl.count2}');
              },
            ),
            Divider(),

            // 按钮
            ElevatedButton(
              onPressed: () {
                controller.add();
              },
              child: Text('add'),
            ),

            ElevatedButton(
              onPressed: () {
                controller.add2();
              },
              child: Text('add2'),
            ),

            ElevatedButton(
              onPressed: () {
                controller.update();
              },
              child: Text('controller.update()'),
            ),

            ElevatedButton(
              onPressed: () {
                controller.update(["id2"]);
              },
              child: Text('controller.update(id2)'),
            ),
          ],
        ),
      ),
    );
  }
}

使用要点

状态(变量)变化后不好自动更新UI

GetBuilder 是一个非响应式状态管理小部件,不自动监听 Rx 变量(如 _count _count2)的变化。

它依赖于手动调用 controller.update()(或 update([id]))来触发 builder 重建。

id 问题

GetBuilder<CountController>(
  id: "id2",
  init: controller,
  initState: (_) {},
  builder: (ctrl) {
    print("GetBuilder - 4");
    return Text('id2 -> value count2 -> ${ctrl.count2}');
  },
),

调用 controller.update() 会确通知所有 GetBuilder(无 id)重建。

调用 controller.update(["id2"]) 只通知指定 id: "id2"GetBuilder 重建。

要使用Get.put(CountController())

原来我是在代码中直接创建:

final controller = CountController(); // 直接实例化

直接创建 CountController 实例没有将其注册到 GetX 的依赖管理系统。虽然 GetBuilderinit: controller 使用了同一个 controller 实例,但 GetX 内部无法正确关联这个实例与 GetBuilder 的状态,导致 controller.update() 无法触发 UI 重建。

通过 final controller = Get.put(CountController());Get.put()CountController 实例注入到 GetX 的全局依赖管理中,GetX会跟踪这个实例。

ValueBuilder

ValueBuilder 是什么

ValueBuilder<T>GetX 提供的一种轻量级状态管理组件,用于管理 单个局部状态

它的特点:

  1. 内部自带状态,不需要额外控制器。
  2. 提供 builder 回调来渲染 UI。
  3. 提供 updateFn 来修改状态,修改后自动刷新 UI。
  4. 可选 onUpdateonDispose 钩子,方便调试和释放资源。

案例

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("ValueBuilder")),
      body: Column(
        children: [
          Center(
            child: ValueBuilder<int?>(
              initialValue: 10,
              builder: (value, updateFn) {
                return Column(
                  children: [
                    Text("count -> $value"),
                    ElevatedButton(
                      onPressed: () {
                        updateFn(value! + 1);
                      },
                      child: Text('ValueBuilder -> add'),
                    ),
                  ],
                );
              },
              onUpdate: (value) => print("Value updated: $value"),
              onDispose: () => print("Widget unmounted"),
            ),
          ),
        ],
      ),
    );
  }
}

代码讲解

ValueBuilder<int?>(
  initialValue: 10,
  builder: (value, updateFn) { ... },
)

ValueBuilder<int?>泛型的作用:

  • 这里的 <int?> 表示:

    1. 状态类型是 int
    2. 可以为空? 表示 nullable)
  • 泛型的作用:

    • builder 回调里的 value 就是 T 类型 (int?)
    • updateFn 的参数也必须是 T 类型 (int?)

initialValue: 10:给builder的value一个初始值

builder 会在 状态变化时自动调用

value → 当前状态值

updateFn → 用于修改状态的函数


ElevatedButton(
  onPressed: () {
    updateFn(value! + 1);
  },
)

调用 updateFn(newValue)

  1. _value 更新为 newValue
  2. 自动触发 重新 build builder
  3. UI 显示最新状态

onUpdate: (value) => print("Value updated: $value")

onUpdate:状态更新时触发


onDispose: () => print("Widget unmounted")

onDispose:ValueBuilder 销毁时触发(页面退出、Widget 被移除)

流程图

[初始化]
   │ initialValue = 10
   ▼
[第一次 builder 执行]
   │ value = 10
   ▼
[UI 渲染] -> Text("count -> 10")
   │
   └─用户点击按钮调用 updateFn(value+1)
        │
        ▼
   [_value 更新]
        │
        ▼
   [触发 builder 再次执行]
        │ value = 11
        ▼
   [UI 重建] -> Text("count -> 11")
        │
        ▼
   [可选触发 onUpdate]

×

喜欢就点赞,疼爱就打赏