Flutter实践:深入 flutter 的状态管理方式(2)——演化BloC
在上篇文章中,我详细详情了 InheritedWidget 及 ScopedModel 实现原理与方法,有同学说找不到源码,其实上篇文章包括这篇文章里的源码都按步骤放在样例代码里了,有同学说有点懵,其实上一篇的概念过多而且本身我表达也不是很清晰,英文文档中我也解释的没有完全语义化,所以还请体谅,结合代码你会有更好地了解。
这篇的重点我将放在 BloC 的实现上面,我们已经知道 Strems 的概念,RXDart 是依赖 Streams 使用的输入(Sink)和输出(Stream)封装而成的响应式库,BloC 基于此便可以实时侦听数据的变化而改变数据,并且,BloC 主要处理的问题就是他不会一刀切的升级整个状态树,它关注的是数据,经过一系列解决后得到它并且只改变应用它的 widget。
[图片上传失败…(image-42614a-1549099616513)]
如何将 Stream 中的数据应用到 Widget?
我们先来实践一下如何在 widget 中使用数据。Flutter 提供了一个名为 StreamBuilder 的 StatefulWidget。
StreamBuilder 监听 Stream,每当少量数据流出 Stream 时,它会自动重建,调用其构建器回调。
StreamBuilder<T>( key: ...optional, the unique ID of this Widget... stream: ...the stream to listen to... initialData: ...any initial data, in case the stream would initially be empty... builder: (BuildContext context, AsyncSnapshot<T> snapshot){ if (snapshot.hasData){ return ...the Widget to be built based on snapshot.data } return ...the Widget to be built if no data is available },)
以下示例使用 Stream 而不是 setState() 模拟默认的“计数器”应用程序:
import 'dart:async';import 'package:flutter/material.dart';class CounterPage extends StatefulWidget { @override _CounterPageState createState() => _CounterPageState();}class _CounterPageState extends State<CounterPage> { int _counter = 0; final StreamController<int> _streamController = StreamController<int>(); @override void dispose(){ _streamController.close(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text('Stream version of the Counter App')), body: Center( child: StreamBuilder<int>( stream: _streamController.stream, initialData: _counter, builder: (BuildContext context, AsyncSnapshot<int> snapshot){ return Text('You hit me: ${snapshot.data} times'); } ), ), floatingActionButton: FloatingActionButton( child: const Icon(Icons.add), onPressed: (){ _streamController.sink.add(++_counter); }, ), ); }}
- 第24-30行:我们监听流,每次有一个新值流出这个流时,我们用该值升级 Text;
- 第35行:当我们点击 FloatingActionButton 时,我们递增计数器并通过接收器将其发送到 Stream; 侦听它的 StreamBuilder 注入了该值相应到后重建并“刷新”计数器;
- 我们不再需要 State,所有东西都可以通过 Stream 接受;
- 这里实现了相当大的优化,由于调用 setState() 方法会强制整个 Widget(和任何子组件)重新渲染。 而在这里,只重建 StreamBuilder(当然还有其子组件);
- 我们仍需要使用 StatefulWidget 的唯一起因,仅仅是由于我们需要通过 dispose 方法第15行释放StreamController;
实现真正的 BloC
是时候展示真正的计技术了,我们仍然将 BloC 用于默认的计数器应用中:
void main() => runApp(new MyApp());class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return new MaterialApp( title: 'Streams Demo', theme: new ThemeData( primarySwatch: Colors.blue, ), home: BlocProvider<IncrementBloc>( bloc: IncrementBloc(), child: CounterPage(), ), ); }}class CounterPage extends StatelessWidget { @override Widget build(BuildContext context) { final IncrementBloc bloc = BlocProvider.of<IncrementBloc>(context); return Scaffold( appBar: AppBar(title: Text('Stream version of the Counter App')), body: Center( child: StreamBuilder<int>( stream: bloc.outCounter, initialData: 0, builder: (BuildContext context, AsyncSnapshot<int> snapshot){ return Text('You hit me: ${snapshot.data} times'); } ), ), floatingActionButton: FloatingActionButton( child: const Icon(Icons.add), onPressed: (){ bloc.incrementCounter.add(null); }, ), ); }}class IncrementBloc implements BlocBase { int _counter; // // Stream to handle the counter // StreamController<int> _counterController = StreamController<int>(); StreamSink<int> get _inAdd => _counterController.sink; Stream<int> get outCounter => _counterController.stream; // // Stream to handle the action on the counter // StreamController _actionController = StreamController(); StreamSink get incrementCounter => _actionController.sink; // // Constructor // IncrementBloc(){ _counter = 0; _actionController.stream .listen(_handleLogic); } void dispose(){ _actionController.close(); _counterController.close(); } void _handleLogic(data){ _counter = _counter + 1; _inAdd.add(_counter); }}
这是上篇文章的最后给打大家制造悬念的代码?五脏俱全,基本已经实现了 BloC。
结合上面的例子来分析 BloC 表现出来的优势:(建议先将这段代码跑起来!)
一,BloC 实现了责任分离
你可以看到 CounterPage(第21-45行),其中没有任何业务逻辑。
它承担的负责仅有:
- 显示计数器,现在只在必要时升级
- 提供一个按钮,当按下时,请求执行动作
此外,整个业务逻辑集中在一个单独的类“IncrementBloc”中。
假如现在,假如我们需要更改业务逻辑,只要升级方法 _handleLogic(第77-80行)。 也许新的业务逻辑将要求做非常复杂的事情…… CounterPage 永远与它无关!
二,可测试性
现在,测试业务逻辑也变得更加容易。
无需再通过客户界面测试业务逻辑。 只要要测试 IncrementBloc 类。
三,任意组织布局
因为使用了 Streams,您现在可以独立于业务逻辑组织布局。
你可以从应用程序中的任何位置用任何操作:只要调用 .incrementCounter 接收器就可。
您可以在任何页面的任何位置显示计数器,只要舰艇监听 .outCounter 流。
四,减少 “build” 的数量
不用 setState()
而是使用 StreamBuilder,从而大大减少了“构建”的数量,只减少了所需的数量。
这是性能上的巨提高!
只有一个束缚…… BLoC的可访问性
为了达到各种目的,BLoC 需要可访问。
有以下几种方法可以访问它:
通过全局单例的变量
这种方式很容易实现,但不推荐。 此外,因为 Dart 中没有类析构函数,因而我们永远无法正确释放资源。
作为本地实例
您可以实例化 BLoC 的本地实例。 在某些情况下,此处理方案完全符合需求。 在这种情况下,您应该始终考虑在 StatefulWidget 中初始化,以便您可以利用 dispose() 方法来释放它。
由根组件提供
使其可访问的最常见方式是通过根 Widget,将其实现为 StatefulWidget。以下代码给出了一个通用 BlocProvider 的示例:(这个例子牛逼!)
// Generic Interface for all BLoCsabstract class BlocBase { void dispose();}// Generic BLoC providerclass BlocProvider<T extends BlocBase> extends StatefulWidget { BlocProvider({ Key key, @required this.child, @required this.bloc, }): super(key: key); final T bloc; final Widget child; @override _BlocProviderState<T> createState() => _BlocProviderState<T>(); static T of<T extends BlocBase>(BuildContext context){ final type = _typeOf<BlocProvider<T>>(); BlocProvider<T> provider = context.ancestorWidgetOfExactType(type); return provider.bloc; } static Type _typeOf<T>() => T;}class _BlocProviderState<T> extends State<BlocProvider<BlocBase>>{ @override void dispose(){ widget.bloc.dispose(); super.dispose(); } @override Widget build(BuildContext context){ return widget.child; }}
关于这段通用的 BlocProvider 仔细回味,你会发现其精妙之处!
通用 BlocProvider 的少量解释:
首先,如何将其用作数据提供者?
假如你看了上面BloC 计数器的示例代码示例代码,您将看到以下代码行(第12-15行)
home: BlocProvider<IncrementBloc>( bloc: IncrementBloc(), child: CounterPage(), ),
使用以上代码,我们实例化了一个想要解决 IncrementBloc 的新 BlocProvider,并将 CounterPage 呈现为子组件。
从 BlocProvider 开始的子组件的任何组件部分都将能够通过以下行访问 IncrementBloc:
IncrementBloc bloc = BlocProvider.of<IncrementBloc>(context);
BLoC 的基本使用就详情完了,所有实例代码在这里 ,我将每种状态管理的方法分板块放在里面,选择使用哪种方式运行代码就可。
BloC 其余你必需知道的事情
可以实现多个 BloC
在大型项目中,这是非常可取的。 给以下几个建议:
- (假如有任何业务逻辑)每页顶部有一个BLoC,
- 用一个 ApplicationBloc 来解决应用程序所有状态
- 每个“足够复杂的组件”都有相应的BLoC。
以下示例代码在整个应用程序的顶部使用 ApplicationBloc,而后在 CounterPage 顶部使用 IncrementBloc。该示例还展现了如何使用两个 Bloc:
void main() => runApp( BlocProvider<ApplicationBloc>( bloc: ApplicationBloc(), child: MyApp(), ));class MyApp extends StatelessWidget { @override Widget build(BuildContext context){ return MaterialApp( title: 'Streams Demo', home: BlocProvider<IncrementBloc>( bloc: IncrementBloc(), child: CounterPage(), ), ); }}class CounterPage extends StatelessWidget { @override Widget build(BuildContext context){ final IncrementBloc counterBloc = BlocProvider.of<IncrementBloc>(context); final ApplicationBloc appBloc = BlocProvider.of<ApplicationBloc>(context); ... }}
为何不用 InheritedWidget 来全局管理 BloC 的状态
我为此也整理了一个将 BLoC 结合 InheritedWidget 使用的示例:bloc_inherited(在 Vscode 打开这段代码是 [close_sinks] 的警告的)
在很多与 BLoC 相关的文章中,您将看到 Provider 的实现其实是一个 InheritedWidget。
当然, 这是完全可以实现的,然而,
- 一个 InheritedWidget 没有提供任何 dispose 方法,记住,在不再需要资源时总是释放资源是一个很好的做法。
- 当然,你也可以将 InheritedWidget 包装在另一个 StatefulWidget 中,但是,乍样使用 InheritedWidget 并没有什么便利之处!
- 最后,假如不受控制,使用 InheritedWidget 经常会导致少量反作用(请参阅下面的 InheritedWidget 上的提示)。
这 3 点解释了我为何将通用 BlocProvider 实现为 StatefulWidget,这样我即可以释放资源。
Flutter无法实例化泛型类型
不幸的是,Flutter 无法实例化泛型类型,我们必需将 BLoC 的实例传递给 BlocProvider。 为了在每个BLoC中强制执行 dispose() 方法,所有BLoC都必需实现 BlocBase 接口。
关于使用 InheritedWidget 的提示
在使用 InheritedWidget 并通过 context.inheritFromWidgetOfExactType(…) 获取指定类型最近的 Widget 时,每当InheritedWidget 的父级或者者子布局发生变化时,这个方法会自动将当前 “context”(= BuildContext)注册到要重建的 widget 当中。
请注意,为了完全正确,我刚才解释的与 InheritedWidget 相关的问题只发生在我们将 InheritedWidget 与 StatefulWidget 结合使用时。 当您只使用没有 State 的 InheritedWidget 时,问题就不会发生。
总结
Flutter 状态管理的这几种模式同样可以适用于很多软件开发中,而 BloC 模式最初的设想是实现允许独立于平台重用相同的代码!因而多花时间学习这类模式便是软件开发的根基。
我的建议是将实例代码运行出来阅读代码,依靠文章了解!希望能帮助到你!
参考链接
这篇内容是我反复看完 Build reactive mobile apps with Flutter (Google I/O ’18) 谷歌大会写完的。
并且大量借鉴了 Reactive Programming – Streams – BLoC 这篇文章。
1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长!
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是摆设,本站源码仅提供给会员学习使用!
7. 如遇到加密压缩包,请使用360解压,如遇到无法解压的请联系管理员
开心源码网 » Flutter实践:深入 flutter 的状态管理方式(2)——演化BloC