44Flutter Provider状态管理

Provider状态管理

1.为什么要使用状态管理组件

​ 通俗的讲:当我们想在多个页面(组件/Widget)之间共享状态(数据),或者一个页面(组
件/Widget)中的多个子组件之间共享状态(数据),这个时候我们就可以用 Flutter 中的状
态管理来管理统一的状态(数据),实现不同组件直接的传值和数据共享。

通过使用小部件进行状态管理,provider可以保证:

  • 通过强制的单向数据流实现可维护性
  • 可测试性/可组合性,因为始终可以模拟/覆盖值
  • 健壮性,因为很难忘记处理模型/小部件的更新方案

2.如何使用Provider

1.了解原理

在这里插入图片描述

  • ChangeNotifierProvider:创建ChangeNotifier
  • ChangeNotifier:用于处理数据,类似于Android中ViewModel + LiveData, 处理完了之后notifyListeners
  • Consumer:消费者,或者说监听,类似于LiveData的监听,将ChangeNotifier(ViewModel)中数据拿过来
  • Widget:显示数据,等价于Android中View
 provider: ^5.0.0
2.发送数据

类似于ViewModel请求数据

///发送事件,发送数据时,不需要回调监听
Provider.of<TimeCounterModel>(context,listen: false).getCurrentTime(); //发送数据,先通过

注意:

Provider.of<T>(BuildContext context, {bool listen = true}) {}
listen:表示是否监听数据,默认true监听,发送时,不需要监听
3接收数据,

第一种方式:通过Provider.of(context, listen: true).formatTime,直接监听

  Widget buildBlocBuilder() {
    return Container(
      ///外边距
      margin: EdgeInsets.only(left: 12, top: 12),
      child: Text(
        '${Provider.of<TimeCounterModel>(context, listen: true).formatTime}',
        style: TextStyle(fontSize: 22.0, color: Colors.red),
      ),
    );
  }

第二种方式:Consumer来监听

 Widget buildBlocBuilder() {
    return Consumer<TimeCounterModel>(
      ///参数 value 就是绑定的事件结果 TimeCounterModel
      builder: (BuildContext context, value, Widget child) {
        return Container(
          ///外边距
          margin: EdgeInsets.only(left: 12, top: 12),
          child: Text(
            '${value.formatTime}',
            style: TextStyle(fontSize: 22.0, color: Colors.red),
          ),
        );
      },
    );

代码如下:

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

import 'package:flutter/cupertino.dart';

import 'TimeModel.dart';

main() =>
  //ChangeNotifierProvider
    runApp(ChangeNotifierProvider(
      create: (context) => TimeModel(),//创建ChangeNotifier(ViewModel)
      child:ProviderMainApp(),
    ));


class ProviderMainApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: "配制",
      theme: ThemeData(
        accentColor: Colors.blue,
        brightness: Brightness.light,
      ),

      ///默认的首页面

      home: ProviderHome(),
    );
  }
}

class ProviderHome extends StatefulWidget {
  ProviderHome({Key key}) : super(key: key);

  @override
  _ProviderHomeState createState() {
    return _ProviderHomeState();
  }
}

class _ProviderHomeState extends State<ProviderHome> {
  @override
  void initState() {
    super.initState();
  }

  @override
  void dispose() {
    super.dispose();
  }

  Widget buildBlocBuilder() {
    return Consumer<TimeModel>( 接收数据
      builder: (BuildContext context, value, Widget child) {
        return Container(
          margin: EdgeInsets.only(left: 12, top: 12),
          child: Text(
            '${value.count}',
            style: TextStyle(fontSize: 22.0, color: Colors.red),
          ),
        );
      },
    );
  }

  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return Column(
      children: [
        Container(
          margin: EdgeInsets.only(top: 10, bottom: 10),
          child: OutlinedButton(
            child: Text("provider"),
            onPressed: () {
              Provider.of<TimeModel>(context, listen: false).addCount(); //发送数据
            },
          ),
        ),
        Container(
          margin: EdgeInsets.only(top: 10, bottom: 10),
          child: Text(
              '${Provider
                  .of<TimeModel>(context, listen: true)
                  .count}' //接收数据
          ),
        ),
        buildBlocBuilder()
      ],
    );
  }
}

TimeModel

import 'package:flutter/cupertino.dart';

class TimeModel with ChangeNotifier {
  var count = 0;

  void addCount() {
    count++;
    notifyListeners();
  }
}
4.多个事情巧妙操作,类似于Android中多个ViewModel

1.通过 MultiProvider 来组合这些 ChangeNotifierProvider,也就相当于Android中注册获取ViewModel

class TestProviderMulPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    ///组合多个Provider
    return MultiProvider(
      providers: [
        ///计时器
        ChangeNotifierProvider(
          create: (BuildContext context) {
            return TimeCounterModel();
          },
        ),
        ///随机数据
        ChangeNotifierProvider(
          create: (BuildContext context) {
            return RandomNumberModel();
          },
        )
      ],
      child: MaterialApp(
        home: TestConsumerTimePage(),
      ),
    );
  }
}

2.消费处理,各自监听获取ChangeNotifier传递过来的数据,其他与上面一致

 @override
 Widget build(BuildContext context) {
   ///页面主体脚手架
   return Scaffold(
     appBar: AppBar(
       title: Text("Provider "),
     ),
     body: Column(
       children: [
         buildTimeConsumer(),
         buildNumberConsumer(),
       ],
     ),
   );
 }

3.组合消费同时监听这两个结果代码

///通过 Consumer2 来同时监听处理两个结果
 Widget buildTimeConsumer2() {
   return Consumer2<TimeCounterModel,RandomNumberModel>(
     ///参数 value 为 TimeCounterModel 类型
     ///参数 value2 为 RandomNumberModel 类型
     builder: (BuildContext context, value,value2, Widget child) {
       return Container(
         ///外边距
         margin: EdgeInsets.only(left: 12, top: 12),
         child: Text(
           '当前时间 ${value.formatTime} 随机数 ${value2.randomNumber}',
           style: TextStyle(fontSize: 22.0, color: Colors.red),
         ),
       );
     },
   );
 }
4组合监听
  ///通过 Consumer2 来同时监听处理两个结果
  Widget buildTimeConsumer2() {
    return Consumer2<TimeModel02,RandomModel>(
      ///参数 value 为 TimeModel02 类型
      ///参数 value2 为 RandomModel 类型
      builder: (BuildContext context, value,value2, Widget child) {
        return Container(
          ///外边距
          margin: EdgeInsets.only(left: 12, top: 12),
          child: Text(
            '当前时间 ${value.count} 随机数 ${value2.randomNumber}',
            style: TextStyle(fontSize: 22.0, color: Colors.red),
          ),
        );
      },
    );
  }

多个以上监听,参照Consumer2

案例

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

import 'RandomModel.dart';
import 'TimeModel02.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      // home: CustomScrollViewDemo(),
      home: Scaffold(
        body: TestProviderMulPage(),
      ),
    );
  }
}

class TestProviderMulPage extends StatelessWidget {
  TestProviderMulPage({Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    // TODO: implement build
    return MultiProvider(
      providers: [
        ChangeNotifierProvider(create: (context) => TimeModel02()),
        ChangeNotifierProvider(
          create: (context) => RandomModel(),
        )
      ],
      child: TestConsumerTimePage(),
    );
  }
}

class TestConsumerTimePage extends StatefulWidget {
  TestConsumerTimePage({Key key}) : super(key: key);

  @override
  _TestConsumerTimePageState createState() {
    return _TestConsumerTimePageState();
  }
}

class _TestConsumerTimePageState extends State<TestConsumerTimePage> {
  @override
  void initState() {
    super.initState();
  }

  @override
  void dispose() {
    super.dispose();
  }

  Widget buildBlocBuilder() {
    return Consumer<TimeModel02>(
      接收数据
      builder: (BuildContext context, value, Widget child) {
        return Container(
          margin: EdgeInsets.only(left: 12, top: 12),
          child: Text(
            '${value.count}',
            style: TextStyle(fontSize: 22.0, color: Colors.red),
          ),
        );
      },
    );
  }

  Widget buildNumberConsumer() {
    return Consumer<RandomModel>(
      builder: (BuildContext context, value, Widget child) {
        return Container(
          ///外边距
          margin: EdgeInsets.only(left: 12, top: 12),
          child: Text(
            '回传的数据 ${value.randomNumber}',
            style: TextStyle(fontSize: 22.0, color: Colors.red),
          ),
        );
      },
    );
  }

  ///通过 Consumer2 来同时监听处理两个结果
  Widget buildTimeConsumer2() {
    return Consumer2<TimeModel02,RandomModel>(
      ///参数 value 为 TimeModel02 类型
      ///参数 value2 为 RandomModel 类型
      builder: (BuildContext context, value,value2, Widget child) {
        return Container(
          ///外边距
          margin: EdgeInsets.only(left: 12, top: 12),
          child: Text(
            '当前时间 ${value.count} 随机数 ${value2.randomNumber}',
            style: TextStyle(fontSize: 22.0, color: Colors.red),
          ),
        );
      },
    );
  }

  @override
  Widget build(BuildContext context) {
    return Center(
        child: Column(
      children: [
        SizedBox(height: 50),
        Container(
          margin: EdgeInsets.only(top: 10, bottom: 10),
          child: OutlinedButton(
            child: Text("provider"),
            onPressed: () {
              Provider.of<TimeModel02>(context, listen: false)
                  .addCount(); //发送数据
            },
          ),
        ),
        Container(
          margin: EdgeInsets.only(top: 10, bottom: 10),
          child: OutlinedButton(
            child: Text("provider"),
            onPressed: () {
              Provider.of<RandomModel>(context, listen: false)
                  .testRandom(); //发送数据
            },
          ),
        ),
        buildBlocBuilder(), //监听1
        buildNumberConsumer(),//监听2
        buildTimeConsumer2()
      ],
    ));
  }
}

3.selector

发现问题:Consumer来获取CounterProvider,只要是值改变了都会通知它的宿主刷新UI,简单说,ButtonA和ButtonB值的改变通过同一个Provider,只要ChangeNotifier(ViewModel)发生改变,那么ButtonA和ButtonB都会被触发,这是我们不想要的, 频繁触发,导致更多的性能消耗,用电也更多

这明显与我们使用Provider的初衷是违背的。而在实际的使用场景中,这种情况会带来比预想更差的UI表现。

解决方案:Selector来解决这类问题。

import 'package:flutter/material.dart';
 
class CounterProvider with ChangeNotifier {
  int _count = 0;
  int _count1 = 100;
  int get value => _count;
  int get value1 => _count1;
  void increment() {
    _count++;
    notifyListeners();
  }
  void increment1() {
    _count1++;
    notifyListeners();
  }
}
1.selector属性
Selector({
    Key key,
    //当父widget请求更新或者selector的返回值与之前的返回值不一样时会调用builder
    @required ValueWidgetBuilder<S> builder,
    //selector返回具体的值,返回的值必须继承自==而且不能为null
    @required S Function(BuildContext, A) selector,
    Widget child,
  })  : assert(selector != null),
        super(
          key: key,
          builder: builder,
          selector: (context) => selector(context, Provider.of(context)),
          child: child,
        );

·Selector控制的粒度比Consumer更细,Consumer是监听一个Provider中所有数据的变化,Selector则是监听某一个/多个值的变化。具体的代码:

Selector类似于ViewModel中的LiveData

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

import 'CounterProvider.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      // home: CustomScrollViewDemo(),
      home: Scaffold(
        body: HomePage(),
      ),
    );
  }
}

class HomePage extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => HomePageState();
}

class HomePageState extends State<HomePage> {
  ///初始化CounterProvider
  CounterProvider _counterProvider = new CounterProvider();

  @override
  Widget build(BuildContext context) {
    print('页面重绘了。。。。。。。。。。。');

    //整个页面使用ChangeNotifier来包裹
    return ChangeNotifierProvider(
      create: (context) => _counterProvider,
      child:
          //child里面的内容不会因为数据的改变而重绘
          Scaffold(
        appBar: AppBar(
          title: Text('my page'),
        ),
        body: Center(
            child: Column(
          children: <Widget>[
            Selector(builder: (BuildContext context, int data, Widget child) {
              print('Text 1 重绘了。。。。。。。。。。');
              return Text(
                  'Text1 : ${data.toString()}',
                  style: TextStyle(fontSize: 20));
            }, selector:
                (BuildContext context, CounterProvider counterProvider) {
              //这个地方返回具体的值,对应builder中的data
              return counterProvider.value; //监听的counterProvider.value的值,这里发生改变时,会被监听
            }),
            Consumer(builder: (BuildContext context,
                CounterProvider counterProvider, Widget child) {
              print('Text2重绘了。。。。。。');

              return Text(
                //获取数据
                'Text2 : ${counterProvider.value1}',
                style: TextStyle(fontSize: 20),
              );
            }),
            RaisedButton(
              onPressed: () {
                print('Button 1被点击了。。。。。。。。。。');
                _counterProvider.increment();
              },
              child: Text('Button1'),
            ),
            RaisedButton(
              onPressed: () {
                print('Button 2被点击了。。。。。。。。。。');
                _counterProvider.increment1();
              },
              child: Text('Button2'),
            )
          ],
        )),
      ),
    );
  }
}

2.进一步去思考

问题1:Consumer包裹多个Selector,如果Consumer发生改变,Selector会怎么变化,如果Selector发生改变,Consumer会发生变化

结论:Selector监听的值发生变化,那么Cousumer页能监听到,所以会触发Consumer包裹的组件整体重绘。

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

import 'CounterProvider.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      // home: CustomScrollViewDemo(),
      home: Scaffold(
        body: HomePage(),
      ),
    );
  }
}

class HomePage extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => HomePageState();
}

class HomePageState extends State<HomePage> {
  ///初始化CounterProvider
  CounterProvider _counterProvider = new CounterProvider();

  Widget buildWidget() {
    return Consumer<CounterProvider>(
        builder: (BuildContext context, value, Widget child) {
      return Column(
        children: [
          Selector(builder: (BuildContext context, int data, Widget child) {
            print('Text 1 重绘了。。。。。。。。。。');
            return Text('Text1 : ${data.toString()}',
                style: TextStyle(fontSize: 20));
          }, selector: (BuildContext context, CounterProvider counterProvider) {
            //这个地方返回具体的值,对应builder中的data
            return counterProvider.value;
          }),
          Selector(builder: (BuildContext context, int data, Widget child) {
            print('Text 2 重绘了。。。。。。。。。。');
            return Text('Text2 : ${data.toString()}',
                style: TextStyle(fontSize: 20));
          }, selector: (BuildContext context, CounterProvider counterProvider) {
            //这个地方返回具体的值,对应builder中的data
            return counterProvider.value1;
          })
        ],
      );
    });
  }

  @override
  Widget build(BuildContext context) {
    print('页面重绘了。。。。。。。。。。。');

    //整个页面使用ChangeNotifier来包裹
    return ChangeNotifierProvider(
      create: (context) => _counterProvider,
      child:
          //child里面的内容不会因为数据的改变而重绘
          Scaffold(
        appBar: AppBar(
          title: Text('my page'),
        ),
        body: Center(
            child: Column(
          children: <Widget>[
            buildWidget(),
            RaisedButton(
              onPressed: () {
                print('Button 1被点击了。。。。。。。。。。');
                _counterProvider.increment();
              },
              child: Text('Button1'),
            ),
            RaisedButton(
              onPressed: () {
                print('Button 2被点击了。。。。。。。。。。');
                _counterProvider.increment1();
              },
              child: Text('Button2'),
            )
          ],
        )),
      ),
    );
  }
}

3.Selector再进一步思考

Selector包裹多个Selector,结果会怎么样的

结论:点击ButtonA,只绘制ButtonA,点击ButtonB,只绘制ButtonB,点击ButtonC(最外层),会触发内部所有的Selector发生重新绘制

  Widget buildWiget2() {
    return Selector(builder: (BuildContext context, value, Widget child) {
      return Column(
        children: [
          Selector(builder: (BuildContext context, int data, Widget child) {
            print('Text 1 重绘了。。。。。。。。。。');
            return Text('Text1 : ${data.toString()}  ${++_num}',
                style: TextStyle(fontSize: 20));
          }, selector: (BuildContext context, CounterProvider counterProvider) {
            //这个地方返回具体的值,对应builder中的data
            return counterProvider.value;
          }),
          Selector(builder: (BuildContext context, int data, Widget child) {
            print('Text 2 重绘了。。。。。。。。。。');
            return Text('Text2 : ${data.toString()}  ${++_num}',
                style: TextStyle(fontSize: 20));
          }, selector: (BuildContext context, CounterProvider counterProvider) {
            //这个地方返回具体的值,对应builder中的data
            return counterProvider.value1;
          })
        ],
      );
    }, selector: (BuildContext context, CounterProvider counterProvider) {
      //这个地方返回具体的值,对应builder中的data
      return counterProvider.count2;
    });
  }

总结:所以在使用Selector时,尽可能地颗粒度要小,Selector会将内部所欲子Widget全部刷新一遍,Consumer针对ChangeNotifier中数值地变化

参考文章:https://blog.csdn.net/u013894711/article/details/102785532

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值