in-app billing doesn't work: "IAB Helper is not set up"

38,493

Solution 1

Had the same issue while executing purchaseFlow function. Take a look at Activity class in the Google's example and specifically at the method protected void onActivityResult(int requestCode, int resultCode, Intent data). You probably forgot to implement this one. This function is vital for the whole mechanism to work without a glitch.

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    Log.i(TAG, "onActivityResult(" + requestCode + "," + resultCode + "," + data);

    // Pass on the activity result to the helper for handling
    if (!inappBillingHelper.handleActivityResult(requestCode, resultCode, data)) {
        super.onActivityResult(requestCode, resultCode, data);
    }
    else {
        Log.i(TAG, "onActivityResult handled by IABUtil.");
    }
}

EDIT: Additionally the problem also exists when you have wrong password associated with your gmail account on your phone (this happened on me today). Of course all the Inapp billing features should be tested on the phone, but that I think is obvious.

Solution 2

The fundamental problem is that startRegistered() is being invoked in direct response to a UI user click, whereas the setup of your IabHelper object is triggered asynchronously, and so cannot be known to have completed until an asynchronous response is received via onIabSetupFinished().

Your startRegistered() method is triggered by a user click, and that calls launchPurchaseFlow(), which in turn requires that the IabHelper object has already completed setup, but if the user clicks to trigger a purchase before that confirmation is received (either because setup failed or because the user is exceptionally quick on the draw), then setup will not have been completed, and launchPurchaseFlow() will report the error that you're seeing. In the case of your logcat, the delay is 14 seconds, which would usually be enough time, but...maybe not in this case. Or, maybe something went wrong and you never would have connected no matter how long you had waited.

In your logcat, there's no message indicating "Billing service connected," which is one of the first things that must happen if your setup is to complete. Since that does not occur, you are also not seeing any message (either of success or of failure) from onIabSetupFinished().

This is tricky stuff because of the asynchronous responses required. One approach would be to disable the button used to trigger a purchase until your onIabSetupFinished() returns with success. This would prevent the triggering of the purchase until the IabHelper object had been successfully set up. Of course, if setup fails, you'll have a non-functioning button, but at least you can tell the user what's up (by putting up a message that indicates you're waiting for setup to complete - e.g., as part of the button text).

Even then, once your purchase is initiated and the payment dialog appears to the user, there's the possibility of your app going through an onStop() cycle that flushes your app from memory while the user is pondering her purchase (since the purchase dialog is part of Google Play, not part of your app, and the OS may require memory to run it, and that memory may be obtained by stopping your app). That would destroy your IabHelper() object, which would then have to be created and asynchronously set up again. And again, since that is be triggered asynchronously in your onCreate() method, onActivityResult() may be invoked by the Google Play service to report the user's purchase action before the IabHelper object's setup has completed, and since in onActivityResult() you will need to use your IabHelper instance, this could result in an error. It seems that you have to be prepared for anything.

This should give you the flavor of what you're dealing with. IAB is difficult for exactly these reasons - multiple threads of asynchronous stuff (e.g., setup vs. purchases vs. Android OS actions that stop your app to grab memory for use by, quite possibly, the very Google Play app purchase operation for which your app is waiting to obtain the results of the purchase). A lot of what gets implemented (including by the TrivialDrive sample) is flaky because it implicitly relies upon your app staying in memory when in fact it might get recycled, or because it relies upon one leg of a race condition (e.g. setup) completing before another leg (e.g., purchase launch) does, and so on.

Solution 3

I have just finished wrapping my head around the exact same problem. IabHelper-Setup starts, but after that, nothing else happens. And clicking on an in-App-Purchase returns the exact same error you had.

Here's what I figured out: I only used emulators from eclipse. Once I read that a certain Google Play version is required, I noticed that Google Play was missing entirely on my test emulation drives.

When I then used a real phone it worked flawlessly! So if you happen to be still stuck on that problem, try to use a real device (if you have one available). That should do the trick.

Solution 4

Another thing that I came across; while you might have the latest version of google play on your device which supports the latest version of in app billing, other users may not. And while crashes caused by this in theory should appear in the developer console, I could not see these crashes until I implemented firebase...and then I saw a lot of them. What I ended up doing was using a try catch and linking users who didn't have the latest version of google play or experienced a problem on the google play store end to this page https://support.google.com/googleplay/answer/1050566?hl=en

try {
        mHelper.launchPurchaseFlow(this, SKU_PRO_LT, RC_REQUEST,
                mPurchaseFinishedListener, payload);
    } catch (Exception e) { //with IabHelper.IabAsyncInProgressException the code still fatally crashes for some reason
        //complain("Error launching purchase flow. Another async operation in progress.");
        alert2("[error msg]");
        setWaitScreen(false);
    }

alert2 is just a dialog box with a link to the webpage above.

But firstly I'd recommend testing out in app purchases on a few friends phones just to make sure it's a play store update issue and not a code issue.

Share:
38,493

Related videos on Youtube

richey
Author by

richey

I'm trying to learn something new every day...

Updated on July 15, 2022

Comments

  • richey
    richey almost 2 years

    I tried to include in-app billing in my app and for the purpose of testing, based the whole procedure on the "TrivialDrive" example for version 3 of in-app billing (and implementing the unmodified versions of the IAB files as supplied in the "util" subdirectory of the demo), but it doesn't work for me - on LogCat, just before the app terminates with an error, it gives the message "In-app billing error: Illegal state for operation (launchPurchaseFlow): IAB Helper is not set up." (right after the startRegistered() function has been fired and given me the LOG message "Register button clicked; launching purchase flow for upgrade.")...

    Any idea what goes wrong here?

    Here are the relevant parts of my code:

    package com.mytest;
    
    (..)
    import com.mytest.iab.IabHelper; // the originals from the demo example, unmodified
    import com.mytest.iab.IabResult;
    import com.mytest.iab.Inventory;
    import com.mytest.iab.Purchase;
    
    public class Result3 extends Activity implements OnClickListener {
    
    private static final String TAG = "BillingService";
    
    private Context mContext;
    
    boolean mIsRegistered = false;
    
        // this has already been set up for my app at the publisher's console
    static final String IS_REGISTERED = "myregistered";
    
    static final int RC_REQUEST = 10001;
    
    // The helper object
    IabHelper mHelper; 
    
    /** Call when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.result3);
        mContext = this;
    
        String base64EncodedPublicKey = "[my public key]"; // (from publisher's console for my app)
    
        // Create the helper, passing it our context and the public key to verify signatures with
        Log.d(TAG, "Creating IAB helper.");
        mHelper = new IabHelper(this, base64EncodedPublicKey);
    
        // enable debug logging (for a production application, you should set this to false).
        mHelper.enableDebugLogging(true);
    
        // Start setup. This is asynchronous and the specified listener
        // will be called once setup completes.
        Log.d(TAG, "Starting setup.");
        mHelper.startSetup(new IabHelper.OnIabSetupFinishedListener() {
            public void onIabSetupFinished(IabResult result) {
                Log.d(TAG, "Setup finished.");
    
                if (!result.isSuccess()) {
                    complain("Problem setting up in-app billing: " + result);
                    return;
                }
    
                // Hooray, IAB is fully set up. Now, let's get an inventory of stuff we own.
                Log.d(TAG, "Setup successful. Querying inventory.");
                mHelper.queryInventoryAsync(mGotInventoryListener);
            }
        });
    
       // Set the onClick listeners
       findViewById(R.id.btnPurchase).setOnClickListener(this);
    }
    
    // Listener that's called when we finish querying the items we own
    IabHelper.QueryInventoryFinishedListener mGotInventoryListener = new IabHelper.QueryInventoryFinishedListener() {
        public void onQueryInventoryFinished(IabResult result, Inventory inventory) {
            Log.d(TAG, "Query inventory finished.");
            if (result.isFailure()) {
                complain("Failed to query inventory: " + result);
                return;
            }
    
            Log.d(TAG, "Query inventory was successful.");
    
            // Do we have the premium upgrade?
            mIsRegistered = inventory.hasPurchase(IS_REGISTERED);
            Log.d(TAG, "User is " + (mIsRegistered ? "REGISTERED" : "NOT REGISTERED"));
    
            setWaitScreen(false);
            Log.d(TAG, "Initial inventory query finished; enabling main UI.");
        }
    };      
    
    // User clicked the "Register" button.
    private void startRegistered() {
        Log.d(TAG, "Register button clicked; launching purchase flow for upgrade.");
        setWaitScreen(true);
        mHelper.launchPurchaseFlow(this, IS_REGISTERED, RC_REQUEST, mPurchaseFinishedListener);
    }
    
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        Log.d(TAG, "onActivityResult(" + requestCode + "," + resultCode + "," + data);
    
        // Pass on the activity result to the helper for handling
        if (!mHelper.handleActivityResult(requestCode, resultCode, data)) {
            // not handled, so handle it ourselves (here's where you'd
            // perform any handling of activity results not related to in-app billing..
            super.onActivityResult(requestCode, resultCode, data);
        }
        else {
            Log.d(TAG, "onActivityResult handled by IABUtil.");
        }
    }
    
    // Callback for when a purchase is finished
    IabHelper.OnIabPurchaseFinishedListener mPurchaseFinishedListener = new IabHelper.OnIabPurchaseFinishedListener() {
        public void onIabPurchaseFinished(IabResult result, Purchase purchase) {
            Log.d(TAG, "Purchase finished: " + result + ", purchase: " + purchase);
            if (result.isFailure()) {
                // Oh noes!
                complain("Error purchasing: " + result);
                setWaitScreen(false);
                return;
            }
    
            Log.d(TAG, "Purchase successful.");
    
            if (purchase.getSku().equals(IS_REGISTERED)) {
                Log.d(TAG, "User has registered..");
                alert("Thank you.");
                mIsRegistered = true;
                setWaitScreen(false);
            }
        }
    };
    
    // We're being destroyed. It's important to dispose of the helper here!
    @Override
    public void onDestroy() {
        // very important:
        Log.d(TAG, "Destroying helper.");
        if (mHelper != null) mHelper.dispose();
        mHelper = null;
    }
    
    void complain(String message) {
        Log.e(TAG, "**** Register Error: " + message);
        alert("Error: " + message);
    }
    
    void setWaitScreen(boolean set) {
        // just a dummy for now
    }
    
    void alert(String message) {
        AlertDialog.Builder bld = new AlertDialog.Builder(this);
        bld.setMessage(message);
        bld.setNeutralButton("OK", null);
        Log.d(TAG, "Showing alert dialog: " + message);
        bld.create().show();
    }
    
    @Override
    public void onClick(View v) {
        switch (v.getId()) {
        case R.id.btnPurchase:
            startRegistered();
            break;
        default:
            break;
        }
    }
    

    }

    Here more lines from Logcat:

    12-20 01:06:36.701: D/dalvikvm(299): GC_FOR_MALLOC freed 4262 objects / 308592 bytes in 84ms
    12-20 01:06:36.701: D/webviewglue(299): nativeDestroy view: 0x2ea718
    12-20 01:06:36.771: W/webcore(299): Can't get the viewWidth after the first layout
    12-20 01:07:07.111: W/webcore(299): Can't get the viewWidth after the first layout
    12-20 01:07:18.510: D/webviewglue(299): nativeDestroy view: 0x2dd458
    12-20 01:07:18.510: D/dalvikvm(299): GC_FOR_MALLOC freed 6042 objects / 544504 bytes in 50ms
    12-20 01:07:18.530: D/webviewglue(299): nativeDestroy view: 0x2ea8d0
    12-20 01:07:18.660: D/BillingService(299): Creating IAB helper.
    12-20 01:07:18.660: D/BillingService(299): Starting setup.
    12-20 01:07:18.660: D/IabHelper(299): Starting in-app billing setup.
    12-20 01:07:19.621: W/webcore(299): Can't get the viewWidth after the first layout
    12-20 01:07:20.160: W/webcore(299): Can't get the viewWidth after the first layout
    12-20 01:07:32.481: D/webviewglue(299): nativeDestroy view: 0x3f88e8
    12-20 01:07:32.491: D/dalvikvm(299): GC_FOR_MALLOC freed 5798 objects / 513640 bytes in 50ms
    12-20 01:07:32.511: D/BillingService(299): Register button clicked; launching purchase flow for upgrade.    
    12-20 01:07:32.511: E/IabHelper(299): In-app billing error: Illegal state for operation (launchPurchaseFlow): IAB helper is not set up.
    12-20 01:07:32.521: D/AndroidRuntime(299): Shutting down VM
    12-20 01:07:32.521: W/dalvikvm(299): threadid=1: thread exiting with uncaught exception (group=0x4001d800)
    12-20 01:07:32.541: E/AndroidRuntime(299): FATAL EXCEPTION: main
    12-20 01:07:32.541: E/AndroidRuntime(299): java.lang.IllegalStateException: IAB helper is not set up. Can't perform operation: launchPurchaseFlow
    12-20 01:07:32.541: E/AndroidRuntime(299):  at com.test_ed.iab.IabHelper.checkSetupDone(IabHelper.java:673)
    12-20 01:07:32.541: E/AndroidRuntime(299):  at com.test_ed.iab.IabHelper.launchPurchaseFlow(IabHelper.java:315)
    12-20 01:07:32.541: E/AndroidRuntime(299):  at com.test_ed.iab.IabHelper.launchPurchaseFlow(IabHelper.java:294)
    12-20 01:07:32.541: E/AndroidRuntime(299):  at com.test_ed.Result3.startRegistered(Result3.java:157)
    12-20 01:07:32.541: E/AndroidRuntime(299):  at com.test_ed.Result3.onClick(Result3.java:248)
    12-20 01:07:32.541: E/AndroidRuntime(299):  at android.view.View.performClick(View.java:2408)
    12-20 01:07:32.541: E/AndroidRuntime(299):  at android.view.View$PerformClick.run(View.java:8816)
    12-20 01:07:32.541: E/AndroidRuntime(299):  at android.os.Handler.handleCallback(Handler.java:587)
    12-20 01:07:32.541: E/AndroidRuntime(299):  at android.os.Handler.dispatchMessage(Handler.java:92)
    12-20 01:07:32.541: E/AndroidRuntime(299):  at android.os.Looper.loop(Looper.java:123)
    12-20 01:07:32.541: E/AndroidRuntime(299):  at android.app.ActivityThread.main(ActivityThread.java:4627)
    12-20 01:07:32.541: E/AndroidRuntime(299):  at java.lang.reflect.Method.invokeNative(Native Method)
    12-20 01:07:32.541: E/AndroidRuntime(299):  at java.lang.reflect.Method.invoke(Method.java:521)
    12-20 01:07:32.541: E/AndroidRuntime(299):  at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:868)
    12-20 01:07:32.541: E/AndroidRuntime(299):  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:626)
    12-20 01:07:32.541: E/AndroidRuntime(299):  at dalvik.system.NativeStart.main(Native Method)
    
    • AndroidPenguin
      AndroidPenguin over 11 years
      This would be because setup hasn't finished or been unsuccessful so within IabHelper the service on connection has checked whether billing is supported and it isn't or the service hasn't connected at all. What's your entire logcat (or at least a few more lines)
    • richey
      richey over 11 years
      thanks, I just added them above.
    • Admin
      Admin over 11 years
      Are you testing on a 4.0 device? I am having the same issue but it works fine on lower APIs.
    • AndroidPenguin
      AndroidPenguin over 11 years
      @richey could you add your log cat from where iabhelper sets up
    • richey
      richey over 11 years
      I did, thanks for trying to help.
    • Admin
      Admin over 11 years
      What API level are you testing this on? I've found that bindService is returning false, which is where it stops. In IabHelper I have this check where the bindService happens: boolean bound = mContext.bindService(new Intent( "com.android.vending.billing.InAppBillingService.BIND"), mServiceConn, Context.BIND_AUTO_CREATE); logDebug("bindService " + bound);
    • Admin
      Admin over 11 years
      Try clearing the data/cache on the Google Play store, then running the Play Store once, then trying the in-app purchase again. It worked for me, although this isn't a suitable solution for users..
  • richey
    richey over 11 years
    No, actually I implemented it if you look at my code snippet above. Thanks.
  • Eric
    Eric over 11 years
    Oh I didn't notice that. Still, from the stackcall you've attached it seems that you're trying to trigger the purchaseFlow method when the startSetup is still in the run (didn't finished) or something within that function went wrong. I would suggest checking out what happens in startSetup function (debug it) before triggering the purchase
  • Bryant Harris
    Bryant Harris over 11 years
    I don't see the log out put from the Log.d(TAG, "Setup finished."); statement in your onIabSetupListener. So it doesn't seem like you are connecting. I've seen a similar issue on some of my older test devices. It works on the newer ones. What device/version of Android are you running this on?
  • britzl
    britzl about 11 years
    The documentation (developer.android.com/google/play/billing/billing_testing.h‌​tml) clearly state that "You cannot use the emulator to test In-app Billing; you must install your application on a device to test In-app Billing." And you must have the following version of Google Play on your phone as well (for API version 3): Purchasing and querying managed in-app items requires Google Play client version 3.9.16 or higher. Purchasing and querying subscription items requires Google Play client version 3.10.10 or higher.
  • NerdyTherapist
    NerdyTherapist about 11 years
    Shame on me, I clearly hadn't read that. But as for the original question, I had the same error log, so maybe the OP made the same mistake.
  • Sanjay Singh
    Sanjay Singh over 10 years
    I am facing the same problem but in different context , My purchase flow is initiated by a button click and its work perfectly. But only after commenting line in onCreate() mHelper.queryInventoryAsync(mGotInventoryListener); which fails because var mSetupDone in class IabHelper is still false. But result.isSuccess() is returning true.
  • Carl
    Carl over 10 years
    @Sanjay Singh: See this answer: stackoverflow.com/questions/17944522/…
  • idish
    idish over 10 years
    Hi Carl, I'm facing the same problem. I'm getting many app reports on this one. Even though, the devices that I have tested on, work perfectly. If the user doesn't have internet connection, will it crash the same way? I suspect that internet is the real problem, because after the setup occures, it takes to the user around 30 seconds to launch the purchase flow. It doesn't make sense that the setup won't be completed in 30 seconds isn't it?
  • idish
    idish over 10 years
    Hi Carl, I'm facing the same problem. I'm getting many app reports on this one. Even though, the devices that I have tested on, work perfectly. If the user doesn't have internet connection, will it crash the same way?If so, I suspect that internet might be the real problem, because after the setup occures, the user will click on a button approximately after around 30 seconds to launch the purchase flow. It doesn't make sense that the setup won't be completed in 30 seconds isn't it?
  • Carl
    Carl over 10 years
    @idish: Since you have been unable to reproduce the error, I suggest that you try testing on a device that has the developer option "Don't Keep Activities," which causes your app to be destroyed each time the user leaves it, to simulate what the OS will do when it needs to reclaim memory, so that you can make sure your app works under those conditions. If you have an older Android version that lacks that option, you can get similar functionality by installing the Developers Tools app from PandaDev (no connection) here: play.google.com/store/apps/details?id=com.ggb.development
  • Carl
    Carl over 10 years
    @idish: I'm guessing that if you're relying on a 30 second user delay, you do not wait until setup is confirmed before using IabHelper. This fails if the user has gone to the GP purchase dialog and the OS needs memory for that and flushes your own app from memory. Then, user confirms purchase and your app is restarted and onCreate() starts the process of re-creating IabHelper object but before it finishes your onActivityResult() is called and tries to use the IabHelper object. See this: stackoverflow.com/questions/17944522/…
  • idish
    idish over 10 years
    I think you got the core problem in your second comment, I'm using IabHelper as singletone. I'm pretty sure that this happens because of what you have just explained now. Thanks alot for that info, I'll try to solve it now.
  • idish
    idish over 10 years
    I've been using the IabHelper as a singletone, and "startSetup" only once, in my first activity. I guess the solution will be to stop using the singletone pattern and start setup on each IabHelper I instantiate. There are alot of activities from which the users can purchase the products, I was trying to make my life easier, I guess it caused these problems.
  • idish
    idish over 10 years
    I never had the idea that the OS might recycle my app to launch the purchase flow, I'm fixing everything now. You're great, thank you so much.
  • Carl
    Carl over 10 years
    Yeah, that happens a lot on the older devices; not so much on the new phones and tablets with lots of available memory. So if you're testing on a Nexus7 you might not encounter that unless you test with "Don't Keep Activities," but your users with their older Froyo or Gingerbread devices will.
  • Abdul Waheed
    Abdul Waheed over 8 years
    I have same issue when I checked as mentioned startSetup() function it shows mSetupDone it has false value but on tablets it is working fine. I dont' know why
  • shaby
    shaby over 7 years
    I faced the same issue if there was no google account added. in my case, I added google account in the device and problem resolved.