转自连接: www.didierboelens.com/2018/12/rea…
原文作者: Didier Boelens
原文作者更多Flutter文章: www.didierboelens.com/
github:github.com/boeledi/blo…
1.BlocProvider
import 'package:flutter/material.dart';
/// 删除BlocBuilder, BlocDisposer
abstract class BlocBase {
void dispose();
}
// 定义泛型 BlocProvider, 继承 StatefulWidget, 参数继承 BlocBase
class BlocProvider<T extends BlocBase> extends StatefulWidget {
// 构造函数, child 和 bloc 为必要参数
BlocProvider({
Key key,
@required this.child,
@required this.bloc,
}): super(key: key);
/// 定义属性
final Widget child;
final T bloc;
@override
_BlocProviderState<T> createState() => _BlocProviderState<T>();
/// 静态方法, 返回泛型 _BlocProviderInherited
static T of<T extends BlocBase>(BuildContext context){
_BlocProviderInherited<T> provider = context.getElementForInheritedWidgetOfExactType<_BlocProviderInherited<T>>()?.widget;
return provider?.bloc;
}
}
// BlocProvider 对应的 泛型 state
class _BlocProviderState<T extends BlocBase> extends State<BlocProvider<T>>{
@override
void dispose(){
widget.bloc?.dispose();
super.dispose();
}
@override
Widget build(BuildContext context){
/// 通过bloc 及 child 实例化 _BlocProviderInherited泛型
return new _BlocProviderInherited<T>(
bloc: widget.bloc,
child: widget.child,
);
}
}
/// 定义继承自InheritedWidget 的 _BlocProviderInherited泛型
class _BlocProviderInherited<T> extends InheritedWidget {
// 构造函数: 必要参数为 bloc 及 child
_BlocProviderInherited({
Key key,
@required Widget child,
@required this.bloc,
}) : super(key: key, child: child);
final T bloc;
@override
bool updateShouldNotify(_BlocProviderInherited oldWidget) => false;
}
API
why
优势在于性能, 使用 InheritedWidget 组件, 通过调用时间复杂度为 O(1) 的 context.getElementForInheritedWidgetOfExactType() 方法, 即刻获取到对应组件.
2.BLoC 初始化
2.1. 应用内全局使用
2.1.1. 最顶层
示例代码
void main() => runApp(Application());
class Application extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocProvider<AuthenticationBloc>(
blocBuilder: () => AuthenticationBloc(),
blocDispose: (AuthenticationBloc bloc) => bloc?.dispose(),
child: MaterialApp(
title: 'BLoC Samples',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: InitializationPage(),
),
);
}
}
2.2. 应用内部分组件使用
2.2.1. 子组件树顶层
示例代码
class MyTree extends StatelessWidget {
@override
Widget build(BuildContext context){
return BlocProvider<MyBloc>(
blocBuilder: () => MyBloc(),
child: Column(
children: <Widget>[
MyChildWidget(),
],
),
);
}
}
class MyChildWidget extends StatelessWidget {
@override
Widget build(BuildContext context){
MyBloc = BlocProvider.of<MyBloc>(context);
return Container();
}
}
2.3. 单一组件使用
2.3.1. 组件内初始化
3. 事件状态
3.1. BlocEventState
3.1.1. 此BLoC设计初衷
- 接收事件作为输入
- 事件触发时, 调用对用的 eventHandler
- eventHandler 负责基于事件作出对应的行为并反馈相应的状态
展示
代码段 2
// 分别定义BLoC 事件 状态 抽象类 暂时用不上, 后面会用到
abstract class BlocEvent extends Object {}
abstract class BlocState extends Object {}
// 定义 AsyncBlocStateBuilder签名, 用作 StreamBuilder 的实际代理
typedef Widget AsyncBlocStateBuilder<State>(BuildContext context, State state);
// 定义泛型 BLoC时间状态基本抽象类, 实现 BlocBase 接口
// 主要负责定义 eventHandler 处理过程
abstract class BlocEventStateBase<Event, State> implements BlocBase {
// 初始化 事件/状态 控制器
PublishSubject<Event> _eventController = PublishSubject<Event>();
BehaviorSubject<State> _stateController = BehaviorSubject<State>();
/// 事件触发器, 接收()事件
Function(Event) get emitEvent => _eventController.sink.add;
/// 当前状态, 监听事件 并 响应 对应 结果 状态
Stream<State> get state => _stateController.stream;
/// 上一状态
State get lastState => _stateController.value;
/// 事件处理器
Stream<State> eventHandler(Event event, State currentState);
/// 初始化状态
final State initialState;
// 构造器, 唯一 且 必要 参数: 初始化状态
BlocEventStateBase({
@required this.initialState,
}){
// 为事件控制器添加监听
// 获取当前状态
// 对于 每一个接收到的事件, 调用 eventHandler 处理 并释放最新的状态
_eventController.listen((Event event){
State currentState = lastState ?? initialState;
eventHandler(event, currentState).forEach((State newState){
_stateController.sink.add(newState);
});
});
}
@override
void dispose() {
_eventController.close();
_stateController.close();
}
}
API
AsyncWidgetBuilder
PublishSubject
BehaviorSubject
Exploring RxJava in Android — Different types of Subjects
注解
It exposes:
- a Sink (emitEvent) to push an Event;
- a Stream (state) to listen to emitted State(s).
At initialization time (see Constructor):
- an initialState needs to be provided;
- it creates a StreamSubscription to listen to incoming Events to dispatch them to the eventHandler to emit resulting state(s).
3.2. Specialized BlocEventState
代码段 3
class TemplateEventStateBloc extends BlocEventStateBase<BlocEvent, BlocState> {
TemplateEventStateBloc()
: super(
initialState: BlocState.notInitialized(),
);
@override
Stream<BlocState> eventHandler( BlocEvent event, BlocState currentState) async* {
yield BlocState.notInitialized();
}
}
API
3.3. BlocEvent and BlocState
代码
3.4. BlocEventStateBuilder Widget
代码段 4
import 'package:blocs/bloc_helpers/bloc_event_state.dart';
import 'package:flutter/material.dart';
/// This Widget is nothing else but a specialized StreamBuilder,
/// which will invoke the builder input argument
/// each time a new BlocState will be emitted.
class BlocEventStateBuilder<State> extends StatelessWidget {
const BlocEventStateBuilder({
Key key,
@required this.builder,
@required this.bloc,
}): assert(builder != null),
assert(bloc != null),
super(key: key);
final BlocEventStateBase<BlocEvent,State> bloc;
/// 参数为 对应 State 的 builder 泛型
final AsyncBlocStateBuilder<State> builder;
@override
Widget build(BuildContext context){
return StreamBuilder<State>(
stream: bloc.state,
initialData: bloc.initialState,
builder: (BuildContext context, AsyncSnapshot<State> snapshot){
return builder(context, snapshot.data);
},
);
}
}
3.5. 示例 1: 应用 初始化
3.5.1. ApplicationInitializationEvent
代码段 5
/// 应用初始化事件 继承 BlocEvent
class ApplicationInitializationEvent extends BlocEvent {
/// 唯一属性 应用初始化类型
final ApplicationInitializationEventType type;
/// 构造方法: 初始化方法 默认 类型 为 start
ApplicationInitializationEvent({
this.type: ApplicationInitializationEventType.start,
}) : assert(type != null);
}
/// 应用初始化类型 枚举
enum ApplicationInitializationEventType {
start, /// 触发 初始化 进程
stop, /// 停止 初始化
}
3.5.2. ApplicationInitializationState
代码段 6
/// 应用初始化状态
class ApplicationInitializationState extends BlocState {
/// 构造函数
ApplicationInitializationState({
@required this.isInitialized,
this.isInitializing: false,
this.progress: 0,
});
/// 是否初始化完成标志
final bool isInitialized;
/// 是否初始化过程中标志
final bool isInitializing;
/// 初始化完成率
final int progress;
/// 三个工厂命名构造函数
/// 未初始化
factory ApplicationInitializationState.notInitialized() {
return ApplicationInitializationState(
isInitialized: false,
);
}
/// 初始化进行中
factory ApplicationInitializationState.progressing(int progress) {
return ApplicationInitializationState(
isInitialized: progress == 100,
isInitializing: true,
progress: progress,
);
}
/// 初始化完成
factory ApplicationInitializationState.initialized() {
return ApplicationInitializationState(
isInitialized: true,
progress: 100,
);
}
}
API
3.5.3. ApplicationInitializationBloc
基于事件处理初始化进程
代码段 7
BlocEventStateBase
ApplicationInitializationEvent
ApplicationInitializationState
/// 应用初始化BLoC, 基于 ApplicationInitializationEvent,
/// ApplicationInitializationState 继承自 BlocEventStateBase
class ApplicationInitializationBloc
extends BlocEventStateBase<ApplicationInitializationEvent, ApplicationInitializationState> {
/// 构造函数, 初始化状态 为 应用初始化 的 未初始化状态
ApplicationInitializationBloc()
: super(
initialState: ApplicationInitializationState.notInitialized(),
);
/// 事件处理器
@override
Stream<ApplicationInitializationState> eventHandler(
ApplicationInitializationEvent event, ApplicationInitializationState currentState) async* {
/// 当前状态 未 初始化完成, 响应未初始化完成状态
if (!currentState.isInitialized){
yield ApplicationInitializationState.notInitialized();
}
/// 启动事件, 自定义响应 进行中 状态
if (event.type == ApplicationInitializationEventType.start) {
for (int progress = 0; progress < 101; progress += 10){
await Future.delayed(const Duration(milliseconds: 300));
yield ApplicationInitializationState.progressing(progress);
}
}
/// 停止事件, 响应 完成状态
if (event.type == ApplicationInitializationEventType.stop){
yield ApplicationInitializationState.initialized();
}
}
}
3.5.4. 整合
代码段 8
ApplicationInitializationBloc
BlocEventStateBuilder
ApplicationInitializationEvent
ApplicationInitializationState
/// 初始化页面
class InitializationPage extends StatefulWidget {
@override
_InitializationPageState createState() => _InitializationPageState();
}
class _InitializationPageState extends State<InitializationPage> {
/// 定义 对应 BLoC
ApplicationInitializationBloc bloc;
@override
void initState(){
super.initState();
/// 初始化bloc
bloc = ApplicationInitializationBloc();
/// 触发 应用启动事件
bloc.emitEvent(ApplicationInitializationEvent());
}
@override
void dispose(){
bloc?.dispose();
super.dispose();
}
@override
Widget build(BuildContext pageContext) {
return SafeArea(
child: Scaffold(
body: Container(
child: Center(
child: BlocEventStateBuilder<ApplicationInitializationState>(
bloc: bloc,
builder: (BuildContext context, ApplicationInitializationState state){
if (state.isInitialized){
/// 初始化完成, 跳转到其他页面
WidgetsBinding.instance.addPostFrameCallback((_){
Navigator.of(context).pushReplacementNamed('/home');
});
}
return Text('Initialization in progress... ${state.progress}%');
},
),
),
),
),
);
}
}
API
3.6. 示例 2: 认证 及 注销
展示
3.6.1. AuthenticationEvent
代码段 9
/// 认证事件抽象类, 继承自 BlocEvent
abstract class AuthenticationEvent extends BlocEvent {
/// 唯一属性, 事件名称
final String name;
AuthenticationEvent({
this.name: '',
});
}
/// 登录事件
class AuthenticationEventLogin extends AuthenticationEvent {
AuthenticationEventLogin({
String name,
}) : super(
name: name,
);
}
/// 注销事件
class AuthenticationEventLogout extends AuthenticationEvent {}
3.6.2. AuthenticationState
代码段 10
/// 认证状态 继承自 BlocState
class AuthenticationState extends BlocState {
/// 构造函数 必要参数 isAuthenticated
AuthenticationState({
@required this.isAuthenticated,
this.isAuthenticating: false,
this.hasFailed: false,
this.name: '',
});
/// 是否认证完成标志
final bool isAuthenticated;
/// 是否认证流程中标志
final bool isAuthenticating;
/// 是否认证失败标志
final bool hasFailed;
/// 用户名称
final String name;
/// 四个工厂命名构造函数
/// 未认证完成
factory AuthenticationState.notAuthenticated() {
return AuthenticationState(
isAuthenticated: false,
);
}
/// 认证完成
factory AuthenticationState.authenticated(String name) {
return AuthenticationState(
isAuthenticated: true,
name: name,
);
}
/// 认证流程中
factory AuthenticationState.authenticating() {
return AuthenticationState(
isAuthenticated: false,
isAuthenticating: true,
);
}
/// 认证失败
factory AuthenticationState.failure() {
return AuthenticationState(
isAuthenticated: false,
hasFailed: true,
);
}
}
3.5.3. AuthenticationBloc
基于事件处理认证流程
代码段 11
class AuthenticationBloc
extends BlocEventStateBase<AuthenticationEvent, AuthenticationState> {
/// 构造函数 初始化状态为 未认证完成
AuthenticationBloc()
: super(
initialState: AuthenticationState.notAuthenticated(),
);
@override
Stream<AuthenticationState> eventHandler(
AuthenticationEvent event, AuthenticationState currentState) async* {
/// 登陆事件处理
if (event is AuthenticationEventLogin) {
// 确定我们在认证流程中
yield AuthenticationState.authenticating();
// 模拟后台处理流程
await Future.delayed(const Duration(seconds: 2));
// 通知我们认证完成 或 失败
if (event.name == "failure"){
yield AuthenticationState.failure();
} else {
yield AuthenticationState.authenticated(event.name);
}
}
/// 注销事件处理
if (event is AuthenticationEventLogout){
yield AuthenticationState.notAuthenticated();
}
}
}
3.5.4. AuthenticationPage
代码段 12
AuthenticationBloc
BlocEventStateBuilder
AuthenticationState
/// 登录页面
class AuthenticationPage extends StatelessWidget {
/// 禁用"返回"按钮
Future<bool> _onWillPopScope() async {
return false;
}
@override
Widget build(BuildContext context) {
AuthenticationBloc bloc = BlocProvider.of<AuthenticationBloc>(context);
return WillPopScope(
onWillPop: _onWillPopScope,
child: SafeArea(
child: Scaffold(
appBar: AppBar(
title: Text('Authentication Page'),
leading: Container(),
),
body: Container(
child:
BlocEventStateBuilder<AuthenticationState>(
bloc: bloc,
builder: (BuildContext context, AuthenticationState state) {
/// 认证中
if (state.isAuthenticating) {
return PendingAction();
}
/// 认证完成
if (state.isAuthenticated){
return Container();
}
List<Widget> children = <Widget>[];
// 模拟认证成功
children.add(
ListTile(
title: RaisedButton(
child: Text('Log in (success)'),
onPressed: () {
/// 触发认证成功事件
bloc.emitEvent(AuthenticationEventLogin(name: 'Didier'));
},
),
),
);
// 模拟认证失败
children.add(
ListTile(
title: RaisedButton(
child: Text('Log in (failure)'),
onPressed: () {
/// 触发认证失败事件
bloc.emitEvent(AuthenticationEventLogin(name: 'failure'));
},
),
),
);
// 展示认证失败提示信息
if (state.hasFailed){
children.add(
Text('Authentication failure!'),
);
}
return Column(
children: children,
);
},
),
),
),
),
);
}
}
API
3.5.5. DecisionPage
代码段 13
/// 判定page
class DecisionPage extends StatefulWidget {
@override
DecisionPageState createState() => DecisionPageState();
}
class DecisionPageState extends State<DecisionPage> {
/// 旧的认证状态
AuthenticationState oldAuthenticationState;
@override
Widget build(BuildContext context) {
AuthenticationBloc bloc = BlocProvider.of<AuthenticationBloc>(context);
return BlocEventStateBuilder<AuthenticationState>(
bloc: bloc,
builder: (BuildContext context, AuthenticationState state) {
/// 认证状态变化
if (state != oldAuthenticationState){
oldAuthenticationState = state;
/// 认证成功, 跳转到主页
if (state.isAuthenticated){
_redirectToPage(context, HomePage());
} else if (state.isAuthenticating || state.hasFailed){
/// 认证失败或 认证中, 什么也不做
} else {
/// 未认证, 跳转到认证页
_redirectToPage(context, AuthenticationPage());
}
}
// This page does not need to display anything since it will
// always remind behind any active page (and thus 'hidden').
return Container();
}
);
}
/// 重定向路由
void _redirectToPage(BuildContext context, Widget page){
WidgetsBinding.instance.addPostFrameCallback((_){
MaterialPageRoute newRoute = MaterialPageRoute(
builder: (BuildContext context) => page
);
Navigator.of(context).pushAndRemoveUntil(newRoute, ModalRoute.withName('/decision'));
});
}
}
3.5.6. 注销
代码段 14
class LogOutButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
AuthenticationBloc bloc = BlocProvider.of<AuthenticationBloc>(context);
return IconButton(
icon: Icon(Icons.exit_to_app),
onPressed: () {
bloc.emitEvent(AuthenticationEventLogout());
},
);
}
}
3.5.7. AuthenticationBloc
代码段 15
void main() => runApp(Application());
class Application extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocProvider<AuthenticationBloc>(
blocBuilder: () => AuthenticationBloc(),
child: MaterialApp(
title: 'BLoC Samples',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: DecisionPage(),
),
);
}
}
4. 表单验证
4.1. The RegistrationFormBloc
代码段 16
/// 注册表单bloc, 使用 邮箱密码验证代码, 并继承自 BlocBase
class RegistrationFormBloc extends Object with EmailValidator, PasswordValidator implements BlocBase {
/// 为三个输入项分别定义控制器
final BehaviorSubject<String> _emailController = BehaviorSubject<String>();
final BehaviorSubject<String> _passwordController = BehaviorSubject<String>();
final BehaviorSubject<String> _passwordConfirmController = BehaviorSubject<String>();
/// 接收输入事件
Function(String) get onEmailChanged => _emailController.sink.add;
Function(String) get onPasswordChanged => _passwordController.sink.add;
Function(String) get onRetypePasswordChanged => _passwordConfirmController.sink.add;
/// 定义输入项的验证结果
Stream<String> get email => _emailController.stream.transform(validateEmail);
Stream<String> get password => _passwordController.stream.transform(validatePassword);
Stream<String> get confirmPassword => _passwordConfirmController.stream.transform(validatePassword)
.doOnData((String c){
/// 确保两次密码输入一致, 不一致则返回错误
if (0 != _passwordController.value.compareTo(c)){
_passwordConfirmController.addError("No Match");
}
});
// 注册按钮可用标志, 所有输入项都验证通过时, 按钮方可使用
Stream<bool> get registerValid => CombineLatestStream.combine3(
email,
password,
confirmPassword,
(e, p, c) => true
);
@override
void dispose() {
_emailController?.close();
_passwordController?.close();
_passwordConfirmController?.close();
}
}
API
4.1.1. Validator Mixins
代码段 17
const String _kEmailRule = r"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$";
class EmailValidator {
final StreamTransformer<String,String> validateEmail =
StreamTransformer<String,String>.fromHandlers(handleData: (email, sink){
final RegExp emailExp = new RegExp(_kEmailRule);
if (!emailExp.hasMatch(email) || email.isEmpty){
sink.addError('Entre a valid email');
} else {
sink.add(email);
}
});
}
4.2. The RegistrationForm
代码段 18
/// 注册表单
class RegistrationForm extends StatefulWidget {
@override
_RegistrationFormState createState() => _RegistrationFormState();
}
class _RegistrationFormState extends State<RegistrationForm> {
/// 定义三个 输入项保持器
TextEditingController _emailController;
TextEditingController _passwordController;
TextEditingController _retypeController;
/// 注册表单 及 注册 BLoC
RegistrationFormBloc _registrationFormBloc;
RegistrationBloc _registrationBloc;
@override
void initState() {
super.initState();
/// 初始化所有属性
_emailController = TextEditingController();
_passwordController = TextEditingController();
_retypeController = TextEditingController();
_registrationFormBloc = RegistrationFormBloc();
_registrationBloc = RegistrationBloc();
}
@override
void dispose() {
_registrationBloc?.dispose();
_registrationFormBloc?.dispose();
_emailController?.dispose();
_passwordController?.dispose();
_retypeController?.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return BlocEventStateBuilder<RegistrationState>(
bloc: _registrationBloc,
builder: (BuildContext context, RegistrationState state) {
if (state.isBusy) {
return PendingAction();
} else if (state.isSuccess) {
return _buildSuccess();
} else if (state.isFailure) {
return _buildFailure();
}
return _buildForm();
});
}
Widget _buildSuccess() {
return Center(
child: Text('Success'),
);
}
Widget _buildFailure() {
return Center(
child: Text('Failure'),
);
}
Widget _buildForm() {
return Form(
child: Column(
children: <Widget>[
StreamBuilder<String>(
stream: _registrationFormBloc.email,
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
return TextField(
decoration: InputDecoration(
labelText: 'email',
errorText: snapshot.error,
),
controller: _emailController,
onChanged: _registrationFormBloc.onEmailChanged,
keyboardType: TextInputType.emailAddress,
);
}),
StreamBuilder<String>(
stream: _registrationFormBloc.password,
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
return TextField(
decoration: InputDecoration(
labelText: 'password',
errorText: snapshot.error,
),
controller: _passwordController,
obscureText: false,
onChanged: _registrationFormBloc.onPasswordChanged,
);
}),
StreamBuilder<String>(
stream: _registrationFormBloc.confirmPassword,
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
return TextField(
decoration: InputDecoration(
labelText: 'retype password',
errorText: snapshot.error,
),
controller: _retypeController,
obscureText: false,
onChanged: _registrationFormBloc.onRetypePasswordChanged,
);
}),
StreamBuilder<bool>(
stream: _registrationFormBloc.registerValid,
builder: (BuildContext context, AsyncSnapshot<bool> snapshot) {
return RaisedButton(
child: Text('Register'),
/// 所有输入项验证成功, 注册按钮方可点击并触发注册事件
onPressed: (snapshot.hasData && snapshot.data == true)
? () {
_registrationBloc.emitEvent(RegistrationEvent(
event: RegistrationEventType.working,
email: _emailController.text,
password: _passwordController.text));
}
: null,
);
}),
],
),
);
}
}
5. Part Of
5.1. ShoppingBloc
代码段 19
/// 购物Bloc, 继承自 BlocBase
class ShoppingBloc implements BlocBase {
/// 购物车商品集合
Set<ShoppingItem> _shoppingBasket = Set<ShoppingItem>();
// 所有商品
BehaviorSubject<List<ShoppingItem>> _itemsController = BehaviorSubject<List<ShoppingItem>>();
Stream<List<ShoppingItem>> get items => _itemsController;
/// 购物车 商品数量, 初始值为 0
BehaviorSubject<int> _shoppingBasketSizeController = BehaviorSubject<int>.seeded(0);
Stream<int> get shoppingBasketSize => _shoppingBasketSizeController;
/// 购物车 总价, 初始值为 0.0
BehaviorSubject<double> _shoppingBasketPriceController = BehaviorSubject<double>.seeded(0.0);
Stream<double> get shoppingBasketTotalPrice => _shoppingBasketPriceController;
/// 购物车 商品列表, 初始化 为空表
BehaviorSubject<List<ShoppingItem>> _shoppingBasketController = BehaviorSubject<List<ShoppingItem>>.seeded(<ShoppingItem>[]);
Stream<List<ShoppingItem>> get shoppingBasket => _shoppingBasketController;
@override
void dispose() {
_itemsController?.close();
_shoppingBasketSizeController?.close();
_shoppingBasketController?.close();
_shoppingBasketPriceController?.close();
}
// 构造器, 初始化 所有商品
ShoppingBloc() {
_loadShoppingItems();
}
/// 购物车新增删除 商品
void addToShoppingBasket(ShoppingItem item){
_shoppingBasket.add(item);
_postActionOnBasket();
}
void removeFromShoppingBasket(ShoppingItem item){
_shoppingBasket.remove(item);
_postActionOnBasket();
}
/// 购物车 对应新增删除 操作
/// 更新商品列表, 商品数量, 商品总价
void _postActionOnBasket(){
_shoppingBasketController.sink.add(_shoppingBasket.toList());
_shoppingBasketSizeController.sink.add(_shoppingBasket.length);
_computeShoppingBasketTotalPrice();
}
/// 计算商品总价
void _computeShoppingBasketTotalPrice(){
double total = 0.0;
_shoppingBasket.forEach((ShoppingItem item){
total += item.price;
});
_shoppingBasketPriceController.sink.add(total);
}
/// 加载 商品
void _loadShoppingItems() {
_itemsController.sink.add(List<ShoppingItem>.generate(50, (int index) {
return ShoppingItem(
id: index,
title: "Item $index",
price: ((Random().nextDouble() * 40.0 + 10.0) * 100.0).roundToDouble() /
100.0,
color: Color((Random().nextDouble() * 0xFFFFFF).toInt() << 0)
.withOpacity(1.0),
);
}));
}
}
5.2. ShoppingPage
代码段 20
/// 购物页面
class ShoppingPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
ShoppingBloc bloc = BlocProvider.of<ShoppingBloc>(context);
return SafeArea(
child: Scaffold(
appBar: AppBar(
title: Text('Shopping Page'),
actions: <Widget>[
ShoppingBasket(),
],
),
body: Container(
child: StreamBuilder<List<ShoppingItem>>(
stream: bloc.items,
builder: (BuildContext context,
AsyncSnapshot<List<ShoppingItem>> snapshot) {
if (!snapshot.hasData) {
return Container();
}
return GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
childAspectRatio: 1.0,
),
itemCount: snapshot.data.length,
itemBuilder: (BuildContext context, int index) {
return ShoppingItemWidget(
shoppingItem: snapshot.data[index],
);
},
);
},
),
),
));
}
}
5.3. ShoppingItemWidget and ShoppingItemBloc
The Part Of pattern relies on the combination of these 2 elements:
- the ShoppingItemWidget is responsible for:
- displaying the item and
- the button to add or remove the item to/from the shopping basket
- the ShoppingItemBloc is responsible for telling the ShoppingItemWidget whether the latter is part or not of the shopping basket.
5.3.1. ShoppingItemBloc
代码段 20
class ShoppingItemBloc implements BlocBase {
/// 流控制器 判断 当前商品是否在购物车中
BehaviorSubject<bool> _isInShoppingBasketController = BehaviorSubject<bool>();
Stream<bool> get isInShoppingBasket => _isInShoppingBasketController;
/// 流控制器 接收所有购物车商品
PublishSubject<List<ShoppingItem>> _shoppingBasketController = PublishSubject<List<ShoppingItem>>();
Function(List<ShoppingItem>) get shoppingBasket => _shoppingBasketController.sink.add;
/// 构造函数, 包含一个 商品 属性, 作为 身份标识
ShoppingItemBloc(ShoppingItem shoppingItem){
/// 每次购物车发生变化,
_shoppingBasketController.stream
/// 判断对应商品是否在购物车中
.map((list) => list.any((ShoppingItem item) => item.id == shoppingItem.id))
/// 如果在, 通知对应 商品组件
.listen((isInShoppingBasket)
=> _isInShoppingBasketController.add(isInShoppingBasket));
}
@override
void dispose() {
_isInShoppingBasketController?.close();
_shoppingBasketController?.close();
}
}
5.3.2. ShoppingItemWidget
This Widget is responsible for:
- creating an instance of the ShoppingItemBloc and passing its own identity to the BLoC
- listening to any variation of the ShoppingBasket content and transferring it to the BLoC
- listening to the ShoppingItemBloc to know whether it is part of the basket
- displaying the corresponding button (add/remove) depending on its presence in the basket
- responding to user action of the button
- when the user clicks the add button, to add itself to the basket
- when the user clicks the remove button, to remove itself from the basket.
代码段 21
/// 商品组件
class ShoppingItemWidget extends StatefulWidget {
/// 构造函数, 必要参数 shoppingItem
ShoppingItemWidget({
Key key,
@required this.shoppingItem,
}) : super(key: key);
final ShoppingItem shoppingItem;
@override
_ShoppingItemWidgetState createState() => _ShoppingItemWidgetState();
}
class _ShoppingItemWidgetState extends State<ShoppingItemWidget> {
/// 分别定义 流订阅, 商品BLoC, 购物BLoC
StreamSubscription _subscription;
ShoppingItemBloc _bloc;
ShoppingBloc _shoppingBloc;
@override
void didChangeDependencies() {
super.didChangeDependencies();
// As the context should not be used in the "initState()" method,
// prefer using the "didChangeDependencies()" when you need
// to refer to the context at initialization time
_initBloc();
}
@override
void didUpdateWidget(ShoppingItemWidget oldWidget) {
super.didUpdateWidget(oldWidget);
// as Flutter might decide to reorganize the Widgets tree
// it is preferable to recreate the links
_disposeBloc();
_initBloc();
}
@override
void dispose() {
_disposeBloc();
super.dispose();
}
/// 初始化相关BLoC
void _initBloc() {
/// 商品 BLoC
_bloc = ShoppingItemBloc(widget.shoppingItem);
/// 购物 BLoC
_shoppingBloc = BlocProvider.of<ShoppingBloc>(context);
//将 购物BLoC 中的购物车商品数据变化 转移到 商品 BLoC 中
_subscription = _shoppingBloc.shoppingBasket.listen(_bloc.shoppingBasket);
}
void _disposeBloc() {
_subscription?.cancel();
_bloc?.dispose();
}
/// 通过判断此商品是否在购物车中, 显示 "ADD"/"REBOVE" 按钮
Widget _buildButton() {
return StreamBuilder<bool>(
stream: _bloc.isInShoppingBasket,
initialData: false,
builder: (BuildContext context, AsyncSnapshot<bool> snapshot) {
return snapshot.data
? _buildRemoveFromShoppingBasket()
: _buildAddToShoppingBasket();
},
);
}
Widget _buildAddToShoppingBasket(){
return RaisedButton(
child: Text('Add...'),
onPressed: (){
_shoppingBloc.addToShoppingBasket(widget.shoppingItem);
},
);
}
Widget _buildRemoveFromShoppingBasket(){
return RaisedButton(
child: Text('Remove...'),
onPressed: (){
_shoppingBloc.removeFromShoppingBasket(widget.shoppingItem);
},
);
}
@override
Widget build(BuildContext context) {
return Card(
child: GridTile(
header: Center(
child: Text(widget.shoppingItem.title),
),
footer: Center(
child: Text('${widget.shoppingItem.price} €'),
),
child: Container(
color: widget.shoppingItem.color,
child: Center(
child: _buildButton(),
),
),
),
);
}
}