Flutter - 数据共享

311 阅读6分钟

一、InheritedWidget

1、简介

用处很简单: 帮根视图的数据、共享到子视图。例如控制主题、语言环境。

官方介绍如下:

InheritedWidget是 Flutter 中非常重要的一个功能型组件,它提供了一种在 widget 树中从上到下共享数据的方式,比如我们在应用的根 widget 中通过InheritedWidget共享了一个数据,那么我们便可以在任意子widget 中来获取该共享的数据!这个特性在一些需要在整个 widget 树中共享数据的场景中非常方便!如Flutter SDK中正是通过 InheritedWidget 来共享应用主题(Theme)和 Locale (当前语言环境)信息的。

2、实例

通过实现一个简单的计算器、来直观的展示 InheritedWidget 的用法。

(1)、定义一个数据共享类。

ShareDataWidget 如下 :

class ShareDataWidget extends InheritedWidget {
  ShareDataWidget({
    Key? key,
    required this.data,
    required Widget child,
  }) : super(key: key, child: child);

  final int data; //需要在子树中共享的数据,保存点击次数

  //定义一个便捷方法,方便子树中的widget获取共享数据
  static ShareDataWidget? of(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType<ShareDataWidget>();
  }

  //该回调决定当data发生变化时,是否通知子树中依赖data的Widget重新build
  @override
  bool updateShouldNotify(ShareDataWidget old) {
    return old.data != data;
  }
}

以上 data 就是我们需要共享的数据。 在真实的开发中、可以给他改成T来兼容各种业务场景。 当data发生改变的时候、就会通过代码方法 updateShouldNotify判断要不要通知其他 子Widget要不要更新。

(2)、定义一个测试用的子Widget

class _TestWidget extends StatefulWidget {
  @override
  __TestWidgetState createState() => __TestWidgetState();
}

class __TestWidgetState extends State<_TestWidget> {
  @override
  Widget build(BuildContext context) {
    //使用InheritedWidget中的共享数据
    return Text(ShareDataWidget.of(context)!.data.toString());
  }

  @override //下文会详细介绍。
  void didChangeDependencies() {
    super.didChangeDependencies();
    //父或祖先widget中的InheritedWidget改变(updateShouldNotify返回true)时会被调用。
    //如果build中没有依赖InheritedWidget,则此回调不会被调用。
    print("Dependencies change");
  }
}

(3)、定义我们的根视图。

InheritedWidgetTestRoute

class InheritedWidgetTestRoute extends StatefulWidget {
  @override
  _InheritedWidgetTestRouteState createState() => _InheritedWidgetTestRouteState();
}

class _InheritedWidgetTestRouteState extends State<InheritedWidgetTestRoute> {
  int count = 0;

  @override
  Widget build(BuildContext context) {
    return  Center(
      child: ShareDataWidget( //使用ShareDataWidget
        data: count,
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Padding(
              padding: const EdgeInsets.only(bottom: 20.0),
              child: _TestWidget(),//子widget中依赖ShareDataWidget
            ),
            ElevatedButton(
              child: Text("Increment"),
              //每点击一次,将count自增,然后重新build,ShareDataWidget的data将被更新  
              onPressed: () => setState(() => ++count),
            )
          ],
        ),
      ),
    );
  }
}

上代码可以看见如下:

  • count 就是我们需要共享的数据、在创建 ShareDataWidget 的时候帮count作为共享数据传给了ShareDataWidget
  • 点击自增的是、去修改共享数据。
  • _TestWidget 通过 ShareDataWidget.of(context)!.data.toString() 方式来展示共享数据、监听数据改变。并通过实现 didChangeDependencies 来捕捉共享状态的变化。

(4)、共享流程

image.png

二、Provider

1、介绍

InheritedWidget 是让根视图的数据共享到子视图。 而Provider就是跨组件共享数据

官方介绍如下:

在 Flutter 开发中,状态管理是一个永恒的话题。一般的原则是:如果状态是组件私有的,则应该由组件自己管理;如果状态要跨组件共享,则该状态应该由各个组件共同的父元素来管理。对于组件私有的状态管理很好理解,但对于跨组件共享的状态,管理的方式就比较多了,如使用全局事件总线EventBus(将在下一章中介绍),它是一个观察者模式的实现,通过它就可以实现跨组件状态同步:状态持有方(发布者)负责更新、发布状态,状态使用方(观察者)监听状态改变事件来执行一些操作。

我们可以发现,通过观察者模式来实现跨组件状态共享有一些明显的缺点:

  1. 必须显式定义各种事件,不好管理。
  2. 订阅者必须需显式注册状态改变回调,也必须在组件销毁时手动去解绑回调以避免内存泄露。

在Flutter当中有没有更好的跨组件状态管理方式了呢?答案是肯定的,那怎么做的?我们想想前面介绍的InheritedWidget,它的天生特性就是能绑定InheritedWidget与依赖它的子孙组件的依赖关系,并且当InheritedWidget数据发生变化时,可以自动更新依赖的子孙组件!利用这个特性,我们可以将需要跨组件共享的状态保存在InheritedWidget中,然后在子组件中引用InheritedWidget即可,Flutter社区著名的Provider包正是基于这个思想实现的一套跨组件状态共享解决方案,接下来我们便详细介绍一下Provider的用法及原理

2、购物车实例

(1)、定义保存共享数据

// ignore_for_file: prefer_const_constructors_in_immutables, prefer_const_constructors, avoid_print, use_key_in_widget_constructors

import 'package:flutter/material.dart';

class InheritedProvider<T> extends InheritedWidget {
  InheritedProvider({
    required this.data,
    required Widget child,
  }) : super(child: child);

  final T data;

  @override
  bool updateShouldNotify(InheritedProvider<T> old) {
    //在此简单返回true,则每次更新都会调用依赖其的子孙节点的`didChangeDependencies`。
    return true;
  }
}

(2)、定义购物车数据模型

// ignore_for_file: prefer_const_constructors_in_immutables, prefer_const_constructors, avoid_print, use_key_in_widget_constructors

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

class CartModel extends ChangeNotifier {
  // 用于保存购物车中商品列表
  final List<Item> _items = [];

  // 禁止改变购物车里的商品信息
  UnmodifiableListView<Item> get items => UnmodifiableListView(_items);

  // 购物车中商品的总价
  double get totalPrice =>
      _items.fold(0, (value, item) => value + item.count * item.price);

  // 将 [item] 添加到购物车。这是唯一一种能从外部改变购物车的方法。
  void add(Item item) {
    _items.add(item);
    // 通知监听器(订阅者),重新构建InheritedProvider, 更新状态。
    notifyListeners();
  }
}

class Item {
  Item(this.price, this.count);
  double price; //商品单价
  int count; // 商品份数
  //... 省略其它属性
}

这里使用的是模型继承ChangeNotifier 是一个类似于EventBus的类。内部方法大致如下

class ChangeNotifier implements Listenable {
  List listeners=[];
  @override
  void addListener(VoidCallback listener) {
     //添加监听器
     listeners.add(listener);
  }
  @override
  void removeListener(VoidCallback listener) {
    //移除监听器
    listeners.remove(listener);
  }
  
  void notifyListeners() {
    //通知所有监听器,触发监听器回调 
    listeners.forEach((item)=>item());
  }
   
  ... //省略无关代码
}

(2)、构建一个状态管理的类。

// ignore_for_file: prefer_const_constructors_in_immutables, prefer_const_constructors, avoid_print, use_key_in_widget_constructors, unused_local_variable

import 'package:flutter/material.dart';
import 'package:flutter_share_test/CarProvider/InheritedProvider.dart';

class ChangeNotifierProvider<T extends ChangeNotifier> extends StatefulWidget {
  ChangeNotifierProvider({
    Key? key,
    required this.data,
    required this.child,
  });

  final Widget child;
  final T data;

  //定义一个便捷方法,方便子树中的widget获取共享数据
  static T of<T>(BuildContext context) {
    // final type = _typeOf<InheritedProvider<T>>();
    final provider =
        context.dependOnInheritedWidgetOfExactType<InheritedProvider<T>>();
    return provider!.data;
  }

  @override
  _ChangeNotifierProviderState<T> createState() =>
      _ChangeNotifierProviderState<T>();
}

class _ChangeNotifierProviderState<T extends ChangeNotifier>
    extends State<ChangeNotifierProvider<T>> {
  void update() {
    //如果数据发生变化(model类调用了notifyListeners),重新构建InheritedProvider
    setState(() => {});
  }

  @override
  void didUpdateWidget(ChangeNotifierProvider<T> oldWidget) {
    //当Provider更新时,如果新旧数据不"==",则解绑旧数据监听,同时添加新数据监听
    if (widget.data != oldWidget.data) {
      oldWidget.data.removeListener(update);
      widget.data.addListener(update);
    }
    super.didUpdateWidget(oldWidget);
  }

  @override
  void initState() {
    // 给model添加监听器
    widget.data.addListener(update);
    super.initState();
  }

  @override
  void dispose() {
    // 移除model的监听器
    widget.data.removeListener(update);
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return InheritedProvider<T>(
      data: widget.data,
      child: widget.child,
    );
  }
}

(4)、构建根视图

// ignore_for_file: prefer_const_constructors_in_immutables, prefer_const_constructors, avoid_print, use_key_in_widget_constructors

import 'package:flutter/material.dart';
import 'package:flutter_share_test/CarProvider/CartModel.dart';
import 'package:flutter_share_test/CarProvider/ChangeNotifierProvider.dart';
import 'package:flutter_share_test/CarProvider/Consumer.dart';

class ProviderRoute extends StatefulWidget {
  @override
  _ProviderRouteState createState() => _ProviderRouteState();
}

class _ProviderRouteState extends State<ProviderRoute> {
  @override
  Widget build(BuildContext context) {
    return Center(
        child: Column(
      children: [
        ChangeNotifierProvider<CartModel>(
          data: CartModel(),
          child: Column(
            children: <Widget>[
               Builder(builder: (context) {
                 var cart = ChangeNotifierProvider.of<CartModel>(context);
                 return Text("总价: ${cart.totalPrice}");
              }),

              
              Builder(builder: (context) {
                print("ElevatedButton build"); //在后面优化部分会用到
                return ElevatedButton(
                  child: Text("添加商品"),
                  onPressed: () {
                    //给购物车中添加商品,添加后总价会更新
                    var cart = ChangeNotifierProvider.of<CartModel>(context);
                    cart.add(Item(20.0, 1));
                  },
                );
              }),
            ],
          ),
        ),
        Consumer<CartModel>(
            builder: (context, cart) => Text(" 这是一个外面的控件 ${cart!.totalPrice}")),
      ],
    ));
  }
}

(5)、 优化

上面代码可以看得出来、ChangeNotifierProvider 既充当了订阅者、又是消费者,而且每个地方引用 购物车数据都要使用ChangeNotifierProvider.of<CartModel>(context); 来获取、显然有点麻烦。

所以就定义如下来专门来充当消费者。

// ignore_for_file: prefer_const_constructors_in_immutables, prefer_const_constructors, avoid_print, use_key_in_widget_constructors

import 'package:flutter/material.dart';
import 'package:flutter_share_test/CarProvider/ChangeNotifierProvider.dart';

// 这是一个便捷类,会获得当前context和指定数据类型的Provider
class Consumer<T> extends StatelessWidget {
  Consumer({
    Key? key,
    required this.builder,
  }) : super(key: key);

  final Widget Function(BuildContext context, T? value) builder;

  @override
  Widget build(BuildContext context) {
    return builder(
      context,
      ChangeNotifierProvider.of<T>(context),
    );
  }
}
// 加这个类的主要原因就是、分开语义 不然 ChangeNotifierProvider 既是订阅者、也是消费者。

有了专门的消费者,在我们需要使用购物车数据的时候直接使用Consumer引用即可。

// 他就是专门定义的消费者
Consumer<CartModel>(
  builder: (context, cart) => Text(" ${cart!.totalPrice}")),

(6)、流程如下

image.png