I'm updating my game to the new Google Play Serviced library, leaderboards and achievements were already working flawlessly, but now when I try to open the achievement activity, it immediately closes without showing any exception in logcat.
I'm logged in with my google account, which is correctly configured as a test user.
public void gameServicesGetAchievements() {
if (gameHelper == null)
return;
Intent i = Games.Achievements.getAchievementsIntent(gameHelper.getApiClient());
((Activity) ctx).startActivityForResult(i, REQUEST_CODE_ACHIEVEMENTS);
}
The activity opens, but closes immediately before showing the achievements list. This is what I get in logcat
D/GameHelper(30459): GameHelper: onActivityResult: req=9802, resp=RESULT_RECONNECT_REQUIRED
D/GameHelper(30459): GameHelper: onActivityResult: request code not meant for us. Ignoring.
I tried handling the RESULT_RECONNECT_REQUIRED code in my onActivityResult, but nothing changes.
#Override
public void onActivityResult(int request, int response, Intent data) {
super.onActivityResult(request, response, data);
GameHelper helper = resolver.getGameServicesHelper();
if (helper != null) {
helper.onActivityResult(request, response, data);
}
if (response == GamesActivityResultCodes.RESULT_RECONNECT_REQUIRED) {
helper.disconnect();
}
}
The leaderboards activities start correctly and work flawlessly.
Found the problem... it wasn't a problem in my app, just the Google Play Games app on my device had some corrupted data or something.
I followed the steps here and that fixed it.
http://howlukeseesit.blogspot.it/2014/08/fixing-google-play-games.html
First navigate to settings > apps and find and tap on Google play
games.
Next tap uninstall updates, then tap clear data.
Next go back
to the apps section and find Google Play Services, tap that then tap
Manage space and then tap on clear all data.
Now all you have to do
is go into the Play store and manually update Google Play Games.
Related
My attempts to integrate Google Play Games Sign-In are mostly working, however there is one oddity:
If the user does not already have a Play Games account and declines to sign-in via the automatic sign-in process, I provide a "sign-in" button that does a manual sign-in. When they click it in this case, they are seamlessly prompted to create a Google Play Games account. Seems great. It works... they are signed in, they can participate in my leaderboard, everything seems functional...
HOWEVER: The Google Play Games account that is created by this particular flow seems to (sometimes) be malformed. If you attempt to view the profile for this player (within the Google Play Games app, using another account), an error is displayed. An example of such a "malformed" Play Games account is: LadyAnnie123
If you try to view that Play Games profile using the Play Games app (please do), you'll see an error message that says "Yikes, there's a problem... It's not you, it's us." (Cute.) The account is set to "Everyone can see your game activity" and "Receive friend invites" is activated.
[EDIT:] As of 7/31/22, only about HALF of the accounts that I try creating using the flow described above are broken like this. Some of them seem to be perfectly normal.
Is this even my fault? Can somebody show me an example of a functional manual sign-in button (that doesn't sometimes create disfunctional Play Games accounts in the case where the user's account does not already exist)?
In onCreate():
gamesSignInClient = PlayGames.getGamesSignInClient(this);
gamesSignInClient.isAuthenticated().addOnCompleteListener(isAuthenticatedTask -> {
boolean isAuthenticated = (isAuthenticatedTask.isSuccessful() &&
isAuthenticatedTask.getResult().isAuthenticated());
if (isAuthenticated) {
signInStatus = true;
setupLeaderboard();
} else {
signInStatus = false;
}
});
My sign-in button executes signIn() like this:
gamesSignInClient.signIn().addOnCompleteListener(isAuthenticatedTask -> {
boolean isAuthenticated = (isAuthenticatedTask.isSuccessful() &&
isAuthenticatedTask.getResult().isAuthenticated());
if (isAuthenticated) {
signInStatus = true;
setupLeaderboard();
}
});
THANKS!
I have an app with a subscription in Google Play.
When the user starts the app, I need to know if the user has an active subscription. This would seem an obvious thing to do, but from searching and trying to implement it, it seems impossible?
I am using Google's newer billing 2/3, following Google's tutorials,
class BillingManager implements PurchasesUpdatedListener
...
public void checkAsync() {
Log.e(TAG, "checkAsync");
billingClient.queryPurchaseHistoryAsync(BillingClient.SkuType.SUBS, new PurchaseHistoryResponseListener() {
#Override
public void onPurchaseHistoryResponse(BillingResult billingResult, List<PurchaseHistoryRecord> list) {
Log.e(TAG, "checkCached result: " + list);
if (list == null) {
return;
}
for (PurchaseHistoryRecord ps : list) {
//System.out.println("PAYMENT: " + ps.getSku() + " : " + ps.getPurchaseTime());
}
}
});
}
public void checkCached() {
Log.e(TAG, "checkCached");
List<Purchase> result = billingClient.queryPurchases(BillingClient.SkuType.SUBS).getPurchasesList();
Log.e(TAG, "checkCached result: " + result);
if (result == null) {
return;
}
for (Purchase purchase : result) {
handlePurchase(purchase);
}
}
This is how I think you're supposed to get a user's purchases. But it does not work at all, both calls return null always. It only returns the correct purchases when you reinstall the app or clear the data.
So how exactly is an app supposed to do this?
Purchasing works for the app once I enter internal testing, and download it through the Google Play link. (before that subscriptions do not work at all).
*** updated
So to further clarify:
I am using a valid test user, and subscriptions are working correctly. My question is on the what the API queryPurchases() or queryPurchaseHistoryAsync() are suppose to do.
What I am seeing, is that these only return purchases that have not be processed by the app. They seem to store that the purchase was processed in the apps data.
After the purchase these return null, after the app restarts these return null.
If I clear the app datam or reinstall the app then they return the purchase (once), then again null after restart.
From what I see, these are only useful to detect when a user reinstalls your app, or installs on a different phone. They cannot be used to determine the status of a subscription.
So my question is,
1 - is this something that just does not work in internal testing and will magically work differently when the app is release?
2 - is there a different API that your suppose to use to check the status of a subscription?
3 - are you suppose to manage subscriptions yourself in your app by storing a user preference/cookie when you acknowledge the subscription the first time so you know when the subscription expires?
You need "licenced testers". They would allow you to "sideload" your app on devices, even for debug builds. My interpretation of sideload in this case would cover installing from Android Studio build tools as well as adb install .... and other methods that don't involve the play store.
https://developer.android.com/google/play/billing/test
Ordinarily, the Google Play Billing API is blocked for apps that aren't signed and uploaded to Google Play. License testers can bypass this check, meaning you can sideload apps for testing, even for apps using debug builds with debug signatures without the need to upload to the new version of your app. Note that the package name must match that of the app that is configured for Google Play, and the Google account must be a license tester for the Google Play Console account.
I also don't see how you're using startConnection. Until that's completed successfully I wouldn't be sure you have the latest data. I wouldn't be surprised if that makes you get stale values. I would check that carefully to make sure there's no silent errors happening, by both looking at onBillingSetupFinished and onBillingServiceDisconnected. And for the time being avoid trusting queryPurchases():
https://medium.com/#NandagopalR/integrating-google-play-billing-into-an-android-application-b6eb6af176a7
The queryPurchases() method uses a cache of the Google Play Store app without initiating a network request. If you need to check the most recent purchase made by the user for each product ID, you can use queryPurchaseHistoryAsync(), passing the purchase type and a PurchaseHistoryResponseListener to handle the query result.
By the way what's the value of isReady() right before queryPurchaseHistoryAsync, and what's the value of BillingResult::getDebugMessage and BillingResult::getResponseCode?
Also, use isFeatureSupported, though it seems it's not like your problem is coming from here. But I'd advise not testing with subscriptions until you get all the moving parts working: https://developer.android.com/reference/com/android/billingclient/api/BillingClient#isFeatureSupported(java.lang.String)
Okay, figured it out, was my mistake.
I was calling queryPurchases() in my main activity onCreate(), but the BillingClient was not ready yet.
I moved it to onBillingSetupFinished() and it now returns the correct purchases.
Everything is now working as expected. You get the active subscriptions when you call queryPurchases() after an app restart.
When a user clicks on my Firebase Dynamic Link (with deep link) to launch my Android app, one of two things will happen:
If the app is not installed, the user will be directed to Google Play to install the app, and then launch it with my deep link.
If the app is installed, it will be launched with my deep link.
My app needs to know which one happened.
As far as I can tell, the Firebase API will not tell me if the app was installed as part of the FDL flow. Am I missing something?
P.S. Using a shared pref isn't good enough for my purposes since they are deleted when the user clears data and therefore don't perfectly represent installs. I'm looking for something like the INSTALL_REFERRER broadcast event is fired by Google Play, but that works with Firebase Dynamic Links.
Per the documentation this is totally possible:
When a user opens one of your Dynamic Links, if your app isn't yet installed, the user is sent to the Play Store or App Store to install your app (unless you specify otherwise), and your app opens. You can then retrieve the link that was passed to your app and handle the deep link as appropriate for your app
You can use the Dynamic Links SDK and call the FirebaseDynamicLinks.getDynamicLink() method to get the data passed to the link:
FirebaseDynamicLinks.getInstance().getDynamicLink(getIntent()).addOnSuccessListener(this, new OnSuccessListener<PendingDynamicLinkData>() {
#Override
public void onSuccess(PendingDynamicLinkData pendingDynamicLinkData) {
Uri deepLink = null;
if (pendingDynamicLinkData != null) {
deepLink = pendingDynamicLinkData.getLink();
}
}
}).addOnFailureListener(this, new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
Log.w(TAG, "getDynamicLink:onFailure", e);
}
});
You can use this coupled with a "firstlaunch" flag stored in SharedPreferences to detect if the app was installed or not.
if (prefs.getBoolean("firstlaunch", true)) {
prefs.edit().putBoolean("firstlaunch", false).commit();
}
https://firebase.google.com/docs/dynamic-links/android/receive
I can easily detect that my device's google play services app needs to be updated, and present the getErrorDialogFragment() to prompt the user to update it with:
GoogleApiAvailability googleApi = GoogleApiAvailability.getInstance();
mServiceAvailabilityCode = googleApi.isGooglePlayServicesAvailable(this);
if (mServiceAvailabilityCode == ConnectionResult.SUCCESS) {
...
else {
if (googleApi.isUserResolvableError(mServiceAvailabilityCode)) {
switch (mServiceAvailabilityCode) {
....
case SERVICE_VERSION_UPDATE_REQUIRED:
googleApi.showErrorDialogFragment(SplashActivity.this, mServiceAvailabilityCode, PLAY_SERVICES_RESOLUTION_REQUEST);
break;
....
}
However, if Google play services is disabled AND out of date, then, the user is presented with a dialog with an "Update" button, once the user presses it, the app returns immediately to the onActivityResult, which I then catch the response and request code in OnActivityResult like this:
#Override
protected void onActivityResult(int requestCode, int mServiceAvailabilityCode, Intent data) {
super.onActivityResult(requestCode, mServiceAvailabilityCode, data);
switch (requestCode) {
case PLAY_SERVICES_RESOLUTION_REQUEST:
finish();
// do i need to launch app manager-> app info for google play services ?
So, by the user pressing the "Update" button on the dialog did not launch Android's Playstore and load the "Playstor services" app for the user to update, which I had expected it to do. Pressing "Update" just goes straight back to onActivityResult. This is where I'm confused. Shouldn't Android have launched this for me ? Or do I have to do it myself in OnActivityResult ?
The problem is caused by a specific condition when you have 2 issues with Google Play Services (GPS). Because GPS is also disabled Google Play Store (GPT), will not run on the device.
If your Google Play Services is out of date, then calling showErrorDialogFragment using an error code of ConnectionResult.SERVICE_VERSION_UPDATE_REQUIRED, (error code 2), which works fine.
But if your GPS is both disabled AND out-of-date, then there is an issue with the way googleApiAvailability api works.If you call isGooglePlayServicesAvailable() it will return the first error it finds, but it's not necessarily the error you want to resolve first. The problem is knowing that you have another error you need to address first. isGooglePlayServicesAvailable() does not help in this regard.
In my case play services is both disabled, AND out of date. So, the approach is to first call showErrorDialogFragment and you'll get a response error code for SERVICE_VERSION_UPDATE_REQUIRED.
Android will attempt to resolve it by sending a pendingIntent to launch Google Play Store (GPT) to update GPS, but this will fail, as GPT depends on an ENABLED version of GPS. Because you're calling showErrorDialogFragment it will call onActivityResult after it fails to launch GPT.
The next step is codig the onActivityResult. I needed to test for isGooglePlayServicesAvailable() again. If you still get the same error code (SERVICE_VERSION_UPDATE_REQUIRED), then you need to call showErrorDialogFragment again in onActivityResult, but this time pass it a different error code, ConnectionResult.SERVICE_DISABLED (error code 3). This will take the user to the app manager to ENABLE google play services first. Then when returning to the app, you need to test for isGooglePlayServicesAvailable and it should then detect google services is still out of date. If you successfully update the app, onActivityResult should allow you to determine that isGooglePlayServicesAvailable is succcessful and you can continue. Note that you will may need to add a flag that so that you know to test again for google play services compatibility rather than continue executing a startup process.
(So, really what googleApiAvailability should do is return the disabled error first (ie ConnectionResult.SERVICE_DISABLED aka error code 3), so you can resolve that first before attempting to update GPS.)
This is working for me
GoogleApiAvailability googleAPI = GoogleApiAvailability.getInstance();
int result = googleAPI.isGooglePlayServicesAvailable(this);
if(result != ConnectionResult.SUCCESS) {
if(googleAPI.isUserResolvableError(result)) {
//prompt the dialog to update google play
googleAPI.getErrorDialog(this,result,PLAY_SERVICES_RESOLUTION_REQUEST).show();
}
}
else{
//google play up to date
}
Not sure in which version Google has it fixed, but with the latest 11.6.0 you can get the correct statusCode and the dialog that leads to Settings to enable the Google Play Services.
Logcat:
11-24 05:48:07.266 V/FA: Activity resumed, time: 2070779368
11-24 05:48:07.320 W/FA: Service connection failed: ConnectionResult{statusCode=SERVICE_DISABLED, resolution=null, message=null}
Dialog:
Google Play Services are available on Play Store to download and update. You can simply open Play Store.
Here is link to the services.
What you can do is start activity with ACTION_VIEW intent like below
String LINK_TO_GOOGLE_PLAY_SERVICES = "play.google.com/store/apps/details?id=com.google.android.gms&hl=en";
try {
startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("market://" + LINK_TO_GOOGLE_PLAY_SERVICES)));
} catch (android.content.ActivityNotFoundException anfe) {
startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("https://" + LINK_TO_GOOGLE_PLAY_SERVICES)));
}
There is also this code:
https://gist.github.com/kristopherjohnson/7554793
Which require:
implementation "com.google.android.gms:play-services-auth:$playServiceAuthVersion"
I'm using google play game services in my app. The one thing that bothers me is that when the user signs out and later back in again, he is not asked to approve the access anymore.
This is my sign out method, pretty much from the examples:
public void signOut() {
if (!mGoogleApiClient.isConnected()) {
// nothing to do
debugLog("signOut: was already disconnected, ignoring.");
return;
}
// for Plus, "signing out" means clearing the default account and
// then disconnecting
if (0 != (mRequestedClients & GameHelper.CLIENT_PLUS)) {
debugLog("Clearing default account on PlusClient.");
Plus.AccountApi.clearDefaultAccount(mGoogleApiClient);
}
// For the games client, signing out means calling signOut and
// disconnecting
if (0 != (mRequestedClients & GameHelper.CLIENT_GAMES)) {
debugLog("Signing out from the Google API Client.");
Games.signOut(mGoogleApiClient);
}
// Ready to disconnect
debugLog("Disconnecting client.");
mGoogleApiClient.disconnect();
}
After this, the app is still connected as I can see in the google settings app. The next time I call mGoogleApiClient.connect();, it connects automatically without asking the user again.
Is there a way to either completely disconnect the app programmatically, or another way to have google play game services ask the user for permission the next time I connect?
Thanks
Simon