You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.09 18:37:29