直接開始幹,沒有為什麼~
上一篇看了官方的預設的計數 Demo ,在 2019 Google I/O 大會中, Provider 代替 Provide 成為官方推薦的狀態管理方式之一。
Provider 可以幹什麼?引用官方的一段話:
By using widgets for state management, provider can guarantee:
maintainability, through a forced uni-directional data-flow testability/composability, since it is always possible to mock/override a value robustness, as it is harder to forget to handle the update scenario of a model/widget
個人總結:提高專案可維護性,使結構更加清晰,重要的是提供單向資料流,實現更小單元的重新整理,提升效能。
接下來就用 Provider 改造下預設的技術Demo。
新增 Provider 依賴
在 pubspec.yaml 中增加 provider: ^3.1.0 ,然後點選 右上角 Packages get 就可以了,這裡說明下:
^ 表示適配和當前大版本一致的版本,~ 表示適配和當前小版本一致的版本,所以這裡的 ^ 可以不要。
dependencies:
flutter:
sdk: flutter
...
# Provider https://github.com/rrousselGit/provider
provider: ^3.1.0
複製程式碼
建立 ChangeNotifier
import 'package:flutter/foundation.dart';
class CounterNotifier with ChangeNotifier {
int _count = 0;
int get count => _count;
increment() {
_count++;
notifyListeners();
}
}
複製程式碼
使用 Provider.of 實現
- 使用 ChangeNotifierProvider.value 建立 Widget 持有 value (CounterNotifier())
- 使用 Provider.of 獲取 value (CounterNotifier())
import 'package:flutter/material.dart';
import 'package:flutter_provider_demo/CounterNotifier.dart';
import 'package:provider/provider.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 Demo Home Page'),
),
);
}
}
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(
'You have pushed the button this many times:',
),
Text(
'${counter.count}',
style: Theme.of(context).textTheme.display1,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
counter.increment();
},
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
}
複製程式碼
這樣就完成了使用 Provider 對預設計數 Demo 改造,使用 Provider.of 當ChangeNotifier 中呼叫 notifyListeners 時每次會重新呼叫 Widget 中的 build
使用 Consumer 實現
這裡實現個稍微複雜點的,建立 Page1 顯示 current count,然後建立 Page2 實現官方預設點選計數,看看 Provider 狀態管理效果。
構建 MyApp
- 這裡使用 MultiProvider 新增多個 Provider,當然也可以使用註釋掉的巢狀方式,願意的話
- Provider.value(value: 36) 用作字型大小
- ChangeNotifierProvider.value(value: CounterNotifier()) 用作 count 計數
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
// return Provider.value(
// value: 36,
// child: ChangeNotifierProvider.value(
// value: CounterNotifier(),
// child: MaterialApp(
// title: 'Privoder Demo',
// theme: ThemeData(
// primarySwatch: Colors.blue,
// ),
// home: Page1(),
// ),
// ),
// );
return MultiProvider(
providers: [
Provider.value(value: 36),
ChangeNotifierProvider.value(value: CounterNotifier())
],
child: MaterialApp(
title: 'Privoder Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Page1(),
),
);
}
}
複製程式碼
構建 Page1
- 使用 Provider.of(context).toDouble() 獲取文字大小
- 使用 Provider.of(context) 獲取計數
- 使用 Colum 列表方式展示計數和跳轉頁面按鈕
- 當 build的時候 print('rebuild page 1');
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'),
),
],
),
),
);
}
}
複製程式碼
構建 Page2
- 使用 Consumer2 獲取計數物件和文字大小
- 分別使用 print('rebuild page 2') 和 print('rebuild page 2 refresh count') 輸出呼叫相應 build 日誌
- 使用 Consumer 獲取計數物件,在點選 button 時呼叫 CounterNotifier 的 increment 方法
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),
),
);
}
}
複製程式碼
2019-09-14 22:21:31.393 14373-14467/com.joker.flutter_provider_demo I/flutter: rebuild page 1 2019-09-14 22:21:31.406 14373-14467/com.joker.flutter_provider_demo I/flutter: rebuild page 2 refresh count
通過這個日誌可以看出,使用 Consumer 可以實現區域性重新整理,所以它的效能更優,應該優先選用。但是 Consumer 只有 Consumer ~ Consumer6,沒有多的是實現。如果需要則只有自己實現。
一點建議:不要吝嗇逗號,這會讓你的程式碼結構看起來更爽
provider 提供了幾種不同型別得 XXProvider ,使用方法都大同小異
name | description |
---|---|
Provider | 最基本得 provider. 攜帶一個 value 並暴露它. |
ListenableProvider | Listenable 物件的特定 Provider 。ListenableProvider將監聽物件,以便在呼叫偵聽器時重建依賴它的 Widgets。 |
ChangeNotifierProvider | 一個具體的 ListenableProvider 監聽 ChangeNotifier 。它會在需要時自動呼叫ChangeNotifier.dispose。 |
ValueListenableProvider | 監聽 ValueListenable 並僅暴露 ValueListenable.value。 |
StreamProvider | 監聽 Stream 並暴露最新發出的值。 |
FutureProvider | 攜帶一個 Future,並在 Future 完成時更新依賴 |
以上就是 Provider 使用,最後奉上Demo地址