androidin-app-billingandroid-billing

A member variable set before billingClient.launchBillingFlow is lost when PurchasesUpdatedListener callback is triggered


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:


Solution

  • 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=...")
            }