一、簡介
在Flutter開發中,或多或少的都會設計到頁面的多狀態管理,如果大家對Flutter技術比較熟悉的話,那麼應該知道下面的一些狀態管理框架,像Bloc,Getx我都用過,整體來說再狀態管理不是很複雜的情況下還是可以的。
接下來,我們來看一下Flutter官方推薦的狀態管理框架Provider是如何使用的。Flutter 針對不同型別物件提供了多種不同的 Provider;Provider 也是藉助了 InheritWidget,將共享狀態放到頂層 MaterialApp 之上;
- setState能重新整理widget子樹,重新整理範圍太大,並且需要把資料物件傳遞到子類。
- InheritedWidget不用傳遞資料物件,通過
context.dependOnInheritedWidgetOfExactType<ShareDataWidget>();
獲取父類的資料。但是重新整理範圍大,只能由上而下傳遞。 - Provider可以實現區域性重新整理。只要資料物件改變,UI能自動變化,實現響應式程式設計。遮蔽重新整理邏輯,實現響應式資料與UI的繫結。無論是子類或父類改變資料都能重新整理繫結的UI。
二、基本使用
使用之前,需要先在pubspec.yaml新增provider
dependencies:
flutter:
sdk: flutter
provider: ^6.0.2
2.1 Provider基本使用
Provider是一款基於資料流的觀察者模式,使用的第一步就是新建一個繼承自ChangeNotifier的資料管理類。下面,我們來看一下官方的例子使用Provider方式如何實現。
import 'package:flutter/cupertino.dart';
class CountProviderModel extends ChangeNotifier {
int _count = 0;
int get count => _count;
void increment() {
print("increment");
_count++;
notifyListeners();
}
}
在CountProviderModel類中,我們定義了有一個資料增加的方法,最後還呼叫notifyListeners傳送通知。接著,我們新建一個測試頁面,該頁面的根部使用ChangeNotifierProvider元件進行包裹,需要重新整理的地方使用Consumer元件進行包裹,用於消費
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'count_provider.dart';
class ProviderPage extends StatelessWidget {
const ProviderPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider<CountProviderModel>(
create: (_) => CountProviderModel(),
builder: (context, child) {
return Scaffold(
appBar: AppBar(
title: const Text("ProviderPage"),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text('You have pushed the button this many times:'),
Consumer<CountProviderModel>(
builder: (context, notifier, child) {
return Text("${notifier.count}",style: const TextStyle(
fontSize: 24,
), );
},
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () => context.read<CountProviderModel>().increment(),
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
},
);
}
}
最後,我們修改一下Flutter專案的入口main檔案。
// 改寫 main.dart
import 'package:flutter/material.dart';
import 'package:stateresearch/pages/ProviderPage.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter狀態管理',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: ProviderPage(),
);
}
}
重新執行專案,點選右下角的按鈕時數字就會自動加。
2.2 跨頁面狀態共享
作為一個全域性的狀態管理框架,跨頁面的狀態共享是必須的,為了方便說明,我們再新建兩個頁面ProviderPageTwo和ProviderPageThree,對應的程式碼如下:
ProviderPageTwo.dart
import 'package:flutter/material.dart';
import 'package:flutter_app/provider_three_page.dart';
import 'package:provider/provider.dart';
import 'count_provider.dart';
class ProviderPageTwo extends StatelessWidget {
const ProviderPageTwo({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("ProviderPageTwo"),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text('You have pushed the button this many times:'),
Consumer<CountProviderModel>(
builder: (context, notifier, child) {
return Text("${notifier.count}",style: const TextStyle(
fontSize: 24,
));
},
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
context.read<CountProviderModel>().increment();
// 2秒後跳轉至新的頁面
Future.delayed(const Duration(seconds: 2), () {
Navigator.of(context).push(MaterialPageRoute(builder: (BuildContext context) {
return ProviderPageThree();
}));
});
},
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
}
ProviderPageThree.dart
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'count_provider.dart';
class ProviderPageThree extends StatelessWidget {
const ProviderPageThree({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("ProviderPageThree"),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text('You have pushed the button this many times:'),
Consumer<CountProviderModel>(
builder: (context, notifier, child) {
return Text("${notifier.count}",style: const TextStyle(
fontSize: 24,
));
},
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () => Provider.of<CountProviderModel>(context, listen: false).increment(),
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
}
接著,我們在App頂層進行全域性監聽,因此,其他頁面無需 ChangeNotifierProvider也可以獲取 Model。
void main() {
runApp(ChangeNotifierProvider(
create: (_) => CountProviderModel(),
child: MyApp(),
),);
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: ProviderPageTwo(),
);
}
}
2.3 多Model全域性共享
除了前面的兩種使用場景,Provider還支援多Model的狀態共享。首先,我們再新建一個ProviderModel類。
import 'package:flutter/material.dart';
class ListProviderModel extends ChangeNotifier {
final List<String> _list = [];
List<String> get list => _list;
void push(String value) {
_list.add(value);
notifyListeners();
}
}
然後,我們修改ProviderPageTwo和ProviderPageThree兩個頁面,對應的程式碼如下:
ProviderPageTwo.dart
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter_app/provider_three_page.dart';
import 'package:provider/provider.dart';
import 'count_provider.dart';
import 'list_provider.dart';
class ProviderPageTwo extends StatelessWidget {
const ProviderPageTwo({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("ProviderPageTwo"),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text('You have pushed the button this many times:'),
Consumer<CountProviderModel>(
builder: (context, notifier, child) {
return Text("${notifier.count}");
},
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
context.read<CountProviderModel>().increment();
context.read<ListProviderModel>().push("List-${Random().nextInt(10)}");
Future.delayed(const Duration(seconds: 2), () {
Navigator.of(context).push(MaterialPageRoute(builder: (BuildContext context) {
return const ProviderPageThree();
}));
});
},
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
ProviderPageThree.dart
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'count_provider.dart';
import 'list_provider.dart';
class ProviderPageThree extends StatelessWidget {
const ProviderPageThree({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("ProviderPageThree"),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text('You have pushed the button this many times:'),
Consumer<CountProviderModel>(
builder: (context, notifier, child) {
return Text("${notifier.count}");
},
),
Consumer<ListProviderModel>(
builder: (context, notifier, child) {
return Text("${notifier.list}");
},
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () => Provider.of<CountProviderModel>(context, listen: false).increment(),
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
}
最後,我們修改main入口檔案的程式碼,使用MultiProvider 包裹多個Model,如下。
void main() {
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => CountProviderModel()),
ChangeNotifierProvider(create: (_) => ListProviderModel()),
],
child: const MyApp(),
),
);
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: ProviderPageTwo(),
);
}
}
三、總結
通過前面的例子可以看到,使用Provider時我們需要先新建一個Model物件,它繼承自ChangeNotifier,是一個被觀察的物件,當Model物件改變時需要呼叫notifyListeners通知觀察者重新整理。下面是ChangeNotifier的原始碼:
class ChangeNotifier implements Listenable {
ObserverList<VoidCallback>? _listeners = ObserverList<VoidCallback>();
@protected
bool get hasListeners {
return _listeners!.isNotEmpty;
}
@override
void addListener(VoidCallback listener) {
_listeners!.add(listener);
}
@override
void removeListener(VoidCallback listener) {
_listeners!.remove(listener);
}
@mustCallSuper
void dispose() {
_listeners = null;
}
@protected
@visibleForTesting
void notifyListeners() {
if (_listeners != null) {
final List<VoidCallback> localListeners = List<VoidCallback>.from(_listeners!);
for (final VoidCallback listener in localListeners) {
try {
if (_listeners!.contains(listener))
listener();
} catch (exception, stack) {
......
}
}
}
}
}
ChangeNotifier把方法新增到陣列中,然後呼叫notifyListeners時會通知觀察者實現,整個工作流程示意圖如下。