0

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.
    }
}

}

1 Answer 1

1

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".

4
  • Very interesting, did you find the issue happened randomly? Sometimes everything is okay for me then some days it times out
    – alfietap
    Mar 23 at 10:37
  • 1
    Yes it happens randomly, it happens in my app when calling any call to the billing lib, not only getSkuDetails. Now I tried the v4.0.0 but it has the same issue. the v3.0.2 works perfectly.
    – Mahmoud
    Mar 23 at 10:56
  • I also found something that temporarily fixes.. If you clear cache on the google play app on your phone, the billing client connects okay. Must be something to do with your phones overall connection to the google play store.
    – alfietap
    2 days ago
  • I'd tried that as well, but more than that I cleared all data no only the cache. But that doesn't fix the problem.
    – Mahmoud
    2 days ago

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.