使用Provider进行Flutter状态管理

发表时间: 2024-01-30 16:31

上一节介绍了通过state来进行状态管理,我们知道了通过state方式管理组件内部状态和组件与子组件之间的状态。但是我们试想一下,如果我们把所有的状态、状态对应的业务方法如果都写在StatefulWidget中,这样的架构是很难进行进行模块化和组件化编码,耦合性太高,难于维护,更不适合大型工程化项目。

那么Flutter有提供什么方式能帮我们解决以上问题吗?Flutter自身提供了InheritedWidget,这一章节我们先不具体讲解,这一节主要介绍通过Provider方式进行状态管理。

基础

Provider概念

Provider是一个由社区构建的状态管理包,而不是Google推出,但ProviderGoogle极力推荐的状态管理方式之一,也就是说Provider是Flutter的一个第三方插件,它其实是对InheritedWidget组件进行了封装,使其更易用,更易复用。

Provider优势

我们为什么要用Provider而不是直接使用InheritedWidget,我们看下官方介绍

1. 简化的资源分配与处置

2. 懒加载

3. 创建新类时减少大量的模板代码

4. 支持 DevTools更通用的调用 InheritedWidget 的方式

5. 提升类的可扩展性,

6. 整体的监听架构时间复杂度以指数级增长(如 ChangeNotifier, 其复杂度为 O(N))

Provider类结构图

基本使用

  1. 首先在pubspec.yaml中添加Provider依赖
dependencies:  flutter:    sdk: flutter  provider: ^6.1.1
  1. 定义需要共享的数据
class CountNotifier with ChangeNotifier {  int count = 0;  void increment() {    count++;    notifyListeners();  }}
  1. 进行初始化
void main() {  runApp(MyApp());}class MyApp extends StatelessWidget {  @override  Widget build(BuildContext context) {    return ChangeNotifierProvider(      create: (_) => CountNotifier(),      child: MaterialApp(        debugShowCheckedModeBanner: false,        home: ProviderCountExample(),      ),    );  }}
  1. 创建StatefulWidget并使用共享数据
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          ),        ),      ),    );  }}

通过以上代码,即可实现点击按钮,数字增加的效果。上面代码中的很多方法和类,我们目前可能还很陌生,但是通过上面的代码已经实现了将所有的状态和业务都放到一个独立的类中进行管理,如果我们做过Android客户端开发,这种架构和google目前推荐的MVVM架构是很类似的。

其实Provider是一种生产者-消费者的设计模式,接下来带大家进一步加深对Provider理解和使用。

提供者

Provider

通用的 Provider 类型,可以用于任何类型的数据,包括基本类型、对象和函数。可以使用它为组件树中的任何位置提供值,但是当该值更改的时候,它并不会更新UI。

代码示例:

//定义一个状态管理类class UserModel {  String name = "Flutter";  void changeName() {    name = "Hello";  }}//程序入口或者界面build方法增加return Provider<UserModel>(  create: (_) => UserModel(),  child: MaterialApp(    debugShowCheckedModeBanner: false,    home: ProviderExample(),  ),);// 增加一个StatelessWidget组件,包含一个文本和按钮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("改变值"),                  ),               );              },            ),          ],        ),      ),    );  }}

当我们运行代码之后,点击按钮修改UserModel中的name值,但是该数据改变之后UI并没有变化也没有重建,那是因为Provider提供者组件不会监听它提供的值的变化。

ChangeNotifierProvider

它跟Provider组件不同,ChangeNotifierProvider会监听模型对象的变化,而且当数据改变时,它也会重建Consumer(消费者),下面我们给出一个示例。

// 定义一个状态管理类,混入ChangeNotifierclass UserModel with ChangeNotifier {  String name = "Flutter";  void changeName() {    name = "Hello";    notifyListeners();  }}//程序入口或者StatefulWidget界面build方法增加return ChangeNotifierProvider<UserModel>(  create: (_) => UserModel(),  child: MaterialApp(    debugShowCheckedModeBanner: false,    home: ChangeNotifierProviderExample(),  ),);// 增加一个StatelessWidget组件,包含一个文本和按钮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<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("改变值"),                  ),                );              },            ),          ],        ),      ),    );  }}

当我们运行代码之后,点击按钮修改UserModel中的name值,文本会从"Flutter"变为"Hello"。

FutureProvider

用于管理未来(Future),可以在异步操作完成后提供数据。适用于需要等待异步操作完成后获取数据的情况,如初始化数据。FutureProvider有一个初始值,子组件可以使用该Future值并告诉子组件使用新的值来进行重建。

注意:

  • FutureProvider只会重建一次
  • 默认显示初始值,然后显示Future值
  • 最后不会再次重建

示例代码如下:

class UserModel{  UserModel({this.name});  String? name = "Flutter";  Future<void> changeName() async {    await Future.delayed(Duration(milliseconds: 2000));    name = "Hello";  }}class UserFuture {  Future<UserModel2> asyncGetUserModel() async {    await Future.delayed(Duration(milliseconds: 2000));    return UserModel(name: "Future数据");  }}//FutureProviderreturn FutureProvider<UserModel>(  initialData: UserModel(name: "hello"),  create: (_) => UserFuture().asyncGetUserModel2(),  child: MaterialApp(    debugShowCheckedModeBanner: false,    home: FutureProviderExample(),  ),);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<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("改变值"),                  ),                );              },            ),          ],        ),      ),    );  }}

运行以上代码,界面会从"hello"变成"Future数据",但是点击按钮,虽然调用了changeName方法,UI并不会刷新,这也应证了FutureProvider只会重建一次。

StreamProvider

用于管理流(Stream),可以监听流中数据的变化,并在数据发生变化时更新部件。适用于从网络或其他异步源获取数据的情况,如实时更新数据。

示例代码如下:

class UserModel{  UserModel3({this.name});  String? name = "Flutter";  void changeName() {    name = "Hello";  }}class UserStream {  Stream<UserModel> getStreamUserModel() {    return Stream<UserModel>.periodic(Duration(milliseconds: 1000),        (value) => UserModel(name: "$value")    ).take(10);  }}//增加StreamProviderreturn StreamProvider<UserModel3>(  initialData: UserModel(name: "hello"),  create: (_) => UserStream().getStreamUserModel(),  child: MaterialApp(    debugShowCheckedModeBanner: false,    home: StreamProviderExample(),  ),);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<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("改变值"),                  ),                );              },            ),          ],        ),      ),    );  }}

运行以上代码,界面会从开始的"Hello"-> "0" -> "1"..."9",但是点击按钮,并不会变成"Hello",如果流的数据是持续的,那么会一直显示的是流里面的值。

MultiProvider

在上面的例子中我们都只是返回了一个提供者,在实际开发过程中肯定会有多个提供者,我们虽然可以采用嵌套的方式来解决,但是这样无疑是混乱的,可读性级差。这个时候强大的MultiProvder就产生了,我们来看下示例:

class UserModel1 with ChangeNotifier {  String name = "Jimi";  void changeName() {    name = "hello";    notifyListeners();  }}class UserModel4 with ChangeNotifier {  String name = "Flutter";  int age = 18;  void changeName() {    name = "hello";    age = 20;    notifyListeners();  }}return MultiProvider(  providers: [    ChangeNotifierProvider<UserModel1>(create: (_) => UserModel1()),    ChangeNotifierProvider<UserModel4>(create: (_) => UserModel4()),    /// 添加更多  ],  child: MaterialApp(    debugShowCheckedModeBanner: false,    home: MultiProviderExample(),  ),);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

是 Provider 的通用版本,可以根据其他 Provider 的值来构建和更新任何类型的数据。适用于根据其他状态动态生成任意类型数据的情况,具有更大的灵活性。

当我们有多个模型的时候,会有模型依赖另一个模型的情况,在这种情况下,我们可以使用ProxyProvider从另一个提供者获取值,然后将其注入到另一个提供者中。
ChangeNotifierProxyProvider和
ListenableProxyProvider,它们工作的方式是相似的。

消费者

上面我们主要介绍了提供者,但是很多代码也使用了消费者,即数据读取方式。那么Provider提供了哪些消费者呢?

Provider.of

Provider.of<T>(context)Provider为我们提供的静态方法,当我们使用该方法去获取值的时候会返回查找到的最近的T类型的provider给我们,而且也不会遍历整个组件树,下面我们看下代码:

class CountNotifier1 with ChangeNotifier {  int count = 0;  void increment() {    count++;    notifyListeners();  }}return ChangeNotifierProvider(  create: (_) => CountNotifier1(),  child: MaterialApp(    debugShowCheckedModeBanner: false,    home: ConsumerExample(),  ),);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, listen: false).count.toString(),              style: TextStyle(                  color: Colors.red,                  fontSize: 50              ),            ),            Padding(              padding: EdgeInsets.only(                top: 20              ),              child: ElevatedButton(                onPressed: (){                  Provider.of<CountNotifier1>(context, listen: false).increment();                },                child: Text("点击加1"),              ),            )          ],        ),      ),    );  }}

Consumer

Consumber只是在Widget中调用了Prvoider.of,并将其构造实现委托给了构造器,比如我们常见的Builder,如果你的Widget依赖多个模型,那么它还提供了Consumer23456方便调用。具体使用代码就不列举了,上面的提供者中的示例代码都基本使用的是Consumer方式。

Selector

Selector类和Consumer类似,只是对build调用Widget方法时提供更精细的控制,简单点来说,Selector也是一个消费者,它允许你可以从模型中准备定义哪些属性。

class MyDataProvider extends ChangeNotifier {  String _data = 'Initial Data';  String get data => _data;  void updateData(String newData) {    _data = newData;    notifyListeners(); // 通知监听器进行更新  }}void main() {  runApp(    ChangeNotifierProvider(      create: (context) => MyDataProvider(),      child: MyApp(),    ),  );}class MyApp extends StatelessWidget {  @override  Widget build(BuildContext context) {    return MaterialApp(      home: Scaffold(        appBar: AppBar(          title: Text('Provider Selector Example'),        ),        body: Center(          child: Column(            mainAxisAlignment: MainAxisAlignment.center,            children: <Widget>[              // 使用 Selector 小部件选择性地监听数据              Selector<MyDataProvider, String>(                selector: (context, myData) => myData.data,                builder: (context, data, _) {                  return Text('Data from Provider: $data');                },              ),              SizedBox(height: 20),              ElevatedButton(                onPressed: () {                  // 更新数据                  Provider.of<MyDataProvider>(context, listen: false)                      .updateData('New Data');                },                child: Text('Update Data'),              ),            ],          ),        ),      ),    );  }}

在这个示例中,我们首先创建了一个 MyDataProvider 类来管理数据,并使用 ChangeNotifierProvider 将其注册为根部件。然后,我们在 UI 中使用 Selector 小部件选择性地监听数据,并在数据变化时更新相关的 UI。最后,通过点击按钮来更新数据,观察 Selector 中的 UI 是否会更新。

InheritedContext

InheritedContextProvider内置扩展了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。
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: [            // 这段代码替换了Provider.of<CountNotifier1>(context,listen: false)            Text(context.read<CountNotifier1>().count.toString(),              style: TextStyle(                  color: Colors.red,                  fontSize: 50              ),            ),            Padding(              padding: EdgeInsets.only(top: 20),              child: ElevatedButton(                onPressed: () => Provider.of<CountNotifier1>(context, listen: false).increment(),                child: Text("点击加1"),              ),            ),          ],        ),      ),    );  }}

小结

在 Flutter 中,Provider 是一个强大的状态管理库,它提供了一种简单而有效的方式来在应用程序中共享和管理状态。以下是关于 Flutter Provider 的小结:

提供者类型

  • Provider 提供了多种类型的提供者,包括 ChangeNotifierProvider、StreamProvider、FutureProvider 等,用于满足不同场景下的状态管理需求。

消费者类型:

  • Provider提供了多种类型的消费者,包括Provider.of、Consumer、Selector、InheritedContext,用于不同场景下读取状态数据。

数据共享

  • 使用 Provider 可以轻松地在整个应用程序中共享状态,无需手动传递数据。
  • 提供者将状态存储在应用程序的组件树中,可以在任何地方访问和更新状态。

简化代码

  • Provider 可以帮助简化代码,减少模板代码的编写量。
  • 它通过依赖注入的方式提供了一种简洁而优雅的方式来管理和访问状态。

性能优化

  • 使用 Provider 可以实现针对性的 UI 更新,减少不必要的重建,提高应用程序的性能。
  • 可以使用 Selector 小部件选择性地监听特定数据,只有在数据发生变化时才会重建相关的 UI。

适用场景

  • Provider 适用于各种规模的 Flutter 应用程序,从简单的示例应用到复杂的生产级应用程序都可以使用。
  • 它特别适用于需要跨多个小部件共享状态的情况,如用户身份认证信息、主题、语言设置等。

综上所述,Flutter Provider 是一个功能强大且易于使用的状态管理库,通过使用 Provider,可以轻松地共享和管理状态,从而提高应用程序的可维护性和可扩展性。