I am testing my billing and I got this exception:
java.lang.IllegalStateException: Can't start async operation (launchPurchaseFlow) because another async operation(launchPurchaseFlow) is in progress.
at utils.IabHelper.flagStartAsync(IabHelper.java:711)
at utils.IabHelper.launchPurchaseFlow(IabHelper.java:316)
at utils.IabHelper.launchPurchaseFlow(IabHelper.java:294)
at com.problemio.SubscribeIntroActivity$6.onClick(SubscribeIntroActivity.java:117)
at android.view.View.performClick(View.java:2532)
at android.view.View$PerformClick.run(View.java:9308)
at android.os.Handler.handleCallback(Handler.java:587)
at android.os.Handler.dispatchMessage(Handler.java:92)
at android.os.Looper.loop(Looper.java:150)
at android.app.ActivityThread.main(ActivityThread.java:4293)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:507)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:839)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:597)
at dalvik.system.NativeStart.main(Native Method)
After I ran this code:
Button subscribe = (Button)findViewById(R.id.subscribe);
subscribe.setOnClickListener(new Button.OnClickListener()
{
public void onClick(View v)
{
// FIRST CHECK IF THE USER IS ALREADY A SUBSCRIBER.
mHelper.launchPurchaseFlow(SubscribeIntroActivity.this, SUBSCRIBE_SKU, RC_REQUEST, mPurchaseFinishedListener);
}
});
But prior to this I ran it as a test user and with the test product id which was this: android.test.purchased and it worked. But when I changed product id to one of my own products ids, it crashed with the exception above.
Any ideas why that happened?
Thanks!
The IabHelper will only allow a single asynchronous query to be executed at a time. You need to implement onActivityResult() and pass the parameters into the handleActivityResult() method of the IabHelper.
The in-app billing sample code implements the method like this:
#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.");
}
}
Just in case someone is missing the forest for the trees like I was...
I received a java.lang.IllegalStateException stack trace in the Play Developer Console which didn't provide much more than the error message... so I was stumped.
I couldn't figure out how this was happening at first because I never thought to try tapping the button that triggers IAB twice! (it looks disabled after the first tap due to an overlay that let's taps through, [sometimes]).
So, make sure your users can't tap your button twice.
You are using sample code of google and in IabHelper class line 793 there is this piece of code
if (mAsyncInProgress) throw new IllegalStateException("Can't start async operation (" +
operation + ") because another async operation(" + mAsyncOperation + ") is in progress.");
and when you make a purchase for first time 'mAsyncInProgress' becomes true,and until you haven't consumed your purchase it remains true ,so you need to consume your purchase.
I recommend you to read all Classes in util package completely,it will help you.
after any successful purchase you need to consume it
mHelper.consumeAsync(purchase, mConsumeFinishedListener)
but sometimes the consume request fails so you need to handle your purchases every time your activity is created :
mHelper.queryInventoryAsync(mGotInventoryListener);
and try to consume your purchases in mGotInventoryListener callback.
get the latest version of the library here: https://code.google.com/p/marketbilling/source/browse/ where they fixed the problem
Related
I am trying to display the Drop-in UI in my app upon clicking a specific button. I have used the guide from Braintree site but for some reason nothing is happening.
Code below:
OnClick function:
public void onClick(View v){
switch (v.getId()){
case R.id.showUI_button:
onBraintreeSubmit(v);
break;
}
}
Drop-in functions:
public void onBraintreeSubmit(View v) {
PaymentRequest paymentRequest = new PaymentRequest()
.clientToken(token)
.amount("$10.00")
.primaryDescription("Awesome payment")
.secondaryDescription("Using the Client SDK")
.submitButtonText("Pay");
startActivityForResult(paymentRequest.getIntent(this), REQUEST_CODE);
}
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_CODE) {
if (resultCode == BraintreePaymentActivity.RESULT_OK) {
PaymentMethodNonce paymentMethodNonce = data.getParcelableExtra(
BraintreePaymentActivity.EXTRA_PAYMENT_METHOD_NONCE
);
String nonce = paymentMethodNonce.getNonce();
// Send the nonce to your server.
}
}
}
I have checked that the token is returned from the server.
I have also tried by setting the onClick via the xml code of the button and removing the onClick from the java file but the result is the same, no UI shown.
The log has only two lines
performCreate Call Injection Manager
Timeline: Activity_idle id:android.os.BinderProxy#etc
Any ideas? If more info is needed to understand better let me know
Actually I found this there is a "BraintreeFragment" set up part. Braintree documentation needs to be more clear on this I think.
https://developers.braintreepayments.com/guides/client-sdk/setup/android/v2
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
try {
mBraintreeFragment = BraintreeFragment.newInstance(this, mAuthorization);
// mBraintreeFragment is ready to use!
} catch (InvalidArgumentException e) {
// There was an issue with your authorization string.
}
}
The above code should work along with the previous code posted. mAuthorization is the token and needs to be valid to show the payment screen (so the variable "token" in the previous code posted which in my code I just have as private but visible from the whole activity).
Try with the test token that they have on their page and if this works then the main setup is ok.
https://developers.braintreepayments.com/start/hello-client/android/v2
For setting up tokens on your server, they have further documentation so that those test tokens work on the sandbox.
I am trying to integrate BrainTree into my app, but I'm having some issues with startActivityForResult() and onActivityResult(). I have already properly retrieved a clientToken but for some reason I'm always receiving a resultCode of 2 in onActivityResult. In addition I also get this in my Logs:
I/art: Rejecting re-init on previously-failed class
java.lang.Class
Any idea what's going on? Here's some snippets of my code in case it helps:
This is the onClick method in my fragment.
void onPaymentClick() {
PaymentRequest paymentRequest = new PaymentRequest().clientToken("<client_token>");
getActivity().startActivityForResult(paymentRequest.getIntent(storeOrderActivity), 1);
}
}
And this is the onActivityResult code in my activity. (PostOrder is the the function that's supposed to run once Braintree sends a nonce back to me, but this doesn't even run since I get a resultCode of 2)
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == 1) {
if (resultCode == Activity.RESULT_OK) {
PaymentMethodNonce paymentMethodNonce = data.getParcelableExtra(
BraintreePaymentActivity.EXTRA_PAYMENT_METHOD_NONCE
);
PostOrder.postOrder(this, fabCartAdapter.getCart(), paymentMethodNonce.getNonce());
}
}
}
The resultCode==2 is reserved for BRAINTREE_RESULT_DEVELOPER_ERROR:
The payment method flow halted due to a resolvable error (authentication, authorization, SDK upgrade required). The reason for the error will be returned in a future release.
Probably you need to double check if the SDK is set it up correctly.
I want to connect my game with google play service. i have read documentation on android developer and try to following type-a-number sample and still can't load leaderboard.
i have import baseGameUtils, but i use andengine so i didn't use extends BaseGameActivity from google.
what i have until now:
- GooglePlayServicesUtil.isGooglePlayServicesAvailable(this) return success
- startActivityForResult(pickAccountIntent, REQUEST_CODE_PICK_ACCOUNT); is working well and i got my account name from onActivityResult(..);
- i already put this on my manifest.
<meta-data android:name="com.google.android.gms.games.APP_ID"
android:value="#string/app_id" />
my questions are
1. can i use google play service without extends BaseGameActivity?
2. if i use gameHelper.beginUserInitiatedSignIn(); after i got my account name, i got this on log cat. (what this connected mean? because i still got error on next question)
08-25 00:09:01.890: D/BaseGameActivity(11222): isGooglePlayServicesAvailable returned 0
08-25 00:09:01.890: D/BaseGameActivity(11222): beginUserInitiatedSignIn: starting new sign-in flow.
08-25 00:09:01.890: D/BaseGameActivity(11222): All clients now connected. Sign-in successful.
08-25 00:09:01.890: D/BaseGameActivity(11222): All requested clients connected. Sign-in succeeded!
3 . how do i use connect()? i have read and tried about gameClient and GameClientBuilder but i have no idea how to use that. when i tried run this code.
startActivityForResult(gameHelper.getGamesClient().getAllLeaderboardsIntent(), RC_UNUSED);
i got this log.
08-25 00:09:05.660: E/AndroidRuntime(11222): java.lang.IllegalStateException: Not connected. Call connect() and wait for onConnected() to be called.
4 . to use leaderboard i know i must use code from google play store such as CgkIx****AIQAA. but i didn't found where i must put this code to load leaderboard.
sorry for long question, but i think if there is a sample that only for connect and either access achievement or leaderboard it will answer all my question. please don't tell me to see type-a-number sample, i did that and i need another sample code.
update, my snipped code
public class MainMenu extends Activity
implements OnClickListener, GooglePlayServicesClient.ConnectionCallbacks, GooglePlayServicesClient.OnConnectionFailedListener, GameHelperListener{
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main_menu);
gameHelper = new GameHelper(this);
}
#Override
public void onClick(View v) {
if(v.equals(loadData)) {
if(gameHelper.isSignedIn()) {
gameHelper.setup(this, GameHelper.CLIENT_GAMES, Scopes.GAMES);
startActivityForResult(gameHelper.getGamesClient().getAllLeaderboardsIntent(), RC_UNUSED);
}
}
else if(v.equals(loginButton)) {
Intent googlePicker = AccountPicker.newChooseAccountIntent(null,null,new String[]{GoogleAuthUtil.GOOGLE_ACCOUNT_TYPE},true,null,null,null,null) ;
startActivityForResult(googlePicker, REQUEST_CODE_PICK_ACCOUNT);
}
}
#Override
protected void onActivityResult(final int requestCode, final int resultCode, final Intent data) {
if(requestCode==REQUEST_CODE_RECOVER_PLAY_SERVICES) {
if (resultCode == RESULT_CANCELED) {
Toast.makeText(this, "Google Play Services must be installed.", Toast.LENGTH_SHORT).show();
finish();
}
return;
}
else if(requestCode==REQUEST_CODE_PICK_ACCOUNT) {
if (resultCode == RESULT_OK) {
String accountName = data.getStringExtra(AccountManager.KEY_ACCOUNT_NAME);
gameHelper.beginUserInitiatedSignIn();
}
else if (resultCode == RESULT_CANCELED) {
Toast.makeText(this, "This application requires a Google account.", Toast.LENGTH_SHORT).show();
finish();
}
return;
}
super.onActivityResult(requestCode, resultCode, data);
}
// this 2 methods not called, is this also because my code is wrong?
#Override
public void onSignInFailed() {
Log.d("rush", "on sign in failed");
}
#Override
public void onSignInSucceeded() {
Log.d("rush", "on sign in succeed");
}
}
Yes. Take a look at the BaseGameActivity source and see that it largely just wraps GameHelper. You can implement the calls to GameHelper yourself - in fact, you can probably copy some code directly from BaseGameActivity. I'm a bit confused, because it appears that your code is already using GameHelper. It looks like you are mixing GameHelper calls with BaseGameActivity calls. You cannot do this, and it will result in... errors like you are getting.
The LogCat you see means that all of your clients are connected. The default call to GameHelper.setup() just requests the Games client. If you aren't using BaseGameActivity and want different clients, do:
gameHelper = new GameHelper(this);
gameHelper.setup(this, GameHelper.CLIENT_GAMES | GameHelper.CLIENT_PLUS);
beginUserInitiatedSignIn() is an asynchronous method with a callback when it finishes. Are you running it that way? GameHelper.GameHelperListener is the interface to implement. If you are using gameHelper, make sure to register the callback. See the this in the setup call above? That's registering the callback (this is my main activity).
As I said above, it looks like you are mixing GameHelper calls with BaseGameActivity calls. The GameHelper that is connected is the BaseGameActivity.mHelper instance, not any GameHelper you might have instantiated. Make sure that if you are using BaseGameActivity that you are not using GameHelper as well.
If you want to display a single leaderboard, use the GamesClient.getLeaderboardIntent(string, int) or method to get the Intent. The string is the code you have (CgkIx****AIQAA).
startActivityForResult(gameHelper.getGamesClient().getLeaderboardIntent(
leaderboard_id, RC_UNUSED);
Again, make sure you are using the correct getGamesClient() method, depending on if you are using BaseGameActivity or GameHelper directly.
Here is basic information how to use GameHelper without BaseGameActivity:
https://developers.google.com/games/services/android/init#using_gamehelper_without_basegameactivity
I am using the IabHelper utility classes, as recommended by Google's tutorial, and I'm being hit hard by this error. Apparently IabHelper can not run multiple async operations at the same time. I even managed to hit it by trying to start a purchase while the inventory taking was still in progress.
I have already tried to implement onActivityResult in my main class as suggested here, but I don't even get a call to that method before the error hits. Then I found this but I have no idea where to find this flagEndAsync method - it's not in the IabHelper class.
Now I'm looking for a way around this (without reimplementing the whole she-bang). The only solution I can think of is to create a boolean field asyncActive that is checked before an async task is started, and not do it if there is another task active. But that has many other problems, and doesn't work across activities. Also I'd prefer to have an async task queue up and run as soon as it's allowed to, instead of not running at all.
Any solutions for this issue?
A simple tricky solution
before calling purchaseItem method just add this line
if (billingHelper != null) billingHelper.flagEndAsync();
so your code looks this way
if (billingHelper != null) billingHelper.flagEndAsync();
purchaseItem("android.test.purchased");
Note: don't forget to make public flagEndAsync() method in IabHelper if you call it from another package.
Make sure that you call the IabHelper's handleActivityResult in the Activity's onActivityResult, and NOT in the Fragment's onActivityResult.
The following code snippet is from TrivialDrive's MainActivity:
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
Log.d(TAG, "onActivityResult(" + requestCode + "," + resultCode + "," + data);
if (mHelper == null) return;
// 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.");
}
}
Update:
There is now a In-app Billing Version 3 API (what was the version in 2013?)
The code sample has moved to Github. Snippet above edited to reflect current sample, but is logically the same as before.
This was not easy to crack but I found the needed workarounds. Quite disappointed with Google lately, their Android web sites have become a mess (very hard to find useful info) and their sample code is poor. When I was doing some Android development a few years ago it all went so much easier! This is yet another example of that...
Indeed IabUtil is buggy, it does not properly call off its own async tasks. The complete set of necessary workarounds to stabilise this thing:
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.
I ended up doing something similar to Kintaro. But added mHelper.flagEndAsync() to the end of the catch. The user still gets the toast but by the next time they push the purchase button, the async operation has been killed and the purchase button is ready to go again.
if (mHelper != null) {
try {
mHelper.launchPurchaseFlow(this, item, RC_REQUEST, mPurchaseFinishedListener, "");
}
catch(IllegalStateException ex){
Toast.makeText(this, "Please retry in a few seconds.", Toast.LENGTH_SHORT).show();
mHelper.flagEndAsync();
}
}
Find flagEndAsync() inside IabHelper.java file and change it to a public function.
Before trying purchase call flagEndAsync() for your IabHelper
You must do somthig like this code :
mHelper.flagEndAsync();
mHelper.launchPurchaseFlow(AboutActivity.this, SKU_PREMIUM, RC_REQUEST, mPurchaseFinishedListener, "payload-string");
I was having the same issue until I stumbled upon another SO thread. I'm including a touched up version of the code found in the other thread that you need to include in your Activity that initialises the purchase.
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
// Pass on the activity result to the helper for handling
// NOTE: handleActivityResult() will update the state of the helper,
// allowing you to make further calls without having it exception on you
if (billingHelper.handleActivityResult(requestCode, resultCode, data)) {
Log.d(TAG, "onActivityResult handled by IABUtil.");
handlePurchaseResult(requestCode, resultCode, data);
return;
}
// What you would normally do
// ...
}
A simple trick that did it for me was to create a method in IabHelper:
public Boolean getAsyncInProgress() {
return mAsyncInProgress;
}
and then in your code, just check:
if (!mHelper.getAsyncInProgress())
//launch purchase
else
Log.d(TAG, "Async in progress already..)
Really annoying issue. Here is a quick and dirty solution that is not perfect code wise, but that is user friendly and avoids bad ratings and crashes:
if (mHelper != null) {
try {
mHelper.launchPurchaseFlow(this, item, RC_REQUEST, mPurchaseFinishedListener, "");
}
catch(IllegalStateException ex){
Toast.makeText(this, "Please retry in a few seconds.", Toast.LENGTH_SHORT).show();
}
}
This way the user just has to tap another time (2 times at worst) and gets the billing popup
Hope it helps
Just check for the onActivityResult requestCode on the activity and if it matches the PURCHASE_REQUEST_CODE you used on the purchase just pass it to the fragment.
When you add or replace the fragment in the FragmentTransaction just set a tag:
fTransaction.replace(R.id.content_fragment, fragment, fragment.getClass().getName());
Then on your activity's onActivityResult
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if(requestCode == PurchaseFragment.PURCHASE_REQUEST_CODE) {
PurchaseFragment fragment = getSuportFragmentManager().findFragmentByTag(PurchaseFragment.class.getNAme());
if(fragment != null) {
fragment.onActivityResult(requestCode, resultCode, data);
}
}
}
if you code in fragment then you this code in IabHelper.java
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);
}
Or, you can get the latest IabHelper.java file here: https://code.google.com/p/marketbilling/source/browse/
The March 15th version fixed this for me. (Note other files with no changes were committed on the 15th)
I still had to fix one crash that happened during testing caused by a null intent extras when "android.test.canceled" was the sent SKU. I changed:
int getResponseCodeFromIntent(Intent i) {
Object o = i.getExtras().get(RESPONSE_CODE);
to:
int getResponseCodeFromIntent(Intent i) {
Object o = i.getExtras() != null ? i.getExtras().get(RESPONSE_CODE) : null;
I have had this issue occasionally, and in my case I've tracked it down to the fact that if the onServiceConnected method in IabHelper can be called more than once if the underlying service disconnects and reconnects (e.g. due to an intermittent network connection).
The specific operations in my case were "Can't start async operation (refresh inventory) because another async operation(launchPurchaseFlow) is in progress."
The way that my app is written, I can't call launchPurchaseFlow until after I've had a completed queryInventory, and I only call queryInventory from my onIabSetupFinished handler function.
The IabHelper code will call this handler function whenever its onServiceConnected is called, which can happen more than once.
The Android documentation for onServiceDisconnected says:
Called when a connection to the Service has been lost. This typically happens when the
process hosting the service has crashed or been killed. This does not remove the
ServiceConnection itself -- this binding to the service will remain active, and you will
receive a call to onServiceConnected(ComponentName, IBinder) when the Service is next
running.
which explains the problem.
Arguably, IabHelper shouldn't call the onIabSetupFinished listener function more than once, but on the other hand it was trivial to fix the problem in my app by simply not calling queryInventory from within this function if I've already done it and got the results.
Another major issue with the IabHelpr class is the poor choice of throwing RuntimeExcptions (IllegalStateException) in multiple methods. Throwing RuntimeExeptions from your own code in most cases is not desirable due to the fact that they are unchecked exceptions. That is like sabotaging your own application- if not caught, these exceptions will bubble up and crash your app.
The solution to this is to implement your own checked exception and change the IabHelper class to throw it, instead of the IllegalStateException. That will force you to handle this exception everywhere it could be thrown in your code at compile time.
Here is my custom exception:
public class MyIllegalStateException extends Exception {
private static final long serialVersionUID = 1L;
//Parameterless Constructor
public MyIllegalStateException() {}
//Constructor that accepts a message
public MyIllegalStateException(String message)
{
super(message);
}
}
Once we make the changes in the IabHelper class, we can handle our checked exception in our code where we call the class methods. For example:
try {
setUpBilling(targetActivityInstance.allData.getAll());
} catch (MyIllegalStateException ex) {
ex.printStackTrace();
}
I had the same issue and the problem was that I didn't implement the method onActivityResult.
#Override
protected void onActivityResult(int requestCode,
int resultCode,
Intent data)
{
try
{
if (billingHelper == null)
{
return;
} else if (!billingHelper.handleActivityResult(requestCode, resultCode, data))
{
super.onActivityResult(requestCode, resultCode, data);
}
} catch (Exception exception)
{
super.onActivityResult(requestCode, resultCode, data);
}
}
Yes, i am also facing this issue but i resolved this but i resolved using
IabHelper mHelpermHelper = new IabHelper(inappActivity, base64EncodedPublicKey);
mHelper.flagEndAsync();
The above method stop all the flags. Its work for me must check
This answer directly addresses the problem that #Wouter has seen...
It is true that onActivityResult() must be triggered, like many people have said. However, the bug is that Google's code isn't triggering onActivityResult() in certain circumstances, i.e. when you're pressing your [BUY] button twice when running the debug build of your app.
Additionally, one major problem is that the user may be in a shaky environment (i.e. Bus or subway) and presses your [BUY] button twice... suddenly you've got yourself an exception !
At least Google fixed this embarrassing exception https://github.com/googlesamples/android-play-billing/commit/07b085b32a62c7981e5f3581fd743e30b9adb4ed#diff-b43848e47f8a93bca77e5ce95b1c2d66
Below is what I implemented in the same class where IabHelper is instantiated (for me, this is in the Application class) :
/**
* invokes the startIntentSenderForResult - which will call your activity's onActivityResult() when it's finished
* NOTE: you need to override onActivityResult() in your activity.
* NOTE2: check IAB code updates at https://github.com/googlesamples/android-play-billing/tree/master/TrivialDrive/app/src/main/java/com/example/android/trivialdrivesample/util
* #param activity
* #param sku
*/
protected boolean launchPurchaseWorkflow(Activity activity, String sku)
{
if (mIabIsInitialized)
{
try
{
mHelper.launchPurchaseFlow(
activity,
sku,
Constants.PURCHASE_REQUEST_ID++,// just needs to be a positive number and unique
mPurchaseFinishedListener,
Constants.DEVELOPER_PAYLOAD);
return true;//success
}
catch (IllegalStateException e)
{
mHelper.flagEndAsync();
return launchPurchaseWorkflow(activity, sku);//recursive call
}
}
else
{
return false;//failure - not initialized
}
}
My [BUY] button calls this launchPurchaseWorkflow() and passes the SKU and the activity the button is in (or if you're in a fragment, the enclosing activity)
NOTE: be sure to make IabHelper.flagEndAsync() public.
Hopefully, Google will improve this code in the near future; this problem is about 3 years old and it's still an ongoing problem :(
My solution is simple
1.) Make the mAsyncInProgress variable visible outside of IabHelper
public boolean isAsyncInProgress() {
return mAsyncInProgress;
}
2.) Use this in your Activity like:
...
if (mIabHelper.AsyncInProgress()) return;
mIabHelper.queryInventoryAsync(...);
...
A little-modified version of NadtheVlad's answer that works like charm
private void makePurchase() {
if (mHelper != null) {
try {
mHelper.launchPurchaseFlow(getActivity(), ITEM_SKU, 10001, mPurchaseFinishedListener, "");
}
catch(IllegalStateException ex){
mHelper.flagEndAsync();
makePurchase();
}
}
}
The logic is simple, just put the launchPurchaseFlow() thing in a method and use recursion in the catch block. You still need to make flagEndAsync() public from the IabHelper class.
I have same issue, but it resolved!
I think you must be not run "launchPurchaseFlow" on UI thread, try to run launchPurchaseFlow on UI thread,
it would be working fine!
mActivity.runOnUiThread(new Runnable(){
public void run(){
mHelper.launchPurchaseFlow(mActivity, item, 10001, mPurchaseFinishedListener,username);
}
});
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)
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.
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.
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.
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.
If you have lucky patcher installed and the modded playstore you will get the message " iab helper is not set up " .
Solution : install an android emulator on your PC or lucky patcher and the modded playstore will not be installed.You will no longer have the error message and will be able to make your payment, it worked perfect for me :)