play-billing-samples: Crash with null pointer exception after Billing Service gets disconnected
After successful setup and the establishment of a connection to the Billing Service in
IabHelper.startSetup()
The user tries to make an in-app purchase.
IabHelper.launchPurchaseFlow()
gets called which in turn calls methods on mService
. But sometimes
IabHelper.onServiceDisconnected()
has been called in the meantime which sets
IabHelper.mService = null
This causes a null pointer exception.
It seems unpredictable when/why this disconnection happens. But I have some pre-launch reports in my developer console as examples.
Would be resolved by: https://github.com/googlesamples/android-play-billing/pull/46
About this issue
- Original URL
- State: closed
- Created 8 years ago
- Comments: 24 (1 by maintainers)
Does it say this somewhere clearly? Presumably it should. Its amazing how confusing the documentation is.
billing_integrate.html still describes using the old IabHelper and IInAppBillingService.aidl
Whereas for example billling_library.html describes using the new BillingClient.
The github example both reference, contains both a “TrivialDrive” and “TrivialDrive_v2” subdir, each of which simply describe themselves as
Even though billing/versions.html shows version 5 to be current since Feb 2015 TrivialDrive_v2 doesn’t state how/why it differs from TrivialDrive, and why TrivialDrive is still there at all (is TrivialDrive obsolete? Are there valid reasons to still use TrivialDrive? Is TrivialDrive_v2 not finished yet?)
And this on top of how hard it is to get the sample working anyways (setting up your own in-app products in developer console, modifying the app to reflect this, only working with signed release etc). A bit more clarity here could save many 1000s of developers many frustrating hours
billling_library.html feels unfinished as well.
Why does the chapter ‘Retrieve existing purchases’ describe and show ‘Google Play calls the onPurchasesUpdated() method to deliver the result of the purchase operation’? How is this relevant? Is it called on service connect?
Only in the last sentence it states: ‘You can also query Google Play to quickly retrieve the list of purchases and subscriptions that the user made.’ It fails to mention how.
Three chapters down we learn you should use
queryPurchases()
and could usequeryPurchaseHistoryAsync()
but neither triggers above callback.The difference between these two methods is also not explained in depth. The first uses the local Play Service cache, the second does some kind of net call but is only able to retrieve the very last transaction. When is the cache refreshed? Is there a use case for the seconds method? Is the first unsafe?
I tried to re-implement billing because of the NPEs. I hate trial and error, and not just the annoying, impossible to really test billing. The docs suck. I’m irritated.
@netomarin “please could you point a bug that still blocking you to use Google Play Billing?” - Sure, how about the bug where queryPurchases() returns an empty list, but any attempt to purchase an IAP returns an error code 7 - item already in inventory. That is a pretty big bug with billing library. Sure, it only seems to happen for my users who had already purchased items with the old IabHelper system before I upgraded to billing library, but because of this bug I had to go back to the old IabHelper system. Worst, there are many people who have been having this bug for months according to your issue tracker. Do you even look at the issue tracker that you have pointed us to?
“We want to help all developers to implement in-app billing easily.” - You’re joking, right? Google’s actions seem to indicate the exact opposite of this.
The “bug” @gorgon and @netomarin missed and failed to mention directly is:
IabHelper
is obsolete und should no longer be used. Their advise is only valid for the newbillingclient
.@netomarin
Thanks. I didn’t notice that TrivialDrive had been updated using recent https://developer.android.com/google/play/billing/billing_library.html
Guess we need to refactor our previous code which is using old
IabHelper
, and move it to https://developer.android.com/google/play/billing/billing_library.htmlIf you want to reproduce this use case:
Once you return back to your app, service should be disconnected from the Play Store. So any operation on it will throw NPE.
And the best way to solve this issue is by implementing retry-policy specific to your needs as Neto mentioned above. We did showcased the most basic one inside the updated TrivialDrive_v2 sample. So feel free to reuse that approach.
I’m not sure my stack trace will help since I’ve made a few fixes to the sample IabHelper to make it more robust.
Fatal Exception: java.lang.NullPointerException: Attempt to invoke interface method ‘android.os.Bundle com.a.a.a.a.a(int, java.lang.String, java.lang.String, java.lang.String)’ on a null object reference at com.fehnerssoftware.billingutils.IabHelper.queryPurchases(IabHelper.java:843) at com.fehnerssoftware.billingutils.IabHelper.queryInventory(IabHelper.java:545) at com.fehnerssoftware.billingutils.IabHelper.queryInventory(IabHelper.java:523) at com.fehnerssoftware.billingutils.IabHelper$2.run(IabHelper.java:618) at java.lang.Thread.run(Thread.java:762)
The failing line of code is the last line of this block
The logs show the billing service gets disconnected after a while and the crash comes when trying to query inventory:
To fix it in my copy I simply added to checkNotDisposed() a check that if mService is null and mSetupDone is true it throws an illegal state exception. I catch this in my code and setup IabHelper again and retry.
Recently, we refactor our code, from
IabHelper
toPlay Billing Library
.The outcome is that, we see much less crash report related to billing.
Thank you for providing
Play Billing Library
.@netomarin @d-schmidt
If by “generic” you mean a “low level confusing and buggy example”, then I suppose you’re right. When I hear the term generic I generally think “high level clear explanation and of the main ideas with a helpful example”
I didn’t know there was an api NDK/C++ developers could use for in-app billing? Can you point me to the documentation? And if a developer did want to do so using C++, did they could just use JNI to call the corresponding methods of the newer less buggy JAVA classes, just as easily as the could the older JAVA example.
@dec1 the page billing_integrate.html is a “generic” guide, provided for developers that doesn’t want to use Play Billing Library, or can’t use due to a platform incompatibility (like developing using NDK or C++). So, we decided to keep this page with generic instructions about how to use AIDL interface. And you can still using the AIDL interface if you want, it’s not deprecated. So, we recommend to use the new Play Billing Library, but you are not obligated to and we need to keep the instructions about how to do it. It’s not confusing or ambiguous, they are different guides and we explain this at the top of the page:
“Note: This guide provides general instructions to help you implement the In-app Billing API in your app. The Play Billing Library provides a wrapper around the In-app Billing API and is designed to help you when integrating the API. To see a complete implementation using the library, visit the Play Billing Library training class. The training class provides a complete sample In-app Billing app, which demonstrates how to start a purchase flow, handle purchase result, list your products, consume an item, and much more.”
@dec1 about the samples descriptions, you are right, we will improve it ASAP. Thanks. Trivial Drive v1 still on GitHub because, as I mentioned earlier, it demonstrates how to use the AIDL interface instead of Play Billing Library. Trivial Drive v2 is the sample for Play Billing Library implementation, and is finished. But, we will keep improving and adding new features as they are launched on Play Billing Library, we are actively improving it. Since the sample is a reference implementation, and in-app billing API have many configurations required to assure the platform security, the sample requires additional steps to have it running in you account. These are steps required by Google Play for your own security.
@d-schmidt Play Billing Library guide is finished and up-to-date to our current development state. If the documentation is missing something or you feel that is not correct, please open a bug in our public bug tracker. This channel is for issues related to the TrivialDrive samples.
@d-schmidt we mention the onPurchasesUpdated method because it’s called when a purchase is updated, even it’s not started from the current device, so it’s important to know how to handle it to keep you inventory up to date.
@d-schmidt Google Play’s cache has it owns update policy, but it happens on a very short amount of time and it’s totally safe. And other difference between queryPurchases() and queryPurchaseHistoryAsync() is that queryPurchasesHistoryAsync returns the most recent purchase made by the user for each SKU, even if that purchase is expired, canceled, or consumed, and queryPurchases() only the valid managed purchases. As mentioned on documentation, we recommend to use the queryPurchases(), because it’s enough for almost all cases:
“Use the queryPurchases() method whenever possible as it uses the local cache, in preference to the queryPurchaseHistoryAsync() method.”
@Aperico-com The issue is closed because it’s not a issue caused by the sample code. The samples are fine and running, and if you are facing a NPE in your modified code, as we mentioned earlier, you need to check the connection status and implement your own retry policy.
Hope I could address all open questions. Thanks
@netomarin why do you close the issue when there still is a big problem with non reproducible NPEs to production builds? Following the documentation for using IABHelper to a tee still cause these problems, but you think its is OK to close the issue? Can I at least get an answer to if IABHelper is valid to use? Is the BillingClient approach what we are supposed to use now? If so why do you not give that info anywhere? The only thing I can do now is catch the internal exceptions to prevent crashes, getting the purchase flow to work… just forget it, at least using IABHelper.