前言
本文更新非常頻繁,最新內容請查看:最新內容---GetX代碼生成IDEA插件功能說明
本文章不是寫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
請注意:isPageView和autoDispose按鈕不能同時選中,他們倆都能解決PageView中存的問題,選擇其中一按鈕,另一按鈕會自動取消勾選
這算是一個非常有用的功能了
如果大家在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
pub:lint庫
這個功能,乍一看,大家估計都懵逼了;這要不是我寫的,我看了也懵逼啊
但是,這個功能,真是少部分強迫症患者的福音
因為getx作者,在demo項目里面,引入的lint庫,一些小伙伴可能也用了這個庫
lint是一個嚴格規則的代碼庫,對於代碼相應不規范的地方,會通過IDEA給與提示;對於我們很多認為合理的代碼,有時候可能也會給出相應的警告
- 在生成的模板代碼,有幾行就會在lint規則下被警告
- 這倆個注入代碼,都會自動推導出對應的類型;但是在lint規則下,會有黃色下划線警告
- 需要做這樣的調整,才能去掉警告
選中lintNorm按鈕,就會以下面這種形式生成模板代碼;所以說這個功能是強迫症患者福音。。。
對於用lint這種強規則的人,我表示:
pub:flutter_lints
最近Flutter在新建項目里面,默認加上了flutter_lints這個庫,這個庫的規則寬松很多,規則基本也是規范flutter的寫法
- 生成了模板代碼里面,會有一個警告
- 需要做如下調整,才能去掉警告
當你開啟lintNorm,也會幫你補上生成頁面的構造函數
template(切換模板命名)
場景
該功能提供了切換模板命名的操作
提供三套模板命名,只提供三套,不會再多增了
內部對持久化模塊進行了重構
- 不重構不行,增加了大量的持久化變量,還全部使用靜態變量着實不優雅
- 增加了數據類,來記錄大量重復的持久化數據
為什么要提供切換模板命名的功能?
當業務逐漸的復雜,很多時候,復雜的通用組件,也可以使用getx去封裝
- 但是使用插件生成對應模塊,view模塊的Widget可能還是為XxxPage
- 上面這種情況就不太友好了,你可能需要XxxComponent或者XxxWidget
- 雖然,可以在設置里重命名后綴名,但是這樣可能又對生成Page模塊產生影響
- 所以,這里提供三套模板命名切換,可以快速切換到你需要的自定命名方式
功能演示
插件窗口增加三套模板切換
- 選擇Template,提供三套切換模板命名:Page、Component、Custom
- 默認Page
三套模板命名都支持自定義修改
- 上面的切換,對應設置頁面的三套自定義通用后綴
- 設置頁面布局也重寫了,看起來更舒服一些,對整體空間利用率也更高了
示例
- mode選擇:Easy;Template選擇:Component
看下代碼
- view
class TestComponent extends StatelessWidget {
final logic = Get.put(TestLogic());
@override
Widget build(BuildContext context) {
return Container();
}
}
- logic
class TestLogic extends GetxController {
}
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
還有其它的一些快捷代碼,自行感受嘍~~
Setting功能
隨着功能的不斷增加,一些細分功能,需要在放在設置模塊里了;是時候寫下詳細說明了
lintNorm細分
開啟 lintNorm 功能時,生成的模板代碼是支持倆種庫的:lint 和 flutter_lints
現在對支持做了細分,大家可以隨意設置:支持其中一種庫或者都支持
版本更新說明
3.2.x
- 增加模板切換功能,大幅度優化內部持久化方式
- 重構設置頁面布局
- 支持 flutter_lints 規則
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;提之前:請先思考下,合不合理
最后
在不斷完善這個插件的時候,也是我不斷思考的一個過程,
感謝大家提的各種蛋痛的需求
能讓這個插件一點點的完善,以至於現在,,能真正的幫助靚仔們節省一點開發時間
系列文章 + 相關地址