Skip to content

Commit

Permalink
feat: switch to callbacks
Browse files Browse the repository at this point in the history
  • Loading branch information
ch4rl3x committed Jul 16, 2024
1 parent 119c000 commit 1afa3c4
Show file tree
Hide file tree
Showing 3 changed files with 56 additions and 96 deletions.
2 changes: 1 addition & 1 deletion billing/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ apply from: '../_ktlint.gradle'
ext {
PUBLISH_GROUP_ID = 'de.charlex.billing'
PUBLISH_VERSION = '7.0.0'
PUBLISH_ARTIFACT_ID = 'billing-suspend'
PUBLISH_ARTIFACT_ID = 'billing'
}

apply from: '../_publish.gradle'
Expand Down
49 changes: 9 additions & 40 deletions billing/src/main/java/de/charlex/billing/BillingClientExt.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,49 +5,18 @@ import com.android.billingclient.api.BillingClient
import com.android.billingclient.api.BillingClientStateListener
import com.android.billingclient.api.BillingResult
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withContext
import kotlin.coroutines.resume

/**
* Starts up BillingClient setup process suspended if necessary.
*
* @return Boolean
*
* true: The billing client is ready. You can query purchases.
*
* false: The billing client is NOT ready or disconnected.
*/
suspend fun BillingClient.startConnectionIfNecessary() = suspendCancellableCoroutine<Boolean> { continuation ->
if (!isReady) {
startConnection(object : BillingClientStateListener {
override fun onBillingServiceDisconnected() {
Log.d("BillingHelper", "The billing client is disconnected.")
if (continuation.isActive) {
continuation.resume(false)
}
}

override fun onBillingSetupFinished(billingResult: BillingResult) {
if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
Log.d("BillingHelper", "The billing client is ready. You can query purchases.")
if (continuation.isActive) {
continuation.resume(true)
}
} else {
Log.d("BillingHelper", "The billing client is NOT ready. ${billingResult.debugMessage}")
if (continuation.isActive) {
continuation.resume(false)
}
}
}
})
} else {
Log.d("BillingHelper", "The billing client is still ready")
if (continuation.isActive) {
continuation.resume(true)
fun BillingClient.onConnected(block: () -> Unit) {
startConnection(object : BillingClientStateListener {
override fun onBillingSetupFinished(billingResult: BillingResult) {
block()
}
override fun onBillingServiceDisconnected() {
// Try to restart the connection on the next request to
// Google Play by calling the startConnection() method.
}
}
})
}

/**
Expand Down
101 changes: 46 additions & 55 deletions billing/src/main/java/de/charlex/billing/BillingHelper.kt
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import com.android.billingclient.api.queryProductDetails
import com.android.billingclient.api.queryPurchaseHistory
import com.android.billingclient.api.queryPurchasesAsync
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext

/**
Expand All @@ -34,7 +35,7 @@ import kotlinx.coroutines.withContext
class BillingHelper(
context: Context,
billingClientBuilder: BillingClient.Builder.() -> Unit,
onPurchasesResult: (purchasesResult: PurchasesResult) -> Unit
onPurchaseResult: (purchasesResult: PurchasesResult) -> Unit
) {

private var billingClient: BillingClient
Expand All @@ -48,7 +49,7 @@ class BillingHelper(
if (billingResult.debugMessage.isNotBlank()) {
Log.d("BillingHelper", "DebugMessage: ${billingResult.debugMessage}")
}
onPurchasesResult(PurchasesResult(billingResult, purchases ?: emptyList()))
onPurchaseResult(PurchasesResult(billingResult, purchases ?: emptyList()))
}
billingClient = builder.build()
}
Expand Down Expand Up @@ -85,18 +86,17 @@ class BillingHelper(
* @return [BillingResult](https://developer.android.com/reference/com/android/billingclient/api/BillingResult) Result of the acknowledge operation
*
*/
suspend fun acknowledgePurchase(purchaseToken: String): BillingResult? = withContext(Dispatchers.IO) {
suspend fun acknowledgePurchase(purchaseToken: String) = withContext(Dispatchers.IO) {
val acknowledgePurchaseParams = AcknowledgePurchaseParams.newBuilder().setPurchaseToken(purchaseToken).build()
return@withContext if (billingClient.startConnectionIfNecessary()) {
val result = billingClient.acknowledgePurchase(acknowledgePurchaseParams)
if (result.responseCode == BillingClient.BillingResponseCode.OK) {
Log.d("BillingHelper", "Purchase acknowleged! (Token: $purchaseToken)")
} else {
Log.d("BillingHelper", "Purchase acknowlege: ${translateBillingResponseCodeToLogString(result.responseCode)}")
billingClient.onConnected {
launch {
val result = billingClient.acknowledgePurchase(acknowledgePurchaseParams)
if (result.responseCode == BillingClient.BillingResponseCode.OK) {
Log.d("BillingHelper", "Purchase acknowleged! (Token: $purchaseToken)")
} else {
Log.d("BillingHelper", "Purchase acknowlege: ${translateBillingResponseCodeToLogString(result.responseCode)}")
}
}
result
} else {
null
}
}

Expand All @@ -115,12 +115,13 @@ class BillingHelper(
*
* @return [BillingResult](https://developer.android.com/reference/com/android/billingclient/api/BillingResult) Result of the consume operation. [ITEM_NOT_OWNED](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.BillingResponseCode#ITEM_NOT_OWNED) if the user does not currently own the item.
*/
suspend fun consume(purchaseToken: String): ConsumeResult? = withContext(Dispatchers.IO) {
suspend fun consume(purchaseToken: String, onConsumeResult: (ConsumeResult) -> Unit) = withContext(Dispatchers.IO) {
val consumeParams = ConsumeParams.newBuilder().setPurchaseToken(purchaseToken).build()
return@withContext if (billingClient.startConnectionIfNecessary()) {
billingClient.consumePurchase(consumeParams)
} else {
null
billingClient.onConnected {
launch {
val result = billingClient.consumePurchase(consumeParams)
onConsumeResult(result)
}
}
}

Expand All @@ -140,17 +141,18 @@ class BillingHelper(
*
* @return [Purchase.PurchasesResult](https://developer.android.com/reference/com/android/billingclient/api/Purchase.PurchasesResult) The Purchase.PurchasesResult containing the list of purchases and the response code ([BillingClient.BillingResponseCode](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.BillingResponseCode))
*/
suspend fun queryPurchases(@ProductType productType: String): PurchasesResult? = withContext(Dispatchers.IO) {
suspend fun queryPurchases(@ProductType productType: String, handler: (PurchasesResult) -> Unit) = withContext(Dispatchers.IO) {
Log.d("BillingHelper", "queryPurchases")
return@withContext if (billingClient.startConnectionIfNecessary()) {
Log.d("BillingHelper", "queryPurchases on billingClient")
billingClient.queryPurchasesAsync(
QueryPurchasesParams.newBuilder()
.setProductType(productType)
.build()
)
} else {
null
billingClient.onConnected {
launch {
val result = billingClient.queryPurchasesAsync(
QueryPurchasesParams.newBuilder()
.setProductType(productType)
.build()
)

handler(result)
}
}
}

Expand Down Expand Up @@ -202,16 +204,6 @@ class BillingHelper(
}
}

@Deprecated(level = DeprecationLevel.ERROR, message = "Use purchase with activity as param", replaceWith = ReplaceWith("purchase(activity, productDetails, offerToken, isOfferPersonalized, validation)"))
suspend fun purchase(
productDetails: ProductDetails,
offerToken: String? = null,
isOfferPersonalized: Boolean = false,
validation: suspend (Purchase) -> Boolean = { true }
): PurchasesResult? {
error("Use purchase with activity as param")
}

/**
* Performs a network query purchase SKUs
*
Expand All @@ -225,9 +217,7 @@ class BillingHelper(
offerToken: String? = null,
isOfferPersonalized: Boolean = false
) {
if (billingClient.startConnectionIfNecessary()) {
// val skuDetails: List<ProductDetails>? = queryProductDetails(sku, type)
// skuDetails?.let {
billingClient.onConnected {
Log.d("BillingHelper", "purchase ${productDetails.name}")

val productDetailsParamsList = listOf(
Expand Down Expand Up @@ -259,8 +249,8 @@ class BillingHelper(
* @param type String Specifies the [BillingClient.SkuType](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.SkuType) of SKU to query.
*
*/
suspend fun queryProductDetails(productId: String, @ProductType productType: String): List<ProductDetails>? = withContext(Dispatchers.IO) {
return@withContext queryProductDetails(
suspend fun queryProductDetails(productId: String, @ProductType productType: String, result: (List<ProductDetails>?) -> Unit) = withContext(Dispatchers.IO) {
queryProductDetails(
productDetailsParams = QueryProductDetailsParams.newBuilder().setProductList(
listOf(
Product.newBuilder().setProductId(
Expand All @@ -269,26 +259,27 @@ class BillingHelper(
productType
).build()
)
).build()
).build(),
result = result
)
}

suspend fun queryProductDetails(productDetailsParams: QueryProductDetailsParams): List<ProductDetails>? = withContext(Dispatchers.IO) {
suspend fun queryProductDetails(productDetailsParams: QueryProductDetailsParams, result: (List<ProductDetails>?) -> Unit) = withContext(Dispatchers.IO) {
// if (productDetailsParams.skusList.size > 1) error("This function accepts only one sku per call")
if (billingClient.startConnectionIfNecessary()) {
val skuDetailsResult = billingClient.queryProductDetails(productDetailsParams)
Log.d("BillingHelper", "Billing Result: ${skuDetailsResult.productDetailsList?.size}")
return@withContext skuDetailsResult.productDetailsList
} else {
return@withContext null
billingClient.onConnected {
launch {
val skuDetailsResult = billingClient.queryProductDetails(productDetailsParams)
Log.d("BillingHelper", "Billing Result: ${skuDetailsResult.productDetailsList?.size}")
result(skuDetailsResult.productDetailsList)
}
}
}

suspend fun queryPurchaseHistory(queryPurchaseHistoryParams: QueryPurchaseHistoryParams): PurchaseHistoryResult? = withContext(Dispatchers.IO) {
if (billingClient.startConnectionIfNecessary()) {
return@withContext billingClient.queryPurchaseHistory(queryPurchaseHistoryParams)
} else {
return@withContext null
suspend fun queryPurchaseHistory(queryPurchaseHistoryParams: QueryPurchaseHistoryParams, result: (PurchaseHistoryResult) -> Unit) = withContext(Dispatchers.IO) {
billingClient.onConnected {
launch {
result(billingClient.queryPurchaseHistory(queryPurchaseHistoryParams))
}
}
}

Expand Down

0 comments on commit 1afa3c4

Please sign in to comment.