--- title: 'Flutter 狀態管理與共享數據' tags: Flutter disqus: hackmd --- # Flutter 狀態管理與共享數據 **目錄:** [TOC] ## 狀態管理 ### setState 內建的setState是其中一種狀態管理辦法。 他會讓需要狀態的元件都重新加載 > 由於每次setState需要rebuild widgets,如果是隔很多層的傳遞,需要被rebuild的widgets會越多,相對效能會比較差,僅適合小規模的更新。 ![](https://hackmd.io/_uploads/S12XgKga3.png) ## 共享數據 ### InheritedWidget 因為Flutter的特性,UI樹是一層包一層,若底層的widget若想取得上層的state會很麻煩(要一路傳下去) ![](https://hackmd.io/_uploads/S1QlGqlp3.png) 所以Flutter提供了InheritedWidget,讓底層widget可以方便取得上層的state。 大概像以前,如果有資料大家都要用的話,會用Singleton。 ![](https://hackmd.io/_uploads/BJJLmqep2.png) InheritedWidget這邊提到的繼承,是Widget樹上的繼承,並不是我們以前物件導向常提到的Class的繼承。 而且InheritedWidget是讓別人繼承,不是去繼承別人 ### 實作 widget架構: * WeatherPageInheritedWidget * appBar:DropdownButton * body:WeatherWidget * SkyWidget 1.建立InheritedWidget ```dart= class WeatherWidget extends InheritedWidget { //官方建議要用final, 這樣你就不能隨便去改它 final Weather todayWeather; //因為是要放在上層, 所以建構子一定要有child, 跟你要共享的資料 const WeatherWidget(this.todayWeather, Widget cho, {super.key}) : super(child: cho); //of方法是一個慣例, 代表這個widget是開放給大家取用, 官方建議這麼寫 static WeatherWidget? of(BuildContext context) { return context.dependOnInheritedWidgetOfExactType<WeatherWidget>(); //如果用getElementForInheritedWidgetOfExactType, 繼承的widget就不會call didChangeDependencies //return context.getElementForInheritedWidgetOfExactType<WeatherWidget>().widget; } //此widget更新後是否通知其他widget @override bool updateShouldNotify(WeatherWidget oldWidget) { return oldWidget.todayWeather != todayWeather; } } ``` 2.把InheritedWidget放在最上方 先把WeatherWidget做為body的root 然後利用WeatherPageInheritedWidget管理子widget的狀態 當appBar的DropdownButton改變後 就把state傳給WeatherWidget ```dart= class _WeatherPageInheritedWidget extends State<WeatherPageInheritedWidget> { Weather currentWeather = Weather.sunny; @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( appBar: AppBar( title: const Text( "天氣", ), actions: <Widget>[ DropdownButton( hint: null, value: currentWeather, items: const [ DropdownMenuItem(value: Weather.sunny, child: Text("晴天")), DropdownMenuItem(value: Weather.rainy, child: Text("雨天")), DropdownMenuItem( value: Weather.typhoon, child: Text("颱風")), DropdownMenuItem( value: Weather.blizzard, child: Text("暴風雪")), ], onChanged: (v) { setState(() { currentWeather = v ?? Weather.sunny; }); }) ], ), backgroundColor: Colors.red, body: WeatherWidget(currentWeather, const SkyWidget()))); } } ``` 3.取得InheritedWidget的狀態 先在build裡面透過of取得WeatherWidget的todayWeather 若InheritedWidget更新了 就會call didChangeDependencies 然後再call一次build ```dart= class _SkyWidgetState extends State<SkyWidget> { @override Widget build(BuildContext context) { Widget widgetIcon(IconData ico) { return Container( alignment: Alignment.center, color: Colors.blue, child: Icon(ico, color: Colors.white, size: 100), ); } switch (WeatherWidget.of(context)?.todayWeather) { case Weather.sunny: return widgetIcon(IcoFontIcons.sunny); case Weather.rainy: return widgetIcon(IcoFontIcons.rainy); case Weather.typhoon: return widgetIcon(IcoFontIcons.wind); case Weather.blizzard: return widgetIcon(IcoFontIcons.snow); default: return Container(child: null, color: Colors.red); } } @override void didChangeDependencies() { debugPrint('did change dependencies'); super.didChangeDependencies(); } } ``` PS. 不要直接去改InheritedWidget的state ![](https://hackmd.io/_uploads/Sy2LjiZT2.png) > InheritedWidget改善了撰寫的麻煩,不過同樣有些缺點: > * 無法將View與Logic部分分開。 > > * 無法定向通知。 > > * 每次更新都會通知所有Widgets,無法區分哪些Widgets需要被更新。 > 解決辦法可以透過**StreamBuilder**來監聽InheritedWidget中的**stream**的資料變化,然後判斷是否更新當前widget。 ## 改善方式 請觀看下篇[Flutter 狀態管理與共享機制工具- Provider](https://hackmd.io/wMHf6UemTh-brgacNUf9Zg?both)