how to connect to google play service and load leaderboard - android

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

Related

Fail to bind InAppBillingService when using IabHelper

I'm currently developing a game and try to implement In-app billing V3 of Google Play. I had followed the In-app sample and used IabHelper. However, when running the app in device, something goes wrong. I found that after mHelper.startSetup, neither onServiceDisconnected nor onServiceConnected was called. So I printed the result of mContext.bindService(serviceIntent, mServiceConn, Context.BIND_AUTO_CREATE); in the IabHelper Class. The result is false.
Here is my code:
private IabHelper mHelper;
// MainActivity onCreate
protected void onCreate(Bundle savedInstanceState){
// ...
mHelper = new IabHelper(this, base64EncodedPublicKey);
mHelper.enableDebugLogging(true);
mHelper.startSetup(new IabHelper.OnIabSetupFinishedListener() {
public void onIabSetupFinished(IabResult result) {
if (!result.isSuccess()) {
return;
}
if (mHelper == null) return;
}
});
}
And in the method startSetup of IabHelper Class:
public void startSetup(final OnIabSetupFinishedListener listener) {
// ...
Intent serviceIntent = new Intent("com.android.vending.billing.InAppBillingService.BIND");
serviceIntent.setPackage("com.android.vending");
if (!mContext.getPackageManager().queryIntentServices(serviceIntent, 0).isEmpty()) {
// service available to handle that Intent
boolean bRes = mContext.bindService(serviceIntent, mServiceConn, Context.BIND_AUTO_CREATE);
// ### I printed the result here it was false
Log.i("IAB", "IAB Service Result = " + bRes);
}
else {
// no service available to handle that Intent
if (listener != null) {
listener.onIabSetupFinished(
new IabResult(BILLING_RESPONSE_RESULT_BILLING_UNAVAILABLE,
"Billing service unavailable on device."));
}
}
}
I don't know the problem. I have done:
add permission: com.android.vending.BILLING
copy the .aidl file from sample to /src/com/android/vending/billing
my android target set to android-22
Any ideas what's wrong?
Thanks in advance.
Finally, I solved it by uploading the latest version of my game to developer console and debug the latest apk. The problem is gone. Hope this helps someone.
Copy all the class from util folder from the Project "Trivial"
or you can copy whole Util package into your project. And then give a try.
I had this problem, and found that
The app version debugged must be the same as the version released in developer console.
Play service needs to be available, some phones may actually kill the service after a screen lock, start play store, then redo the binding worked.
Using IabHelper out of box may trigger problems like service disconnect not handled, re-initialize fail, I had to modify it a bit.

How to detect sign out via the Google Play Leaderboards UI?

Signing out or disconnecting the GamesClient is straightforward when it is from your own UI, such as a button on the main menu.
However, users can also sign out from the game from the Google Play UI in the acheivements and leaderboard views displayed by the intents such as getAllLeaderboardsIntent(). (It's a bit hidden, but if you tap the menu in the upper right, it lets you sign out.)
There are a few promising listener interfaces like OnSignOutCompleteListener but they don't seem to work with a sign out via the google UI, only from your own UI calling GamesClient.signOut().
How can I detect that the user has signed out from the leaderboards or achievement intents? Is it possible to have a callback for this?
I want to be able to update my in-game UI to reflect the logged-in status.
Unfortunately GameHelper doesn't detect when you logout from Google play games.
What you need to do is to put this in your onActivityResult() method in your activity.
I encounter a crash error when I tried using aHelper.signOut() when res == RESULT_RECONNECT_REQUIRED is true.
Instead I created a resetAllValues() method which resets back all values to its default in GameHelper.
In my MainActivity.java
protected void onActivityResult(int req, int res, Intent data) {
super.onActivityResult(req, res, data);
if (res == GamesActivityResultCodes.RESULT_RECONNECT_REQUIRED) {
aHelper.resetAllValues();
} else {
aHelper.onActivityResult(req, res, data);
}
}
My method in GameHelper.java
public void resetAllValues()
{
mProgressDialog = null;
mAutoSignIn = true;
mUserInitiatedSignIn = false;
mConnectionResult = null;
mSignInError = false;
mExpectingActivityResult = false;
mSignedIn = false;
mDebugLog = false;
}
Duplicate from:
How can i check if user sign's out from games services default view?
As I see it, there is no elegant solution to that. You can check the response_code in onActivityResult for INCONSISTENT_STATE and cut off the GamesClient, but I'm not sure, if you can potetially get to an inconsistent state in any other manner...

Google Play Game Services & libGDX "SignInActivity must be started with startActivityForResult"

I'm trying to implement Google Play Game Services into my libGDX game. I followed the tutorial here: http://helios.hud.ac.uk/u1070589/blog/?p=202
When my game loads, I create a new GameHelper object and call its setup method. I've debugged this part and it completes without any problems. I've placed a GPGS button on my main menu, when it's clicked I call the Login() method in my main Android Activity.
At this point the mConnectionResult.startResolutionForResult(mActivity, RC_RESOLVE) call in the resolveConnectionResult method (GameHelper class) returns the error:
E/SignInActivity(21930): SignInActivity must be started with startActivityForResult
The ConnectionResult object (mConnectionResult) doesn't have a public method available called startActivityForResult so I can't just change the startResolutionForResult call.
In the beginUserInitiatedSignIn() method (GameHelper) the connection returns ConnectionResult.SUCCESS. When resolveConnectionResult() is called, mConnectionResult.hasResolution() also returns true.
I've double-checked that my debug key matches the entry for my app in the Google APIs Console. I've checked that my app ID matches the one shown on the Google Play Developer Console.
I have managed to get the Type-a-Number example app working without any problems. Here's my main Android activity:
package com.eb.droid;
import android.content.Intent;
import android.view.Window;
import android.view.WindowManager;
import com.badlogic.gdx.backends.android.AndroidApplication;
import com.badlogic.gdx.backends.android.AndroidApplicationConfiguration;
import com.swarmconnect.Swarm;
import com.eb.GoogleInterface;
import com.google.example.games.basegameutils.GameHelper;
import com.google.example.games.basegameutils.GameHelper.GameHelperListener;
public final class AndroidGame extends AndroidApplication implements GameHelperListener, GoogleInterface
{
private final int RC_RESOLVE = 5000, RC_UNUSED = 5001; //request codes we use when invoking an external activity
private final SwarmData swarmData = new SwarmData();
private GameHelper aHelper;
public AndroidGame()
{
aHelper = new GameHelper(this);
aHelper.enableDebugLog(true, "MYTAG");
}
public final void onCreate(android.os.Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
AndroidApplicationConfiguration cfg = new AndroidApplicationConfiguration();
// Disable hardware functions to save battery power.
cfg.useAccelerometer = false;
cfg.useCompass = false;
cfg.useGL20 = true;
aHelper.setup(this);
setSwarmKeyPartA();
initialize(new Game(this, swarmData), cfg);
// Activate Swarm if user is already logged-in. App won't nag player to log-in, they always have to
// do it manually via the Swarn dashboard via the title screen.
//Log.d(LOG, "onCreate() Swarm.isLoggedIn() = " + Swarm.isLoggedIn());
if (Swarm.isLoggedIn())
{
Swarm.setActive(this);
}
}
public void onResume()
{
super.onResume();
if (Swarm.isLoggedIn())
{
Swarm.setActive(this);
Swarm.init(this, swarmData.swarmAppID, swarmData.swarmAppKey);
}
}
public void onPause()
{
super.onPause();
Swarm.setInactive(this);
}
#Override
ublic void onStart()
{
super.onStart();
aHelper.onStart(this);
}
#Override
public void onStop()
{
super.onStop();
aHelper.onStop();
}
#Override
public void onActivityResult(int request, int response, Intent data)
{
super.onActivityResult(request, response, data);
aHelper.onActivityResult(request, response, data);
}
public void onSignInFailed()
{
System.out.println("sign in failed");
}
public void onSignInSucceeded()
{
System.out.println("sign in succeeded");
}
public void Login()
{
try
{
runOnUiThread(new Runnable()
{
#Override
public void run()
{
aHelper.beginUserInitiatedSignIn();
}
});
}
catch (final Exception ex)
{
}
}
public void LogOut()
{
try
{
runOnUiThread(new Runnable()
{
#Override
public void run()
{
aHelper.signOut();
}
});
}
catch (final Exception ex)
{
}
}
public boolean getSignedIn()
{
return aHelper.isSignedIn();
}
public void submitScore(int _score)
{
System.out.println("in submit score");
aHelper.getGamesClient().submitScore(getString(R.string.leaderboard_high_scores), _score);
}
public void showAchievements()
{
startActivityForResult(aHelper.getGamesClient().getAchievementsIntent(), RC_UNUSED);
}
public void showLeaderboards()
{
startActivityForResult(aHelper.getGamesClient().getAllLeaderboardsIntent(), RC_UNUSED);
}
public void getScoresData()
{
}
}
My app also currently implements Swarm, as can be seen in the Activity. I tried removing Swarm to see if that was negatively affecting the GPGS login, but still no luck.
I had the same issue, then I noticed this in the logs: "Activity is launching as a new task, so cancelling activity result"
In the AndroidManifest.xml file of my libgdx game, I had set this property: 'android:launchMode="singleInstance"' As soon as I removed that, I was able to use GPGS just like the sample apps.
I haven't looked at your code in detail. Basically I used the supplied GameHelper (I think you have done the same) and I put everything I needed from BaseGameUtils in my fragments. I suggest you look at a successful trace from TypeANumber to see what you are missing. Here is one of my traces:
07-04 10:21:54.511: D/ian_(1781): MultiTab3 beginUserInitiatedSignIn
04 10:21:54.531: D/ian_(1781): isGooglePlayServicesAvailable returned 0
07-04 10:21:54.531: D/ian_(1781): beginUserInitiatedSignIn: continuing pending sign-in flow.
07-04 10:21:54.611: D/ian_(1781): resolveConnectionResult: trying to resolve result: C onnectionResult{statusCode=SIGN_IN_REQUIRED, resolution=PendingIntent{40f3ed38: android.os.BinderProxy#40ee3de0}}
07-04 10:21:54.611: D/ian_(1781): result has resolution. Starting it.
07-04 10:21:54.621: D/ian_(1781): startResolutionForResult - this may be prob ?
07-04 10:23:29.480: D/ian_(1781): MultiPlayer onActivityResult called9001-1null
07-04 10:23:29.520: D/ian_(1781): MultiPlayer passing onActivityResult to MultiTab3 Req/Resp/Data=9001-1null
07-04 10:23:29.520: D/ian_(1781): MultiTab3 onActivityResult - passing through to GameHelper ...9001-1null
07-04 10:23:29.520: D/ian_(1781): onActivityResult, req 9001 response -1
07-04 10:23:29.520: D/ian_(1781): responseCode == RESULT_OK. So connecting.
07-04 10:23:30.130: D/ian_(1781): onConnected: connected! client=1
07-04 10:23:30.130: D/ian_(1781): All clients now connected. Sign-in successful.
07-04 10:23:30.130: D/ian_(1781): All requested clients connected. Sign-in succeeded!
Update:
What about this - is this just a copy and paste error or is your program missing the "P" on Public ? (You do need to make this call)
#Override
ublic void onStart()
{
super.onStart();
aHelper.onStart(this);
}
Update 2:
Based on the work you have done, you have now eliminated the possibility of a programming error. I have not seen a setup error that caused this problem. However I did encounter this which is (sort of) similar:
onConnectionFailed: result 4
onConnectionFailed: since user initiated sign-in, trying to resolve problem
statusCode=SIGN_IN_REQUIRED resolution=PendingIntent
result has resolution. Starting it.
Explanation from google docs "The client attempted to connect to the service but the user is not signed in"
n.b. Google setting is showing that we are signed in as ..........#gmail.com
further investigation **suggests** that this may be because of a set up problem in the
google play api console and/or google play developer console ...
I also notice that you mentioned the Google API console. I suggest you create a new set of definitions in the Developer Console and that you do not make any amendments through the API console. I am not saying that you have changed things in the API console - I just think it is easier at this point to create a new set of definitions and (I'm repeating myself I know) don't make any changes through the API console.

Android in-app billing: Can't start async operation because another async operation (is in progress)

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);
}
});

Does the Facebook SDK only work with Activities?

I've been trying to implement the Facebook SDK into my application in order to let users post messages on our fanwall through the app. However, I've been unsuccessful at even logging the user in through the SDK.
In the SDK examples, a simple sample has been given which uses an Activity to try and authorize the user using Single Sign-On. I've tried this example myself, and it works. I'm able to log in, I had to authorize the app to use my Facebook data and I could see the requests being made and received in LogCat.
Now, I've tried adding the same code to my own app. This app is Fragment based using the Compatibility package. There is one central FragmentActivity and the rest of my classes are simple Fragments. When adding the sample code to one of these Fragments, the Facebook app starts up for half a second when trying to authorize, but afterwards closes and nothing has happened. I'm back in my regular Fragment again.
When checking the LogCat after this, nothing suggests that the Facebook app even opened or made any requests other than the fact it shows some printing checks I added and the fact it says it's starting the Facebook intent :
01-12 13:19:40.874: I/System.out(6087): Calling authorize
01-12 13:19:40.874: I/ActivityManager(1380): Starting activity: Intent { cmp=com.facebook.katana/.ProxyAuth (has extras) } from pid 6087
01-12 13:19:40.874: I/System.out(6087): Called authorize
Other than that, nothing gets returned. No Facebook-checks, no statements saying my keys are wrong or anything, just nothing. The Facebook intent was called, but closed almost immediately and nothing else shows that it was even open.
This has been boggling my mind for a few hours now, and I'm starting to think the regular , sample-provided approach just doesn't work in Fragments due to the way Fragments work.
The code I've been using is posted below. The Fragment gets fired by a button which calls a FragmentTransaction. Am I doing something fundamentally wrong here, or does the Facebook SDK really just not work with Fragments? I've tried searching for this problem but I haven't been able to find anyone else with the same sort of situation.
public class FanWallFacebook extends Fragment {
Facebook facebook = new Facebook("294678133912628");
public FanWallFacebook() {
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.filler, container, false);
}
#Override
public void onStart() {
super.onStart();
System.out.println("Calling authorize");
facebook.authorize(getActivity(), new DialogListener() {
#Override
public void onComplete(Bundle values) {
System.out.println("Completed");
}
#Override
public void onFacebookError(FacebookError error) {
System.out.println("Facebook error: "+error.getMessage());
}
#Override
public void onError(DialogError e) {
System.out.println("General error: "+e.getMessage());
}
#Override
public void onCancel() {
System.out.println("Cancelled");
}
});
System.out.println("Called authorize");
}
#Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
facebook.authorizeCallback(requestCode, resultCode, data);
System.out.println("Authorize callback'd");
}
}
Edit
Just tried using a FragmentActivity, and lo and behold, that does work. It successfully logs in. Seems like the SDK really only works with classes that explicitly extend ...Activity. Could anyone give me an idea why that might be the case? I always thought Fragments somewhere down the line extended Activity as well.
I ended up using a FragmentActivity for my Facebook interaction. Not the ideal solution, but it works.
well...
the new Facebook SDK has an Activity that implement FragmentActivity so problem solved.

Categories

Resources