【Flutter 程式設計入門實戰 30 天】Day 29:Provider 狀態管理
【Flutter 程式設計入門實戰 30 天】線上教學課程目錄 使用 Dart 程式語言,開發 Android 和 iOS APP 應用程式。
哈囉~大家好,我是 KT ,今天實戰第二十九天,KT 將為大家來介紹,Provider 狀態管理。
狀態管理介紹
APP 應用程式常需要在不同的頁面中,共享應用程式的狀態資料,例如下方展示簡單的電商購物畫面,當使用者登入後,可以在商品介紹頁,挑選喜愛的商品,加入購物車,切換到購物車時可以看到剛剛加入的清單與項目,可以想像一下,當最後要結帳購買時,若使用者已登入,使用者不需要在最後結帳頁再次輸入帳號相關資料,會從一開始登入畫面時,就已經將相關狀態資料帶到結帳頁。簡單說就是在特定頁面新增或修改相關資料狀態,想要在其他頁面共享資料狀態,就可以透過今天狀態管理的功能來實作出此項功能。
購物車應用
若想學習此購物車範例應用,可以參考 Flutter 官方實作範例 github 原始碼:provider_shopper
狀態管理應用
狀態管理常應用在以下這些功能:
- 使用者偏好設定選項
- 使用者登入資料
- 電商購物車資料
狀態層級
狀態管理我們通常會放在 widget 元件樹中的最上層,好方便可以輕鬆的傳遞到其他底層頁面,以上方購物車應用範例中,我們可以看到 cart 購物車狀態放在 widget 元件樹中的最上層。當我們在購物項目中變化時,前往購物車頁則可以獲取新的變化資料。
Provider 套件
在 2019 Google I/O 官方大力推薦,狀態管理建議採用 Provider 套件來取代之前的 Provide 。提高程式碼維護性,使結構更清晰,更重要的是提供單向數據流,實現更小單元的刷新,大幅提升整體運作性能。
provider 有四個重要的概念:
- ChangeNotifier
- ChangeNotifierProvider
- Consumer
- Provider.of
ChangeNotifier
主要用在向已註冊的監聽者發送通知,ChangeNotifier 會呼叫 notifyListeners() 來通知狀態已改變,換句話說你可以註冊你想監聽變化狀態,當狀態改變,則可以透過此收到更新通知。
ChangeNotifierProvider
主要用在向其他子節點,底層的頁面, 提供 ChangeNotifier 的實例 (instance),所以 ChangeNotifierProvider 需放在要訪問狀態的元件之上。而如果想提供多個狀態可以使用 MultiProvider。
Consumer
當我們註冊的 ChangeNotifier 與 ChangeNotifierProvider關聯起來後,當發生狀態變化時,我們可以將要變化的資料透過 Consumer 來接收更改對應資料。而最好將 Consumer 放在 widget 樹較低的位置上。才不會 UI 上任何一點小變化,就需要全盤重新構建整個 widget。
Provider.of
因為透過 Consumer 更新資料會讓整體框架整個重構,有時我們只是需要訪問特定資料或調用特定方法,此時可以使用 Provider.of 並且將listen 設定為 false,則可以訪問特定數據,又不會讓整體 UI 框架重構。
加入 Provider 套件
在 pubspec.yaml 中加入 Provider 套件,
provider: ^3.1.0
PS. ^表示與當前大版號一致的版本,〜表示和當前小版號一致的版本。
加入位置實際範例
範例:使用 Provider 監聽計時器數字變化狀態
我們將建立兩個頁面:HomePage 和 BPage。HomePage 會顯示目前計數值,另外有一個按鈕,點擊可以前往 BPage。BPage 點擊右下角按鈕,計數值將會累加一,返回 HomePage 則可以同步獲取剛剛的計數變化值。
執行畫面
建立 ChangeNotifier
新增一個檔名 MyCountChangeNotifier 的 Dart 檔案。
import 'package:flutter/foundation.dart';
class MyCountChangeNotifier with ChangeNotifier {
// 設定一個整數私有變數 _count的欄位,初值為零
int _count = 0;
//可以透過 Consumer 來獲得當下 count 值
int get count => _count;
//當點擊右下角+ 浮動按鈕,會呼叫此方法
//此方法會將 _count 累加 1,並叫 notifyListeners
increment() {
_count++;
notifyListeners();
}
}
使用 Provider.of 獲取資料
//透過 Provider.of 來獲取資料
final counter = Provider.of<MyCountChangeNotifier>(context);
使用 Consumer 來接收更改對應資料
Consumer 接收一個參數、若有兩個參數使用 Consumer2,三個參數使用 Consumer3 以此類推,目前最多支援到 Consumer6,接收六個參數。
child: Consumer<MyCountChangeNotifier>(builder: (context, counter, _) {
...
...
...
}
完整程式碼
Main.dart
import 'package:flutter/material.dart';
import 'MyCountChangeNotifier.dart';
import 'package:provider/provider.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
//在 widget 元件樹中的最上層,使用 provider ,方便傳遞到其他底層頁面
//KT 建議採用 MultiProvider,因為一個 APP 很少一個 provider 就夠用,所以直接上 MultiProvider 。
return MultiProvider(
providers: [ChangeNotifierProvider.value(value: MyCountChangeNotifier())],
child: MaterialApp(
home: HomePage(),
),
);
}
}
//
class HomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
//透過 Provider.of 來獲取資料
final counter = Provider.of<MyCountChangeNotifier>(context);
return Scaffold(
appBar: AppBar(
title: Text('HKT 線上教室'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'目前計數值: ${counter.count}',
),
RaisedButton(
//點擊按鈕後,導轉跳到B頁
onPressed: () => Navigator.of(context).push(
MaterialPageRoute(builder: (context) => BPage()),
),
child: Text('跳到B頁'),
),
],
),
),
);
}
}
class BPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('B頁'),
),
body: Center(
// 透過 Consumer 來接收更改對應資料
child: Consumer<MyCountChangeNotifier>(builder: (context, counter, _) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'目前計數值:',
),
Text(
'${counter.count}',
),
],
);
}),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
// 使用 Provider.of,並且將 listen 設定為 false(若沒設定,預設為 true),
// 則不會再次調用 Widget 重新構建( build )畫面 ,更省效能。
Provider.of<MyCountChangeNotifier>(context, listen: false)
.increment();
},
child: const Icon(Icons.add),
),
);
}
}
MyCountChangeNotifier.dart
import 'package:flutter/foundation.dart';
class MyCountChangeNotifier with ChangeNotifier {
// 設定一個整數私有變數 _count的欄位,初值為零
int _count = 0;
//可以透過 Consumer 來獲得當下 count 值
int get count => _count;
//當點擊右下角+ 浮動按鈕,會呼叫此方法
//此方法會將 _count 累加 1,並叫 notifyListeners
increment() {
_count++;
notifyListeners();
}
}
那今天【iT邦幫忙鐵人賽】就介紹到這邊囉~
順帶一提,KT 線上教室,臉書粉絲團,會不定期發佈相關資訊,不想錯過最新資訊,不要忘記來按讚,加追蹤喔!也歡迎大家將這篇文章分享給更多人喔。
我們明天見囉!!!掰掰~
參考資料
HKT 線上教室
http://tw-hkt.blogspot.com/
http://tw-hkt.blogspot.com/
Background vector created by freepik
https://www.freepik.com
https://www.freepik.com
Data & backend
https://flutter.dev/docs/development/data-and-backend
https://flutter.dev/docs/development/data-and-backend