該文已授權(quán)公眾號 「碼個蛋」,轉(zhuǎn)載請指明出處
講了那么多的部件,這節(jié)我打算來點(diǎn)不太一樣的,可能會沒有部件那么好理解,也可能是我講的不夠簡單明了,總之系好安全帶,我們要準(zhǔn)備開車了。
Stream
在 dart 部分記得分享過 Stream 的文章鏈接,但是我知道你們肯定沒幾個愿意看的,所以這里再提下。還是得從源碼開始...因?yàn)樵创a的注釋比較長,就不貼注釋了,可以自己看,我這邊就提取一些關(guān)鍵信息。
Stream 是 Dart 提供的一種數(shù)據(jù)流訂閱管理的"工具",感覺有點(diǎn)像 Android 中的 EventBus 或者 RxBus,Stream 可以接收任何對象,包括是另外一個 Stream,接收的對象通過 StreamController 的 sink 進(jìn)行添加,然后通過 StreamController 發(fā)送給 Stream,通過 listen 進(jìn)行監(jiān)聽,listen 會返回一個 StreamSubscription 對象,StreamSubscription 可以操作對數(shù)據(jù)流的監(jiān)聽,例如 pause,resume,cancel 等。
Stream 分兩種類型:
-
Single-subscription Stream:單訂閱 stream,整個生命周期只允許有一個監(jiān)聽,如果該監(jiān)聽 cancel 了,也不能再添加另一個監(jiān)聽,而且只有當(dāng)有監(jiān)聽了,才會發(fā)送數(shù)據(jù),主要用于文件IO流的讀取等。 -
Broadcast Stream:廣播訂閱 stream,允許有多個監(jiān)聽,當(dāng)添加了監(jiān)聽后,如果流中有數(shù)據(jù)存在就可以監(jiān)聽到數(shù)據(jù),這種類型,不管是否有監(jiān)聽,只要有數(shù)據(jù)就會發(fā)送,用于需要多個監(jiān)聽的情況。
還是看下例子會比較直觀
class _StreamHomeState extends State<StreamHome> {
StreamController _controller = StreamController(); // 創(chuàng)建單訂閱類型 `StreamController`
Sink _sink;
StreamSubscription _subscription;
@override
void initState() {
super.initState();
_sink = _controller.sink; // _sink 用于添加數(shù)據(jù)
// _controller.stream 會返回一個單訂閱 stream,
// 通過 listen 返回 StreamSubscription,用于操作流的監(jiān)聽操作
_subscription = _controller.stream.listen((data) => print('Listener: $data'));
// 添加數(shù)據(jù),stream 會通過 `listen` 方法打印
_sink.add('A');
_sink.add(11);
_sink.add(11.16);
_sink.add([1, 2, 3]);
_sink.add({'a': 1, 'b': 2});
}
@override
void dispose() {
super.dispose();
// 最后要釋放資源...
_sink.close();
_controller.close();
_subscription.cancel();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(),
);
}
}
看下控制臺的輸出:

果然把所有的數(shù)據(jù)都打印出來了,前面有說過,單訂閱的 stream 只有當(dāng) listen 后才會發(fā)送數(shù)據(jù),不試試我還是不相信的,我們把 _sink.add 放到 listen 前面去執(zhí)行,再看控制臺的打印結(jié)果。居然真的是一樣的,Google 粑粑果然誠不欺我。接著試下 pause,resume 方法,看下數(shù)據(jù)如何監(jiān)聽,修改代碼
_sink = _controller.sink;
_subscription = _controller.stream.listen((data) => print('Listener: $data'));
_sink.add('A');
_subscription.pause(); // 暫停監(jiān)聽
_sink.add(11);
_sink.add(11.16);
_subscription.resume(); // 恢復(fù)監(jiān)聽
_sink.add([1, 2, 3]);
_sink.add({'a': 1, 'b': 2});
再看控制臺的打印,你們可以先猜下是什么結(jié)果,我猜大部分人都會覺得應(yīng)該是不會有 11 和 11.16 打印出來了。然鵝事實(shí)并非這樣,打印的結(jié)果并未發(fā)生變化,也就是說,調(diào)用 pause 方法后,stream 被堵住了,數(shù)據(jù)不繼續(xù)發(fā)送了。
接下來看下廣播訂閱 stream,對代碼做下修改
StreamController _controller = StreamController.broadcast();
Sink _sink;
StreamSubscription _subscription;
@override
void initState() {
super.initState();
_sink = _controller.sink;
_sink.add('A');
_subscription = _controller.stream.listen((data) => print('Listener: $data'));
_sink.add(11);
_subscription.pause();
_sink.add(11.16);
_subscription.resume();
_sink.add([1, 2, 3]);
_sink.add({'a': 1, 'b': 2});
}
// ...
}
我們再看下控制臺的打印:

你猜對答案了嗎,這邊做下小總結(jié):
單訂閱 Stream 只有當(dāng)存在監(jiān)聽的時候,才發(fā)送數(shù)據(jù),廣播訂閱 Stream 則不考慮這點(diǎn),有數(shù)據(jù)就發(fā)送;當(dāng)監(jiān)聽調(diào)用 pause 以后,不管哪種類型的 stream 都會停止發(fā)送數(shù)據(jù),當(dāng) resume 之后,把前面存著的數(shù)據(jù)都發(fā)送出去。
sink 可以接受任何類型的數(shù)據(jù),也可以通過泛型對傳入的數(shù)據(jù)進(jìn)行限制,比如我們對 StreamController 進(jìn)行類型指定 StreamController<int> _controller = StreamController.broadcast(); 因?yàn)闆]有對 Sink 的類型進(jìn)行限制,還是可以添加除了 int 外的類型參數(shù),但是運(yùn)行的時候就會報錯,_controller 對你傳入的參數(shù)做了類型判定,拒絕進(jìn)入。
Stream 中還提供了很多 StremTransformer,用于對監(jiān)聽到的數(shù)據(jù)進(jìn)行處理,比如我們發(fā)送 0~19 的 20 個數(shù)據(jù),只接受大于 10 的前 5 個數(shù)據(jù),那么可以對 stream 如下操作
_subscription = _controller.stream
.where((value) => value > 10)
.take(5)
.listen((data) => print('Listen: $data'));
List.generate(20, (index) => _sink.add(index));
那么打印出來的數(shù)據(jù)如下圖

除了 where,take 還有很多 Transformer, 例如 map,skip 等等,小伙伴們可以自行研究。了解了 Stream 的基本屬性后,就可以繼續(xù)往下了~
StreamBuilder
前面提到了 stream 通過 listen 進(jìn)行監(jiān)聽數(shù)據(jù)的變化,Flutter 就為我們提供了這么個部件 StreamBuilder 專門用于監(jiān)聽 stream 的變化,然后自動刷新重建。接著來看下源碼
const StreamBuilder({
Key key,
this.initialData, // 初始數(shù)據(jù),不傳入則為 null
Stream<T> stream,
@required this.builder
}) : assert(builder != null),
super(key: key, stream: stream);
@override
AsyncSnapshot<T> initial() => AsyncSnapshot<T>.withData(ConnectionState.none, initialData);
StreamBuilder 必須傳入一個 AsyncWidgetBuilder 參數(shù),初始值 initialData 可為空, stream 用于監(jiān)聽數(shù)據(jù)變化,initial 方法的調(diào)用在其父類 StremBuilderBase 中,接著看下 StreamBuilderBaseState 的源碼,這里我刪除一些不必要的源碼,方便查看,完整的源碼可自行查看
class _StreamBuilderBaseState<T, S> extends State<StreamBuilderBase<T, S>> {
// ...
@override
void initState() {
super.initState();
_summary = widget.initial(); // 通過傳入的初始值生成默認(rèn)值,如果沒有傳入則會是 null
_subscribe(); // 注冊傳入的 stream,用于監(jiān)聽變化
}
// _summary 為監(jiān)聽到的數(shù)據(jù)
@override
Widget build(BuildContext context) => widget.build(context, _summary);
// ...
void _subscribe() {
if (widget.stream != null) {
// stream 通過外部傳入,對數(shù)據(jù)的變化進(jìn)行監(jiān)聽,
// 在不同回調(diào)中,通過 setState 進(jìn)行更新 _summary
// 當(dāng) _summary 更新后,由于調(diào)用了 setState,重新調(diào)用 build 方法,將最新的 _summary 傳遞出去
_subscription = widget.stream.listen((T data) {
setState(() {
_summary = widget.afterData(_summary, data);
});
}, onError: (Object error) {
setState(() {
_summary = widget.afterError(_summary, error);
});
}, onDone: () {
setState(() {
_summary = widget.afterDone(_summary);
});
});
_summary = widget.afterConnected(_summary); //
}
}
}
在之前更新數(shù)據(jù)都需要通過 setState 進(jìn)行更新,這里了解完了 stream,我們就不使用 setState 更新,使用 Stream 來更新
class _StreamHomeState extends State<StreamHome> {
// 定義一個全局的 `StreamController`
StreamController<int> _controller = StreamController.broadcast();
// `sink` 用于傳入新的數(shù)據(jù)
Sink<int> _sink;
int _counter = 0;
@override
void initState() {
super.initState();
_sink = _controller.sink;
}
@override
void dispose() {
super.dispose();
// 需要銷毀資源
_sink.close();
_controller.close();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Container(
alignment: Alignment.center,
child: StreamBuilder(
builder: (_, snapshot) => Text('${snapshot.data}', style: TextStyle(fontSize: 24.0)),
stream: _controller.stream, // stream 在 StreamBuilder 銷毀的時候會自動銷毀
initialData: _counter,
),
)),
// 通過 `sink` 傳入新的數(shù)據(jù),去通知 `stream` 更新到 builder 中
floatingActionButton: FloatingActionButton(
onPressed: () => _sink.add(_counter++),
child: Icon(Icons.add),
),
);
}
}
那么當(dāng)點(diǎn)擊按鈕的時候,就會刷新界面上的值,通過上面的源碼分析,StreamBuilder 也是通過 setState 方法進(jìn)行刷新,那么兩種方法孰優(yōu)孰劣呢,當(dāng)然是通過 Stream 啦,這不是廢話嗎。因?yàn)橥ㄟ^調(diào)用 setState 刷新的話,會把整個界面都進(jìn)行重構(gòu),但是通過 StreamBuilder 的話,只刷新其 builder,這樣效率就更高了,最后看小效果吧,所謂有圖有真相嘛

這一步,我們摒棄了 setState 方法,那么下一步,我們試試把 StatefulWidget 替換成 StatelessWidget 吧,而且官方也推薦使用 StatelessWidget 替換 StatefulWidget,這里就需要提下 BLoC 模式了。
BLoC
說實(shí)話,現(xiàn)在 Google 下 「flutter bloc」能搜到很多文章,基本上都是通過 InheritedWidget 來實(shí)現(xiàn)的,例如這篇Flutter | 狀態(tài)管理探索篇——BLoC(三),但是 InheritedWidget 沒有提供 dispose 方法,那么就會存在 StreamController 不能及時銷毀等問題,所以,參考了一篇國外的文章,Reactive Programming - Streams - BLoC 這里通過使用 StatefulWidget 來實(shí)現(xiàn),當(dāng)該部件銷毀的時候,可以在其 dispose 方法中及時銷毀 StreamController,這里我還是先當(dāng)個搬運(yùn)工,搬下大佬為我們實(shí)現(xiàn)好的基類
abstract class BaseBloc {
void dispose(); // 該方法用于及時銷毀資源
}
class BlocProvider<T extends BaseBloc> extends StatefulWidget {
final Widget child; // 這個 `widget` 在 stream 接收到通知的時候刷新
final T bloc;
BlocProvider({Key key, @required this.child, @required this.bloc}) : super(key: key);
@override
_BlocProviderState<T> createState() => _BlocProviderState<T>();
// 該方法用于返回 Bloc 實(shí)例
static T of<T extends BaseBloc>(BuildContext context) {
final type = _typeOf<BlocProvider<T>>(); // 獲取當(dāng)前 Bloc 的類型
// 通過類型獲取相應(yīng)的 Provider,再通過 Provider 獲取 bloc 實(shí)例
BlocProvider<T> provider = context.ancestorWidgetOfExactType(type);
return provider.bloc;
}
static Type _typeOf<T>() => T;
}
class _BlocProviderState<T> extends State<BlocProvider<BaseBloc>> {
@override
void dispose() {
widget.bloc.dispose(); // 及時銷毀資源
super.dispose();
}
@override
Widget build(BuildContext context) {
return widget.child;
}
}
接著我們對前面的例子使用 BLoC 進(jìn)行修改。
首先,我們需要創(chuàng)建一個 Bloc 類,用于修改 count 的值
class CounterBloc extends BaseBloc {
int _count = 0;
int get count => _count;
// stream
StreamController<int> _countController = StreamController.broadcast();
Stream<int> get countStream => _countController.stream; // 用于 StreamBuilder 的 stream
void dispatch(int value) {
_count = value;
_countController.sink.add(_count); // 用于通知修改值
}
@override
void dispose() {
_countController.close(); // 注銷資源
}
}
在使用 Bloc 前,需要在最上層的容器中進(jìn)行注冊,也就是 MaterialApp 中
void main() => runApp(StreamApp());
class StreamApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
// 這里對創(chuàng)建的 bloc 類進(jìn)行注冊,如果說有多個 bloc 類的話,可以通過 child 進(jìn)行嵌套注冊即可
// 放在最頂層,可以全局調(diào)用,當(dāng) App 關(guān)閉后,銷毀所有的 Bloc 資源,
// 也可以在路由跳轉(zhuǎn)的時候進(jìn)行注冊,至于在哪里注冊,完全看需求
// 例如實(shí)現(xiàn)主題色的切換,則需要在全局定義,當(dāng)切換主題色的時候全局切換
// 又比如只有某個或者某幾個特殊界面調(diào)用,那么完全可以通過在路由跳轉(zhuǎn)的時候注冊
return BlocProvider(
child: MaterialApp(
debugShowCheckedModeBanner: false,
home: StreamHome(),
),
bloc: CounterBloc());
}
}
class StreamHome extends StatelessWidget {
@override
Widget build(BuildContext context) {
// 獲取注冊的 bloc,必須先注冊,再去查找
final CounterBloc _bloc = BlocProvider.of<CounterBloc>(context);
return Scaffold(
body: SafeArea(
child: Container(
alignment: Alignment.center,
child: StreamBuilder(
initialData: _bloc.count,
stream: _bloc.countStream,
builder: (_, snapshot) => Text('${snapshot.data}', style: TextStyle(fontSize: 20.0)),
),
)),
floatingActionButton:
// 通過 bloc 中的 dispatch 方法進(jìn)行值的修改,通知 stream 刷新界面
FloatingActionButton(onPressed: () =>
_bloc.dispatch(_bloc.count + 1), child: Icon(Icons.add)),
);
}
}
重新運(yùn)行后,查看效果還是一樣的。所以我們成功的對 StatefulWidget 進(jìn)行了替換
再繼續(xù)講之前,先總結(jié)下 Bloc
? 1. 成功的把頁面和邏輯分離開了,頁面只展示數(shù)據(jù),邏輯通過 BLoC 進(jìn)行處理
? 2. 減少了 setState 方法的使用,提高了性能
? 3. 實(shí)現(xiàn)了狀態(tài)管理
RxDart
因?yàn)樯厦娴膮⒖嘉恼轮刑岬搅?RxDart,個人覺得有必要了解下,當(dāng)然目前也有很多文章介紹 RxDart,所以我就講下和 BLoC 有點(diǎn)關(guān)系的部分吧。RxDart 需要通過引入插件的方式引入(rxdart: ^0.21.0)
如果需要查看詳細(xì)的內(nèi)容,我這里提供幾篇文章鏈接
RxDart: Magical transformations of Streams
其實(shí) RxDart 就是對 Stream 的進(jìn)一步分裝,RxDart 提供了三種 Subject,其功能類似 Stream 中的單訂閱 stream 和 廣播 stream。
-
PublishSubject/// PublishSubject is, by default, a broadcast (aka hot) controller, in order /// to fulfill the Rx Subject contract. This means the Subject's `stream` can /// be listened to multiple times.通過注釋可以發(fā)現(xiàn)
PuslishSubject不可被多次訂閱,盡管實(shí)現(xiàn)是通過StreamController<T>.broadcast方式實(shí)現(xiàn),其實(shí)三種都是通過broadcast方式實(shí)現(xiàn)的,所以實(shí)現(xiàn)的功能就是類似Single-subscription Stream的功能。 -
BehaviorSubject/// BehaviorSubject is, by default, a broadcast (aka hot) controller, in order /// to fulfill the Rx Subject contract. This means the Subject's `stream` can /// be listened to multiple times.BehaviorSubject可以被多次訂閱,那么這個就是實(shí)現(xiàn)了Broadcast Stream功能。 -
ReplaySubject/// ReplaySubject is, by default, a broadcast (aka hot) controller, in order /// to fulfill the Rx Subject contract. This means the Subject's `stream` can /// be listened to multiple times.ReplaySubject其實(shí)也是實(shí)現(xiàn)Broadcast Stream功能,那么它和BehaviorSubject的區(qū)別在哪呢,別急,等我慢慢講。/// As items are added to the subject, the ReplaySubject will store them. /// When the stream is listened to, those recorded items will be emitted to /// the listener.當(dāng)有數(shù)據(jù)添加了,但是還沒有監(jiān)聽的時候,它會將數(shù)據(jù)存儲下來,等到有監(jiān)聽了,再發(fā)送出去,也就是說,
ReplaySubject實(shí)現(xiàn)了Brodacast Stream的多訂閱功能,同時也實(shí)現(xiàn)了Single-subscription Stream的存儲數(shù)據(jù)的功能,每次添加了新的監(jiān)聽,都能夠獲取到全部的數(shù)據(jù)。當(dāng)然,這還不是它的全部功能,它還可以設(shè)置最大的監(jiān)聽數(shù)量,會只監(jiān)聽最新的幾個數(shù)據(jù),在注釋中,提供了這么兩個例子,可以看下/// ### Example /// /// final subject = new ReplaySubject<int>(); /// /// subject.add(1); /// subject.add(2); /// subject.add(3); /// /// subject.stream.listen(print); // prints 1, 2, 3 /// subject.stream.listen(print); // prints 1, 2, 3 /// subject.stream.listen(print); // prints 1, 2, 3 /// /// ### Example with maxSize /// /// final subject = new ReplaySubject<int>(maxSize: 2); // 實(shí)現(xiàn)監(jiān)聽數(shù)量限制 /// /// subject.add(1); /// subject.add(2); /// subject.add(3); /// /// subject.stream.listen(print); // prints 2, 3 /// subject.stream.listen(print); // prints 2, 3 /// subject.stream.listen(print); // prints 2, 3
那么我們可以使用 RxDart 對前面使用 Stream 實(shí)現(xiàn)的例子進(jìn)行替換,最簡單的其實(shí)只需要使用 BehaviorSubject 替換 StreamController.broadcast() 就可以了,別的都不需要變化。但是 RxDart 有自己的變量,還是按照 RxDart 的方式來
// 繼承自 StreamController,所以 StreamController 擁有的屬性都有
BehaviorSubject<int> _countController = BehaviorSubject();
// StreamController<int> _countController = StreamController.broadcast();
// 繼承自 Stream,所以這里直接用之前 stream 的寫法也沒問題,但是這樣就有點(diǎn)不 RxDart 了
Observable<int> get countStream => Observable(_countController.stream);
// Stream<int> get countStream => _countController.stream;
void dispatch(int value) {
_count = value;
// 直接提供了 add 方法,不需要通過 sink 來添加
_countController.add(_count);
// _countController.sink.add(_count);
}
再次運(yùn)行還是能過實(shí)現(xiàn)相同的效果。如果說要在 RxDart 和 Stream 兩種實(shí)現(xiàn)方式中選擇一種,個人更偏向于 RxDart,因?yàn)樗鼘?Stream 進(jìn)行了進(jìn)一步的封裝,提供了更多更方便的數(shù)據(jù)轉(zhuǎn)換方法,而且鏈?zhǔn)降膶懛ㄕ娴暮苁娣?,用過了就停不下來,具體的方法介紹可以參考上面提供的鏈接。
Provide
說實(shí)話自己封裝 BLoC 來實(shí)現(xiàn)分離邏輯和界面,相對還是有點(diǎn)難度的,這邊可以通過第三方來實(shí)現(xiàn),這邊推薦 Google 粑粑的庫,flutter_provide,看下官方對關(guān)鍵部件和靜態(tài)方法的介紹
Provide<T>- Widget used to obtain values from aProviderNodehigher up in the widget tree and rebuild on change. TheProvide<T>widget should only be used withStreams orListenables. Equivalent toScopedModelDescendantinScopedModelProvide.value<T>- Static method used to get a value from aProviderNodeusing theBuildContext. This will not rebuild on change. Similar to manually writing a static.of()method for anInheritedWidget.Provide.stream<T>- Static method used to get aStreamfrom aProviderNode. Only works if eitherTis listenable, or if theProvidercomes from aStream.Provider<T>- A class that returns a typed value on demand. Stored in aProviderNodeto allow retrieval usingProvide.ProviderNode- The equivalent of theScopedModelwidget. ContainsProviderswhich can be found as anInheritedWidget.
Provide 這個部件主要用于從上層的 ProvideNode 中獲取值,當(dāng)變化的時候刷新重建,只能同 Stream 和 Listenable 一同使用,類似于 ScopeMode 中的 ScopedModelDescendant。(這個部件放在需要狀態(tài)管理的部件的上層,例如有個 Text 需要修改狀態(tài),那么就需要在外層提供一個 Provide 部件,通過內(nèi)部 builder 參數(shù)返回 Text 部件)
Provide.value 是個靜態(tài)方法,用于從 ProvideNode 獲取值,但是當(dāng)接收的值改變的時候不會重建。類似于 InheritedWidget 的靜態(tài)方法 of。(這個方法用于獲取指定類型的 provide,每個 provide 都需要提供一個數(shù)據(jù)類,該類 with ChangeNotifier,當(dāng)數(shù)據(jù)變化的時候通過 notifyListeners 通知 provide 變化,進(jìn)行刷新重建)
Provide.stream 是個靜態(tài)方法,用于從 ProvideNode 獲取一個 stream,僅在 T 可被監(jiān)聽,或者 Provide 來自 stream 的情況下有效。(這個通常結(jié)合 StreamBuilder 使用,StreamBuilder 在上面已經(jīng)提到,就不多說了)
Provider 按需要的類型返回相關(guān)值的類,存儲在 ProviderNode 中方便 Provide 進(jìn)行檢索。(這個類主要是將我們自己創(chuàng)建的數(shù)據(jù)類通過 function 等方法轉(zhuǎn)換成 Provider,并在 Providers 中進(jìn)行注冊)
ProvideNode 類似于 ScopedModel 的一個部件,包含所有能被查找的 Providers。(這個需要放在頂層,方便下面的容器進(jìn)行查找 provider,刷新相應(yīng)的部件,一般放在 MaterialApp 上層)
這邊再補(bǔ)充一個個人覺得關(guān)鍵的類 Providers,這個類主要用于存儲定義的 Provider,主要是在建立 MaterialApp 的時候?qū)⑿枰玫降?Provider 通過 provide 方法添加進(jìn)去存儲起來,然后在 ProvideNode 中注冊所有的 provider 方便下層容器獲取值,并調(diào)用。
說那么多,還不如直接看個例子直接,代碼來了~,首先需要建立一個類似 BLoC 中監(jiān)聽數(shù)據(jù)變化的 counter_bloc 類的數(shù)據(jù)管理類,我們這邊定義為 count_provider 需要混入 ChangeNotifier 類
class CountProvider with ChangeNotifier {
int _value = 0; // 存儲的數(shù)據(jù),也是我們需要管理的狀態(tài)值
int get value => _value; // 獲取狀態(tài)值
void changeValue(int value) {
_value = value;
notifyListeners(); // 當(dāng)狀態(tài)值發(fā)生變化的時候,通過該方法刷新重建部件
}
}
然后需要將定義的類注冊到全局的 Providers 中
void main() {
final providers = Providers()
// 將我們創(chuàng)建的數(shù)據(jù)管理類,通過 Provider.function 方法轉(zhuǎn)換成 Provider,
// 然后添加到 Providers 中
..provide(Provider.function((_) => CountProvider()));
// 在 App 上層,通過包裹一層 ProvideNode,并將我們生成的 Providers 實(shí)例
// 注冊到 ProvideNode 中去,這樣整個 App 都可以通過 Provide.value 查找相關(guān)的 Provider
// 找到 Provider 后就可以找到我們的數(shù)據(jù)管理類
runApp(ProviderNode(child: StreamApp(), providers: providers));
}
接著就是替換我們的界面實(shí)現(xiàn)了,前面通過 BLoC 實(shí)現(xiàn),這里替換成 Provide 來實(shí)現(xiàn)
class StreamHome extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Container(
alignment: Alignment.center,
// 通過指定類型,獲取特定的 Provide,這個 Provide 會返回我們的數(shù)據(jù)管理類 provider
// 通過內(nèi)部定義的方法,獲取到需要展示的值
child: Provide<CountProvider>(builder: (_, widget, provider) => Text('${provider.value}')),
)),
floatingActionButton: FloatingActionButton(
onPressed: () =>
// 通過 value 方法獲取到我們的數(shù)據(jù)管理類 provider,
// 通過調(diào)用改變值的方法,修改內(nèi)部的值,并通知界面刷新重建
Provide.value<CountProvider>(context).changeValue(
Provide.value<CountProvider>(context).value + 1),
child: Icon(Icons.add))
);
}
}
本文代碼查看 bloc 包名下的所有文件,需要單獨(dú)運(yùn)行 stream_main.dart 文件
最后運(yùn)行后還是一樣的效果,也摒棄了 StatefulWidget 部件和 SetState 方法,實(shí)現(xiàn)了邏輯和界面分離。但是 Provide 最終還是通過 InheritedWidget 來實(shí)現(xiàn),當(dāng)然在資源方面 Google 的大佬們做了一些相關(guān)的處理,至于如何處理,這邊就不多說了。目前 provide 的這個庫還存在一點(diǎn)爭議的地方,具體查看 issue#3,但是目前來看并沒有太大的影響。當(dāng)然你不放心的話,可以使用 Scoped_model 或者上面的 Bloc 模式,Google 在文檔也有相關(guān)的注明
If you must choose a package today, it's safer to go with
package:scoped_modelthan with this package.
這篇概念性的比較多,但是等理解了以后,對于以后的開發(fā)還是非常有利的。
最后代碼的地址還是要的:
文章中涉及的代碼:demos
基于郭神
cool weather接口的一個項(xiàng)目,實(shí)現(xiàn)BLoC模式,實(shí)現(xiàn)狀態(tài)管理:flutter_weather一個課程(當(dāng)時買了想看下代碼規(guī)范的,代碼更新會比較慢,雖然是跟著課上的一些寫代碼,但是還是做了自己的修改,很多地方看著不舒服,然后就改成自己的實(shí)現(xiàn)方式了):flutter_shop
如果對你有幫助的話,記得給個 Star,先謝過,你的認(rèn)可就是支持我繼續(xù)寫下去的動力~