在以前我寫過一篇文章,教你如何實現Flutter
的BLoC
框架(juejin.im/post/5cb80c… ),這個BLoC
的實現,模仿android
開發中的MVVM
開發方式,利用Flutter
的Stream
,在資料改變的時候,由Stream
推送資料給UI
層,然後UI
層自動更新UI
。這次我們來自己實現一個Provider
,這個也是Flutter
中最常用的狀態管理框架,由我們自己實現Provider
,來了解Provider
內部核心原理。
首先,在上一篇文章中(InheritWidget
原理解析:juejin.im/post/5edb97… ),我們檢視輸出日誌發現一個問題,就是點選“數字+1”這個按鈕的時候,“數字+1”這個按鈕也重新整理了(日誌輸出CustomRaisedButton build
),這是因為我們呼叫的setState
方法是BodyWidgetState
這個類的方法,所以BodyWidget
和它的child widget
都重建了,而我們的需求是InheritedWidget
的資料改變的時候只重新整理依賴此InheritedWidget
的widget
,要做到這一點,我們就不能呼叫BodyWidgetState的setState
方法,而只呼叫InheritedWidget
上一個節點的setState
,也就是說要把InheritedWidget
作為一個StatefulWidget
的child
。然後我們分步驟編寫程式碼:
- 建立可被監聽的資料管理類,為什麼我們不直接讓
InheritedWidget
成為可被監聽的型別呢?這是因為InheritedWidget
只是提供資料,資料的消費者應該持有的是資料而不是InheritedWidget
,否則資料消費者如果持有InheritedWidget
的話,修改具體資料的方法就要新增到InheritedWidget
裡,而資料型別是多種多樣的,不可能全部寫到InheritedWidget
裡,所以我們要建立可被監聽的資料管理類,這樣當資料發生變化的時候,呼叫資料管理類的方法,資料管理類再通知該類的監聽者 - 建立
InheritedWidget
子類,用來儲存資料管理類,該類可以在重建以後通知所有依賴了該類的widget
- 建立
StatefulWidget
,這裡我們起名為ProviderCreator
,用來儲存實際顯示的Widget
和可被監聽的資料管理類,並根據兩者建立InheritedWidget
,並監聽資料管理類的變化。這樣可以在資料管理類中的資料發生變化的時候可以通過ProviderCreator
對應的State
呼叫setState
來讓InheritedWidget
重建。InheritedWidget
重建時如果ProviderCreator
對應的Element
沒有被銷燬的話,那這個ProviderCreator
內部的可被監聽的資料管理類和實際顯示的child
就被快取起來了(注意:這個child
是我們傳入的實際顯示的widget
,而不是ProviderCreator
對應的State
的build
方法裡返回的widget
)
開始我們的程式碼編寫
1. 建立可被監聽的資料管理類
建立InheritedWidget
子類的時候,裡面的資料可以是任意型別,但是我們需要在資料改變的時候通知監聽者,所以我們約束一下里面的資料型別必須是可被監聽的型別,在flutter
裡,有一個類叫做ChangeNotifier
,非常適合用來作為被監聽者,程式碼如下
class ChangeNotifier implements Listenable {
ObserverList<VoidCallback> _listeners = ObserverList<VoidCallback>();
@protected
bool get hasListeners {
return _listeners.isNotEmpty;
}
@override
void addListener(VoidCallback listener) {
_listeners.add(listener);
}
@override
void removeListener(VoidCallback listener) {
_listeners.remove(listener);
}
@mustCallSuper
void dispose() {
_listeners = null;
}
@protected
@visibleForTesting
void notifyListeners() {
if (_listeners != null) {
final List<VoidCallback> localListeners = List<VoidCallback>.from(_listeners);
for (final VoidCallback listener in localListeners) {
listener();
}
}
}
}
複製程式碼
程式碼我簡化了一下,就是上面的樣子,我們的可被監聽的資料類都需要繼承該類
2. 建立InheritedWidget
子類
因為該類用來提供可被監聽的資料管理類,所以起名叫`Provider`
複製程式碼
class Provider<T extends ChangeNotifier> extends InheritedWidget {
final T data;
Provider({Key key, this.data, Widget child}) : super(key: key, child: child) {
print("Provider=$hashCode");
}
//定義一個便捷方法,方便子樹中的widget獲取共享資料
static Provider<T> of<T extends ChangeNotifier>(BuildContext context, bool dependOn) {
if (dependOn) {
return context.dependOnInheritedWidgetOfExactType<Provider<T>>();
} else {
return context.getElementForInheritedWidgetOfExactType<Provider<T>>().widget as Provider<T>;
}
}
@override
bool updateShouldNotify(InheritedWidget oldWidget) {
//在此簡單返回true,則每次更新都會呼叫依賴其的子孫節點的'didChangeDependencies'。
return true;
}
}
複製程式碼
3. 建立StatefulWidget
,用來儲存實際顯示的Widget
和可被監聽的資料管理類,並根據兩者建立InheritedWidget
,並監聽資料管理類的變化。
class ProviderCreator<T extends ChangeNotifier> extends StatefulWidget {
final T data;
final Widget child;
ProviderCreator({
Key key,
this.data,
this.child,
}) {
print("ProviderCreator=$hashCode");
}
@override
State<StatefulWidget> createState() {
return ProviderCreatorState<T>();
}
}
class ProviderCreatorState<T extends ChangeNotifier> extends State<ProviderCreator> {
void update() {
setState(() {});
}
@override
void initState() {
widget.data.addListener(update);
super.initState();
}
@override
void dispose() {
widget.data.dispose();
super.dispose();
}
@override
void didUpdateWidget(ProviderCreator<ChangeNotifier> oldWidget) {
//當Provider更新時,如果新舊資料不"==",則解綁舊資料監聽,同時新增新資料監聽
if (oldWidget.data != widget.data) {
oldWidget.data.dispose();
widget.data.addListener(update);
}
super.didUpdateWidget(oldWidget);
}
@override
Widget build(BuildContext context) {
print("""CustomInheritedWidgetCreatorState build
\twidget=${widget.hashCode}
\twidget.data.hashCode=${widget.data.hashCode}
\twidget.child=${widget.child.hashCode}""");
return Provider<T>(
data: widget.data,
child: widget.child,
);
}
}
複製程式碼
示例
然後,我們用一個示例來試一下我們剛才自己寫的Provider
我們還用上篇文章裡的示例,用一個計數器程式來測試。
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter InheritWidget',
home: Scaffold(
appBar: AppBar(),
body: Center(
child: BodyWidget(),
),
),
);
}
}
class BodyWidget extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return BodyWidgetState();
}
}
class BodyWidgetState extends State<BodyWidget> {
Counter counter = Counter();
@override
Widget build(BuildContext context) {
return ProviderCreator<Counter>(
data: counter,
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
//依賴
DependOnInheritedWidget<Counter>(),
//不依賴
Builder(builder: (context) {
return Text(Provider.of<Counter>(context, false).data.toString());
}),
Builder(builder: (context) {
return CustomRaisedButton(
onPressed: () {
//不依賴
Provider.of<Counter>(context, false).data.increment();
},
child: Text("數字+1"),
);
})
],
),
);
}
}
class Counter extends ChangeNotifier {
int num = 0;
void increment() {
num++;
notifyListeners();
}
@override
String toString() {
return "$num";
}
}
class DependOnInheritedWidget<T extends ChangeNotifier> extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return DependOnInheritedWidgetState<T>();
}
}
class DependOnInheritedWidgetState<T extends ChangeNotifier> extends State<DependOnInheritedWidget> {
@override
Widget build(BuildContext context) {
print("DependOnInheritedWidgetState build");
return Text(Provider.of<T>(context, true).data.toString());
}
@override
void didChangeDependencies() {
print("DependOnInheritedWidgetState didChangeDependencies");
super.didChangeDependencies();
}
}
class CustomRaisedButton extends RaisedButton {
const CustomRaisedButton({
@required VoidCallback onPressed,
Widget child,
}) : super(onPressed: onPressed, child: child);
@override
Widget build(BuildContext context) {
print("CustomRaisedButton build");
return super.build(context);
}
}
複製程式碼
當我們點選“數字+1”這個按鈕的時候,會在日誌列印裡發現如下資訊:
I/flutter ( 489): CustomInheritedWidgetCreatorState build
I/flutter ( 489): widget=136741630
I/flutter ( 489): widget.data.hashCode=597399651
I/flutter ( 489): widget.child=443053943
I/flutter ( 489): Provider=611638398
I/flutter ( 489): DependOnInheritedWidgetState didChangeDependencies
I/flutter ( 489): DependOnInheritedWidgetState build
複製程式碼
說明CustomRaisedButton
不再build
了,注意:Provider
的of
方法中的dependOn
引數,為true
說明呼叫了該方法的widget
依賴了InheritedWidget
,為false
就沒有依賴InheritedWidget
,具體的可以看dependOnInheritedWidgetOfExactType
和getElementForInheritedWidgetOfExactType
這兩個方法的原始碼,這裡不再贅述。
至此,我們做了一個簡單的Provider
,大功告成!