「这是我参与2022首次更文挑战的第8天,活动详情查看:2022首次更文挑战」
GetBuilder
是 GetX 中状态管理的重要组价之一,本文将从使用方法介绍、原理分析深入了解 GetBuilder
的使用和实现原理。
作用
GetBuilder
是一个 Widget
组件, 在 GetX 的状态管理中,GetBuilder
的主要作用是结合 GetxController
实现界面数据的更新。当调用 GetxController
的 update
方法时,GetBuilder
包裹的 Widget 就会刷新从而实现界面数据的更新。
使用
在前面的文章:Flutter应用框架搭建(一)GetX集成及使用详解 中介绍了 GetX 状态管理的使用,下面再通过代码看一下在状态管理中 GetBuilder
的使用方法,通过 GetBuilder 和 GetxController 实现官方计数器示例:
CounterBinding:
class CounterBinding extends Bindings {
@override
void dependencies() {
Get.lazyPut(() => CounterController());
}
}
CounterController:
class CounterController extends GetxController {
int count = 0;
void increase(){
count += 1;
update();
}
}
CounterPage:
class CounterPage extends StatelessWidget {
final controller = Get.find<CounterController>();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Counter"),
),
body: Center(
child: GetBuilder<CounterController>(builder: (logic) {
return Text("${controller.count}", style: const TextStyle(fontSize: 50),);
}),
),
floatingActionButton: FloatingActionButton(
child: const Icon(Icons.add),
onPressed: controller.increase,
),
);
}
}
实现效果:
通过依赖注入将 CounterController 通过 CounterBinding 注入到 CounterPage 中,通过 GetBuilder 包裹 Text 显示 CounterController 中的 count 变量的值,点击加号按钮时调用 CounterController 的 increase 方法,执行数字的加一,然后调用 update
方法更新界面数据,从而实现计数器的功能。
关于依赖注入的相关使用见如下文章:
- Flutter 通过源码一步一步剖析 Getx 依赖管理的实现
- Flutter之GetX依赖注入使用详解
- Flutter之GetX依赖注入tag使用详解
- Flutter之GetX依赖注入Bindings使用详解
源码分析
前面介绍了 GetBuilder 的简单使用,接下来将通过源码分析 GetBuilder 的实现原理以及通过源码进一步了解 GetBuilder 的更多用法。
GetBuilder
查看 GetBuilder 的源码:
class GetBuilder<T extends GetxController> extends StatefulWidget {
final GetControllerBuilder<T> builder;
final bool global;
final Object? id;
final String? tag;
final bool autoRemove;
final bool assignId;
final Object Function(T value)? filter;
final void Function(GetBuilderState<T> state)? initState,
dispose,
didChangeDependencies;
final void Function(GetBuilder oldWidget, GetBuilderState<T> state)?
didUpdateWidget;
final T? init;
const GetBuilder({
Key? key,
this.init,
this.global = true,
required this.builder,
this.autoRemove = true,
this.assignId = false,
this.initState,
this.filter,
this.tag,
this.dispose,
this.id,
this.didChangeDependencies,
this.didUpdateWidget,
}) : super(key: key);
@override
GetBuilderState<T> createState() => GetBuilderState<T>();
}
首先发现 GetBuilder 是继承自 StatefulWidget ,也就是在 Flutter 中常用的有状态的 Widget,并且有一个继承自 GetxController 泛型。其次 GetBuilder 除了上面使用到的 builder 参数以外,还有一系列的参数,关于参数的具体作用和使用将在下面源码分析过程中一一介绍。
GetBuilder 中没有做任何逻辑处理,只是接收了传入的参数,核心代码在 State 中, 也就是 GetBuilderState 中。
GetBuilderState
GetBuilderState 源码如下:
class GetBuilderState<T extends GetxController> extends State<GetBuilder<T>>
with GetStateUpdaterMixin {
T? controller;
bool? _isCreator = false;
VoidCallback? _remove;
Object? _filter;
@override
void initState() {...}
void _subscribeToController() {...}
void _filterUpdate() {...}
@override
void dispose() {...}
@override
void didChangeDependencies() {...}
@override
void didUpdateWidget(GetBuilder oldWidget) {...}
@override
Widget build(BuildContext context) {...}
}
GetBuilderState 中实现了 state 相关生命周期方法,同时还混入了 GetStateUpdaterMixin
,下面将一一通过源码分析每个方法的具体实现。
build
首先看一下 State 的最重要的方法, build 方法,也就是创建显示 Widget 的方法:
@override
Widget build(BuildContext context) {
return widget.builder(controller!);
}
实现很简单,调用 widget.builder
方法,即使用 GetBuilder 时传入的 builder 参数,也就是真正要在界面上展示的 Widget。调用 widget.builder
方法时传入了 controller 参数,controller 的值则在 initState
中实现。
initState
initState 方法源码如下:
@override
void initState() {
super.initState();
widget.initState?.call(this);
var isRegistered = GetInstance().isRegistered<T>(tag: widget.tag);
if (widget.global) {
if (isRegistered) {
if (GetInstance().isPrepared<T>(tag: widget.tag)) {
_isCreator = true;
} else {
_isCreator = false;
}
controller = GetInstance().find<T>(tag: widget.tag);
} else {
controller = widget.init;
_isCreator = true;
GetInstance().put<T>(controller!, tag: widget.tag);
}
} else {
controller = widget.init;
_isCreator = true;
controller?.onStart();
}
if (widget.filter != null) {
_filter = widget.filter!(controller!);
}
_subscribeToController();
}
首先调用了 widget.initState
,而 widget.initState
是 GetBuilder 构造方法参数,定义如下:
void Function(GetBuilderState<T> state)? initState
initState 是一个函数,即 GetBuilder 的 initState 参数是一个生命周期回调,在 State 的 initState 方法中调用。
然后调用了 GetInstance().isRegistered<T>(tag: widget.tag);
判断 Controller 的依赖是否注册,并传入了 tag
参数,这里的 tag 即为 GetBuilder 构造方法传入的 tag 参数,作用跟依赖管理的 tag 作用一致,用于区分注入的 Controller 依赖实例。
关于 isRegistered 方法及 tag 的原理见 Flutter之GetX依赖注入使用详解
接下来判断 widget.global
是否为 true,该参数通过字面意思理解为是否为全局, widget.global
为 true 时,判断 Controller 依赖是否注册,如果注册则调用 GetInstance().isPrepared<T>(tag: widget.tag)
判断依赖对象是否准备好,isPrepared
源码如下:
bool isPrepared<S>({String? tag}) {
final newKey = _getKey(S, tag);
final builder = _getDependency<S>(tag: tag, key: newKey);
if (builder == null) {
return false;
}
if (!builder.isInit) {
return true;
}
return false;
}
}
当依赖的 builder 为空或者 builder.isInit
为 true 即已经初始化时,返回 false,只有当依赖未初始化时返回 true。
再回到 initState
中,即当依赖关系不为空且还未初始化时 _isCreator
为 true ,关于 _isCreator
的使用见 dispose
方法。
然后通过 GetInstance().find<T>(tag: widget.tag);
获取依赖的 Controller 对象。
如果 isRegistered
为 false 即 Controller 未注册:
controller = widget.init;
_isCreator = true;
GetInstance().put<T>(controller!, tag: widget.tag);
将 widget.init
赋值给 controller,并将 _isCreator 赋值为 true,最后通过 GetInstance().put 将 controller 注册到依赖中。
这里用到了 widget.init
,看一下 widget.init
的定义:final T? init;
类型为泛型 T 即 Controller,所以 init 参数传入的是 Controller 的初始值。
widget.global
为 true 的分支就分析完了,接下来看看为 false 的分支代码:
controller = widget.init;
_isCreator = true;
controller?.onStart();
当 widget.global
为 false 时,将 widget.init
赋值给 controller,并将 _isCreator 赋值为 true,最后调用了 controller 的 onStart 方法。
initState
的以上代码主要是在获取 Controller 的值,init
参数的作用是在依赖的 Controller 未注册或者 global 为false时,将其值作为 GetBuilder 的 Controller 使用。
接下来判断 widget.filter
是如果不为空则调用 filter ,顾名思义是一个过滤器,filter 定义如下:
final Object Function(T value)? filter;
传入 Controller 参数返回一个 Object 值,具体作用后面使用时再进行分析。
initState
最后调用了 _subscribeToController
方法,即订阅 Controller 的更新。
initState
流程如下(点击放大查看):
_subscribeToController
_subscribeToController
订阅 Controller 更新消息,源码如下:
void _subscribeToController() {
_remove?.call();
_remove = (widget.id == null)
? controller?.addListener(
_filter != null ? _filterUpdate : getUpdate,
)
: controller?.addListenerId(
widget.id,
_filter != null ? _filterUpdate : getUpdate,
);
}
首先 _remove
不为空则进行调用,而 _remove
的值是 Controller.addListener 方法的返回值。
_subscribeToController
的核心代码就是调用 Controller 的添加监听的方法,Controller 有两个监听方法:addListener
和 addListenerId
方法,当 widget.id
为 null 时调用 addListener
否则调用 addListenerId
,Controller 中两个方法的源码如下:
@override
Disposer addListener(GetStateUpdate listener) {
assert(_debugAssertNotDisposed());
_updaters!.add(listener);
return () => _updaters!.remove(listener);
}
Disposer addListenerId(Object? key, GetStateUpdate listener) {
_updatersGroupIds![key] ??= <GetStateUpdate>[];
_updatersGroupIds![key]!.add(listener);
return () => _updatersGroupIds![key]!.remove(listener);
}
两个方法的区别是 addListenerId
需要传入一个 key,也就是前面的 id,两者都需要传入一个 GetStateUpdate
类型的参数,类型定义:
typedef GetStateUpdate = void Function();
是一个函数参数,即回调方法。
实现上 addListener
是将 listener 添加到 _updaters 的 List 中,而 addListenerId
是将 listener 添加到 _updatersGroupIds 的 Map 中,以传入的 key 为 Map 的 key,Map 的 value 为 List。同时两个方法最后返回的都是一个方法,方法实现是调用对应的 List 的 remove 方法。
即通过 addListener
和 addListenerId
将监听的 listener 添加到集合中,然后返回从集合中移除监听的方法。
调用添加监听方法时判断 _filter
是否为空,不为空则传入 _filterUpdate
方法,为空则传入 getUpdate
方法,而 _filter
的值是 widget.filter
调用返回的值。先看一下 _filterUpdate
方法源码:
void _filterUpdate() {
var newFilter = widget.filter!(controller!);
if (newFilter != _filter) {
_filter = newFilter;
getUpdate();
}
}
源码实现很简单,调用 widget.filter
获取新的值,然后与当前的 _filter
值进行比较,不相同则将 _filter
赋值为新的值,并调用 getUpdate
,为空则不处理。所以最终 _filterUpdate
还是调用的 getUpdate
方法。getUpdate
源码:
mixin GetStateUpdaterMixin<T extends StatefulWidget> on State<T> {
void getUpdate() {
if (mounted) setState(() {});
}
}
getUpdate
实现是在 GetStateUpdaterMixin
中,代码很简单,就是判断当前 Widget 是否挂载,如果挂载则调用 setState
刷新 Widget。从而实现当 Controller 中数据变化回调监听时刷新 GetBuilder 中的 Widget。
通过上面的代码了解了在 GetBuilder 中怎么为 Controller 中添加监听,并且监听到变化后刷新 Widget 的,那么 Controller 中是在什么时候调用监听回调的呢?
通过上面一开始的 GetBuilder 的使用知道,当调用 Controller 的 update 时会刷新 Widget,看一下 Controller 的 update 源码:
void update([List<Object>? ids, bool condition = true]) {
if (!condition) {
return;
}
if (ids == null) {
refresh();
} else {
for (final id in ids) {
refreshGroup(id);
}
}
}
}
update 可以传入一个 id 的集合,如果 id 集合为空则调用 refresh
,不为空则循环调用 refreshGroup
,refresh
、refreshGroup
源码:
@protected
void refresh() {
assert(_debugAssertNotDisposed());
_notifyUpdate();
}
@protected
void refreshGroup(Object id) {
assert(_debugAssertNotDisposed());
_notifyIdUpdate(id);
}
又调用了 _notifyUpdate
和 _notifyIdUpdate
方法:
void _notifyUpdate() {
for (var element in _updaters!) {
element!();
}
}
void _notifyIdUpdate(Object id) {
if (_updatersGroupIds!.containsKey(id)) {
final listGroup = _updatersGroupIds![id]!;
for (var item in listGroup) {
item();
}
}
}
发现最终调用 _notifyUpdate
和 _notifyIdUpdate
方法的实现是从对应监听器集合里取出所有监听器,也就是上面 addListener
和 addListenerId
方法注册的监听,然后进行调用,区别是当传入 id 时则只更新对应 id 的监听器,即当 update 中传入的 id 与 GetBuilder 中传入的 id 相同时才会刷新 Widget。
注意:通过上面源码分析可以看出来,有 id 和没有 id 存放监听器的容器是分开的,调用时也是分开的。所以当 update 没有传入 id 时,并不是更新所有的 GetBuilder ,而是更新没有 id 的 GetBuilder 中的 Widget。
dispose
接下来看看 dispose
方法,即当 Widget 从渲染树中移除的时候调用的实现:
@override
void dispose() {
super.dispose();
widget.dispose?.call(this);
if (_isCreator! || widget.assignId) {
if (widget.autoRemove && GetInstance().isRegistered<T>(tag: widget.tag)) {
GetInstance().delete<T>(tag: widget.tag);
}
}
_remove?.call();
controller = null;
_isCreator = null;
_remove = null;
_filter = null;
}
首先调用了 widget.dispose
, GetBuilder 的 dispose
定义为 void Function(GetBuilderState<T> state)
,也是一个生命周期的回调方法。
然后判断 _isCreator
和 widget.assignId
, 先看一下 _isCreator
,通过前面对 initState 的源码分析得知,当 Controller 的依赖关系的 builder 为空,或者当前 Controller 依赖已经被初始化,此时 _isCreator
才会为 false。
widget.assignId
是一个 bool 类型,默认为 false,根据字面意思理解为是否分配 id ?但是查看源码跟参数的 id 并没有关系,而且该参数也只有在 dispose
中使用到。
然后判断 widget.autoRemove
是否为 true,autoRemove
也是一个 bool 类型,是否自动移除,默认为 true。接着判断 Controller 是否注册。
经过一系列的条件判断,最终执行 GetInstance().delete
方法,即移除 GetBuilder 中 Controller 在依赖管理中的依赖关系。
当 assignId
设置为 true 且 autoRemove
为 true 时,如果 Controller 已经注册,那么在 GetBuilder 从渲染树中移除的时候就会将 Controller 从依赖中移除。
下一步调用 _remove?.call()
,根据前面 _subscribeToController
及 Controller 源码的分析知道,_remove
的作用是移除在 Controller 中的监听。
最后将 controller
、_isCreator
、_remove
、 _filter
值赋值为 null。
didUpdateWidget
父节点调用 setState
时会触发子节点的 didUpdateWidget
方法:
@override
void didUpdateWidget(GetBuilder oldWidget) {
super.didUpdateWidget(oldWidget as GetBuilder<T>);
// to avoid conflicts when modifying a "grouped" id list.
if (oldWidget.id != widget.id) {
_subscribeToController();
}
widget.didUpdateWidget?.call(oldWidget, this);
}
判断旧的 GetBuilder 的 id 与 当前 id 是否相同,不相同则重新订阅 Controller;最后调用 widget.didUpdateWidget
。
didChangeDependencies
widget树中,若节点的父级结构中的层级
或 父级结构中的任一节点的widget类型
有变化时调用
@override
void didChangeDependencies() {
super.didChangeDependencies();
widget.didChangeDependencies?.call(this);
}
未做其他操作,只调用了 widget.didChangeDependencies
。
总结
通过对 GetBuilder 的源码分析,基本了解了 GetBuilder 各个参数的作用和实现原理。
GetBuilder 参数作用总结如下:
- builder: Widget 构建器,创建界面显示的 Widget
-
init: 初始化 Controller 值,当
global
为 false 时使用该值作为 Controller,当global
为 true 时且 Controller 未注册依赖,则将 init 的值注入依赖使用。 - global: 是否全局,作用于 Controller 初始化中,与 init 结合使用
-
autoRemove: 是否自动移除 Controller 依赖,结合
assignId
一起使用 - assignId: 为 true 时结合 autoRemove 使用会自动移除 Controller 依赖关系
- filter: 过滤器,通过返回值过滤是否需要刷新,返回值变化时才会刷新界面
- tag: Controller 依赖注入的 tag,根据 tag 获取 Controller 实例
- id: 刷新标识,结合 Controller 的 update 使用,可以刷新指定 GetBuilder 控件内的 Widget
-
initState: 回调函数,生命周期
initState
方法中调用 -
dispose: 回调函数,生命周期
dispose
中调用 -
didUpdateWidget: 回调函数,生命周期
didUpdateWidget
中调用 -
didChangeDependencies: 回调函数,生命周期
didChangeDependencies
中调用