Flutter传值--全局状态管理(Provider)

Provider是目前官方推荐的全局状态管理工具,由社区作者Remi Rousselet 和 Flutter Team共同编写。使用之前,我们需要先引入对它的依赖,

dependencies:
provider: ^4.0.4

1.Provider的基本使用 

在使用Provider的时候,我们主要关心三个概念:

  • 1.1.ChangeNotifier:真正数据(状态)存放的地方

  • 1.2.ChangeNotifierProvider:Widget树中提供数据(状态)的地方,会在其中创建对应的ChangeNotifier

  • 1.3.Consumer:Widget树中需要使用数据(状态)的地方

第一步:创建自己的ChangeNotifier 

我们需要一个ChangeNotifier来保存我们的状态,所以创建它

  • 这里我们可以使用继承自ChangeNotifier,也可以使用混入,这取决于概率是否需要继承自其它的类

  • 我们使用一个私有的_counter,并且提供了getter和setter

  • 在setter中我们监听到_counter的改变,就调用notifyListeners方法,通知所有的Consumer进行更新

  • class CounterProvider extends ChangeNotifier {
      int _counter = 100;
      int get counter {
        return _counter;
      }
      set counter(int value) {
        _counter = value;
        notifyListeners();
      }
    }

    第二步:在Widget Tree中插入ChangeNotifierProvider

    我们需要在Widget Tree中插入ChangeNotifierProvider,以便Consumer可以获取到数据:

  • 将ChangeNotifierProvider放到了顶层,这样方便在整个应用的任何地方可以使用CounterProvider

  • void main() {
      runApp(ChangeNotifierProvider(
        create: (context) => CounterProvider(),
        child: MyApp(),
      ));
    }

    第三步:在首页中使用Consumer引入和修改状态

  • 引入位置一:在body中使用Consumer,Consumer需要传入一个builder回调函数,当数据发生变化时,就会通知依赖数据的Consumer重新调用builder方法来构建;

  • 引入位置二:在floatingActionButton中使用Consumer,当点击按钮时,修改CounterNotifier中的counter数据;

  • class HYHomePage extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text("列表测试"),
          ),
          body: Center(
            child: Consumer<CounterProvider>(
              builder: (ctx, counterPro, child) {
                return Text("当前计数:${counterPro.counter}", style: TextStyle(fontSize: 20, color: Colors.red),);
              }
            ),
          ),
          floatingActionButton: Consumer<CounterProvider>(
            builder: (ctx, counterPro, child) {
              return FloatingActionButton(
                child: child,
                onPressed: () {
                  counterPro.counter += 1;
                },
              );
            },
            child: Icon(Icons.add),
          ),
        );
      }
    }

    Consumer的builder方法解析:

  • 参数一:context,每个build方法都会有上下文,目的是知道当前树的位置
  • 参数二:ChangeNotifier对应的实例,也是我们在builder函数中主要使用的对象
  • 参数三:child,目的是进行优化,如果builder下面有一颗庞大的子树,当模型发生改变的时候,我们并不希望重新build这颗子树,那么就可以将这颗子树放到Consumer的child中,在这里直接引入即可(注意我案例中的Icon所放的位置)

步骤四:创建一个新的页面,在新的页面中修改数据 

class SecondPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("第二个页面"),
      ),
      floatingActionButton: Consumer<CounterProvider>(
        builder: (ctx, counterPro, child) {
          return FloatingActionButton(
            child: child,
            onPressed: () {
              counterPro.counter += 1;
            },
          );
        },
        child: Icon(Icons.add),
      ),
    );
  }
}

Provider.of的弊端

事实上,因为Provider是基于InheritedWidget,所以我们在使用ChangeNotifier中的数据时,我们可以通过Provider.of的方式来使用,比如下面的代码:

Text("当前计数:${Provider.of<CounterProvider>(context).counter}",
  style: TextStyle(fontSize: 30, color: Colors.purple),
),

我们会发现很明显上面的代码会更加简洁,那么开发中是否要选择上面这种方式呢?

  • 答案是否定的,更多时候我们还是要选择Consumer的方式。

为什么呢?因为Consumer在刷新整个Widget树时,会尽可能少的rebuild Widget。

方式一:Provider.of的方式完整的代码:

  • 当我们点击了floatingActionButton时,HYHomePage的build方法会被重新调用。

  • 这意味着整个HYHomePage的Widget都需要重新build

class HYHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    print("调用了HYHomePage的build方法");
    return Scaffold(
      appBar: AppBar(
        title: Text("Provider"),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text("当前计数:${Provider.of<CounterProvider>(context).counter}",
              style: TextStyle(fontSize: 30, color: Colors.purple),
            )
          ],
        ),
      ),
      floatingActionButton: Consumer<CounterProvider>(
        builder: (ctx, counterPro, child) {
          return FloatingActionButton(
            child: child,
            onPressed: () {
              counterPro.counter += 1;
            },
          );
        },
        child: Icon(Icons.add),
      ),
    );
  }
}

 方式二:将Text中的内容采用Consumer的方式修改如下:

  • 你会发现HYHomePage的build方法不会被重新调用;

  • 设置如果我们有对应的child widget,可以采用上面案例中的方式来组织,性能更高

  • Consumer<CounterProvider>(builder: (ctx, counterPro, child) {
      print("调用Consumer的builder");
      return Text(
        "当前计数:${counterPro.counter}",
        style: TextStyle(fontSize: 30, color: Colors.red),
      );
    }),

    Selector的选择

       Consumer是否是最好的选择呢?并不是,它也会存在弊端 

  • 1.比如当点击了floatingActionButton时,我们在代码的两处分别打印它们的builder是否会重新调用;

  • 2.我们会发现只要点击了floatingActionButton,两个位置都会被重新builder;

  • 3.但是floatingActionButton的位置有重新build的必要吗?没有,因为它是否在操作数据,并没有展示;

  • 如何可以做到让它不要重新build了?使用Selector来代替Consumer

floatingActionButton: Selector<CounterProvider, CounterProvider>(
  selector: (ctx, provider) => provider,
  shouldRebuild: (pre, next) => false,
  builder: (ctx, counterPro, child) {
    print("floatingActionButton展示的位置builder被调用");
    return FloatingActionButton(
      child: child,
      onPressed: () {
        counterPro.counter += 1;
      },
    );
  },
  child: Icon(Icons.add),
),

 Selector和Consumer对比,不同之处主要是三个关键点:

  • 关键点1:泛型参数是两个

    • 泛型参数一:我们这次要使用的Provider

    • 泛型参数二:转换之后的数据类型,比如我这里转换之后依然是使用CounterProvider,那么他们两个就是一样的类型

  • 关键点2:selector回调函数

    • 转换的回调函数,你希望如何进行转换

    • S Function(BuildContext, A) selector

    • 我这里没有进行转换,所以直接将A实例返回即可

  • 关键点3:是否希望重新rebuild

    • 这里也是一个回调函数,我们可以拿到转换前后的两个实例;

    • bool Function(T previous, T next);

    • 因为这里我不希望它重新rebuild,无论数据如何变化,所以这里我直接return false;

MultiProvider (处理多个共享数据)

我们在增加一个新的ChangeNotifier

import 'package:flutter/material.dart';

class UserInfo {
  String nickname;
  int level;

  UserInfo(this.nickname, this.level);
}

class UserProvider extends ChangeNotifier {
  UserInfo _userInfo = UserInfo("why", 18);

  set userInfo(UserInfo info) {
    _userInfo = info;
    notifyListeners();
  }

  get userInfo {
    return _userInfo;
  }
}

使用MultiProvider

runApp(MultiProvider(
  providers: [
    ChangeNotifierProvider(create: (ctx) => CounterProvider()),
    ChangeNotifierProvider(create: (ctx) => UserProvider()),
  ],
  child: MyApp(),
));

猜你喜欢

转载自blog.csdn.net/eastWind1101/article/details/129693850