在上篇文章中,我详细介绍了 InheritedWidget 及 ScopedModel 实现原理与方法,有同窗说找不到源码,其实上篇文章包括这篇文章里的源码都按步骤放在样例代码里了,有同窗说有点懵,其实上一篇的概念过多并且自己我表达也不是很清晰,英文文档中我也解释的没有彻底语义化,因此还请谅解,结合代码你会有更好地理解。html
这篇的重点我将放在 BloC 的实现上面,咱们已经知道 Strems 的概念,RXDart 是依赖 Streams 使用的输入(Sink)和输出(Stream)封装而成的响应式库,BloC 基于此即可以实时侦听数据的变化而改变数据,而且,BloC 主要解决的问题就是他不会一刀切的更新整个状态树,它关注的是数据,通过一系列处理后获得它而且只改变应用它的 widget。react
咱们先来实践一下如何在 widget 中使用数据。Flutter 提供了一个名为 StreamBuilder 的 StatefulWidget。git
StreamBuilder 监听 Stream,每当一些数据流出 Stream 时,它会自动重建,调用其构建器回调。github
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() 模拟默认的“计数器”应用程序:app
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);
},
),
);
}
}
复制代码
是时候展示真正的计技术了,咱们依然将 BloC 用于默认的计数器应用中:less
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。async
结合上面的例子来分析 BloC 体现出来的优点:(建议先将这段代码跑起来!)ide
你能够看到 CounterPage(第21-45行),其中没有任何业务逻辑。函数
它承担的负责仅有:布局
此外,整个业务逻辑集中在一个单独的类“IncrementBloc”中。
若是如今,若是咱们须要更改业务逻辑,只需更新方法 _handleLogic(第77-80行)。 也许新的业务逻辑将要求作很是复杂的事情...... CounterPage 永远与它无关!
如今,测试业务逻辑也变得更加容易。
无需再经过用户界面测试业务逻辑。 只须要测试 IncrementBloc 类。
因为使用了 Streams,您如今能够独立于业务逻辑组织布局。
你能够从应用程序中的任何位置用任何操做:只需调用 .incrementCounter 接收器便可。
您能够在任何页面的任何位置显示计数器,只需舰艇监听 .outCounter 流。
不用 setState()
而是使用 StreamBuilder,从而大大减小了“构建”的数量,只减小了所需的数量。
这是性能上的巨提升!
为了达到各类目的,BLoC 须要可访问。
有如下几种方法能够访问它:
经过全局单例的变量
这种方式很容易实现,但不推荐。 此外,因为 Dart 中没有类析构函数,所以咱们永远没法正确释放资源。
做为本地实例
您能够实例化 BLoC 的本地实例。 在某些状况下,此解决方案彻底符合需求。 在这种状况下,您应该始终考虑在 StatefulWidget 中初始化,以便您能够利用 dispose() 方法来释放它。
由根组件提供 使其可访问的最多见方式是经过根 Widget,将其实现为 StatefulWidget。
如下代码给出了一个通用 BlocProvider 的示例:(这个例子牛逼!)
// Generic Interface for all BLoCs
abstract class BlocBase {
void dispose();
}
// Generic BLoC provider
class 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 的基本使用就介绍完了,全部实例代码在这里 ,我将每种状态管理的方法分模块放在里面,选择使用哪一种方式运行代码便可。
在大型项目中,这是很是可取的。 给如下几个建议:
如下示例代码在整个应用程序的顶部使用 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);
...
}
}
复制代码
我为此也整理了一个将 BLoC 结合 InheritedWidget 使用的示例:bloc_inherited(在 Vscode 打开这段代码是 [close_sinks] 的警告的)
在不少与 BLoC 相关的文章中,您将看到 Provider 的实现实际上是一个 InheritedWidget。
固然, 这是彻底能够实现的,然而,
这 3 点解释了我为什么将通用 BlocProvider 实现为 StatefulWidget,这样我就能够释放资源。
Flutter没法实例化泛型类型
不幸的是,Flutter 没法实例化泛型类型,咱们必须将 BLoC 的实例传递给 BlocProvider。 为了在每一个BLoC中强制执行 dispose() 方法,全部BLoC都必须实现 BlocBase 接口。
在使用 InheritedWidget 并经过 context.inheritFromWidgetOfExactType(...) 获取指定类型最近的 Widget 时,每当InheritedWidget 的父级或者子布局发生变化时,这个方法会自动将当前 “context”(= BuildContext)注册到要重建的 widget 当中。
请注意,为了彻底正确,我刚才解释的与 InheritedWidget 相关的问题只发生在咱们将 InheritedWidget 与 StatefulWidget 结合使用时。 当您只使用没有 State 的 InheritedWidget 时,问题就不会发生。
Flutter 状态管理的这几种模式一样能够适用于不少软件开发中,而 BloC 模式最初的设想是实现容许独立于平台重用相同的代码!所以多花时间学习这类模式即是软件开发的根基。
个人建议是将实例代码运行出来阅读代码,依靠文章理解!但愿能帮助到你!
这篇内容是我反复看谷歌大会写完的。
而且大量借鉴了 Reactive Programming - Streams - BLoC 这篇文章。