3

Edit: Please see my answer below for my solution.


I'm receiving a NullPointerException error when trying to check owned items using getPurchases() and I'm not sure why as I've followed the documentation. I'm finding that the documentation can be misleading at times, so here I am.

I've set up In-app billing such that it initializes okay and I get a success message. After it is initialized, I want to check if the user has previously purchased item(s) and display a button based on the result. Here is my code so far and below is my LogCat. The error appears at the beginning of the try/catch.

public class MainActivity extends Activity
{
    IabHelper mHelper;
    IInAppBillingService mService;
    private Button buyButton;
    AdView adView;

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

        adView = (AdView)this.findViewById(R.id.adView);
        AdRequest adRequest = new AdRequest.Builder().build();
        adView.loadAd(adRequest);

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

        String base64EncodedPublicKey = "public_key";

        mHelper = new IabHelper(this, base64EncodedPublicKey);

        mHelper.startSetup(new IabHelper.OnIabSetupFinishedListener()
        {
            @Override
            public void onIabSetupFinished(IabResult result)
            {
                if(!result.isSuccess())
                {
                    Log.d("TEST", "In-app Billing setup failed: " + result);
                }
                else
                {
                    Log.d("TEST", "In-app Billing is set up OK"); //This passes!
                }
            }
        });

        bindService(new Intent("com.android.vending.billing.InAppBillingService.BIND"), mServiceConn, Context.BIND_AUTO_CREATE);

        try
        {
            Bundle ownedItems = mService.getPurchases(3, getPackageName(), "inapp", null); //This is line 66 referenced in LogCat error

            if(ownedItems.getInt("RESPONSE_CODE") == 0)
            {
                ArrayList ownedSkus = ownedItems.getStringArrayList("INAPP_PURCHASE_ITEM_LIST");
                ArrayList purchaseDataList = ownedItems.getStringArrayList("INAPP_PURCHASE_DATA_LIST");
                ArrayList signatureList = ownedItems.getStringArrayList("INAPP_DATA_SIGNATURE");
                String continuationToken = ownedItems.getString("INAPP_CONTINUATION_TOKEN");

                for(int i=0; i<purchaseDataList.size(); ++i)
                {
                    String purchaseData = (String) purchaseDataList.get(i);
                    String signature = (String) signatureList.get(i);
                    String sku = (String) ownedSkus.get(i);

                    Log.d("TEST", "Purchased: "+i+ " -> "+sku);
                }
            }
            else
            {
                Log.d("TEST", "Not Items Owned!");
            }
        }
        catch(RemoteException e)
        {
            //TODO: Error, unable to get owned items
            e.printStackTrace();
            Log.d("TEST", "owned items check failed: "+e);
        }           
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu)
    {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }

    ServiceConnection mServiceConn = new ServiceConnection() 
    {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service)
        {
            mService = IInAppBillingService.Stub.asInterface(service);
            Log.d("TEST", "mService ready to go!"); //This displays if try/catch above is commented out. Is it not waiting for mService to be initialized before running try/catch?     
        }

        @Override
        public void onServiceDisconnected(ComponentName name)
        {
            mService = null;            
        }
    };

    @Override
    public void onDestroy()
    {
        super.onDestroy();

        if(mServiceConn != null)
        {
            unbindService(mServiceConn);
        }
    }
}

And the Logcat errors

01-04 22:39:35.052: E/AndroidRuntime(32375): FATAL EXCEPTION: main
01-04 22:39:35.052: E/AndroidRuntime(32375): java.lang.RuntimeException: Unable to start activity ComponentInfo{com.test.iab/com.test.iab.MainActivity}: java.lang.NullPointerException
01-04 22:39:35.052: E/AndroidRuntime(32375):    at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2180)
01-04 22:39:35.052: E/AndroidRuntime(32375):    at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2230)
01-04 22:39:35.052: E/AndroidRuntime(32375):    at android.app.ActivityThread.access$600(ActivityThread.java:141)
01-04 22:39:35.052: E/AndroidRuntime(32375):    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1234)
01-04 22:39:35.052: E/AndroidRuntime(32375):    at android.os.Handler.dispatchMessage(Handler.java:99)
01-04 22:39:35.052: E/AndroidRuntime(32375):    at android.os.Looper.loop(Looper.java:137)
01-04 22:39:35.052: E/AndroidRuntime(32375):    at android.app.ActivityThread.main(ActivityThread.java:5041)
01-04 22:39:35.052: E/AndroidRuntime(32375):    at java.lang.reflect.Method.invokeNative(Native Method)
01-04 22:39:35.052: E/AndroidRuntime(32375):    at java.lang.reflect.Method.invoke(Method.java:511)
01-04 22:39:35.052: E/AndroidRuntime(32375):    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:793)
01-04 22:39:35.052: E/AndroidRuntime(32375):    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:560)
01-04 22:39:35.052: E/AndroidRuntime(32375):    at dalvik.system.NativeStart.main(Native Method)
01-04 22:39:35.052: E/AndroidRuntime(32375): Caused by: java.lang.NullPointerException
01-04 22:39:35.052: E/AndroidRuntime(32375):    at com.test.iab.MainActivity.onCreate(MainActivity.java:66)
01-04 22:39:35.052: E/AndroidRuntime(32375):    at android.app.Activity.performCreate(Activity.java:5104)
01-04 22:39:35.052: E/AndroidRuntime(32375):    at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1080)
01-04 22:39:35.052: E/AndroidRuntime(32375):    at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2144)
01-04 22:39:35.052: E/AndroidRuntime(32375):    ... 11 more
01-04 22:39:35.083: E/GooglePlayServicesUtil(32375): The Google Play services resources were not found. Check your project configuration to ensure that the resources are included.

My guess is that I'm not initializing mService correctly? But I don't understand if I'm not because I believe I did it exactly as the documentation outlines. Thanks for guidance.

Community
  • 1
  • 1
user3001127
  • 383
  • 3
  • 15

2 Answers2

3

The reason this was not working was because onServiceConnected() isn't guaranteed to be called until onCreate() completes. So I moved the try/catch code into onServiceConnected(). I'm not sure if this is best practice, but it seems to have fixed my issue. Here is the resulting code for anyone finding themselves with this same error. This is exuding button and purchasing logic. I recommend this tutorial for that code.

public class MainActivity extends Activity
{
    IabHelper mHelper;
    IInAppBillingService mService;
    static final String ITEM_SKU = "android.test.purchased";
    private Button buyButton;
    AdView adView;

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

        adView = (AdView)this.findViewById(R.id.adView);
        AdRequest adRequest = new AdRequest.Builder().build();
        adView.loadAd(adRequest);

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

        bindService(new Intent("com.android.vending.billing.InAppBillingService.BIND"), mServiceConn, Context.BIND_AUTO_CREATE);      
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu)
    {
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }

    ServiceConnection mServiceConn = new ServiceConnection() 
    {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service)
        {
            mService = IInAppBillingService.Stub.asInterface(service);
            Log.d("TEST", "mService ready to go!");
            checkOwnedItems();    
        }

        @Override
        public void onServiceDisconnected(ComponentName name)
        {
            mService = null;            
        }
    };

    private void checkownedItems()
    {
        try
        {
            Bundle ownedItems = mService.getPurchases(3, getPackageName(), "inapp", null);

            if(ownedItems.getInt("RESPONSE_CODE") == 0)
            {
                ArrayList<String> ownedSkus = ownedItems.getStringArrayList("INAPP_PURCHASE_ITEM_LIST");
                ArrayList<String> purchaseDataList = ownedItems.getStringArrayList("INAPP_PURCHASE_DATA_LIST");
                ArrayList<String> signatureList = ownedItems.getStringArrayList("INAPP_DATA_SIGNATURE");
                String continuationToken = ownedItems.getString("INAPP_CONTINUATION_TOKEN");

                if(purchaseDataList.size() > 0)
                {
                    //Item(s) owned

                    for(int i=0; i<purchaseDataList.size(); ++i)
                    {
                        String purchaseData = purchaseDataList.get(i);
                        String signature = signatureList.get(i); //Note signatures do not appear to work with android.test.purchased (silly google)
                        String sku = ownedSkus.get(i);
                    }
                }
                else
                {
                    //Item(s) not owned

                    String base64EncodedPublicKey = "public_key";

                    mHelper = new IabHelper(this, base64EncodedPublicKey);

                    mHelper.startSetup(new IabHelper.OnIabSetupFinishedListener()
                    {
                        @Override
                        public void onIabSetupFinished(IabResult result)
                        {
                            if(!result.isSuccess())
                            {
                                Log.d("TEST", "In-app Billing setup failed: " + result);
                            }
                            else
                            {
                                Log.d("TEST", "In-app Billing is set up OK");
                            }
                        }
                    });
                }
            }
            else
            {
                //Error checking owned items
            }
        }
        catch(RemoteException e)
        {
            e.printStackTrace();
        }
    }

    @Override
    public void onDestroy()
    {
        super.onDestroy();

        if(mServiceConn != null)
        {
            unbindService(mServiceConn);
        }
    }
}
user3001127
  • 383
  • 3
  • 15
  • THANK YOU SO MUCH!!! It would not hurt Google if they tell this to us at the developer reference. Never call price list from the onCreate activity again! Got it! – Totalys Feb 22 '14 at 22:15
1

Your code just says

IInAppBillingService mService;

It is null, you need to initialise it.

Put a System.out.println() in the onServiceConnected method to see if it is being initialised. If it is not, it means that onServiceConnected is not being called so the bindService method might not be working as intended

EDIT: I think i know the problem.

Add this outside the onCreate method as a class variable...

ServiceConnection mServiceConn;

Then change

ServiceConnection mServiceConn = new ServiceConnection()

to

mServiceConn = new ServiceConnection()
Ogen
  • 6,499
  • 7
  • 58
  • 124
  • Isn't bindService() initializing it when it calls mServiceConn right before the try/catch? – user3001127 Jan 05 '14 at 06:21
  • @user3001127 Yeah that was my mistake, bindService is initialising it. Try out my most recent edit. I think it MIGHT solve the problem. – Ogen Jan 05 '14 at 06:29
  • I tried to create mServiceConn as a class variable, but `mServiceConn = new ServiceConnection()` creates syntax errors. Specifically, the closing bracket of the onCreateOptionsMenue() method errors with the message "Sytaxt error on token "}", delete this token". Also the closing braket of mServiceConn now has the error "Multiple markers at this line -Syntax error, insert "}" to complete MethodBody -Syntax error, insert "}" to complete MethodBody" – user3001127 Jan 05 '14 at 06:30
  • I have edited my previous comment to include the exact error messages. – user3001127 Jan 05 '14 at 06:35
  • @user3001127 Is your **onServiceConnected** method being called after **bindService** is called? Verify with a System.out.println() statement. – Ogen Jan 05 '14 at 06:39
  • If I comment out the try/catch, I get success messages from `onServiceConnected()`. However, without commenting out the try/catch, the code bombs out and I don't see the success message. Its as if its not waiting for mService to be initialized before running the try/catch. – user3001127 Jan 05 '14 at 06:41
  • @user3001127 Okay then do a null check on mService at the very start of the try to verify this. `if (mService == null) { System.out.println("it is null"); }` – Ogen Jan 05 '14 at 06:43
  • Yeah, got "it is null" message. By the way, I appreciate your active help with this! – user3001127 Jan 05 '14 at 06:46
  • @user3001127 Well I guess that is the problem. mService is not being initialised before the try catch is run. Now to figure out why... I'll try to find out and get back to you – Ogen Jan 05 '14 at 06:49
  • I figured out why, `onServiceConnected()` isn't called until `onCreate()` is complete. I'm not sure how to rearrange the code at this point to call `getPurchases` after `onCreate()` finishes. [Here](http://stackoverflow.com/a/3055749/3001127) is where I read about it. – user3001127 Jan 05 '14 at 06:54
  • the only thing I can think of is to do the try/catch within the `onServiceConnected()` method. Would this be good practice? – user3001127 Jan 05 '14 at 07:08