2

GetX代码生成IDEA插件,超详细功能讲解(透过现象看本质)

 3 years ago
source link: https://segmentfault.com/a/1190000040641518
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

GetX代码生成IDEA插件,超详细功能讲解(透过现象看本质)

本文章不是写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

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

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

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());
  }
}
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

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)

    • 说下这里为什么不用箭头符号,如果需要包裹的Widget非常长的话,使用箭头符号后,格式化后的代码并不整齐
    • 考虑到这种情况,所以使用了return形式

Obx

    • 这个组件我虽然不太喜欢用,但是指不定有喜欢用的小伙伴,就加上了

GetX

  • 可选择性关闭

image-20210802160631405

快捷代码生成

插件也为大家提供了,输入关键字生成快键代码片段的功能

请注意:关键字前缀为getx

  • getxroutepagemap

getxroutepagemap

  • getxroutename

getxroutename

  • getxroutepage

getxroutenpage

  • getxto,getxtoname

getxto

  • getxoff,getxoffall,getxoffnamed,getxoffallnameed

getxoff

getxput

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

系列文章 + 相关地址


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK