Billing API v3 IabHelper NullPointerException

AndroidIn App-Billing

Android Problem Overview


edit 4/15: Catching nullpointer in IabHelper appears to have stopped this problem. I am no longer seeing the exceptions being thrown, I'm going to accept this as an answer.


edit 4/04: A little bit of a deeper dive. There are try catch blocks that handle RemoteExceptions and JSONExceptions for the queryPurchases method, but no NullPointerException handling. What I am going to try is include NullPointer Exception handling so IabHelper looks like this when trying to querySkuDetails:

    catch (NullPointerException e) {
        throw new IabException(IABHELPER_UNKNOWN_ERROR, "NullPointer while refreshing inventory.", e);
    }

I just filed a bug on this:

https://code.google.com/p/marketbilling/issues/detail?id=114


edit 3/25: well, still receiving this crash... now it happens while trying to get a context at line 3 of the following excerpt from IabHelper:

int queryPurchases(Inventory inv, String itemType) throws JSONException, RemoteException {
    logDebug("Querying owned items, item type: " + itemType);
    logDebug("Package name: " + mContext.getPackageName());

This is frustrating because in my manifest I always use the full path name of my app for "name".

Example "com.myappname.blah.ClassName"

I've also tried passing this, MyClass.this, getApplicationContext() to mHelper. However they all produce the same NullPointer results randomly from devices in the wild. I also tried name=".MyClass" in the manifest. This is what it looks like currently:

mHelper = new IabHelper(MyClass.this, myKey);

edit 3/18/13: I am still receiving exceptions, even with the new IabHelper version deployed on 3/17.

I am starting to see a pattern here, that the crashes are all when trying to get a context when executing mContext.getPackageName(). I'm curious why this works on all of my test devices, and I can't reproduce this crash, and only seems to be on a small number of devices.

Here is the new crash:

java.lang.NullPointerException
	at com.myapp.util.IabHelper.queryPurchases(SourceFile:836)
	at com.myapp.util.IabHelper.queryInventory(SourceFile:558)
	at com.myapp.util.IabHelper.queryInventory(SourceFile:522)
	at com.myapp.util.IabHelper$2.run(SourceFile:617)
	at java.lang.Thread.run(Thread.java:1019)

Caused by IabHelper...

line 836: logDebug("Package name: " + mContext.getPackageName());

edit 3/17/13: I see that there have been many bug fixes published over the past several months, I will try the latest code available here and see if this resolves the problem:

https://code.google.com/p/marketbilling/source/browse/v3/src/com/example/android/trivialdrivesample/util


In one of my apps, I am using the billing API and the boilerplate code included with it.

I am using the latest version of billing API available via the SDK manager as of 3/16/2013.

In my activity, I query the inventory using the following:

final List<String> skuList = new ArrayList<String>();
skuList.add("sku1");
skuList.add("sku2");
skuList.add("sku3");
if (skuList != null) {
	if (skuList.size() > 0) {
		try {
			mHelper.queryInventoryAsync(true, skuList, mGotInventoryListener);
		} catch (Exception e) { 
			ACRA.getErrorReporter().handleException(e);
		}
	}
}

I am receiving multiple NullPointerException reports in the wild from the IabHelper class for the following devices. I can't reproduce the issue and can't find any information regarding these crashes, and is the reason why I am posting this question.

I have countless other checks for nulls and try/catch blocks in the "developer facing" part of the billing API, including within onQueryInventoryFinished, so I know this exception is not being thrown from "my code" (because I'm not capturing crashes from any of my app's classes), but instead is being thrown from within the IabHelper itself. I have not modified the IabHelper other than this recommended fix: https://stackoverflow.com/a/14737699

Crash #1 Galaxy Nexus

java.lang.NullPointerException
	at com.myapp.util.IabHelper.querySkuDetails(SourceFile:802)
	at com.myapp.util.IabHelper.queryInventory(SourceFile:471)
	at com.myapp.util.IabHelper$2.run(SourceFile:521)
	at java.lang.Thread.run(Thread.java:856)

Caused by IabHelper...

line 802: Bundle skuDetails = mService.getSkuDetails(3, mContext.getPackageName(), ITEM_TYPE_INAPP, querySkus);    

Crash #2 Samsung GT-S5570L

java.lang.NullPointerException
	at com.myapp.util.IabHelper.queryPurchases(SourceFile:735)
	at com.myapp.util.IabHelper.queryInventory(SourceFile:465)
	at com.myapp.util.IabHelper$2.run(SourceFile:521)
	at java.lang.Thread.run(Thread.java:1019)

Caused by IabHelper...

line 735: Bundle ownedItems = mService.getPurchases(3, mContext.getPackageName(), ITEM_TYPE_INAPP, continueToken);

Android Solutions


Solution 1 - Android

edit 4/15: Catching nullpointer in IabHelper appears to have stopped this problem. I am no longer seeing the exceptions being thrown, I'm going to accept this as an answer.


edit 4/04: A little bit of a deeper dive. There are try catch blocks that handle RemoteExceptions and JSONExceptions for the queryPurchases method, but no NullPointerException handling. What I am going to try is include NullPointer Exception handling so IabHelper looks like this when trying to querySkuDetails:

    catch (NullPointerException e) {
        throw new IabException(IABHELPER_UNKNOWN_ERROR, "NullPointer while refreshing inventory.", e);
    }

I just filed a bug on this:

https://code.google.com/p/marketbilling/issues/detail?id=114


Change

        if (querySkuDetails) {
            r = querySkuDetails(ITEM_TYPE_INAPP, inv, moreItemSkus);
            if (r != BILLING_RESPONSE_RESULT_OK) {
                throw new IabException(r, "Error refreshing inventory (querying prices of items).");
            }
        }

to

        if (querySkuDetails) {
            try {
                r = querySkuDetails(ITEM_TYPE_INAPP, inv, moreItemSkus);
                if (r != BILLING_RESPONSE_RESULT_OK) {
                    throw new IabException(r, "Error refreshing inventory (querying prices of items).");
                }
            } catch (NullPointerException e) {
                throw new IabException(IABHELPER_UNKNOWN_ERROR, "NPE while refreshing inventory.", e);
            }
        }

Change

            if (querySkuDetails) {
                r = querySkuDetails(ITEM_TYPE_SUBS, inv, moreSubsSkus);
                if (r != BILLING_RESPONSE_RESULT_OK) {
                    throw new IabException(r, "Error refreshing inventory (querying prices of subscriptions).");
                }
            }

to

            if (querySkuDetails) {
                try {
                    r = querySkuDetails(ITEM_TYPE_SUBS, inv, moreSubsSkus);
                    if (r != BILLING_RESPONSE_RESULT_OK) {
                        throw new IabException(r, "Error refreshing inventory (querying prices of subscriptions).");
                    }
                } catch (NullPointerException e) {
                    throw new IabException(IABHELPER_UNKNOWN_ERROR, "NPE while refreshing inventory.", e);
                }
            }

Solution 2 - Android

You are probably using async operations. The current IabHelper is not safe in case you use the ...async methods. The problem is that in any moment an async operation is running dispose can be called on the main thread. In this case you will get NullPointerExceptions and IllegalStateExceptions.

Here is the patch fixing it:

Index: src/com/evotegra/aCoDriver/iabUtil/IabHelper.java
===================================================================
--- src/com/evotegra/aCoDriver/iabUtil/IabHelper.java	(revision 1162)
+++ src/com/evotegra/aCoDriver/iabUtil/IabHelper.java	(working copy)
@@ -86,7 +86,10 @@
 
     // Is an asynchronous operation in progress?
     // (only one at a time can be in progress)
-    boolean mAsyncInProgress = false;
+    volatile boolean mAsyncInProgress = false;
+    
+    // is set to true if dispose is called while a thread is running. Allows graceful shutdown
+    volatile boolean mDisposeRequested = false;
 
     // (for logging/debugging)
     // if mAsyncInProgress == true, what asynchronous operation is in progress?
@@ -285,6 +288,12 @@
      * disposed of, it can't be used again.
      */
     public void dispose() {
+    	// do not dispose while an async Thread is running. Will cause all kinds of exceptions.
+    	// In this case dispose must be called from thread after setting mAsyncInProgress to true
+    	if (mAsyncInProgress) {
+    		mDisposeRequested = true;
+    		return;
+    	}
         logDebug("Disposing.");
         mSetupDone = false;
         if (mServiceConn != null) {
@@ -827,6 +836,7 @@
         logDebug("Ending async operation: " + mAsyncOperation);
         mAsyncOperation = "";
         mAsyncInProgress = false;
+        if (mDisposeRequested) IabHelper.this.dispose();
     }

Or download the patch here. http://code.google.com/p/marketbilling/issues/detail?id=139&thanks=139&ts=1375614409

Solution 3 - Android

Slightly modify the beginning of the queryPurchases method to look like this:

int queryPurchases(Inventory inv, String itemType) throws JSONException, RemoteException {
        // Query purchases
        //logDebug("Querying owned items, item type: " + itemType);
       //logDebug("Package name: " + mContext.getPackageName());
       boolean verificationFailed = false;
       String continueToken = null;

        do {
//            logDebug("Calling getPurchases with continuation token: " + continueToken);
        	if(mDisposed || mService==null) return IABHELPER_UNKNOWN_ERROR;
            Bundle ownedItems = mService.getPurchases(3, mContext.getPackageName(),
                    itemType, continueToken);

Thanks to sebastie for pointing out the cause of this.

Solution 4 - Android

tmanthey patch also requires

mDisposeRequested = false;

after the disposing takes place

Solution 5 - Android

If you're getting this error on the emulator, it may be a very simple thing which happens in more than a half cases.

Check that you're using Google API SDK and not regular SDK!!!

Solution 6 - Android

The IabHelper is obsolete and has been replaced by the BillingClient. See https://developer.android.com/google/play/billing/billing_library.html

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionlograyView Question on Stackoverflow
Solution 1 - AndroidlograyView Answer on Stackoverflow
Solution 2 - AndroidtmantheyView Answer on Stackoverflow
Solution 3 - AndroidThunderView Answer on Stackoverflow
Solution 4 - AndroidMarkos EvlogimenosView Answer on Stackoverflow
Solution 5 - AndroidsandaloneView Answer on Stackoverflow
Solution 6 - AndroidDSchmidtView Answer on Stackoverflow