Flutter之GetX状态管理GetBuilder使用详解及源码分析

7,583 阅读10分钟

「这是我参与2022首次更文挑战的第8天,活动详情查看:2022首次更文挑战

GetBuilder 是 GetX 中状态管理的重要组价之一,本文将从使用方法介绍、原理分析深入了解 GetBuilder 的使用和实现原理。

作用

GetBuilder 是一个 Widget 组件, 在 GetX 的状态管理中,GetBuilder 的主要作用是结合 GetxController 实现界面数据的更新。当调用 GetxControllerupdate 方法时,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,
      ),
    );
  }
}

实现效果:

counter.gif

通过依赖注入将 CounterController 通过 CounterBinding 注入到 CounterPage 中,通过 GetBuilder 包裹 Text 显示 CounterController 中的 count 变量的值,点击加号按钮时调用 CounterController 的 increase 方法,执行数字的加一,然后调用 update 方法更新界面数据,从而实现计数器的功能。

关于依赖注入的相关使用见如下文章:

源码分析

前面介绍了 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 流程如下(点击放大查看):

Getx-GetBuilder-initState.drawio.png

_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 有两个监听方法:addListeneraddListenerId 方法,当 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 方法。

即通过 addListeneraddListenerId 将监听的 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,不为空则循环调用 refreshGrouprefreshrefreshGroup 源码:

@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 方法的实现是从对应监听器集合里取出所有监听器,也就是上面 addListeneraddListenerId 方法注册的监听,然后进行调用,区别是当传入 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) ,也是一个生命周期的回调方法。

然后判断 _isCreatorwidget.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 中调用