Our android app is a chat app. Users can paste a branch link in a chat message. When another user taps on it, we want to retrieve the link parameters to take the user to another screen.
Unfortunately, we are unable to retrieve the link parameters when we tap on such link inside the app (note that we are not using a webview), we are getting the error "Warning. Session initialisation already happened.
To force a new session, set intent extra, branch_force_new_session, to true in the onInitFinished(#Nullable JSONObject referringParams, #Nullable BranchError error) method.
How can we solve this? It's not obvious to me how I could pass a new intent param in that use case.
Notes:
Our launcher activity is singleTask
We are on branch.io sdk 4.3.2
onNewIntent() does not seem to be called (in the code below), maybe that is the root cause for our issue.
sample code:
private Branch.BranchReferralInitListener branchReferralInitListener =
new Branch.BranchReferralInitListener() {
#Override
public void onInitFinished(#Nullable JSONObject referringParams, #Nullable BranchError error) {
...
}
#Override
protected void onStart() {
super.onStart();
Branch.getInstance().initSession(branchReferralInitListener, getIntent() != null ?
getIntent().getData() : null, this);
}
#Override
public void onNewIntent(Intent intent) {
super.onNewIntent(intent);
this.setIntent(intent);
// activity will skip onStart, handle this case with reInitSession
Branch.getInstance().reInitSession(this, branchReferralInitListener);
}
This is a known issue with the Android SDK v4.3.2 and we are working on a resolution.
Would suggest you to try the following in the meanwhile:
When the user tries to open an app that is running in the background, we get an error for set branch_force_new_session to true.
Branch SDK gets initialised on onStart for the Launcher Activity and when the app comes foreground from the background, its on onResume.
In this scenario, we could need to re-initialise the SDK here.
Would request you to implement the below snippet as per Branch docs (https://docs.branch.io/apps/android/#initialize-branch)
// activity will skip onStart, handle this case with reInitSession
Branch.getInstance().reInitSession(this, branchReferralInitListener);
Alternatively, would suggest you to install Branch SDK v4.3.1.
Initialized, you branch IO in application class so that it will initialize once and will not be require again
// Branch logging for debugging
Branch.enableLogging();
//Disable Device ID #2966
Branch.disableDeviceIDFetch(true);
// Initialize the Branch object
BranchIOManager.setupBranchInstance(this);
// It tells the Branch initialization to wait for the Google Play Referrer before proceeding.
Branch.enablePlayStoreReferrer(1000L);
Then inside initSession() branch method use. Pass them as JSON Object to method where you can retrieve the value based on key names.
if (branch != null && uri != null) {
branch.initSession(new Branch.BranchUniversalReferralInitListener() {
#Override
public void onInitFinished(BranchUniversalObject branchUniversalObject, LinkProperties linkProperties, BranchError error) {
// Log.d("onInitFinished", error + "");
if (error == null && branchUniversalObject != null) {
JSONObject jBranch = branchUniversalObject.getContentMetadata().convertToJson();
if (!branchJSONString.equals(jBranch.toString())) {
//This check is applied as if we launch another mLandingScreenPhoneActivity from Branch link then app will become in loop
branchJSONString = jBranch.toString();
loadScreenFromBranchIODynamicLink(jBranch, 0);
}
}
if (error != null) {
// //Toast.makeText(LandingScreenPhoneActivity.this, error + "", Toast.LENGTH_SHORT).show();
}
}
}, uri, this);
}
Here you can get screen values
String screenName = referringParams.optString("screen_key");
//Screen in app where needs to navigate
int screenIndex = referringParams.optInt("screen_index");
I'm using sromku to share score from my LibGDX game.
The feed was successfully published, but no one can see it but myself (even when my profile is seen by others).
Code:
public class AndroidLauncher extends AndroidApplication implements FacebookConn {
SimpleFacebook mSimpleFacebook;
OnPublishListener onPublishListener = new OnPublishListener() {
#Override
public void onComplete(String postId) {
Log.i("OnComplete", "Published successfully. The new post id = " + postId);
}
};
#Override
protected void onCreate (Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Permission[] permissions = new Permission[] {
Permission.USER_PHOTOS,
Permission.EMAIL,
Permission.PUBLISH_ACTION,
};
SimpleFacebookConfiguration configuration =
new SimpleFacebookConfiguration.Builder()
.setAppId("410074072507300")
.setNamespace("sromkuapp")
.setPermissions(permissions)
.build();
SimpleFacebook.setConfiguration(configuration);
AndroidApplicationConfiguration config = new AndroidApplicationConfiguration();
config.useImmersiveMode = true;
initialize(new Jump4Love(this), config);
}
#Override
public void onResume() {
super.onResume();
mSimpleFacebook = SimpleFacebook.getInstance(this);
}
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
mSimpleFacebook.onActivityResult(this, requestCode, resultCode, data);
super.onActivityResult(requestCode, resultCode, data);
}
#Override
public void submitScore(int score) {
Feed feed = new Feed.Builder()
.setMessage("message")
.setName("name")
.setCaption("caption")
.setDescription("description")
.setPicture("picture")
.setLink("link")
.build();
// Note: the values above aren't the actual values I'm using
// I only simplified them so my code becomes easier to read
mSimpleFacebook.publish(feed, true, onPublishListener);
}
}
I found a similar case in StackOverflow here.
The OP said the following:
Only without using "link" and "picture" it does appear on my friend's
News Feed
I tried removing the link and picture but the result was still the same, so my case is kinda different.
That post still has no acceptable answer btw.
======================================================================
It's useless to promote my game in Facebook if no one can see it.
I have seen posts from friends promoting other games, so I know it is possible.
Anyone has any idea why I'm having this problem?
You need to set your app “live”.
Go to Status & Review tab in app dashboard, on top you find
“Do you want to make this app and all its live features available to the general public?”
… that one you need to set to Yes.
Before setting this to Yes, your app is considered to be in “development mode”, and therefor posts made via it are not shown to users that do not have a role in your app (admin/developer/tester).
Sorry for my poor English and the fact I am a newbie on Android development.
I'm developping an Android app which should send data to the datastore of Dropbox. My problem is, when my code is getting this line:
mAccountManager.startLink(DropboxHelper.this,REQUEST_LINK_TO_DBX);
Then my logcat send me the message below:
10-19 10:40:32.411: W/com.dropbox.client2.android.AuthActivity(28381): There are multiple
apps registered for the AuthActivity URI scheme (db-qeojdcjk0dkkswc). Another app may be
trying to impersonate this app, so authentication will be disabled.
The closer explanation I have found is this one:
Android + DropboxSync startLink
But the comments have not helped me.
Here my code :
public class DropboxHelper extends ActionBarActivity {
// *** Objects and APP_KEY + APP_SECRET are instantiate here ***
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.dropbox_activity);
// Set up the account manager
mAccountManager = DbxAccountManager.getInstance(getApplicationContext(), APP_KEY, APP_SECRET);
mUnlinkButton = (Button) findViewById(R.id.unlink_button);
mUnlinkButton.setVisibility(View.GONE);
// Button to link to Dropbox
mLinkButton = (Button) findViewById(R.id.link_button);
mLinkButton.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
if ( mAccountManager.getLinkedAccount() == null )
mAccountManager.startLink(DropboxHelper.this, REQUEST_LINK_TO_DBX);
else
{
Toast.makeText(DropboxHelper.this, "Connection déjà établie, vous pouvez vous déconnecter si vous le souhaitez", Toast.LENGTH_LONG).show();
mUnlinkButton.setVisibility(View.VISIBLE);
}
}
});
mUnlinkButton.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
mAccountManager.unlink();
Toast.makeText(DropboxHelper.this, "Déconnecté", Toast.LENGTH_LONG).show();
}
});
// Set up the datastore manager
if (mAccountManager.hasLinkedAccount()) {
try {
// Use Dropbox datastores
mDatastoreManager = DbxDatastoreManager.forAccount(mAccountManager.getLinkedAccount());
mLinkButton.setVisibility(View.GONE);
} catch (DbxException.Unauthorized e) {
System.out.println("Account was unlinked remotely");
}
}
if (mDatastoreManager == null) {
// Account isn't linked yet, use local datastores
mDatastoreManager = DbxDatastoreManager.localManager(mAccountManager);
// Show link button
mLinkButton.setVisibility(View.VISIBLE);
}
}
#Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_LINK_TO_DBX) {
if (resultCode == Activity.RESULT_OK) {
account = mAccountManager.getLinkedAccount();
try {
// Migrate any local datastores to the linked account
mDatastoreManager.migrateToAccount(account);
// Now use Dropbox datastores
mDatastoreManager = DbxDatastoreManager.forAccount(account);
// Hide link button
mLinkButton.setVisibility(View.GONE);
} catch (DbxException e) {
e.printStackTrace();
}
} else {
// Link failed or was cancelled by the user
}
} else {
super.onActivityResult(requestCode, resultCode, data);
}
}
}
I can't figure out How to solve this issue, I really thank you If you can help me.
Best regards.
This generally means you have more than one app installed using the same app key. Often, this can happen if you have, for example, a sample app from the SDK as well as the app you're developing installed on the same device (or virtual device) using your app key. The solution is just to uninstall one of the apps, or use a different app key in one of them.
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);
}
});