天天看點

Flutter _ 狀态管理指南篇,Android開發兩年

),
  ); 
           
然後我們開始測試,點選收藏按鈕,檢視 rebuild 情況。

           

Performing hot reload…

Syncing files to device iPhone Xs Max…

flutter: No.8 rebuild

flutter: No.9 rebuild

flutter: No.10 rebuild

Reloaded 2 of 492 libraries in 446ms.

flutter: No.6 rebuild

flutter: No.1 rebuild

Cool! 現在隻有我們目前收藏的這個 Selector 進行了 rebuild,避免了整個清單全部重新整理。

你可以在[這裡]( )檢視該 Widget 的全部代碼。

You also need to know
=====================

合理選擇使用 Provides 的構造方法
---------------------

在上面這個例子中👆,我們選擇了使用 `XProvider<T>.value` 的構造方法來建立祖先節點中的 提供者。除了這種方式,我們還可以使用預設構造方法。

           

Provider({

Key key,

@required ValueBuilder builder,

Disposer dispose,

Widget child,

}) : this._(

key: key,

delegate: BuilderStateDelegate(builder, dispose: dispose),

updateShouldNotify: null,

child: child,

);

正常的 key/child 屬性我們不在這裡啰嗦。我們先來看這個看上去相對教複雜一點的 builder。

### ValueBuilder

相比起 `.value` 構造方式中直接傳入一個 value 就 ok,這裡的 builder 要求我們傳入一個 ValueBuilder。WTF?

`typedef ValueBuilder<T> = T Function(BuildContext context);`

其實很簡單,就是傳入一個 Function 傳回一個資料而已。在上面這個例子中,你可以替換成這樣。

           

Provider(

builder: (context) => textSize,

)

由于是 Builder 模式,這裡預設需要傳入 context,實際上我們的 Model(textSize)與 context 并沒有關系,是以你完全可以這樣寫。

           

Provider(

builder: (_) => textSize,

)

### Disposer

現在我們知道了 builder,那這個 dispose 方法又用來做什麼的呢。實際上這才是 Provider 的點睛之筆。

`typedef Disposer<T> = void Function(BuildContext context, T value);`

dispose 屬性需要一個 `Disposer<T>`,而這個其實也是一個回調。

如果你之前使用過 BLoC 的話,相信你肯定遇到過一個頭疼的問題。我應該在什麼時候釋放資源呢? BloC 使用了觀察者模式,它旨在替代 StatefulWidget。然而大量的流使用完畢之後必須 close 掉,以釋放資源。

然而 Stateless Widget 并沒有給我們類似于 dispose 之類的方法,這便是 BLoC 的硬傷。你不得不為了釋放資源而使用 StatefulWidget,這與我們的本意相違。而 Provider 則為我們解決了這一點。

當 Provider 所在節點被移除的時候,它就會啟動 `Disposer<T>`,然後我們便可以在這裡釋放資源。

舉個例子,假如我們有這樣一個 BLoC。

           

class ValidatorBLoC {

StreamController _validator = StreamController.broadcast();

get validator => _validator.stream;

validateAccount(String text) {

//Processing verification text …

}

dispose() {

_validator.close();

}

}

這時候我們想要在某個頁面提供這個 BLoC 但是又不想使用 StatefulWidget。這時候我們可以在頁面頂層套上這個 Provider。

           

Provider(

builder:() => ValidatorBLoC(),

dispose:(, ValidatorBLoC bloc) => bloc.dispose(),

}

)

這樣就完美解決了資料釋放的問題!🤩

現在我們可以放心的結合 BLoC 一起使用了,很贊有沒有。但是現在你可能又有疑問了,在使用 Provider 的時候,我應該選擇哪種構造方法呢。

我的推薦是,**簡單模型**就選擇 `Provider<T>.value`,好處是可以精确控制重新整理時機。而需要對資源進行釋放處理等**複雜模型**的時候,`Provider()` 預設構造方式絕對是你的最佳選擇。

其他幾種 Provider 也遵循該模式,需要的時候可以自行檢視源碼。

我該使用哪種 Provider
---------------

如果你在 Provider 中提供了可監聽對象(Listenable 或者 Stream)及其子類的話,那麼你會得到下面這個異常警告。

![](https://user-gold-cdn.xitu.io/2019/6/13/16b4ed4311992f6a?imageView2/0/w/1280/h/960/ignore-error/1)

你可以将本文中所使用到的 CounterModel 放入 Provider 進行提供(記得 hot restart 而不是 hot reload),那麼你就能看到上面這個 FlutterError 了。

你也可以在 main 方法中通過下面這行代碼來禁用此提示。 `Provider.debugCheckInvalidValueType = null;`

這是由于 Provider 隻能提供恒定的資料,不能通知依賴它的子部件重新整理。提示也說的很清楚了,假如你想使用一個會發生 change 的 Provider,請使用下面的 Provider。

*   ListenableProvider
*   ChangeNotifierProvider
*   ValueListenableProvider
*   StreamProvider

你可能會在這裡産生一個疑問,不是說(Listenable 或者 Stream)才不行嗎,為什麼我們的 CounterModel 混入的是 ChangeNotifier 但是還是出現了這個 FlutterError 呢。

`class ChangeNotifier implements Listenable`

我們再來看上面的這幾個 Provider 有什麼異同。先關注 `ListenableProvider / ChangeNotifierProvider` 這兩個類。

ListenableProvider 提供(provide)的對象是**繼承**了 Listenable 抽象類的子類。由于無法混入,是以通過繼承來獲得 Listenable 的能力,同時必須實作其 `addListener / removeListener` 方法,手動管理收聽者。顯然,這樣太過複雜,我們通常都不需要這樣做。

而混入了 `ChangeNotifier` 的類自動幫我們實作了聽衆管理,是以 ListenableProvider 同樣也可以接收混入了 ChangeNotifier 的類。

ChangeNotifierProvider 則更為簡單,它能夠對子節點提供一個 **繼承** / **混入** / **實作** 了 ChangeNotifier 的類。通常我們隻需要在 Model 中 `with ChangeNotifier` ,然後在需要重新整理狀态的時候調用 `notifyListeners` 即可。

那麼 **ChangeNotifierProvider** 和 **ListenableProvider** 究竟差別在哪呢,**ListenableProvider** 不是也可以提供(provide)混入了 ChangeNotifier 的 Model 嗎。

還是那個你需要思考的問題。你在這裡的 Model 究竟是一個簡單模型還是複雜模型。這是因為 ChangeNotifierProvider 會在你需要的時候,自動調用其 \_disposer 方法。

`static void _disposer(BuildContext context, ChangeNotifier notifier) => notifier?.dispose();`

我們可以在 Model 中重寫 ChangeNotifier 的 dispose 方法,來釋放其資源。這對于複雜 Model 的情況下十分有用。

現在你應該已經十厘清楚 `ListenableProvider / ChangeNotifierProvider` 的差別了。下面我們來看 ValueListenableProvider。

ValueListenableProvider 用于提供實作了 **繼承** / **混入** / **實作** 了 ValueListenable 的 Model。它實際上是專門用于處理隻有一個單一變化資料的 ChangeNotifier。

`class ValueNotifier<T> extends ChangeNotifier implements ValueListenable<T>`

通過 ValueListenable 處理的類**不再需要**資料更新的時候調用 `notifyListeners`。

好了,終于隻剩下最後一個 `StreamProvider` 了。

`StreamProvider` 專門用作提供(provide)一條 Single Stream。我在這裡僅對其核心屬性進行講解。

*   `T initialData`:你可以通過這個屬性聲明這條流的初始值。
*   `ErrorBuilder<T> catchError`:這個屬性用來捕獲流中的 error。在這條流 addError 了之後,你會能夠通過 `T Function(BuildContext context, Object error)` 回調來處理這個異常資料。實際開發中它非常有用。
*   `updateShouldNotify`:和之前的回調一樣,這裡不再贅述。

除了這三個構造方法都有的屬性以外,StreamProvider 還有三種不同的構造方法。

*   `StreamProvider(...)`:預設構造方法用作建立一個 Stream 并收聽它。
*   `StreamProvider.controller(...)`:通過 builder 方式建立一個 `StreamController<T>`。并且在 StreamProvider 被移除時,自動釋放 StreamController。
*   `StreamProvider.value(...)`:監聽一個已有的 Stream 并将其 value 提供給子孫節點。

除了上面這五種已經提到過的 Provider,還有一種 FutureProvider,它提供了一個 Future 給其子孫節點,并在 Future 完成時,通知依賴的子孫節點進行重新整理,這裡不再詳細介紹,需要的話自行檢視 api 文檔。

優雅地處理多個 Provider
----------------

在我們之前的例子中,我們使用了嵌套的方式來組合多個 Provider。這樣看上去有些傻瓜(我就是有一百個 Model 🙃)。

這時候我們就可以使用一個非常 sweet 的元件 —— `MultiProvider`。

這時候我們剛才那個例子就可以改成這樣。

           

void main() {

final counter = CounterModel();

final textSize = 48;

runApp(

MultiProvider(

providers: [

Provider.value(value: textSize),

ChangeNotifierProvider.value(value: counter)

],

child: MyApp(),

),

);

}

我們的代碼瞬間清晰很多,而且與剛才的嵌套做法**完全等**價。

Tips
====

保證 build 方法無副作用
---------------

build 無副作用也通常被人叫做,build 保持 pure,二者是一個意思。

通常我們經常會看到,為了擷取頂層資料我們會在 build 方法中調用 XXX.of(context) 方法。你必須**非常小心**,你的 build 函數不應該産生任何副作用,包括新的對象(Widget 以外),請求網絡,或作出一個映射視圖以外的操作等。

這是因為,你的根本無法控制什麼時候你的 build 函數将會被調用。我可以說**随時**。每當你的 build 函數被調用,那麼都會産生一個副作用。這将會發生非常恐怖的事情。🤯

我這樣說你肯定會感到比較抽象,我們來舉一個例子。

假如你有一個 `ArticleModel` 這個 Model 的作用是 **通過網絡** 擷取一頁 List 資料,并用 ListView 顯示在頁面上。

這時候,我們假設你在 build 函數中做了下面這些事情。

           

@override

Widget build(BuildContext context) {

final articleModel = Provider.of(context);

mainCategoryModel.getPage(); // By requesting data from the server

return XWidget(…);

}

我們在 build 函數中獲得了祖先節點中的 articleModel,随後調用了 getPage 方法。

這時候會發生什麼事情呢,當我們請求成功獲得了結果的時候,根據之前我們已經介紹過的,調用了 `Provider.of<T>(context);` 會重新運作其 build。這樣 getPage 就又被執行了一次。

而你的 Model 中每次請求 getPage 都會導緻 Model 中儲存的目前請求頁自增(第一次請求第一頁的資料,第二次請求第二頁的資料以此類推),那麼每次 build 都會導緻新的一次資料請求,并在新的資料 get 的時候請求下一頁的資料。你的伺服器挂掉那是遲早的事情。(come on baby!

> 由于 didChangeDependence 方法也會随着依賴改變而被調用,是以也需要保證它沒有副作用。具體解釋參見下面單頁面資料初始化。

是以你應該嚴格遵守這項原則,否則會導緻一系列糟糕的後果。

那麼怎麼解決資料初始化這個問題呢,請看 Q&A 部分。

不要所有狀态都放在全局
-----------

第二個小貼士是不要把你的所有狀态都放在頂層。開發者為了圖友善省事,再接觸了狀态管理之後經常喜歡把所有東西都放在頂層 MaterialApp 之上。這樣看上去就很友善共享資料了,我要資料就直接去擷取。

不要這麼做。嚴格區分你的全局資料與局部資料,資源不用了就要釋放!否則将會嚴重影響你的應用 performance。

盡量在 Model 中使用私有變量“\_”
---------------------

這可能是我們每個人在新手階段都會出現的疑問。為什麼要用私有變量呢,我在任何地方都能夠操作成員不是很友善嗎。

一個應用需要大量開發人員參與,你寫的代碼也許在幾個月之後被另外一個開發看到了,這時候假如你的變量沒有被保護的話,也許同樣是讓 count++,他會用 countController.sink.add(++\_count) 這種原始方法,而不是調用你已經封裝好了的 increment 方法。

雖然兩種方式的效果完全一樣,但是第二種方式将會讓我們的business logic零散的混入其他代碼中。久而久之項目中就會大量充斥着這些垃圾代碼增加項目代碼耦合程度,非常不利于代碼的維護以及閱讀。

是以,請務必使用私有變量保護你的 Model。

控制你的重新整理範圍
--------

在 Flutter 中,**組合**大于**繼承**的特性随處可見。常見的 Widget 實際上都是由更小的 Widget 組合而成,直到基本元件為止。為了使我們的應用擁有更高的性能,控制 Widget 的重新整理範圍便顯得至關重要。

我們已經通過前面的介紹了解到了,在 Provider 中擷取 Model 的方式會影響重新整理範圍。所有,請盡量使用 Consumer 來擷取祖先 Model,以維持最小重新整理範圍。

Q&A
===

在這裡對一些大家可能會有疑問的常見問題做一個回答,如果你還有這之外的疑問的話,歡迎在下方評論區一起讨論。

Provider 是如何做到狀态共享的
-------------------

這個問題實際上得分兩步。

### 擷取頂層資料

實際上在祖先節點中共享資料這件事我們已經在之前的文章中接觸過很多次了,都是通過系統的 InheritedWidget 進行實作的。

Provider 也不例外,在所有 Provider 的 build 方法中,傳回了一個 InheritedProvider。

`class InheritedProvider<T> extends InheritedWidget`

Flutter 通過在每個 Element 上維護一個 `InheritedWidget` 哈希表來向下傳遞 Element 樹中的資訊。通常情況下,多個 Element 引用相同的哈希表,并且該表僅在 Element 引入新的 `InheritedWidget` 時改變。

是以尋找祖先節點的時間複雜度為 O(1) 😎

### 通知重新整理

通知重新整理這一步實際上在講各種 Provider 的時候已經講過了,其實就是使用了 Listener 模式。Model 中維護了一堆聽衆,然後 notifiedListener 通知重新整理。(空間換時間🤣

為什麼全局狀态需要放在頂層 MaterialApp 之上
----------------------------

這個問題需要結合 Navigator 以及 BuildContext 來回答,在之前的文章中 [Flutter | 深入了解BuildContext]( ) 已經解釋過了,這裡不再贅述。

我應該在哪裡進行資料初始化
-------------

對于資料初始化這個問題,我們必須要分類讨論。

### 全局資料

當我們需要擷取全局頂層資料(就像之前 CounterApp 例子一樣)并需要做一些會産生額外結果的時候,main 函數是一個很好的選擇。

我們可以在 main 方法中建立 Model 并進行初始化的工作,這樣就隻會執行一次。

### 單頁面

如果我們的資料隻是在這個頁面中需要使用,那麼你有這兩種方式可以選擇。

#### StatefulWidget

這裡訂正一個錯誤,感謝 @曉傑的V笑 以及 @fantasy525 在讨論中幫我指出。

在之前文章的版本中我推薦大家在 State 的 didChangeDependence 中進行資料初始化。這裡其實是使用 BLoC 延續下來的習慣。因為使用了 InheritWidget 之後,隻有在 State 的 didChangeDependence 階段進行 Inherit 初始化,initState 階段是無法與資料建立持久聯系的(listen)。而由于 BLoC 是使用的 Stream,資料直接走 Stream 進來,由 StreamBuilder 去 listen,這樣 State 的依賴一直都隻是這個 Stream 對象而已,不會再次觸發 didChangeDependence 方法。那 Provider 有何不同呢。

           

/// If [listen] is

true

(default), later value changes will trigger a new

/// [State.build] to widgets, and [State.didChangeDependencies] for

/// [StatefulWidget].

源碼中的注釋解釋了,如果這個 `Provider.of<T>(context)` listen 了的話,那麼當 notifyListeners 的時候,就會觸發 context 所對應的 State 的 \[State.build\] 和 \[State.didChangeDependencies\] 方法。也就是說,如果你使用了非 Provider 提供的資料,例如 ChangeNotifierProvider 這樣會改變依賴的類,并且擷取資料時 `Provider.of<T>(context, listen: true)` 選擇 listen (預設就為 listen)的話,資料重新整理時會重新運作 didChangeDependencies 和 build 兩個方法。這樣一來對 didChangeDependencies 也會産生副作用。假如在這裡請求了資料,當資料到來的時候,又回觸發下一次請求,最終無限請求下去。

這裡除了副作用以外還有一點,假如資料改變是一個同步行為,例如這裡的 counter.increment 這樣的方法,在 didChangeDependencies 中調用的話,就會造成下面這個錯誤。

           

The following assertion was thrown while dispatching notifications for CounterModel:

flutter: setState() or markNeedsBuild() called during build.

flutter: This ChangeNotifierProvider widget cannot be marked as needing to build because the

flutter: framework is already in the process of building widgets. A widget can be marked as needing to be

flutter: built during the build phase only if one of its ancestors is currently building. This exception is

flutter: allowed because the framework builds parent widgets before children, which means a dirty descendant

flutter: will always be built. Otherwise, the framework might not visit this widget during this build phase.

這裡和和 Flutter 的建構算法有關。簡單來說,就是不能夠在 State 的 build 期間調用 setState() 或者 markNeedsBuild(),在我們這裡 didChangeDependence 的時候調用了此方法,導緻出現這個錯誤。異步資料則會由于 event loop 的緣故不會立即執行。想要深入了解的同學可以看閑魚技術的這篇文章:[Flutter快速上車之Widget]( )。

感覺處處都是坑啊,那該怎麼初始化呢。目前我找到的辦法是這樣,首先 要保證初始化資料不能夠産生副作用,我們需要找一個在 State 聲明周期内**一定**隻會運作一次的方法。initState 就是為此而生的。我們現在本身就在頁面頂層,頁面級别的 Model 就在頂層被建立,現在根本就不需要 Inherit。

           

class _HomeState extends State {

final _myModel = MyModel();

@override
           

void initState() {

super.initState();

_myModel.init();

}

}

頁面級别的 Model 資料都在頁面頂層 Widget 建立并初始化即可。

我們還需要考慮一種情況,假如這個操作是一個同步操作應該如何處理,就如我們之前舉的 CounterModel.increment 這個操作一樣。

           

void initState() {

super.initState();

WidgetsBinding.instance.addPostFrameCallback((callback){

Provider.of(context).increment();

});

}

我們通過 addPostFrameCallback 回調中在第一幀 build 結束時調用 increment 方法,這樣就不會出現建構錯誤了。

##### provider 作者 Remi 給出了另外一種方式

> This code is relatively unsafe. There's more than one reason for didChangeDependencies to be called.

> You probably want something similar to:

           

MyCounter counter;

@override

void didChangeDependencies() {

final counter = Provider.of(context);

if (conter != this.counter) {

this.counter = counter;

counter.increment();

}

}

> This should trigger increment only once.

也就是說初始化資料之前判斷一下這個資料是否已經存在。

#### cascade

你也可以在使用 dart 的級連文法 `..do()` 直接在頁面的 StatelessWidget 成員變量聲明時進行初始化。

           

class FirstScreen extends StatelessWidget {

CounterModel _counter = CounterModel()…increment();

double _textSize = 48;

}

使用這種方式需要注意,當這個 StatelessWidget 重新運作 build 的時候,狀态會丢失。這種情況在 TabBarView 中的子頁面切換過程中就可能會出現。

是以建議還是使用第一種,在 State 中初始化資料。

我需要擔心性能問題嗎
----------

是的,無論 Flutter 再怎麼努力優化,Provider 考慮的情況再多,我們總是有辦法讓應用卡爆 😂(開個玩笑)

僅當我們不遵守其行為規範的時候,會出現這樣的情況。性能會因為你的各種不當操作而變得很糟糕。我的建議是:遵守其規範,做任何事情都考慮對性能的影響,要知道 Flutter 把更新算法可是優化到了 O(N)。

Provider 僅僅是對 InheritedWidget 的一個更新,你不必擔心引入 Provider 會對應用造成性能問題。

為什麼選擇 Provider
--------------

Provider 不僅做到了提供資料,而且它擁有着一套完整的解決方案,覆寫了你會遇到的絕大多數情況。就連 BLoC 未解決的那個棘手的 dispose 問題,和 ScopedModel 的侵入性問題,它也都解決了。

然而它就是完美的嗎,并不是,至少現在來說。Flutter Widget 構模組化式很容易在 UI 層面上元件化,但是僅僅使用 Provider,Model 和 View 之間還是容易産生依賴。

我們隻有通過手動将 Model 轉化為 ViewModel 這樣才能消除掉依賴關系,是以假如各位有元件化的需求,還需要另外處理。

不過對于大多數情況來說,Provider 足以優秀,它能夠讓你開發出**簡單**、**高性能**、**層次清晰** 的應用。

我應該如何選擇狀态管理
-----------

介紹了這麼多狀态管理,你可能會發現,一些狀态管理之間職責并不沖突。例如 BLoC 可以結合 RxDart 庫變得很強大,很好用。而 BLoC 也可以結合 Provider / ScopedModel 一起使用。那我應該選擇哪種狀态管理方式呢。

我的建議是遵守以下幾點:

1.  使用狀态管理的目的是為了讓編寫代碼變得更簡單,任何會增加你的應用複雜度的狀态管理,統統都不要用。
2.  選擇自己能夠 hold 住的,BLoC / Rxdart / Redux / Fish-Redux 這些狀态管理方式都有一定上手難度,不要選自己無法了解的狀态管理方式。
3.  在做最終決定之前,敲一敲 demo,真正感受各個狀态管理方式給你帶來的 好處/壞處 然後再做你的決定。

希望能夠幫助到你。

源碼淺析
====

這裡在分享一點源碼淺析(真的很淺😅)

Flutter 中的 Builder 模式
---------------------

在 Provider 中,各種 Provider 的原始構造方法都有一個 builder 參數,這裡一般就用 `(_) => XXXModel()` 就行了。感覺有點多次一舉,為什麼不能像 `.value()` 構造方法那樣簡潔呢。

實際上,Provider 為了幫我們管理 Model,使用到了 delegation pattern。

builder 聲明的 ValueBuilder 最終被傳入代理類 `BuilderStateDelegate` / `SingleValueDelegate`。 然後通過代理類才實作的 Model 生命周期管理。

           

class BuilderStateDelegate extends ValueStateDelegate {

BuilderStateDelegate(this._builder, {Disposer dispose})

: assert(_builder != null),

_dispose = dispose;

final ValueBuilder _builder;

final Disposer _dispose;

T _value;

@override

T get value => _value;

@override

void initDelegate() {

super.initDelegate();

_value = _builder(context);

}

@override

void didUpdateDelegate(BuilderStateDelegate old) {

super.didUpdateDelegate(old);

_value = old.value;

}

@override

void dispose() {

_dispose?.call(context, value);

super.dispose();

}

}

這裡就僅放 BuilderStateDelegate,其餘的請自行檢視源碼。

如何實作 MultiProvider
------------------

           

Widget build(BuildContext context) {

var tree = child;

for (final provider in providers.reversed) {

tree = provider.cloneWithChild(tree);

}

return tree;

}

MultiProvider 實際上就是通過每一個 provider 都實作了的 cloneWithChild 方法把自己一層一層包裹起來。

           

MultiProvider(

providers:[

AProvider,

BProvider,

CProvider,

],

child: child,

)

等價于

           

AProvider(

child: BProvider(

child: CProvider(

child: child,

),

),

)

從 Provider 3.X 遷移到 4.0
======================

### Flutter SDK 版本

Provider 4.0 要求最低 Flutter SDK 版本需大于 v1.12.1,如果你的 SDK 小于這個版本,在擷取依賴時就會收到這個報錯。

           

The current Flutter SDK version is xxx

Because provider_example depends on provider >=4.0.0-dev which requires Flutter SDK version >=1.12.1, version solving failed.

pub get failed (1)

Process finished with exit code 1

是以你的項目若不是基于 Flutter v1.12.1 以上的話,則無需更新 Provider。

### Breaking Changes

#### `builder` 和 `initialBuilder` 改名

(在“提供資料”的地方可能會出現)

*   `initialBuilder` 更名為 `create`
*   "proxy" provider 的 `builder` 改為 `update`
*   經典 provider 的 `builder` 改名為 `create`

#### 提供懶加載

新的 `create` / `update` 現在是**懶加載**的,這意味着它們不是在 Provider 建立的時候就建立,而是在第一次被讀取值的時候才建立。

如果你不希望它是懶加載,你也可以通過聲明 `lazy: false` 關閉懶加載。

下面是一個樣例:

           

FutureProvider(

create: (_) async => doSomeHttpRequest(),

lazy: false,

child: …

)

#### 接口變化

SingleChildCloneableWidget 接口被删除,并被一種新型的控件 SingleChildWidget 所取代。

你可以在這個 [issue]( ) 裡檢視具體資訊。

#### Selector 增強

現在 Selector 的 shouldRebuild 支援對兩個 list 進行深比較,如果你不想要這個功能可以傳一個自定義的 `shouldRebuild`。

           

Selector<Selected, Consumed>(

shouldRebuild: (previous, next) => previous == next,

builder: …,

)

#### DelegateWidget 系列被移除

現在想要自定義 Provider 的話,可以直接繼承 InheritedProvider 或者其他已有的 Provider。

#### 新增使用提示

Provider 作者 Remi 在 Readme 中新增了許多實用的 Provider [使用提示]( ),建議大家都去看看。

樣例 App
======

你可以在 [FlutterNotebook]( ) 上檢視 Provider 樣例應用。

寫在最後
====

這次寫的太順暢,不小心就寫得過多了。能看到這裡的朋友,都很強 🤣。
### 最後

今天關于面試的分享就到這裡,還是那句話,有些東西你不僅要懂,而且要能夠很好地表達出來,能夠讓面試官認可你的了解,例如Handler機制,這個是面試必問之題。有些晦澀的點,或許它隻活在面試當中,實際工作當中你壓根不會用到它,但是你要知道它是什麼東西。

最後在這裡小編分享一份自己收錄整理上述技術體系圖相關的幾十套**騰訊、頭條、阿裡、美團等公司2021年的面試題**,把技術點整理成了視訊和PDF(實際上比預期多花了不少精力),包含**知識脈絡 + 諸多細節**,由于篇幅有限,這裡以圖檔的形式給大家展示一部分。

還有 **進階架構技術進階腦圖、Android開發面試專題資料**,進階進階架構資料 幫助大家學習提升進階,也節省大家在網上搜尋資料的時間來學習,也可以分享給身邊好友一起學習。

**[CodeChina開源項目:《Android學習筆記總結+移動架構視訊+大廠面試真題+項目實戰源碼》](https://codechina.csdn.net/m0_60958482/android_p7)**

![](https://img-blog.csdnimg.cn/img_convert/459e6e60de0a75728a8164cda74060e7.png)

**【算法合集】**

![](https://img-blog.csdnimg.cn/img_convert/ed8cd47abc8f7aa86f75da5bfb42f13b.png)

**【延伸Android必備知識點】**

![](https://img-blog.csdnimg.cn/img_convert/9f676ecc970e26fe7871dc20e730bbd8.png)

**【Android部分進階架構視訊學習資源】**

懂,而且要能夠很好地表達出來,能夠讓面試官認可你的了解,例如Handler機制,這個是面試必問之題。有些晦澀的點,或許它隻活在面試當中,實際工作當中你壓根不會用到它,但是你要知道它是什麼東西。

最後在這裡小編分享一份自己收錄整理上述技術體系圖相關的幾十套**騰訊、頭條、阿裡、美團等公司2021年的面試題**,把技術點整理成了視訊和PDF(實際上比預期多花了不少精力),包含**知識脈絡 + 諸多細節**,由于篇幅有限,這裡以圖檔的形式給大家展示一部分。

還有 **進階架構技術進階腦圖、Android開發面試專題資料**,進階進階架構資料 幫助大家學習提升進階,也節省大家在網上搜尋資料的時間來學習,也可以分享給身邊好友一起學習。

**[CodeChina開源項目:《Android學習筆記總結+移動架構視訊+大廠面試真題+項目實戰源碼》](https://codechina.csdn.net/m0_60958482/android_p7)**

[外鍊圖檔轉存中...(img-rVnuUZVu-1630926940367)]

**【算法合集】**

[外鍊圖檔轉存中...(img-4qToZWFt-1630926940369)]

**【延伸Android必備知識點】**

[外鍊圖檔轉存中...(img-dyl2AMED-1630926940370)]

**【Android部分進階架構視訊學習資源】**

**Android精講視訊領取學習後更加是如虎添翼!**進軍BATJ大廠等(備戰)!現在都說網際網路寒冬,其實無非就是你上錯了車,且穿的少(技能),要是你上對車,自身技術能力夠強,公司換掉的代價大,怎麼可能會被裁掉,都是淘汰末端的業務Curd而已!現如今市場上初級程式員泛濫,這套教程針對Android開發工程師1-6年的人員、正處于瓶頸期,想要年後突破自己漲薪的,進階Android中進階、架構師對你更是如魚得水,趕快領取吧!