前言
本文章不是写 getx 框架的应用,而且其代码生成 IDEA 插件的性能解说
我之前写过俩篇很长很长的 getx 文章
一篇入门应用:Flutter GetX 应用 — 简洁的魅力!
一篇原理深度分析:Flutter GetX 深度分析 | 咱们终将走出本人的路(万字图文)
鱼和渔都曾经交给大家了,就没必要去赘述了
同时,我也写了一个 getx 代码生成插件:getx_template,这个工具相当于钓鱼座椅(让你更难受的钓鱼或吃鱼?)吧!初期性能非常简略,就是生成单页面相应的模块代码,连个记忆选项性能都没有,基本上就是个塑料座椅的水平
- 然而随着大量
叼毛靓仔 给我提的各种需要,这个插件变的曾经有点简单了 - 尤其是波及 Select Function 模块,有些人可能都搞不懂选中的性能按钮是啥意思,就一通全副勾中。。。
- 所以,本凤雏想具体的,和各位卧龙谈谈这个工具方方面面的性能,心愿能帮忙各位节俭点开发工夫
兄弟们,我切实不想写水文;然而这个工具一个性能按钮,扭转的代码可能很少,其背地所蕴含的货色,可能须要大量的笔墨去形容,这边就对立的和各位彦祖于宴亦菲们,说道说道。
本文长期更新,如果想晓得插件每次具体更新内容,能够点进来看。
代码生成
- Plugins 里搜寻 getx 即可
比照
-
晚期代码生成弹框,可选性能比拟少,过后还不反对长久化贮存
- 淦,图标也丑
- 这是屡次欠缺后的性能抉择弹窗
鄙人是个十足的颜值党,这次最新版本的页面,我做了很多考量
-
首页随着各位靓仔提的各种需要,Select Function,从最后的俩个性能,减少到当初的七个性能
- 随着性能按钮的增多,在 dialog 上平铺下来,整个 dialog 的高度会变得相当的长
- 最重要的是:会让使用者,不明确 Function 外面的重点性能按钮是什么!
-
基于上述的思考,我搜索枯肠的想解决这个问题
-
计划一:我原本是想做一个折叠收纳区域,主要性能按钮放在折叠区域中
- 用 swing 一通写后,发现成果是真的丑,收纳的时候,高度计算也有问题:放弃
-
计划二:这个是我在翻 swing 控件的时候,发现了 JBTabbedPane 这个 tab 控件
- 成果简洁优雅,完爆折叠思路:采纳
-
-
这次我全面的改善了 dialog 布局问题
- 以前的整个 dialog 的长宽是写死的,在高尺寸的分辨率屏幕上会存在问题
- 这次,发现了 pack 办法的妙用(swing 菜狗的辛酸泪),全面重构的界面布局逻辑
- 这一次,在 48 寸的屏幕上,必定不会呈现上面这种状况了
尽管我没试,然而我对本人的代码有信念
模式抉择
这里提供俩种大的模式抉择:default,easy
来看下区别
default 模式
- view
class TestPage extends StatelessWidget {final logic = Get.put(TestLogic());
final state = Get.find<TestLogic>().state;
@override
Widget build(BuildContext context) {return Container();
}
}
- logic
class TestLogic extends GetxController {final TestState state = TestState();
}
- state
class TestState {TestState() {///Initialize variables}
}
Easy 模式
- view
class TestPage extends StatelessWidget {final logic = Get.put(TestLogic());
@override
Widget build(BuildContext context) {return Container();
}
}
- logic
class TestLogic extends GetxController {}
总结
下面的 default 模式和 easy 模式,从代码上看,还是能看出很显著的区别
- Default 模式比 Easy 模式多了一个 State 层
- State 是专门用来寄存页面变量和初始化相干变量数据的
我曾写过一个比较复杂模块
- 页面的变量达到几百个(波及到简单的表单提交),与用户的事件交互也有几十个
- 整个模块很多逻辑依附相干变量去标定,会初始化很多不同数据,State 层的代码简直快一千行
- 所以当业务逐步的简单,State 层并不薄,他撑持着整个模块的逻辑标定和扭转
除非是肉眼可见的业务极简模块,举荐应用 Easy 模块;其余的状况举荐应用 Default 模式
次要性能(main)
useFolder,usePrefix
useFolder 和 usePrefix 性能比较简单,这里就放在一起讲了
useFolder
本项性能是默认选中的,会在创立的多个文件外,创立一个文件夹,方便管理
usePrefix
一些小伙伴喜爱在各层:view,state,logic,前加上 module 名的前缀(小写 + 下划线)
这边也为大家提供了一个这样的可选性能
isPageView
这算是一个十分有用的性能了
如果大家在 PageView 中应用 getx,可能会发现,所有的子页面中的 GetXController,一下全被注入了!并不是切换到对应页面,注入对应的 GetXController!
PageView(children: [FunctionPage(),
ExamplePage(),
SettingPage(),])
剖析
咱们能够来剖析下,为什么会产生这种状况,来看下:FunctionPage
class FunctionPage extends StatelessWidget {final logic = Get.put(TestLogic());
final state = Get.find<TestLogic>().state;
@override
Widget build(BuildContext context) {return Container();
}
}
咱们注入的步骤,是放在类的成员变量作用域
- 这个作用域是在实例化构造函数之前起效的
- 所以咱们在增加被实例的 Page 的时候,成员变量的作用域间接被触发,GetXController 就被注入
PageView 触发机制
- PageView 触发被增加 Widget,是触发对应 Widget 的 build 办法
- 切换到哪个 Widget,就触发对应 Widget 的 build 办法
有了下面这层了解,就很容易解决 PageView 的问题了
- 只须要将注入过程放在 build 办法中
- 因为咱们应用的是 StatelessWidget,并不需要思考其刷新问题,只有它的父节点刷新,它才会被刷新
- GetX 存储对象应用的 putIfAbsent 办法,只会存储第一次注入对象,后续雷同类的对象间接疏忽,这能防止很多问题
解决
所以此性能只须要扭转 View 文件里,GetXController 的注入地位,其它文件不须要变动
addBinding
binding 是为了对立治理 GetXController,来看下 binding 和非 binding 的区别
非 Binding
- view
class TestOnePage extends StatelessWidget {final logic = Get.put(TestOneLogic());
@override
Widget build(BuildContext context) {return Container();
}
}
- logic
class TestOneLogic extends GetxController {}
Binding:须要配套 GetX 路由
- binding
class TestTwoBinding extends Bindings {
@override
void dependencies() {Get.lazyPut(() => TestTwoLogic());
}
}
- view
class TestTwoPage extends StatelessWidget {final logic = Get.find<TestTwoLogic>();
@override
Widget build(BuildContext context) {return Container();
}
}
- logic
class TestTwoLogic extends GetxController {}
- 须要在路由模块绑定下这个 binding
void main() {runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return GetMaterialApp(
initialRoute: RouteConfig.testOne,
getPages: RouteConfig.getPages,
);
}
}
class RouteConfig {
static const String testTwo = "/testTwo";
static final List<GetPage> getPages = [
GetPage(
name: testTwo,
page: () => TestTwoPage(),
binding: TestTwoBinding(),),
];
}
总结
binding 文件外面,应用的是懒注入:在应用了 find 办法的时候,才会真正的注入
所以在 view 外面,就须要将 put 改成 find 就行了,总结下
- 减少 binding 文件,应用懒注入
- view 文件,put 改成 find
- 须要在 getx 路由模块,对应的页面上绑定 binding 实例
主要性能(minor)
addLifecycle
这是个非常简单的性能,就放在主要性能 tab 下
一些小伙伴,logic 模块须要常常写 onReady 和 onClose 回调,懒得每次手写;所以在插件里增加了主动补上这俩个回调的性能
- 仅仅 Logic 文件有区别
autoDispose
该性能正如名字一样:主动开释 GetXController
实际上,这是个十分重要的性能,然而实现的太不优雅了,就把它移到了主要性能 tab 外面了
GetX 外部对回收 GetXController,做了很多解决,开释的操作是在 GetX 路由解决的;然而,业务多变简单,导致某些 GetXController 很难被框架主动开释,例如:
- PageView 的子页面
- 应用 GetX 封装的简单组件
- 不应用 GetX 路由
下面的这些状况都无奈主动回收 GetXController;为此,我在插件里,给出了一个解决方案,区别只在 view 文件
通用解决方案
- view
class TestTwoPage extends StatefulWidget {
@override
_TestTwoPageState createState() => _TestTwoPageState();
}
class _TestTwoPageState extends State<TestTwoPage> {final logic = Get.put(TestTwoLogic());
@override
Widget build(BuildContext context) {return Container();
}
@override
void dispose() {Get.delete<TestTwoLogic>();
super.dispose();}
}
- logic
class TestTwoLogic extends GetxController {}
下面这种计划,是根本都能解决回收 GetXController 问题(除非你手动开启保活 GetXController 的参数)
然而!这外面须要应用 StatefulWidget!多了很多代码!这太不优雅了!
优化解决方案
下面的是个通用解决办法,你不须要额定的引入任何其它的货色;然而这种计划用到了 StatefulWidget,代码多了一大坨,让我有点膈应
鄙人有着相当的强迫症,想了很久
-
原本是想 GetBuilder 写个回收逻辑,而后提个 PR 给作者
- 发现 getx 框架曾经做了这样的解决,然而,须要配套一个参数开启应用
- 在 GetBuilder 外面写了回收逻辑:对 Obx 刷新模块无奈起效,Obx 刷新控件外部无奈定位到 GetXController,所以无奈做回收操作
-
那只能从内部动手,我就写了一个通用控件,来对相应的 GetXController 进行回收
- 这个通用控件,我也给 getx 提了 PR,始终在审核
- 就算这个控件的 PR 通过了,集成到 getx 中,getx 低版本也无奈应用,没辙
- 这边我给出这个通用回收控件代码,各位能够自行复制到我的项目中应用
GetBindWidget
- 该控件能够回收单个 GetXController(bind 参数),能够加上对应 tag(tag 参数);也能够回收多个 GetXController(binds),能够加上多个 tag(tags 参数,请和 binds 一 一 对应;无 tag 的 GetXController 的,tag 能够写成空字符:””)
import 'package:flutter/material.dart';
import 'package:get/get.dart';
/// GetBindWidget can bind GetxController, and when the page is disposed,
/// it can automatically destroy the bound related GetXController
///
///
/// Sample:
///
/// class SampleController extends GetxController {
/// final String title = 'My Awesome View';
/// }
///
/// class SamplePage extends StatelessWidget {/// final controller = SampleController();
///
/// @override
/// Widget build(BuildContext context) {
/// return GetBindWidget(
/// bind: controller,
/// child: Container(),
/// );
/// }
/// }
class GetBindWidget extends StatefulWidget {
const GetBindWidget({
Key? key,
this.bind,
this.tag,
this.binds,
this.tags,
required this.child,
}) : assert(
binds == null || tags == null || binds.length == tags.length,
'The binds and tags arrays length should be equal\n'
'and the elements in the two arrays correspond one-to-one',
),
super(key: key);
final GetxController? bind;
final String? tag;
final List<GetxController>? binds;
final List<String>? tags;
final Widget child;
@override
_GetBindWidgetState createState() => _GetBindWidgetState();
}
class _GetBindWidgetState extends State<GetBindWidget> {
@override
Widget build(BuildContext context) {return widget.child;}
@override
void dispose() {_closeGetXController();
_closeGetXControllers();
super.dispose();}
///Close GetxController bound to the current page
void _closeGetXController() {if (widget.bind == null) {return;}
var key = widget.bind.runtimeType.toString() + (widget.tag ?? '');
GetInstance().delete(key: key);
}
///Batch close GetxController bound to the current page
void _closeGetXControllers() {if (widget.binds == null) {return;}
for (var i = 0; i < widget.binds!.length; i++) {var type = widget.binds![i].runtimeType.toString();
if (widget.tags == null) {GetInstance().delete(key: type);
} else {var key = type + (widget.tags?[i] ?? '');
GetInstance().delete(key: key);
}
}
}
}
- 应用十分的简略
/// 回收单个 GetXController
class TestPage extends StatelessWidget {final logic = Get.put(TestLogic());
@override
Widget build(BuildContext context) {
return GetBindWidget(
bind: logic,
child: Container(),);
}
}
/// 回收多个 GetXController
class TestPage extends StatelessWidget {final logicOne = Get.put(TestLogic(), tag: 'one');
final logicTwo = Get.put(TestLogic());
final logicThree = Get.put(TestLogic(), tag: 'three');
@override
Widget build(BuildContext context) {
return GetBindWidget(binds: [logicOne, logicTwo, logicThree],
tags: ['one', '','three'],
child: Container(),);
}
}
/// 回收日志
[GETX] Instance "TestLogic" has been created with tag "one"
[GETX] Instance "TestLogic" with tag "one" has been initialized
[GETX] Instance "TestLogic" has been created
[GETX] Instance "TestLogic" has been initialized
[GETX] Instance "TestLogic" has been created with tag "three"
[GETX] Instance "TestLogic" with tag "three" has been initialized
[GETX] "TestLogicone" onDelete() called
[GETX] "TestLogicone" deleted from memory
[GETX] "TestLogic" onDelete() called
[GETX] "TestLogic" deleted from memory
[GETX] "TestLogicthree" onDelete() called
[GETX] "TestLogicthree" deleted from memory
总结
对于下面的优化计划
- 就算你不应用 GetX 路由,你也能够很轻松的回收对应的 GetXController 了
- 这种回收形式在 GetBuilder 和 Obx 俩种刷新机制中,都是通用的
- 回收的机会:是以后页面被回收的时候
惟一麻烦的:须要你手动把 GetBindWidget 这个控件,引入到本人的我的项目中
LintNorm
这个性能,乍一看,大家预计都懵逼了;这要不是我写的,我看了也懵逼啊
然而,这个性能,真是少部分强迫症患者的福音
因为 getx 作者,在 demo 我的项目外面,引入的 lint 库,一些小伙伴可能也用了这个库
lint(pub):https://pub.dev/packages/lint
lint 是一个严格规定的代码库,对于代码相应不标准的中央,会通过 IDEA 给与提醒;对于咱们很多认为正当的代码,有时候可能也会给出相应的正告
-
在生成的模板代码,有几行就会在 lint 规定下被正告
- 这俩个注入代码,都会主动推导出对应的类型;然而在 lint 规定下,会有黄色下划线正告
- 须要做这样的调整,能力去掉正告
选中 lintNorm 按钮,就会以上面这种模式生成模板代码;所以说这个性能是强迫症患者福音。。。
对于用 lint 这种强规定的人,我示意:
通用后缀批改
- 反对通用后缀名批改
Wrap Widget
这是一个十分好用的性能
目前反对四种 Wrap Widget 类型:GetBuilder,GetBuilder(autoDispose),Obx,GetX
应用注意事项:鼠标点击在 Widget 上即可,而后按 alt+enter;请勿双击选中 Widget 名字
- GetBuilder
-
GetBuilder(Auto Dispose)
- assignId 设置为 true:GetBuilder 就会在页面被回收的时候,主动回收其指定泛型的 GetXController
-
Obx
- 说下这里为什么不必箭头符号,如果须要包裹的 Widget 十分长的话,应用箭头符号后,格式化后的代码并不参差
- 思考到这种状况,所以应用了 return 模式
-
GetX
- 这个组件我尽管不太喜爱用,然而指不定有喜爱用的小伙伴,就加上了
- 可选择性敞开
快捷代码生成
插件也为大家提供了,输出关键字生成快键代码片段的性能
请留神:关键字前缀为getx
路由模块
- getxroutepagemap
- getxroutename
- getxroutepage
- getxto,getxtoname
- getxoff,getxoffall,getxoffnamed,getxoffallnameed
依赖注入
- put
- find
- lazyPut
业务层
- GetxController
- getxfinal,getxfinal_
- getxget,getxget_
- getset,getset_
其它
- getsnakebar,getdialog,getbottomsheet
- getxbuilder,getxobx
- binding
还有其它的一些快捷代码,自行感触喽~~
版本更新阐明
3.1.x
-
显著的晋升整体页面布局
- 高尺寸屏幕不会再呈现坑比问题了
- 反对 lint 规定(lintNorm)
-
改善快捷代码提醒性能,“get”前缀改成为“getx”
- getx 为前缀,会让提醒代码被很多零碎代码吞没,改为 getx 之后就能够高深莫测了
3.0.x
- 我的项目代码从 Java 迁徙为 kotlin
- ModuleName 输出:首字母小写,外部会主动标为大写
- 减少大量快捷代码片段生成
- 插件我的项目逻辑重构,界面层和逻辑层拆散
-
Wrap Widget 减少:GetBuilder(Auto Dispose)
- 可主动回收对应的 GetXController
- 减少 PageView 解决方案
- 修复一些 bug
2.1.x
- 重大更新!
- 减少 Wrap Widget:GetBuilder,Obx,GetX
- 减少快捷代码片段生成
- 大幅度优化插件布局
- 减少欠缺生命周期回调性能(addLifecycle)
- 增加 binding 性能(addBinding)
1.5.x
- 减少记忆性能(记忆抉择的按钮)
- 增加 GetXController 主动回收性能(autoDispose)
- 反对批改通用后缀:view,logic,state
- 调整插件阐明,修复一些 bug
1.3.x
- 适配多版本的 IDEA(之前只适配了一个 IDEA 版本,坑)
- 增加插件 logo
- 减少一篇 getx 英文文章(机翻本人的博客文章)
- 改善插件形容
1.2
- 调整形容内容
1.1
- 修复减少前缀时,产生的导包异样问题
1.0
- 你能够应用本插件生成大量的 getx 框架代码
- 这能大大晋升你的效率
- 如果有任何问题,欢送给我提 issue;提之前:请先思考下,合不合理
最初
在不断完善这个插件的时候,也是我一直思考的一个过程,
感激大家提的各种蛋痛的需要
能让这个插件一点点的欠缺,以至于当初,,能真正的帮忙靓仔们节俭一点开发工夫
系列文章 + 相干地址
- 插件的 Github 地址:getx_template
- Flutter GetX 应用 — 简洁的魅力!
- Flutter GetX 深度分析 | 咱们终将走出本人的路(万字图文)