GetX程式碼生成IDEA外掛,超詳細功能講解(透過現象看本質)

xdd666發表於2021-09-07

前言

本文章不是寫getx框架的使用,而且其程式碼生成IDEA外掛的功能講解

我之前寫過倆篇很長很長的getx文章

一篇入門使用:Flutter GetX使用---簡潔的魅力!

一篇原理深度剖析:Flutter GetX深度剖析 | 我們終將走出自己的路(萬字圖文)

魚和漁都已經交給大家了,就沒必要去贅述了

img

同時,我也寫了一個getx程式碼生成外掛:getx_template,這個工具相當於釣魚座椅(讓你更舒服的釣魚或吃魚?)吧!初期功能十分簡單,就是生成單頁面相應的模組程式碼,連個記憶選項功能都沒有,基本上就是個塑料座椅的程度

  • 但是隨著大量 叼毛 靚仔 給我提的各種需求,這個外掛變的已經有點複雜了
  • 尤其是涉及Select Function模組,有些人可能都搞不懂選中的功能按鈕是啥意思,就一通全部勾中。。。
  • 所以,本鳳雛想詳細的,和各位臥龍談談這個工具方方面面的功能,希望能幫助各位節省點開發時間

兄弟們,我實在不想寫水文;但是這個工具一個功能按鈕,改變的程式碼可能很少,其背後所蘊含的東西,可能需要大量的筆墨去描述,這邊就統一的和各位彥祖於宴亦菲們,說道說道。

img

本文長期更新,如果想知道外掛每次詳細更新內容,可以點進來看。

程式碼生成

  • Plugins裡搜尋getx即可

image-20210906222922384

對比

  • 早期程式碼生成彈框,可選功能比較少,當時還不支援持久化儲存
    • 淦,圖示也醜

20210130182809

  • 這是多次完善後的功能選擇彈窗

getx_new

鄙人是個十足的顏值黨,這次最新版本的頁面,我做了很多考量

  • 首頁隨著各位靚仔提的各種需求,Select Function,從最初的倆個功能,增加到現在的七個功能

    • 隨著功能按鈕的增多,在dialog上平鋪下來,整個dialog的高度會變得相當的長
    • 最重要的是:會讓使用者,不明確Function裡面的重點功能按鈕是什麼!
  • 基於上述的思考,我絞盡腦汁的想解決這個問題

    • 方案一:我本來是想做一個摺疊收納區域,次要功能按鈕放在摺疊區域中
      • 用swing一通寫後,發現效果是真的醜,收納的時候,高度計算也有問題:放棄
    • 方案二:這個是我在翻swing控制元件的時候,發現了 JBTabbedPane 這個tab控制元件
      • 效果簡潔優雅,完爆摺疊思路:採用
  • 這次我全面的改善了dialog佈局問題

    • 以前的整個dialog的長寬是寫死的,在高尺寸的解析度螢幕上會存在問題
    • 這次,發現了pack方法的妙用(swing菜狗的辛酸淚),全面重構的介面佈局邏輯
  • 這一次,在48寸的螢幕上,肯定不會出現下面這種情況了

圖片

雖然我沒試,但是我對自己的程式碼有信心

img

模式選擇

這裡提供倆種大的模式選擇:default,easy

來看下區別

default模式

image-20210905174923566

  • 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模式

image-20210905175435395

  • 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

本項功能是預設選中的,會在建立的多個檔案外,建立一個資料夾,方便管理

useFolder

usePrefix

一些小夥伴喜歡在各層:view,state,logic,前加上module名的字首(小寫+下劃線)

這邊也為大家提供了一個這樣的可選功能

usePrefix

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的注入位置,其它檔案不需要變動

isPageView

addBinding

binding是為了統一管理GetXController,來看下binding和非binding的區別

addBinding

非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檔案有區別

addLifecycle

autoDispose

該功能正如名字一樣:自動釋放GetXController

實際上,這是個非常重要的功能,但是實現的太不優雅了,就把它移到了次要功能tab裡面了

GetX內部對回收GetXController,做了很多處理,釋放的操作是在GetX路由處理的;但是,業務多變複雜,導致某些GetXController很難被框架自動釋放,例如:

  • PageView的子頁面
  • 使用GetX封裝的複雜元件
  • 不使用GetX路由

上面的這些情況都無法自動回收GetXController;為此,我在外掛裡,給出了一個解決方案,區別只在view檔案

通用解決方案

autoDispose

  • 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這個控制元件,引入到自己的專案中

img

LintNorm

這個功能,乍一看,大家估計都懵逼了;這要不是我寫的,我看了也懵逼啊

img

但是,這個功能,真是少部分強迫症患者的福音

因為getx作者,在demo專案裡面,引入的lint庫,一些小夥伴可能也用了這個庫

lint(pub):https://pub.dev/packages/lint

lint是一個嚴格規則的程式碼庫,對於程式碼相應不規範的地方,會通過IDEA給與提示;對於我們很多認為合理的程式碼,有時候可能也會給出相應的警告

  • 在生成的模板程式碼,有幾行就會在lint規則下被警告
    • 這倆個注入程式碼,都會自動推匯出對應的型別;但是在lint規則下,會有黃色下劃線警告

image-20210906174811659

  • 需要做這樣的調整,才能去掉警告

image-20210906175204796

選中lintNorm按鈕,就會以下面這種形式生成模板程式碼;所以說這個功能是強迫症患者福音。。。

對於用lint這種強規則的人,我表示:

img

通用字尾修改

  • 支援通用字尾名修改

image-20210906231011995

Wrap Widget

這是一個非常好用的功能

目前支援四種Wrap Widget型別:GetBuilder,GetBuilder(autoDispose),Obx,GetX

使用注意事項:滑鼠點選在Widget上即可,然後按 alt+enter;請勿雙擊選中Widget名字

  • GetBuilder

GetBuilder

  • GetBuilder(Auto Dispose)
    • assignId設定為true:GetBuilder就會在頁面被回收的時候,自動回收其指定泛型的GetXController

GetBuilder(Auto Dispose)

  • Obx
    • 說下這裡為什麼不用箭頭符號,如果需要包裹的Widget非常長的話,使用箭頭符號後,格式化後的程式碼並不整齊
    • 考慮到這種情況,所以使用了return形式

Obx

  • GetX
    • 這個元件我雖然不太喜歡用,但是指不定有喜歡用的小夥伴,就加上了

GetX

  • 可選擇性關閉

image-20210802160631405

快捷程式碼生成

外掛也為大家提供了,輸入關鍵字生成快鍵程式碼片段的功能

請注意:關鍵字字首為getx

路由模組

  • getxroutepagemap

getxroutepagemap

  • getxroutename

getxroutename

  • getxroutepage

getxroutenpage

  • getxto,getxtoname

getxto

  • getxoff,getxoffall,getxoffnamed,getxoffallnameed

getxoff

依賴注入

  • put

getxput

  • find

getxfind

  • lazyPut

getxlazyput

業務層

  • GetxController

getxcontroller

  • getxfinal,getxfinal_

getxfinal

  • getxget,getxget_

getxget

  • getset,getset_

getset

其它

  • getsnakebar,getdialog,getbottomsheet

getxdialog

  • getxbuilder,getxobx

getxobx

  • binding

getxbinding

還有其它的一些快捷程式碼,自行感受嘍~~

版本更新說明

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;提之前:請先思考下,合不合理

最後

在不斷完善這個外掛的時候,也是我不斷思考的一個過程,

感謝大家提的各種蛋痛的需求

img

能讓這個外掛一點點的完善,以至於現在,,能真正的幫助靚仔們節省一點開發時間

img

系列文章 + 相關地址

相關文章