目录
我在Google Playstore上的应用程序
github代码
简介
- 本系列将是对我在开发Android应用程序时面临的任何问题或任何观察结果的非正式演示。本系列中的每个博客文章都将是独特的,并且与其他博客文章是独一无二的,因此请随时环顾四周。
我们要谈论的是
- 因此,如果您像我一样,开始通过测试来强化您的应用程序。您要测试的第一批层之一是repository/data layer。如果将Firebase身份验证集成到代码中,则可能会遇到一些问题。
意大利面条方式(不好)
- 这种实现火箱的方式是您在意大利面代码中通常会看到的(我的代码库中的一个版本这样做):
class AuthRepositoryImpl(
private val auth: FirebaseAuth = Firebase.auth
): AuthRepository {
override fun authRegister(email: String, password: String): Flow<Response<Boolean>> = callbackFlow {
try{
trySend(Response.Loading)
auth.createUserWithEmailAndPassword(email,password)
.addOnCompleteListener { task ->
if(task.isSuccessful){
trySend(Response.Success(true))
}else{
trySend(Response.Failure(Exception()))
}
}
}catch(e:Exception){
trySend(Response.Failure(Exception()))
}
awaitClose()
}
}
- 美味的意大利面条代码,但有两个主要问题:
1) The exception handling
2) No wrapper class for FirebaseAuth
例外处理
-
处理流程时,一个大不是否否是在流动构建器中进行尝试/捕获块,例如
flow{}
或callbackflow{}
。因为这创造了它产生捕获下游例外的副作用的可能性(当收集{}被称为{}时发生异常) -
通过将尝试/捕获块内部放在流动构建器的内部,我们也违反了Separation of concerns principle。由于该代码现在对收集流量的代码有副作用
-
try/catch块应使用
ONLY!!!
包围收集器,以处理从收集器中提出的异常。如果您使用的是尝试捕获块,则应该像这样:
fun main() ={
try {
upstreamFlow.collect { value ->
if(value <= 4) {
"Collected $value while we expect values below 2"
}else{
throw RuntimeException()
}
}
} catch (e: Throwable) {
println("Caught $e")
}
}
-
注意我们如何无法处理流程中产生的异常。我们在收集期间创建的唯一例外。
-
但是,我们如何在流中捕获异常?该问题的答案是捕获操作员
捕获操作员
- 捕获操作员允许我们
ONLY!!
捕获上游异常(流量的异常)。这意味着我们可以将第一个代码转换为以下方式:
override suspend fun authRegister(email: String, password: String) = callbackFlow {
trySend(Response.Loading)
auth.createUserWithEmailAndPassword(email,password)
.addOnCompleteListener { task ->
if(task.isSuccessful){
trySend(Response.Success(true))
}else{
trySend(Response.Failure(Exception()))
}
}
awaitClose()
}.catch { cause: Throwable->
if(cause is FirebaseAuthWeakPasswordException){
emit(Response.Failure(Exception("Stronger password required")))
}
if(cause is FirebaseAuthInvalidCredentialsException){
emit(Response.Failure(Exception("Invalid credentials")))
}
if(cause is FirebaseAuthUserCollisionException){
emit(Response.Failure(Exception("Email already exists")))
}
else{
emit(Response.Failure(Exception("Error! Please try again")))
}
}
- 从上方的代码块中,您还会注意到我们是
Materializing
我们的例外。这意味着我们将它们转换为代码可以处理的东西。
2)没有firebaseauth 的包装班
- 通常,当我们第一次将firebase添加到存储库层时,它看起来像这样:
class AuthRepositoryImpl(
private val auth: FirebaseAuth = Firebase.auth
): AuthRepository {
}
-
注意我们刚刚刚刚制成的
FirebaseAuth
。这不仅使测试更难,而且还使代码严格依赖于FirebaseAuth
及其所有方法。这意味着,如果我们想从一个身份验证服务切换到另一种身份验证服务,我们将不得不重写所有代码以处理新的身份验证服务(这不有趣)。- 为了解决这个问题,我们必须为Firebaseauth创建包装班。实际上,这只是我们试图尝试program to the interface not the implementation。
创建接口
- 我们要做的第一件事是创建一个接口:
interface AuthenticationSource {
fun authRegister(email: String, password: String, username: String): Flow<Response<Boolean>>
}
- 然后我们可以使用此界面来创建包装器类
创建包装器类:
class FireBaseAuthentication : AuthenticationSource {
private val auth: FirebaseAuth = Firebase.auth
override fun authRegister(email: String, password: String): Flow<Response<Boolean>> = callbackFlow {
trySend(Response.Loading)
auth.createUserWithEmailAndPassword(email,password)
.addOnCompleteListener { task ->
if(task.isSuccessful){
trySend(Response.Success(true))
}else{
trySend(Response.Failure(Exception()))
}
}
awaitClose()
}
-
请注意,此包装班如何在内部打电话给
FirebaseAuth
,这是可以的,因为我们将通过authRegister
方法进行。 -
您也可能注意到我们在这里没有处理异常,因为我决定在存储库层的内部执行此操作。不确定这是否是正确的电话,但我现在坚持下去。我还相信,存储库层是您应该实现任何Intermediate flow operators
的地方
用包装器类替换硬编码的实现
class AuthRepositoryImpl(
private val auth:AuthenticationSource = FireBaseAuthentication()
): AuthRepository {
override fun registerUser(email:String,password: String): Flow<Response<Boolean>> {
val items = auth.authRegister(email, password)
.catch { cause: Throwable->
if(cause is FirebaseAuthWeakPasswordException){
emit(Response.Failure(Exception("Stronger password required")))
}
if(cause is FirebaseAuthInvalidCredentialsException){
emit(Response.Failure(Exception("Invalid credentials")))
}
if(cause is FirebaseAuthUserCollisionException){
emit(Response.Failure(Exception("Email already exists")))
}
else{
emit(Response.Failure(Exception("Error! Please try again")))
}
}
return items
}
}
- 由于上面的代码现在通过
AuthenticationSource
接口,因此它是灵活的,我们可以轻松地将其与实现此接口的新身份验证源交换。此外,此代码尚未变得更加100%友好。
结论
- 感谢您抽出宝贵的时间阅读我的博客文章。如果您有任何疑问或疑虑,请在下面发表评论或在Twitter上与我联系。