Flutter BLoC 实践

1,421 阅读6分钟

转自连接: 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

getElementForInheritedWidgetOfExactType

updateShouldNotify

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

BlocBase

// 分别定义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

generators

3.3. BlocEvent and BlocState

代码

BlocEvent and BlocState

3.4. BlocEventStateBuilder Widget

代码段 4

AsyncBlocStateBuilder


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

dart constructors

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

addPostFrameCallback
pushReplacementNamed

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

/// 认证状态 继承自 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

BlocEventStateBase AuthenticationEvent AuthenticationState


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

WillPopScope
onWillPop

3.5.5. DecisionPage
代码段 13

AuthenticationState AuthenticationBloc

/// 判定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'));
    });
  }
}

addPostFrameCallback
pushAndRemoveUntil

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

AuthenticationBloc

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

CombineLatestStream

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

BlocEventStateBuilder

/// 注册表单
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(),
          ),
        ),
      ),
    );
  }
}
API

didChangeDependencies
didUpdateWidget

5.4. How does all this work?

展示

流程展示