介绍
GetX,是一个 flutter 插件,它的 LIKES 非常非常多,目前为止是 LIKES 最多的 Flutter package。它功能很强大,而且使用便捷,支持路由管理,状态管理,响应式开发。有了它就可以直接一把梭哈,神速开发 Flutter 应用。 插件官方地址 get。
安装
1 | flutter pub add get |
响应式开发
1 | import 'package:flutter/material.dart'; |
现在,当我们点击添加按钮时,标题上的点击次数就会跟着变化,我们已经实现了响应式的开发。 太简单了!
第一眼看过去,”.obs”是什么东西。这其实是一个拓展语法,平时可能不太常见。简单的说就是在其他类上新增了一个方法。可以查看官网解释拓展方法。
在编辑器里面点击 “.obs”,跳转到源码的位置。
1 | extension IntExtension on int { |
翻译结果就是,返回一个 RxInt 类型的值,使用 .obs 前面的值作为初始值。
它其实是一个语法糖,类似的我们还可以使用 RxString,声明一个响应式的 String。
1 | RxString s = RxString("s"); |
想要声明一个响应式的变量,我们直接在原有类型的变量后面加上 “.obs”,一把梭哈就完事了。
点击 RxInt,直接查看源码。
1 | class RxInt extends Rx<int> { |
翻译一下:operator 运算符重载,实现一个加法和减法操作,然后 RxInt 的 value 可以和 int 类型进行加减操作,然后返回 RxInt。
我们看到 RxInt 继承了 Rx
1 | /// Foundation class used for custom `Types` outside the common native Dart |
Rx
示例代码:
1 | import 'package:get/get.dart'; |
对于自定义的类,对类的实例使用 “.obs”,更新属性值时使用 update 方法,使用 value 属性去访问实例成员属性。
对于 _RxImpl 类,代码就比较复杂,它是响应式的核心。具体源码这里就不展示了,感兴趣的小伙伴可以自己去查看源码,这里只做一个简单的分析。
1 | _RxImpl(T initial) { |
RxImpl 内部使用私有属性 _value 保存初始化的 value。这时候就有人要问了,你刚刚在说通过 value 属性去访问实例的成员属性的,这是怎么回事?
源码里面有一个抽象类 “abstract class _RxImpl
注意这个 RxObjectMixin,它的内部有一段代码。
1 | /// Returns the current [value] |
这里我们可以看到成员有一个 getter,当我们访问 value,实际上返回的是 _value,也就是存储的原始值。
如果我们需要设计一个响应式的系统,也就是说当数据源发送改变的时候,widget 会重新 build。是的,在前两篇文章我介绍了 Stream 和 StreamBuilder, 大体上就是通过 StreamController 添加数据,然后 listen 监听到数据流时会重新 build,从而更新 UI。
_RxImpl 内部有一个 Stream, 主要通过 StreamController 和 StreamSubscription 控制。当我们重新设置 value 的时候,内部会发送一个 Stream,然后监听者们就会收到通知做响应的处理。当然其内部逻辑具体实现还有很多细节,比如设置同样的值并不会触发更新,Stream 关闭的时候不能设置值等。
当我们访问属性值的时候,就会添加 GetStream 到监听者列表,方便查询监听者数量等。然后我们看 Obx(() => Text(‘${c.person.value.age}’)) 方法,它实际上返回的是一个 StatefulWidget, 内部的 build 方法被重写了,实际上调用的就是 Obx 传入的一个返回 widget 的回调函数。在 _ObxState 内部的 initState 生命周期监听 Stream,如果有数据流/事件通知,就调用 setState 重新触发 bulid,更新UI。
简化流程就是,首先设置响应式数据生成 Stream,改变响应式数据的时候,发送通知事件,Obx 内部监听Stream 重新 build,更新UI。具体内容请查看源码分析。
另外关于响应式的状态管理,还有 GetBuilder 可用,这里不在赘述。具体请查看 GetBuilder vs GetX vs Obx vs MixinBuilder。
路由管理
路由跳转
- 导航到新的页面
1
Get.to(NextScreen());
- 关闭SnackBars、Dialogs、BottomSheets或任何你通常会用Navigator.pop(context)关闭的东西。
1
Get.back();
- 替换当前页面,无法回到上一个页面
1
Get.off(NextScreen());
- 删除所有路由记录,跳转到一个新的页面
1
Get.offAll(NextScreen());
- 要导航到下一条路由,并在返回后立即接收或更新数据。
1
var data = await Get.to(Payment());
- 返回上一个页面并传递数据结合起来就是下面这样:
1
Get.back(result: 'success');
1
if(data == 'success') doSomething();
命名路由导航
- 导航到新的页面
1
Get.toNamed(NextScreen());
- 替换当前页面,无法回到上一个页面
1
Get.offNamed(NextScreen());
- 删除所有路由记录,跳转到一个新的页面
1
Get.offAllNamed(NextScreen());
路由传参
发送任意类型的数据,如一个Map
1 | Get.toNamed('/second', arguments: {'id': 1}); |
接收参数
1 | print(Get.arguments['id']); |
类似web使用querystring
1 | Get.toNamed('/second?id=2'); |
接收参数
1 | print(Get.parameters['id']); |
路由参数(需要定义路由)
1 | void main() { |
传递参数
1 | Get.toNamed('/other/1') |
接收参数
1 | print(Get.parameters['user']); // 1 |
路由中间件
类似前端的vue开发,vue-router中路由的全局前置守卫和后置守卫在项目中经常被使用到。利用 Get 插件,我们可以实现类似的功能。
假如我们需要实现一个权限管理功能,没有登录的用户,点击某个页面需要跳转到登录页。
首先,定义一个路由中间件它继承于 GetMiddleware 并重写 redirect 方法,GetMiddleware有很多方法,读者可自行查看源码。
1 | class MyMiddleWare extends GetMiddleware { |
然后将它配置到路由表使用,我们也可以使用多个路由中间件,并可以设置中间件的权重,也就是控制多个中间价的执行顺序。
1 | void main() { |
类似路由跳转后触发的后置守卫就是代码中的 routingCallback 方法,可以监听路由跳转的发生然后做相应的处理。
非跳转导航
在 flutter 中经常需要打开消息弹窗等操作,页面没有发送跳转,但是需要使用 Navigator.pop(context) 关闭。他们的使用通知都依赖于 context,现在使用 get 插件,我们可以很容易实现类似消息弹窗的功能(使用 Get.back() 关闭它们)。
打开 SnackBar
1 | Get.snackbar('Hi', 'i am a modern snackbar'); |
打开Dialog
1 | Get.dialog(YourDialogWidget()); |
打开默认Dialog
1 | Get.defaultDialog( |
打开BottomSheets
1 | Get.bottomSheet( |
依赖管理
在前面的响应式状态管理章节,我们的状态使用 Get.put() 插入了依赖关系,并且返回了一个控制器,我们可以直接使用它。还有可能我们在父 widget 插入了依赖关系。在子 widget 使用,我们就可以使用下面的代码获取到控制器。
1 | final controller = Get.find<Controller>(); // 通过范型获取 |
集成管理
将路由、状态管理器和依赖管理器完全集成,可以简化许多操作。
创建一个类并实现Binding
1 | class Controller extends GetxController { |
然后在你的命名路由定义的时候绑定它们即可。
1 | void main() => runApp( |
现在所有的绑定的路由都可以获取并使用 Controller。
1 | final Controller c = Get.find(); |
你也可以通过 “initialBinding” 来插入所有将要创建的依赖。在 GetMaterialApp 内部,把所有控制器绑定到一起,然后实例化。
1 | GetMaterialApp( |
通过 GetView 可以简化控制器的获取操作。
1 | // GetView<T> 通过范型获取了对应的控制器 |
生命周期
通过编辑器点击 GetxController,进入源码,我们看见 GetxController 是一个抽象类,它继承自 DisposableInterface。
1 | abstract class GetxController extends DisposableInterface |
然后我们查看 DisposableInterface,它继承自 GetLifeCycle。
1 | abstract class DisposableInterface extends GetLifeCycle |
最后发现 GetLifeCycle 继承自 GetLifeCycleBase。
1 | abstract class GetLifeCycle with GetLifeCycleBase |
查看 GetLifeCycleBase 源码,发现它有很多方法。其中的生命周期作者都做了对应的解释,读者可以自己去尝试。
1 | /// The [GetLifeCycle] |
以下给出一个测试生命周期的案例。
1 | class Controller extends GetxController { |
在 onReady 生命周期的时候,我们打印了 ‘onReady’,可以在这时候发送网络请求。