To increment the quantity in the Realtime Database, I can simply use:
override fun incrementQuantity() = flow {
try {
heroIdRef.update("quantity", FieldValue.increment(1)).await()
emit(Result.Success(true))
} catch (e: Exception) {
emit(Result.Failure(e))
}
}
And works well. The problem comes when I need to check the quantity
first and then increment. The above solution doesn't help, so I need to use transactions. Here is what I've tried:
override fun incrementQuantity() {
val transaction = object : Transaction.Handler {
override fun doTransaction(mutableData: MutableData): Transaction.Result {
val quantity = mutableData.getValue(Long::class.java) ?: return Transaction.success(mutableData)
if (quantity == 1L) {
mutableData.value = null
} else {
mutableData.value = quantity 1
}
return Transaction.success(mutableData)
}
override fun onComplete(error: DatabaseError?, committed: Boolean, data: DataSnapshot?) {
throw error.toException()
}
}
heroIdRef.runTransaction(transaction)
}
And works, but I cannot see how use Kotlin Coroutines. I just want to call await() and return a flow, as in the first example. How can I do that?
CodePudding user response:
This isn't really a correct use of Flow in either case, because a Flow is for retrieving multiple things in series, but this only returns one thing. It is more suited to a suspend function that directly returns that thing.
Anyway, I'm not a Firebase user, so I might make a mistake here. It looks like their Kotlin library doesn't provide a suspend function version of running a transaction. You could write your own like this. It's slightly messy because the completion callback has three parameters, so we must either return a tuple or a wrapper class.
data class CompletedTransaction(val error: DatabaseError?, val committed: Boolean, val data: DataSnapshot?)
suspend fun DatabaseReference.runTransaction(
fireLocalEvents: Boolean = true,
action: (MutableData)->Transaction.Result
): CompletedTransaction = suspendCoroutine { continuation ->
val handler = object : Transaction.Handler {
override fun doTransaction(mutableData: MutableData): Transaction.Result =
action(mutableData)
override fun onComplete(error: DatabaseError?, committed: Boolean, data: DataSnapshot?) =
continuation.resume(CompletedTransaction(error, committed, data))
}
runTransaction(handler, fireLocalEvents)
}
Then you could do:
override suspend fun incrementQuantity(): Result {
val transaction = heroIdRef.runTransaction { mutableData ->
val quantity = mutableData.getValue(Long::class.java) ?: return@runTransaction Transaction.success(mutableData)
mutableData.value = if (quantity == 1L) null else quantity 1
Transaction.success(mutableData)
}
val failure = transaction.error?.toException()?.let { Result.Failure(it) }
return failure ?: Result.Success(true)
}
If you are required to use a Flow for some reason, it would be like:
override suspend fun incrementQuantity() = flow {
val transaction = heroIdRef.runTransaction { mutableData ->
val quantity = mutableData.getValue(Long::class.java) ?: return@runTransaction Transaction.success(mutableData)
mutableData.value = if (quantity == 1L) null else quantity 1
Transaction.success(mutableData)
}
val failure = transaction.error?.toException()?.let { Result.Failure(it) }
emit(failure ?: Result.Success(true))
}