跳转至

BLoC 模式

简而言之,定义 eventstatebloc 负责响应来自视图层的 event,维护自身的的 state;另外有 repository 提供数据源。

传统的 MVC 模式中,Model 和 View 是分开的,Controller 将二者联系起来;BLoC 是 Google 提出的一种新的响应式风格,将 app 内所有内容都表现为事件流,部分 widget 发送事件,另一部分 widget 接收事件。

BLoC 的一大优点是文档丰富,可以在 examples 目录下找到很多使用示例。

一个 BLoC 模式的的 widget 由

  • cubit / bloc
  • model:用于数据存储
  • view:用于数据展示

三部分组成。

cubitbloc 的子集,包含了基本的状态管理功能。简单地说,就是 CubitState 之间通过 emit 交互——State 定义状态,而 Cubit 相应状态。

,Bloc 框架对开发页面,做了很清晰划分,框架强行定了两种开发模式

  • Bloc 模式:该模式划分四层结构
  • bloc:逻辑层
  • state:数据层
  • event:所有的交互事件
  • view:页面
  • Cubit 模式:该模式划分了三层结构
  • cubit:逻辑层
  • state:数据层
  • view:页面

如果用 GoF 的传统设计模式来看,bloc 模式更像状态模式策略模式的合体。cubit 模式中,省去了 state,也就是省掉了状态转移。

Bloc 模式的核心逻辑是:view 层向 bloc 发送 eventbloc响应 event,更新 stateview 响应 state 的更新,重绘界面。

上文没有提到的是 repository。在更模块化的设置下,bloc 不应该包含从数据源(网络请求、本地数据库)获取数据的逻辑,而是将这些逻辑交给 repository。这样统一了数据获取接口,也便于缓存的实现。

另外,bloc 之间应该是独立的,永远不要假设一个 bloc 依赖另一个 bloc,比如 IMBloc 不能依赖 UserBloc,这种关联应该提升到视图层或者下沉到 Repository 层。

该模式几乎是完美符合“高内聚、低耦合”的要求, 非常方便复用和测试。但是也有人批评 bloc 的规定过于死板,更倾向于简单易用的 provider 或者 getx。但我认为,bloc 这套逻辑非常适合大项目。

BLoC 源码的解读参照:https://juejin.cn/post/6973900070358319135


示例来自 bloc 官网,以最简单的注册逻辑为例,页面入口:

import 'package:authentication_repository/authentication_repository.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_login/login/login.dart';

class LoginPage extends StatelessWidget {
  const LoginPage({super.key});

  static Route<void> route() {
    return MaterialPageRoute<void>(builder: (_) => const LoginPage());
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Login')),
      body: Padding(
        padding: const EdgeInsets.all(12),
        child: BlocProvider(
          create: (context) {
            return LoginBloc(
              authenticationRepository:
                  RepositoryProvider.of<AuthenticationRepository>(context),
            );
          },
          child: const LoginForm(),
        ),
      ),
    );
  }
}
login/bloc/login_bloc.dart
import 'package:authentication_repository/authentication_repository.dart';
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter_login/login/login.dart';
import 'package:formz/formz.dart';

part 'login_event.dart';
part 'login_state.dart';

class LoginBloc extends Bloc<LoginEvent, LoginState> {
  LoginBloc({
    required AuthenticationRepository authenticationRepository,
  })  : _authenticationRepository = authenticationRepository,
        super(const LoginState()) {
    on<LoginUsernameChanged>(_onUsernameChanged);
    on<LoginPasswordChanged>(_onPasswordChanged);
    on<LoginSubmitted>(_onSubmitted);
  }

  final AuthenticationRepository _authenticationRepository;

  void _onUsernameChanged(
    LoginUsernameChanged event,
    Emitter<LoginState> emit,
  ) {
    final username = Username.dirty(event.username);
    emit(
      state.copyWith(
        username: username,
        isValid: Formz.validate([state.password, username]),
      ),
    );
  }

  void _onPasswordChanged(
    LoginPasswordChanged event,
    Emitter<LoginState> emit,
  ) {
    final password = Password.dirty(event.password);
    emit(
      state.copyWith(
        password: password,
        isValid: Formz.validate([password, state.username]),
      ),
    );
  }

  Future<void> _onSubmitted(
    LoginSubmitted event,
    Emitter<LoginState> emit,
  ) async {
    if (state.isValid) {
      emit(state.copyWith(status: FormzSubmissionStatus.inProgress));
      try {
        await _authenticationRepository.logIn(
          username: state.username.value,
          password: state.password.value,
        );
        emit(state.copyWith(status: FormzSubmissionStatus.success));
      } catch (_) {
        emit(state.copyWith(status: FormzSubmissionStatus.failure));
      }
    }
  }
}
login/bloc/login_event.dart
part of 'login_bloc.dart';

sealed class LoginEvent extends Equatable {
  const LoginEvent();

  @override
  List<Object> get props => [];
}

final class LoginUsernameChanged extends LoginEvent {
  const LoginUsernameChanged(this.username);

  final String username;

  @override
  List<Object> get props => [username];
}

final class LoginPasswordChanged extends LoginEvent {
  const LoginPasswordChanged(this.password);

  final String password;

  @override
  List<Object> get props => [password];
}

final class LoginSubmitted extends LoginEvent {
  const LoginSubmitted();
}
login/bloc/login_state.dart
part of 'login_bloc.dart';

final class LoginState extends Equatable {
  const LoginState({
    this.status = FormzSubmissionStatus.initial,
    this.username = const Username.pure(),
    this.password = const Password.pure(),
    this.isValid = false,
  });

  final FormzSubmissionStatus status;
  final Username username;
  final Password password;
  final bool isValid;

  LoginState copyWith({
    FormzSubmissionStatus? status,
    Username? username,
    Password? password,
    bool? isValid,
  }) {
    return LoginState(
      status: status ?? this.status,
      username: username ?? this.username,
      password: password ?? this.password,
      isValid: isValid ?? this.isValid,
    );
  }

  @override
  List<Object> get props => [status, username, password];
}

关于 Provider 将被 Deprecate 的问题,Provider 作者也作出了回应了;provider 还会正常工作,但基本不会有功能改进;但也不是必须要更新。

另外,flutter_bloc 虽然依赖于 provider,但一旦 provider 被废弃,也有理由相信 bloc 的公有方法不会受到太大影响。

简而言之,定义 eventstatebloc 负责响应来自视图层的 event,维护自身的的 state;另外有 repository 提供数据源。

传统的 MVC 模式中,Model 和 View 是分开的,Controller 将二者联系起来;BLoC 是 Google 提出的一种新的响应式风格,将 app 内所有内容都表现为事件流,部分 widget 发送事件,另一部分 widget 接收事件。

BLoC 的一大优点是文档丰富,可以在 examples 目录下找到很多使用示例。

一个 BLoC 模式的的 widget 由

  • cubit / bloc
  • model:用于数据存储
  • view:用于数据展示

三部分组成。

cubitbloc 的子集,包含了基本的状态管理功能。简单地说,就是 CubitState 之间通过 emit 交互——State 定义状态,而 Cubit 相应状态。

,Bloc 框架对开发页面,做了很清晰划分,框架强行定了两种开发模式

  • Bloc 模式:该模式划分四层结构
  • bloc:逻辑层
  • state:数据层
  • event:所有的交互事件
  • view:页面
  • Cubit 模式:该模式划分了三层结构
  • cubit:逻辑层
  • state:数据层
  • view:页面

https://juejin.cn/post/6973900070358319135

以最简单的注册逻辑为例,页面入口:

import 'package:authentication_repository/authentication_repository.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_login/login/login.dart';

class LoginPage extends StatelessWidget {
  const LoginPage({super.key});

  static Route<void> route() {
    return MaterialPageRoute<void>(builder: (_) => const LoginPage());
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Login')),
      body: Padding(
        padding: const EdgeInsets.all(12),
        child: BlocProvider(
          create: (context) {
            return LoginBloc(
              authenticationRepository:
                  RepositoryProvider.of<AuthenticationRepository>(context),
            );
          },
          child: const LoginForm(),
        ),
      ),
    );
  }
}
login/bloc/login_bloc.dart
import 'package:authentication_repository/authentication_repository.dart';
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter_login/login/login.dart';
import 'package:formz/formz.dart';

part 'login_event.dart';
part 'login_state.dart';

class LoginBloc extends Bloc<LoginEvent, LoginState> {
  LoginBloc({
    required AuthenticationRepository authenticationRepository,
  })  : _authenticationRepository = authenticationRepository,
        super(const LoginState()) {
    on<LoginUsernameChanged>(_onUsernameChanged);
    on<LoginPasswordChanged>(_onPasswordChanged);
    on<LoginSubmitted>(_onSubmitted);
  }

  final AuthenticationRepository _authenticationRepository;

  void _onUsernameChanged(
    LoginUsernameChanged event,
    Emitter<LoginState> emit,
  ) {
    final username = Username.dirty(event.username);
    emit(
      state.copyWith(
        username: username,
        isValid: Formz.validate([state.password, username]),
      ),
    );
  }

  void _onPasswordChanged(
    LoginPasswordChanged event,
    Emitter<LoginState> emit,
  ) {
    final password = Password.dirty(event.password);
    emit(
      state.copyWith(
        password: password,
        isValid: Formz.validate([password, state.username]),
      ),
    );
  }

  Future<void> _onSubmitted(
    LoginSubmitted event,
    Emitter<LoginState> emit,
  ) async {
    if (state.isValid) {
      emit(state.copyWith(status: FormzSubmissionStatus.inProgress));
      try {
        await _authenticationRepository.logIn(
          username: state.username.value,
          password: state.password.value,
        );
        emit(state.copyWith(status: FormzSubmissionStatus.success));
      } catch (_) {
        emit(state.copyWith(status: FormzSubmissionStatus.failure));
      }
    }
  }
}
login/bloc/login_event.dart
part of 'login_bloc.dart';

sealed class LoginEvent extends Equatable {
  const LoginEvent();

  @override
  List<Object> get props => [];
}

final class LoginUsernameChanged extends LoginEvent {
  const LoginUsernameChanged(this.username);

  final String username;

  @override
  List<Object> get props => [username];
}

final class LoginPasswordChanged extends LoginEvent {
  const LoginPasswordChanged(this.password);

  final String password;

  @override
  List<Object> get props => [password];
}

final class LoginSubmitted extends LoginEvent {
  const LoginSubmitted();
}
login/bloc/login_state.dart
part of 'login_bloc.dart';

final class LoginState extends Equatable {
  const LoginState({
    this.status = FormzSubmissionStatus.initial,
    this.username = const Username.pure(),
    this.password = const Password.pure(),
    this.isValid = false,
  });

  final FormzSubmissionStatus status;
  final Username username;
  final Password password;
  final bool isValid;

  LoginState copyWith({
    FormzSubmissionStatus? status,
    Username? username,
    Password? password,
    bool? isValid,
  }) {
    return LoginState(
      status: status ?? this.status,
      username: username ?? this.username,
      password: password ?? this.password,
      isValid: isValid ?? this.isValid,
    );
  }

  @override
  List<Object> get props => [status, username, password];
}

关于 Provider 将被 Deprecate 的问题,Provider 作者也作出了回应了;provider 还会正常工作,但基本不会有功能改进;但也不是必须要更新。

另外,flutter_bloc 虽然依赖于 provider,但一旦 provider 被废弃,也有理由相信 bloc 的公有方法不会受到太大影响。

img


最后更新: 2023-06-07