前一篇我們提到InheritedWidget
,InheritedWidget
能夠與它的子孫控件建立依賴關係,並且當InheritedWidget
資料發生變化時,可以自動更新有依賴它的子孫控件,而Provider
利用這一點來解決跨widget 狀態共享的問題,將需要跨widget 共享的狀態存在InheritedWidget
中,然後在子widget 引用InheritedWidget
即可
然而InheritedWidget
還是存在著一些缺點,像是容易造成不必要的刷新,不支持跨頁面的資料傳遞 (如果不在同一個widget 樹,即無法共享資料),再來就是它共享的資料是不可改變的,如果想讓它監聽資料的變化並且重新構建InheritedWidget
,必須透過結合StatefulWidget
、ChangeNotifier
或Stream
來使用
我們在Android Studio 建立一個 provider_tutorial 的專案,來模擬Provider
大致上運作原理的範例:
建立一個InheritedWidget
能保存要共享的跨widget 狀態,其狀態使用泛型,在呼叫的時候會指定具體型別,來讓我們的InheritedWidget
通用於各種類型
inherited_provider.dart
:
import 'package:flutter/material.dart';
class InheritedProvider<T> extends InheritedWidget {
InheritedProvider({@required this.data, Widget child}) : super(child: child);
final T data;
@override
bool updateShouldNotify(InheritedProvider<T> old) {
//先都返回true,即每次更新此InheritedWidget,都通知有依賴共享數據的子widget 調用 didChangeDependencies 方法
return true;
}
}
接下來我們就要想辦法讓InheritedProvider
的資料發生變化的時候,來重新構建InheritedProvider
首先我們透過Flutter SDK中提供的ChangeNotifier
類別,它用於向監聽器發送通知。換言之,如果被定義為ChangeNotifier
,你可以訂閱它的狀態變化 (即觀察者模式)
ChangeNotifier
主要功能:
class ChangeNotifier implements Listenable {
List listeners=[];
//添加監聽器
@override
void addListener(VoidCallback listener) {
listeners.add(listener);
}
//移除監聽器
@override
void removeListener(VoidCallback listener) {
listeners.remove(listener);
}
//通知所有監聽器,觸發監聽器回調
void notifyListeners() {
listeners.forEach((item)=>item());
}
...
}
現在我們將要共享的狀態設計為一個 Model 類,然後讓它繼承ChangeNotifier
,這樣當共享的狀態改變時,我們只需要調用notifyListeners()
來通知訂閱者,然後由訂閱者來重新構建InheritedProvider
change_notifier_provider.dart
:
import 'package:flutter/material.dart';
import 'inherited_provider.dart';
// 將要共享的狀態設計為一個 Model 類,然後讓它繼承ChangeNotifier,透過泛型指定共享資料data 型別
class ChangeNotifierProvider<T extends ChangeNotifier> extends StatefulWidget {
ChangeNotifierProvider({
Key key,
this.data,
this.child,
});
final Widget child;
final T data;
//提供了靜態方法 of,來給子widget 獲取 InheritedProvider 所保存的共享資料 (設計的Model)
static T of<T>(BuildContext context) {
//透過dependOnInheritedWidgetOfExactType 取得InheritedProvider,並指定設計的Model 當作InheritedProvider 的共享狀態型別
final provider =
context.dependOnInheritedWidgetOfExactType<InheritedProvider<T>>();
return provider.data;
}
@override
_ChangeNotifierProviderState<T> createState() =>
_ChangeNotifierProviderState<T>();
}
//主要作用就是監聽到共享狀態 (model) 改變時重新構建Widget樹
class _ChangeNotifierProviderState<T extends ChangeNotifier>
extends State<ChangeNotifierProvider<T>> {
void update() {
//如果共享資料發生變化 (設計的model 類調用了notifyListeners),重新構建InheritedProvider
setState(() => {});
}
@override
void didUpdateWidget(ChangeNotifierProvider<T> oldWidget) {
//當Provider更新資料,將舊資料解除監聽,同時監聽新的資料
if (widget.data != oldWidget.data) {
oldWidget.data.removeListener(update);
widget.data.addListener(update);
}
super.didUpdateWidget(oldWidget);
}
@override
void initState() {
// 給model 新增監聽器
widget.data.addListener(update);
super.initState();
}
@override
void dispose() {
// 移除model 的監聽器
widget.data.removeListener(update);
super.dispose();
}
@override
Widget build(BuildContext context) {
return InheritedProvider<T>(
data: widget.data,
child: widget.child,
);
}
}
現在CartModel
已經通過ChangeNotifierProvider
讓我們能夠在widget 上建立監聽、訂閱,但我們該如何去使用它呢,Provider
提供Consumer
讓我們去完成這一步
Consumer
使用了Builder 模式,當收到更新通知就會通過 builder 重新構建,最好能把Consumer
放在widget樹盡量低的位置上,避免UI上任何一點小變化就全盤重新構建widget
consumer.dart
:
import 'package:flutter/material.dart';
import 'change_notifier_provider.dart';
//Consumer widget唯一必須的參數就是builder
//當ChangeNotifier 發生變化的時候會調用builder這個函數 (就是當我們設計的model 中調用notifyListeners() 時,所有和Consumer相關的builder方法都會被調用)
class Consumer<T> extends StatelessWidget {
Consumer({
Key key,
@required this.builder,
this.child,
}) : assert(builder != null),
super(key: key);
final Widget child;
final Widget Function(BuildContext context, T value) builder;
@override
Widget build(BuildContext context) {
return builder(
context,
ChangeNotifierProvider.of<T>(context), //取得共享資料 (設計的Model)
);
}
}
我們用購物車當範例來使用我們模擬的 Provider:
我們需要實現一個顯示購物車中所有商品總價的功能:向購物車中添加新商品時總價更新
定義一個Item
類,用於表示商品信息
cart_item.dart
:
class Item {
Item(this.price, this.count);
double price;
int count;
}
定義一個保存購物車內商品資料的CartModel
類
cart_model.dart
:
import 'dart:collection';
import 'package:flutter/material.dart';
import 'cart_item.dart';
class CartModel extends ChangeNotifier {
// 購物車的商品列表
final List<Item> _items = [];
// 禁止改變購物車裡的商品資訊
UnmodifiableListView<Item> get items => UnmodifiableListView(_items);
double get totalPrice =>
_items.fold(0, (value, item) => value + item.count * item.price);
void add(Item item) {
_items.add(item);
// 當購物車新增商品時,通知監聽器 (訂閱者),重新構建InheritedProvider,更新狀態
notifyListeners();
}
}
main.dart
:
import 'package:flutter/material.dart';
import 'package:provider_tutorial/cart_model.dart';
import 'cart_item.dart';
import 'change_notifier_provider.dart';
import 'consumer.dart';
void main() {
runApp(ChangeNotifierProvider<CartModel>(
data: CartModel(),
child: MyApp(),
));
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Flutter Provider Demo'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Column(
children: <Widget>[
Consumer<CartModel>(
builder: (context, cart) => Text(
"購物車總價: ${cart.totalPrice}",
style: Theme.of(context).textTheme.headline5,
),
),
Consumer<CartModel>(
builder: (context, cart) => RaisedButton(
child: Text("添加商品"),
onPressed: () {
//添加商品至購物車,添加後總價會更新,先固定用商品為 Item(10.0, 1)當範例
ChangeNotifierProvider.of<CartModel>(context)
.add(Item(10.0, 1));
},
),
),
],
),
],
),
),
);
}
}
跑出來的結果,每當按下按鈕,即會增加一筆商品 Item(10.0, 1),總價也會改變
此範例只是模擬大概的運作,還是有很多能改善的問題,我們之後就用 Provider Package
我們來在pubspec.yaml
添加依賴
...
dependencies:
flutter:
sdk: flutter
provider: ^4.3.2
...
我們一樣拿購物車當作範例:
首先一樣建立一個Item
類,用於表示商品信息
cart_item.dart
:
class Item {
Item(this.price, this.count);
double price;
int count;
}
定義一個保存購物車內商品資料的CartModel
類
cart_model.dart
:
import 'dart:collection';
import 'package:flutter/material.dart';
import 'cart_item.dart';
class CartModel extends ChangeNotifier {
final List<Item> _items = [];
UnmodifiableListView<Item> get items => UnmodifiableListView(_items);
double get totalPrice =>
_items.fold(0, (value, item) => value + item.count * item.price);
void add(Item item) {
_items.add(item);
notifyListeners();
}
}
接下來我們就可以直接使用 provider
,在widget 上建立監聽、訂閱,達成共享狀態資訊等等
main.dart
:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:provider_tutorial/cart_model.dart';
import 'cart_item.dart';
void main() {
runApp(ChangeNotifierProvider(
create: (context) => CartModel(),
child: MyApp(),
));
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Flutter Demo Home Page'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Consumer<CartModel>(
builder: (context, cartModel, child) => Text(
'總價:${cartModel.totalPrice}',
style: Theme.of(context).textTheme.headline5,
),
),
Builder(builder: (context) {
return RaisedButton(
child: Text("添加商品"),
onPressed: () {
var counter = context.read<CartModel>();
counter.add(Item(10.0, 1));
},
);
})
],
),
),
);
}
}
這樣就完成了,跑出來的結果就跟我們模擬的一樣
透過本篇的內容,希望大家能了解對Provider
的基本的原理與應用,下一篇將繼續對Provider
做補充