前言
今天偶然發現在谷歌爸爸的倉庫下出現了一個叫做flutter-provide的狀態管理框架,2月8日才第一次提交,非常新鮮。在簡單上手之後感覺就是一個字——爽!所以今天就跟大家分享一下這個新的狀態管理框架。
Provider被設計爲ScopedModel的替代品,並且允許我們更加靈活地處理數據類型和數據。但是首先呢還是先說說老生常談的狀態管理。
爲什麼需要狀態管理
在我們一開始構建應用的時候,也許很簡單。我們有一些狀態,直接把他們映射成視圖就可以了。這種簡單應用可能並不需要狀態管理。
但是隨着功能的增加,你的應用程序將會有幾十個甚至上百個狀態。這個時候你的應用應該會是這樣。
Wow,這是什麼鬼。我們很難再清楚的測試維護我們的狀態,因爲它看上去實在是太複雜了!而且還會有多個頁面共享同一個狀態,例如當你進入一個文章點贊,退出到外部縮略展示的時候,外部也需要顯示點贊數,這時候就需要同步這兩個狀態。
這時候,我們便迫切的需要一個架構來幫助我們理清這些關係,狀態管理框架應運而生。
什麼是Provide
和Scoped_model一樣,Provide也是藉助了InheritWidget,將共享狀態放到頂層MaterialApp之上。底層部件通過Provier獲取該狀態,並通過混合ChangeNotifier通知依賴於該狀態的組件刷新。
Provide還提供了Provide.stream,讓我們能夠以處理流的方式處理數據,不過目前還有一些問題,不推薦使用。
Lets do it!
我們這裏還是以一個簡單app爲例,詳細介紹Provide的用法。其中涉及共享狀態以及多個狀態之間如何管理。
這兩個頁面都同時依賴於counter 和 switcher兩個不同的狀態。並且一個頁面改變狀態之後另外一個頁面狀態也隨之改變。
該項目完整代碼已放在 Github
第一步:添加依賴
在pubspec.yaml中添加Provide的依賴。
- 實際添加請參考:https://pub.dartlang.org/packages/provide#-installing-tab-
- 由於版本衝突添加失敗請參考: https://juejin.im/post/5b8958d351882542b03e6d57
第二步:創建Model
這裏實際上它承擔了State的職責,但是爲了和官方的State區分所以叫做model。
import 'package:flutter/material.dart';
class Counter with ChangeNotifier{
int value = 0;
increment(){
value++;
notifyListeners();
}
}
這裏我們可以看到,數據和操作數據的方法都在model中,我們可以很清晰的把業務分離出來。
對比Scoped_model可以發現,Provide模式中model不再需要繼承Model類,只需要實現Listenable,我們這裏混入ChangeNotifier,可以不用管理聽衆。
通過 notifyListeners 我們可以通知聽衆刷新。
第三步:將狀態放入頂層
void main() {
var counter = Counter();
var providers = Providers();
//將counter對象添加進providers
providers.provide(Provider<Counter>.value(counter));
runApp(
ProviderNode(
child: MyApp(),
providers: providers),
);
}
ProviderNode封裝了InheritWidget,並且提供了
一個providers容器用於放置狀態。
ProviderScope 爲Provider提供單獨的類型空間,它允許多個相同類型的提供者。默認使用ProviderScope('_default'),存放的時候你可以通過ProviderScope("name")來指定key。
添加一組Provider的時候建議使用provideFrom或者provide方法,而不是provideAll,因爲它可以檢查編譯時的類型錯誤。
Provider<Counter>.value將counter包裝成了_ValueProvider。並在它的內部提供了StreamController從而實現對數據進行流式操作。
第四步:獲取狀態
同樣的Provide也提供了兩種獲取State的方法。我們先來介紹第一種,通過Provide<T>小部件獲取。
Provide<Counter>(
builder: (context, child, counter) {
return Text(
'${counter.value}',
style: Theme.of(context).textTheme.display1,
);
},
),
每次通知數據刷新時,builder將會重新構建這個小部件。
builder方法接收三個參數,這裏主要介紹第二個和第三個。
- 第二個參數child:假如這個小部件足夠複雜,內部有一些小部件是不會改變的,那麼我們可以將這部分小部件寫在Provide的child屬性中,讓builder不再重複創建這些小部件,以提升性能。
- 第三個參數counter:這個參數代表了我們獲取的頂層providers中的狀態<T>。
scope:通過指定ProviderScope獲取該鍵所對應的狀態。在需要使用多個相同類型狀態的時候使用。
第二種獲取方式:Provide.value<T>(context)
final currentCounter = Provide.value<Counter>(context);
這種方式實際上調用了context.inheritFromWidgetOfExactType找到頂層的_InheritedProviders來獲取到頂層providers中的狀態。
如何組織多個狀態
和scoped_model不同的是,provide模式中你可以輕鬆組織多個狀態。只需要將狀態provide進provider中就可以了。
void main() {
var counter = Counter();
var switcher = Switcher();
var providers = Providers();
providers
..provide(Provider<Counter>.value(counter))
..provide(Provider<Switcher>.value(switcher));
runApp(
ProviderNode(
child: MyApp(),
providers: providers)
);
}
獲取數據流
在將counter添加進providers的過程中進行了一次包裝。我們剛纔通過分析源碼知道了這個操作能夠讓我們處理流式數據。
通過 Provide.stream<T>(context) 就能獲取數據流。需要注意的是,這裏每次獲取的數據流都
StreamBuilder<Counter>(
initialData: currentCounter,
stream: Provide.stream<Counter>(context)
.where((counter) => counter.value % 2 == 0),
builder: (context, snapshot) =>
Text('Last even value: ${snapshot.data.value}')),
不過在我的使用當中出現了streamTransformer失效的情況。在firstScreen和secondScreen同樣應用這一段相同的代碼,second screen的where方法能夠生效,過濾掉奇數數據,而first screen中則是收到了完整的數據。
需要注意的是,這裏每次獲取的數據流都會重新創建一條新的流。
/// Creates a provider that listens to a stream and caches the last
/// received value of the stream.
/// This provider notifies for rebuild after every release.
factory Provider.stream(Stream<T> stream, {T initialValue}) =>
_StreamProvider<T>(stream, initialValue: initialValue);
關於這個做法還有一些爭議,具體可以查看這個issue:
https://github.com/google/flutter-provide/issues/3
不過這個功能還可以結合rxdart使用,可以通過stream輕鬆構建Observer,讓我們更加靈活的組織數據。
根據多個狀態重建小部件
當我們一個視圖可能依賴於多個狀態進行重建的時候,可以使用ProvideMulti小部件。
寫在最後
自從上次寫完狀態管理拓展篇Rxdart之後斷更了三個月。總結篇遲遲沒有出來,在這裏先說一聲抱歉。對於我來講狀態管理這個本身就是一個新鮮玩意,所以在沒有經過大型應用實戰檢驗的總結都是空談。
這也是爲什麼我遲遲沒有開始寫總結篇的原因。不過在這我可以說一些自己的感受,供大家參考。
在這幾個月中,我用的比較多的是BLoC,它組織數據確實非常靈活,可以很輕鬆的實現懶加載之類的操作。而且stateful widget寫的是越來越少了。缺點就是入門的門檻比較高,理解StreamTransformer和爲什麼需要pipe花了我不少時間。使用bloc思維方式需要比較大的改變,我看到了許多人在項目中使用bloc,但是用得很奇怪,還在以之前的思維模式思考。而且bloc只是對數據進行組織,共享狀態平時還是使用的InheritWidget,確實要做很多額外的功夫。
其次我比較喜歡的就是scoped_model,理由就是簡單好用。學習成本很低,而且沒有寫什麼模版代碼。
我最不想使用的狀態管理方式就是redux了,一個是入門難度比較高,而且對於異步數據處理我也覺得是相當麻煩的。但是閒魚團隊倒是喜歡redux,之後還會開源閒魚的狀態管理框架fish_redux。所以說,可能還是我編寫的應用還不夠複雜,纔會有這種感受。redux在複雜應用上能夠更加清楚的劃分職責,並且單向數據流以及state是immutable的特點這些都是redux的好處。
最後我再談談Provide。Provide整體上給我的體驗非常接近Scoped,簡單易上手,並且更加強大。model不用再繼承,只用實現Listenable讓它不再具有侵入性。於此同時又增加了stream的特性,和bloc的做法又有幾分相似。如果你使用過Scoped_model你會很快就上手。
不過可以說的是,Provide是一個非常優秀的狀態管理方式,值得你去使用。但是目前該package還存在一些問題,例如Provide.stream,在未來可能會進行較大的變動,需要慎重使用。
本次代碼已上傳Github: https://github.com/OpenFlutter/Flutter-Notebook/tree/master/mecury_project/example/flutter_provide
【附】相關資料:
Android進階
Android前沿技術
Flutter
移動架構師
需要這些資料的大夥關注+點贊+加羣:185873940 免費獲取!
羣內還有許多免費的關於高階安卓學習資料,包括高級UI、性能優化、架構師課程、 NDK、混合式開發:ReactNative+Weex等多個Android技術知識的架構視頻資料,還有職業生涯規劃及面試指導。