androidkotlinin-app-purchaseandroid-inapp-purchase

null cannot be cast to non-null type kotlin.collections.List<com.android.billingclient.api.Purchase>


Most of my app users don't experience any crashes. However, some of my app users are getting this fatal exception:

Fatal Exception: java.lang.NullPointerException: null cannot be cast to non-null type kotlin.collections.List<com.android.billingclient.api.Purchase>
       at com.company.appname.ActivityClass$loadAllSKUs$1.onSkuDetailsResponse(ActivityClass.java:10)
       at com.android.billingclient.api.zzj.run(zzj.java:7)
       at android.os.Handler.handleCallback(Handler.java:873)
       at android.os.Handler.dispatchMessage(Handler.java:99)
       at android.os.Looper.loop(Looper.java:226)
       at android.app.ActivityThread.main(ActivityThread.java:7178)
       at java.lang.reflect.Method.invoke(Method.java)
       at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:503)
       at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:942)

Here is my code. I use it to see if a user has already bought an in-app purchase. If he didn't, launch the billing flow.

  private lateinit var billingClient: BillingClient
  private val skuList = listOf("product")
  private boolean isOwned = false

  private fun loadAllSKUs() {
    if (billingClient.isReady)
    {
      val params = SkuDetailsParams.newBuilder()
              .setSkusList(skuList)
              .setType(BillingClient.SkuType.INAPP)
              .build()
      billingClient.querySkuDetailsAsync(params) { billingResult, skuDetailsList ->
        if (skuDetailsList != null)
        {
          if (billingResult.responseCode === BillingClient.BillingResponseCode.OK && !skuDetailsList.isEmpty())
          {
            for (skuDetailsObject in skuDetailsList)
            {
              val skuDetails = skuDetailsObject
              if (skuDetails.sku.equals("product"))
              {
                val result: Purchase.PurchasesResult = billingClient.queryPurchases(BillingClient.SkuType.INAPP)
                val purchases: List<Purchase> = result.getPurchasesList() as List<Purchase>

                for (purchase in purchases)
                {
                  val thisSKU = purchase.sku
                  if (thisSKU == "product")
                  {
                    isOwned = true;
                    // System.out.println("OWNED")
                  }
                }
               


if(isOwned == false){
                    val billingFlowParams = BillingFlowParams
                            .newBuilder()
                            .setSkuDetails(skuDetails)
                            .build()
                    billingClient.launchBillingFlow(this@ActivityClass, billingFlowParams)
}
              }
            }
          }
        }
      }
    }
  }

Please tell me how to fix this, thanks. This question gets the same error but a fix has not been found apparently.


Solution

  • Kotlin is a lot more expressive than a lot of cascading if...

    Also, Kollin handles nullity at compile time so IDE should notify you if is null or not.

    I tried to rewrite your code because is very undebuggable. I honestly don't know if it works and if it does the same things (it should but as i saidall those if are difficult to understand) but I find it more readable and debuggable.

    Remember that in order to remove all if !=null you can use ?. operator

    Hope it helps you to understand Kotlin expressivity

    private fun loadAllSKUs() {
        billingClient.takeIf{it.isReady}
        ?.let {
        val params = SkuDetailsParams.newBuilder()
                .setSkusList(skuList)
                .setType(BillingClient.SkuType.INAPP)
                .build()
        billingClient.querySkuDetailsAsync(params) { billingResult, skuDetailsList ->
        skuDetailsList
            ?.takeIf{ billingResult.responseCode === BillingClient.BillingResponseCode.OK && skuDetailsList.isNotEmpty() }
            ?.filter {it.sku.equals("product")}
            ?.forEach { skuDetails -> 
                val result: Purchase.PurchasesResult = billingClient.queryPurchases(BillingClient.SkuType.INAPP)
                val purchases: List<Purchase>? = result.getPurchasesList() as? List<Purchase>
                val isOwned = purchases?.any { it.sku == "product" }?:false
                
                if(!isOwned){
                    val billingFlowParams = BillingFlowParams
                            .newBuilder()
                            .setSkuDetails(skuDetails)
                            .build()
                    billingClient.launchBillingFlow(skuDetails@ActivityClass, billingFlowParams)
                    }
                }
            }
        }