⼤佬⼿把⼿教你优雅地进⾏Flutter开发(GetX值得⼀试)
推开程序员的⼤门时,前⾯展现的是⽆⽌境的学习旅途。
本次是Flutter开发列车,⼤家上车后记得系好安全带。
原⽂地址:
前⾔
使⽤Bloc的时候,有⼀个让我⾄今为⽌⼗分在意的问题,⽆法真正的跨页⾯交互!在反复的查阅官⽅⽂档后,使⽤⼀个全局Bloc的⽅式,实现了“伪”跨页⾯交互,详细可查看:flutter_bloc使⽤解析;fish_redux的⼴播机制是可以⽐较完美的实现跨页⾯交互的,我也写了⼀篇近万字介绍如何使⽤该框架:fish_redux使⽤详解,对于中⼩型项⽬使⽤
fish_redux,这会⼀定程度上降低开发效率,最近尝试了GetX相关功能,解决了我的相当⼀部分痛点。
把整篇⽂章写完后,我马上把⾃⼰的⼀个demo⾥⾯所有Bloc代码全⽤GetX替换,且去掉了Fluro框架;感觉⽤Getx虽然会省掉⼤量的模板代码,但还是有些重复⼯作:创建⽂件夹,创建⼏个必备⽂件,写那些必须要写的初始化代码和类;略微繁琐,为了对得起GetX给我开发带来的巨⼤便利,我就花了⼀些时间,给它写了⼀个插件!上⾯这重复的代码,⽂件,⽂件夹统统能⼀键⽣成!
GetX相关优势
build刷新⽅法极度简单!
getx:Obx(() => Text())
这是我⾮常⾮常在意的⼀个⽅⾯,因为bloc的build刷新组件⽅法要传俩个泛型,加上build⽅法⾥⾯的俩个参数,导致⼀个build⽅法如果不使⽤箭头⽅法简写,⼏乎占四五⾏,⽤起来实在蛋筒,导致我平时开发直接把BlocBuilder⽅法直接写在页⾯顶层(不提倡写顶层),⼀个页⾯只⽤写⼀次了,不⽤定点到处写BlocBuilder了,⼿动滑稽.jpg
跨页⾯交互
这绝对是GetX的⼀个优点!对于复杂的⽣产环境,跨页⾯交互的场景,实在太常见了,GetX的跨页⾯交互,⼏乎和fish_redux⼀样简单,爱了爱了
路由管理
是的,getx内部实现了路由管理,⽽且⽤起来,那叫⼀个简单!bloc没实现路由管理,这让我不得不去⼀个star量⾼的路由管理框架,就选择了fluro,但是让我不得不说,这个fluro⽤起来真的叫⼀个折磨⼈,每次新建⼀个页⾯,最让我抗拒的就是去写fluro路由代码,横跨⼏个⽂件来回写,真是肝疼。
GetX实现了动态路由传参,也就是说直接在命名路由上拼参数,然后能拿到这些拼在路由上的参数,也就是说⽤flutter写H5,直接能通过Url传值(fluro也能做到),OMG!可以⽆脑舍弃复杂的fluro了。
实现了全局BuildContext
国际化,主题实现
上⾯单单是build简写的优势,就会让我考虑是否去使⽤了,⽽且还能实现跨页⾯的功能,这还考虑啥,开搞!接下来将全⾯的介绍GetX的使⽤,⽂章也不分篇⽔阅读量了,⼒求⼀⽂写清楚,⽅便⼤家随时查阅。
准备
引⼊
⾸先导⼊GetX的插件
# getx 状态管理框架 pub.flutter-io/packages/get
get: ^3.24.0
flutter开发app
GetX地址Github:
Pub:
主⼊⼝配置
只需要将MaterialApp改成GetMaterialApp即可。
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模式下开发插件,把模板
说明
插件效果
看下插件使⽤的效果图吧,样式参考了fish_redux插件样式
有⼀些可选择的功能,所以做成多按钮的样式,⼤家可以按照⾃⼰的需求进⾏操作
说下插件的功能含义
Model:⽣成GetX的模式,
Default:默认模式,⽣成三个⽂件:state,logic,view
Easy:简单模式,⽣成俩个⽂件:logic,view
Function:功能选择
useFolder:使⽤⽂件,选择后会⽣成⽂件夹,⼤驼峰命名⾃动转换为:⼩写+下划线
usePrefix:使⽤前缀,⽣成的⽂件前加上前缀,前缀为:⼤驼峰命名⾃动转换为:⼩写+下划线
Module Name:模块的名称,请使⽤⼤驼峰命名
安装
在设置⾥⾯选择:Plugins ---> 输⼊“getx”搜索 ---> 选择名字为:“GeX” ---> 然后安装 ---> 最后记得点击下“Apply”
如果在使⽤该插件的过程中有什么问题,请在该项⽬的github上给我提issue,我看到后,会尽快处理
计数器
效果图
实现
⾸页,当然是实现⼀个简单的计数器,来看GetX怎么将逻辑层和界⾯层解耦的。来使⽤插件⽣成下简单⽂件:
模式选择:Easy
功能选择:useFolder
来看下⽣成的默认代码,默认代码⼗分简单,详细解释放在俩种状态管理⾥。
logic
import 'package:get/get.dart';
class CounterGetLogic extends GetxController {
}
view
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();
}
}
响应式状态管理
当数据源变化时,将⾃动执⾏刷新组件的⽅法。logic层因为是处理页⾯逻辑的,加上Controller单词过长,也防⽌和Flutter⾃带的⼀些控件控制器弄混,所以该层⽤logic结尾,这⾥就定为了logic层,当然这点随个⼈意向,写Event,Controller均可。这⾥变量数值后写.obs操作,是说明定义了该变量为响应式变量,当该变量数值变化时,页⾯的刷新⽅法将⾃动刷新;基础类型,List,类都可以加.obs,使其变成响应式变量
class CounterGetLogic extends GetxController {
var count = 0.obs;
///⾃增
void increase() => ++count;
}
view层
这地⽅获取到Logic层的实例后,就可进⾏操作了,⼤家可能会想:WTF,为什么实例的操作放在build⽅法⾥?逗我呢?---------  实际不然,stl是⽆状态组件,说明了他就不会被⼆次重组,所以实例操作只会被执⾏⼀次,⽽且Obx()⽅法是可以刷新组件的,完美解决刷新组件问题了。
class CounterGetPage extends StatelessWidget {
return Scaffold(
appBar: AppBar(title: const Text('GetX计数器')),
body: Center(
child: Obx(
() => Text('点击了 ${unt.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('点击了 ${unt.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:这是⼀个极其轻巧的状态管理器,占⽤资源极少!
logic:先来看看logic层
class CounterEasyGetLogic extends GetxController {
var count = 0;
void increase() {
++count;
update();
}
}
view
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(
'点击了 ${unt} 次',
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所有周期回调,可以在相应回调内做⼀些操作
总结
分析
GetBuilder内部实际上是对StatefulWidget的封装,所以占⽤资源极⼩
响应式变量,因为使⽤的是StreamBuilder,会消耗⼀定资源
使⽤场景
⼀般来说,对于⼤多数场景都是可以使⽤响应式变量的
但是,在⼀个包含了⼤量对象的List,都使⽤响应式变量,将⽣成⼤量的StreamBuilder,必将对内存造成较⼤的压⼒,该情况下,就要考虑使⽤简单状态管理了
跨页⾯交互
跨页⾯交互,在复杂的场景中,是⾮常重要的功能,来看看GetX怎么实现跨页⾯事件交互的。
实现
页⾯⼀,常规代码。
logic
这⾥的⾃增事件,是供其它页⾯调⽤的,该页⾯本⾝没使⽤。
class JumpOneLogic extends GetxController {
var count = 0.obs;
///跳转到跨页⾯
void toJumpTwo() {
}
///跳转到跨页⾯
void increase() => count++;
} </pre>
**view**此处就⼀个显⽰⽂字和跳转功能。
<pre >class JumpOnePage extends StatelessWidget {  /// 使⽤Get.put()实例化你的类,使其对当下的所有⼦路由可⽤。
final JumpOneLogic logic = Get.put(JumpOneLogic());
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(title: Text('跨页⾯-One')),
floatingActionButton: FloatingActionButton(
onPressed: () => JumpTwo(),
child: const Icon(Icons.arrow_forward_outlined),
),
body: Center(
child: Obx(
() => Text('跨页⾯-Two点击了 ${unt.value} 次',
style: TextStyle(fontSize: 30.0)),
),
),
);
}
}
页⾯⼆,这个页⾯就是重点了。
logic
将演⽰怎么调⽤前⼀个页⾯的事件
怎么接收上个页⾯数据
请注意,GetxController包含⽐较完整的⽣命周期回调,可以在onInit()接受传递的数据;如果接收的数据需要刷新到界⾯上,请在onReady回调⾥⾯接收数据操
作,onReady是在addPostFrameCallback回调中调⽤,刷新数据的操作在onReady进⾏,能保证界⾯是初始加载完毕后才进⾏页⾯刷新操作的
class JumpTwoLogic extends GetxController {
var count = 0.obs;
var msg = ''.obs;
@override
void onReady() {
var map = Get.arguments;
msg.value = map['msg'];