当前位置: 首页 > news >正文

第13讲:Bloc/Riverpod进阶 - 构建可预测、易于测试的业务逻辑

导言:

在上一讲中,我们学习了 Provider,它是一个非常优秀且易于上手的状态管理方案。然而,随着业务逻辑变得复杂,你可能会发现一些问题:业务逻辑分散在各个地方,难以进行单元测试,状态变更的流程不够清晰。

本讲将带你走向更“声明式”、更“可预测”的状态管理。我们将探索两种现代且强大的模式:Bloc 和 Riverpod。它们能强制地将你的业务逻辑UI界面分离,让代码更健壮、更易于维护和测试。


一、 为什么需要进阶状态管理?

我们先回顾一下简单状态管理可能遇到的挑战:

  1. 业务逻辑与UI耦合:在 ChangeNotifier 的方法里直接调用网络请求,UI层既负责显示又负责处理逻辑。

  2. 可测试性差:因为逻辑和UI耦合,测试业务逻辑需要构建Widget,非常笨重。

  3. 状态变更难以追踪:当应用复杂时,一个状态的变化可能由多个事件触发,很难追踪是哪个事件导致了状态的改变。

  4. “面条式”代码:所有逻辑都写在同一个类里,随着功能增加,类会变得臃肿不堪。

Bloc 和 Riverpod 的核心思想就是:让状态的变化变得像“状态机”一样清晰可预测。

UI层 => 触发事件(Event) => 业务逻辑处理 => 产生新状态(State) => UI层根据新状态刷新


二、 Bloc 模式:严格的单向数据流

Bloc 模式将应用分为三个核心部分:

  • Events(事件):UI层触发的动作,如 LoginButtonPressed

  • Bloc(业务逻辑组件):接收Events,处理业务逻辑,并输出States。

  • States(状态):应用在某一时刻的表现状态,如 LoginInitialLoginLoadingLoginSuccessLoginFailure

实战:用 Bloc 实现登录流程

我们将使用最流行的 flutter_bloc 库。

1. 定义状态(State)

首先,我们定义登录过程中所有可能的状态。

dart

复制

下载

// login_state.dart
part of 'login_bloc.dart';// 使用 sealed class (推荐) 或 abstract class 来定义所有可能的状态
// 这确保了状态类型的完备性,非常强大!
sealed class LoginState extends Equatable {const LoginState();@overrideList<Object> get props => [];
}// 初始状态
class LoginInitial extends LoginState {}// 正在登录中
class LoginLoading extends LoginState {}// 登录成功
class LoginSuccess extends LoginState {final String userEmail;const LoginSuccess(this.userEmail);
}// 登录失败
class LoginFailure extends LoginState {final String errorMessage;const LoginFailure(this.errorMessage);
}

2. 定义事件(Event)

然后,定义能触发状态变化的事件。

dart

复制

下载

// login_event.dart
part of 'login_bloc.dart';// 同样使用 sealed class 或 abstract class
sealed class LoginEvent extends Equatable {const LoginEvent();@overrideList<Object> get props => [];
}// 当用户点击登录按钮时触发的事件
class LoginButtonPressed extends LoginEvent {final String email;final String password;const LoginButtonPressed({required this.email,required this.password,});@overrideList<Object> get props => [email, password];
}

3. 创建 Bloc 类

这是核心,它包含了处理事件的业务逻辑。

dart

复制

下载

// login_bloc.dart
import 'package:bloc/bloc.dart';class LoginBloc extends Bloc<LoginEvent, LoginState> {// 可以在这里注入你的认证Repositoryfinal AuthRepository authRepository;LoginBloc({required this.authRepository}) : super(LoginInitial()) {// 注册事件处理器:当收到 `LoginButtonPressed` 事件时,执行 `_onLoginButtonPressed` 方法。on<LoginButtonPressed>(_onLoginButtonPressed);}Future<void> _onLoginButtonPressed(LoginButtonPressed event,Emitter<LoginState> emit,) async {// 1. 发出“加载中”状态emit(LoginLoading());try {// 2. 调用认证接口(业务逻辑)final user = await authRepository.authenticate(email: event.email,password: event.password,);// 3. 如果成功,发出“成功”状态emit(LoginSuccess(user.email));} catch (error) {// 4. 如果失败,发出“失败”状态emit(LoginFailure(error.toString()));}}
}

4. 在UI层中使用

dart

复制

下载

// login_screen.dart
import 'package:flutter_bloc/flutter_bloc.dart';class LoginScreen extends StatelessWidget {@overrideWidget build(BuildContext context) {return Scaffold(body: BlocProvider(// 提供LoginBloc给子树create: (context) => LoginBloc(authRepository: AuthRepository()),child: LoginForm(),),);}
}class LoginForm extends StatelessWidget {final _emailController = TextEditingController();final _passwordController = TextEditingController();@overrideWidget build(BuildContext context) {// 使用 BlocBuilder 来响应状态变化并重建UIreturn BlocBuilder<LoginBloc, LoginState>(builder: (context, state) {// 根据不同的状态,显示不同的UIif (state is LoginLoading) {return const Center(child: CircularProgressIndicator());}return Padding(padding: EdgeInsets.all(16.0),child: Column(children: [TextField(controller: _emailController, decoration: InputDecoration(labelText: 'Email')),TextField(controller: _passwordController, obscureText: true, decoration: InputDecoration(labelText: 'Password')),SizedBox(height: 20),ElevatedButton(onPressed: () {if (state is! LoginLoading) { // 防止重复提交// 触发事件!这是UI层唯一与Bloc交互的方式context.read<LoginBloc>().add(LoginButtonPressed(email: _emailController.text,password: _passwordController.text,));}},child: Text('Login'),),// 处理错误状态if (state is LoginFailure)Text('Error: ${state.errorMessage}', style: TextStyle(color: Colors.red)),// 处理成功状态if (state is LoginSuccess)Text('Welcome, ${state.userEmail}!', style: TextStyle(color: Colors.green)),],),);},);}
}

Bloc 优势总结:

  • 清晰可预测:状态变化路径一目了然。

  • 易于测试:你可以单独测试Bloc,只需输入Event,验证输出的State。

  • 强大的DevTools:有专门的Bloc DevTools用于调试和追踪状态变化。


三、 Riverpod:下一代的状态管理与依赖注入

Riverpod 被设计为 Provider 的改进版,解决了 Provider 的诸多痛点(如对BuildContext的依赖、编译时安全等)。它同时也是一个强大的依赖注入框架。

实战:用 Riverpod 实现相同的登录流程

我们将使用 flutter_riverpod 库。

1. 创建状态Notifier

Riverpod 推荐使用 AsyncNotifier 来处理异步操作。

dart

复制

下载

// login_notifier.dart
import 'package:flutter_riverpod/flutter_riverpod.dart';// 状态类
class LoginState {final bool isLoading;final String? userEmail;final String? errorMessage;const LoginState({this.isLoading = false,this.userEmail,this.errorMessage,});
}// Notifier 类
class LoginNotifier extends AsyncNotifier<LoginState> {// 初始化状态@overrideLoginState build() {return const LoginState();}// 提供一个获取 AuthRepository 的方法(依赖注入)AuthRepository get _authRepository => ref.read(authRepositoryProvider);// 登录方法Future<void> login(String email, String password) async {// 更新状态为加载中state = const AsyncValue.data(LoginState(isLoading: true));// 使用 AsyncValue.guard 优雅地处理异步操作和错误state = await AsyncValue.guard(() async {final user = await _authRepository.authenticate(email: email, password: password);return LoginState(userEmail: user.email); // 成功,返回新状态});// 如果出错,state 会自动包含错误信息}
}// 提供 Notifier 的全局 Provider
final loginNotifierProvider = AsyncNotifierProvider<LoginNotifier, LoginState>(() {return LoginNotifier();
});

2. 在UI层中使用

dart

复制

下载

// login_screen.dart
import 'package:flutter_riverpod/flutter_riverpod.dart';class LoginScreen extends ConsumerWidget { // 使用 ConsumerWidget 替代 StatelessWidget@overrideWidget build(BuildContext context, WidgetRef ref) { // 多了一个 WidgetRef ref 参数// 监听 loginNotifierProvider 的状态final loginState = ref.watch(loginNotifierProvider);return Scaffold(body: Padding(padding: EdgeInsets.all(16.0),child: Column(children: [TextField(/* ... */),TextField(/* ... */),SizedBox(height: 20),ElevatedButton(onPressed: () {// 调用 Notifier 中的方法ref.read(loginNotifierProvider.notifier).login('user@example.com', 'password');},child: Text('Login'),),// 根据状态显示UIif (loginState.isLoading) const CircularProgressIndicator(),if (loginState.hasError) // 处理错误Text('Error: ${loginState.error}', style: TextStyle(color: Colors.red)),if (loginState.value?.userEmail != null) // 处理成功Text('Welcome, ${loginState.value!.userEmail}!', style: TextStyle(color: Colors.green)),],),),);}
}

Riverpod 优势总结:

  • 编译时安全:错误的Provider引用会在编译时报错,而非运行时。

  • 不依赖BuildContext:可以在任何地方(包括类内部)访问Provider。

  • 强大的依赖注入:轻松管理、覆盖和测试依赖项。

  • 出色的组合性:Provider之间可以轻松地相互引用。

  • 原生支持异步操作AsyncNotifier 和 AsyncValue 让异步状态处理变得异常简单和安全。


四、 如何选择?Bloc vs Riverpod

这是一个社区常见问题,没有绝对答案。

  • 选择 Bloc,如果你:

    • 喜欢非常严格的架构单向数据流

    • 项目非常复杂,需要清晰地追踪每一个状态变化的来源

    • 团队需要统一的、强制性的开发模式。

    • 看重强大的调试工具(Bloc DevTools)

  • 选择 Riverpod,如果你:

    • 希望一个更灵活、更现代的解决方案。

    • 看中编译时安全和卓越的开发者体验

    • 需要强大的依赖注入功能。

    • 觉得Bloc的模板代码(Boilerplate)太多,希望更简洁。

    • 它与 flutter_hooks 结合得非常好。

结论: 两者都是顶级的选择。对于大多数新项目,Riverpod 因其灵活性和开发效率正变得越来越流行。但对于超大型或需要严格规范的团队,Bloc 依然是无懈可击的选择。

http://www.dtcms.com/a/575255.html

相关文章:

  • Rust 练习册 8:链表实现与所有权管理
  • 非常好的Rust自动管理内存的例子
  • 昆明响应式网站制作网站建设插件
  • 站长素材音效下载极速网站建设定制多少钱
  • CATASTROPHIC FAILURE OF LLM UNLEARNING VIA QUANTIZATION
  • 医院门户网站模板网站设置受信任
  • 网站简历一个ip地址上可以做几个网站吗
  • Avalonia 使用ItemsControl示例以及问题记录
  • 建设部网站公示钦州公租房摇号查询无锡手机网站开发
  • 公司网站内容相近为什么要给企业建设网站?
  • wordpress 仿微博福州网站seo推广优化
  • 网站建设行业发展史网站源码下载安全吗
  • 自己做视频网站资源从哪里来沈阳市城乡建设网站
  • 注意力机制:Jointly Learning to Align and Translate中从双向RNN编码器到软对齐的完整流程
  • 关键词排名点击软件网站信息产业部icp备案中心网站
  • NLP-常见任务
  • 娄底市建设银行宣传部网站胶州网站设计公司
  • 网站开发前景好吗商丘企业网站建设公司
  • 建设银行网站wordpress绑定二级域名插件
  • 自己怎么拍做美食视频网站详情页设计模板图片
  • 【设计题】如何涉及一个高并发的计数器
  • 网站开发教程 布局长域名转换短域名
  • 通过Ollama搭建本地LLM
  • 《新概念英语青少年版》单词全整理
  • 好的建筑设计网站有没有什么好看的网址
  • 8、webgl 基本概念 + 图像变换(平移 + 旋转 + 缩放)
  • 郑州建设信息网站环球网最新国际新闻
  • 租赁公司网站源码上线吧做的网站可以备案
  • 手机网站开发解决方案如何将自己做的网页做成网站
  • 在JavaScript中,将包含HTML实体字符的字符串转换为普通字符