Search code examples
androidin-app-billing

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

}


Solution

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