当前位置:   article > 正文

Flutter状态管理终极方案GetX第二篇——状态管理_getx 全局状态管理

getx 全局状态管理

GetX状态管理

说状态管理到底在说些什么

一个应用的状态就是当这个应用运行时存在于内存中的所有内容。当然许多状态,例如纹理、动画状态等,框架本身会替开发者管理,所以对于状态更合适的定义是**“当你需要重建用户界面时所需要的数据”**,我们需要自己管理的状态可以分为两种概念类型:短时 (ephemeral) 状态和应用 (app) 状态。

短时状态

短时状态是可以完全包含在一个独立 widget 中的状态,也成为局部状态。

  • 一个 PageView 组件中的当前页面
  • 一个复杂动画中当前进度
  • 一个 BottomNavigationBar 中当前被选中的 tab
  • 一个文本框显示的内容

应用状态

如果在应用中的多个部分之间共享一个非短时的状态,并且在用户会话期间保留这个状态,我们称之为应用状态(有时也称共享状态)。

  • 用户选项
  • 登录信息
  • 一个社交应用中的通知
  • 一个电商应用中的购物车
  • 一个新闻应用中的文章已读/未读状态

为什么选择 GetX 做状态管理?

开发者一直致力于业务逻辑分离的概念,Flutter 也有利用 BLoc 、Provider 衍生的 MVC、MVVM 等架构模式,但是这几种方案的状态管理均使用了上下文(context),需要上下文来寻找InheritedWidget,这种解决方案限制了状态管理必须在父子代的 widget 树中,业务逻辑也会对 View 产生较强依赖。

而 GetX 因为不需要上下文,突破了InheritedWidget的限制,我们可以在全局和模块间共享状态,这正是 BLoc 、Provider 等框架的短板。

另外 GetX 控制器也是有生命周期的,例如当我们需要业务层进行 APIREST 时,我们可以不依赖于界面中的任何东西。可以使用onInit来启动http调用,当数据到达赋值给变量后,利用 GetX 响应式的特性,使用该变量的 Widgets 将在界面中自动更新。这样在 UI层只需要写界面,除了用户事件(比如点击按钮)之外,不需要向业务逻辑层发送任何东西。


简单使用

对于以前使用过 ChangeNotifier 的同学来说,可以把GetxController当做ChangeNotifier,我们使用计数器示例来演示一下基本使用:

  1. class SimpleController extends GetxController {
  2. int _counter = 0;
  3. int get counter => _counter;
  4. void increment() {
  5. _counter++;
  6. update();
  7. }
  8. }

这是一个控制器,有 UI 需要的数据counter和用户点击一次加1的方法。

在 UI 层一个展示的文本和一个按钮:

  1. class SimplePage extends StatelessWidget {
  2. @override
  3. Widget build(BuildContext context) {
  4. print('SimplePage--build');
  5. return GetBuilder<SimpleController>(
  6. init: SimpleController(),
  7. builder: (controller) {
  8. return Scaffold(
  9. appBar: AppBar(title: Text('Simple')),
  10. body: Center(
  11. child: Text(controller.counter.toString()),
  12. ),
  13. floatingActionButton: FloatingActionButton(
  14. onPressed: () {
  15. controller.increment();
  16. },
  17. child: Icon(Icons.add),
  18. ),
  19. );
  20. });
  21. }
  22. }

使用了GetBuilder这个 Widget 包裹了页面,在 init初始化SimpleController,然后每次点击,都会更新builder对应的 Widget ,GetxController通过update()更新GetBuilder

这看起来和别状态管理框架并无不同,有时我们只想重新 build 需要变化的部分,遵循最小原则,那么我们改下GetBuilder的位置,只包裹 Text:

  1. class SimplePage extends StatelessWidget {
  2. @override
  3. Widget build(BuildContext context) {
  4. print('SimplePage--build');
  5. return Scaffold(
  6. appBar: AppBar(title: Text('Simple')),
  7. body: Center(
  8. child: GetBuilder<SimpleController>(
  9. init: SimpleController(),
  10. builder: (controller) {
  11. return Text(controller.counter.toString());
  12. }),
  13. ),
  14. floatingActionButton: FloatingActionButton(
  15. onPressed: () {
  16. controller.increment();
  17. },
  18. child: Icon(Icons.add),
  19. ),
  20. );
  21. }
  22. }

因为 controlle作用域问题,此时按钮里面的 controller会找不到,GetX强大的一点的就表现出来了,按钮和文本并不在父子组件,并且和GetBuilder不在一个作用域,但是我们依然能正确得到:

  1. onPressed: () {
  2. Get.find<SimpleController>().increment();
  3. // controller..increment();
  4. },

GetxController也有生命周期的:

  1. class SimpleController extends GetxController {
  2. int _counter = 0;
  3. int get counter => _counter;
  4. void increment() {
  5. _counter++;
  6. update();
  7. }
  8. @override
  9. void onInit() {
  10. super.onInit();
  11. print('SimpleController--onInit');
  12. }
  13. @override
  14. void onReady() {
  15. super.onReady();
  16. print('SimpleController--onReady');
  17. }
  18. @override
  19. void onClose() {
  20. super.onClose();
  21. print('SimpleController--onClose');
  22. }
  23. }

之前在这里打印了一句:

  1. class SimplePage extends StatelessWidget {
  2. @override
  3. Widget build(BuildContext context) {
  4. print('SimplePage--build');
  5. return Scaffold(
  6. 。。。

再次打开这个页面,控制台输出:

  1. flutter: SimplePage--build
  2. flutter: SimpleController--onInit
  3. [GETX] "SimpleController" has been initialized
  4. flutter: SimpleController--onReady
SimplePage-build->SimpleController-onInit->SimpleController-onReady

推出当前页面返回:

  1. [GETX] CLOSE TO ROUTE /SimplePage
  2. flutter: SimpleController--onClose
  3. [GETX] "SimpleController" onClose() called
  4. [GETX] "SimpleController" deleted from memory
  5. [GETX] Instance "SimpleController" already removed.

可以看到SimpleController已经被删除。

局部更新

多种状态可以分别更新,不需要为每个状态创建一个类。

再添加一个变量:

  1. int _counter = 0;
  2. int get counter => _counter;
  3. String _name = "Lili";
  4. String get firstName => _name;
  5. void increment() {
  6. _counter++;
  7. _name = WordPair.random().asPascalCase;
  8. update(['counter']);
  9. }
  10. void changeName() {
  11. _counter++;
  12. _name = WordPair.random().asPascalCase;
  13. update(['name']);
  14. }

两个方法分别改变两个变量,但是注意update(['counter']里添加了 id 数组,这样就只更新这个 id 对应的GetBuilder:

  1. GetBuilder<SimpleAdvancedController>(
  2. id: 'counter',
  3. builder: (ctl) => Text(ctl.counter.toString()),
  4. ),
  5. SizedBox(
  6. height: 50,
  7. ),
  8. GetBuilder<SimpleAdvancedController>(
  9. id: 'name',
  10. builder: (ctl) => Text(ctl.firstName),
  11. ),

响应式刷新

我们都用过 StreamControllers ,然后以流的方式发送数据。在 GetX 可以实现同样的功能,并且实现起来只有几个单词,不需要为每个观察的对象创建一个 StreamController ,也不需要创建 StreamBuilder。

var name = '新垣结衣';

下面简单的一个后缀就可以把一个变量变得可观察,变量每次改变的时候,使用它的小部件就会被更新:

var name = '新垣结衣'.obs;

就这么简单,这个变量已经是响应式的了。然后通过 Obx 或者 GetX 包裹并使用响应式变量的控件,在变量改变的时候就会被更新:

Obx (() => Text (controller.name));

下面写个计算器的例子:

  1. final count1 = 0.obs;
  2. final count2 = 0.obs;

.obs就实现了一个被观察者,他们不再是 int 类型,而是 RxInt 类型。对应的小部件也不再是GetBuilder了,而是下面两种:

  1. GetX<SumController>(
  2. builder: (_) {
  3. print("count1 rebuild");
  4. return Text(
  5. '${_.count1}',
  6. style: TextStyle(fontWeight: FontWeight.bold),
  7. );
  8. },
  9. ),
  10. Obx(() => Text(
  11. '${Get.find<SumController>().count2}',
  12. style: TextStyle(fontWeight: FontWeight.bold),
  13. )),

因为是响应式,不再需要update,每次更改值,都自动刷新。但是更神奇的是,他们的运算和也是响应式的:

  int get sum => count1.value + count2.value;

只要更新count1或者count2使用sum的小部件也会更改:

  1. Obx(() => Text(
  2. '${Get.find<SumController>().sum}',
  3. style: TextStyle(fontWeight: FontWeight.bold),
  4. )),

非常简单的使用方式,不是吗?除了使用.obs还有2种方法把变量变成可观察的:

  1. 第一种是使用 Rx{Type}。
  1. // 建议使用初始值,但不是强制性的
  2. final name = RxString('');
  3. final isLogged = RxBool(false);
  4. final count = RxInt(0);
  5. final balance = RxDouble(0.0);
  6. final items = RxList<String>([]);
  7. final myMap = RxMap<String, int>({});
  1. 第二种是使用 Rx,规定泛型 Rx。
  1. final name = Rx<String>('');
  2. final isLogged = Rx<Bool>(false);
  3. final count = Rx<Int>(0);
  4. final balance = Rx<Double>(0.0);
  5. final number = Rx<Num>(0)
  6. final items = Rx<List<String>>([]);
  7. final myMap = Rx<Map<String, int>>({});
  8. // 自定义类 - 可以是任何类
  9. final user = Rx<User>();

将一个对象转变成可观察的,也有2种方法:

  1. 可以将我们的类值转换为 obs
  1. class RxUser {
  2. final name = "Camila".obs;
  3. final age = 18.obs;
  4. }
  1. 或者可以将整个类转换为一个可观察的类。
  1. class User {
  2. User({String name, int age});
  3. var name;
  4. var age;
  5. }
  6. //实例化时。
  7. final user = User(name: "Camila", age: 18).obs;

注意,转化为可观察的变量后,它的类型不再是原生类型,所以取值不能用变量本身,而是.value

当然 GetX 也提供了 api 简化对 int、List 的操作。此外,Get还提供了精细的状态控制。我们可以根据特定的条件对一个事件进行条件控制(比如将一个对象添加到List中):

  1. // 第一个参数:条件,必须返回true或false。
  2. // 第二个参数:如果条件为真,则为新的值。
  3. list.addIf(item < limit, item);

响应式编程虽好,可不要贪杯。因为响应式对 RAM 的消耗比较大,因为他们的实现都是流,如果创建一个有80个对象的 List ,每个对象都有几个流,打开dart inspect,查看一个 StreamBuilder 的消耗量,我们就会明白这不是一个好的方法。而 GetBuilder 在 RAM 中是非常高效的,几乎没有比他更高效的方法。所以这些使用方式在使用过程中要斟酌。

Workers

响应式不只这些好处,还有一个 Workers ,将协助我们在事件发生时触发特定的回调,也就是 RxJava 的一些操作符;

  1. @override
  2. onInit() {
  3. super.onInit();
  4. /// 每次更改都会回调
  5. ever(count1, (_) => print("$_ has been changed"));
  6. /// 第一次更改回调
  7. once(count1, (_) => print("$_ was changed once"));
  8. /// 更改后3秒回调
  9. debounce(count1, (_) => print("debouce$_"), time: Duration(seconds: 3));
  10. ///3秒内更新回调一次
  11. interval(count1, (_) => print("interval $_"), time: Duration(seconds: 3));
  12. }

我们可以利用 Workers ,去实现写一堆对代码才能实现的功能。比如防抖函数,在搜索的时候使用,节流函数,在点击事件的时候使用。

跨路由

上面演示过在同一个页面兄弟组件跨组件使用,接下来实现下不同页面跨组件使用,首先在CrossOnePageput 一个 Controller:

  1. class CrossOnePage extends StatelessWidget {
  2. @override
  3. Widget build(BuildContext context) {
  4. CrossOneController controller = Get.put(CrossOneController());
  5. ...
  6. }}

然后在另一个页面CrossTwoPage,打印下上一个页面put的控制器:

  1. CheetahButton('打印CrossOneController的age', () {
  2. print(Get.find<CrossOneController>().age);
  3. }),

正常输出。

那么CrossOneController的生命周期多久呢?如果像第一个页面一样是在build里 put 的,那么当前页面退出就销毁了。如果是成员变量,那么当前页面的引用销毁才会销毁:

  1. class CrossTwoPage extends StatelessWidget {
  2. final CrossTwoSecondController controller = Get.put(CrossTwoSecondController());
  3. @override
  4. Widget build(BuildContext context) {
  5. Get.put(CrossTwoController());
  6. return Scaffold(
  7. appBar: AppBar(title: Text('CrossTwoPage')),
  8. body: Container(
  9. child: Column(
  10. children: [
  11. CheetahButton('打印CrossTwoController', () {
  12. print(Get.find<CrossTwoController>());
  13. }),
  14. CheetahButton('CrossTwoSecondController', () {
  15. print(Get.find<CrossTwoSecondController>());
  16. }),
  17. CheetahButton('打印CrossOneController的age', () {
  18. print(Get.find<CrossOneController>().age);
  19. }),
  20. ],
  21. )),
  22. );
  23. }
  24. }

CrossTwoSecondController是成员变量,CrossTwoController是在build的时候 put 进去的,现在打印2个控制器,都能打印出来:

  1. [GETX] "CrossTwoSecondController" has been initialized
  2. [GETX] GOING TO ROUTE /CrossTwoPage
  3. [GETX] "CrossTwoController" has been initialized
  4. I/flutter (16952): Instance of 'CrossTwoController'
  5. I/flutter (16952): Instance of 'CrossTwoSecondController'

现在返回第一个页面,GetX 已经给我们打印了:

  1. GETX] CLOSE TO ROUTE /CrossTwoPage
  2. [GETX] "CrossTwoController" onClose() called
  3. [GETX] "CrossTwoController" deleted from memory

然后我们在第一个页面点击按钮,分别打印页面CrossTwoPage的2个控制器:

  1. ════════ Exception caught by gesture ═══════════════════════════════════════════
  2. "CrossTwoController" not found. You need to call "Get.put(CrossTwoController())" or "Get.lazyPut(()=>CrossTwoController())"
  3. ════════════════════════════════════════════════════════════════════════════════
  4. I/flutter (16952): Instance of 'CrossTwoSecondController'

buildput 的控制器已经销毁为 null 了,另一个依然存在,那是不是这种不会销毁呢?因为第一个页面的路由依然持有第二个页面,第二个页面的实例还在内存中,所以控制器作为成员变量依然存在,退出第一个页面,自然就销毁了:

  1. [GETX] CLOSE TO ROUTE /CrossOnePage
  2. [GETX] "CrossOneController" onClose() called
  3. [GETX] "CrossOneController" deleted from memory
  4. [GETX] "CrossTwoSecondController" onClose() called
  5. [GETX] "CrossTwoSecondController" deleted from memory

不使用 GetX 路由的状态管理

GetX虽然各个功能均可单独引用使用,但是状态管理和路由是搭配的,如果没有使用 route_manager 组件,那么状态管理的生命周期就会失效。putController在不使用的时候不会再被删除,而变成了应用状态常驻内存里。

如果项目的路由暂时不能使用 GetX 替换,那么怎么使用状态管理呢,很简单,封装一个自动删除Controller的控件即可,因为习惯使用GetBinding,待可以替换为 GetX 路由的时候直接带上GetBinding,所以封装了一个GetBinding的控件和一个不使用GetBinding的控件:

  1. abstract class GetBindingView<T extends GetxController>
  2. extends StatefulWidget {
  3. final String? tag = null;
  4. T get controller => GetInstance().find<T>(tag: tag);
  5. @protected
  6. Widget build(BuildContext context);
  7. @protected
  8. Bindings? binding();
  9. @override
  10. _AutoDisposeState createState() => _AutoDisposeState<T>();
  11. }
  12. class _AutoDisposeState<S extends GetxController>
  13. extends State<GetBindingView> {
  14. _AutoDisposeState();
  15. @override
  16. Widget build(BuildContext context) {
  17. return widget.build(context);
  18. }
  19. @override
  20. void initState() {
  21. super.initState();
  22. widget.binding()?.dependencies();
  23. }
  24. @override
  25. void dispose() {
  26. Get.delete<S>();
  27. super.dispose();
  28. }
  29. }

使用很简单:

  • 创建对应的GetBindingGetxController和 Page ,
  • 对应的 Page 修改为继承 GetDisposeView
  • 实现binding()方法并返回第一步创建的GetBinding
  1. class BingPagePage extends GetBindingView<BingPageController> {
  2. @override
  3. Widget build(BuildContext context) {
  4. return Scaffold(
  5. appBar: AppBar(title: Text('BingPage Page')),
  6. body: Container(
  7. child: Obx(()=>Container(child: Text(controller.obj),)),
  8. ),
  9. );
  10. }
  11. @override
  12. Bindings? binding() =>BingPageBinding();
  13. }

接下来就可以像使用 GetView 一样使用了,如果以后替换了 GetX 路由,只需要把 GetDisposeView替换为GetView

下面是一个不使用GetBinding的控件,比上面的使用更简单,不需要创建GetBinding

  1. abstract class GetDisposeView<T extends GetxController> extends StatefulWidget {
  2. final String? tag = null;
  3. T get controller => GetInstance().find<T>(tag: tag);
  4. @protected
  5. Widget build(BuildContext context);
  6. @protected
  7. void setController();
  8. @override
  9. _AutoDisposeState createState() => _AutoDisposeState<T>();
  10. }
  11. class _AutoDisposeState<S extends GetxController>
  12. extends State<GetDisposeView> {
  13. _AutoDisposeState();
  14. @override
  15. Widget build(BuildContext context) {
  16. return widget.build(context);
  17. }
  18. @override
  19. void initState() {
  20. super.initState();
  21. widget.setController();
  22. }
  23. @override
  24. void dispose() {
  25. Get.delete<S>();
  26. super.dispose();
  27. }
  28. }

使用:

  • 创建对应的GetxController和 Page ,

  • 对应的 Page 修改为继承 GetDisposeView

  • 实现setController()方法并返回第一步创建的put第一步创建的GetxController对象。

    1. class AutoDisposePage extends GetDisposeView<BingPageController> {
    2. @override
    3. Widget build(BuildContext context) {
    4. return Scaffold(
    5. appBar: AppBar(title: Text('Auto Dispose Page')),
    6. body: Container(
    7. child: Obx(()=>Container(child: Text(controller.obj),)),
    8. ),
    9. );
    10. }
    11. @override
    12. void setController() {
    13. Get.put(BingPageController());
    14. }
    15. }
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/凡人多烦事01/article/detail/307797
推荐阅读
相关标签
  

闽ICP备14008679号