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

Flutter Bloc+onGenerateRoute页面重复注入与导航栈重复推送问题

Flutter Bloc导航重复触发问题解决方案

问题核心原因

  1. 状态字段未重置:AuthBloc中isOtpCodeValid字段在OTP验证成功后未重置为false,导致后续状态更新(比如重复点击按钮触发loading状态后回到success状态)时,BlocListener会重复检测到isOtpCodeValid为true,进而重复触发导航。
  2. listenWhen条件不严谨:当前仅判断字段是否变化,未限定isOtpCodeValid的变化方向,只要前后状态该字段值不同就会触发导航(比如从true变回false再变回true)。

解决方案1:优化AuthBloc状态设计(推荐)

采用一次性状态类型替代bool字段,更符合Bloc状态驱动的设计原则,从根源避免状态残留问题。

修改AuthState结构

abstract class AuthState {}

class AuthInitial extends AuthState {}
class AuthLoading extends AuthState {}
// 单独定义OTP验证成功的状态
class AuthOtpVerified extends AuthState {
  final bool isUserProfileDataFilled;
  AuthOtpVerified({required this.isUserProfileDataFilled});
}
class AuthError extends AuthState {
  final String message;
  AuthError({required this.message});
}

调整BlocListener逻辑

BlocListener<AuthBloc, AuthState>(
  listener: (context, state) {
    if (state is AuthOtpVerified) {
      // 调用登录成功逻辑
      _loginUser();
      // 根据用户资料状态导航
      if (!state.isUserProfileDataFilled) {
        Navigator.pushReplacementNamed(context, '/userDataInputScreen');
      } else {
        Navigator.pushReplacementNamed(context, '/mainWrapper');
      }
    } else if (state is AuthError) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text(state.message)),
      );
    }
  },
  child: BlocBuilder<AuthBloc, AuthState>(
    builder: (context, state) {
      return Padding(
        padding: const EdgeInsets.symmetric(horizontal: 45.0),
        child: PrimaryButton(
          size: ButtonSize.small,
          label: 'VERIFY NOW',
          onTap: () {
            String otpCode = otpControllers.map((controller) => controller.text).join();
            if(otpCode.length == 4){
              context.read<AuthBloc>().add(AuthOtpSendCode(otpCode: otpCode));
            }
          },
          isLoading: state is AuthLoading,
        ),
      );
    },
  ),
)

AuthBloc事件处理示例

on<AuthOtpSendCode>((event, emit) async {
  emit(AuthLoading());
  try {
    // 执行OTP验证逻辑
    final isProfileFilled = await checkUserProfileCompletion();
    // 发出一次性验证成功状态
    emit(AuthOtpVerified(isUserProfileDataFilled: isProfileFilled));
  } catch (e) {
    emit(AuthError(message: e.toString()));
  }
});

解决方案2:优化listenWhen条件并重置状态字段

如果不想修改状态结构,可通过以下方式修复:

调整listenWhen条件

仅在isOtpCodeValid从false变为true时触发导航:

listenWhen: (previous, current) {
  // 仅当OTP验证从无效变为有效时触发导航
  final otpValidTrigger = !previous.isOtpCodeValid && current.isOtpCodeValid;
  // 错误信息变化时触发提示
  final errorTrigger = previous.errorMessage != current.errorMessage;
  return otpValidTrigger || errorTrigger;
},

在AuthBloc中重置状态字段

每次处理OTP事件时,先重置isOtpCodeValid为false,验证成功后短暂设置为true再重置:

on<AuthOtpSendCode>((event, emit) async {
  emit(state.copyWith(
    isLoading: true,
    isOtpCodeValid: false,
    errorMessage: null
  ));
  try {
    final isProfileFilled = await checkUserProfile();
    // 发出验证成功状态
    emit(state.copyWith(
      isLoading: false,
      isOtpCodeValid: true,
      isUserProfileDataFilled: isProfileFilled,
    ));
    // 延迟重置,避免后续状态更新触发重复导航
    Future.delayed(Duration.zero, () {
      emit(state.copyWith(isOtpCodeValid: false));
    });
  } catch (e) {
    emit(state.copyWith(
      isLoading: false,
      errorMessage: e.toString(),
    ));
  }
});

额外修复点

  1. 调用登录成功逻辑:在验证成功后必须调用_loginUser(),否则SharedPreferences不会更新,下次启动App无法正确跳转:
if (state.isOtpCodeValid && !state.isUserProfileDataFilled) {
  _loginUser();
  Navigator.pushReplacementNamed(context, '/userDataInputScreen');
} else if(state.isOtpCodeValid){
  _loginUser();
  Navigator.pushReplacementNamed(context, '/mainWrapper');
}
  1. 清理AppRouter冗余代码:getInitialRoute方法中有多余的return语句,需删除:
Future<String> getInitialRoute() async {
  SharedPreferences prefs = await SharedPreferences.getInstance();
  bool isLoggedIn = prefs.getBool('isLoggedIn') ?? false;
  bool isFirstTime = prefs.getBool('isFirstTime') ?? true;

  if (isLoggedIn) {
    return mainWrapper;
  } else if (isFirstTime) {
    return onboarding1;
  } else {
    return signInScreen;
  }
}
  1. 控制器内存管理:将OtpScreen改为StatefulWidget,在dispose中释放TextEditingController,避免内存泄漏:
class OtpScreen extends StatefulWidget {
  const OtpScreen({super.key});

  @override
  State<OtpScreen> createState() => _OtpScreenState();
}

class _OtpScreenState extends State<OtpScreen> {
  late final List<TextEditingController> otpControllers;

  @override
  void initState() {
    super.initState();
    otpControllers = List.generate(4, (index) => TextEditingController());
  }

  @override
  void dispose() {
    for (var controller in otpControllers) {
      controller.dispose();
    }
    super.dispose();
  }

  // 剩余build逻辑不变
}

内容的提问来源于stack exchange,提问作者sunny 4k4

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 13:15:54