Flutter状态管理Provider(二)

       Google2019I/O大会上被谷歌推荐,原本谷歌的provide被弃用,与大部分状态管理一样使用了InheritedWidget。基于Provider3.0
上一篇Flutter状态管理Provider(一)

ChangeNotifierProvider()

       它与scoped_model差不多,不同的是它使用mixin而scoped_model使用的继承,首先状态类mixin一个ChangeNotifier,然后通过调用notifyListeners()来通知状态更新。
       和ValueListenableProvider一样也有两种方式,ChangeNotifierProvider()和ChangeNotifierProvider.value(),区别在于ChangeNotifierProvider()会在销毁时自动调用ChangeNotifier中的dispose()方法释放一些资源。
       下面使用ChangeNotifierProvider()写一个需求,与微信一样底部4个按钮切换界面,然后跳到二级页面点击二级页面按钮控制一级页面的切换,先上图:


sp.2019-06-27 18_11_16.gif

下面上代码:

import "package:flutter/material.dart";
import 'index_page.dart';
import 'package:provider/provider.dart';
import 'index_provider.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    //MultiProvider可以添加多个状态管理
    //包裹在MaterialApp外面,作用范围是全局
    return MultiProvider(
      providers: [
//      两种方式,这里使用ChangeNotifierProvider,因为可以自动调用dispose()方法,帮我释放资源
        ChangeNotifierProvider(builder: (_) => IndexProvider()),
//        ChangeNotifierProvider.value(value: IndexProvider())
      ],
      child: MaterialApp(
        title: "Flutter Demo",
        theme: ThemeData(primarySwatch: Colors.blue),
        home: IndexPage(),
      ),
    );
  }
}
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'index_provider.dart';

class IndexPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Consumer<IndexProvider>(
        //优化:在状态改变时viewpage子页面不会走build方法
        child: PageView(
          physics: NeverScrollableScrollPhysics(), //禁止滚动
          //获取pageController后不监听改变
          controller: Provider.of<IndexProvider>(context,listen: false).pageController,
          children: [ChildPage("第一页"), ChildPage("第二页"), ChildPage("第三页")],
        ),
        builder: (context, indexProvider, child) {
          return Scaffold(
            body: child,
            bottomNavigationBar: BottomNavigationBar(
                onTap: (index) {
                  indexProvider.index = index;
                },
                currentIndex: indexProvider.index,
                items: [
                  BottomNavigationBarItem(icon: Icon(Icons.android), title: Text("android")),
                  BottomNavigationBarItem(icon: Icon(Icons.home), title: Text("home")),
                  BottomNavigationBarItem(icon: Icon(Icons.person), title: Text("person")),
                ]),
            floatingActionButton: FloatingActionButton(onPressed: () {
              Navigator.push(context, MaterialPageRoute(builder: (context) => SecondPage()));
            }),
          );
        });
  }
}

class ChildPage extends StatefulWidget {
  final String title;

  ChildPage(this.title);

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

class _ChildPageState extends State<ChildPage> with AutomaticKeepAliveClientMixin {
  @override
  bool get wantKeepAlive => true;

  @override
  void initState() {
    super.initState();
    print("${widget.title}: initState");
  }

  @override
  Widget build(BuildContext context) {
    super.build(context);
    print("${widget.title}: build");
    return Scaffold(
      backgroundColor: widget.title == '第一页'
          ? Colors.red.withOpacity(0.5)
          : widget.title == '第二页'
          ? Colors.yellow.withOpacity(0.5)
          : Colors.green.withOpacity(0.5),
      appBar: AppBar(title: Text(widget.title)),
      body: Center(child: Text(widget.title)),
    );
  }
}

class SecondPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: Center(
        child: Row(
          mainAxisSize: MainAxisSize.max,
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            FlatButton(onPressed: () => changePageIndex(context, 0), child: Text("切换1"), color: Colors.red),
            FlatButton(onPressed: () => changePageIndex(context, 1), child: Text("切换2"), color: Colors.yellow),
            FlatButton(onPressed: () => changePageIndex(context, 2), child: Text("切换3"), color: Colors.green),
          ],
        ),
      ),
    );
  }

  changePageIndex(context, int index) {
    Provider.of<IndexProvider>(context, listen: false).index = index;
  }
}
import 'package:flutter/material.dart' show ChangeNotifier, PageController;

class IndexProvider with ChangeNotifier {
  int _index = 0;
  PageController pageController;

  int get index => _index;

  set index(int value) {
    _index = value;
    pageController.jumpToPage(this._index);
    notifyListeners();
  }

  IndexProvider() {
    pageController = PageController(initialPage: _index);
  }

  //使用ChangeNotifierProvider会在销毁时调用dispose()方法释放资源
  @override
  void dispose() {
    pageController?.dispose();
    super.dispose();
  }
}

源码
ChangeNotifierProvider.png

StreamProvider

       它有三种使用方式,StreamProvider、StreamProvider.value()和StreamProvider.controller(),StreamProvider和StreamProvider.value()几乎一样,StreamProvider.controller()有一点不一样,先上图:


aaaaaa.2019-06-27 18_15_11.gif

先介绍StreamProvider和StreamProvider.value()

class StreamPage extends StatefulWidget {
  @override
  _StreamPageState createState() => _StreamPageState();
}

class _StreamPageState extends State<StreamPage> {
  StreamController _streamController;
  int _count=4;

  @override
  void initState() {
    super.initState();
    _streamController = StreamController<int>();
  }

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("StreamProvider")),
//      body: StreamProvider<int>.value(
//        value: _streamController.stream,
//        initialData: _count,
////        catchError: ,
//        child: MyText(),
//      ),
      body: StreamProvider<int>(
        builder: (_) => _streamController.stream,//builder等于value
        initialData: _count,//initialData:初始化时的值,不写为null
        catchError: (BuildContext context, Object error) {
          //catchError:异常时调用,返回值与StreamController范型一样
          //如果catchError不写,当报错时屏幕直接爆红出错
          print("哈哈:${error.toString()}");
          return 10000;
        },
        child: MyText(),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          if(_count<8) _streamController.sink.add(++_count);
          else _streamController.sink.addError("异常啦");
        },
        child: Icon(Icons.add),
      ),
    );
  }
}

class MyText extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final count = Provider.of<int>(context);
    return Center(child: Text("$count"));
  }
}

StreamProvider<T>.controller()代码:

class StreamPage extends StatefulWidget {
  @override
  _StreamPageState createState() => _StreamPageState();
}

class _StreamPageState extends State<StreamPage> {
  StreamController _streamController;
  int _count = 4;

  @override
  void initState() {
    super.initState();
    _streamController = StreamController<int>();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("StreamProvider")),
      body: StreamProvider<int>.controller(
        builder: (_) => _streamController,
        initialData: _count, //initialData:初始化时的值,不写为null
        catchError: (BuildContext context, Object error) {
          //catchError:异常时调用,返回值与StreamController范型一样
          //如果catchError不写,当报错时屏幕直接爆红出错
          print("哈哈:${error.toString()}");
          return 10000;
        },
        child: MyText(),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          if (_count < 8)
            _streamController.sink.add(++_count);
          else
            _streamController.sink.addError("异常啦");//手动抛异常
        },
        child: Icon(Icons.add),
      ),
    );
  }
}

class MyText extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final count = Provider.of<int>(context);
    return Center(child: Text("$count"));
  }
}

       嗯?!看上去好像没什么不同,但你仔细看StreamProvider<T>.controller代码里面我没有重写dispose()方法,原因是StreamProvider<T>.controller会帮我们在销毁是调用StreamController的close()方法。
进入源码可以看到


StreamProvider.controller.png

StreamControllerBuilderDelegate.png

总结:

  1. Provider()与Provider.value()区别是Provider()有一个dispose参数传递一个方法可以帮助我们销毁的时候释放资源,它们都不提供状态改变监听。
  2. ValueListenableProvider.value()与ValueListenableProvider()是差不多的,只支持一个状态值。
  3. ChangeNotifierProvider.value()与ChangeNotifierProvider()区别是ChangeNotifierProvider()在销毁的时候调用dispose()释放资源,在需要使用多个状态值时可以使用ChangeNotifierProvider。
  4. ListenableProvider代码没有写出来,它是ChangeNotifierProvider的父类,
    ListenableProvider.value()和ChangeNotifierProvider.value()功能一样,ListenableProvider()与ValueListenableProvider()差不多,但ListenableProvider()多了一个dispose参数,需要自己传,在销毁的时候调用释放资源.
  5. StreamProvider.value()和StreamProvider()基本一样,都需要手动关闭流,而StreamProvider.controller()自动关闭流。
  6. MultiProvider()可以提供多个状态。
  7. Provider.of<T>()用来获取Widget树中的状态,在使用 ValueListenableProvider、ChangeNotifierProvider和StreamProvider时Provider.of<T>()中的listen参数可以控制是否监听状态改变。
  8. Consumer<T>()与Provider.of<T>()都是用来获取Widget树中的状态,但Consumer可以用在没有context的地方,也可以用来优化性能,使用child参数可以缩小重绘的范围。
  9. 状态管理包裹在MaterialApp()外面作用域是全局,其他作用域在本页面或本页的子Widget中;

如有写的不对的地方欢迎指出!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,277评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,777评论 1 298
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,946评论 0 245
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,271评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,636评论 3 288
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,767评论 1 221
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,989评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,733评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,457评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,674评论 2 249
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,155评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,518评论 3 258
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,160评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,114评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,898评论 0 198
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,822评论 2 280
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,705评论 2 273