FlutterDojo设计之道—状态管理之路(一)

Flutter万物皆Widget的理念很容易搭建出这样一个WidgetTree。android

在这个Widget Tree中,一般会存在不少组件之间的相互依赖,时间一长,就很容易变成下面这样。git

这是申明式编程的通病,由于Widget用于展现数据,而数据可能来源于不少其它的Widget,这时候跨Widget共享数据、传递数据,就变得很麻烦,并且不容易管理。github

因此,Flutter在StatelessWidget、StatefulWidget的基础之上,还有一个InheritedWidget,专门用于进行数据、状态的共享与传递,除此以外,申明式编程独特的响应式架构,也经过观察者模式,让数据状态改变的监听变得比较容易,这些都是Flutter处理数据的优点。web

下面的文章,将带领你们梳理Flutter中的数据流向,掌握Flutter的状态管理方案。编程

开篇

要管理Widget的数据、状态,首先要了解下,在Flutter中有哪些须要管理数据的场景。微信

通常来讲,数据管理有两个场景:架构

  • 同页面跨Widget数据管理
  • 跨页面数据管理

Flutter在同一个Page中,可能存在不少的不一样的Widget,这些Widget都在同一个Page层级之下,当某个Widget的状态发生改变以后,须要让其它Widget响应。less

另一种,就是多页面之间的数据共享。dom

那它们的区别是什么呢,在同一个Page下,全部的Widget与Page根Widget是能够造成父子关系的,由于经过PageRoute产生的新页面,其Page根Widget是挂载到App根Widget上的,多个Page之间的Widget,不存在父子关系。编辑器

首先,咱们先来看下同页面跨Widget数据管理。

为了保证文章的完整性,本文会由浅入深,依次讲解Flutter中状态管理的方方面面,因此有些冗余的地方,请不要介意。

方案1-1 :StatefulWidget

这个相信你们都很了解了,StatefulWidget经过State来保存状态,当调用setState函数以后,整个StatefulWidget会从新执行build函数,从而使用全新的数据,生成新的Widget,这样看来,有了StatefulWidget以后,是否是就能够彻底实现同页面的数据管理了呢?

的确能够,可是有个问题,若是页面里面有100个Widget,数据发生改变后,只有一个Widget须要接受这个改变,修改本身的UI,可是在这个StatefulWidget中,因为调用了setState函数,因此这个页面中的100个Widget都将执行重建,这显然是「家里有矿系列」,因此为了不这个问题,就须要缩小StatefulWidget的范围,让setState函数控制的刷新,尽量的范围小,这样当100个Widget中只有一个须要重建时,就不须要从新建立那99个不须要的Widget了。

可是新的问题又来了,StatefulWidget的范围小了,发生在这个StatefulWidget以外的数据改变,如何让这个StatefulWidget进行刷新呢?这时候,就须要利用到Flutter的响应式编程架构了。

方案1-2:ValueNotifier

从ValueNotifier的注释就能看明白,ValueNotifier实际上实现了一个观察者模式,ValueNotifier会持有一个Value对象,当Value对象发生改变时,即会通知到全部注册过的观察者。

A [ChangeNotifier] that holds a single value.

When [value] is replaced with something that is not equal to the old
value as evaluated by the equality operator ==, this class notifies its
listeners.

那么借助ValueNotifier,就能够实现同Page内跨Widget的数据管理,将须要管理的数据托管给ValueNotifier,全部须要由于该数据而改变的Widget,都会注册监听,那么在数据发生改变时,ValueNotifier将自动通知到全部监听者,从而实现数据的管理。

下面这个例子,就演示了一个最简单的ValueNotifier的使用。

class ValueNotifierWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    ValueNotifier<String> valueNotifier = ValueNotifier<String>('Init String Data');

    return Column(
      children: <Widget>[
        MainTitleWidget('ValueNotifier基本使用'),
        SubtitleWidget('在须要响应的Widget中addListener以后,一旦ValueNotifier的值发生改变,就会触发通知'),
        NotifierWidget(data: valueNotifier),
        RaisedButton(
          onPressed: () => valueNotifier.value = 'New Value ${Random().nextInt(100)}',
          child: Text('Change'),
        ),
      ],
    );
  }
}

class NotifierWidget extends StatefulWidget {
  final ValueNotifier<String> data;

  NotifierWidget({this.data});

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

class _NotifierWidgetState extends State<NotifierWidget> {
  String info;

  @override
  initState() {
    super.initState();
    widget.data.addListener(changeNotifier);
    info = '${widget.data.value}';
  }

  void changeNotifier() {
    setState(() => info = '${widget.data.value}');
  }

  @override
  Widget build(BuildContext context) {
    return Text(
      info,
      style: TextStyle(fontSize: 30),
    );
  }

  @override
  dispose() {
    widget.data.removeListener(changeNotifier);
    super.dispose();
  }
}

NotifierWidget注册了对ValueNotifier的监听,当Demo页面中的其它Widget触发了ValueNotifier的更新的时候(RaisedButton触发),NotifierWidget会自动接受到通知,从而刷新UI。

代码位置:Flutter Dojo-Widget-Async-ValueNotifier

自定义ValueNotifier

ValueNotifier一样能够指定自定义类型,其原理与使用基础类型是同样的。

class ValueNotifierWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    PersonNotifier customNotifier = PersonNotifier(People('xuyisheng', 18));

    return Column(
      children: <Widget>[
        MainTitleWidget('Custom ValueNotifier'),
        CustomNotifierWidget(data: customNotifier),
        RaisedButton(
          onPressed: () => customNotifier.changePeopleName('zhujia'),
          child: Text('Change'),
        ),
      ],
    );
  }
}

class CustomNotifierWidget extends StatefulWidget {
  final ValueNotifier<People> data;

  CustomNotifierWidget({this.data});

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

class _CustomNotifierWidgetState extends State<CustomNotifierWidget> {
  People info;

  @override
  initState() {
    super.initState();
    widget.data.addListener(changeNotifier);
    info = widget.data.value;
  }

  void changeNotifier() {
    setState(() => info = widget.data.value);
  }

  @override
  Widget build(BuildContext context) {
    return Text(
      '${info.name},${info.age}',
      style: TextStyle(fontSize: 30),
    );
  }

  @override
  dispose() {
    widget.data.removeListener(changeNotifier);
    super.dispose();
  }
}

class People {
  String name;
  int age;

  People(this.name, this.age);
}

class PersonNotifier extends ValueNotifier<People> {
  PersonNotifier(People value) : super(value);

  void changePeopleName(String name) {
    value.name = name;
    notifyListeners();
  }
}

一样是点击RaisedButton后,改变ValueNotifier.value的值,从而修改UI。

代码位置:Flutter Dojo-Widget-Async-ValueNotifier

经过ValueNotifier,咱们将每一个可能由于共享数据的变化而改变的Widget,封装起来,从而在数据改变的时候,只更新监听了该数据的Widget。

可是你们有没有发现,在使用ValueNotifier的时候,是有些冗余的,就好像前面用到的NotifierWidget,实际上大部分的ValueNotifier都须要这样配合使用,因此,Flutter也提供了这样一个相似的Widget——ValueListenableBuilder。

1-3:ValueListenableBuilder

ValueListenableBuilder正是这样一个Widget,它封装了对ValueNotifier的使用,简化了其建立过程,Flutter Dojo的首页上,PageView和下面的进度条保存同步的过程,就是经过ValueListenableBuilder来实现的。

代码位置:Flutter Dojo-/pages/main/mainpage_scroll_container.dart

ValueListenableBuilder的使用范式很是简单,即在多个建立修改、监听修改的Widget上,经过ValueNotifier来共享管理数据。

因为ValueListenableBuilder是一个StatefulWidget,因此它们的父Widget能够直接使用StatelessWidget来组织Widget,一个简单的示例以下所示。

class ValueListenableBuilderWidget extends StatefulWidget {
  @override
  _ValueListenableBuilderWidgetState createState() => _ValueListenableBuilderWidgetState();
}

class _ValueListenableBuilderWidgetState extends State<ValueListenableBuilderWidget> {
  int _counter = 0;

  final ValueNotifier<int> _notifier = ValueNotifier<int>(0);

  void _incrementCounter() {
    _counter++;
    _notifier.value++;
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: <Widget>[
        MainTitleWidget('ValueListenableBuilder基本使用'),
        SubtitleWidget('修改数据时未调用setState,因此未经过ValueListenableBuilder管理的数据不会发生改变'),
        SizedBox(height: 20),
        ValueListenableBuilder(
          valueListenable: _notifier,
          builder: (context, value, widget) {
            return Text('Click with ValueListenableBuilder $value');
          },
        ),
        SizedBox(height: 20),
        Text('Click without setState $_counter'),
        SizedBox(height: 20),
        RaisedButton(
          onPressed: () {
            _incrementCounter();
          },
          child: Text('Click me'),
        ),
      ],
    );
  }
}

代码位置:Flutter Dojo-Widget-Async-ValueListenableBuilder

修仙

Flutter Dojo开源至今,受到了不少Flutter学习者和爱好者的喜好,也有愈来愈多的人加入到Flutter的学习中来,因此我建了个Flutter修仙群,可是人数太多,因此分红了【Flutter修仙指南】【Flutter修仙指北】【Flutter修仙指东】三个群,对Flutter感兴趣的朋友,能够添加个人微信,注明加入Flutter修仙群,或者直接关注个人微信公众号【Android群英传】。

感兴趣的朋友能够加我微信【Tomcat_xu】,我拉你入群。

项目地址:

https://github.com/xuyisheng/flutter_dojo


本文分享自微信公众号 - Android群英传(android_heroes)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。

相关文章
相关标签/搜索