Flutter Provider状态管理

介绍、类图分析、基本使用

Provider是一个由社区构建的状态管理包,而不是Google推出,但ProviderGoogle极力推荐的状态管理方式之一,它是对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: 它是一个实现SingleChildWidgetStatelessWidget,必须使用buildWithChild构建子组件
  • SingleChildStatefulWidget: 它是一个实现SingleChildWidgetStatefulWidget,是与Nested兼容的StatefulWidget

Provider组件

Provider组件分为四大类,分别如下:

Nested系列

MultiProvider: 主要作用是提高代码可读性和减少重复代码,是将多个提供者合并成单个线性的组件提供者。

SingleChildStatefulWidget系列

  • Selector0: 它是Selector至Selector6的基类
  • Selector1-6: 它们是将Selector0Provider.of结合使用的语法糖,继承自Selector0

SingleChildStatelessWidget系列

  • Consumer1-6: 消费者,只是调用了Provider.of,主要作用是从顶层获取Provider<T>并将其值传递给了builder
  • InheritedProvider: InheritedWidget的通用实现,并且所有的继承自该类的都可以通过Provider.of来获取value
  • DeferredInheritedProvider: InheritedProvider的子类,用于监听流或者接收一个Future
  • StreamProvider: 监听流,并暴露出当前的最新值。
  • FutureProvider: 接收一个 Future,并在其进入 complete 状态时更新依赖它的组件。
  • ListenableProvider: 供可监听对象使用的特殊 providerListenableProvider 会监听对象,并在监听器被调用时更新依赖此对象的widgets
  • ChangeNotifierProvider: 为 ChangeNotifier 提供的 ListenableProvider规范,会在需要时自动调用 ChangeNotifier.dispose
  • ListenableProxyProvider0: 可见的代理提供者,主要是从其他提供者获取值。
  • ListenableProxyProvider1-6: 它是ListenableProvider的一个变体,继承ListenableProxyProvider0, 从其他提供者获取值
  • ChangeNotifierProxyProvider0: 主要用于构建和同步ChangeNotifierChangeNotifierProvider
  • 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不太了解的话,那么你就很难理解StreamProviderStreamProvider文档地址

第一步:创建模型

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从另一个提供者获取值,然后将其注入到另一个提供者中。我们来看下代码演示

第一步:创建两个模型

下面我们创建了两个模型UserModel5WalletModel,而WalletModel依赖与UserModel5,当调用WalletModelchangeName方法时会改变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原理一样,唯一的区别在于它构建和同步ChangeNotifierChangeNotifierProvider,当提供者数据变化时,将会重构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

ListenableProxyProviderListenableProvider的一个变体,但是在使用上和ChangeNotifierProvider效果惊人的一致,如果大家对ListenableProxyProvider有更深的理解,请联系我补充。

总结

Provider为我们提供了非常多的提供者,总共有八种。但我们比较常用的是ChangeNotifierProviderMultiProviderChangeNotifierProxyProvider,关于其他的提供者可根据自己的实际应用场景来。


四种消费者使用分析

前言

在上一篇文章中我们对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

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

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为我们提供了多种读取值的方式,上面我们对消费者四大类的一个使用和分析对比,大家可根据自己的实际应用场景去使用对应的方式。

猜你喜欢

转载自blog.csdn.net/jdsjlzx/article/details/127956374