Flutter中利用Streams实现登录状态跳转及Clean Architecture进阶咨询
基于Clean Architecture的Flutter登录状态自动跳转(Streams实现)+ 架构学习指南
一、Streams实现登录状态自动跳转的具体方案
我会按照Clean Architecture的分层逻辑来拆解实现,确保各层职责清晰、依赖正确:
1. 核心层(Entities & Use Cases)
这一层是应用的业务核心,不依赖任何外部框架或工具。
- 先定义登录状态实体,用来统一表示认证状态:
// core/entities/auth_state.dart enum AuthStatus { authenticated, unauthenticated } class AuthState { final AuthStatus status; final User? user; // User是你的核心用户实体,包含用户名、ID等业务属性 AuthState({required this.status, this.user}); }
- 再实现获取认证状态流的用例,它依赖抽象的仓库接口(不关心具体数据来源):
// core/use_cases/get_auth_state_stream_use_case.dart class GetAuthStateStreamUseCase { final AuthRepository authRepository; GetAuthStateStreamUseCase(this.authRepository); Stream<AuthState> call() { // 直接委托给仓库的流,核心层只关注业务逻辑,不处理数据细节 return authRepository.authStateStream; } }
2. 接口适配器层(Repositories & Data Sources)
这一层负责连接核心层和外部资源(本地存储、网络等),核心层只依赖这里的抽象。
- 定义认证仓库的抽象接口:
// features/auth/repositories/auth_repository.dart abstract class AuthRepository { Stream<AuthState> get authStateStream; // 这里还可以添加login、logout等抽象方法 }
- 实现具体的仓库类,对接本地数据源:
// features/auth/repositories/auth_repository_impl.dart class AuthRepositoryImpl implements AuthRepository { final LocalAuthDataSource localAuthDataSource; AuthRepositoryImpl(this.localAuthDataSource); @override Stream<AuthState> get authStateStream { // 将本地数据源的状态流转换为核心层的AuthState流 return localAuthDataSource.authStatusStream.map((status) { if (status == AuthStatus.authenticated) { return AuthState( status: status, user: localAuthDataSource.getCurrentUser(), ); } else { return AuthState(status: status, user: null); } }); } }
- 实现本地数据源,用StreamController管理状态变化:
// features/auth/data_sources/local_auth_data_source.dart class LocalAuthDataSource { final StreamController<AuthStatus> _statusController = StreamController.broadcast(); final SharedPreferences _prefs; LocalAuthDataSource(this._prefs) { // 初始化时从本地读取初始登录状态 final isLoggedIn = _prefs.getBool('is_logged_in') ?? false; _statusController.add(isLoggedIn ? AuthStatus.authenticated : AuthStatus.unauthenticated); } Stream<AuthStatus> get authStatusStream => _statusController.stream; User? getCurrentUser() { // 从本地存储解析用户信息为核心层的User实体 final userJson = _prefs.getString('current_user'); if (userJson != null) { return User.fromJson(jsonDecode(userJson)); } return null; } // 登录成功后调用,更新本地状态和流 void setLoggedIn(User user) { _prefs.setBool('is_logged_in', true); _prefs.setString('current_user', jsonEncode(user.toJson())); _statusController.add(AuthStatus.authenticated); } // 登出时调用 void setLoggedOut() { _prefs.setBool('is_logged_in', false); _prefs.remove('current_user'); _statusController.add(AuthStatus.unauthenticated); } // 记得在应用退出时关闭控制器 void dispose() { _statusController.close(); } }
3. 框架层(Flutter UI)
这一层是具体的Flutter实现,只依赖接口适配器层的抽象。
- 在应用入口用StreamBuilder监听状态流,自动跳转页面:
// presentation/app.dart class App extends StatelessWidget { final GetAuthStateStreamUseCase getAuthStateStreamUseCase; const App({required this.getAuthStateStreamUseCase, super.key}); @override Widget build(BuildContext context) { return MaterialApp( home: StreamBuilder<AuthState>( stream: getAuthStateStreamUseCase(), initialData: const AuthState(status: AuthStatus.unauthenticated), builder: (context, snapshot) { switch (snapshot.data?.status) { case AuthStatus.authenticated: return const HomePage(); case AuthStatus.unauthenticated: return const LoginPage(); default: return const SplashScreen(); // 加载过渡页面 } }, ), ); } }
- 依赖注入(用get_it示例):初始化时把各层实例绑定,确保依赖正确:
// di/injection.dart void setupDependencies() { final getIt = GetIt.instance; // 本地存储实例 getIt.registerSingletonAsync<SharedPreferences>(() => SharedPreferences.getInstance()); // 数据源 getIt.registerSingleton<LocalAuthDataSource>( LocalAuthDataSource(getIt<SharedPreferences>()), ); // 仓库 getIt.registerSingleton<AuthRepository>( AuthRepositoryImpl(getIt<LocalAuthDataSource>()), ); // 用例 getIt.registerSingleton<GetAuthStateStreamUseCase>( GetAuthStateStreamUseCase(getIt<AuthRepository>()), ); }
二、成为Clean Architecture专家的学习建议
我当初学Clean Architecture的时候也踩了不少坑,分享几个实用的经验:
1. 先吃透核心原则,再写代码
- 反复琢磨依赖倒置原则:核心层(Entities、Use Cases)绝不依赖外层,所有外层都依赖核心层的抽象。记住「内层定规则,外层做实现」。
- 搞清楚各层的职责边界:
- Entities:纯业务模型,只包含属性和业务逻辑(比如User的密码验证),不碰任何外部工具。
- Use Cases:具体业务流程(比如“登录并保存用户信息”),只依赖Repository抽象,不管数据从本地还是网络来。
- Interface Adapters:做数据格式转换,实现Repository抽象,是核心层和外部资源的桥梁。
- Frameworks & Drivers:具体技术实现(Flutter UI、SharedPreferences、Dio等),只对接Interface Adapters的抽象。
2. 从小项目入手,逐步迭代
- 别一开始就用Clean Architecture写复杂应用,先写个简单的TODO应用,只实现增删改查,把分层逻辑理清楚。
- 每写完一个功能,回头检查:核心层有没有依赖外层?各层的依赖是不是符合“内层不依赖外层”的规则?
3. 多参考优秀开源项目
- 找几个基于Clean Architecture的Flutter项目,仔细看他们的目录结构和代码逻辑:
- 看他们怎么划分文件夹(比如core、features、data、domain、presentation)
- 看他们怎么处理依赖注入
- 看他们怎么实现用例和仓库的交互
4. 不要过度设计,保持实用
- Clean Architecture不是教条,要根据项目规模调整。比如小项目可以合并一些层(比如把Use Cases和Repository抽象放在同一目录),别为了分层而分层。
- 如果你的应用不需要网络请求,就不用写远程数据源,只保留本地数据源即可。
5. 写测试,加深理解
- 核心层的代码(Entities、Use Cases)是纯业务逻辑,非常适合写单元测试。通过写测试,你会更清晰各层的职责,也能确保业务逻辑的正确性。
- 比如测试
GetAuthStateStreamUseCase,只需要模拟AuthRepository的返回,不需要关心具体的本地存储实现。
内容的提问来源于stack exchange,提问作者Madhurshanan Thillainathan
相关产品推荐
相关产品推荐

