Get源码阅读之动态绑定

425 阅读3分钟

实现动态绑定可以用Obx和ObxValue。

class Obx extends ObxWidget {
  final WidgetCallback builder;

  const Obx(this.builder);

  @override
  Widget build() => builder();
}

class ObxValue<T extends RxInterface> extends ObxWidget {
  final Widget Function(T) builder;
  final T data;

  const ObxValue(this.builder, this.data, {Key? key}) : super(key: key);

  @override
  Widget build() => builder(data);
}

Obx和ObxValue的区别是ObxValue的监听对象可以外部传入,可以避免强引用。

Obx和ObxValue都是抽象类ObxWidget的实现,ObxWidget是一个StatefulWidget,动态绑定的核心实现在_ObxState中

class _ObxState extends State<ObxWidget> {
  RxInterface? _observer;
  late StreamSubscription subs;

  _ObxState() {
    _observer = RxNotifier();
  }

  @override
  void initState() {
    subs = _observer!.listen(_updateTree, cancelOnError: false);
    super.initState();
  }

  void _updateTree(_) {
    if (mounted) {
      setState(() {});
    }
  }

  @override
  void dispose() {
    subs.cancel();
    _observer!.close();
    super.dispose();
  }

  Widget get notifyChilds {
    final observer = RxInterface.proxy;
    RxInterface.proxy = _observer;
    final result = widget.build();
    if (!_observer!.canUpdate) {
      throw """
      [Get] the improper use of a GetX has been detected. 
      You should only use GetX or Obx for the specific widget that will be updated.
      If you are seeing this error, you probably did not insert any observable variables into GetX/Obx 
      or insert them outside the scope that GetX considers suitable for an update 
      (example: GetX => HeavyWidget => variableObservable).
      If you need to update a parent widget and a child widget, wrap each one in an Obx/GetX.
      """;
    }
    RxInterface.proxy = observer;
    return result;
  }

  @override
  Widget build(BuildContext context) => notifyChilds;
}

先介绍下RxNotifier,RxNotifier继承了RxInterface和NotifyManager。

核心实现在NotifyManager中,NotifyManager有一个GetStream类型的subject对象,它用的是观察者模式,当数据变化或者发生异常时会通知被观察者。

_subscriptions维护了NotifyManager监听的所有GetStream(在ObxWidget中,当他们发生变化时候调用subject更新界面),NotifyManager关闭的时候取消监听。

mixin NotifyManager<T> {
  GetStream<T> subject = GetStream<T>();
  final _subscriptions = <GetStream, List<StreamSubscription>>{};

  bool get canUpdate => _subscriptions.isNotEmpty;

  /// This is an internal method.
  /// Subscribe to changes on the inner stream.
  void addListener(GetStream<T> rxGetx) {
    if (!_subscriptions.containsKey(rxGetx)) {
      final subs = rxGetx.listen((data) {
        if (!subject.isClosed) subject.add(data);
      });
      final listSubscriptions =
          _subscriptions[rxGetx] ??= <StreamSubscription>[];
      listSubscriptions.add(subs);
    }
  }

  StreamSubscription<T> listen(
    void Function(T) onData, {
    Function? onError,
    void Function()? onDone,
    bool? cancelOnError,
  }) =>
      subject.listen(
        onData,
        onError: onError,
        onDone: onDone,
        cancelOnError: cancelOnError ?? false,
      );

  /// Closes the subscriptions for this Rx, releasing the resources.
  void close() {
    _subscriptions.forEach((getStream, _subscriptions) {
      for (final subscription in _subscriptions) {
        subscription.cancel();
      }
    });

    _subscriptions.clear();
    subject.close();
  }
}

下面介绍下如何注册监听的,在_ObxState的build时,把当前页面的observer存入RxInterface.proxy的静态变量中,然后通过widget.build构建widget,此时会调用可观察对象(RxInt,RxString...)的get value,在get value中获取RxInterface.proxy,调用NotifyManager的Listener方法,实现注册。

class _ObxState extends State<ObxWidget> {

  ...
  
  Widget get notifyChilds {
    final observer = RxInterface.proxy;
    RxInterface.proxy = _observer;
    final result = widget.build();
    if (!_observer!.canUpdate) {
      throw """
      [Get] the improper use of a GetX has been detected. 
      You should only use GetX or Obx for the specific widget that will be updated.
      If you are seeing this error, you probably did not insert any observable variables into GetX/Obx 
      or insert them outside the scope that GetX considers suitable for an update 
      (example: GetX => HeavyWidget => variableObservable).
      If you need to update a parent widget and a child widget, wrap each one in an Obx/GetX.
      """;
    }
    RxInterface.proxy = observer;
    return result;
  }

  @override
  Widget build(BuildContext context) => notifyChilds;
}

可观察对象get value的逻辑在RxObjectMixin中。

mixin RxObjectMixin<T> on NotifyManager<T> {

  ...
  
  set value(T val) {
    if (subject.isClosed) return;
    if (_value == val && !firstRebuild) return;
    firstRebuild = false;
    _value = val;

    subject.add(_value);
  }

  T get value {
    if (RxInterface.proxy != null) {
      RxInterface.proxy!.addListener(subject);
    }
    return _value;
  }
}

总结

动态绑定使用的是观察者模式,先通过extension关键字扩展int、string、double、bool、map、list、set数据类型添加get obs方法,在get obs方法中返回对应数据类型的Rx对象(可观察,底层是GetStream)。在ObxWidget中的build时,将当前widget的NotifyManager设置到proxy(全局静态变量)中,然后把调用build,当子widget使用到Rx对象时,就会调用到get value方法,在get value方法中往proxy addListener。这样就收集到当前ObxWidget下所有Rx对象了。当Rx对象发生变化时,NotifyManager的监听被执行,进而NotifyManager把事件转发给subject,subject持有ObxWidget的setState,刷新界面。

Main.png