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
相关产品推荐
相关产品推荐

