Getx狀態管理
宣告式的介面開發就是如今的潮流,不管你喜不喜歡這樣的編碼方式,React、Flutter、Android Jetpack Compose等都是使用宣告式編寫的。使用宣告式編寫的UI介面就是應用的當前狀態,對於應用來說時時刻刻知道當前狀態很重要,所以狀態管理也就至關重要;
相比於Provider、flutter_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方法檢視呼叫棧
呼叫棧可以看到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方法
RxNotifier中subject的add方法繼續觸發遍歷_onData的訂閱_data回撥 _updateTree()方法;
Getx功能很多,更多檢視官方文件,github網速限制可以檢視碼雲gitee