Flutter同页面使用两个BLoC,传递首个BLoC状态至第二个BLoC
嘿,我来帮你捋捋这个问题!你拆分两个独立Bloc的思路完全没问题——毕竟生成OTP和验证OTP是两个独立的业务逻辑,职责分离很合理。至于你遇到的setState报错,核心原因是BlocBuilder的builder是纯UI构建函数,不应该在这里做状态修改的副作用操作,而且你的setState写法本身也不对(应该是setState(() { _uniqueIdentifier = state.uniqueIdentifier; }))。
下面给你几个符合Bloc模式的最优解决方案,按场景复杂度排序:
方案1:页面层级维护共享状态(最推荐,适合单页场景)
既然两个Bloc都在同一个OtpPage下,我们可以在页面的State类里维护uniqueIdentifier变量,用BlocListener监听GenerateOtpBloc的状态变化来保存数据(BlocListener就是用来处理状态变化的逻辑副作用,比如保存数据、导航,而不是构建UI)。
class OtpPage extends StatefulWidget { const OtpPage({super.key}); @override State<OtpPage> createState() => _OtpPageState(); } class _OtpPageState extends State<OtpPage> { String? _uniqueIdentifier; final TextEditingController _otpController = TextEditingController(); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text("OTP验证")), body: Padding( padding: const EdgeInsets.all(16.0), child: MultiBlocListener( listeners: [ // 监听GenerateOtpBloc的状态,保存uniqueIdentifier BlocListener<GenerateOtpBloc, GenerateOtpState>( listener: (context, state) { if (state is GenerateOtpLoaded) { setState(() { _uniqueIdentifier = state.uniqueIdentifier; }); } }, ), // 可选:监听ValidateOtpBloc的状态,处理验证结果 BlocListener<ValidateOtpBloc, ValidateOtpState>( listener: (context, state) { if (state is ValidateOtpLoaded) { // 验证成功,比如跳转到首页 Navigator.of(context).pop(); } else if (state is ValidateOtpError) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text(state.message)), ); } }, ), ], child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ // 根据是否获取到uniqueIdentifier切换UI if (_uniqueIdentifier == null) ElevatedButton( onPressed: () { context.read<GenerateOtpBloc>().add(const GenerateOtpRequested()); }, child: const Text("获取验证码"), ) else Column( children: [ TextField( controller: _otpController, keyboardType: TextInputType.number, decoration: const InputDecoration( labelText: "输入6位验证码", hintText: "请输入收到的验证码", ), ), const SizedBox(height: 20), BlocBuilder<ValidateOtpBloc, ValidateOtpState>( builder: (context, state) { if (state is ValidateOtpLoading) { return const CircularProgressIndicator(); } return ElevatedButton( onPressed: _otpController.text.length == 6 ? () { context.read<ValidateOtpBloc>().add( ValidateOtpRequested( otp: _otpController.text, uniqueIdentifier: _uniqueIdentifier!, ), ); } : null, child: const Text("验证验证码"), ); }, ), ], ), ], ), ), ), ); } }
方案优势:
- 逻辑清晰:页面负责UI状态切换和数据传递,Bloc专注业务逻辑
- 代码轻量:不需要额外的全局状态或Bloc依赖
- 符合Bloc设计原则:用
BlocListener处理副作用,BlocBuilder只做UI渲染
方案2:让ValidateOtpBloc依赖GenerateOtpBloc
如果你不想在页面维护状态,可以让ValidateOtpBloc直接监听GenerateOtpBloc的状态变化,内部保存uniqueIdentifier。
步骤1:修改ValidateOtpBloc的构造函数
class ValidateOtpBloc extends Bloc<ValidateOtpEvent, ValidateOtpState> { final GenerateOtpBloc _generateOtpBloc; String? _uniqueIdentifier; late final StreamSubscription _generateOtpSubscription; ValidateOtpBloc( ValidateOtpInitial initialState, required GenerateOtpBloc generateOtpBloc, ) : _generateOtpBloc = generateOtpBloc, super(initialState) { // 监听GenerateOtpBloc的状态变化,保存uniqueIdentifier _generateOtpSubscription = _generateOtpBloc.stream.listen((state) { if (state is GenerateOtpLoaded) { _uniqueIdentifier = state.uniqueIdentifier; } }); on<ValidateOtpRequested>((event, emit) async { if (_uniqueIdentifier == null) { emit(const ValidateOtpError(message: "请先获取验证码")); return; } try { emit(ValidateOtpLoading()); // 调用验证API,传入OTP和uniqueIdentifier final response = await validateOtpApi( otp: event.otp, uniqueIdentifier: _uniqueIdentifier!, ); emit(ValidateOtpLoaded(response: response)); } catch (e) { emit(ValidateOtpError(message: e.toString())); } }); } @override Future<void> close() { // 取消订阅,避免内存泄漏 _generateOtpSubscription.cancel(); return super.close(); } }
步骤2:修改MultiBlocProvider的创建逻辑
Navigator.of(context).push( MaterialPageRoute<LandingPage>( builder: (_) => MultiBlocProvider( providers: [ BlocProvider<GenerateOtpBloc>( create: (context) => GenerateOtpBloc(GenerateOtpInitial()) ), BlocProvider<ValidateOtpBloc>( create: (context) => ValidateOtpBloc( ValidateOtpInitial(), generateOtpBloc: context.read<GenerateOtpBloc>(), ) ) ], child: const OtpPage(), ), ), );
方案优势:
- 页面无需维护状态,直接触发验证事件即可
- 数据传递逻辑封装在Bloc内部,页面更简洁
方案3:全局共享状态(适合多页/复杂场景)
如果你的OTP流程涉及多个页面,或者需要全局访问uniqueIdentifier,可以用一个共享的Cubit来存储这个值。
步骤1:创建共享Cubit
class OtpSharedCubit extends Cubit<OtpSharedState> { OtpSharedCubit() : super(OtpSharedInitial()); // 保存uniqueIdentifier void setUniqueIdentifier(String identifier) { emit(OtpUniqueIdentifierSet(identifier: identifier)); } // 获取uniqueIdentifier String? getUniqueIdentifier() { if (state is OtpUniqueIdentifierSet) { return (state as OtpUniqueIdentifierSet).identifier; } return null; } } // 共享状态类 abstract class OtpSharedState {} class OtpSharedInitial extends OtpSharedState {} class OtpUniqueIdentifierSet extends OtpSharedState { final String identifier; OtpUniqueIdentifierSet({required this.identifier}); }
步骤2:在MultiBlocProvider中添加共享Cubit
MultiBlocProvider( providers: [ BlocProvider<GenerateOtpBloc>( create: (context) => GenerateOtpBloc(GenerateOtpInitial()) ), BlocProvider<ValidateOtpBloc>( create: (context) => ValidateOtpBloc(ValidateOtpInitial()) ), BlocProvider<OtpSharedCubit>( create: (context) => OtpSharedCubit() ), ], child: const OtpPage(), ),
步骤3:在GenerateOtpBloc中保存数据到共享Cubit
on<GenerateOtpRequested>((event, emit) async { try { emit(GenerateOtpLoading()); final response = await generateOtpApi(); emit(GenerateOtpLoaded( generateOtpRes: response, uniqueIdentifier: response.uniqueIdentifier, )); // 保存到共享Cubit context.read<OtpSharedCubit>().setUniqueIdentifier(response.uniqueIdentifier); } catch (e) { emit(GenerateOtpError(message: e.toString())); } });
步骤4:在ValidateOtpBloc中获取共享数据
on<ValidateOtpRequested>((event, emit) async { final uniqueIdentifier = context.read<OtpSharedCubit>().getUniqueIdentifier(); if (uniqueIdentifier == null) { emit(const ValidateOtpError(message: "请先获取验证码")); return; } // 调用验证API try { emit(ValidateOtpLoading()); final response = await validateOtpApi( otp: event.otp, uniqueIdentifier: uniqueIdentifier, ); emit(ValidateOtpLoaded(response: response)); } catch (e) { emit(ValidateOtpError(message: e.toString())); } });
方案优势:
- 全局共享数据,适合跨页面/组件的场景
- 数据管理集中,便于统一维护
内容的提问来源于stack exchange,提问作者Cherry
相关产品推荐
相关产品推荐

