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

Flutter Web初始认证时跨路由用BlocListener显示加载指示器问题

问题描述

我正在开发一个使用flutter_bloc进行状态管理、AzureAD实现认证的Flutter Web应用。认证流程在main.dart的runApp(MyApp())之后自动启动,无需用户手动登录,初始配置的相关代码片段如下:

MultiBlocProvider(
  providers: [
    BlocProvider(
      create: (context) => AuthBloc(authRepository: authRepository)..add(AuthInitializationEvent()),
    ),
  ],
  // Child: MaterialApp and routes setup
)

在MaterialApp中我定义了多个路由:

navigatorKey: globalNavigatorKey,
routes: {
  '/': (context) => RootScreen(),
  '/page1': (context) => Page1(),
  '/page2': (context) => Page2(),
},

在RootScreen中,我使用BlocListener监听认证状态变化:

BlocListener<AuthBloc, AuthState>(
  listener: (context, state) {
    if (state.status == AuthStatus.inProgress) {
      // Intention to show loading indicator
    } else if (state.status == AuthStatus.authenticated) {
      globalNavigatorKey.currentState?.pushNamedAndRemoveUntil(
        '/Page1',
        (route) => false,
      );
    }
  },
  // Child: BlocBuilder to rebuild UI based on state
)

遇到的问题:在初始认证过程中,跨不同路由显示加载指示器(CircularProgressIndicator)时,用户在非根路由(/)刷新页面(F5)或手动导航到非根路由时,加载指示器无法正常显示,但正常导航应用时可以正常显示。

请问如何确保初始认证过程中,无论当前路由或是否刷新页面,都能在所有路由上一致显示加载指示器?另外,考虑到认证流程是自动触发的,如何有效构建BlocListener和BlocBuilder来处理该场景?


解决方案

1. 将加载指示器提升到全局层级

问题核心是当前加载逻辑仅绑定在RootScreen,非根路由刷新时不会渲染该页面,自然无法感知认证状态。解决思路是把加载指示器放在MaterialApp的父级,确保所有路由场景下都能响应认证状态:

修改MultiBlocProvider的子组件结构,用BlocBuilder包裹MaterialApp,结合Stack实现全局加载层覆盖:

MultiBlocProvider(
  providers: [
    BlocProvider(
      create: (context) => AuthBloc(authRepository: authRepository)..add(AuthInitializationEvent()),
    ),
  ],
  child: BlocBuilder<AuthBloc, AuthState>(
    builder: (context, state) {
      return Stack(
        children: [
          MaterialApp(
            navigatorKey: globalNavigatorKey,
            routes: {
              '/': (context) => RootScreen(),
              '/page1': (context) => Page1(),
              '/page2': (context) => Page2(),
            },
          ),
          // 根据认证状态显示全局加载指示器
          if (state.status == AuthStatus.inProgress)
            Container(
              color: Colors.black54,
              child: const Center(
                child: CircularProgressIndicator(),
              ),
            ),
        ],
      );
    },
  ),
)

2. 重构全局认证状态监听逻辑

原来的BlocListener仅在RootScreen中,非根路由刷新时无法触发页面跳转。需要把路由跳转逻辑移到MaterialApp层级的BlocListener中,确保任何路由下都能响应认证完成事件:

MultiBlocProvider(
  providers: [
    BlocProvider(
      create: (context) => AuthBloc(authRepository: authRepository)..add(AuthInitializationEvent()),
    ),
  ],
  child: BlocListener<AuthBloc, AuthState>(
    listener: (context, state) {
      if (state.status == AuthStatus.authenticated) {
        globalNavigatorKey.currentState?.pushNamedAndRemoveUntil(
          '/page1',
          (route) => false,
        );
      }
      // 可添加未认证状态的处理逻辑,比如跳转到登录页(如果需要)
    },
    child: BlocBuilder<AuthBloc, AuthState>(
      builder: (context, state) {
        return Stack(
          children: [
            MaterialApp(
              navigatorKey: globalNavigatorKey,
              routes: {
                '/': (context) => RootScreen(),
                '/page1': (context) => Page1(),
                '/page2': (context) => Page2(),
              },
            ),
            if (state.status == AuthStatus.inProgress)
              Container(
                color: Colors.black54,
                child: const Center(
                  child: CircularProgressIndicator(),
                ),
              ),
          ],
        );
      },
    ),
  ),
)

3. 处理非根路由初始化时的认证状态

Flutter Web刷新或手动导航到非根路由时,会直接初始化对应页面,此时需要在每个页面的初始化阶段检查认证状态,避免未完成认证就显示页面内容:

class Page1 extends StatefulWidget {
  @override
  State<Page1> createState() => _Page1State();
}

class _Page1State extends State<Page1> {
  @override
  void initState() {
    super.initState();
    final authState = context.read<AuthBloc>().state;
    if (authState.status != AuthStatus.authenticated) {
      // 未认证时触发重新初始化
      context.read<AuthBloc>().add(AuthInitializationEvent());
    }
  }

  @override
  Widget build(BuildContext context) {
    return BlocBuilder<AuthBloc, AuthState>(
      builder: (context, state) {
        if (state.status != AuthStatus.authenticated) {
          // 未完成认证时返回空布局,依赖全局加载层
          return const SizedBox.shrink();
        }
        // 页面正常内容
        return Scaffold(
          appBar: AppBar(title: const Text('Page1')),
          body: const Center(child: Text('Authenticated Content')),
        );
      },
    );
  }
}

4. 优化认证事件触发逻辑

在AuthBloc中添加状态判断,避免重复触发初始化事件:

class AuthBloc extends Bloc<AuthEvent, AuthState> {
  final AuthRepository authRepository;

  AuthBloc({required this.authRepository}) : super(AuthState(status: AuthStatus.unauthenticated)) {
    on<AuthInitializationEvent>(_onAuthInitialization);
  }

  Future<void> _onAuthInitialization(AuthInitializationEvent event, Emitter<AuthState> emit) async {
    // 正在初始化时直接返回,避免重复请求
    if (state.status == AuthStatus.inProgress) return;
    emit(state.copyWith(status: AuthStatus.inProgress));
    try {
      final user = await authRepository.initializeAuth();
      emit(state.copyWith(status: AuthStatus.authenticated, user: user));
    } catch (e) {
      emit(state.copyWith(status: AuthStatus.unauthenticated, error: e.toString()));
    }
  }
}

内容的提问来源于stack exchange,提问作者Fabian M

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 07:27:40