Closed mihanovak1024 closed 2 weeks ago
Hi @mihanovak1024,
Thanks for reaching out! You might be onto something with your assumption — it definitely seems related to GC action. We’ll need to investigate further to identify a proper solution to prevent this exception from occurring.
Could you provide a minimal reproducible example? That would be incredibly helpful. If that’s not possible, any details on how you’re using this method in your coroutine would be greatly appreciated.
Hey @SpertsyanKM
Could you provide a minimal reproducible example?
I don't have any steps to reproduce, unfortunately.
I tried crashing the coroutine in which Qonversion.shared.products
is running, but went up with a different (non-qonversion) crash. This proves my assumption above to be wrong.
I also tried resuming the coroutine immediately after Qonversion.shared.products
call, but I don't get the qonversion crash either.
If that’s not possible, any details on how you’re using this method in your coroutine would be greatly appreciated.
I'm actually doing nothing fancy above this, I'm just running it in a simple IO Coroutine:
fun updateProducts() {
CoroutineScope(Dispatchers.IO).launch {
val transactions = billingProvider.getExistingTransactions()
withContext(Dispatchers.Main) {
sendExistingTransactionsToGame(transactions)
}
val products = billingProvider.getProducts()
withContext(Dispatchers.Main) {
products.forEach { sendPriceInfoToGame(it.priceInfo) }
}
val purchases = billingProvider.getPurchases()
withContext(Dispatchers.Main) {
purchases.forEach { sendSuccessToGame(it, true) }
}
}
}
Existing transactions (a call prior to getProducts()
) are fetched through Entitlements:
suspend fun getExistingTransactions(): List<String> = suspendCoroutine { cont ->
Logger.debug(TAG, "getExistingTransactions")
try {
Qonversion.shared.checkEntitlements(object : QonversionEntitlementsCallback {
override fun onSuccess(entitlements: Map<String, QEntitlement>) {
Logger.debug(TAG, "getExistingTransactions - entitlements size = ${entitlements.size}")
val transactions = entitlements.values.flatMap { it.transactions.map { x -> x.transactionId } }
cont.resume(transactions)
}
override fun onError(error: QonversionError) {
Logger.warning(TAG, "getExistingTransactions - entitlements onError = $error")
triggerEvent("existing-transactions", error.code.name)
cont.resume(listOf())
}
})
} catch (e: Exception) {
Logger.error(TAG, "getExistingTransactions", e)
triggerEvent("existing-transactions-exception", e.message)
cont.resume(listOf())
}
}
I don't really have anything else I could give you :(
Thanks for this useful information! Just to clarify, are you experiencing crashes only during the products
call, or is it also happening with the checkEntitlements
call?
Sorry I can't be a bit more helpful, but have a really hard time thinking how to reproduce this 😅
Only for products
call, even though checkEntitlements
always happens before products
.
products
call waits for checkEntitlements
to complete before starting.
Okay, thank you! We'll try to do something with it.
We’ve just released version 8.2.2, which may resolve this issue. Please upgrade your SDK version.
I’ll close this issue for now, but feel free to reopen it if the problem continues. We’ll also monitor our logs closely to see if it remains a concern.
Hey team (@SpertsyanKM ), we just went live with one of our apps that utilises Qonversion for billing.
We're using latest 8.2.1 version.
We noticed the following
NullPointerException
occuring:and
We have the following implementation for
Qonversion.shared.products
:I have been digging into the Qonversion open source code and am a bit confused how the
QonversionProductsCallback
can becomenull
.Is it possible that our coroutine (when completed) somehow makes everything null, while Qonversion async code is still running? If this is the case, should we just catch this issue with
CoroutineExceptionHandler
and ignore it since our coroutine probably already completed?Thanks a lot!