alfietap
alfietap

Reputation: 2279

Google play billing service timeout

I set up Google Play billing for in app purchases in my app yesterday, everything was working as expected and I could query the products, select them and purchase.

Today this has changed, it suddenly does not work and is returning these error messages:

W/BillingClient: Async task is taking too long, cancel it!
D/billingSetupError: Timeout communicating with service

The error response code coming back is -3. Below is my code for connecting to the service and querying the sku details (everything is in my viewmodel as im using a full compose app so no access to activity or fragments where i am)

@HiltViewModel
class StoreViewModel @Inject constructor(
private val billingClientBuilder: BillingClient.Builder
) : ViewModel(), StoreEvents, BillingClientStateListener, PurchasesUpdatedListener {

companion object {
    const val TAG = "StoreViewModel"
}

private val _state = MutableStateFlow(StoreScreenState.default)
val state: StateFlow<StoreScreenState> = _state

private val _purchaseTokens = MutableStateFlow(emptyList<String?>())
val purchaseTokens: StateFlow<List<String?>> = _purchaseTokens

private val availableSkus = listOf(
    "comic_pack",
    "elements_pack"
)

private val BACKOFF_IN_MILLIS = 500
private var backoffAttempts = 0

private var billingClient = billingClientBuilder
    .setListener(this)
    .enablePendingPurchases()
    .build()

init {

    billingClient.startConnection(this)

    viewModelScope.launch {

        state.collect {
            it.skuDetails.forEach {
                Log.d(TAG, it.toString())
            }

        }
    }
}

private suspend fun queryOneTimeProducts(): List<SkuDetails> = withContext(Dispatchers.IO) {
    if (!billingClient.isReady) {
        Log.e("TAG", "queryOneTimeProducts: BillingClient is not ready")
        return@withContext emptyList()
    }

    val availableSkus = SkuDetailsParams
        .newBuilder()
        .setSkusList(availableSkus)
        .setType(BillingClient.SkuType.INAPP)
        .build()


    return@withContext runCatching {
        val result = billingClient.querySkuDetails(availableSkus)
        result.skuDetailsList ?: emptyList()
    }.getOrNull() ?: emptyList()
}

private suspend fun handlePurchase(purchase: Purchase) {

    Log.d("purchaseComplete", purchase.toString())

    val consumeParams = ConsumeParams.newBuilder()
        .setPurchaseToken(purchase.purchaseToken)
        .build()

    withContext(Dispatchers.IO) {
        billingClient.consumePurchase(consumeParams)
    }

}

private fun updateLoading(isLoading: Boolean) {
    _state.value = state.value.copy(
        loading = isLoading
    )
}

private fun updateProducts(products: List<SkuDetails>) {
    _state.value = state.value.copy(
        skuDetails = products
    )
}

override fun StoreItemClicked(activity: Activity, name: String) {

    val selectedSku = state.value.skuDetails.find { it.title == name }

    val flowParams = selectedSku?.let {
        it
        BillingFlowParams.newBuilder()
            .setSkuDetails(it)
            .build()
    }

    flowParams?.let { billingClient.launchBillingFlow(activity, it) }
}


override fun onBillingServiceDisconnected() {
    viewModelScope.launch {
        if (backoffAttempts == 0) {
            delay(BACKOFF_IN_MILLIS.toLong())
        } else {
            delay(backoffAttempts * BACKOFF_IN_MILLIS.toLong())
        }

        if (!billingClient.isReady) {
            billingClient.startConnection(this@StoreViewModel)
            backoffAttempts++
        }
    }
}

override fun onBillingSetupFinished(billingResult: BillingResult) {
    backoffAttempts = 0

    Log.d("responsecode", billingResult.responseCode.toString())

    if (billingResult.responseCode != 0) {

        if (billingResult.responseCode == 2 || billingResult.responseCode == 3) {

            viewModelScope.launch {

                Log.d("billingSetupError", billingResult.debugMessage)

            }
        } else {
            viewModelScope.launch {

                Log.d("billingSetupError", billingResult.debugMessage)

            }
        }

    } else {

        viewModelScope.launch {

            val products = queryOneTimeProducts()

            Log.d("PRODUCTS", products.toString())

            updateProducts(products)
            updateLoading(false)
        }
    }
}

override fun onPurchasesUpdated(result: BillingResult, purchases: MutableList<Purchase>?) {
    if (result.responseCode == BillingClient.BillingResponseCode.OK && purchases != null) {
        for (purchase in purchases) {
            viewModelScope.launch {
                Log.d("purchase", purchase.toString())
                handlePurchase(purchase)
            }
        }
    } else if (result.responseCode == BillingClient.BillingResponseCode.USER_CANCELED) {

    } else {
        // Handle any other error codes.
    }
}

}

Upvotes: 1

Views: 1408

Answers (2)

stefan222
stefan222

Reputation: 679

Had the same problem after the update from version 3.0.2 to 5.0.0. Seems that the timeout occurs when I query for subscriptions when the app has no subscriptions at all.

My advice is to skip the query for product types that don't have any configured products, i.e. don't query for in-app products when the app doesn't have in-app products, and don't query for subscriptions when the app doesn't have subscriptions.

Upvotes: 0

Mahmoud
Mahmoud

Reputation: 2893

I have the same issue when using the v4.1.0. I used the version 3.0.2 instead and the issue is gone. This could be a temp solution, since it's not recommended to use a previous version.

Maybe a version between 3.0.2 and 4.1.0 could fix it as well. In the release notes here https://developer.android.com/google/play/billing/release-notes didn't find anything related to the "timeout issue".

Upvotes: 1

Related Questions