引言
在Flutter开发圈内,随着Get框架的认知度日渐提高,选择尝试它的小伙伴也越来越多。尝试新框架时,被各种毒打也是家常便饭,但这也是保障其被正确使用所必不可少的。
Get提供了多种根据Rx变量构建Widget的小组件,如Obx
、GetX
、GetBuilder
等。其中最简洁的莫属Obx
,但是对于新手,十有八九遇到过这样的错误:
The following message was thrown building Obx(dirty, state: _ObxState#777c8):
[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.
复制代码
错误说明很明确,Obx
或GetX
组件错误使用,因为检测到在其中没有使用observable变量。明白意思后在其中使用相应变量便可以顺利解决错误。但是你是否想过Get是如何知道我们没有使用observable变量的呢?
初探
class Obx extends ObxWidget {
final WidgetCallback builder;
const Obx(this.builder);
@override
Widget build() => builder();
}
复制代码
Obx
的定义很简单,它仅接受一个返回Widget
的闭包作为参数。既然这里看不出个所以,那么就继续分析他的父类:
abstract class ObxWidget extends StatefulWidget {
const ObxWidget({Key? key}) : super(key: key);
@override
_ObxState createState() => _ObxState();
@protected
Widget build();
}
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(() {});
}
}
// ...
Widget get notifyChilds {
final observer = RxInterface.proxy; // 2
RxInterface.proxy = _observer; // 3
final result = widget.build();
// 1
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; // 4
return result;
}
@override
Widget build(BuildContext context) => notifyChilds;
}
复制代码
在ObxWidget
里,我们马上在1
处看到了最终抛给我们的错误提示,看来可以在这里找到我们想要的答案。ObxWidget
继承了StatefulWidget
,但是和我们平时的使用方式不同,它在widget的部分也包含了一个build()
方法,只是少接收一个BuildContext参数。2
处保存当前proxy,并在 4
处还原, 3
处切换为当前的RxNotifier
,这一系列操作的作用我们稍后分析。
可以发现由于检查到了_observer!.canUpdate
为false
才抛出的错误,所以Get一定是通过它检测我们有没有使用observable变量,其命名canUpdate
也印证了我们的猜测(没有可观测的变量,自然也就没法响应式更新)。由于Obx
是为了响应式更新而专门设计的,而我们的使用方式违背了设计初衷,所以此处抛出错误。
继续查看canUpdate
的定义,这里我们可能会被IDE带偏到RxInterface
的定义,它是一个抽象类,被所有Reactive类所继承,并没有具体实现。回看上一步代码,我们发现_observer
其实是一个RxNotifier
:
class RxNotifier<T> = RxInterface<T> with NotifyManager<T>;
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);
}
}
// ...
}
复制代码
可以发现,canUpdate
最终是由NotifyManager
实现的,canUpdate
检查的是当前实例中流的订阅个数是否为0,即是否监听了observable变量。继续观察,只发现addListner
能够向_subscriptions
中添加新的entry,那么谁使用了这个方法呢?直接使用IDE搜索Usage(IDEA系使用Alt/Option+F7)没有搜索到相关代码,只能由我们自己猜测了。
换一条思路,回到最初的问题,Get检测了我们在组件内是否使用
了observable变量,是否是使用
本身调用了addListener
呢?
当我们使用Get提供的拓展方法.obs
创建observable变量时,其实是创建了一个Rx<T>
变量,Rx变量是Get响应式组件的核心,Get借鉴了RxDart使用了自己的一套实现。沿着某一种Rx
变量(如RxString
),我们一路向上追踪,RxString
-> Rx
-> _RxImpl
-> RxObjectMixin
,最终可以发现如下代码:
mixin RxObjectMixin<T> on NotifyManager<T> {
// ...
T get value {
if (RxInterface.proxy != null) {
RxInterface.proxy!.addListener(subject);
}
return _value;
}
// ...
}
复制代码
这里就是每次使用
时会调用的代码,正如我们预料的,在这里变量被记录到了_subscriptions
中。
回顾下正确使用的整个build流程:
-
创建
Obx
,保存当前proxy
,切换当前proxy
为此Obx
持有的RxNotifier
-
使用了
.value
方法,使用proxy
记录下使用记录,并listen这个变量。 -
build时检查监测到有正在watch的变量,通过检查。
-
还原第一步保存的
proxy
了解了build的全貌,我们可以猜测出1、4步这一系列操作保证了递归build过程中正确的proxy记录正确的变量。
常见错误案例
final controller = Get.put(HomeController());
@override
Widget build(BuildContext){
final goods = controller.goods.value;
return Obx(() => Text(goods.name));
}
复制代码
了解了原理后我们可以清楚知道这样的代码问题点:在访问value
时,Obx
尚未创建,也没有对应的RxNotifier
可供记录和监听用,于是便会抛出文章开始的错误。
简单修改:
final controller = Get.put(HomeController());
@override
Widget build(BuildContext){
return Obx(() => Text(controller.goods.value.name));
}
复制代码
新人第一次投稿,如有错误请指正