赞
踩
一个应用的状态就是当这个应用运行时存在于内存中的所有内容。当然许多状态,例如纹理、动画状态等,框架本身会替开发者管理,所以对于状态更合适的定义是**“当你需要重建用户界面时所需要的数据”**,我们需要自己管理的状态可以分为两种概念类型:短时 (ephemeral) 状态和应用 (app) 状态。
短时状态
短时状态是可以完全包含在一个独立 widget 中的状态,也成为局部状态。
应用状态
如果在应用中的多个部分之间共享一个非短时的状态,并且在用户会话期间保留这个状态,我们称之为应用状态(有时也称共享状态)。
开发者一直致力于业务逻辑分离的概念,Flutter 也有利用 BLoc 、Provider 衍生的 MVC、MVVM 等架构模式,但是这几种方案的状态管理均使用了上下文(context),需要上下文来寻找InheritedWidget
,这种解决方案限制了状态管理必须在父子代的 widget 树中,业务逻辑也会对 View 产生较强依赖。
而 GetX 因为不需要上下文,突破了InheritedWidget
的限制,我们可以在全局和模块间共享状态,这正是 BLoc 、Provider 等框架的短板。
另外 GetX 控制器也是有生命周期的,例如当我们需要业务层进行 APIREST 时,我们可以不依赖于界面中的任何东西。可以使用onInit
来启动http调用,当数据到达赋值给变量后,利用 GetX 响应式的特性,使用该变量的 Widgets 将在界面中自动更新。这样在 UI层只需要写界面,除了用户事件(比如点击按钮)之外,不需要向业务逻辑层发送任何东西。
对于以前使用过 ChangeNotifier
的同学来说,可以把GetxController
当做ChangeNotifier
,我们使用计数器示例来演示一下基本使用:
- class SimpleController extends GetxController {
- int _counter = 0;
- int get counter => _counter;
-
- void increment() {
- _counter++;
- update();
- }
- }
这是一个控制器,有 UI 需要的数据counter
和用户点击一次加1的方法。
在 UI 层一个展示的文本和一个按钮:
- class SimplePage extends StatelessWidget {
- @override
- Widget build(BuildContext context) {
- print('SimplePage--build');
- return GetBuilder<SimpleController>(
- init: SimpleController(),
- builder: (controller) {
- return Scaffold(
- appBar: AppBar(title: Text('Simple')),
- body: Center(
- child: Text(controller.counter.toString()),
- ),
- floatingActionButton: FloatingActionButton(
- onPressed: () {
- controller.increment();
- },
- child: Icon(Icons.add),
- ),
- );
- });
- }
- }
使用了GetBuilder
这个 Widget 包裹了页面,在 init
初始化SimpleController
,然后每次点击,都会更新builder
对应的 Widget ,GetxController
通过update()
更新GetBuilder
。
这看起来和别状态管理框架并无不同,有时我们只想重新 build 需要变化的部分,遵循最小原则,那么我们改下GetBuilder
的位置,只包裹 Text
:
- class SimplePage extends StatelessWidget {
- @override
- Widget build(BuildContext context) {
- print('SimplePage--build');
- return Scaffold(
- appBar: AppBar(title: Text('Simple')),
- body: Center(
- child: GetBuilder<SimpleController>(
- init: SimpleController(),
- builder: (controller) {
- return Text(controller.counter.toString());
- }),
- ),
- floatingActionButton: FloatingActionButton(
- onPressed: () {
- controller.increment();
- },
- child: Icon(Icons.add),
- ),
- );
- }
- }
因为 controlle
作用域问题,此时按钮里面的 controller
会找不到,GetX强大的一点的就表现出来了,按钮和文本并不在父子组件,并且和GetBuilder
不在一个作用域,但是我们依然能正确得到:
- onPressed: () {
- Get.find<SimpleController>().increment();
- // controller..increment();
- },
GetxController
也有生命周期的:
- class SimpleController extends GetxController {
- int _counter = 0;
- int get counter => _counter;
-
- void increment() {
- _counter++;
- update();
- }
-
- @override
- void onInit() {
- super.onInit();
- print('SimpleController--onInit');
- }
-
- @override
- void onReady() {
- super.onReady();
- print('SimpleController--onReady');
- }
-
- @override
- void onClose() {
- super.onClose();
- print('SimpleController--onClose');
- }
- }
-
之前在这里打印了一句:
- class SimplePage extends StatelessWidget {
- @override
- Widget build(BuildContext context) {
- print('SimplePage--build');
- return Scaffold(
- 。。。
再次打开这个页面,控制台输出:
- flutter: SimplePage--build
- flutter: SimpleController--onInit
- [GETX] "SimpleController" has been initialized
- flutter: SimpleController--onReady
-
SimplePage-build->SimpleController-onInit->SimpleController-onReady
推出当前页面返回:
- [GETX] CLOSE TO ROUTE /SimplePage
- flutter: SimpleController--onClose
- [GETX] "SimpleController" onClose() called
- [GETX] "SimpleController" deleted from memory
- [GETX] Instance "SimpleController" already removed.
-
可以看到SimpleController
已经被删除。
多种状态可以分别更新,不需要为每个状态创建一个类。
再添加一个变量:
- int _counter = 0;
- int get counter => _counter;
-
- String _name = "Lili";
- String get firstName => _name;
-
- void increment() {
- _counter++;
- _name = WordPair.random().asPascalCase;
- update(['counter']);
- }
-
- void changeName() {
- _counter++;
- _name = WordPair.random().asPascalCase;
- update(['name']);
- }
两个方法分别改变两个变量,但是注意update(['counter']
里添加了 id 数组,这样就只更新这个 id 对应的GetBuilder
:
- GetBuilder<SimpleAdvancedController>(
- id: 'counter',
- builder: (ctl) => Text(ctl.counter.toString()),
- ),
- SizedBox(
- height: 50,
- ),
- GetBuilder<SimpleAdvancedController>(
- id: 'name',
- builder: (ctl) => Text(ctl.firstName),
- ),
我们都用过 StreamControllers ,然后以流的方式发送数据。在 GetX 可以实现同样的功能,并且实现起来只有几个单词,不需要为每个观察的对象创建一个 StreamController ,也不需要创建 StreamBuilder。
var name = '新垣结衣';
下面简单的一个后缀就可以把一个变量变得可观察,变量每次改变的时候,使用它的小部件就会被更新:
var name = '新垣结衣'.obs;
就这么简单,这个变量已经是响应式的了。然后通过 Obx 或者 GetX 包裹并使用响应式变量的控件,在变量改变的时候就会被更新:
Obx (() => Text (controller.name));
下面写个计算器的例子:
- final count1 = 0.obs;
- final count2 = 0.obs;
.obs
就实现了一个被观察者,他们不再是 int 类型,而是 RxInt 类型。对应的小部件也不再是GetBuilder
了,而是下面两种:
- GetX<SumController>(
- builder: (_) {
- print("count1 rebuild");
- return Text(
- '${_.count1}',
- style: TextStyle(fontWeight: FontWeight.bold),
- );
- },
- ),
- Obx(() => Text(
- '${Get.find<SumController>().count2}',
- style: TextStyle(fontWeight: FontWeight.bold),
- )),
因为是响应式,不再需要update
,每次更改值,都自动刷新。但是更神奇的是,他们的运算和也是响应式的:
int get sum => count1.value + count2.value;
只要更新count1
或者count2
使用sum
的小部件也会更改:
- Obx(() => Text(
- '${Get.find<SumController>().sum}',
- style: TextStyle(fontWeight: FontWeight.bold),
- )),
非常简单的使用方式,不是吗?除了使用.obs
还有2种方法把变量变成可观察的:
- // 建议使用初始值,但不是强制性的
- 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>({});
-
- 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>();
将一个对象转变成可观察的,也有2种方法:
- class RxUser {
- final name = "Camila".obs;
- final age = 18.obs;
- }
- class User {
- User({String name, int age});
- var name;
- var age;
- }
-
- //实例化时。
- final user = User(name: "Camila", age: 18).obs;
注意,转化为可观察的变量后,它的类型不再是原生类型,所以取值不能用变量本身,而是.value
当然 GetX 也提供了 api 简化对 int、List 的操作。此外,Get还提供了精细的状态控制。我们可以根据特定的条件对一个事件进行条件控制(比如将一个对象添加到List中):
- // 第一个参数:条件,必须返回true或false。
- // 第二个参数:如果条件为真,则为新的值。
- list.addIf(item < limit, item);
响应式编程虽好,可不要贪杯。因为响应式对 RAM 的消耗比较大,因为他们的实现都是流,如果创建一个有80个对象的 List ,每个对象都有几个流,打开dart inspect,查看一个 StreamBuilder 的消耗量,我们就会明白这不是一个好的方法。而 GetBuilder 在 RAM 中是非常高效的,几乎没有比他更高效的方法。所以这些使用方式在使用过程中要斟酌。
响应式不只这些好处,还有一个 Workers ,将协助我们在事件发生时触发特定的回调,也就是 RxJava 的一些操作符;
- @override
- onInit() {
- super.onInit();
-
- /// 每次更改都会回调
- ever(count1, (_) => print("$_ has been changed"));
-
- /// 第一次更改回调
- once(count1, (_) => print("$_ was changed once"));
-
- /// 更改后3秒回调
- debounce(count1, (_) => print("debouce$_"), time: Duration(seconds: 3));
-
- ///3秒内更新回调一次
- interval(count1, (_) => print("interval $_"), time: Duration(seconds: 3));
- }
我们可以利用 Workers ,去实现写一堆对代码才能实现的功能。比如防抖函数,在搜索的时候使用,节流函数,在点击事件的时候使用。
上面演示过在同一个页面兄弟组件跨组件使用,接下来实现下不同页面跨组件使用,首先在CrossOnePage
里 put
一个 Controller:
- class CrossOnePage extends StatelessWidget {
- @override
- Widget build(BuildContext context) {
- CrossOneController controller = Get.put(CrossOneController());
- ...
- }}
然后在另一个页面CrossTwoPage
,打印下上一个页面put
的控制器:
- CheetahButton('打印CrossOneController的age', () {
- print(Get.find<CrossOneController>().age);
- }),
正常输出。
那么CrossOneController
的生命周期多久呢?如果像第一个页面一样是在build
里 put 的,那么当前页面退出就销毁了。如果是成员变量,那么当前页面的引用销毁才会销毁:
- class CrossTwoPage extends StatelessWidget {
- final CrossTwoSecondController controller = Get.put(CrossTwoSecondController());
- @override
- Widget build(BuildContext context) {
- Get.put(CrossTwoController());
- return Scaffold(
- appBar: AppBar(title: Text('CrossTwoPage')),
- body: Container(
- child: Column(
- children: [
- CheetahButton('打印CrossTwoController', () {
- print(Get.find<CrossTwoController>());
- }),
- CheetahButton('CrossTwoSecondController', () {
- print(Get.find<CrossTwoSecondController>());
- }),
- CheetahButton('打印CrossOneController的age', () {
- print(Get.find<CrossOneController>().age);
- }),
- ],
- )),
- );
- }
- }
-
CrossTwoSecondController
是成员变量,CrossTwoController
是在build
的时候 put 进去的,现在打印2个控制器,都能打印出来:
- [GETX] "CrossTwoSecondController" has been initialized
- [GETX] GOING TO ROUTE /CrossTwoPage
- [GETX] "CrossTwoController" has been initialized
- I/flutter (16952): Instance of 'CrossTwoController'
- I/flutter (16952): Instance of 'CrossTwoSecondController'
-
现在返回第一个页面,GetX 已经给我们打印了:
- GETX] CLOSE TO ROUTE /CrossTwoPage
- [GETX] "CrossTwoController" onClose() called
- [GETX] "CrossTwoController" deleted from memory
-
然后我们在第一个页面点击按钮,分别打印页面CrossTwoPage
的2个控制器:
- ════════ Exception caught by gesture ═══════════════════════════════════════════
- "CrossTwoController" not found. You need to call "Get.put(CrossTwoController())" or "Get.lazyPut(()=>CrossTwoController())"
- ════════════════════════════════════════════════════════════════════════════════
- I/flutter (16952): Instance of 'CrossTwoSecondController'
-
在build
里 put
的控制器已经销毁为 null 了,另一个依然存在,那是不是这种不会销毁呢?因为第一个页面的路由依然持有第二个页面,第二个页面的实例还在内存中,所以控制器作为成员变量依然存在,退出第一个页面,自然就销毁了:
- [GETX] CLOSE TO ROUTE /CrossOnePage
- [GETX] "CrossOneController" onClose() called
- [GETX] "CrossOneController" deleted from memory
- [GETX] "CrossTwoSecondController" onClose() called
- [GETX] "CrossTwoSecondController" deleted from memory
-
GetX虽然各个功能均可单独引用使用,但是状态管理和路由是搭配的,如果没有使用 route_manager
组件,那么状态管理的生命周期就会失效。put
的Controller
在不使用的时候不会再被删除,而变成了应用状态常驻内存里。
如果项目的路由暂时不能使用 GetX 替换,那么怎么使用状态管理呢,很简单,封装一个自动删除Controller
的控件即可,因为习惯使用GetBinding
,待可以替换为 GetX 路由的时候直接带上GetBinding
,所以封装了一个GetBinding
的控件和一个不使用GetBinding
的控件:
- abstract class GetBindingView<T extends GetxController>
- extends StatefulWidget {
- final String? tag = null;
-
- T get controller => GetInstance().find<T>(tag: tag);
-
- @protected
- Widget build(BuildContext context);
-
- @protected
- Bindings? binding();
-
- @override
- _AutoDisposeState createState() => _AutoDisposeState<T>();
- }
-
- class _AutoDisposeState<S extends GetxController>
- extends State<GetBindingView> {
- _AutoDisposeState();
-
- @override
- Widget build(BuildContext context) {
- return widget.build(context);
- }
-
- @override
- void initState() {
- super.initState();
- widget.binding()?.dependencies();
- }
-
- @override
- void dispose() {
- Get.delete<S>();
- super.dispose();
- }
- }
使用很简单:
GetBinding
、GetxController
和 Page ,GetDisposeView
,binding()
方法并返回第一步创建的GetBinding
。- class BingPagePage extends GetBindingView<BingPageController> {
- @override
- Widget build(BuildContext context) {
- return Scaffold(
- appBar: AppBar(title: Text('BingPage Page')),
- body: Container(
- child: Obx(()=>Container(child: Text(controller.obj),)),
- ),
- );
- }
-
- @override
- Bindings? binding() =>BingPageBinding();
- }
接下来就可以像使用 GetView
一样使用了,如果以后替换了 GetX 路由,只需要把 GetDisposeView
替换为GetView
。
下面是一个不使用GetBinding
的控件,比上面的使用更简单,不需要创建GetBinding
:
- abstract class GetDisposeView<T extends GetxController> extends StatefulWidget {
- final String? tag = null;
-
- T get controller => GetInstance().find<T>(tag: tag);
-
- @protected
- Widget build(BuildContext context);
-
- @protected
- void setController();
-
- @override
- _AutoDisposeState createState() => _AutoDisposeState<T>();
- }
-
- class _AutoDisposeState<S extends GetxController>
- extends State<GetDisposeView> {
- _AutoDisposeState();
-
- @override
- Widget build(BuildContext context) {
- return widget.build(context);
- }
-
- @override
- void initState() {
- super.initState();
- widget.setController();
- }
-
- @override
- void dispose() {
- Get.delete<S>();
- super.dispose();
- }
- }
使用:
创建对应的GetxController
和 Page ,
对应的 Page 修改为继承 GetDisposeView
,
实现setController()
方法并返回第一步创建的put
第一步创建的GetxController
对象。
- class AutoDisposePage extends GetDisposeView<BingPageController> {
- @override
- Widget build(BuildContext context) {
- return Scaffold(
- appBar: AppBar(title: Text('Auto Dispose Page')),
- body: Container(
- child: Obx(()=>Container(child: Text(controller.obj),)),
- ),
- );
- }
- @override
- void setController() {
- Get.put(BingPageController());
- }
- }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。