文章系列
Flutter Provider狀態管理---介紹、類圖分析、基本使用
Flutter Provider狀態管理---八種提供者使用分析
Flutter Provider狀態管理---四種消費者使用分析
Flutter Provider狀態管理---MVVM架構實戰
視訊系列
Flutter Provider狀態管理---介紹、類圖分析、基本使用
Flutter Provider狀態管理---八種提供者使用分析
Flutter Provider狀態管理---四種消費者使用分析
Flutter Provider狀態管理---MVVM架構實戰
原始碼倉庫地址
前言
在我們上一篇文章中對Provider
進行了介紹以及類結構的說明,最後還寫了一個簡單的示例,通過上一章節我們對Provider
有了一個基本的瞭解,這一章節我們來說說Provider
的8種提供者以及他們的使用區別。
Provider
Provider
是最基本的Provider元件,可以使用它為元件樹中的任何位置提供值,但是當該值更改的時候,它並不會更新UI,下面我們給出一個示例
第一步:建立模型
class UserModel {
String name = "Jimi";
void changeName() {
name = "hello";
}
}
第二步:應用程式入口設定
return Provider<UserModel>(
create: (_) => UserModel(),
child: MaterialApp(
debugShowCheckedModeBanner: false,
home: ProviderExample(),
),
);
第三步:使用共享資料
關於Consumer
後面將消費者在提及,我們這裡只需要知道有兩個消費者,第一個用於展示模型的資料,第二個用於改變模型的資料。
- 第一個
Comsumer
是用於讀取模型的資料name
- 第二個
Consumer
用於改變模型的資料name
import 'package:flutter/material.dart';
import 'package:flutter_provider_example/provider_example/user_model.dart';
import 'package:provider/provider.dart';
class ProviderExample extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("ProviderExample"),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Consumer<UserModel>(
builder: (_, userModel, child) {
return Text(userModel.name,
style: TextStyle(
color: Colors.red,
fontSize: 30
)
);
},
),
Consumer<UserModel>(
builder: (_, userModel, child) {
return Padding(
padding: EdgeInsets.all(20),
child: ElevatedButton(
onPressed: (){
userModel.changeName();
},
child: Text("改變值"),
),
);
},
),
],
),
),
);
}
}
執行結果
我們點選按鈕的會導致模型資料改變,但是模型資料改變之後UI並沒有變化也沒有重建,那是因為Provider
提供者元件不會監聽它提供的值的變化。
ChangeNotifierProvider
它跟Provider
元件不同,ChangeNotifierProvider
會監聽模型物件的變化,而且當資料改變時,它也會重建Consumer
(消費者),下面我們給出一個示例
第一步:建立模型
細心點我們可以發現這裡定義的模型有兩處變化,如下:
- 混入了
ChangeNotifier
- 呼叫了
notifyListeners()
因為模型類使用了ChangeNotifier
,那麼我們就可以訪問notifyListeners()
並且在呼叫它的任何時候,ChangeNotifierProvider
都會收到通知並且消費者將重建UI。
import 'package:flutter/material.dart';
class UserModel1 with ChangeNotifier {
String name = "Jimi";
void changeName() {
name = "hello";
notifyListeners();
}
}
第二步:應用程式入口設定
return ChangeNotifierProvider<UserModel1>(
create: (_) => UserModel1(),
child: MaterialApp(
debugShowCheckedModeBanner: false,
home: ChangeNotifierProviderExample(),
),
);
第三步:使用共享資料
import 'package:flutter/material.dart';
import 'package:flutter_provider_example/change_notifier_provider_example/user_model1.dart';
import 'package:provider/provider.dart';
class ChangeNotifierProviderExample extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("ChangeNotifierProvider"),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Consumer<UserModel1>(
builder: (_, userModel, child) {
return Text(userModel.name,
style: TextStyle(
color: Colors.red,
fontSize: 30
)
);
},
),
Consumer<UserModel1>(
builder: (_, userModel, child) {
return Padding(
padding: EdgeInsets.all(20),
child: ElevatedButton(
onPressed: (){
userModel.changeName();
},
child: Text("改變值"),
),
);
},
),
],
),
),
);
}
}
執行結果
FutureProvider
簡單來說,FutureProvider
用於提供在元件樹中準備好使用其值時可能尚未準備好的值,主要是確保空值不會傳遞給任何子元件,而且FutureProvider
有一個初始值,子元件可以使用該Future
值並告訴子元件使用新的值來進行重建。
注意:
FutureProvider
只會重建一次- 預設顯示初始值
- 然後顯示
Future
值 - 最後不會再次重建
第一步:建立模型
這裡和Provider
不同的是增加了建構函式,以及changeName
變成了Future
,我們模擬網路請求延遲兩秒後改變其值。
class UserModel2{
UserModel2({this.name});
String? name = "Jimi";
Future<void> changeName() async {
await Future.delayed(Duration(milliseconds: 2000));
name = "hello";
}
}
第二步:提供Future
我們有一個方法,就是非同步獲取userModel2
,模擬網路請求延遲兩秒執行,最後修改了name
並返回UserModel2
import 'package:flutter_provider_example/future_provider_example/user_model2.dart';
class UserFuture {
Future<UserModel2> asyncGetUserModel2() async {
await Future.delayed(Duration(milliseconds: 2000));
return UserModel2(name: "獲取新的資料");
}
}
第三步:應用程式入口設定
initialData
是預設值,create
引數我們傳了一個Future<UserModel2>
,因為它接收的模型Create<Future<T>?>
return FutureProvider<UserModel2>(
initialData: UserModel2(name: "hello"),
create: (_) => UserFuture().asyncGetUserModel2(),
child: MaterialApp(
debugShowCheckedModeBanner: false,
home: FutureProviderExample(),
),
);
第四步:使用共享資料
import 'package:flutter/material.dart';
import 'package:flutter_provider_example/future_provider_example/user_model2.dart';
import 'package:provider/provider.dart';
class FutureProviderExample extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("FutureProviderExample"),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Consumer<UserModel2>(
builder: (_, userModel, child) {
return Text(userModel.name ?? "",
style: TextStyle(
color: Colors.red,
fontSize: 30
)
);
},
),
Consumer<UserModel2>(
builder: (_, userModel, child) {
return Padding(
padding: EdgeInsets.all(20),
child: ElevatedButton(
onPressed: (){
userModel.changeName();
},
child: Text("改變值"),
),
);
},
),
],
),
),
);
}
}
執行結果
我們可以看到先展示預設值hello
,最後獲取到結果的時候展示了獲取新的資料
,我們嘗試改變其值,雖然值改變但是並沒有重新整理UI。
StreamProvider
StreamProvider
提供流值,是圍繞StreamBuilder
,所提供的值會在傳入的時候替換掉新值。和FutureProvider
一樣,主要的區別在於值會根據多次觸發重新構建UI。
如果你對StreamBuilder
不太瞭解的話,那麼你就很難理解StreamProvider
,StreamProvider文件地址
第一步:建立模型
class UserModel3{
UserModel3({this.name});
String? name = "Jimi";
void changeName() {
name = "hello";
}
}
第二步:提供Stream
下面這段程式碼類似計時器,每隔一秒鐘生成一個數字
import 'package:flutter_provider_example/stream_provider_example/user_model3.dart';
class UserStream {
Stream<UserModel3> getStreamUserModel() {
return Stream<UserModel3>.periodic(Duration(milliseconds: 1000),
(value) => UserModel3(name: "$value")
).take(10);
}
}
第三步:應用程式入口設定
這裡也有initialData
初始值,和FutureProvider
類似,只是create
屬性是獲取一個Stream
流。
return StreamProvider<UserModel3>(
initialData: UserModel3(name: "hello"),
create: (_) => UserStream().getStreamUserModel(),
child: MaterialApp(
debugShowCheckedModeBanner: false,
home: StreamProviderExample(),
),
);
第四步:使用共享資料
import 'package:flutter/material.dart';
import 'package:flutter_provider_example/stream_provider_example/user_model3.dart';
import 'package:provider/provider.dart';
class StreamProviderExample extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("StreamProviderExample"),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Consumer<UserModel3>(
builder: (_, userModel, child) {
return Text(userModel.name ?? "",
style: TextStyle(
color: Colors.red,
fontSize: 30
)
);
},
),
Consumer<UserModel3>(
builder: (_, userModel, child) {
return Padding(
padding: EdgeInsets.all(20),
child: ElevatedButton(
onPressed: (){
userModel.changeName();
},
child: Text("改變值"),
),
);
},
),
],
),
),
);
}
}
執行結果
MultiProvider
在上面的例子中我們都只是返回了一個提供者,在實際開發過程中肯定會有多個提供者,我們雖然可以採用巢狀的方式來解決,但是這樣無疑是混亂的,可讀性級差。這個時候強大的MultiProvder
就產生了,我們來看下示例:
第一步:建立兩個模型
import 'package:flutter/material.dart';
class UserModel1 with ChangeNotifier {
String name = "Jimi";
void changeName() {
name = "hello";
notifyListeners();
}
}
class UserModel4 with ChangeNotifier {
String name = "Jimi";
int age = 18;
void changeName() {
name = "hello";
age = 20;
notifyListeners();
}
}
第二步:應用程式入口設定
相對於方式一這種巢狀方式設定,方式二就顯得尤為簡單。
方式一:巢狀設定
return ChangeNotifierProvider<UserModel1>(
create: (_) => UserModel1(),
child: ChangeNotifierProvider<UserModel4>(
create: (_) => UserModel4(),
child: MaterialApp(
debugShowCheckedModeBanner: false,
home: MultiProviderExample(),
),
),
);
方式二:使用MultiProvider
return MultiProvider(
providers: [
ChangeNotifierProvider<UserModel1>(create: (_) => UserModel1()),
ChangeNotifierProvider<UserModel4>(create: (_) => UserModel4()),
/// 新增更多
],
child: MaterialApp(
debugShowCheckedModeBanner: false,
home: MultiProviderExample(),
),
);
第三步:使用共享資料
import 'package:flutter/material.dart';
import 'package:flutter_provider_example/change_notifier_provider_example/user_model1.dart';
import 'package:flutter_provider_example/multi_provider_example/user_model4.dart';
import 'package:provider/provider.dart';
class MultiProviderExample extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("MultiProviderExample"),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Consumer<UserModel1>(
builder: (_, userModel, child) {
return Text(userModel.name,
style: TextStyle(
color: Colors.red,
fontSize: 30
)
);
},
),
Consumer<UserModel4>(
builder: (_, userModel, child) {
return Text(userModel.age.toString(),
style: TextStyle(
color: Colors.green,
fontSize: 30
)
);
},
),
Consumer2<UserModel1, UserModel4>(
builder: (_, userModel1, userModel4, child) {
return Padding(
padding: EdgeInsets.all(20),
child: ElevatedButton(
onPressed: (){
userModel1.changeName();
userModel4.changeName();
},
child: Text("改變值"),
),
);
},
),
],
),
),
);
}
}
執行結果
ProxyProvider
當我們有多個模型的時候,會有模型依賴另一個模型的情況,在這種情況下,我們可以使用ProxyProvider
從另一個提供者獲取值,然後將其注入到另一個提供者中。我們來看下程式碼演示
第一步:建立兩個模型
下面我們建立了兩個模型UserModel5
和WalletModel
,而WalletModel
依賴與UserModel5
,當呼叫WalletModel
的changeName
方法時會改變UserModel5
裡面的name,當然我們在實際開發的過程中並不是這麼簡單,這裡只是演示模型依賴時如果使用ProxyProvider
import 'package:flutter/material.dart';
class UserModel5 with ChangeNotifier {
String name = "Jimi";
void changeName({required String newName}) {
name = newName;
notifyListeners();
}
}
class WalletModel {
UserModel5? userModel5;
WalletModel({this.userModel5});
void changeName() {
userModel5?.changeName(newName: "JIMI");
}
}
第二步:應用程式入口設定
return MultiProvider(
providers: [
ChangeNotifierProvider<UserModel5>(create: (_) => UserModel5()),
ProxyProvider<UserModel5, WalletModel>(
update: (_, userModel5, walletModel) => WalletModel(userModel5: userModel5),
)
],
child: MaterialApp(
debugShowCheckedModeBanner: false,
home: ProxyProviderExample(),
),
);
第三步:使用共享資料
import 'package:flutter/material.dart';
import 'package:flutter_provider_example/proxy_provider_example/user_model5.dart';
import 'package:flutter_provider_example/proxy_provider_example/wallet_model.dart';
import 'package:provider/provider.dart';
class ProxyProviderExample extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("ProxyProviderExample"),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Consumer<UserModel5>(
builder: (_, userModel, child) {
return Text(userModel.name,
style: TextStyle(
color: Colors.red,
fontSize: 30
)
);
},
),
Consumer<UserModel5>(
builder: (_, userModel, child) {
return Padding(
padding: EdgeInsets.all(20),
child: ElevatedButton(
onPressed: (){
userModel.changeName(newName: "hello");
},
child: Text("改變值"),
),
);
},
),
Consumer<WalletModel>(
builder: (_, walletModel, child) {
return Padding(
padding: EdgeInsets.all(20),
child: ElevatedButton(
onPressed: (){
walletModel.changeName();
},
child: Text("通過代理改變值"),
),
);
},
),
],
),
),
);
}
}
執行結果
ChangeNotifierProxyProvider
和ProxyProvider
原理一樣,唯一的區別在於它構建和同步ChangeNotifier
的ChangeNotifierProvider
,當提供者資料變化時,將會重構UI。
下面我們給出一個例子:
- 獲取書籍列表
- 獲取收藏書籍列表
- 點選書籍可加入或者取消收藏
- 通過代理實時重構UI
第一步:建立兩個模型
1、BookModel
BookModel
使用者儲存模型資料,將書籍轉換成模型。
class BookModel {
static var _books = [
Book(1, "夜的命名數"),
Book(2, "大奉打更人"),
Book(3, "星門"),
Book(4, "大魏讀書人"),
Book(5, "我師兄實在太穩健了"),
Book(6, "深空彼岸"),
];
// 獲取書籍長度
int get length => _books.length;
// 根據ID獲取書籍
Book getById(int id) => _books[id -1];
// 根據索引獲取資料
Book getByPosition(int position) => _books[position];
// 更多....
}
class Book {
final int bookId;
final String bookName;
Book(this.bookId, this.bookName);
}
2、BookManagerModel
BookManagerModel
主要用於管理書籍、收藏書籍、取消收藏等操作
import 'package:flutter/material.dart';
import 'package:flutter_provider_example/change_notifier_proxy_provider_example/models/book_model.dart';
class BookManagerModel with ChangeNotifier {
// 依賴bookModel
final BookModel _bookModel;
// 獲取資料所有的ID
List<int>? _bookIds;
// 建構函式
BookManagerModel(this._bookModel, {BookManagerModel? bookManagerModel})
: _bookIds = bookManagerModel?._bookIds ?? [];
// 獲取所有的書
List<Book> get books => _bookIds!.map((id) => _bookModel.getById(id)).toList();
// 根據索引獲取資料
Book getByPosition(int position) => books[position];
// 獲取書籍的長度
int get length => _bookIds?.length ?? 0;
// 新增書籍
void addFaves(Book book) {
_bookIds!.add(book.bookId);
notifyListeners();
}
// 刪除書籍
void removeFaves(Book book) {
_bookIds!.remove(book.bookId);
notifyListeners();
}
}
第二步:應用程式入口設定
return MultiProvider(
providers: [
Provider(create: (_) => BookModel()),
ChangeNotifierProxyProvider<BookModel, BookManagerModel>(
create: (_) => BookManagerModel(BookModel()),
update: (_, bookModel, bookManagerModel) => BookManagerModel(bookModel),
)
],
child: MaterialApp(
debugShowCheckedModeBanner: false,
home: ChangeNotifierProxyProviderExample(),
),
);
第三步:設定BottomNavigationBar
import 'package:flutter/material.dart';
import 'package:flutter_provider_example/change_notifier_proxy_provider_example/pages/page_a.dart';
import 'package:flutter_provider_example/change_notifier_proxy_provider_example/pages/page_b.dart';
class ChangeNotifierProxyProviderExample extends StatefulWidget {
@override
_ChangeNotifierProxyProviderExampleState createState() => _ChangeNotifierProxyProviderExampleState();
}
class _ChangeNotifierProxyProviderExampleState extends State<ChangeNotifierProxyProviderExample> {
var _selectedIndex = 0;
var _pages = [PageA(), PageB()];
@override
Widget build(BuildContext context) {
return Scaffold(
body: _pages[_selectedIndex],
bottomNavigationBar: BottomNavigationBar(
currentIndex: _selectedIndex,
onTap: (index) {
setState(() {
_selectedIndex = index;
});
},
items: [
BottomNavigationBarItem(
icon: Icon(Icons.book),
label: "書籍列表"
),
BottomNavigationBarItem(
icon: Icon(Icons.favorite),
label: "收藏"
)
],
),
);
}
}
第四步:書籍列表UI構建
import 'package:flutter/material.dart';
import 'package:flutter_provider_example/change_notifier_proxy_provider_example/models/book_manager_model.dart';
import 'package:flutter_provider_example/change_notifier_proxy_provider_example/models/book_model.dart';
import 'package:flutter_provider_example/change_notifier_proxy_provider_example/widgets/book_item.dart';
import 'package:provider/provider.dart';
class PageA extends StatelessWidget {
@override
Widget build(BuildContext context) {
var bookModel = Provider.of<BookModel>(context);
return Scaffold(
appBar: AppBar(
title: Text("書籍列表"),
),
body: ListView.builder(
itemCount: bookModel.length,
itemBuilder: (_, index) => BookItem(id: index + 1),
),
);
}
}
第五步:收藏列表UI構建
import 'package:flutter/material.dart';
import 'package:flutter_provider_example/change_notifier_proxy_provider_example/models/book_manager_model.dart';
import 'package:flutter_provider_example/change_notifier_proxy_provider_example/widgets/book_item.dart';
import 'package:provider/provider.dart';
class PageB extends StatelessWidget {
@override
Widget build(BuildContext context) {
var bookManagerModel = Provider.of<BookManagerModel>(context);
var bookCount = bookManagerModel.length;
return Scaffold(
appBar: AppBar(
title: Text("收藏列表"),
),
body: ListView.builder(
itemCount: bookCount,
itemBuilder: (_, index) => BookItem(id: bookManagerModel.getByPosition(index).bookId),
),
);
}
}
其他輔助封裝類
import 'package:flutter/material.dart';
import 'package:flutter_provider_example/change_notifier_proxy_provider_example/models/book_manager_model.dart';
import 'package:flutter_provider_example/change_notifier_proxy_provider_example/models/book_model.dart';
import 'package:provider/provider.dart';
class BookButton extends StatelessWidget {
final Book book;
BookButton({
Key? key,
required this.book
}) : super(key: key);
@override
Widget build(BuildContext context) {
var bookManagerModel = Provider.of<BookManagerModel>(context);
return GestureDetector(
onTap: bookManagerModel.books.contains(this.book)
? () => bookManagerModel.removeFaves(this.book)
: () => bookManagerModel.addFaves(this.book),
child: SizedBox(
width: 100,
height: 60,
child: bookManagerModel.books.contains(this.book)
? Icon(Icons.star, color: Colors.red,)
: Icon(Icons.star_border),
),
);
}
}
import 'package:flutter/material.dart';
import 'package:flutter_provider_example/change_notifier_proxy_provider_example/models/book_model.dart';
import 'package:flutter_provider_example/change_notifier_proxy_provider_example/widgets/book_button.dart';
import 'package:provider/provider.dart';
class BookItem extends StatelessWidget {
final int id;
BookItem({
Key? key,
required this.id
}) : super(key: key);
@override
Widget build(BuildContext context) {
var bookModel = Provider.of<BookModel>(context);
var book = bookModel.getById(id);
return ListTile(
leading: CircleAvatar(
child: Text("${book.bookId}"),
),
title: Text("${book.bookName}",
style: TextStyle(
color: Colors.black87
),
),
trailing: BookButton(book: book),
);
}
}
執行結果
ListenableProxyProvider
ListenableProxyProvider
是ListenableProvider
的一個變體,但是在使用上和ChangeNotifierProvider
效果驚人的一致,如果大家對ListenableProxyProvider
有更深的理解,請聯絡我補充。
總結
Provider
為我們提供了非常多的提供者,總共有八種。但我們比較常用的是ChangeNotifierProvider
、MultiProvider
、ChangeNotifierProxyProvider
,關於其他的提供者可根據自己的實際應用場景來。