Flutter Getx核心功能淺析

Getx簡介

詳情請看:https://github.com/jonataslaw/getx/blob/master/README.zh-cn.md 中文介紹

版本:get: 3.24.0
Getx 提供了非常豐富的功能, 功能模塊:

├── lib
│   ├── get_connect  [網(wǎng)絡模塊]
│   │   ├── http 
│   │   └── sockets
│   ├── get_core   [核心模塊]
│   ├── get_instance   [依賴管理模塊]
│   ├── get_navigation   [路由管理模塊]
│   ├── get_rx   [響應式模塊(類似RxDart)]
│   ├── get_state_manager   [狀態(tài)管理模塊]
│   └── get_utils   [通用工具]
│           ├── extensions  [一些基礎功能擴展]
│           ├── get_utils   [提供常用功能集(各種判斷isNull玛臂、isBlank封孙、isNum等等等)]
│           ├── platform   [提供平臺判斷(isAndroid、isIOS)]
│           └── queue  [隊列]

安裝

  1. 在pubspec.yaml文件中添加依賴 get: ^3.24.0
  2. MaterialApp改為GetMaterialApp
    請看以下完整代碼泡徙,實現(xiàn)了HelloWorld的功能:
import 'package:flutter/material.dart';
import 'package:get/get.dart';

void main() => runApp(GetMaterialApp(home: Home()));

/// 計數(shù)器控制器
class CounterController extends GetxController {
  var count = 0.obs;
}

class Home extends StatelessWidget {
  // 初始化計數(shù)器控制器膜蠢,放入依賴管理中,并獲取這個實例
  final _counter = Get.put(CounterController());

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Count Getx')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text('You have pushed the button this many times:'),
            Obx(() => Text(// 響應數(shù)據(jù)變化
                  '${_counter.count.value}',
                  style: Theme.of(context).textTheme.headline4,
                )),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        // 觸發(fā)數(shù)據(jù)變化
        onPressed: () => _counter.count + 1,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  }
}
  1. 代碼分析
  • CounterController類
    1. 繼承自GetxController
    2. 聲明了 一個響應式的變量count庶橱,類型為RxInt贪惹,get_rx模塊中定義寂嘉。
  • Home類
    1. 變量_counter 初始化計數(shù)器控制器枫绅,放入依賴管理中硼端,并獲取這個實例
    2. 對應的響應數(shù)據(jù)變化使用了 Rx類型對應的Widget:Obx。 當CounterController.count 值方發(fā)生變化時县耽,觸發(fā)Obx的更新镣典。當在Obx中調(diào)用_counter.count.value時,RxInterface.proxy.addListener()為這個值添加了監(jiān)聽事件

有幾個疑問

1. 狀態(tài)管理數(shù)據(jù)是怎么存儲的澎剥?

final _counter = Get.put(CounterController());

調(diào)用流程詳見下圖:


stack1.png
static final Map<String, _InstanceBuilderFactory> _singl = {};
......
  S put<S>(
    S dependency, {
    String tag,
    bool permanent = false,
    @deprecated InstanceBuilderCallback<S> builder,
  }) {
    _insert(
        isSingleton: true,
        name: tag,
        permanent: permanent,
        builder: builder ?? (() => dependency));
    return find<S>(tag: tag);
  }

調(diào)用棧中哑姚,可以看到CounterController最終數(shù)據(jù)存到了GetInstance全局單例中芜茵。 與provider狀態(tài)管理相比,provider數(shù)據(jù)是存放到了InheritedWidget中绞佩,InheritedWidget特性是只有子節(jié)點才能獲取到存放的數(shù)據(jù)蒸辆, 在和界面無關時,無法獲取到provider中的數(shù)據(jù)谆奥。相比之下拂玻,Getx把數(shù)據(jù)存到了單例對象中,不依賴于UI魄懂, 這種方式更加靈活闯第,試用范圍更廣。

2. 狀態(tài)管理怎么做到自動管理的填帽?

詳見下圖:

  1. 添加GetxController和路由的綁定關系


    stack2.png
  // 保存路由和GetxController的對應關系
  static final Map<String, String> _routesKey = {};
  ......
  // 將CounterController的類名與當前路由對應起來篡腌, 存入_routesKey中。
  void _registerRouteInstance<S>({String tag}) {
    _routesKey.putIfAbsent(_getKey(S, tag), () => Get.reference);
  }
  1. 當頁面退出時嘹悼,GetPageRoute.dispose 移除_routesKey中對應的Key與Value杨伙,并刪除在GetInstance._singl中保存的GetxController類


    截屏2021-01-08 下午2.22.00.png

注意:Get.put調(diào)用的時機,像開頭例子中Home就不能正確綁定路徑棠赛。 因為還沒有進入Home頁面時膛腐,Get.put就已經(jīng)觸發(fā)了,這個時候Get.reference獲取的路徑是Home的上一個路由路徑辩涝。

3. Obx() 是怎么響應Obs變量變化的勘天?

Obx(() => Text(// 響應數(shù)據(jù)變化
                  '${_counter.count.value}',
                  style: Theme.of(context).textTheme.headline4,
                )),

只能看到_counter.count.value獲取了值,應該也是這個地方綁定了數(shù)據(jù)響應關系商膊〕杞看源碼具體實現(xiàn):

  T get value {
    if (RxInterface.proxy != null) {
      RxInterface.proxy.addListener(subject); // 這里建立了數(shù)據(jù)關系,添加了數(shù)據(jù)變化監(jiān)聽
    }
    return _value;
  }

subject為GetStream<T>類型实幕。數(shù)據(jù)變化監(jiān)聽弄明白了堤器,再看看Obx是怎么監(jiān)聽數(shù)據(jù)變化的,看Obx源碼:

class Obx extends ObxWidget {
  final WidgetCallback builder;

  const Obx(this.builder);

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

Obx繼承自ObxWidget整吆。

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;
  StreamSubscription subs;

  _ObxState() {
    _observer = RxNotifier();
  }

  @override
  void initState() {
    subs = _observer.listen(_updateTree);
    super.initState();
  }

  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;
}
  1. initState() 方法中掂为, subs = _observer.listen(_updateTree),監(jiān)聽了數(shù)據(jù)更新,當數(shù)據(jù)更新后寸齐,調(diào)用_updateTree更新視圖。
  2. notifyChilds() 方法中RxInterface.proxy = _observer; 返回到Obx()開頭的代碼:
  Obx(() => Text(// 響應數(shù)據(jù)變化
                  '${_counter.count.value}',
                  style: Theme.of(context).textTheme.headline4,
                )),

  T get value {
    if (RxInterface.proxy != null) {
      RxInterface.proxy.addListener(subject); // 這里建立了數(shù)據(jù)關系扰法,添加了數(shù)據(jù)變化監(jiān)聽
    }
    return _value;
  }

可以看到RxInterface.proxy毅厚。 當Obx 執(zhí)行build時調(diào)用notifyChilds()吸耿,會把當前_observer賦值給RxInterface.proxy,再執(zhí)行widget.build()咽安,運行到_counter.count.value的get方法妆棒,RxInterface.proxy.addListener(subject); 這樣變量和Widget的關聯(lián)就完整建立。

  1. 恢復RxInterface.proxy糕珊,詳見下面代碼:
    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;

RxInterface.proxy = observer; 恢復RxInterface.proxy為之前保存的值红选。

4. GetBuilder

這個綁定關系比較好理解,就不寫了

以上代碼為自己的理解玛瘸,有錯誤的歡迎各位大佬指正苟蹈。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市渺绒,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌躏鱼,老刑警劉巖殷绍,帶你破解...
    沈念sama閱讀 219,188評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異茶行,居然都是意外死亡登钥,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,464評論 3 395
  • 文/潘曉璐 我一進店門看锉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來塔鳍,“玉大人,你說我怎么就攤上這事懂傀±校” “怎么了?”我有些...
    開封第一講書人閱讀 165,562評論 0 356
  • 文/不壞的土叔 我叫張陵犀斋,是天一觀的道長情连。 經(jīng)常有香客問我却舀,道長,這世上最難降的妖魔是什么挽拔? 我笑而不...
    開封第一講書人閱讀 58,893評論 1 295
  • 正文 為了忘掉前任螃诅,我火速辦了婚禮状囱,結(jié)果婚禮上倘是,老公的妹妹穿的比我還像新娘。我一直安慰自己叨粘,他們只是感情好瘤睹,可當我...
    茶點故事閱讀 67,917評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般苇羡。 火紅的嫁衣襯著肌膚如雪设江。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,708評論 1 305
  • 那天码俩,我揣著相機與錄音歼捏,去河邊找鬼。 笑死瞳秽,一個胖子當著我的面吹牛练俐,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播腺晾,決...
    沈念sama閱讀 40,430評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼悯蝉,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了泉粉?” 一聲冷哼從身側(cè)響起榴芳,我...
    開封第一講書人閱讀 39,342評論 0 276
  • 序言:老撾萬榮一對情侶失蹤窟感,失蹤者是張志新(化名)和其女友劉穎歉井,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體躏嚎,經(jīng)...
    沈念sama閱讀 45,801評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡菩貌,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,976評論 3 337
  • 正文 我和宋清朗相戀三年箭阶,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片仇参。...
    茶點故事閱讀 40,115評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡诈乒,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出喂饥,到底是詐尸還是另有隱情癌压,我是刑警寧澤,帶...
    沈念sama閱讀 35,804評論 5 346
  • 正文 年R本政府宣布集侯,位于F島的核電站帜消,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏泡挺。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,458評論 3 331
  • 文/蒙蒙 一贱除、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧碍讯,春花似錦扯躺、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,008評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至蒲稳,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背拙已。 一陣腳步聲響...
    開封第一講書人閱讀 33,135評論 1 272
  • 我被黑心中介騙來泰國打工倍踪, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人建车。 一個月前我還...
    沈念sama閱讀 48,365評論 3 373
  • 正文 我出身青樓缤至,卻偏偏與公主長得像,于是被迫代替她去往敵國和親领斥。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,055評論 2 355

推薦閱讀更多精彩內(nèi)容