首先,當然可以看看,官方文件翻譯參考
一、為什麼需要Provider管理狀態
資料變化,資料共享,需要Provider
- Flutter的程式碼,是
響應式/宣告式
的。以前安卓/iOS的程式碼,是命令式
的。 響應式
的程式碼,基本都需要進行狀態管理
,也可以理解為資料共享
。介面、資料是變化的,就需要管理的,
簡單的直接在StatefulWidget進行管理就好,複雜的就是用Provider之類來管理
。
簡單和複雜資料的例子
資料的變化,怎麼算簡單的呢?——比如一個 PageView 元件中的當前頁面、一個複雜動畫中當前進度、一個 BottomNavigationBar 中當前被選中的 tab。這些在widget 樹,其他部分不需要訪問這種狀態。不需要去序列化這種狀態,這種狀態也不會以複雜的方式改變。
什麼資料變化需要Privider來管理呢?
舉例子,比如,使用者選項、登入資訊、一個社交應用中的通知、一個電商應用中的購物車、一個新聞應用中的文章已讀/未讀狀態
。
.
.
Flutter的狀態管理有Redux
、Rx
、hooks
、ScopedModel
, 和Provider
等,其中Provider
是官方推薦的。
如果你不瞭解其他的,那肯定是官方推薦的優先。
二、Provider的使用
嗯,Provider是官方推薦的。不熟悉的,可以看看這兩篇先。
二.1 使用步驟
引入 provider
dependencies:
provider: ^3.1.0
複製程式碼
關於 provider 的使用可以簡單理解為3步:
- 建立繼承自 ChangeNotifier 的共享類
- 設定資料
- 獲取資料,2種方式,分別是Provider.of(context) 和 Consumer
很簡單吧~
.
.
.
二.2 最簡單的例子 Provider.of(context) 方式
以下程式碼:
- 建立共享類,新增一個增長資料的方法,呼叫就重新整理
- 呼叫資料
- build重新呼叫,重新整理ui
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:flutter/foundation.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
// 建立 Widget 持有 CounterNotifier 資料
return ChangeNotifierProvider.value(
value: CounterNotifier(),
child: MaterialApp(
title: 'Privoder Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: ProvidePage(title: 'Provider 測試頁面'),
),
);
}
}
class ProvidePage extends StatelessWidget {
final String title;
ProvidePage({Key key, this.title}) : super(key: key);
@override
Widget build(BuildContext context) {
// 獲取 CounterNotifier 資料 (最簡單的方式)
final counter = Provider.of<CounterNotifier>(context);
return Scaffold(
appBar: AppBar(
title: Text(title),
),
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'按下按鈕,使數字增長:',
),
Text(
'${counter.count}',
style: Theme.of(context).textTheme.display1,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
counter.increment();
},
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
}
// 核心:繼承自ChangeNotifier
// 這種檔案本來應該單獨放在一個類檔案連的
class CounterNotifier with ChangeNotifier {
int _count = 0;
int get count => _count;
increment() {
_count++;
// 核心方法,通知重新整理UI,呼叫build方法
notifyListeners();
}
}
複製程式碼
.
.
效果:
注意
- 使用 Provider.of 當ChangeNotifier 中呼叫 notifyListeners 時每次會重新呼叫 Widget 中的 build
大概如此了。你可能誰說,直接 setState 就好了。不,這肯定不一樣,setState太不靈活了。
.
.
.
二.3 例子 Consumer 方式
例子:在頁面一設定1個值,然後在頁面2顯示出來 (不要問為什麼不直接用Navigator,演示演示,只為演示)
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
main() {
runApp(ChangeNotifierProvider<CounterNotifier>.value(
value: CounterNotifier(),
child: MyApp(),
));
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
// TODO: implement build
return MultiProvider(
providers: [
Provider.value(value: 36),
ChangeNotifierProvider.value(value: CounterNotifier())
],
child: MaterialApp(
title: 'Privoder Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Page1(),
),
);
}
}
class Page1 extends StatelessWidget {
@override
Widget build(BuildContext context) {
//獲取文字大小
final size = Provider.of<int>(context).toDouble();
// 獲取計數
final counter = Provider.of<CounterNotifier>(context);
// 呼叫 build 時輸出
print('rebuild page 1');
return Scaffold(
appBar: AppBar(
title: Text('Page1'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
// 顯示計數
Text(
'Current count: ${counter.count}',
// 設定文字大小
style: TextStyle(
fontSize: size,
),
),
SizedBox(
height: 50,
),
// 跳轉 Page2
RaisedButton(
onPressed: () => Navigator.of(context).push(
MaterialPageRoute(builder: (context) => Page2()),
),
child: Text('Next'),
),
],
),
),
);
}
}
class Page2 extends StatelessWidget {
@override
Widget build(BuildContext context) {
print('rebuild page 2');
return Scaffold(
appBar: AppBar(
title: Text('Page2'),
),
body: Center(
child: Consumer2<CounterNotifier, int>(
builder: (context, counter, size, _) {
print('rebuild page 2 refresh count');
return Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Text(
'${counter.count}',
style: TextStyle(
fontSize: size.toDouble(),
),
),
],
);
}),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
// 不需要監聽改變(listen: false 不會重新呼叫build)
Provider.of<CounterNotifier>(context, listen: false).increment();
},
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
}
class CounterNotifier with ChangeNotifier {
int _count = 0;
int get count => _count;
increment() {
_count++;
notifyListeners();
}
}
複製程式碼
.
注意
- 1、在Page2中,使用了Consumer
- 2、Provider.of的listen如果為false,不會重新呼叫build
.
頁面2設定數值,返回頁面1時,頁面1顯示的是頁面2的數值
.
.
.
對比
- 觸發者(Provider.of):如果只是需要獲取到資料model,
不需要監聽變化
(例如點選按鈕),推薦使用Provider.of(context, listen: false)來獲取資料model。 - 監聽者(推薦使用Consumer):推薦使用Consumer。
.
.
END
參考: Flutter開始干係列-狀態管理Provider3