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

Flutter中借助Bloc实现多页面认证管理的最优方案咨询

Flutter底部导航栏+Bloc认证实现方案

1. 全局注入共享的AuthBloc

要解决跨页面共享Bloc实例的问题,必须在App最顶层用BlocProvider包裹整个应用,确保所有子Widget都能获取到同一个AuthBloc实例。

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  final prefs = await SharedPreferences.getInstance();
  runApp(MyApp(prefs: prefs));
}

class MyApp extends StatelessWidget {
  final SharedPreferences prefs;
  const MyApp({super.key, required this.prefs});

  @override
  Widget build(BuildContext context) {
    return BlocProvider(
      create: (context) => AuthBloc(prefs: prefs)..add(AuthCheckInitialToken()),
      child: MaterialApp(
        title: 'Auth Demo',
        home: const BottomNavScreen(),
        routes: {
          '/login': (context) => const LoginScreen(),
          '/register': (context) => const RegisterScreen(),
        },
      ),
    );
  }
}

2. 定义AuthBloc的核心逻辑

状态与事件设计

// 状态
abstract class AuthState extends Equatable {
  const AuthState();
  @override
  List<Object> get props => [];
}

class AuthLoading extends AuthState {}
class AuthAuthenticated extends AuthState {
  final String token;
  const AuthAuthenticated(this.token);
  @override
  List<Object> get props => [token];
}
class AuthUnauthenticated extends AuthState {}

// 事件
abstract class AuthEvent extends Equatable {
  const AuthEvent();
  @override
  List<Object> get props => [];
}

class AuthCheckInitialToken extends AuthEvent {}
class AuthLogin extends AuthEvent {
  final String email;
  final String password;
  const AuthLogin(this.email, this.password);
  @override
  List<Object> get props => [email, password];
}
class AuthRegister extends AuthEvent {
  final String email;
  final String password;
  const AuthRegister(this.email, this.password);
  @override
  List<Object> get props => [email, password];
}
class AuthLogout extends AuthEvent {}

Bloc业务逻辑

class AuthBloc extends Bloc<AuthEvent, AuthState> {
  final SharedPreferences prefs;
  AuthBloc({required this.prefs}) : super(AuthLoading()) {
    on<AuthCheckInitialToken>(_checkInitialToken);
    on<AuthLogin>(_handleLogin);
    on<AuthRegister>(_handleRegister);
    on<AuthLogout>(_handleLogout);
  }

  void _checkInitialToken(AuthCheckInitialToken event, Emitter<AuthState> emit) {
    final token = prefs.getString('auth_token');
    if (token?.isNotEmpty == true) {
      emit(AuthAuthenticated(token!));
    } else {
      emit(AuthUnauthenticated());
    }
  }

  void _handleLogin(AuthLogin event, Emitter<AuthState> emit) async {
    emit(AuthLoading());
    // 替换为真实API请求
    await Future.delayed(const Duration(seconds: 1));
    const token = 'user_token_123';
    await prefs.setString('auth_token', token);
    emit(AuthAuthenticated(token));
  }

  void _handleRegister(AuthRegister event, Emitter<AuthState> emit) async {
    emit(AuthLoading());
    // 替换为真实注册API请求
    await Future.delayed(const Duration(seconds: 1));
    const token = 'new_user_token_456';
    await prefs.setString('auth_token', token);
    emit(AuthAuthenticated(token));
  }

  void _handleLogout(AuthLogout event, Emitter<AuthState> emit) async {
    await prefs.remove('auth_token');
    emit(AuthUnauthenticated());
  }
}

3. 封装认证拦截组件

给需要认证的页面统一套一层拦截逻辑,避免重复代码:

class AuthRequiredWrapper extends StatelessWidget {
  final Widget child;
  const AuthRequiredWrapper({super.key, required this.child});

  @override
  Widget build(BuildContext context) {
    return BlocConsumer<AuthBloc, AuthState>(
      listener: (context, state) {
        if (state is AuthUnauthenticated) {
          Navigator.pushNamed(context, '/login');
        }
      },
      builder: (context, state) {
        if (state is AuthAuthenticated) return child;
        if (state is AuthLoading) return const Center(child: CircularProgressIndicator());
        return const SizedBox.shrink();
      },
    );
  }
}

4. 底部导航栏实现

区分无需认证和需要认证的页面,切换时主动校验状态:

class BottomNavScreen extends StatefulWidget {
  const BottomNavScreen({super.key});

  @override
  State<BottomNavScreen> createState() => _BottomNavScreenState();
}

class _BottomNavScreenState extends State<BottomNavScreen> {
  int _currentIndex = 0;
  final List<Widget> _pages = [
    // 无需认证的首页
    const HomeScreen(),
    // 需要认证的页面,用拦截组件包裹
    AuthRequiredWrapper(child: const ProfileScreen()),
    AuthRequiredWrapper(child: const OrdersScreen()),
    AuthRequiredWrapper(child: const MessagesScreen()),
    AuthRequiredWrapper(child: const SettingsScreen()),
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: _pages[_currentIndex],
      bottomNavigationBar: BottomNavigationBar(
        currentIndex: _currentIndex,
        onTap: (index) {
          setState(() => _currentIndex = index);
          // 切换到需认证页面时,主动触发token校验
          if (index != 0) {
            context.read<AuthBloc>().add(AuthCheckInitialToken());
          }
        },
        items: const [
          BottomNavigationBarItem(icon: Icon(Icons.home), label: '首页'),
          BottomNavigationBarItem(icon: Icon(Icons.person), label: '我的'),
          BottomNavigationBarItem(icon: Icon(Icons.shopping_bag), label: '订单'),
          BottomNavigationBarItem(icon: Icon(Icons.message), label: '消息'),
          BottomNavigationBarItem(icon: Icon(Icons.settings), label: '设置'),
        ],
      ),
    );
  }
}

5. 登录/注册页面处理

登录页监听AuthBloc状态,成功后返回底部导航:

class LoginScreen extends StatelessWidget {
  const LoginScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('登录')),
      body: BlocConsumer<AuthBloc, AuthState>(
        listener: (context, state) {
          if (state is AuthAuthenticated) Navigator.pop(context);
        },
        builder: (context, state) {
          if (state is AuthLoading) return const Center(child: CircularProgressIndicator());
          return Padding(
            padding: const EdgeInsets.all(16),
            child: Column(
              children: [
                TextField(decoration: const InputDecoration(labelText: '邮箱')),
                TextField(decoration: const InputDecoration(labelText: '密码'), obscureText: true),
                const SizedBox(height: 20),
                ElevatedButton(
                  onPressed: () => context.read<AuthBloc>().add(const AuthLogin('test@demo.com', '123456')),
                  child: const Text('登录'),
                ),
                TextButton(
                  onPressed: () => Navigator.pushNamed(context, '/register'),
                  child: const Text('没有账号?去注册'),
                ),
              ],
            ),
          );
        },
      ),
    );
  }
}

注册页逻辑与登录页一致,只需触发AuthRegister事件即可。

关键注意事项

  • 必须在App顶层注入AuthBloc,禁止在子页面单独创建实例,否则无法共享状态。
  • 启动时主动检查本地token,确保App重启后状态正确。
  • 切换到需认证页面时主动触发状态校验,避免token过期未及时更新状态。

内容的提问来源于stack exchange,提问作者Med Oussema Zaier

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 04:01:07