将火池集成到您的Android应用程序中的正确方法
#kotlin #android #mobile #tristan

目录

  1. What we are talking about
  2. Spaghetti code example
  3. Exception handling
  4. Wrapper class

我在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及其所有方法。这意味着,如果我们想从一个身份验证服务切换到另一种身份验证服务,我们将不得不重写所有代码以处理新的身份验证服务(这不有趣)。

创建接口

  • 我们要做的第一件事是创建一个接口:
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上与我联系。