Clean Architecture下Firebase addOnCompleteListener回调传递(Android)
项目采用Clean Architecture三层架构:
- presentation层:UI与ViewModel逻辑
- domain层:UseCase、业务实体、Repository接口定义
- data层:Repository实现、SDK相关逻辑(Firebase操作全部在这一层)
当前Firebase邮箱注册功能可正常运行,但需要把异步请求的加载状态透传到UI层以展示进度条。已知createUserWithEmailAndPassword的addOnCompleteListener回调触发前存在固定耗时,需要在不违反整洁架构规则的前提下实现状态回调传递。
现有各层代码如下:
ViewModel(Presentation层)
fun createUser(email: String, password: String) { createUser.invoke(email, password) }
UseCase(Domain层)
class CreateUser @Inject constructor( private val authenticationDomainRepository: AuthenticationDomainRepository ) { operator fun invoke(email: String, password: String) { authenticationDomainRepository.createUser(email, password) } }
AuthenticationDomainRepository(Domain层)
interface AuthenticationDomainRepository { fun createUser(email: String, password: String) }
AuthenticationRepository(Data层)
class AuthenticationRepository @Inject constructor( private val authenticationManager: AuthenticationManager ) : AuthenticationDomainRepository { override fun createUser(email: String, password: String) { authenticationManager.createUserWithEmailAndPassword(email, password) } }
AuthenticationManager(Data层)
class AuthenticationManager @Inject constructor() { private val firebaseAuth: FirebaseAuth by lazy { FirebaseAuth.getInstance() } fun createUserWithEmailAndPassword(email: String, password: String) { firebaseAuth.createUserWithEmailAndPassword(email, password) } }
核心原则:所有抽象契约定义在domain层,依赖方向始终从外层指向内层,Firebase相关逻辑完全收敛在data层,不向上层泄露SDK细节。
1. 在Domain层定义通用状态模型与接口契约
domain层作为最内层,定义所有业务相关的结构,外层全部依赖这些抽象,不会出现反向依赖。
首先定义注册操作的全链路状态密封类:
// domain层下的result包 sealed class CreateUserResult { object Loading : CreateUserResult() // 请求发起,展示加载状态 object Success : CreateUserResult() // 注册成功 data class Error(val throwable: Throwable) : CreateUserResult() // 注册失败,携带错误信息 }
修改Repository接口,将返回值改为可连续发射状态的Flow(协程标准组件,不耦合任何第三方SDK):
// Domain层: AuthenticationDomainRepository interface AuthenticationDomainRepository { fun createUser(email: String, password: String): Flow<CreateUserResult> }
修改UseCase,直接透传Repository返回的状态流即可,参数校验等业务逻辑可在此处扩展:
// Domain层: CreateUser UseCase class CreateUser @Inject constructor( private val authenticationDomainRepository: AuthenticationDomainRepository ) { operator fun invoke(email: String, password: String): Flow<CreateUserResult> { // 可在此处增加邮箱、密码格式校验,校验不通过直接返回Error状态 return authenticationDomainRepository.createUser(email, password) } }
2. 在Data层实现状态发射逻辑
所有Firebase的回调转换全部在data层完成,将SDK的任务回调转换成domain层定义的CreateUserResult状态流:
首先修改Repository实现,匹配接口返回值:
// Data层: AuthenticationRepository class AuthenticationRepository @Inject constructor( private val authenticationManager: AuthenticationManager ) : AuthenticationDomainRepository { override fun createUser(email: String, password: String): Flow<CreateUserResult> { return authenticationManager.createUserWithEmailAndPassword(email, password) } }
修改AuthenticationManager,在发起Firebase请求时第一时间发射Loading状态,根据任务回调发射成功/失败状态:
// Data层: AuthenticationManager class AuthenticationManager @Inject constructor() { private val firebaseAuth: FirebaseAuth by lazy { FirebaseAuth.getInstance() } fun createUserWithEmailAndPassword(email: String, password: String): Flow<CreateUserResult> = callbackFlow { // 请求发起立刻发送加载状态,UI收到后展示进度条 trySend(CreateUserResult.Loading) val task = firebaseAuth.createUserWithEmailAndPassword(email, password) task.addOnCompleteListener { authTask -> if (authTask.isSuccessful) { trySend(CreateUserResult.Success) } else { trySend(CreateUserResult.Error(authTask.exception ?: RuntimeException("注册失败,请稍后重试"))) } close() // 状态全部发射完成,关闭流 }.addOnFailureListener { e -> trySend(CreateUserResult.Error(e)) close() } // 流被取消时可在此处添加资源释放逻辑 awaitClose { } } }
3. Presentation层收集状态更新UI
在ViewModel中通过协程收集UseCase返回的状态流,暴露给UI层观察:
// Presentation层: AuthViewModel class AuthViewModel @Inject constructor( private val createUser: CreateUser ) : ViewModel() { private val _uiState = MutableStateFlow<CreateUserResult?>(null) val uiState: StateFlow<CreateUserResult?> = _uiState.asStateFlow() fun register(email: String, password: String) { viewModelScope.launch { createUser(email, password).collect { result -> _uiState.value = result } } } }
在Activity/Fragment中观察uiState即可做对应UI操作:
- 收到
Loading:显示进度条,禁用注册按钮避免重复点击 - 收到
Success:隐藏进度条,跳转主页或展示成功提示 - 收到
Error:隐藏进度条,根据携带的异常信息展示错误提示
- 依赖方向完全符合Clean Architecture规则:presentation、data层全部依赖domain层的抽象,domain层不感知任何外层实现细节
- Firebase相关逻辑100%收敛在data层,后续如果替换认证SDK(比如改成自建账号体系、其他第三方认证服务),只需要修改data层的实现,上层代码完全不需要改动
- 没有跨层调用,各层职责清晰:domain层定义业务规则,data层负责数据实现,presentation层负责UI渲染
内容的提问来源于stack exchange,提问作者Jamal Aartsen

