BLoC 模式¶
简而言之,定义 event 和 state,bloc 负责响应来自视图层的 event,维护自身的的 state;另外有 repository 提供数据源。
传统的 MVC 模式中,Model 和 View 是分开的,Controller 将二者联系起来;BLoC 是 Google 提出的一种新的响应式风格,将 app 内所有内容都表现为事件流,部分 widget 发送事件,另一部分 widget 接收事件。
BLoC 的一大优点是文档丰富,可以在 examples 目录下找到很多使用示例。
一个 BLoC 模式的的 widget 由
cubit/bloc:model:用于数据存储view:用于数据展示
三部分组成。
cubit 是 bloc 的子集,包含了基本的状态管理功能。简单地说,就是 Cubit 与 State 之间通过 emit 交互——State 定义状态,而 Cubit 相应状态。
,Bloc 框架对开发页面,做了很清晰划分,框架强行定了两种开发模式
- Bloc 模式:该模式划分四层结构
- bloc:逻辑层
- state:数据层
- event:所有的交互事件
- view:页面
- Cubit 模式:该模式划分了三层结构
- cubit:逻辑层
- state:数据层
- view:页面
如果用 GoF 的传统设计模式来看,bloc 模式更像状态模式和策略模式的合体。cubit 模式中,省去了 state,也就是省掉了状态转移。
Bloc 模式的核心逻辑是:view 层向 bloc 发送 event,bloc响应 event,更新 state;view 响应 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 的公有方法不会受到太大影响。
简而言之,定义 event 和 state,bloc 负责响应来自视图层的 event,维护自身的的 state;另外有 repository 提供数据源。
传统的 MVC 模式中,Model 和 View 是分开的,Controller 将二者联系起来;BLoC 是 Google 提出的一种新的响应式风格,将 app 内所有内容都表现为事件流,部分 widget 发送事件,另一部分 widget 接收事件。
BLoC 的一大优点是文档丰富,可以在 examples 目录下找到很多使用示例。
一个 BLoC 模式的的 widget 由
cubit/bloc:model:用于数据存储view:用于数据展示
三部分组成。
cubit 是 bloc 的子集,包含了基本的状态管理功能。简单地说,就是 Cubit 与 State 之间通过 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 的公有方法不会受到太大影响。
{loading=lazy}