文章系列
Flutter Provider狀態管理---介紹、類圖分析、基本使用
Flutter Provider狀態管理---八種提供者使用分析
Flutter Provider狀態管理---四種消費者使用分析
Flutter Provider狀態管理---MVVM架構實戰
視訊系列
Flutter Provider狀態管理---介紹、類圖分析、基本使用
Flutter Provider狀態管理---八種提供者使用分析
Flutter Provider狀態管理---四種消費者使用分析
Flutter Provider狀態管理---MVVM架構實戰
原始碼倉庫地址
前言
在上一篇文章中我們對Provider
的8種提供者進行了詳細的描述以及用對應的案例說明他們的區別,那麼這一節我們來聊一聊Provider
的消費者,如果去優化你的專案結構以及它們的使用區別。
Provider.of
Provider.of<T>(context)
是Provider
為我們提供的靜態方法,當我們使用該方法去獲取值的時候會返回查詢到的最近的T
型別的provider
給我們,而且也不會遍歷整個元件樹,下面我們看下程式碼:
第一步:定義模型
我們定義了一個CountNotifier1
的模型,後面所有的示例程式碼將圍繞該模型來演示
import 'package:flutter/material.dart';
class CountNotifier1 with ChangeNotifier {
int count = 0;
void increment() {
count++;
notifyListeners();
}
}
第二步:應用程式入口設定
return ChangeNotifierProvider(
create: (_) => CountNotifier1(),
child: MaterialApp(
debugShowCheckedModeBanner: false,
home: ConsumerExample(),
),
);
第三步:使用Provider.of
這裡讀取值和點選按鈕+1時都是通過Provider.of<T>()
來獲取及使用。
import 'package:flutter/material.dart';
import 'package:flutter_provider_example/consumer_example/count_notifier1.dart';
import 'package:provider/provider.dart';
class ConsumerExample extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("ConsumerExample"),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(Provider.of<CountNotifier1>(context).count.toString(),
style: TextStyle(
color: Colors.red,
fontSize: 50
),
),
Padding(
padding: EdgeInsets.only(
top: 20
),
child: ElevatedButton(
onPressed: (){
Provider.of<CountNotifier1>(context).increment();
},
child: Text("點選加1"),
),
)
],
),
),
);
}
}
錯誤日誌
當我們執行程式碼的時候會提示一個報錯,它提示說試圖從Widget
樹外部監聽提供者公開的值,如果要修復可以把listen
改成false
,這個問題其實是在Provider 4.0.2
後會出現的,最主要的是它的預設行為就是ture
,錯誤日誌如下:
======== Exception caught by gesture ===============================================================
The following assertion was thrown while handling a gesture:
Tried to listen to a value exposed with provider, from outside of the widget tree.
This is likely caused by an event handler (like a button's onPressed) that called
Provider.of without passing `listen: false`.
To fix, write:
Provider.of<CountNotifier1>(context, listen: false);
It is unsupported because may pointlessly rebuild the widget associated to the
event handler, when the widget tree doesn't care about the value.
The context used was: ConsumerExample(dependencies: [_InheritedProviderScope<CountNotifier1?>])
'package:provider/src/provider.dart':
Failed assertion: line 276 pos 7: 'context.owner!.debugBuilding ||
listen == false ||
debugIsInInheritedProviderUpdate'
When the exception was thrown, this was the stack:
........
====================================================================================================
設定listen為false
Provider.of<CountNotifier1>(context, listen: false).increment();
執行結果
Consumer
Consumber
只是在Widget
中呼叫了Prvoider.of
,並將其構造實現委託給了構造器,比如我們常見的Builder
,如果你的Widget
依賴多個模型,那麼它還提供了Consumer23456
方便呼叫,我們接下來對上面的案例採用Consumer
來修改
用Consumer包裹元件
裡面有個builder
構造器,當我們把body
改成下面重新執行後可以發現和使用Provider.of
的結果一樣,但是這裡不需要在像使用Provider.of
那樣每次使用都要寫一大串的重複性程式碼。
裡面有三個屬性:
- context: 當前的上下文
- Provider.of<T>(context): 模型物件
- child: 子元件(不需要重新整理的部分)
body: Consumer(
builder: (_, CountNotifier1 countNotifier1, child) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(countNotifier1.count.toString(),
style: TextStyle(
color: Colors.red,
fontSize: 50
),
),
Padding(
padding: EdgeInsets.only(
top: 20
),
child: ElevatedButton(
onPressed: (){
countNotifier1.increment();
},
child: Text("點選加1"),
),
)
],
),
);
},
),
優化Consumer
優化方式一:儘可能調整Consumer的位置
我們在上面的程式碼中發現Center
以及Column
元件也被Consumer
包裹了進來,但是這兩個元件是不需要更新狀態的,而我們每次構建的Widget
的時候,會重建整個body
,所以我們優化一下程式碼結構,看起來就像下面這樣:
body: Center(
child: Consumer(
builder: (_, CountNotifier1 countNotifier1, child) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
countNotifier1.count.toString(),
style: TextStyle(color: Colors.red, fontSize: 50),
),
Padding(
padding: EdgeInsets.only(top: 20),
child: ElevatedButton(
onPressed: () {
countNotifier1.increment();
},
child: Text("點選加1"),
),
),
Container(
child: Column(
children: [
Text("更多元件1"),
Text("更多元件2"),
Text("更多元件3"),
Text("更多元件4"),
Text("更多元件5"),
Text("更多元件6"),
],
),
)
],
),
);
},
)
)
優化方式二:不需要重新整理但被Consumer包裹的元件用child
比如上面我們有更多元件1-6甚至數百個元件無需重新整理狀態,但由於你用Consumer
包裹會導致全部重新整理,那麼明顯會導致效能的下降,你可能會想到單獨用多個Consumer
包裹需要重新整理的元件就解決了,但這不就是本末倒置了嗎,本身Provider
是解決程式碼的健壯、重複的程式碼,所以這個時候我們可以採用Consumer
為我們提供的child
引數,如下:
body: Center(
child: Consumer(
builder: (_, CountNotifier1 countNotifier1, child) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
countNotifier1.count.toString(),
style: TextStyle(color: Colors.red, fontSize: 50),
),
Padding(
padding: EdgeInsets.only(top: 20),
child: ElevatedButton(
onPressed: () {
countNotifier1.increment();
},
child: Text("點選加1"),
),
),
child!
],
),
);
},
child: Container(
child: Column(
children: [
Text("更多元件1"),
Text("更多元件2"),
Text("更多元件3"),
Text("更多元件4"),
Text("更多元件5"),
Text("更多元件6"),
],
),
),
)
),
Selector
Selector
類和Consumer
類似,只是對build
呼叫Widget
方法時提供更精細的控制,簡單點來說,Selector
也是一個消費者,它允許你可以從模型中準備定義哪些屬性。
我們來舉個例子:
比如,使用者模型中有50個屬性,但是我只需要更新年齡,這樣我希望不需要重建使用者名稱、電話號碼等元件,那麼Selector
就是用於解決這個問題,我們看一下示例:
第一步:定義模型
import 'package:flutter/material.dart';
class UserModel6 with ChangeNotifier {
String name = "Jimi";
int age = 18;
String phone = "18888888888";
void increaseAge() {
age++;
notifyListeners();
}
}
第二步:應用程式入口設定
return ChangeNotifierProvider(
create: (_) => UserModel6(),
child: MaterialApp(
debugShowCheckedModeBanner: false,
home: SelectorExample(),
),
);
第三步:使用Selector更精細的控制
import 'package:flutter/material.dart';
import 'package:flutter_provider_example/selector_example/user_model6.dart';
import 'package:provider/provider.dart';
class SelectorExample extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("SelectorExample"),
),
body: Center(
child: Selector<UserModel6, int>(
selector: (_, userModel6) => userModel6.age,
builder: (_, age, child) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(age.toString(),
style: TextStyle(
color: Colors.red,
fontSize: 30
)
),
child!
],
);
},
child: Padding(
padding: EdgeInsets.all(20),
child: ElevatedButton(
onPressed: (){
Provider.of<UserModel6>(context, listen: false).increaseAge();
},
child: Text("改變年齡"),
),
),
),
),
);
}
}
執行結果
InheritedContext
InheritedContext
是Provider
內建擴充套件了BuildContext
,它不儲存了元件在樹中自己位置的引用,我們在上面的案例中見到Provider.of<CountNotifier1>(context,listen: false)
,其實這個of
方法就是使用Flutter
查詢樹並找到Provider
子型別為CountNotifier1
而已。
三大方式:
- BuildContext.read:
BuildContext.read<CountNotifier1>()
可以替換掉Provider.of<CountNotifier1>(context,listen: false)
,它會找到CountNotifier1
並返回它。 - BuildContext.watch:
BuildContext.watch<CountNotifier1>()
可以替換掉Provider.of<CountNotifier1>(context,listen: false)
,看起來和read
沒有什麼不同,但是使用watch
你就不需要在使用Consumer
。 - BuildContext.select:
BuildContext.select<CountNotifier1>()
可以替換掉Provider.of<CountNotifier1>(context,listen: false)
,看起來和watch
也沒有什麼不同,但是使用select
你就不需要在使用Selector
。
BuildContext.read
下面兩種使用方式結果是一樣的
使用Provider.of<T>()
import 'package:flutter/material.dart';
import 'package:flutter_provider_example/Inherited_context_example/count_notifier2.dart';
import 'package:provider/provider.dart';
class InheritedContextExample extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("InheritedContextExample"),
),
/// Provider.of 獲取值
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(Provider.of<CountNotifier2>(context).count.toString(),
style: TextStyle(
color: Colors.red,
fontSize: 50
),
),
Padding(
padding: EdgeInsets.only(top: 20),
child: ElevatedButton(
onPressed: () => Provider.of<CountNotifier2>(context, listen: false).increment(),
child: Text("點選加1"),
),
),
],
),
),
);
}
}
使用BuildContext.read
import 'package:flutter/material.dart';
import 'package:flutter_provider_example/Inherited_context_example/count_notifier2.dart';
import 'package:provider/provider.dart';
class InheritedContextExample extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("InheritedContextExample"),
),
/// read 獲取值
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(context.read<CountNotifier2>().count.toString(),
style: TextStyle(
color: Colors.red,
fontSize: 50
),
),
Padding(
padding: EdgeInsets.only(top: 20),
child: ElevatedButton(
onPressed: () => Provider.of<CountNotifier2>(context, listen: false).increment(),
child: Text("點選加1"),
),
),
],
),
),
);
}
}
BuildContext.watch
使用Consumer
import 'package:flutter/material.dart';
import 'package:flutter_provider_example/Inherited_context_example/count_notifier2.dart';
import 'package:provider/provider.dart';
class InheritedContextExample extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("InheritedContextExample"),
),
/// Consumer 獲取值
body: Center(
child: Consumer<CountNotifier2>(
builder: (_, countNotifier2, child) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(countNotifier2.count.toString(),
style: TextStyle(
color: Colors.red,
fontSize: 50
),
),
Padding(
padding: EdgeInsets.only(top: 20),
child: ElevatedButton(
onPressed: () => countNotifier2.increment(),
child: Text("點選加1"),
),
),
],
);
},
),
),
);
}
}
使用BuildContext.watch
import 'package:flutter/material.dart';
import 'package:flutter_provider_example/Inherited_context_example/count_notifier2.dart';
import 'package:provider/provider.dart';
class InheritedContextExample extends StatelessWidget {
@override
Widget build(BuildContext context) {
/// 重要
final countNotifier2 = context.watch<CountNotifier2>();
return Scaffold(
appBar: AppBar(
title: Text("InheritedContextExample"),
),
/// watch
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(countNotifier2.count.toString(),
style: TextStyle(
color: Colors.red,
fontSize: 50
),
),
Padding(
padding: EdgeInsets.only(top: 20),
child: ElevatedButton(
onPressed: () => countNotifier2.increment(),
child: Text("點選加1"),
),
),
],
),
),
);
}
}
BuildContext.select
使用Selector
import 'package:flutter/material.dart';
import 'package:flutter_provider_example/Inherited_context_example/count_notifier2.dart';
import 'package:provider/provider.dart';
class InheritedContextExample extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("InheritedContextExample"),
),
/// Selector
body: Center(
child: Selector<CountNotifier2, int>(
selector: (_, countNotifier2) => countNotifier2.count,
builder: (_, count, child) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(count.toString(),
style: TextStyle(
color: Colors.red,
fontSize: 50
),
),
child!
],
);
},
child: Padding(
padding: EdgeInsets.only(top: 20),
child: ElevatedButton(
onPressed: () => Provider.of<CountNotifier2>(context, listen: false).increment(),
child: Text("點選加1"),
),
),
),
),
);
}
}
使用BuildContext.select
import 'package:flutter/material.dart';
import 'package:flutter_provider_example/Inherited_context_example/count_notifier2.dart';
import 'package:provider/provider.dart';
class InheritedContextExample extends StatelessWidget {
@override
Widget build(BuildContext context) {
/// 重要
final count = context.select((CountNotifier2 countNotifier2) => countNotifier2.count);
return Scaffold(
appBar: AppBar(
title: Text("InheritedContextExample"),
),
/// select
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(count.toString(),
style: TextStyle(
color: Colors.red,
fontSize: 50
),
),
Padding(
padding: EdgeInsets.only(top: 20),
child: ElevatedButton(
onPressed: () => Provider.of<CountNotifier2>(context, listen: false).increment(),
child: Text("點選加1"),
),
)
],
),
),
);
}
}
總結
Flutter
為我們提供了多種讀取值的方式,上面我們對消費者四大類的一個使用和分析對比,大家可根據自己的實際應用場景去使用對應的方式。