简体   繁体   中英

Java - app crash when using in app purchase

So I have experienced some issue with in app purchase in my app.

I have started working on an old (8 months old) project I had earlier, but I am having some issue with app purchase. The app is already live on Play Store, so in app purchase is active.

In build.gradle (:app) I have changed from:

dependencies {
  implementation 'com.android.billingclient:billing:2.2.1'

to this:

dependencies {
  def billing_version = "3.0.0" // In App Purchase
      implementation "com.android.billingclient:billing:$billing_version"

This is the full code of my UpgradeActivity.java:

package com.pinloop.testproj;

import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;

import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;

import com.android.billingclient.api.BillingClient;
import com.android.billingclient.api.BillingClientStateListener;
import com.android.billingclient.api.BillingFlowParams;
import com.android.billingclient.api.BillingResult;
import com.android.billingclient.api.Purchase;
import com.android.billingclient.api.PurchasesUpdatedListener;
import com.android.billingclient.api.SkuDetails;
import com.android.billingclient.api.SkuDetailsParams;
import com.android.billingclient.api.SkuDetailsResponseListener;

import java.util.ArrayList;
import java.util.List;

public class UpgradeActivity extends AppCompatActivity implements PurchasesUpdatedListener {

    private Button upgradeButton;
    private TextView restoreButton;

    private BillingClient billingClient;
    private List skuList = new ArrayList();

    private String sku = "com.pinloop.testproj.pro";

    private SkuDetails mSkuDetails;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_upgrade);

        upgradeButton = (Button) findViewById(R.id.upgradeButton);
        upgradeButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                upgradeAction();
            }
        });

        // Check In App Purchase

        upgradeButton.setEnabled(true);
        skuList.add(sku);


        Boolean pro = getBoolFromPref(this,"myPref", sku);
        if (pro)
        {
            Toast.makeText(this, "you are a premium user", Toast.LENGTH_SHORT).show();
            //upgradeButton.setVisibility(View.INVISIBLE);
            upgradeButton.setText(R.string.you_are_premium);

        }
        else
        {
            Toast.makeText(this, "not pro", Toast.LENGTH_SHORT).show();
            setupBillingClient();
        }
    }

    private void upgradeAction() {
        BillingFlowParams billingFlowParams = BillingFlowParams
                .newBuilder()
                .setSkuDetails(mSkuDetails)
                .build();
        billingClient.launchBillingFlow(UpgradeActivity.this, billingFlowParams);
    }


    // In App Handler:

    private void setupBillingClient() {
        billingClient = BillingClient.newBuilder(this).enablePendingPurchases().setListener(this).build();
        billingClient.startConnection(new BillingClientStateListener(){

            @Override
            public void onBillingSetupFinished(BillingResult billingResult) {
                if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
                    // The BillingClient is setup successfully
                    loadAllSKUs();
                }
            }

            @Override
            public void onBillingServiceDisconnected() {
                // Try to restart the connection on the next request to
                // Google Play by calling the startConnection() method.
            }
        });
    }

    private void loadAllSKUs() {
        Toast.makeText(this, "loadAllSKUs", Toast.LENGTH_SHORT).show();

        if (billingClient.isReady())
        {
            Toast.makeText(this, "billingclient ready", Toast.LENGTH_SHORT).show();
            SkuDetailsParams params = SkuDetailsParams.newBuilder()
                    .setSkusList(skuList)
                    .setType(BillingClient.SkuType.INAPP)
                    .build();

            billingClient.querySkuDetailsAsync(params, new SkuDetailsResponseListener() {
                @Override
                public void onSkuDetailsResponse(BillingResult billingResult, List<SkuDetails> skuDetailsList) {
                    Toast.makeText(UpgradeActivity.this, "inside query" + billingResult.getResponseCode(), Toast.LENGTH_SHORT).show();
                    if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK
                            && !skuDetailsList.isEmpty())
                    {
                        for (Object skuDetailsObject : skuDetailsList) {
                            final SkuDetails skuDetails = (SkuDetails) skuDetailsObject;
                            Toast.makeText(UpgradeActivity.this, "" + skuDetails.getSku(), Toast.LENGTH_SHORT).show();

                            if (skuDetails.getSku() == sku)
                                mSkuDetails = skuDetails;
                            upgradeButton.setEnabled(true);

                            upgradeButton.setOnClickListener(new View.OnClickListener() {
                                @Override
                                public void onClick(View v) {
                                    BillingFlowParams billingFlowParams = BillingFlowParams
                                            .newBuilder()
                                            .setSkuDetails(skuDetails)
                                            .build();
                                    billingClient.launchBillingFlow(UpgradeActivity.this, billingFlowParams);

                                }
                            });
                        }
                    }
                }
            });
        }
        else
            Toast.makeText(this, "billingclient not ready", Toast.LENGTH_SHORT).show();

    }

    @Override
    public void onPurchasesUpdated(BillingResult billingResult, @Nullable List<Purchase> purchases) {
        int responseCode = billingResult.getResponseCode();
        if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK
                && purchases != null) {
            for (Purchase purchase : purchases) {
                handlePurchase(purchase);
            }
        }
        else
        if (responseCode == BillingClient.BillingResponseCode.USER_CANCELED) {
            // Handle an error caused by a user cancelling the purchase flow.
            //Log.d(TAG, "User Canceled" + responseCode);
        }
        else if (responseCode == BillingClient.BillingResponseCode.ITEM_ALREADY_OWNED) {
            ///mSharedPreferences.edit().putBoolean(getResources().getString(R.string.pref_remove_ads_key), true).commit();
            ///setAdFree(true);
            setBoolInPref(this,"myPref",sku, true );
        }
        else {
            //Log.d(TAG, "Other code" + responseCode);
            // Handle any other error codes.
        }
    }

    private void handlePurchase(Purchase purchase) {
        if (purchase.getSku().equals(sku)) {
            ///mSharedPreferences.edit().putBoolean(getResources().getString(R.string.pref_remove_ads_key), true).commit();
            ///setAdFree(true);
            setBoolInPref(this,"myPref",sku, true );
            Toast.makeText(this, "Purchase done. you are now a premium member.", Toast.LENGTH_SHORT).show();
        }
    }

    private Boolean getBoolFromPref(Context context, String prefName, String constantName) {
        SharedPreferences pref = context.getSharedPreferences(prefName, 0); // 0 - for private mode

        return pref.getBoolean(constantName, false);

    }

    private void setBoolInPref(Context context,String prefName, String constantName, Boolean val) {
        SharedPreferences pref = context.getSharedPreferences(prefName, 0); // 0 - for private mode

        SharedPreferences.Editor editor = pref.edit();
        editor.putBoolean(constantName, val);
        //editor.commit();
        editor.apply();
        // Update 2015: Android recommends the use of apply() now over commit(),
        // because apply() operates on a background thread instead of storing the persistent data immediately,
        // and possible blocking the main thread.
    }
}

Basically what happens when pressing the upgradeButton is that the app crashes. I cannot figure out what I am doing wrong. I am using the correct SKU, and the app has been live on Play Store for over a year now.

This is the error log I get when pressing the upgradeButton :

I/zygote: Do partial code cache collection, code=124KB, data=72KB
    After code cache collection, code=124KB, data=72KB
    Increasing code cache capacity to 512KB
D/EGL_emulation: eglMakeCurrent: 0xdb928540: ver 3 0 (tinfo 0xdb92bbd0)
D/AndroidRuntime: Shutting down VM
E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.pinloop.testproj, PID: 13352
    java.lang.IllegalArgumentException: SKU cannot be null.
        at com.android.billingclient.api.BillingFlowParams$Builder.build(com.android.billingclient:billing@@3.0.0:23)
        at com.pinloop.testproj.UpgradeActivity.upgradeAction(UpgradeActivity.java:130)
        at com.pinloop.testproj.UpgradeActivity.access$000(UpgradeActivity.java:31)
        at com.pinloop.testproj.UpgradeActivity$1.onClick(UpgradeActivity.java:56)
        at android.view.View.performClick(View.java:6294)
        at android.view.View$PerformClick.run(View.java:24770)
        at android.os.Handler.handleCallback(Handler.java:790)
        at android.os.Handler.dispatchMessage(Handler.java:99)
        at android.os.Looper.loop(Looper.java:164)
        at android.app.ActivityThread.main(ActivityThread.java:6494)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807)

Not sure what to look at here, but I can see that java.lang.IllegalArgumentException: SKU cannot be null. , but sku has already been declared: private String sku = "com.pinloop.testproj.pro"; . Any ideas?

Here is the update code

import android.content.Context;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import com.android.billingclient.api.BillingClient;
import com.android.billingclient.api.BillingClientStateListener;
import com.android.billingclient.api.BillingFlowParams;
import com.android.billingclient.api.BillingResult;
import com.android.billingclient.api.Purchase;
import com.android.billingclient.api.PurchasesUpdatedListener;
import com.android.billingclient.api.SkuDetails;
import com.android.billingclient.api.SkuDetailsParams;
import com.android.billingclient.api.SkuDetailsResponseListener;
import com.aumhum.aumhum.R;

import java.util.ArrayList;
import java.util.List;

public class UpgradeActivity extends AppCompatActivity implements PurchasesUpdatedListener {

    private Button upgradeButton;
    private TextView restoreButton;

    private BillingClient billingClient;
    private final List<String> skuList = new ArrayList();

    private final String sku = "com.pinloop.testproj.pro";

    private SkuDetails mSkuDetails;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_upgrade);

        upgradeButton = (Button) findViewById(R.id.upgradeButton);

        upgradeButton.setEnabled(false);

        upgradeButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                upgradeAction();

            }
        });

        // Check In App Purchase

        skuList.add(sku);

        setButtonStatus();

    }

    private void setButtonStatus(){

        Boolean pro = getBoolFromPref(this,"myPref", sku);
        if (pro)
        {
            Toast.makeText(this, "you are a premium user", Toast.LENGTH_SHORT).show();
            //upgradeButton.setVisibility(View.INVISIBLE);
            upgradeButton.setText(R.string.you_are_premium);

        }
        else
        {
            Toast.makeText(this, "not pro", Toast.LENGTH_SHORT).show();
            setupBillingClient();
        }
    }


    private void upgradeAction() {

        if (mSkuDetails==null){
            Toast.makeText(this,"Please wait while we get details",Toast.LENGTH_SHORT).show();
            return;
        }

        BillingFlowParams billingFlowParams = BillingFlowParams
                .newBuilder()
                .setSkuDetails(mSkuDetails)
                .build();
        billingClient.launchBillingFlow(UpgradeActivity.this, billingFlowParams);
    }


    // In App Handler:

    private void setupBillingClient() {
        billingClient = BillingClient.newBuilder(this).enablePendingPurchases().setListener(this).build();
        billingClient.startConnection(new BillingClientStateListener(){

            @Override
            public void onBillingSetupFinished(BillingResult billingResult) {
                if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
                    // The BillingClient is setup successfully
                    loadAllSKUs();
                }
            }

            @Override
            public void onBillingServiceDisconnected() {
                // Try to restart the connection on the next request to
                // Google Play by calling the startConnection() method.
            }
        });
    }

    private void loadAllSKUs() {
        Toast.makeText(this, "loadAllSKUs", Toast.LENGTH_SHORT).show();

        if (billingClient.isReady())
        {
            Toast.makeText(this, "billingclient ready", Toast.LENGTH_SHORT).show();
            SkuDetailsParams params = SkuDetailsParams.newBuilder()
                    .setSkusList(skuList)
                    .setType(BillingClient.SkuType.INAPP)
                    .build();

            billingClient.querySkuDetailsAsync(params, new SkuDetailsResponseListener() {
                @Override
                public void onSkuDetailsResponse(BillingResult billingResult, List<SkuDetails> skuDetailsList) {
                    Toast.makeText(UpgradeActivity.this, "inside query" + billingResult.getResponseCode(), Toast.LENGTH_SHORT).show();
                    if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK
                            && !skuDetailsList.isEmpty())
                    {
                        for (SkuDetails skuDetails : skuDetailsList) {
                            Toast.makeText(UpgradeActivity.this, "" + skuDetails.getSku(), Toast.LENGTH_SHORT).show();
                            if (skuDetails.getSku().equals(sku)) {
                                mSkuDetails = skuDetails;
                                upgradeButton.setEnabled(true);
                            }
                        }
                    }
                }
            });
        }
        else
            Toast.makeText(this, "billingclient not ready", Toast.LENGTH_SHORT).show();

    }

    @Override
    public void onPurchasesUpdated(BillingResult billingResult, @Nullable List<Purchase> purchases) {
        int responseCode = billingResult.getResponseCode();
        if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK
                && purchases != null) {
            for (Purchase purchase : purchases) {
                handlePurchase(purchase);
            }
        }
        else
        if (responseCode == BillingClient.BillingResponseCode.USER_CANCELED) {
            // Handle an error caused by a user cancelling the purchase flow.
            //Log.d(TAG, "User Canceled" + responseCode);
        }
        else if (responseCode == BillingClient.BillingResponseCode.ITEM_ALREADY_OWNED) {
            ///mSharedPreferences.edit().putBoolean(getResources().getString(R.string.pref_remove_ads_key), true).commit();
            ///setAdFree(true);
            setBoolInPref(this,"myPref",sku, true );
        }
        else {
            //Log.d(TAG, "Other code" + responseCode);
            // Handle any other error codes.
        }
    }

    private void handlePurchase(Purchase purchase) {
        if (purchase.getSku().equals(sku)) {
            ///mSharedPreferences.edit().putBoolean(getResources().getString(R.string.pref_remove_ads_key), true).commit();
            ///setAdFree(true);
            setBoolInPref(this,"myPref",sku, true );
            Toast.makeText(this, "Purchase done. you are now a premium member.", Toast.LENGTH_SHORT).show();
        }
    }

    private Boolean getBoolFromPref(Context context, String prefName, String constantName) {
        SharedPreferences pref = context.getSharedPreferences(prefName, 0); // 0 - for private mode

        return pref.getBoolean(constantName, false);

    }

    private void setBoolInPref(Context context,String prefName, String constantName, Boolean val) {
        SharedPreferences pref = context.getSharedPreferences(prefName, 0); // 0 - for private mode

        SharedPreferences.Editor editor = pref.edit();
        editor.putBoolean(constantName, val);
        //editor.commit();
        editor.apply();
        // Update 2015: Android recommends the use of apply() now over commit(),
        // because apply() operates on a background thread instead of storing the persistent data immediately,
        // and possible blocking the main thread.
    }
}

3 problems

  1. You have enabled upgrade button by default and don't have null check in upgradeAction method
  2. You are comparing strings by == instead of.equals in java
  3. You are setting upgrade onclick listener 2 times

the 'mSkuDetails' attribute?

 if (skuDetails.getSku() == sku){
         mSkuDetails = skuDetails;
         upgradeButton.setEnabled(true);
         upgradeButton.setOnClickListener(new View.OnClickListener() {
         @Override
         public void onClick(View v) {
           BillingFlowParams billingFlowParams = BillingFlowParams
                                            .newBuilder()
                                            .setSkuDetails(mSkuDetails)
                                            .build();
                                    billingClient.launchBillingFlow(UpgradeActivity.this, billingFlowParams);

                                }
                            });
}
  

a. Verify the sku is the same as product code set in Google Play In App Billing section.

b. Debug through the code to see that null is not passed as sku in the call to launch billing flow.

c. Refresh Play Store cache in the device your are testing

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM