Flutter GetX 使用 --- 简洁的魅力!,Android 高级
GetX 实现了动态路由传参,也就是说直接在命名路由上拼参数,然后能拿到这些拼在路由上的参数,也就是说用 flutter 写 H5,直接能通过 Url 传值(fluro 也能做到),OMG!可以无脑舍弃复杂的 fluro 了
实现了全局 BuildContext
国际化,主题实现
上面单单是 build 简写的优势,就会让我考虑是否去使用了,而且还能实现跨页面的功能,这还考虑啥,开搞!
下来将全面的介绍 GetX 的使用,文章也不分篇水阅读量了,力求一文写清楚,方便大家随时查阅
准备
引入
首先导入 GetX 的插件
getx 状态管理框架 https://pub.flutter-io.cn/packages/get
非空安全最后一个版本(flutter 2.0 之前版本)
get: ^3.26.0
空安全版本 最新版本请查看 https://pub.flutter-io.cn/packages/get
get: ^4.1.3
GetX 地址
Github:[jonataslaw/getx](
)
Pub:[get](
)
主入口配置
只需要将
MaterialApp
改成GetMaterialApp
即可
void main() {runApp(MyApp());}
class MyApp extends StatelessWidget {@overrideWidget build(BuildContext context) {return GetMaterialApp(home: CounterGetPage(),);}}
各模块导包,均使用下面包即可
import 'package:get/get.dart';
插件
吐槽下写插件的过程,实际写这种模板代码生成插件,其实也不难,网上有很多人写了范例,参考参考思路,能较快的整出来,就是有些配置比较蛋筒。
一开始选择 Plugin DevKit 模式整的,都已经写好,但是看官网文档的时候,官方文档开头就说了:建议使用 Gradle 模式开发插件,又巴拉巴拉列了一堆好处;考虑良久,决定用 Gradle 模式重写。
这个 Gradle 模式,最烦的还是开头新建项目的时候,那个 Gradle 死活下载不下来,科学全局上网都不行,然后手动下载了 Gradle,指定本地 Gradle,开全局再次同步时,会下载一个较大的社区版 IDEA,但是使用本地 Gradle 加载完,存在一个很大的 BUG!main 文件夹下,不会自动生成 Java 文件夹!我真是佛了,点击其它的文件夹,右击:New -> Plugin DevKit 居然不会没有 Action 选项,差点把我劝退了,换了了七八个版本 IDEA 试了都不行!Action 选项出不来,过了俩天后,晚上无意尝试在 main 文件夹下面新建了一个 Java 文件,然后在这个 java 文件上右击:New -> Plugin DevKit,Action 选项出现了!真几把佛了。。。
还有个巨坑的问题,在 Gradle 模式下开发插件,把模板代码文件放在 main 文件下、放在 src 下、放在根目录下,都获取不到文件里面的内容,这个真是坑了我不少时间,搜了很多博客,都发现没写这个问题,官方文档范例看了几遍也没发现有啥说明,后来找到了一个三年前的项目,翻了翻代码发现,所有的资源文件都必须放在 resources 文件夹下,才能读取到文件内容。。。我勒个去。。。
说明
插件地址
Github:[getx_template](
)
Jetbrains:[getx_template](
)
插件效果
看下插件使用的效果图吧,样式参考了 fish_redux 插件样式
有一些可选择的功能,所以做成多按钮的样式,大家可以按照自己的需求进行操作
说下插件的功能含义
Model:生成 GetX 的模式,
Default:默认模式,生成三个文件:state,logic,view
Easy:简单模式,生成俩个文件:logic,view
Function:功能选择
useFolder:使用文件,选择后会生成文件夹,大驼峰命名自动转换为:小写+下划线
usePrefix:使用前缀,生成的文件前加上前缀,前缀为:大驼峰命名自动转换为:小写+下划线
Module Name:模块的名称,请使用大驼峰命名
效果图
生成模板代码
提供后缀名修改,也支持了持久化
Alt + Enter : 可以选择包裹 Widget,有三种可选(GetBuilder、Obx、GetX),大大方便开发哟(^U^)ノ~YO
快捷代码片段提示:我自己写了俩个,其它的快捷代码来自:[getx-snippets-intelliJ](
)
输入 get 前缀便有提示
安装
在设置里面选择:Plugins ---> 输入“getx”搜索 ---> 选择名字为:“GeX” ---> 然后安装 ---> 最后记得点击下“Apply”
如果在使用该插件的过程中有什么问题,请在该项目的 github 上给我提 issue,我看到后,会尽快处理
资源释放
关于 GetxController 的资源释放,这个栏目的内容,我必须把它放在前面说明下,这个模块内容相当重要!
资源未释放的场景
在我们使用 GetX 的时候,可能没什么 GetxController 未被释放的感觉,这种情况,是因为我们一般都是用了 getx 的那一套路由跳转 api(Get.to、Get.toName...)之类:使用 Get.toName,肯定需要使用 GetPage;如果使用 Get.to,是不需要在 GetPage 中注册的,Get.to 的内部有一个添加到 GetPageRoute 的操作
通过上面会在 GetPage 注册可知,说明在我们跳转页面的时候,GetX 会拿你到页面信息存储起来,加以管理,下面俩种场景会导致 GetxController 无法释放
未使用 GetX 提供的路由跳转:直接使用原生路由 api 的跳转操作
这样会直接导致 GetX 无法感知对应页面 GetxController 的生命周期,会导致其无法释放
在 GetPage 注册页面,不使用 Get.toName,这样无法释放;GetPage+Get.toName 配套使用可释放
直接使用 Get.to,可释放
Navigator.push(context,MaterialPageRoute(builder: (context) => XxxxPage()),);
一般 app 都是,一个主页面,加几个 tab:首页、信息流页、操作页、个人中心之类
如果你遇到需要重新登录,再回到主页,你可能会发现个人中心这些页面的 GetxController 控制未被回收!
这些页面已经和上面的路由页面无关了,因为他们本身只能算是主页面上的几个 tab 子页面,没法用路由去标定绑定关系
解决方案
这边我模拟了上面场景,写了一个解决方案
第一个页面跳转
Navigator.push(Get.context,MaterialPageRoute(builder: (context) => AutoDisposePage()),);
演示页面
这地方地方必须要使用 StatefulWidget,因为在这种情况,无法感知生命周期,就需要使用 StatefulWidget 生命周期
在 dispose 回调处,把当前 GetxController 从整个 GetxController 管理链中删除即可
class AutoDisposePage extends StatefulWidget {@override_AutoDisposePageState createState() => _AutoDisposePageState();}
class _AutoDisposePageState extends State<AutoDisposePage> {final AutoDisposeLogic logic = Get.put(AutoDisposeLogic());
@overrideWidget build(BuildContext context) {return BaseScaffold(appBar: AppBar(title: const Text('计数器-自动释放')),body: Center(child: Obx(() => Text('点击了 ${logic.count.value} 次',style: TextStyle(fontSize: 30.0)),),),floatingActionButton: FloatingActionButton(onPressed: () => logic.increase(),child: const Icon(Icons.add),),);}
@overridevoid dispose() {Get.delete<AutoDisposeLogic>();super.dispose();}}
class AutoDisposeLogic extends GetxController {var count = 0.obs;
///自增 void increase() => ++count;}
看到这,你可能会想,啊这!怎么这么麻烦,我怎么还要写 StatefulWidget,好麻烦!
各位放心,这个问题,我也想到了,我特地在插件里面加上了自动回收的功能
如果你写的页面无法被回收,记得勾选 autoDispose
怎么判断页面的 GetxController 是否能被回收呢?实际上很简单,上面的未被释放的场景已经描述的比较清楚了,不清楚的话,就再看看
来看下代码,default 模式一样可以的
view
class AutoDisposePage extends StatefulWidget {@override_AutoDisposePageState createState() => _AutoDisposePageState();}
class _AutoDisposePageState extends State<AutoDisposePage> {final AutoDisposeLogic logic = Get.put(AutoDisposeLogic());
@overrideWidget build(BuildContext context) {return Container();}
@overridevoid dispose() {Get.delete<AutoDisposeLogic>();super.dispose();}}
logic
class AutoDisposeLogic extends GetxController {
}
好了,接下来,进入正文吧!
计数器
效果图
[体验一下](
)
实现
首页,当然是实现一个简单的计数器,来看 GetX 怎么将逻辑层和界面层解耦的
来使用插件生成下简单文件
模式选择:Easy
功能选择:useFolder
来看下生成的默认代码,默认代码十分简单,详细解释放在俩种状态管理里
logic
import 'package:get/get.dart';
class CounterGetLogic extends GetxController {
}
view
import 'package:flutter/material.dart';import 'package:get/get.dart';
import 'logic.dart';
class CounterGetPage extends StatelessWidget {final CounterGetLogic logic = Get.put(CounterGetLogic());
@overrideWidget build(BuildContext context) {return Container();}}
响应式状态管理
当数据源变化时,将自动执行刷新组件的方法
logic 层
因为是处理页面逻辑的,加上 Controller 单词过长,也防止和 Flutter 自带的一些控件控制器弄混,所以该层用
logic
结尾,这里就定为了logic
层,当然这点随个人意向,写 Event,Controller 均可这里变量数值后写
.obs
操作,是说明定义了该变量为响应式变量,当该变量数值变化时,页面的刷新方法将自动刷新;基础类型,List,类都可以加.obs
,使其变成响应式变量
class CounterGetLogic extends GetxController {var count = 0.obs;
///自增 void increase() => ++count;}
view 层
这地方获取到 Logic 层的实例后,就可进行操作了,大家可能会想:WTF,为什么实例的操作放在 build 方法里?逗我呢?--------- 实际不然,stl 是无状态组件,说明了他就不会被二次重组,所以实例操作只会被执行一次,而且 Obx()方法是可以刷新组件的,完美解决刷新组件问题了
class CounterGetPage extends StatelessWidget {@overrideWidget build(BuildContext context) {CounterGetLogic logic = Get.put(CounterGetLogic());
return Scaffold(appBar: AppBar(title: const Text('GetX 计数器')),body: Center(child: Obx(() => Text('点击了 ${logic.count.value} 次',style: TextStyle(fontSize: 30.0)),),),floatingActionButton: FloatingActionButton(onPressed: () => logic.increase(),child: const Icon(Icons.add),),);}}
当然,也可以这样写
class CounterGetPage extends StatelessWidget {final CounterGetLogic logic = Get.put(CounterGetLogic());
@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: const Text('GetX 计数器')),body: Center(child: Obx(() => Text('点击了 ${logic.count.value} 次',style: TextStyle(fontSize: 30.0)),),),floatingActionButton: FloatingActionButton(onPressed: () => logic.increase(),child: const Icon(Icons.add),),);}}
可以发现刷新组件的方法极其简单:
Obx()
,这样可以愉快的到处写定点刷新操作了Obx()方法刷新的条件
只有当响应式变量的值发生变化时,才会会执行刷新操作,当某个变量初始值为:“test”,再赋值为:“test”,并不会执行刷新操作
当你定义了一个响应式变量,该响应式变量改变时,包裹该响应式变量的 Obx()方法才会执行刷新操作,其它的未包裹该响应式变量的 Obx()方法并不会执行刷新操作,Cool!
来看下如果把整个类对象设置成响应类型,如何实现更新操作呢?
下面解释来自官方 README 文档
这里尝试了下,将整个类对象设置为响应类型,当你改变了类其中一个变量,然后执行更新操作,
只要包裹了该响应类变量的Obx(),都会实行刷新操作
,将整个类设置响应类型,需要结合实际场景使用
// model// 我们将使整个类成为可观察的,而不是每个属性。class User{User({this.name = '', this.age = 0});String name;int age;}
// controllerfinal user = User().obs;//当你需要更新 user 变量时。user.update( (user) { // 这个参数是你要更新的类本身。user.name = 'Jonny';user.age = 18;});// 更新 user 变量的另一种方式。user(User(name: 'Jo?o', age: 35));
// viewObx(()=> Text("Name {user.value.age}"));// 你也可以不使用.value 来访问模型值。user().name; // 注意是 user 变量,而不是类变量(首字母是小写的)。
简单状态管理
GetBuilder:这是一个极其轻巧的状态管理器,占用资源极少!
logic:先来看看 logic 层
class CounterEasyGetLogic extends GetxController {var count = 0;
void increase() {++count;update();}}
view
class CounterEasyGetPage extends StatelessWidget {final CounterEasyGetLogic logic = Get.put(CounterEasyGetLogic());
@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: const Text('计数器-简单式')),body: Center(child: GetBuilder<CounterEasyGetLogic>(builder: (logicGet) => Text('点击了 ${logicGet.count} 次',style: TextStyle(fontSize: 30.0),),),),floatingActionButton: FloatingActionButton(onPressed: () => logic.increase(),child: const Icon(Icons.add),),);}}
分析下:GetBuilder 这个方法
init:虽然上述代码没用到,但是,这个参数是存在在 GetBuilder 中的,因为在加载变量的时候就使用
Get.put()
生成了CounterEasyGetLogic
对象,GetBuilder 会自动查找该对象,所以,就可以不使用 init 参数builder:方法参数,拥有一个入参,类型便是 GetBuilder 所传入泛型的类型
initState,dispose 等:GetBuilder 拥有 StatefulWidget 所有周期回调,可以在相应回调内做一些操作
总结
分析
Obx 是配合 Rx 响应式变量使用、GetBuilder 是配合 update 使用:请注意,这完全是俩套定点刷新控件的方案
区别:前者响应式变量变化,Obx 自动刷新;后者需要使用 update 手动调用刷新
响应式变量,因为使用的是
StreamBuilder
,会消耗一定资源GetBuilder
内部实际上是对 StatefulWidget 的封装,所以占用资源极小
使用场景
一般来说,对于大多数场景都是可以使用响应式变量的
但是,在一个包含了大量对象的 List,都使用响应式变量,将生成大量的
StreamBuilder
,必将对内存造成较大的压力,该情况下,就要考虑使用简单状态管理了
跨页面交互
跨页面交互,在复杂的场景中,是非常重要的功能,来看看 GetX 怎么实现跨页面事件交互的
效果图
[体验一下](
)
Cool,这才是真正的跨页面交互!下级页面能随意调用上级页面事件,且关闭页面后,下次重进,数据也很自然重置了(全局 Bloc 不会重置,需要手动重置)
实现
页面一
常规代码
logic
这里的自增事件,是供其它页面调用的,该页面本身没使用
class JumpOneLogic extends GetxController {var count = 0.obs;
///跳转到跨页面 void toJumpTwo() {Get.toNamed(RouteConfig.jumpTwo, arguments: {'msg': '我是上个页面传递过来的数据'});}
///跳转到跨页面 void increase() => count++;}
view
此处就一个显示文字和跳转功能
class JumpOnePage extends StatelessWidget {/// 使用 Get.put()实例化你的类,使其对当下的所有子路由可用。final JumpOneLogic logic = Get.put(JumpOneLogic());
@overrideWidget build(BuildContext context) {return Scaffold(backgroundColor: Colors.white,appBar: AppBar(title: Text('跨页面-One')),floatingActionButton: FloatingActionButton(onPressed: () => logic.toJumpTwo(),child: const Icon(Icons.arrow_forward_outlined),),body: Center(child: Obx(() => Text('跨页面-Two 点击了 ${logic.count.value} 次',style: TextStyle(fontSize: 30.0)),),),);}}
页面二
这个页面就是重点了
logic
将演示怎么调用前一个页面的事件
怎么接收上个页面数据
请注意,
GetxController
包含比较完整的生命周期回调,可以在onInit()
接受传递的数据;如果接收的数据需要刷新到界面上,请在onReady
回调里面接收数据操作,onReady
是在addPostFrameCallback
回调中调用,刷新数据的操作在onReady
进行,能保证界面是初始加载完毕后才进行页面刷新操作的
class JumpTwoLogic extends GetxController {var count = 0.obs;var msg = ''.obs;
@overridevoid onReady() {var map = Get.arguments;msg.value = map['msg'];
super.onReady();}
///跳转到跨页面 void increase() => count++;}
view
加号的点击事件,点击时,能实现俩个页面数据的变换
重点来了,这里通过
Get.find()
,获取到了之前实例化 GetXController,获取某个模块的 GetXController 后就很好做了,可以通过这个 GetXController 去调用相应的事件,也可以通过它,拿到该模块的数据!
class JumpTwoPage extends StatelessWidget {final JumpOneLogic oneLogic = Get.find();final JumpTwoLogic twoLogic = Get.put(JumpTwoLogic());
@overrideWidget build(BuildContext context) {return Scaffold(backgroundColor: Colors.white,appBar: AppBar(title: Text('跨页面-Two')),floatingActionButton: FloatingActionButton(onPressed: () {oneLogic.increase();twoLogic.increase();},child: const Icon(Icons.add),),body: Center(child: Column(mainAxisSize: MainAxisSize.min, children: [//计数显示 Obx(() => Text('跨页面-Two 点击了 ${twoLogic.count.value} 次',style: TextStyle(fontSize: 30.0)),),
//传递数据 Obx(() => Text('传递的数据:${twoLogic.msg.value}',style: TextStyle(fontSize: 30.0)),),]),),);}}
总结
GetX 这种的跨页面交互事件,真的是非常简单了,侵入性也非常的低,不需要在主入口配置什么,在复杂的业务场景下,这样简单的跨页面交互方式,就能实现很多事了
进阶吧!计数器
我们可能会遇到过很多复杂的业务场景,在复杂的业务场景下,单单某个模块关于变量的初始化操作可能就非常多,在这个时候,如果还将 state(状态层)和 logic(逻辑层)写在一起,维护起来可能看的比较晕,这里将状态层和逻辑层进行一个拆分,这样在稍微大一点的项目里使用 GetX,也能保证结构足够清晰了!
在这里就继续用计数器举例吧!
实现
此处需要划分三个结构了:state(状态层),logic(逻辑层),view(界面层)
这里使用插件生成下模板代码
Model:选择 Default(默认)
Function:useFolder(默认中)
来看下生成的模板代码
state
class CounterHighGetState {CounterHighGetState() {///Initialize variables}}
logic
import 'package:get/get.dart';
import 'state.dart';
class CounterHighGetLogic extends GetxController {final state = CounterHighGetState();}
view
import 'package:flutter/material.dart';import 'package:get/get.dart';
import 'logic.dart';import 'state.dart';
class CounterHighGetPage extends StatelessWidget {final CounterHighGetLogic logic = Get.put(CounterHighGetLogic());final CounterHighGetState state = Get.find<CounterHighGetLogic>().state;
@overrideWidget build(BuildContext context) {return Container();}}
为什么写成这样三个模块,需要把 State 单独提出来,请速速浏览下方
改造
state
这里可以发现,count 类型使用的是
RxInt
,没有使用var
,使用该变量类型的原因,此处是将所有的操作都放在构造函数里面初始化,如果直接使用var
没有立马赋值,是无法推导为Rx
类型,所以这里直接定义为RxInt
,实际很简单,基础类型将开头字母大写,然后加上Rx
前缀即可实际上直接使用
var
也是可以的,但是,使用该响应变量的时候.value
无法提示,需要自己手写,所以还是老老实实的写明 Rx 具体类型吧详细可查看:[声明响应式变量](
)
class CounterHighGetState {RxInt count;
CounterHighGetState() {count = 0.obs;}}
logic
逻辑层就比较简单,需要注意的是:开始时需要实例化状态类
class CounterHighGetLogic extends GetxController {final state = CounterHighGetState();
///自增 void increase() => ++state.count;}
view
实际上 view 层,和之前的几乎没区别,区别的是把状态层给独立出来了
因为
CounterHighGetLogic
被实例化,所以直接使用Get.find<CounterHighGetLogic>()
就能拿到刚刚实例化的逻辑层,然后拿到 state,使用单独的变量接收下ok,此时:logic 只专注于触发事件交互,state 只专注数据
class CounterHighGetPage extends StatelessWidget {final CounterHighGetLogic logic = Get.put(CounterHighGetLogic());final CounterHighGetState state = Get.find<CounterHighGetLogic>().state;
@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: const Text('计数器-响应式')),body: Center(child: Obx(() => Text('点击了 ${state.count.value} 次',style: TextStyle(fontSize: 30.0)),),),floatingActionButton: FloatingActionButton(onPressed: () => logic.increase(),child: const Icon(Icons.add),),);
评论