* 直接使用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());
@override
Widget 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),
),
);
}
@override
void 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());
@override
Widget build(BuildContext context) {
return Container();
}
@override
void 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());
@override
Widget build(BuildContext context) {
return Container();
}
}
响应式状态管理
当数据源变化时,将自动执行刷新组件的方法
- logic层
- 因为是处理页面逻辑的,加上Controller单词过长,也防止和Flutter自带的一些控件控制器弄混,所以该层用
logic
结尾,这里就定为了logic
层,当然这点随个人意向,写Event,Controller均可 - 这里变量数值后写
.obs
操作,是说明定义了该变量为响应式变量,当该变量数值变化时,页面的刷新方法将自动刷新;基础类型,List,类都可以加.obs
,使其变成响应式变量
- 因为是处理页面逻辑的,加上Controller单词过长,也防止和Flutter自带的一些控件控制器弄混,所以该层用
class CounterGetLogic extends GetxController {
var count = 0.obs;
///自增
void increase() => ++count;
}
-
view层
-
这地方获取到Logic层的实例后,就可进行操作了,大家可能会想:WTF,为什么实例的操作放在build方法里?逗我呢?--------- 实际不然,stl是无状态组件,说明了他就不会被二次重组,所以实例操作只会被执行一次,而且Obx()方法是可以刷新组件的,完美解决刷新组件问题了
class CounterGetPage extends StatelessWidget { @override Widget 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()); @override Widget 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;
}
// controller
final user = User().obs;
//当你需要更新user变量时。
user.update( (user) { // 这个参数是你要更新的类本身。
user.name = 'Jonny';
user.age = 18;
});
// 更新user变量的另一种方式。
user(User(name: 'João', age: 35));
// view
Obx(()=> Text("Name ${user.value.name}: Age: ${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());
@override
Widget 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所有周期回调,可以在相应回调内做一些操作
- init:虽然上述代码没用到,但是,这个参数是存在在GetBuilder中的,因为在加载变量的时候就使用
总结
分析
- 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());
@override
Widget 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;
@override
void 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());
@override
Widget 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;
@override
Widget build(BuildContext context) {
return Container();
}
}
为什么写成这样三个模块,需要把State单独提出来,请速速浏览下方
改造
- state
- 这里可以发现,count类型使用的是
RxInt
,没有使用var
,使用该变量类型的原因,此处是将所有的操作都放在构造函数里面初始化,如果直接使用var
没有立马赋值,是无法推导为Rx
类型,所以这里直接定义为RxInt
,实际很简单,基础类型将开头字母大写,然后加上Rx
前缀即可 - 实际上直接使用
var
也是可以的,但是,使用该响应变量的时候.value
无法提示,需要自己手写,所以还是老老实实的写明Rx具体类型吧 - 详细可查看:[声明响应式变量](
- 这里可以发现,count类型使用的是
)
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;
@override
Widget 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),
),
);
}
}
对比
看了上面的改造,屏幕前的你可能想吐槽了:坑比啊,之前简简单单的逻辑层,被拆成俩个,还搞得这么麻烦,你是猴子请来的逗比吗?
大家先别急着吐槽,当业务过于复杂,state层,也是会维护很多东西的,让我们看看下面的一个小栗子,下面实例代码是不能直接运行的,想看详细运行代码,请查看项目:[flutter_use](
)
最后
文章所有资料全部已经打包整理好,另外小编手头上整理了大量Android架构师全套学习资料,Android核心高级技术PDF文档+全套高级学习资料+视频+2021 BAT 大厂面试真题解析
**[CodeChina开源项目:《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》](
)**
资料展示:
本文已被腾讯CODING开源托管项目:《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》收录,自学资源及系列文章持续更新中…
这么麻烦,你是猴子请来的逗比吗?大家先别急着吐槽,当业务过于复杂,state层,也是会维护很多东西的,让我们看看下面的一个小栗子,下面实例代码是不能直接运行的,想看详细运行代码,请查看项目:[flutter_use](
)
最后
文章所有资料全部已经打包整理好,另外小编手头上整理了大量Android架构师全套学习资料,Android核心高级技术PDF文档+全套高级学习资料+视频+2021 BAT 大厂面试真题解析
**[CodeChina开源项目:《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》](
)**
资料展示:
[外链图片转存中…(img-V6YMmA1l-1631249894194)]
[外链图片转存中…(img-rBr6hXXb-1631249894195)]
[外链图片转存中…(img-JGDw7gWA-1631249894196)]
[外链图片转存中…(img-NyP0h2UL-1631249894197)]
本文已被腾讯CODING开源托管项目:《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》收录,自学资源及系列文章持续更新中…