Как увеличить счетчик в базе данных реального времени, используя транзакции с Kotlin Coroutines?

Чтобы увеличить количество в базе данных реального времени, я могу просто использовать:

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() и вернуть поток, как в первом примере. Как я могу это сделать?

1
0
70
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

В любом случае это не совсем правильное использование 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))
}

Еще один очень быстрый вопрос. Если бы у меня было состояние Result.Loading, а внутри функции incrementQuantity() я должен был использовать emit(Result.Loading) перед получением любого другого результата, имело бы смысл использовать поток?

Joan P. 16.09.2022 11:00

В этом случае это более разумно использовать, хотя я думаю, что это было бы проще сделать на принимающей стороне, чтобы вам не приходилось иметь дело с потоком. Например, вызывая последовательно: showLoadingUi(); viewModel.incrementQuantity(); removeLoadingUi();

Tenfour04 16.09.2022 14:49

Спасибо. Я только что добавил новый вопрос относительно этой новой путаницы. Если у вас есть время, может быть, вы можете взглянуть.

Joan P. 17.09.2022 12:40

Я только что поднял награду за этот вопрос, потому что получил только один неудовлетворительный ответ. Как вы думаете, вы можете помочь мне с этим? Спасибо.

Joan P. 19.09.2022 12:46

Извините, у меня пока очень мало опыта работы с Compose, поэтому я не являюсь хорошим источником рекомендаций по этому вопросу. С точки зрения соглашений о сопрограммах, я думаю, что это нормально в любом случае. Если бы вы использовали старый императивный пользовательский интерфейс, я думаю, что без потоков было бы проще, как описано в моем комментарии выше. Но в Compose может быть проще использовать поток с collectAsState(). @ДжоанП.

Tenfour04 19.09.2022 14:53

Привет. Может быть, вы можете помочь мне с этим тоже.

Joan P. 28.09.2022 13:59

Другие вопросы по теме