Чтобы увеличить количество в базе данных реального времени, я могу просто использовать:
override fun incrementQuantity() = flow {
try {
heroIdRef.update("quantity", FieldValue.increment(1)).await()
emit(Result.Success(true))
} catch (e: Exception) {
emit(Result.Failure(e))
}
}
И работает хорошо. Проблема возникает, когда мне нужно сначала проверить quantity, а затем увеличить. Приведенное выше решение не помогает, поэтому мне нужно использовать транзакции. Вот что я пробовал:
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)
}
И работает, но я не вижу, как использовать Kotlin Coroutines. Я просто хочу вызвать await() и вернуть поток, как в первом примере. Как я могу это сделать?
В любом случае это не совсем правильное использование Flow, потому что Flow предназначен для последовательного извлечения нескольких элементов, а возвращает только один элемент. Это больше подходит для функции приостановки, которая напрямую возвращает эту вещь.
В любом случае, я не пользователь Firebase, поэтому могу ошибиться. Похоже, их библиотека Kotlin не предоставляет версию функции приостановки выполнения транзакции. Вы можете написать свой собственный, как это. Это немного запутанно, потому что обратный вызов завершения имеет три параметра, поэтому мы должны либо вернуть кортеж, либо класс-оболочку.
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)
}
Тогда вы можете сделать:
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)
}
Если вам по какой-то причине необходимо использовать Flow, это будет выглядеть так:
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))
}
В этом случае это более разумно использовать, хотя я думаю, что это было бы проще сделать на принимающей стороне, чтобы вам не приходилось иметь дело с потоком. Например, вызывая последовательно: showLoadingUi(); viewModel.incrementQuantity(); removeLoadingUi();
Спасибо. Я только что добавил новый вопрос относительно этой новой путаницы. Если у вас есть время, может быть, вы можете взглянуть.
Я только что поднял награду за этот вопрос, потому что получил только один неудовлетворительный ответ. Как вы думаете, вы можете помочь мне с этим? Спасибо.
Извините, у меня пока очень мало опыта работы с Compose, поэтому я не являюсь хорошим источником рекомендаций по этому вопросу. С точки зрения соглашений о сопрограммах, я думаю, что это нормально в любом случае. Если бы вы использовали старый императивный пользовательский интерфейс, я думаю, что без потоков было бы проще, как описано в моем комментарии выше. Но в Compose может быть проще использовать поток с collectAsState(). @ДжоанП.
Привет. Может быть, вы можете помочь мне с этим тоже.
Еще один очень быстрый вопрос. Если бы у меня было состояние Result.Loading, а внутри функции
incrementQuantity()я должен был использоватьemit(Result.Loading)перед получением любого другого результата, имело бы смысл использовать поток?