美文网首页
2022-02-24 Flutter Provider使用

2022-02-24 Flutter Provider使用

作者: 我是小胡胡123 | 来源:发表于2022-02-24 15:26 被阅读0次

https://zhuanlan.zhihu.com/p/392551608

在各种前端开发中,由于状态管理对于App的开发维护成本,性能等方面都起着至关重要的作用,所以选择合适的状态管理框架显得尤为重要。Flutter作为跨平台框架的后起之秀,背靠Google大树,短时间内开发者们在开源社区提供了多种状态管理框架。而Provider是官方推荐的状态管理方式之一,可用作跨组件的数据共享。本文对Provider框架的使用方法做了介绍,并在最后对主流的状态管理框架进行比较。

使用

Provider,通常使用ChangeNotifierProvider配合ChangeNotifier一起使用来实现状态的管理与Widget的更新。

其中 ChangeNotifier 是系统提供的,用来负责数据的变化通知。 ChangeNotifier 是一个类

ChangeNotifierProvider本质上其实就是Widget,它作为父节点Widget,可将数据共享给其所有子节点Widget使用或更新。

所以通常我们只需要三步即可利用Provider来实现状态管理。

1.创建混合或继承ChangeNotifier的Model,用来实现数据更新的通知并监听数据的变化。

2.创建ChangeNotifierProvider 或者用MultiProvider组合,用来声明Provider,实现跨组建的数据共享。

3.接收共享数据。

1、model部分

import 'package:flutter/material.dart';

class CountModel extends ChangeNotifier {
  int _count;

  CountModel() : _count = 1;

  int get count => _count;

  set count(int count) {
    _count = count;
    notifyListeners();
  }

  void add() {
    this.count = _count+1;
  }

  void minus() {
    this.count = _count-1;
  }
}

model部分的写法:

1、 类

class 普通类 (成员方法必须要实现)
mixin 多继承,混入类(不能有构造方法)
abstract class 抽象类 (定义抽象方法)

2、类继承, 3个关键字:

extends 继承
implements 继承(父类方法必须要实现)
with 多继承(父类不能有构造方法)

3、约束:

on 约束(继承mixin类的类,必须同时继承约束类)

调用notifyListeners方法通知widget刷新界面

2、 视图部分

接受共享数据

使用ChangeNotifierProvider

Container(
    width: double.maxFinite,
    child: ChangeNotifierProvider<CountModel>.value(
      value: this.countModel,
      builder: (context, child) {
        return Container(
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: <Widget>[
              ChildAPage(),
              Text(
                  context.watch<CountModel>().count.toString()),
            ],
          ),
        );
      },
    ),
  )

NO.1:
ChangeNotifierProvider的 builder方法与child方法区别

ChangeNotifierProvider.value({
  Key key,
  @required T value,
  TransitionBuilder builder,
  Widget child,
}) 

builder: (context, child)
builder的类型是= Widget Function(BuildContext context, Widget child);
这里可以获取当前的context。

  @override
  Widget buildWithChild(BuildContext context, Widget child) {
    assert(
      builder != null || child != null,
      '$runtimeType used outside of MultiProvider must specify a child',
    );
    return _InheritedProviderScope<T>(
      owner: this,
      child: builder != null
          ? Builder(
              builder: (context) => builder(context, child),
            )
          : child,
    );
  }

NO.2:

ChangeNotifierProvider<CountModel>.value value方法和 create方法

ChangeNotifierProvider<CountModel>.value(
      value: this.countModel


ChangeNotifierProvider<CountModel>(
      create: (context) => CountModel(),

ChangeNotifierProvider 非常聪明,它 不会 重复实例化 CartModel,除非在个别场景下。如果该实例已经不会再被调用, ChangeNotifierProvider 也会自动调用 CartModel 的 dispose() 方法。

接受共享数据

调用方法获取model 变化的值。 这两种方法作用相同:

1、context.watch<CountModel>().count

2、Provider.of<CountModel>(context).count

3、 Consumer<CountModel>(
      builder: (context, model, child) {
         return Text(
           'Consumer: ${model.count.toString()}');
     },
    )


使用 Consumer:

Consumer widget 唯一必须的参数就是 builder。当 ChangeNotifier 发生变化的时候会调用 builder 这个函数。(换言之,当你在模型中调用 notifyListeners() 时,所有相关的 Consumer widget 的 builder 方法都会被调用。)

builder 在被调用的时候会用到三个参数。第一个是 context。在每个 build 方法中都能找到这个参数。

builder 函数的第二个参数是 ChangeNotifier 的实例。它是我们最开始就能得到的实例。你可以通过该实例定义 UI 的内容。

第三个参数是 child,用于优化目的。如果 Consumer 下面有一个庞大的子树,当模型发生改变的时候,该子树 并不会 改变,那么你就可以仅仅创建它一次,然后通过 builder 获得该实例。

3、修改model

在外层父widget可以调用:

countModel.minus();

在子widget可以调用:

 Provider.of<CountModel>(context, listen: false).add();

4、 使用MultiProvider

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:provider_test/test/MultiProvider/CountA.dart';

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

  @override
  _MultiProviderState createState() => _MultiProviderState();
}

class _MultiProviderState extends State<MultiProviderPage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text('MultiProvider使用'),
        ),
        body: SafeArea(
          child: Container(
            margin: EdgeInsets.only(top: 20, left: 20, right: 20),
            child: MultiProvider(
              providers: [
                //Could not find the correct Provider<CountA> above this MultiProviderPage Widget
                ChangeNotifierProvider<CountA>(
                  create: (_) => CountA(),
                ),
                ChangeNotifierProvider<CountB>(
                  create: (_) => CountB(),
                ),
              ],
              child: Column(
                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                children: <Widget>[
                  WidgetA(),
                ],
              ),
            ),
          ),
        ));
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Column(
          mainAxisAlignment: MainAxisAlignment.start,
          crossAxisAlignment: CrossAxisAlignment.start,
          children: <Widget>[
            Text(
              Provider.of<CountA>(context).astr,
              style: TextStyle(color: Colors.black),
            ),
            Text(
              context.watch<CountA>().astr,
              style: TextStyle(color: Colors.black),
            ),
            Text(
              Provider.of<CountB>(context).astr,
              style: TextStyle(color: Colors.black),
            ),
            Text(
              context.watch<CountB>().astr,
              style: TextStyle(color: Colors.black),
            ),
          ],
        ),
        ButtonBar(
          alignment: MainAxisAlignment.spaceAround,
          children: <Widget>[
            InkWell(
              onTap: () {
                DateTime now = new DateTime.now();
                Provider.of<CountA>(context, listen: false).astr = 'a:$now';
              },
              child: Text(
                '修改a',
              ),
            ),
            InkWell(
              onTap: () {
                DateTime now = new DateTime.now();
                Provider.of<CountB>(context, listen: false).astr = 'b:$now';
              },
              child: Text(
                '修改b',
              ),
            ),
          ],
        ),
      ],
    );
  }
}

原理

Provider实际上是个无状态的StatelessWidget,通过包装了InheritedWidget实现父子组件的数据共享,

通过自定义InheritedElement实现刷新。

Provider通过与ChangeNotifier配合使用,实现了观察者模式,Provider会将子组件添加到父组件的依赖关系中,

在notifyListeners()时,会执行InheritedContext.markNeedsNotifyDependents(),将组件标记为dirty等待重绘。

Consumer会只将被它包裹住的子组件注册给父的_dependents形成依赖关系,从而实现了局部更新。

demo

AddPage.dart

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:provider_test/test/ChildPage.dart';
import 'package:provider_test/test/model/count.dart';

class AddPage extends StatefulWidget {
  AddPage({this.countModel});

  CountModel countModel;

  @override
  _AddPageState createState() => _AddPageState()..countModel = countModel;
}

class _AddPageState extends State<AddPage> {
  CountModel countModel;

  @override
  void setState(fn) {
    // TODO: implement setState
    super.setState(fn);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SafeArea(
        child: Container(
          color: Colors.white,
          child: Column(
            // mainAxisSize: MainAxisSize.max,
            crossAxisAlignment: CrossAxisAlignment.start,
            children: <Widget>[
              Text(
                '我是父widget',
                style: TextStyle(
                  fontSize: 20,
                  color: Colors.black,
                ),
              ),
              Stack(
                alignment: Alignment.center,
                children: <Widget>[
                  Container(
                    width: double.maxFinite,
                    child: ChangeNotifierProvider<CountModel>.value(
                      value: this.countModel,
                      builder: (context, child) {
                        return Container(
                          child: Column(
                            crossAxisAlignment: CrossAxisAlignment.start,
                            children: <Widget>[
                              ChildAPage(),
                              Text(
                                  'countModel: ${countModel.count.toString()}'),
                              Text(
                                  'Provider.of: ${Provider.of<CountModel>(context).count.toString()}'),
                              Text(
                                  'context.watch: ${context.watch<CountModel>().count.toString()}'),
                              Consumer<CountModel>(
                                builder: (context, model, child) {
                                  return Text(
                                      'Consumer: ${model.count.toString()}');
                                },
                              ),
                            ],
                          ),
                        );
                      },
                    ),
                  ),
                  Positioned(
                    bottom: 20,
                    right: 20,
                    child: Container(
                      child: Column(
                        mainAxisSize: MainAxisSize.min,
                        children: <Widget>[
                          FlatButton(
                              color: Colors.yellow,
                              onPressed: () {
                                countModel.add();
                              },
                              child: Icon(Icons.add)),
                          FlatButton(
                              color: Colors.red,
                              onPressed: () {
                                countModel.minus();
                              },
                              child: Icon(Icons.remove)),
                        ],
                      ),
                    ),
                  ),
                ],
              ),
            ],
          ),
        ),
      ),
    );
  }
}


ChildPage.dart

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:provider_test/test/model/count.dart';

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

  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.greenAccent,
      child: Column(
        children: <Widget>[
          Text(
            '我是子widget',
            style: TextStyle(
              fontSize: 20,
            ),
          ),
          Text('${Provider.of<CountModel>(context).count}'),
          Text('${context.watch<CountModel>().count}'),
          FlatButton.icon(
            color: Colors.red,
            icon: Icon(Icons.add_circle_outline),
            label: Text('加'),
            onPressed: () {
              //Provider.of<CountModel>(context).add();
              Provider.of<CountModel>(context, listen: false).add();
            },
          ),
          FlatButton.icon(
            icon: Icon(Icons.remove_circle_outline),
            color: Colors.yellow,
            label: Text('减'),
            onPressed: () {
              //Provider.of<CountModel>(context).minus();
              Provider.of<CountModel>(context, listen: false).minus();
            },
          )
        ],
      ),
    );
  }
}

CountModel.dart


import 'package:flutter/material.dart';

class CountModel extends ChangeNotifier {
  int _count;

  CountModel() : _count = 1;

  int get count => _count;

  set count(int count) {
    _count = count;
    notifyListeners();
  }

  void add() {
    this.count = _count+1;
  }

  void minus() {
    this.count = _count-1;
  }
}

几种状态同步框架的对比和选择

640.png

相关文章

网友评论

      本文标题:2022-02-24 Flutter Provider使用

      本文链接:https://www.haomeiwen.com/subject/soizlrtx.html