# flutter_provider_demo **Repository Path**: LiuLinXi/flutter_provider_demo ## Basic Information - **Project Name**: flutter_provider_demo - **Description**: provider状态管理简单使用 - **Primary Language**: Dart - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 4 - **Forks**: 0 - **Created**: 2019-05-12 - **Last Updated**: 2020-12-19 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # flutter_provider_demo ## Provider [参考文章]() ### 基础使用 引入依赖: ```dart provider: ^3.0.0+1 ``` 使用: ```dart import 'package:provider/provider.dart'; ``` 创建一个Provider类 ```dart import 'package:flutter/foundation.dart'; class WeatherInfo with ChangeNotifier{ //温度单位 String _tempType='Celcius'; //温度值 int _temperatureValue=25; String get tempType => _tempType; int get temperatureValue=>_temperatureValue; set temperatureType(String tempType){ _tempType=tempType; notifyListeners(); } set temperatureValue(int temperatureValue){ _temperatureValue=temperatureValue; notifyListeners(); } } ``` 在父类布局前加入Provider ```dart import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'WeatherInfo.dart'; void main() { runApp(MyApp()); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return ChangeNotifierProvider( //类似于实例化一个WeatherInfo builder: (context) => WeatherInfo(), child: MaterialApp( debugShowCheckedModeBanner: false, title: 'Hello Rectangle', home: MyHomePage(), ), ); } } ``` 子布局获取值的方法 第一种: ```dart var weatherInfo = Provider.of(context); ``` 第二种: ```dart Consumer(builder: (context, weather, _) { return Text( weather.temperatureValue.toString(), style: TextStyle(fontSize: 30), ); }), ``` 完整代码: ```dart import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'WeatherInfo.dart'; void main() { runApp(MyApp()); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return ChangeNotifierProvider( //类似于实例化一个WeatherInfo builder: (context) => WeatherInfo(), child: MaterialApp( debugShowCheckedModeBanner: false, title: 'Hello Rectangle', home: MyHomePage(), ), ); } } class MyHomePage extends StatelessWidget { @override Widget build(BuildContext context) { var weatherInfo = Provider.of(context); return Scaffold( appBar: AppBar( title: Text('Provider Demo'), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Consumer(builder: (context, weather, _) { return Text( weather.temperatureValue.toString(), style: TextStyle(fontSize: 30), ); }), Text( weatherInfo.tempType, style: TextStyle(fontSize: 30), ), RaisedButton( onPressed: () { String newTemperatureType = weatherInfo.tempType == 'Celcius' ? 'Fahrenheit' : 'Celcius'; weatherInfo.temperatureType = newTemperatureType; }, child: Text('改变温度单位'), ), RaisedButton( onPressed: () { weatherInfo.temperatureValue++; }, child: Text('温度+1'), ) ], ), ), ); } } ``` 效果:
### StreamProvider的使用 首先是使用单例来创建一个Stream ```dart class ThemeDataBloc { StreamController _streamController; Stream _stream; ThemeData _themeData; //这边使用单例模式,保证每次获得同一个实例化对象 static ThemeDataBloc _instance; ThemeDataBloc._insteral() { _streamController = StreamController(); _stream = _streamController.stream; _themeData = ThemeData.light(); } static ThemeDataBloc _getInstance() { if (_instance == null) { _instance = ThemeDataBloc._insteral(); } return _instance; } factory ThemeDataBloc() => _getInstance(); static ThemeDataBloc get instance=>_getInstance(); //这边使用单例模式,保证每次获得同一个实例化对象 Stream get stream => _stream; changeTheme() { _themeData = _themeData == ThemeData.light() ? ThemeData.dark() : ThemeData.light(); _streamController.sink.add(_themeData); } dispose() { _streamController.close(); } } ``` 之后在顶层中加入StreamProvider ```dart class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MultiProvider( providers: [ ChangeNotifierProvider( //类似于实例化一个WeatherInfo builder: (context) => WeatherInfo()), //这边使用dispose来控制销毁 Provider( builder: (_) => CounterBloc(), dispose: (_, CounterBloc bloc) => bloc.dispose(), ), //使用StreamProvider提供value值 StreamProvider.value(value:ThemeDataBloc().stream,initialData: ThemeData.light(),) ], child: MaterialApp( debugShowCheckedModeBanner: false, title: 'Provider使用Demo', home: MyHomePage(), ), ); } } ``` 在其它页面获取和改变value ```dart class MultiProviderDemo extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( home: MyHomePage(), //获取值 theme: Provider.of(context), ); } } class MyHomePage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('MultiProviderWithStream'), ), body: Center( child: ......, floatingActionButton: FloatingActionButton( onPressed: () { //改变值 ThemeDataBloc().changeTheme(); }, child: Icon(Icons.change_history), ), ); } } ``` ### 补充知识 **Dispoaeble:** ![Dispose](https://images.gitee.com/uploads/images/2019/0512/153646_f600656f_1812582.png "屏幕截图.png") **MulitProvider:** ![多Provider](https://images.gitee.com/uploads/images/2019/0512/153733_2333831b_1812582.png "屏幕截图.png") **Listen to parts:** 监听全屏幕的同时更加细的监听每个对象 ![全监听](https://images.gitee.com/uploads/images/2019/0512/153807_766b5f11_1812582.png "屏幕截图.png") ![部分监听](https://images.gitee.com/uploads/images/2019/0512/154002_3f23aa20_1812582.png "屏幕截图.png") **Provide anything:** Provider提供流的处理方法 ## Stream* 作者:Vadaski 链接:https://juejin.im/post/5baa4b90e51d450e6d00f12e - Stream非常有用,它是为处理**异步事件**而生的。而在应用中有大量的场景需要使用异步事件,例如请求网络,和用户交互等等,它们都无法同步完成。Stream能够极大的帮助我们处理这些问题。 ### 什么是Stream? Stream非常有特点但却不太好理解,我与其按照字面意思把它看作流,更愿意把它看成一个工厂或者是机器。 ![img](https://user-gold-cdn.xitu.io/2018/10/1/1662d2d63df9251d?imageView2/0/w/1280/h/960/format/webp/ignore-error/1) **特点**: - 它有一个入口,可以放东西/指令(anything) - 这个机器不知道入口什么时候会放东西进来 - 中间的机器能够生产或者加工,这应该会耗费一些时间 - 他有一个出口,应该会有产品从那出来 - 我们也不知道到底什么时候产品会从出口出来 整个过程,时间都是一个不确定因素,我们随时都可以向这个机器的入口放东西进去,放进去了以后机器进行处理,但是我们并不知道它多久处理完。所以出口是需要专门派人盯着的,等待机器流出东西来。整个过程都是以异步的眼光来看的。 #### 将机器模型转化成Stream ![img](assets/1662d3f569b48736) - 这个大机器就是**StreamController**,它是创建流的方式之一。 - StreamController有一个入口,叫做**sink** - sink可以使用**add**方法放东西进来,放进去以后就**不再关心**了。 - 当有东西从sink进来以后,我们的机器就开始工作啦,空空空。 - StreamController有一个出口,叫做stream - 机器处理完毕后就会把产品从出口丢出来,但是我们并不知道什么时候会出来,所以我们需要使用listen方法一直监听这个出口。 - 而且当多个物品被放进来了之后,它不会打乱顺序,而是先入先出。 ### 如何使用Stream? #### 获得Stream的方法 - 通过构造函数 - 使用StreamController - IO Stream stream有三个构造方法: - **Stream.fromFuture**:从Future创建新的单订阅流,当future完成时将触发一个data或者error,然后使用Down事件关闭这个流。 - **Stream.fromFutures**:从一组Future创建一个单订阅流,每个future都有自己的data或者error事件,当整个Futures完成后,流将会关闭。如果Futures为空,流将会立刻关闭。 - **Stream.fromIterable**:创建从一个集合中获取其数据的单订阅流。 ```dart Stream.fromIntreable([1,2,3]); ``` #### 监听Stream的方法 监听一个流最常见的方法就是listen。当有事件发出时,流将会通知listener。Listen方法提供了这几种触发事件: - onData(必填):收到数据时触发 - onError:收到Error时触发 - onDone:结束时触发 - unsubscribeOnError:遇到第一个Error时是否取消订阅,默认为false #### StreamController 如果你想创建一条新的流的话,非常简单!使用StreamController,它为你提供了非常丰富的功能,你能够在streamController上发送数据,处理错误,并获得结果! ```dart //任意类型的流 StreamController controller = StreamController(); controller.sink.add(123); controller.sink.add("xyz"); controller.sink.add(Anything); //创建一条处理int类型的流 StreamController numController = StreamController(); numController.sink.add(123); ``` 泛型定义了我们能向流上推送什么类型的数据。它可以是任何类型! 我们再来看看如何获取最后的结果。 ```dart StreamController controller = StreamController(); //监听这个流的出口,当有data流出时,打印这个data StreamSubscription subscription = controller.stream.listen((data)=>print("$data")); controller.sink.add(123); ``` **输出:** 123 你需要将一个方法交给stream的listen函数,这个方法入参(data)是我们的StreamController处理完毕后产生的结果,我们监听出口,并获得了这个结果(data)。这里可以使用lambda表达式,也可以是其他任何函数。 (这里我为了方便区分,把listen说成函数,(data)=>print(data)说成方法,其实是一个东西。) #### Transforming an existing stream 假如你已经有了一个流,你可以通过它转化成为一条新的流。非常简单!流提供了map(),where(),expand(),和take()方法,能够轻松将已有的流转化为新的流。 **where** 如果你想要筛选掉一些不想要的事件。例如一个猜数游戏,用户可以输入数字,当输入正确的时候,我们做出一定反应。而我们必须筛选掉所有错误的答案,这个时候我们可以使用where筛选掉不需要的数字。 ```dart stream.where((event){...}) ``` where函数接收一个事件,每当这个流有东西流到where函数的时候,这就是那个事件。我们或许根本不需要这个事件,但是必须作为参数传入。 **take** 如果你想要控制这个流最多能传多少个东西。比如输入密码,我们可能想让用户最多输四次,那么我们可以使用take来限制。 ```dart stream.take(4); ``` take函数接收一个int,代表最多能经过take函数的事件次数。当传输次数达到这个数字时,这个流将会关闭,无法再传输。 #### transform 如果你需要更多的控制转换,那么请使用transform()方法。他需要配合StreamTransformer进行使用。我们先来看下面一段猜数游戏,然后我会向你解释。 ```dart StreamController controller = StreamController(); final transformer = StreamTransformer.fromHandlers( handleData:(value, sink){ if(value==100){ sink.add("你猜对了"); } else{ sink.addError('还没猜中,再试一次吧'); } }); controller.stream .transform(transformer) .listen( (data) => print(data), onError:(err) => print(err)); controller.sink.add(23); //controller.sink.add(100); ``` **输出:** 还没猜中,再试一次吧 StreamTransformer是我们stream的检查员,他负责接收stream通过的信息,然后进行处理返回一条新的流。 - S代表之前的流的输入类型,我们这里是输入一个数字,所以是int。 - T代表转化后流的输入类型,我们这里add进去的是一串字符串,所以是String。 - handleData接收一个value并创建一条新的流并暴露sink,我们可以在这里对流进行转化。 - 我们还可以addError进去告诉后面有问题。 然后我们监听transform之后的流,当转换好的event流出时,我们打印这个event,这个event就是我们刚才add进sink的数据。onError能够捕捉到我们add进去的err。 ### Stream的种类 流有两种 - "Single-subscription" streams 单订阅流 - "broadcast" streams 多订阅流 #### "Single-subscription" streams 单个订阅流在流的整个生命周期内仅允许有一个listener。它在有收听者之前不会生成事件,并且在取消收听时它会停止发送事件,即使你仍然在Sink.add更多事件。 即使在第一个订阅被取消后,也不允许在单个订阅流上进行两次侦听。 单订阅流通常用于流式传输更大的连续数据块,如文件I / O. ```dart StreamController controller = StreamController(); controller.stream.listen((data)=> print(data)); controller.stream.listen((data)=> print(data)); controller.sink.add(123); ``` **输出:** Bad state: Stream has already been listened to. **单订阅流不能有多个收听者。** #### "Broadcast" streams 广播流允许任意数量的收听者,且无论是否有收听者,他都能产生事件。所以中途进来的收听者将**不会收到**之前的消息。 如果多个收听者想要收听单个订阅流,请使用asBroadcastStream在非广播流之上创建广播流。 如果在触发事件时将收听者添加到广播流,则该侦听器将不会接收当前正在触发的事件。如果取消收听,收听者会立即停止接收事件。 一般的流都是单订阅流。从Stream继承的广播流必须重写isBroadcast 才能返回true。 ```dart StreamController controller = StreamController(); //将单订阅流转化为广播流 Stream stream = controller.stream.asBroadcastStream(); stream.listen((data)=> print(data)); stream.listen((data)=> print(data)); controller.sink.add(123); ``` **输出:** 123 123