Flutter狀態管理--Getx學習1--Obx

小小糖糖發表於2021-03-26

Getx狀態管理

宣告式的介面開發就是如今的潮流,不管你喜不喜歡這樣的編碼方式,ReactFlutterAndroid Jetpack Compose等都是使用宣告式編寫的。使用宣告式編寫的UI介面就是應用的當前狀態,對於應用來說時時刻刻知道當前狀態很重要,所以狀態管理也就至關重要;

相比於Providerflutter_bloc狀態管理框架,突破InheritedWidget的限制需要依賴於BuildContext,在沒有BuildContext的情況下無法使用;

GetX的官方計數器示例

先看下程式碼

import 'package:flutter/material.dart';
import 'package:get/get.dart';

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

class CounterController extends GetxController{
  var count = 0.obs;
  increment() => count++;
  
  @override
  void onInit() {
    super.onInit();
    print("init-----");
  }

  @override
  void onReady() {
    super.onReady();
    print("----onready");
  }

  @override
  void onClose() {
    super.onClose();
    print("----onClose");
  }
}

class HomePage extends StatelessWidget {

  @override
  Widget build(context) {

    // 使用Get.put()例項化你的類,使其對當下的所有子路由可用。
    final CounterController c = Get.put(Controller());

    return Scaffold(
      // 使用Obx(()=>每當改變計數時,就更新Text()。
      appBar: AppBar(title: Obx(() => Text("Clicks: ${c.count}"))),

      // 用一個簡單的Get.to()即可代替Navigator.push那8行,無需上下文!
      body: Center(child: ElevatedButton(
              child: Text("Go to Other"), onPressed: () => Get.to(Other()))),
      floatingActionButton:
          FloatingActionButton(child: Icon(Icons.add), onPressed: c.increment));
  }
}

class Other extends StatelessWidget {
  // 你可以讓Get找到一個正在被其他頁面使用的Controller,並將它返回給你。
  final Controller c = Get.find();

  @override
  Widget build(context){
     // 訪問更新後的計數變數
     return Scaffold(body: Center(child: Text("${c.count}")));
  }
}
複製程式碼

這是一個簡單的專案,但它已經讓人明白Get的強大。心裡絕對有疑問???怎麼做這麼簡單的狀態管理的,通過原始碼分析解決心中的疑問;

GetX 狀態管理、依賴管理分析

  • 狀態管理

要想讓一個資料變成可觀察,你只需要在它的末尾加上".obs"

var count = 0.obs;
複製程式碼
  • 依賴管理

Get有一個簡單而強大的依賴管理器,它允許你只用1行程式碼就能檢索到與你的Bloc或Controller相同的類,無需Provider上下文,無需 inheritedWidget。

final Controller c = Get.put(Controller());
複製程式碼
  • 依賴管理資料怎麼儲存

通過debug斷點CounterController的onInit方法檢視呼叫棧

image.png

呼叫棧可以看到CounterController資料存到了GetInstance全域性單例中,所以與flutter_bloc provider狀態管理資料存放到了InheritedWidget中不同,Getx把資料存到了全域性單例中,不依賴於UI,將業務邏輯與介面分離,低耦合,更靈活;

Obx widget怎麼響應資料變化的

  /// Obx widget 只用這個地方使用到了可觀察的count;
  Obx(() => Text("Clicks: ${c.count}"))

  /// 檢視count的value;這裡的是RxInt型別,c.count.value 簡寫 c.count;這裡面設定了監聽
  /// Returns the current [value]

  T get value {
    if (RxInterface.proxy != null) {
      RxInterface.proxy.addListener(subject);
    }
    return _value;
  }
複製程式碼

繼續檢視Obx widget的原始碼

class Obx extends ObxWidget {
  final WidgetCallback builder;

  const Obx(this.builder);

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

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() {
    /// 設定widget重新整理監聽,資料改變後呼叫_updateTree
    subs = _observer.listen(_updateTree);
    super.initState();
  }

  void _updateTree(_) {
    if (mounted) {
      setState(() {});
    }
  }

  @override
  void dispose() {
    subs.cancel();
    _observer.close();
    super.dispose();
  }

  Widget get notifyChilds {
    /// 這裡可以看到我們設定Rx資料value時候的用到的RxInterface.proxy
    final observer = RxInterface.proxy;
    /// 將_observer賦值給RxInterface.proxy
    RxInterface.proxy = _observer;
    /// 再呼叫widget.build(),繼而呼叫builder(),執行到回撥builder時候這個時候
    /// RxInterface.proxy != null 資料的更新推動UI的更新建立了關係
    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的值
    RxInterface.proxy = observer;
    return result;
  }

  @override
  Widget build(BuildContext context) => notifyChilds;
}
複製程式碼
  • 資料更新響應流程

  /// 再來看獲取資料
  T get value {
    /// Obx在build的時候RxInterface.proxy != null  RxInterface.proxy= RxNotifier();
    if (RxInterface.proxy != null) {
      RxInterface.proxy.addListener(subject);
    }
    return _value;
  }
  /// RxNotifier.addListener() 會呼叫 mixin NotifyManager<T>中addListener的方法
  void addListener(GetStream<T> rxGetx) {
    if (!_subscriptions.containsKey(rxGetx)) {
    /// 這個rxGetx就是Rx響應資料中 GetStream<T> subject = GetStream<T>();
      final subs = rxGetx.listen(subject.add);
      final listSubscriptions =
          _subscriptions[rxGetx] ??= <StreamSubscription>[];
      listSubscriptions.add(subs);
    }
  }
/// Rx響應資料中 GetStream<T> subject設定listen()的回撥是RxNotifier中subject的add方法
  void add(T event) {
    assert(!isClosed, 'You cannot add event to closed Stream');
    _value = event;
    _notifyData(event);
  }
   /// 設定資料響應
  set value(T val) {
    if (_value == val && !firstRebuild) return;
    firstRebuild = false;
    _value = val;
    ///  GetStream<T> subject = GetStream<T>();
    subject.add(_value);
  }
/// 檢視 subject.add(_value)方法
 void add(T event) {
    assert(!isClosed, 'You cannot add event to closed Stream');
    _value = event;
    _notifyData(event);
  }
 /// 遍歷_onData 執行每個訂閱資料的_data回撥 RxNotifier中subject的add方法
 /// RxNotifier中subject的add方法繼續遍歷每個訂閱物件的_data回撥就是_updateTree回撥
 void _notifyData(T data) {
    _isBusy = true;
    for (final item in _onData) {
      if (!item.isPaused) {
        item._data?.call(data);
      }
    }
    _isBusy = false;
  }  

複製程式碼
  • UI重新整理訂閱流程

 /// 上面ObxWidget原始碼的設定UI更新
    @override
  void initState() {
    subs = _observer.listen(_updateTree);
    super.initState();
  }
/// _observer.listen(_updateTree);  會呼叫 mixin NotifyManager<T>中listen方法
/// subject GetStream型別
   StreamSubscription<T> listen(
    void Function(T) onData, {
    Function onError,
    void Function() onDone,
    bool cancelOnError = false,
  }) =>
      subject.listen(onData,
          onError: onError, onDone: onDone, cancelOnError: cancelOnError);
          
///最後UI重新整理監聽訂閱新增到List<LightSubscription<T>> _onData = <LightSubscription<T>>[];

 FutureOr<void> addSubscription(LightSubscription<T> subs) async {
    if (!_isBusy) {
      return _onData.add(subs);
    } else {
      await Future.delayed(Duration.zero);
      return _onData.add(subs);
    }
  }
複製程式碼

斷點檢視當資料更改的時候先遍歷_onData的訂閱_data回撥,RxNotifier中subject的add方法

image.png RxNotifier中subject的add方法繼續觸發遍歷_onData的訂閱_data回撥 _updateTree()方法; image.png

Getx功能很多,更多檢視官方文件github網速限制可以檢視碼雲gitee

相關文章