Issue
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".
Answered By - Mahmoud
Answer Checked By - Katrina (JavaFixing Volunteer)