Flutter Provider状态管理
Posted 一叶飘舟
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Flutter Provider状态管理相关的知识,希望对你有一定的参考价值。
介绍、类图分析、基本使用
Provider
是一个由社区构建的状态管理包,而不是Google
推出,但Provider
是Google
极力推荐的状态管理方式之一,它是对InheritedWidget
组件进行了封装,使其更易用,更易复用。
学习本章节前,希望你能了解如下知识:
- 熟悉dart语言
- 熟悉flutter基本组件
- 了解InheritedWidget
- 了解ChangeNotifier
如果大家大家之前没接触过InheritedWidget
,那么建议你先去了解,你可以通过链接来查看并掌握对应的只是 英文官方文档 中文官方文档 源码分析 视频教程
Provider优势
我们为什么要用Provider
而不是直接使用InheritedWidget
,我们看下官方介绍
- 简化的资源分配与处置
- 懒加载
- 创建新类时减少大量的模板代码
- 支持 DevTools
- 更通用的调用 InheritedWidget 的方式(参考 Provider.of/Consumer/Selector)
- 提升类的可扩展性,整体的监听架构时间复杂度以指数级增长(如 ChangeNotifier, 其复杂度为 O(N))
Provider类结构图
Provider类说明
Nested组件
- Nested: 简化树结构嵌套过深
- SingleChildWidget: 单个子组件的组件,但是它与
ProxyWidget
不同,有一个build
方法。 - SingleChildStatelessWidget: 它是一个实现
SingleChildWidget
的StatelessWidget
,必须使用buildWithChild
构建子组件 - SingleChildStatefulWidget: 它是一个实现
SingleChildWidget
的StatefulWidget
,是与Nested
兼容的StatefulWidget
Provider组件
Provider
组件分为四大类,分别如下:
Nested系列
MultiProvider: 主要作用是提高代码可读性和减少重复代码,是将多个提供者合并成单个线性的组件提供者。
SingleChildStatefulWidget系列
- Selector0: 它是Selector至Selector6的基类
- Selector1-6: 它们是将
Selector0
与Provider.of
结合使用的语法糖,继承自Selector0
SingleChildStatelessWidget系列
- Consumer1-6: 消费者,只是调用了
Provider.of
,主要作用是从顶层获取Provider<T>
并将其值传递给了builder
。 - InheritedProvider:
InheritedWidget
的通用实现,并且所有的继承自该类的都可以通过Provider.of
来获取value
- DeferredInheritedProvider:
InheritedProvider
的子类,用于监听流或者接收一个Future
- StreamProvider: 监听流,并暴露出当前的最新值。
- FutureProvider: 接收一个
Future
,并在其进入complete
状态时更新依赖它的组件。 - ListenableProvider: 供可监听对象使用的特殊
provider
,ListenableProvider
会监听对象,并在监听器被调用时更新依赖此对象的widgets
。 - ChangeNotifierProvider: 为
ChangeNotifier
提供的ListenableProvider
规范,会在需要时自动调用ChangeNotifier.dispose
。 - ListenableProxyProvider0: 可见的代理提供者,主要是从其他提供者获取值。
- ListenableProxyProvider1-6: 它是
ListenableProvider
的一个变体,继承ListenableProxyProvider0
, 从其他提供者获取值 - ChangeNotifierProxyProvider0: 主要用于构建和同步
ChangeNotifier
的ChangeNotifierProvider
。 - Provider: 最基础的 provider 组成,接收一个任意值并暴露它。
- ProxyProvider0: 它公开的值会通过创建或更新构建,然后传递给
InheritedProvider
。 - ProxyProvider1-6:
ProxyProvider0
的语法糖。
InheritedContext系列
- InheritedContext: 与
InheritedProvider
关联的BuildContext
,提供公开的值 - ReadContext: 公开读取方法
- SelectContext: 在 BuildContext 上添加一个选择方法。
- WatchContext: 公开 watch 方法。
Provider基本使用
第一步:添加依赖
本文中所有的代码都是基本空安全的,所有dart sdk版本为>=2.12.0 <3.0.0
,目前官方最新版本为^6.0.1
environment:
sdk: ">=2.12.0 <3.0.0"
dependencies:
flutter:
sdk: flutter
provider: ^6.0.1
第二步:定义需要共享的数据
我们这里创建了一个类CountNotifier
继承了ChangeNotifier
,我们这里的案例是以计数器为案例,所有我们定义一个变量count
,以及一个改变数值的increment
方法,当我们调用increment
后会对count
进行+1,最后调用notifyListeners()
来更新数据,代码如下:
import 'package:flutter/material.dart';
class CountNotifier with ChangeNotifier
int count = 0;
void increment()
count++;
notifyListeners();
第三步:在应用程序入口初始化
我们在MaterialApp
之前对定义的共享数据进行了初始化,代码如下:
import 'package:flutter/material.dart';
import 'package:flutter_provider_example/provider_count_example/count_notifier.dart';
import 'package:flutter_provider_example/provider_count_example/provider_count_example.dart';
import 'package:provider/provider.dart';
void main()
runApp(MyApp());
class MyApp extends StatelessWidget
@override
Widget build(BuildContext context)
return ChangeNotifierProvider(
create: (_) => CountNotifier(),
child: MaterialApp(
debugShowCheckedModeBanner: false,
home: ProviderCountExample(),
),
);
第四步:使用共享数据
我们定义了一个counter
变量来获取到共享的数据,更新数据的方法直接通过counter.increment()
,获取数据的方法通过ounter.count.toString()
来获取,代码如下:
import 'package:flutter/material.dart';
import 'package:flutter_provider_example/provider_count_example/count_notifier.dart';
import 'package:provider/provider.dart';
class ProviderCountExample extends StatefulWidget
@override
_ProviderCountExampleState createState() => _ProviderCountExampleState();
class _ProviderCountExampleState extends State<ProviderCountExample>
@override
Widget build(BuildContext context)
final counter = Provider.of<CountNotifier>(context);
return Scaffold(
appBar: AppBar(
title: Text("InheritedWidget"),
),
floatingActionButton: FloatingActionButton(
onPressed: ()
counter.increment();
,
child: Icon(Icons.add),
),
body: Center(
child: Text(counter.count.toString(),
style: TextStyle(
color: Colors.red,
fontSize: 50
),
),
),
);
总结
以上是对Provider
进行了介绍、优势、类结构和说明以及一个基本使用的例子,相对于使用InheritedWidget
来说,显然Provider
使用起来更简单。但是从它的提供者、消费者这些类来看稍微复杂,后面的章节中我们来讲讲这些类。
八种提供者使用分析
前言
在我们上一篇文章中对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
,关于其他的提供者可根据自己的实际应用场景来。
四种消费者使用分析
前言
在上一篇文章中我们对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
为我们提供了多种读取值的方式,上面我们对消费者四大类的一个使用和分析对比,大家可根据自己的实际应用场景去使用对应的方式。
以上是关于Flutter Provider状态管理的主要内容,如果未能解决你的问题,请参考以下文章
从 Selector 访问多个字段(在 Flutter 中使用 Provider 状态管理时)?