The proper way to integrate Firebase into your Android app
Tags: android, mobile, kotlin, tristan
Published: 2023-07-01 15:08:35

### Table of contents
1. [What we are talking about](#what)
2. [Spaghetti code example](#spaghetti)
3. [Exception handling](#exception)
4. [Wrapper class](#wrapper)

### My app on the Google Playstore
- [HERE](https://play.google.com/store/apps/details?id=com.elliottsoftware.calftracker)

### GitHub code <a name="github"></a>
- [HERE](https://github.com/thePlebDev/CalfTracker)


### Introduction <a name="intro"></a>
- This series will be an informal demonstration of any problems I face or any observations I make while developing Android apps. Each blog post in this series will be unique and separate from the others, so feel free to look around.


### What we are going to talk about <a name="what"></a>
- So if you are like me and starting to fortify your application with tests. One of the first layers you will want to test is the [repository/data layer](https://developer.android.com/topic/architecture/data-layer). If you have FireBase Authentication integrated into your code, you might run into a bit of a problem.


### The spaghetti way (not good) <a name="spaghetti"></a>
- This way of implementing FireBase is what you will typically see in spaghetti code (version one of my code base does this):
```
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()
    }
}

```

- yummy spaghetti code but there are two main problems:

`1) The exception handling`
`2) No wrapper class for FirebaseAuth `

### Exception Handling <a name="exception"></a>
- When dealing with flows a big no no is having a try/catch block inside a flow builder, like `flow{}` or `callbackflow{}`. As this creates the possibility of it producing a side effect of catching downstream exceptions(exceptions happening when collect{} is called)

- By having the try/catch block inside of a flow builder, we are also violating the [Separation of concerns principle](https://en.wikipedia.org/wiki/Separation_of_concerns). As the code now has a side effect on the code that collects the flow

- The try/catch block should `ONLY!!!` be used to surround the collector to handle exceptions raised from the collector its self. If you are using the try catch block it should look like this:

```


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")
    }
}

```
- Notice how we are not handling exceptions that are produced inside the flow. Only exceptions that we created during collection.

- But then how do we catch exceptions inside a flow? The answer to that question is the catch operator

### Catch operator
- The catch operator allows us to `ONLY!!` catch upstream exceptions(exceptions from the flow). Which means we can transform our first block of code to this:
```
   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")))
        }
    }

```
- From the code block above you will also notice that we are `Materializing` our exceptions. Which means we are transforming them into something our code can handle.



### 2) No wrapper class for FirebaseAuth <a name="wrapper"></a>
- Typically when we first add FireBase to our repository layer, it will look something like this:
```
class AuthRepositoryImpl(
    private val auth: FirebaseAuth = Firebase.auth
): AuthRepository {

}

```
 - Notice how we have just hardcoded `FirebaseAuth`. This not only makes it harder to test but it also makes the code strictly dependant on `FirebaseAuth` and all of its methods. Which means if we want to switch from one authentication service to another, we would have to rewrite all of our code to deal with the new authentication service(which is not fun).

- To get around this we have to create a wrapper class for FirebaseAuth. Which is really just us trying to [program to the interface not the implementation](https://stackoverflow.com/questions/2697783/what-does-program-to-interfaces-not-implementations-mean).

### Create the interface
- The first thing we have to do is to create an interface:
```
interface AuthenticationSource {

fun authRegister(email: String, password: String, username: String): Flow<Response<Boolean>>

}

```
- Then we can use this interface to create the wrapper class

### Create the wrapper class:
```
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()
    }

```
- Notice how this wrapper class still internally calls to `FirebaseAuth`, which is ok because we will doing so through the `authRegister` method.

- You may also noticed that we are not handling exceptions here, as I have decided to do that inside of the repository layer. Not sure if this is the right call, but I am sticking to it now. I also believe that the repository layer is where you should implement any [Intermediate flow operators](https://kotlinlang.org/docs/flow.html#intermediate-flow-operators)

### Replace the hard coded implementation with the wrapper class

```
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
    }


}

```

- Since our code above now goes through the `AuthenticationSource` interface, it is now flexible and we can easily swap it out with a new authentication source that implements this interface. Also this code has not become 100% more test friendly.

### Conclusion
- Thank you for taking the time out of your day to read this blog post of mine. If you have any questions or concerns please comment below or reach out to me on [Twitter](https://twitter.com/AndroidTristan).