引言
在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));
}
複製程式碼
新人第一次投稿,如有錯誤請指正