35

I'm implementing In App Billing for the first time and I'm testing my first purchases using the static SKU ids.

It worked very well the first time. I called mHelper.launchPurchaseFlow(...) and completed the test purchase. My activity received the onActivityResult callback and I made sure to process it with mHelper.handleActivityResult(...). Everything was great.

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    // Pass on the activity result to the helper for handling
    log("onActivityResult");
    if (!this.mHelper.handleActivityResult(requestCode, resultCode, data)) {
        log("cleared the launch flow");
        // 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);
    }
}

However, I wanted to test the next part, so I relaunched the app and tried to purchase the same SKU (the static purchased SKU).

mHelper.launchPurchaseFlow(rootActivity, "android.test.purchased", 10002,   
       new IabHelper.OnIabPurchaseFinishedListener() {

        @Override
        public void onIabPurchaseFinished(IabResult result, Purchase purchaseInfo) {
            if (result.isFailure()) {
                log("purchased failed");
            } else {
                log("purchase succeeded");
            }
        }
    }, "bGoa+V7g/yqDXvKRqq+JTFn4uQZbPiQJo4pf9RzJ");

The second time I try to purchase the item, my OnIabPurchaseFinishedListener is called and I see purchase failed in my log: "In-app billing error: Unable to buy item, Error response: 7:Item Already Owned"

That makes sense, but if I try to purchase another item, then my app crashes with the following error:

java.lang.IllegalStateException: Can't start async operation (launchPurchaseFlow) because another async operation(launchPurchaseFlow) is in progress.

The onActivityResult callback doesn't happen when I try to do the purchase that fails, so the launch flow that failed doesn't get handled and cleaned up. So, when I try another purchase, that's why it crashes because it's still supposedly in the middle of the last failed transaction.

What am I doing wrong? How do I ensure that the launchPurchaseFlow() is cleaned up after a failure?

Juuso Ohtonen
  • 8,826
  • 9
  • 65
  • 98
Kenny Wyland
  • 20,844
  • 26
  • 117
  • 229

6 Answers6

43

I believe you just have to get the updated code the the in-app billing classes and you shouldn't run into the same problem again.

Google hasn't pushed out the changes to the SDK Manager yet as far as I know. Just copy/paste the new classes into yours and you shouldn't run into the problem any longer.

Have a look at the new code changes here: https://code.google.com/p/marketbilling/source/detail?r=7ec85a9b619fc5f85023bc8125e7e6b1ab4dd69f&path=/v3/src/com/example/android/trivialdrivesample/MainActivity.java

The classes that were changed as of March 15th are: IABHelper.java, Inventory.java, SkuDetails.java and some of the MainActivity.java file

joelreeves
  • 1,955
  • 1
  • 18
  • 24
  • That did it, thanks. I'm glad to know that it wasn't me, because I was sure I had implemented it as they said. :) – Kenny Wyland Apr 06 '13 at 01:10
  • 4
    This worked for me in my own devices BUT I still get the error through bugsense reports in dozens of my user's devices that have updated the app (and therefore they are using a compilation with the fixed classes). – Fran Marzoa May 23 '13 at 12:23
  • @Fran does the same thing happen if you have them either uninstall/re-install the app or going into Settings | Application and clearing the Google Play app's cache? – joelreeves May 23 '13 at 19:38
  • Any reason why Google haven't updated the code they ship with the SDK manager? Thanks for posting this! – Christer Nordvik Sep 14 '13 at 21:16
  • Looks like an update came out yesterday. Not sure which bugs were fixed: https://code.google.com/p/marketbilling/source/list – joelreeves Oct 10 '13 at 00:51
  • @jmrmb80 thanks for that, but is this bug fixed ? did you try it? – Goofy Oct 14 '13 at 09:17
  • The bug that the OP asked about was fixed by my answer. The one I just posted about mentioned other bug fixes. – joelreeves Oct 14 '13 at 19:40
  • @jmrmb80 can you please check my question http://stackoverflow.com/questions/29422942/in-app-billing-workflow-in-a-listview – Mostafa Addam Apr 07 '15 at 11:01
  • Sample on Android docs is correct/updated. Check http://stackoverflow.com/a/20485213/1708390 for the correct answer to this question – Bugs Happen Dec 10 '15 at 06:53
29

I know it is kind of late contribution to the question, but I was facing the same problem today and I was calling the in App billing within a fragment, so I looked in "labHelper.java" and I saw a direct solution I believe to the problem which is ... I modified the method "void flagStartAsync(String operation)" in labHelper.java to be like the following

void flagStartAsync(String operation) {
    if (mAsyncInProgress) {
        flagEndAsync();
    }
    if (mAsyncInProgress) throw new IllegalStateException("Can't start async operation (" +
            operation + ") because another async operation(" + mAsyncOperation + ") is in progress.");
    mAsyncOperation = operation;
    mAsyncInProgress = true;
    logDebug("Starting async operation: " + operation);
}

I hope this would help some one out there ...

alazmi95
  • 485
  • 5
  • 14
  • 1
    THIS is the solution if you are using fragments!! Basically, the new part is calling "flagEndAsync()" if the sync is still in progress when you're trying to start a new operation. This resolved an issue I've been having with the app crashing after payment for a very long time! – DiscDev Aug 25 '15 at 23:09
  • 4
    Just so everyone knows, this is a bad solution which breaks the whole point of the flagStartAsync method. This is the correct way to solve this issue: http://stackoverflow.com/a/20485213/354793 – Olof Hedman Nov 12 '15 at 15:02
10

For me, the best fix was to both udpate the code to the recent one (here), and do what this post suggest:

1) make method flagEndAsync public. It is there, just not visible.

2) have every listener call iabHelper.flagEndAsync to make sure the procedure is marked finished properly; it seems to be needed in all listeners.

3) surround calls with a try/catch to catch the IllegalStateException which may occur, and handle it that way.

The reason that updating the code wasn't enough is that I've found special cases where this bug still occurs (or at least one):

  • disconnect from the Internet;
  • enter your app;
  • let it initialize the IabHelper;
  • connect to the Internet;
  • once the device is connected, try to make a purchase.
Community
  • 1
  • 1
android developer
  • 114,585
  • 152
  • 739
  • 1,270
  • 1
    "surround calls with a try/catch to catch the IllegalStateException which may occur, and handle it that way." Such important thing. ;) – Nhat Dinh May 11 '15 at 04:27
  • 2
    @DinhNhat Actually, after so long of writing this, I don't remember what I meant by "handle it that way". – android developer May 11 '15 at 05:30
8

I have the same problem.

First attempt: Workaround

I downloaded the current IabHelper.java, as per jmrmb80's solution, but that didn't work. (It seems that the repo is now deprecated and we should rely upon the version supplied by Android SDK manager.) So I followed Khan's advice:

  • define IabHelper.flagEndAsync() as public, and
  • add iabHelper.flagEndAsync() before iabHelper.launchPurchaseFlow(...)

This seems like a blatant hack! And it may have undesirable side effects. But, it does "work"...

This seems to be a known bug: #134 and #189.

Second attempt: Fix

After further investigation, I don't think the above workaround solved my problem. I think the real solution is to override onActivityResult in the UI thread.

Community
  • 1
  • 1
user2768
  • 794
  • 8
  • 31
2

No need for hacky solutions. The Activity or Fragment that is requesting the purchase flow should have this:

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    Log.d(TAG, "onActivityResult(" + requestCode + "," + resultCode + "," + data);
    if (billingHelper == null) return;

    // Pass on the activity result to the helper for handling
    if (!billingHelper.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.");
    }
}

That's from Google's sample project, tried it on my project and it works.

RominaV
  • 3,335
  • 1
  • 29
  • 59
0

Error response: 7:Item Already Owned means you bought item but you haven't consumed it yet and you try to buy it again.

This happened to me when I set in AndroidManifest launchMode in my in-app acitivity to singleInstance. App always finished with error you described.

To avoid this behaviour, change your launchMode to any other value that fits to your needs android:launchMode="singleInstance" -> android:launchMode="singleTask"

I didn't try to understand deeply why singleInstance doesn't work. If someone knows please provide more info.

So my solution was to change launchMode and consume already owned item. Since that time IAP works fine for me.

HefferWolf
  • 3,894
  • 1
  • 23
  • 29
Honza Musil
  • 320
  • 2
  • 10