Firebase: How to check if document write was successful

1.4k views Asked by At

I want to check if my database write was successful in order to show the user an error message.

My current approach doesn't work as it says "Type mismatch, required Unit found EmailStatus"

Current approach

class EmailRepositoryImpl : EmailRepository {
    private val db = Firebase.firestore

    override fun sendEmail(email: Email): EmailStatus<Nothing> {
        db.collection("emails").document().set(email).addOnCompleteListener {
            if (it.isSuccessful) return@addOnCompleteListener EmailStatus.Success<Nothing>
            if (it.isCanceled) return@addOnCompleteListener EmailStatus.Error(it.exception!!)
        }
    }
}

Status Sealed Class

sealed class EmailStatus<out T> {
    data class Success<out T>(val data: T) : EmailStatus<T>()
    data class Error(val exception: Exception) : EmailStatus<Nothing>()
}

Is it even possible to write something like this? As far as I know there is a generic firebase error type but I didn't found anything related to kotlin or android...

I appreciate every help, thank you

Edit

I've tried getting my document, but I am just getting null: (When I use the listener approach, everything works fine)

Interface

interface EmailRepository {
    suspend fun getEmail(): Flow<EmailEntity?>
}

Interface Implementation

override suspend fun getEmail(): Flow<EmailEntity?> = flow {
    val result = db.collection("emailprice").document("Email").get().await()
    emit(result.toObject<EmailEntity>())
}

ViewModel

private val emailEntity = liveData<EmailEntity?>(Dispatchers.IO) {
    emailRepository.getCalibratePrice()
}
2

There are 2 answers

0
Andrew On BEST ANSWER

Okay, this is the final solution, thanks to @Gastón Saillén and @Doug Stevenson :

EmailRepository

interface EmailRepository {
    fun sendEmail(email: Email): Flow<EmailStatus<Unit>>
}

EmailRepository Implementation

class EmailRepositoryImpl @Inject constructor(
    private val db: FirebaseFirestore
) : EmailRepository {

    override fun sendEmail(email: Email)= flow<EmailStatus<Unit>> {
        db.collection("emails").add(email).await()
        emit(EmailStatus.success(Unit))
    }.catch {
        emit(EmailStatus.failed(it.message.toString()))
    }.flowOn(Dispatchers.Main)

}

ViewModel

fun sendEmail(): LiveData<EmailStatus<Unit>> {
    val newEmail = createEmail()
    return emailRepository.sendEmail(newEmail).asLiveData()
}

Fragment

btn.setOnClickListener {
            viewModel.sendEmail().observe(viewLifecycleOwner) {
                when(it) {
                    is EmailStatus.Success -> {
                        valid = true
                        navigateTo(next, bundleNext)
                        Toast.makeText(requireContext(), "Success", Toast.LENGTH_SHORT).show()
                    }
                    is EmailStatus.Failure -> {
                        valid = false
                        Toast.makeText(requireContext(), "Failed ${it.message}", Toast.LENGTH_SHORT).show()
                    }
                }
            }
}

The only problem I currently have is that my "faield state" does not work like it should.

It should fail, if the user has no internet access. Currently, the write to the db never fails and Firebase just waits till the user has internet access. The problem here is that when I click multiple times, the write is executed multiple times. But I think I have to implement a bit more logic here and the above written code is fine like it currently is.

8
Gastón Saillén On

The problem is that addOnCompleteListener callback does not return anything (Unit) and you are trying to return an EmailStatus from that scope.

You have three approaches:

  1. Create an interface that will populate the value and return that EmailStatus down to your caller layer
  2. Use Coroutines to suspend this function when the async call to firebase is done and then return that value
  3. Use Flow to offer the data when it's ready to process

I think the easiest way to do this one shot operation is to use Coroutines; I have written an article about that.