我正在参与创作者训练营第4期,点击了解活动详情,一起学习吧!
Obx 是 GetX 库中结合 Rx 可观察者对象实现状态管理的 Widget 组件,使其在 Flutter 开发中实现状态管理变得更加的快捷方便,让代码变得更加的简洁。
关于 GetX 的更多使用,请参考以下文章
Flutter应用框架搭建(一)GetX集成及使用详解
Flutter 通过源码一步一步剖析 Getx 依赖管理的实现
Flutter之GetX依赖注入使用详解
Flutter之GetX依赖注入tag使用详解
Flutter之GetX依赖注入Bindings使用详解
Flutter之GetX状态管理GetBuilder使用详解及源码分析
使用
先从一个简单的实现 Flutter 官方计数器效果的例子,感受一下是 Obx 是如何使用的:
class CounterPage extends StatelessWidget {
var count = 0.obs;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text("Counter")),
body: Center(
child: Obx(() {
return Text("${count.value}", style: const TextStyle(fontSize: 50),);
}),
),
floatingActionButton: FloatingActionButton(
child: const Icon(Icons.add),
onPressed: (){
count += 1;
},
),
);
}
}
实现效果:
去除上面其他干扰代码,核心代码如下:
var count = 0.obs;
Obx(() {
return Text("${count.value}");
});
count += 1;
整个实现分为三步:创建数据对象、在 Obx 中使用数据、更新数据
1、创建数据对象
如上面的例子,使用 .obs
即可创建一个可观察者对象,这里创建的是可观察者对象是 Rx
类型,除了使用 .obs
创建外也可以直接使用 Rx 创建:
var count = Rx(0);
/// or
Rx<int> count = Rx(0);
Rx 是一个容器,存放的值是一个泛型,即能创建任意类型的可观察者对象。
除了直接使用 Rx 创建外,GetX 还提供了常用类型的 Rx 封装,如 RxInt
、RxDouble
、RxBool
、RxString
、RxNum
、RxList
、RxMap
等,在上面的例子中使用 0.obs
创建的实际是 RxInt 类型。封装后的 Rx 类型扩展了常见方法和操作符,在使用这些封装类型时可与使用实际值一样,下面看一下 RxInt
和 Rx<Int>
的区别 :
/// 创建
RxInt a = RxInt(0);
/// or
RxInt a = 0.obs;
Rx<Int> b = Rx(0);
/// 使用
a += 1;
b += 1; /// 报错
b.value += 1;
print(a > 0); /// true
print(b > 0); /// false
通过上面的示例发现,RxInt
可以直接使用 +=
操作符以及 >
等运算符,而 Rx<int>
却不行,这就是封装后的 Rx 类的方便之处,关于 RxInt
、RxDouble
、RxBool
、RxString
、RxNum
、RxList
、RxMap
更多的功能大家可参考源码查看,因实现都很简单这里就不多赘述了。
2、Obx 中使用数据
Rx 使用很简单,调用 .value
即可获取到实际数据。Rx 可单独使用,但更多的是在 Obx 中使用实现状态管理功能。
var count = 0.obs;
int count_value = count.value;
3、更新数据
Rx 对象可使用 .value
重新赋值或者使用 update
进行数据更新,对于基础数据类型:Int、bool、String、Double、num 则只能使用 .value
重新赋值进行更新,无论是 Rx<int>
还是 RxInt
都只能使用 .value
进行更新:
var count = 0.obs;
count.value = 1;
count += 1;
var user = User("loongwind", 0).obs;
user.value = User("loongwind", 1);
user.update((value) {
value?.age = 20;
});
当使用 .value
或 update
更新数据后,Obx 中的控件就会重新刷新界面数据。
原理解析
前面将了 Obx 以及 Rx 的使用,下面将通过源码分析 Obx 与 Rx 实现状态管理的原理。
在上面的使用中我们说到了 Rx 是一个可观察者对象,那么 Obx 必然是订阅了 Rx 对象,监听 Rx 值的变化,当监听到 Rx 值发生变化时刷新界面。那么 Obx 是怎样监听 Rx 的数据变化的呢?Rx 又是怎样在数据发生变化时通知 Obx 刷新的?下面一步一步通过源码进行剖析。
为了帮助大家更好的梳理 Obx 与 Rx 源码的关系,我画了一张 UML 图,如下:
既然 Obx 的状态管理分为订阅和通知,那接下来就分别通过订阅和通知来进行分析。
订阅
从 UML 图中发现,Obx 继承自 ObxWidget,而 ObxWidget 则继承自我们熟悉的 StatefulWidget,既然是 StatefulWidget 则肯定存在 State,也就是 UML 图中的 _ObxState , 先看看 Obx 和 ObxWidget 的源码:
class Obx extends ObxWidget {
final WidgetCallback builder;
const Obx(this.builder);
@override
Widget build() => builder();
}
Obx 中的代码比较简单,构造函数中传入 builder 即构建 Widget 的方法,实现了 build 调用传入的 builder 方法。
abstract class ObxWidget extends StatefulWidget {
const ObxWidget({Key? key}) : super(key: key);
@override
_ObxState createState() => _ObxState();
@protected
Widget build();
}
ObxWidget 是一个抽象类,只有一个 build 抽象方法,在 Obx 中实现。在 createState 里创建了 _ObxState 类。
然后看看 _ObxState 的源码:
class _ObxState extends State<ObxWidget> {
final _observer = RxNotifier();
late StreamSubscription subs;
@override
void initState() {
super.initState();
subs = _observer.listen(_updateTree, cancelOnError: false);
}
void _updateTree(_) {
if (mounted) {
setState(() {});
}
}
@override
void dispose() {
subs.cancel();
_observer.close();
super.dispose();
}
@override
Widget build(BuildContext context) =>
RxInterface.notifyChildren(_observer, widget.build);
}
在 _ObxState 中首先创建了 RxNotifier 对象,即通知者,然后在 initState 中调用了其 listen 方法进行监听,传入的回调方法是 _updateTree
即调用 setState
刷新 Widget。看一下 RxNotifier.listen 方法源码:
StreamSubscription<T> listen(
void Function(T) onData, {
Function? onError,
void Function()? onDone,
bool? cancelOnError,
}) =>
subject.listen(
onData,
onError: onError,
onDone: onDone,
cancelOnError: cancelOnError ?? false,
);
RxNotifier.listen 方法中直接调用了 subject.listen 方法,而 subject 是 GetStream 类型,继续跟进 GetStream.listen 源码:
LightSubscription<T> listen(void Function(T event) onData,
{Function? onError, void Function()? onDone, bool? cancelOnError}) {
final subs = LightSubscription<T>(
removeSubscription,
onPause: onPause,
onResume: onResume,
onCancel: onCancel,
)
..onData(onData)
..onError(onError)
..onDone(onDone)
..cancelOnError = cancelOnError;
addSubscription(subs);
onListen?.call();
return subs;
}
GetStream.listen 方法中将传入的参数封装为 LightSubscription
对象,传入的 onData 回调方法传入了 onData 方法,看一下其源码:
void onData(OnData<T>? handleData) => _data = handleData;
直接将其赋值给了 _data 变量。
封装 LightSubscription
对象后调用了 addSubscription 方法:
FutureOr<void> addSubscription(LightSubscription<T> subs) async {
if (!_isBusy!) {
return _onData!.add(subs);
} else {
await Future.delayed(Duration.zero);
return _onData!.add(subs);
}
}
addSubscription 中将封装后的 LightSubscription 对象添加到 _onData
中,_onData
是一个 List : List<LightSubscription<T>>? _onData = <LightSubscription<T>>[]
。
时序图如下:
上面介绍的是 RxNotifier 的监听流程,但是通过上面源码发现与 Rx 并没有任何关系,接下来看看 _ObxState 的 build 方法:
Widget build(BuildContext context) => RxInterface.notifyChildren(_observer, widget.build);
直接调用 RxInterface.notifyChildren
方法,这是一个静态方法,源码:
static T notifyChildren<T>(RxNotifier observer, ValueGetter<T> builder) {
final _observer = RxInterface.proxy;
RxInterface.proxy = observer;
final result = builder();
if (!observer.canUpdate) {
RxInterface.proxy = _observer;
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;
}
首先使用临时变量 _observer 保存 RxInterface.proxy
, 然后将传入的 observer 赋值给了 RxInterface.proxy
,proxy 是一个静态变量,定义如下:static RxInterface? proxy;
, 而根据上面的 UML 图可知 RxNotifier 实现了 RxInterface。
接着调用了 builder 方法,而传入的 builder 就是使用 Obx 时传入的 builder 参数,也就是真正构建 Widget 的方法,然后判断 observer 是否能更新,如果不能更新抛出异常,最后再将开始保存的临时的 _observer 重新赋值给 RxInterface.proxy
,再返回 builder 构建的 Widget 结果。
可能看到这里大家会跟我最开始看这段源码时有同样的疑惑,没看到怎么将 Obx 跟 Rx 进行关联啊?经过本人反复阅读相关源码,发现关键点就在 RxInterface.proxy
上, RxInterface.proxy
是一个公开的静态变量,说明它可能在其他地方进行调用,于是查找源码发现其在 RxObjectMixin
的 get value 中有调用。
T get value {
RxInterface.proxy?.addListener(subject);
return _value;
}
在这里调用了 RxInterface.proxy?.addListener
添加监听,跟进 UML 图及源码发现,Rx 最终是混入了 RxObjectMixin
类,即在 Rx 的获取数据中调用了 RxInterface.proxy?.addListener
,那什么时候获取 Rx 的数据呢?看看最开始的示例代码:
Obx(() {
return Text("${count.value}");
});
没错,就是在 Obx 的 builder 方法里,这就清楚了为什么在 RxInterface.notifyChildren
方法里是先将传入的 observer
赋值给 proxy 然后再调用 builder 方法了,因为这样在调用 builder 方法时调用了 Rx.value
,而在 get value
中调用了 RxInterface.proxy?.addListener
,且 addListener
传入的 subject
是 Rx 的 GetStream, 而 proxy 是 _ObxState 里创建的 RxNotifier。
addListener 实现是在 NotifyManager
里,源码:
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);
}
}
判断是否包含传入的 rxGetx 也就是 GetStream,如果不包含则调用其 listen 方法,该方法的源码我们在上面分析 RxNotifier 订阅的时候已经介绍了,就是对 GetStream 添加监听。当数据更新时调用 subject.add
方法,注意这里的 subject 是 _ObxState.observer
的 RxNotifier 里的 subject,即当 Rx 数据更新时通知 _ObxState.observer
最终回调到 _ObxState 的 _updateTree 方法实现订阅功能。
订阅时序图如下:
通知
订阅分析完后,再来看通知是怎么实现的,前面介绍了当调用 Rx 的 value
赋值或者 update
方法时会触发界面刷新,那么就先来看看这两个方法的源码。
update 的 实现是在 _RxImpl
里:
void update(void fn(T? val)) {
fn(_value);
subject.add(_value);
}
value 的赋值实现是在 RxObjectMixin
里:
set value(T val) {
if (subject.isClosed) return;
sentToStream = false;
if (_value == val && !firstRebuild) return;
firstRebuild = false;
_value = val;
sentToStream = true;
subject.add(_value);
}
通过源码发现两个方法最后都调了 subject.add(_value);
方法,并把最新的值作为变量传入了,而 subject 是 GetStream 类型,其源码如下:
void add(T event) {
assert(!isClosed, 'You cannot add event to closed Stream');
_value = event;
_notifyData(event);
}
最终调用 _notifyData
方法,继续跟进源码:
void _notifyData(T data) {
_isBusy = true;
for (final item in _onData!) {
if (!item.isPaused) {
item._data?.call(data);
}
}
_isBusy = false;
}
发现最终遍历 _onData
调用 item 的 _data.call ,而我们知道 _onData 是我们前面通过 addListener
调 listen 方法向其添加封装的 LightSubscription 对象,而其 _data
就是 _ObxState._observer.listen
传入的回调方法也就是 _updateTree
,最终调用 setState 方法实现界面的刷新。
通知的时序图如下:
总结
通过对 Obx 和 Rx 的订阅和通知的源码分析理解,加深了对 Obx 状态管理的理解,在开发过程中更能灵活的对其使用。本文只讲解了 Obx 和 Rx 关于订阅和通知部分的源码,关于其更多的源码大家有兴趣可以再深入学习,通过对源码的学习能使我们更好的理解 GetX 的库,也能让我们学到更多的知识。