最近在做畫面時,滿常使用 GetX 的 Obx + Rx 變數,讓畫面可以根據狀態變化即時更新。過程中有時會碰到一些錯誤,在建置 Widget 的過程中,因為 Obx 找不到可以被監聽的目標,導致 Obx 認為使用者錯誤的使用了 Obx,所以透過 Exception 來提醒使用者。
Obx 的使用方法是把一個 builder 方法傳給 Obx 這個 Widget,不需要任何 Rx 變數做為參數。像是下面的這個簡單的 count 例子中,Obx 傳入一個 builder 方法,只要 builder 在建置 Widget 的過程中使用 Rx 變數,Obx 就能進行監聽。
這就讓我自己十分好奇,Obx 到底是如何找到 builder 方法中的 Rx 變數,然後對這個 Rx 變數進行監聽呢?自己花了一些時間研究和實驗,今天就來分享一下,到底 GetX 中的 Obx 是如何完成他的工作的。
在 Counter 例子中,有兩個重要的物件,一個是 Obx,另一個則是存放 count 的 Rx 變數。其中 Obx 身上有一個型態為 RxNotifier 的 _observer 變數,主要用來監聽 Rx 變數並更新畫面的。而存放 count 的 Rx 變數的爺爺也是 RxNotifier。
RxNotifier 本身沒有任何實作,實作都是集中 NotifyManager 這個 mixin 身上。NotifyManger 身上主要有兩個方法
當開始 build GetCountView 的畫面並 build 到 Obx 時,程式會先執行 ObxWidget initState() 方法。在 initState() 中執行 _observer.listen,並傳入 _updateTree,讓 _observer 監聽到事件時,可以呼叫 setState 更新畫面。
再來程式會走進 ObxWidget 的 build 方法。可以發現 ObxWidget 的 build 方法也就只是轉頭呼叫 RxInterface 的 notifyChildren 靜態方法,並傳入 ObxWidget 身上的 _observer 和 widget.build 。
此時的 widget.build 也就是我們在 GetCountView 中傳給 Obx 的那段印出 count 的 builder。
當我們在深入 RxInterface.notifyChildren 之後,可以發現在程式會把 ObxWidget 身上的 _observer 塞到一個全域變數 RxInterface.proxy 中。然後繼續執行 builder 並 build 出顯示 count 數的 Text Widget。最後把 RxInterface.proxy 還原成原本的值,然後就回傳結果了。
看到這邊好像還是不知道 Obx 到底是如何發現 builder 之中的 RxInt 的,我們只有看到 _observer 被賦予了他要觸發 setState 的工作,但是 _observer 是如何監聽 count 事件呢?
其實關鍵就在 builder 中。我們回頭看一下使用 Obx 那段程式碼,Text 中使用了 count.value,而關鍵就在這個 value getter 中。
讓我們深入看一下 value 這個 getter 是如何被實作的。
在使用 value getter 時,程式會向 RxInterface.proxy 這個全域變數註冊一個 subject,還記得先前 RxInterface.notifyChildren 做了什麼嗎,它把 ObxWidget 身上的 _observer 塞給 RxInterface.proxy,此時 value getter 中用的對象就是 ObxWidget 身上的 _observer。最後,當 value 發生變化時, Rx 變數就會透過 subject 通知 _observer。
簡單來說,Obx 與 Rx 變數是透過 RxInterface 這個全域變數作為橋樑,讓兩者可以在 build 的時候建立觀察者模式。當 Rx 變數透過 value setter 賦值時,就能成功通知 ObxWidget 身上的 _observer,_observer 接收到事件之後就觸發 setState(),讓畫面根據新的狀態更新。
ps. 實際上並不是由 GetCountView 直接呼叫 ObxWidget.build 的,這邊只是簡化一下呼叫流程。實際上是由 GetCountView 的 Element 去呼叫的。
GetX 作為熱門的 Flutter 狀態管理套件之一,有許多容易使用的 API,其中也大量的使用全域變數 。使用的時候還需要多加考慮一下,用得太多容易導致產品程式碼與套件緊緊相依,不只測試難寫,想抽換狀態管理的套件也會十分麻煩,使用的時候需要多多思考。