前言
使用 Bloc 的时候,有一个让我至今为止十分在意的问题,无法真正的跨页面交互!在反复的查阅官方文档后,使用一个全局 Bloc 的方式,实现了“伪”跨页面交互,详细可查看:flutter_bloc使用解析;fish_redux 的广播机制是可以比较完美的实现跨页面交互的,我也写了一篇近万字介绍如何使用该框架:fish_redux使用详解,对于中小型项目使用 fish_redux,这会一定程度上降低开发效率,最近尝试了 GetX 相关功能,解决了我的相当一部分痛点
把整篇文章写完后,我马上把自己的一个 demo 里面所有 Bloc 代码全用 GetX 替换,且去掉了 Fluro 框架;感觉用 Getx 虽然会省掉大量的模板代码,但还是有些重复工作:创建文件夹,创建几个必备文件,写那些必须要写的初始化代码和类;略微繁琐,为了对得起 GetX 给我开发带来的巨大便利,我就花了一些时间,给它写了一个插件! 上面这重复的代码,文件,文件夹统统能一键生成!
GetX 相关优势
依赖注入
GetX 是通过依赖注入的方式,存储相应的 XxxGetxController;已经脱离了 InheritedWidget 那一套玩法,自己手动去管理这些实例,使用场景被大大拓展
简单的思路,却能产生深远的影响:优雅的跨页面功能便是基于这种设计而实现的、获取实例无需 BuildContext、GetBuilder 自动化的处理及其减少了入参等等
跨页面交互
这绝对是 GetX 的一个优点!对于复杂的生产环境,跨页面交互的场景,实在太常见了,GetX 的跨页面交互,几乎和 fish_redux 一样简单,爱了爱了
路由管理
是的,getx 内部实现了路由管理,而且用起来,那叫一个简单!bloc 没实现路由管理,这让我不得不去找一个 star 量高的路由管理框架,就选择了 fluro,但是让我不得不说,这个 fluro 用起来真的叫一个折磨人,每次新建一个页面,最让我抗拒的就是去写 fluro 路由代码,横跨几个文件来回写,真是肝疼
GetX 实现了动态路由传参,也就是说直接在命名路由上拼参数,然后能拿到这些拼在路由上的参数,也就是说用 flutter 写 H5,直接能通过 Url 传值(fluro 也能做到),OMG!可以无脑舍弃复杂的 fluro 了
实现了全局 BuildContext
国际化,主题实现
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 地址
主入口配置
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget 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:模块的名称,请使用大驼峰命名
效果图
安装
计数器
效果图
实现
首页,当然是实现一个简单的计数器,来看 GetX 怎么将逻辑层和界面层解耦的
来使用插件生成下简单文件
模式选择:Easy
功能选择:useFolder
来看下生成的默认代码,默认代码十分简单,详细解释放在俩种状态管理里
import 'package:get/get.dart';
class CounterGetLogic extends GetxController {
}
复制代码
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();
}
}
复制代码
响应式状态管理
当数据源变化时,将自动执行刷新组件的方法
class CounterGetLogic extends GetxController {
var count = 0.obs;
///自增
void increase() => ++count;
}
复制代码
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:这是一个极其轻巧的状态管理器,占用资源极少!
class CounterEasyGetLogic extends GetxController {
var count = 0;
void increase() {
++count;
update();
}
}
复制代码
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 所有周期回调,可以在相应回调内做一些操作
总结
分析
Obx 是配合 Rx 响应式变量使用、GetBuilder 是配合 update 使用:请注意,这完全是俩套定点刷新控件的方案
区别:前者响应式变量变化,Obx 自动刷新;后者需要使用 update 手动调用刷新
响应式变量,因为使用的是StreamBuilder
,会消耗一定资源
GetBuilder
内部实际上是对 StatefulWidget 的封装,所以占用资源极小
使用场景
一般来说,对于大多数场景都是可以使用响应式变量的
但是,在一个包含了大量对象的 List,都使用响应式变量,将生成大量的StreamBuilder
,必将对内存造成较大的压力,该情况下,就要考虑使用简单状态管理了
总的来说:推荐 GetBuilder 和 update 配合的写法
GetBuilder 内置回收 GetxController 的功能,能避免一些无法自动回收 GetxController 的坑爹问题
使用 Obx,相关变量定义初始化以及实体更新和常规写法不同,会对初次接触该框架的人,造成很大的困扰
getx 插件现已支持一键 Wrap Widget 生成 GetBuilder,可以一定程度上提升你的开发效率
跨页面交互
跨页面交互,在复杂的场景中,是非常重要的功能,来看看 GetX 怎么实现跨页面事件交互的
效果图
实现
页面一
常规代码
class GetJumpOneLogic extends GetxController {
var count = 0;
///跳转到跨页面
void toJumpTwo() {
Get.toNamed(RouteConfig.getJumpTwo, arguments: {'msg': '我是上个页面传递过来的数据'});
}
///跳转到跨页面
void increase() {
count = ++count;
update();
}
}
复制代码
class GetJumpOnePage extends StatelessWidget {
/// 使用Get.put()实例化你的类,使其对当下的所有子路由可用。
final GetJumpOneLogic logic = Get.put(GetJumpOneLogic());
@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: GetBuilder<GetJumpOneLogic>(
builder: (logic) {
return Text('跨页面-Two点击了 ${logic.count} 次',
style: TextStyle(fontSize: 30.0));
},
),
),
);
}
}
复制代码
页面二
这个页面就是重点了
class GetJumpTwoLogic extends GetxController {
var count = 0;
var msg = '';
@override
void onReady() {
var map = Get.arguments;
msg = map['msg'];
update();
super.onReady();
}
///跳转到跨页面
void increase() {
count = ++count;
update();
}
}
复制代码
class GetJumpTwoPage extends StatelessWidget {
final GetJumpOneLogic oneLogic = Get.find();
final GetJumpTwoLogic twoLogic = Get.put(GetJumpTwoLogic());
@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: [
//计数显示
GetBuilder<GetJumpTwoLogic>(
builder: (logic) {
return Text('跨页面-Two点击了 ${twoLogic.count} 次',
style: TextStyle(fontSize: 30.0));
},
),
//传递数据
GetBuilder<GetJumpTwoLogic>(
builder: (logic) {
return Text('传递的数据:${twoLogic.msg}',
style: TextStyle(fontSize: 30.0));
},
),
]),
),
);
}
}
复制代码
总结
GetX 这种的跨页面交互事件,真的是非常简单了,侵入性也非常的低,不需要在主入口配置什么,在复杂的业务场景下,这样简单的跨页面交互方式,就能实现很多事了
进阶吧!计数器
我们可能会遇到过很多复杂的业务场景,在复杂的业务场景下,单单某个模块关于变量的初始化操作可能就非常多,在这个时候,如果还将 state(状态层)和 logic(逻辑层)写在一起,维护起来可能看的比较晕,这里将状态层和逻辑层进行一个拆分,这样在稍微大一点的项目里使用 GetX,也能保证结构足够清晰了!
在这里就继续用计数器举例吧!
实现
此处需要划分三个结构了:state(状态层),logic(逻辑层),view(界面层)
这里使用插件生成下模板代码
Model:选择 Default(默认)
Function:useFolder(默认中)
来看下生成的模板代码
class CounterHighGetState {
CounterHighGetState() {
///Initialize variables
}
}
复制代码
import 'package:get/get.dart';
import 'state.dart';
class CounterHighGetLogic extends GetxController {
final state = CounterHighGetState();
}
复制代码
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 具体类型吧
详细可查看:声明响应式变量
class GetCounterHighState {
late int count;
GetCounterHighState() {
count = 0;
}
}
复制代码
class GetCounterHighLogic extends GetxController {
final state = GetCounterHighState();
///自增
void increase() {
state.count = ++state.count;
update();
}
}
复制代码
view
实际上 view 层,和之前的几乎没区别,区别的是把状态层给独立出来了
因为CounterHighGetLogic
被实例化,所以直接使用Get.find<CounterHighGetLogic>()
就能拿到刚刚实例化的逻辑层,然后拿到 state,使用单独的变量接收下
ok,此时:logic 只专注于触发事件交互,state 只专注数据
class GetCounterHighPage extends StatelessWidget {
final GetCounterHighLogic logic = Get.put(GetCounterHighLogic());
final GetCounterHighState state = Get.find<GetCounterHighLogic>().state;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('计数器-进阶版')),
body: Center(
child: GetBuilder<GetCounterHighLogic>(
builder: (logic) {
return Text(
'点击了 ${state.count} 次',
style: TextStyle(fontSize: 30.0),
);
},
),
),
floatingActionButton: FloatingActionButton(
onPressed: () => logic.increase(),
child: Icon(Icons.add),
),
);
}
}
复制代码
对比
看了上面的改造,屏幕前的你可能想吐槽了:坑比啊,之前简简单单的逻辑层,被拆成俩个,还搞得这么麻烦,你是猴子请来的逗比吗?
大家先别急着吐槽,当业务过于复杂,state 层,也是会维护很多东西的,让我们看看下面的一个小栗子,下面实例代码是不能直接运行的,想看详细运行代码,请查看项目:flutter_use
class MainState {
///选择index
late int selectedIndex;
///控制是否展开
late bool isUnfold;
///是否缩放
late bool isScale;
///分类按钮数据源
late List<BtnInfo> list;
///Navigation的item信息
late List<BtnInfo> itemList;
///PageView页面
late List<Widget> pageList;
late PageController pageController;
MainState() {
//初始化index
selectedIndex = 0;
//默认不展开
isUnfold = false;
//默认不缩放
isScale = false;
//PageView页面
pageList = [
KeepAlivePage(FunctionPage()),
KeepAlivePage(ExamplePage()),
KeepAlivePage(SettingPage()),
];
//item栏目
itemList = [
BtnInfo(
title: "功能",
icon: Icon(Icons.bubble_chart),
),
BtnInfo(
title: "范例",
icon: Icon(Icons.opacity),
),
BtnInfo(
title: "设置",
icon: Icon(Icons.settings),
),
];
//页面控制器
pageController = PageController();
}
}
复制代码
class MainLogic extends GetxController {
final state = MainState();
@override
void onInit() {
///初始化应用信息
InitConfig.initApp(Get.context);
super.onInit();
}
///切换tab
void switchTap(int index) {
state.selectedIndex = index;
state.pageController.jumpToPage(index);
update();
}
///是否展开侧边栏
void onUnfold(bool isUnfold) {
state.isUnfold = !state.isUnfold;
update();
}
///是否缩放
void onScale(bool isScale) {
state.isScale = !state.isScale;
update();
initWindow(scale: isScale ? 1.25 : 1.0);
}
}
复制代码
class MainPage extends StatelessWidget {
final MainLogic logic = Get.put(MainLogic());
final MainState state = Get.find<MainLogic>().state;
@override
Widget build(BuildContext context) {
return BaseScaffold(
backgroundColor: Colors.white,
body: Row(children: [
///侧边栏区域
GetBuilder<MainLogic>(
builder: (logic) {
return SideNavigation(
selectedIndex: state.selectedIndex,
isUnfold: state.isUnfold,
isScale: state.isScale,
sideItems: state.itemList,
//点击item
onItem: (index) => logic.switchTap(index),
//展开侧边栏
onUnfold: (isUnfold) => logic.onUnfold(isUnfold),
//缩放整体布局
onScale: (isScale) => logic.onScale(isScale),
);
},
),
///Expanded占满剩下的空间
Expanded(
child: PageView.builder(
physics: NeverScrollableScrollPhysics(),
itemCount: state.pageList.length,
itemBuilder: (context, index) => state.pageList[index],
controller: state.pageController,
),
)
]),
);
}
}
复制代码
从上面可以看出,state 层里面的状态已经较多了,当某些模块涉及到大量的:提交表单数据,跳转数据,展示数据等等,state 层的代码会相当的多,相信我,真的是非常多,一旦业务发生变更,还要经常维护修改,就蛋筒了
在复杂的业务下,将状态层(state)和业务逻辑层(logic)分开,绝对是个明智的举动
最后
路由管理
GetX 实现了一套用起来十分简单的路由管理,可以使用一种极其简单的方式导航,也可以使用命名路由导航
关于简单路由和命名路由的区别
GetStorage box = GetStorage();
GetMaterialApp(
getPages: [
GetPage(name: '/', page:(){
return box.hasData('token') ? Home() : Login();
})
]
)
复制代码
简单路由
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return GetMaterialApp(
home: MainPage(),
);
}
}
复制代码
//跳转新页面
Get.to(SomePage());
复制代码
命名路由导航
这里是推荐使用命名路由导航的方式
下面说明下,如何使用
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return GetMaterialApp(
initialRoute: RouteConfig.main,
getPages: RouteConfig.getPages,
);
}
}
复制代码
class RouteConfig {
///主页面
static final String main = "/";
///演示SmartDialog控件 喜马拉雅 dialog页面
static final String smartDialog = "/smartDialog";
static final String himalaya = "/himalaya";
static final String dialog = "/dialog";
///bloc计数器模块 Bloc跨页面传递事件
static final String blCubitCounterPage = "/blCubitCounterPage";
static final String blBlocCounterPage = "/blBlocCounterPage";
static final String cubitSpanOne = "/cubitSpanOne";
static final String cubitSpanTwo = "/cubitSpanOne/cubitSpanTwo";
static final String streamPage = "/streamPage";
static final String blCustomBuilderPage = "/blCustomBuilderPage";
static final String counterEasyCPage = "/counterEasyCPage";
///测试布局页面
static final String testLayout = "/testLayout";
///GetX 计数器 跨页面交互
static final String getCounterRx = "/getCounterRx";
static final String getCounterEasy = "/counterEasyGet";
static final String getCounterHigh = "/counterHighGet";
static final String getJumpOne = "/jumpOne";
static final String getJumpTwo = "/jumpOne/jumpTwo";
static final String getCounterBinding = "/getCounterBinding";
///Provider
static final String proEasyCounterPage = "/proEasyCounterPage";
static final String proHighCounterPage = "/proHighCounterPage";
static final String proSpanOnePage = "/proSpanOnePage";
static final String proSpanTwoPage = "/proSpanOnePage/proSpanTwoPage";
static final String testNotifierPage = "/testNotifierPage";
static final String customBuilderPage = "/customBuilderPage";
static final String counterEasyPPage = "/counterEasyPPage";
static final String counterGlobalEasyPPage = "/counterGlobalEasyPPage";
///别名映射页面
static final List<GetPage> getPages = [
GetPage(name: main, page: () => MainPage()),
GetPage(name: dialog, page: () => DialogPage()),
GetPage(name: blCubitCounterPage, page: () => BlCubitCounterPage()),
GetPage(name: blBlocCounterPage, page: () => BlBlocCounterPage()),
GetPage(name: streamPage, page: () => StreamPage()),
GetPage(name: blCustomBuilderPage, page: () => BlCustomBuilderPage()),
GetPage(name: counterEasyCPage, page: () => CounterEasyCPage()),
GetPage(name: testLayout, page: () => TestLayoutPage()),
GetPage(name: smartDialog, page: () => SmartDialogPage()),
GetPage(name: cubitSpanOne, page: () => CubitSpanOnePage()),
GetPage(name: cubitSpanTwo, page: () => CubitSpanTwoPage()),
GetPage(name: getCounterRx, page: () => GetCounterRxPage()),
GetPage(name: getCounterEasy, page: () => GetCounterEasyPage()),
GetPage(name: getCounterHigh, page: () => GetCounterHighPage()),
GetPage(name: getJumpOne, page: () => GetJumpOnePage()),
GetPage(name: getJumpTwo, page: () => GetJumpTwoPage()),
GetPage(
name: getCounterBinding,
page: () => GetCounterBindingPage(),
binding: GetCounterBinding(),
),
GetPage(name: himalaya, page: () => HimalayaPage()),
GetPage(name: proEasyCounterPage, page: () => ProEasyCounterPage()),
GetPage(name: proHighCounterPage, page: () => ProHighCounterPage()),
GetPage(name: proSpanOnePage, page: () => ProSpanOnePage()),
GetPage(name: proSpanTwoPage, page: () => ProSpanTwoPage()),
GetPage(name: testNotifierPage, page: () => TestNotifierPage()),
GetPage(name: customBuilderPage, page: () => CustomBuilderPage()),
GetPage(name: counterEasyPPage, page: () => CounterEasyPPage()),
GetPage(name: counterGlobalEasyPPage, page: () => CounterGlobalEasyPPage()),
];
}
复制代码
路由 API
请注意命名路由,只需要在 api 结尾加上Named
即可,举例:
详细 Api 介绍,下面内容来自 GetX 的 README 文档,进行了相关整理
Get.to(NextScreen());
Get.toNamed("/NextScreen");
复制代码
Get.off(NextScreen());
Get.offNamed("/NextScreen");
复制代码
Get.offAll(NextScreen());
Get.offAllNamed("/NextScreen");
复制代码
只要发送你想要的参数即可。Get 在这里接受任何东西,无论是一个字符串,一个 Map,一个 List,甚至一个类的实例。
Get.to(NextScreen(), arguments: 'Get is the best');
Get.toNamed("/NextScreen", arguments: 'Get is the best');
复制代码
在你的类或控制器上:
print(Get.arguments);
//print out: Get is the best
复制代码
var data = await Get.to(Payment());
var data = await Get.toNamed("/payment");
复制代码
Get.back(result: 'success');
// 并使用它,例:
if(data == 'success') madeAnything();
复制代码
// 默认的Flutter导航
Navigator.of(context).push(
context,
MaterialPageRoute(
builder: (BuildContext context) {
return HomePage();
},
),
);
// 使用Flutter语法获得,而不需要context。
navigator.push(
MaterialPageRoute(
builder: (_) {
return HomePage();
},
),
);
// get语法
Get.to(HomePage());
复制代码
动态网页链接
Get 提供高级动态 URL,就像在 Web 上一样。Web 开发者可能已经在 Flutter 上想要这个功能了,Get 也解决了这个问题。
Get.offAllNamed("/NextScreen?device=phone&id=354&name=Enzo");
复制代码
在你的 controller/bloc/stateful/stateless 类上:
print(Get.parameters['id']);
// out: 354
print(Get.parameters['name']);
// out: Enzo
复制代码
你也可以用 Get 轻松接收 NamedParameters。
void main() {
runApp(
GetMaterialApp(
initialRoute: '/',
getPages: [
GetPage(
name: '/',
page: () => MyHomePage(),
),
GetPage(
name: '/profile/',
page: () => MyProfile(),
),
//你可以为有参数的路由定义一个不同的页面,也可以为没有参数的路由定义一个不同的页面,但是你必须在不接收参数的路由上使用斜杠"/",就像上面说的那样。
GetPage(
name: '/profile/:user',
page: () => UserProfile(),
),
GetPage(
name: '/third',
page: () => Third(),
transition: Transition.cupertino
),
],
)
);
}
复制代码
发送命名路由数据
Get.toNamed("/profile/34954");
复制代码
在第二个页面上,通过参数获取数据
print(Get.parameters['user']);
// out: 34954
复制代码
现在,你需要做的就是使用 Get.toNamed()来导航你的命名路由,不需要任何 context(你可以直接从你的 BLoC 或 Controller 类中调用你的路由),当你的应用程序被编译到 web 时,你的路由将出现在 URL 中。
资源释放
关于 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()),
);
复制代码
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,好麻烦!
各位放心,这个问题,我也想到了,我特地在插件里面加上了自动回收的功能
来看下代码,default 模式一样可以的
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();
}
}
复制代码
class AutoDisposeLogic extends GetxController {
}
复制代码
一些问题汇总
如果使用中,有比较坑的问题,希望大家在评论里提出来,我会在这个栏目汇总一下
使用 Get.to(Get.toName)在系统 Dialog 上跳转页面,未关闭 Dialog;返回,再跳转,会出现无法跳转的情况
debug 了下 to 方法内部的运行,在系统 Dialog 上跳转,然后回来再跳转;会导致内部路由不匹配,从而 return,然后跳转失败
他的内部 api,给了一个解决方案
Get.to(XxxxPage(), preventDuplicates: false);
// 或者
Get.toNamed('xxx', preventDuplicates: false);
复制代码
最后
相关地址
系列文章
引流了,手动滑稽.jp
评论