I'm initializing a member variable to null.
Later in the process, this member variable is assigned a value, before starting the payment flow, ie before invoking billingClient.launchBillingFlow(context, billingFlowParams)
When arriving in the PurchasesUpdatedListener
callback, the member variable is null again.
I first thought it was a scope problem, so I made an inner class MyPurchasesUpdatedListener : PurchasesUpdatedListener
to make sure that the containing-class-member-variable is accessible in the contained-class, but it makes no difference.
So I suppose it's something totally different, like lifecycle, different threads/activities implied by the billing flow in the middle of my own flow...
I tried to strip my code to the smallest useful portion of it:
class GooglePlayPay(val context:NativeAppActivity) {
...
var purchaseCallback: ((Boolean)->Unit)?=null
...
private val purchasesUpdatedListener = MyPurchasesUpdatedListener()
inner class MyPurchasesUpdatedListener : PurchasesUpdatedListener {
override fun onPurchasesUpdated(billingResult:BillingResult, purchases:List<Purchase>?){
Log.d("fp_purchUpdatedListener","PurchaseCallback: ${purchaseCallback.toString()}")
// ===> in catlog: PurchaseCallback: null
if (billingResult.responseCode == BillingClient.BillingResponseCode.OK && purchases != null) {
for (purchase in purchases) {
handlePurchase(purchase)
}
} else if (billingResult.responseCode == BillingClient.BillingResponseCode.USER_CANCELED) {
// Handle an error caused by a user cancelling the purchase flow.
} else {
// Handle any other error codes.
}
}
}
fun handlePurchase(purchase: Purchase){
...
Log.d("fp_handlePurchase","PurchaseCallback: ${purchaseCallback.toString()}")
// ===> in catlog: PurchaseCallback: null
...
}
fun purchaseProduct(productDetails: ProductDetails, cb:(Boolean)->Unit) {
purchaseCallback = cb
Log.d("fp_purchaseProduct","2.PurchaseCallback: ${purchaseCallback.toString()}")
// ===> in catlog: PurchaseCallback: Function1<java.lang.Boolean, kotlin.Unit>
// Launch the billing flow
billingClient.launchBillingFlow(context, billingFlowParams)
}
I don't know if it's important: this purchaseProduct()
is called from within a @JavascriptInterface
function.
[EDIT] Here an added extract of the calling class:
class GooglePlayPayJSInterface(val context: WebAppActivity) {
@JavascriptInterface fun getProduct(str:String) {
GooglePlayPay(context).getProduct(str, ::getProductCallback)
}
fun getProductCallback(details: ProductDetails){
context.postToJs("""{"data":{"productId":"${details.getProductId()}", "name": "${details.getName()}", "price":"${details.getOneTimePurchaseOfferDetails()?.getFormattedPrice()?:"n/a"}"}}""")
}
@JavascriptInterface fun purchase(str:String) {
GooglePlayPay(context).purchase(str, ::purchaseCallback} )
}
fun purchaseCallback(success:Boolean){
if(success){ context.postToJs("""{"data":{"result":"success"}}""") }
else { context.postToJs("""{"errcode":"Purchase failed"}""") }
}
...
}
But I don't think there's a problem here: getProduct()
and its getProductCallback()
work like a charm.
The only difference I see between getProduct
+callback and purchase
+callback is that:
purchase
gets interrupted by Android's billing flow,getProduct
just calls API functions (billingClient.startConnection()
, billingClient.queryProductDetailsAsync()
) then calls the previously stored-as-a-member-var callback.UPDATE
Please try to refactor your code so that GooglePlayPay object is created and stored as a field in GooglePlayPayJSInterface and that field is used across GooglePlayPayJSInterface.
ORIGINAL ANSWER
Seems like you cannot keep objects from javascript. Instead of saving the callback to a local variable, try to load the target page in a webview using the purchase token.
fun handlePurchase(purchase: Purchase){
...
Log.d("fp_handlePurchase","PurchaseCallback: ${purchaseCallback.toString()}")
// ===> in catlog: PurchaseCallback: null
...
webView.loadUrl("my_result_page?purchase_token=...")
}