googblogs.com

All Google blogs and Press in one site

Skip to content
  • Home
  • Categories
    • Google Ads Developer Blog
    • Google Africa Blog
    • AdWords Agency Blog
    • Android Blog
    • Android Developers Blog
    • Australia Blog
    • Blogger Buzz
    • Consumer Packaged Goods Blog
    • Custom Search Blog
    • DoubleClick Advertiser Blog

Migrating to the new Play Games Services APIs

In 11.6.0 of the Google Play Services SDK, we are introducing a major change to how the APIs are structured. We've deprecated the GoogleApiClient class, and introduced a decoupled collection of feature-specific API clients. This reduces the amount of boilerplate code required to access the Play Games Services APIs.

The change in the APIs is meant to make the APIs easier to use, thread-safe, and more memory efficient. The new API model also makes use of the Task model to give better separation of the concerns between your activity and handling the asynchronous results of the APIs. This programming model first appeared in Firebase and was well received. To dive in deeper into the wonderful world of Tasks, check out the blog series on tasks and the Tasks API developer guide.

As always, the developer documentationis a reliable source of information on these APIs, as well as all the other Google resources for developers. TheAndroid Basic Samples project, and Client Server Skeleton project have both been updated to use the Play Services API clients so you can see them in action. These sample projects are also the best place to add issues or problems you encounter using these APIs.

These changes seem big, but fear not! Using the Play Services API clients is very simple and will result in much less clutter in your code. There are three parts to using the API clients:

  1. Authentication is now explicitly done using the Google Sign-In client. This makes it more clear how to control the authentication process and the difference between a Google Sign-In identity and the Play Games Services identity.
  2. Convert all the Games.category static method calls to use the corresponding API client methods. This also includes converting PendingResult usages to use the Task class. The Task model helps greatly with separation of concerns in your code, and reduces the amount of multi-threaded complexity since tasks listeners are called back on the UI thread.
  3. Handling multi-player invitations is done explicitly through the turn-based and real-time multiplayer API clients. Since GoogleApiClient is no longer used, there is no access to the "connection hint" object which contains multi-player invitations. The invitation is now accessed through an explicit method call.

Authentication

The details of the authentication process are found on the Google Developers website.

The most common use case for authentication is to use the DEFAULT_GAMES_SIGN_IN option. This option enables your game to use the games profile for the user. Since a user's games profile only contains a gamer tag that your game can display like a name, and an avatar for a image, the actual identity of the user is protected. This eliminates the need for the user to consent to sharing any additional personal information reducing the friction between the user and your game.

Note: The only Google sign-in option that can be requested which only uses games profile is requestServerAuthCode(). All others, such as requestIDToken()require the user to consent to additional information being shared. This also has the effect of preventing users from having a zero-tap sign-in experience.

One more note: if you are using the Snapshots API to save game data,you need to add the Drive.SCOPE_APPFOLDER scope when building the sign-in options:

private  GoogleSignInClient signInClient;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

// other code here

GoogleSignInOptions signInOption = new GoogleSignInOptions.Builder(
GoogleSignInOptions.DEFAULT_GAMES_SIGN_IN)
// If you are using Snapshots add the Drive scope.
.requestScopes(Drive.SCOPE_APPFOLDER)
// If you need a server side auth code, request it here.
.requestServerAuthCode(webClientId)
.build();
signInClient = GoogleSignIn.getClient(context, signInOption);
}

Since there can only be one user account signed in at a time, it's good practice to attempt a silent sign-in when the activity is resuming. This will have the effect of automatically signing in the user if it is valid to do so. It will also update or invalidate the signed-in account if there have been any changes, such as the user signing out from another activity.

private void signInSilently() {
GoogleSignInOptions signInOption =
new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_GAMES_SIGN_IN)
.build();
GoogleSignInClient signInClient = GoogleSignIn.getClient(this, signInOption);
signInClient.silentSignIn().addOnCompleteListener(this,
new OnCompleteListener<GoogleSignInAccount>() {
@Override
public void onComplete(@NonNull Task<GoogleSignInAccount> task) {
// Handle UI updates based on being signed in or not.
enableUIButtons(task.isSuccessful());
// It is OK to cache the account for later use.
mSignInAccount = task.getResult();
}
});
}
    @Override
protected void onResume() {
super.onResume();
signInSilently();
}

Signing in interactively is done by launching a separate intent. This is great! No more checking to see if errors have resolution and then trying to call the right APIs to resolve them. Just simply start the activity, and get the result in onActivityResult().

  Intent intent = signInClient.getSignInIntent();
startActivityForResult(intent, RC_SIGN_IN);
    @Override
protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
super.onActivityResult(requestCode, resultCode, intent);
if (requestCode == RC_SIGN_IN) {
// The Task returned from this call is always completed, no need to attach
// a listener.
Task<GoogleSignInAccount> task =
GoogleSignIn.getSignedInAccountFromIntent(intent);

try {
GoogleSignInAccount account = task.getResult(ApiException.class);
// Signed in successfully, show authenticated UI.
enableUIButtons(true);
} catch (ApiException apiException) {
// The ApiException status code indicates the
// detailed failure reason.
// Please refer to the GoogleSignInStatusCodes class reference
// for more information.
Log.w(TAG, "signInResult:failed code= " +
apiException.getStatusCode());
new AlertDialog.Builder(MainActivity.this)
.setMessage("Signin Failed")
.setNeutralButton(android.R.string.ok, null)
.show();
}
}
}

To determine if a user is signed in, you can call the GoogleSignIn.getLastSignedInAccount() method. This returns the GoogleSignInAccount for the user that is signed in, or null if no user is signed in.

if (GoogleSignIn.getLastSignedInAccount(/*context*/ this) != null) {
// There is a user signed in, handle updating the UI.
enableUIButtons(true);
} else {
// Not signed in; update the UI.
enableUIButtons(false);
}

Signing out is done by calling GoogleSignInClient.signOut(). There is no longer a Games specific sign-out.

signInClient.signOut().addOnCompleteListener(MainActivity.this,
new OnCompleteListener<Void>() {
@Override
public void onComplete(@NonNull Task<Void> task) {
enableUIButtons(false);
}
});
);

Using Games Services API clients

In previous versions of Play Games Services, the general pattern of calling an API was something like this:

    PendingResult<Stats.LoadPlayerStatsResult> result =
Games.Stats.loadPlayerStats(
mGoogleApiClient, false /* forceReload */);
result.setResultCallback(new
ResultCallback<Stats.LoadPlayerStatsResult>() {
public void onResult(Stats.LoadPlayerStatsResult result) {
Status status = result.getStatus();
if (status.isSuccess()) {
PlayerStats stats = result.getPlayerStats();
if (stats != null) {
Log.d(TAG, "Player stats loaded");
if (stats.getDaysSinceLastPlayed() > 7) {
Log.d(TAG, "It's been longer than a week");
}
if (stats.getNumberOfSessions() > 1000) {
Log.d(TAG, "Veteran player");
}
if (stats.getChurnProbability() == 1) {
Log.d(TAG, "Player is at high risk of churn");
}
}
} else {
Log.d(TAG, "Failed to fetch Stats Data status: "
+ status.getStatusMessage());
}
}
});

The API was accessed from a static field on the Games class, the API returned a PendingResult, which you added a listener to in order to get the result.

Now things have changed slightly. There is a static method to get the API client from the Games class, and the Task class has replaced the PendingResult class.

As a result, the new code looks like this:

GoogleSignInAccount mSignInAccount = null;
Games.getPlayerStatsClient(this, mSignInAccount).loadPlayerStats(true)
.addOnCompleteListener(
new OnCompleteListener<AnnotatedData<PlayerStats>>() {
@Override
public void onComplete(Task<AnnotatedData<PlayerStats>> task) {
try {
AnnotatedData<PlayerStats> statsData =
task.getResult(ApiException.class);
if (statsData.isStale()) {
Log.d(TAG,"using cached data");
}
PlayerStats stats = statsData.get();
if (stats != null) {
Log.d(TAG, "Player stats loaded");
if (stats.getDaysSinceLastPlayed() > 7) {
Log.d(TAG, "It's been longer than a week");
}
if (stats.getNumberOfSessions() > 1000) {
Log.d(TAG, "Veteran player");
}
if (stats.getChurnProbability() == 1) {
Log.d(TAG, "Player is at high risk of churn");
}
}
} catch (ApiException apiException) {
int status = apiException.getStatusCode();
Log.d(TAG, "Failed to fetch Stats Data status: "
+ status + ": " + task.getException());
}
}
});

So, as you can see, the change is not too big, but you will gain all the goodness of the Task API, and not have to worry about the GoogleApiClient lifecycle management.

The pattern of changes is the same for all the APIs. If you need more information, you can consult the Developer website. For example if you used Games.Achievements, you now need to use Games.getAchievementClient().

The last major change to the Play Games Services APIs is the introduction of a new API class, GamesClient. This class handles support methods such as setGravityForPopups(), getSettingsIntent(), and also provides access to the multiplayer invitation object when your game is launched from a notification.

Previously the onConnected() method was called with a connection hint. This hint was a Bundle object that could contain the invitation that was passed to the activity when starting.

Now using the GamesClient API, if there is an invitation, your game should call signInSilently(); this call will succeed since the user is known from the invitation. Then retrieve the activation hint and process the invitation if present by calling GamesClient.getActivationHint():

Games.getGamesClient(MainActivity.this,  mSignInAccount)
.getActivationHint().addOnCompleteListener(
new OnCompleteListener<Bundle>() {
@Override
public void onComplete(@NonNull Task<Bundle> task) {
try {
Bundle hint = task.getResult(ApiException.class);
if (hint != null) {
Invitation inv =
hint.getParcelable(Multiplayer.EXTRA_INVITATION);
if (inv != null && inv.getInvitationId() != null) {
// retrieve and cache the invitation ID
acceptInviteToRoom(inv.getInvitationId());
return;
}
}
} catch (ApiException apiException) {
Log.w(TAG, "getActivationHint failed: " +
apiException.getMessage());
}
}
});

Handling failure

When a method call fails, the Task.isSuccessful()will be false and information about the failure is accessed by calling Task.getException(). In some cases the exception is simply a non-success return value from the API call. You can check for this by casting to an ApiException:

if (task.getException() instanceof ApiException) {
ApiException apiException = (ApiException) task.getException();
status = apiException.getStatusCode();
}

In other cases, a MatchApiException can be returned and contains updated match data structure. It can be retrieved in a similar manner:

if (task.getException() instanceof MatchApiException) {
MatchApiException matchApiException =
(MatchApiException) task.getException();
status = matchApiException.getStatusCode();
match = matchApiException.getMatch();
} else if (task.getException() instanceof ApiException) {
ApiException apiException = (ApiException) task.getException();
status = apiException.getStatusCode();
}

If the status code is SIGN_IN_REQUIRED, this indicates that the player needs to be re-authenticated. To do this, call GoogleSignInClient.getSignInIntent()to sign in the player interactively.

Summary

The change from the GoogleApiClient usage to a more loosely coupled API clients usage will provide benefits of less boilerplate code, more clear usage patterns, and thread safety. As you migrate your current game to API clients, refer to these resources:

Sign-In Best practices for Games:

https://developers.google.com/games/services/checklist

Play Games Services Samples:

Android Basic Samples

Client Server Skeleton

StackOverflow:

https://stackoverflow.com/questions/tagged/google-play-games

Source: Google Developers Blog


This entry was posted in Google Developers Blog and tagged Games, play services, sign-in on November 9, 2017 by Google Devs.

Post navigation

← Reach more shoppers this holiday season with new innovations from Merchant Center and AdWords Create and edit objects in jams on your mobile phone →

Recent Comments

    Archives

    • June 2025
    • May 2025
    • April 2025
    • March 2025
    • February 2025
    • January 2025
    • December 2024
    • November 2024
    • October 2024
    • September 2024
    • August 2024
    • July 2024
    • June 2024
    • May 2024
    • April 2024
    • March 2024
    • February 2024
    • January 2024
    • December 2023
    • November 2023
    • October 2023
    • September 2023
    • August 2023
    • July 2023
    • June 2023
    • May 2023
    • April 2023
    • March 2023
    • February 2023
    • January 2023
    • December 2022
    • November 2022
    • October 2022
    • September 2022
    • August 2022
    • July 2022
    • June 2022
    • May 2022
    • April 2022
    • March 2022
    • February 2022
    • January 2022
    • December 2021
    • November 2021
    • October 2021
    • September 2021
    • August 2021
    • July 2021
    • June 2021
    • May 2021
    • April 2021
    • March 2021
    • February 2021
    • January 2021
    • December 2020
    • November 2020
    • October 2020
    • September 2020
    • August 2020
    • July 2020
    • June 2020
    • May 2020
    • April 2020
    • March 2020
    • February 2020
    • January 2020
    • December 2019
    • November 2019
    • October 2019
    • September 2019
    • August 2019
    • July 2019
    • June 2019
    • May 2019
    • April 2019
    • March 2019
    • February 2019
    • January 2019
    • December 2018
    • November 2018
    • October 2018
    • September 2018
    • August 2018
    • July 2018
    • June 2018
    • May 2018
    • April 2018
    • March 2018
    • February 2018
    • January 2018
    • December 2017
    • November 2017
    • October 2017
    • September 2017
    • August 2017
    • July 2017
    • June 2017
    • May 2017
    • April 2017
    • March 2017
    • February 2017
    • January 2017
    • December 2016
    • November 2016
    • October 2016
    • September 2016
    • August 2016
    • July 2016
    • June 2016
    • May 2016
    • April 2016
    • March 2016
    • February 2016
    • January 2016
    • December 2015
    • November 2015
    • October 2015
    • September 2015
    • August 2015
    • July 2015
    • June 2015
    • May 2015
    • April 2015
    • March 2015
    • February 2015
    • January 2015
    • December 2014
    • November 2014
    • October 2014
    • September 2014
    • August 2014
    • July 2014
    • June 2014
    • May 2014
    • April 2014
    • March 2014
    • February 2014
    • January 2014
    • December 2013
    • November 2013
    • October 2013
    • September 2013
    • August 2013
    • July 2013
    • June 2013
    • May 2013
    • April 2013
    • March 2013
    • February 2013
    • January 2013
    • December 2012
    • November 2012
    • October 2012
    • September 2012
    • August 2012
    • July 2012
    • June 2012
    • May 2012
    • April 2012
    • March 2012
    • February 2012
    • January 2012
    • December 2011
    • November 2011
    • October 2011
    • September 2011
    • August 2011
    • July 2011
    • June 2011
    • April 2011
    • March 2011
    • February 2011
    • March 2010
    • January 2010
    • December 2009
    • November 2009
    • October 2009
    • September 2009

    Categories

    • AdMob Blog
    • Ads Developer Blog
    • AdWords Agency Blog
    • Android Blog
    • Android Developers Blog
    • Apps Feed Blog
    • Artificial Intelligence
    • Australia Blog
    • Blogger Buzz
    • Consumer Packaged Goods Blog
    • Custom Search Blog
    • Data Liberation Blog
    • DoubleClick Advertiser Blog
    • DoubleClick Publishers Blog
    • DoubleClick Search Blog
    • Geo Developers Blog
    • Google Ads Developer Blog
    • Google Africa Blog
    • Google Analytics Blog
    • Google and Your Business
    • Google Apps Developer Blog
    • Google Canada Blog
    • Google Chrome Blog
    • Google Chrome Releases
    • Google Cloud Platform Blog
    • Google Commerce Blog
    • Google Developers Blog
    • Google Drive Blog
    • Google Europe Blog
    • Google Fiber
    • Google for Education Blog
    • Google for Nonprofits
    • Google for Work Blog
    • Google Green Blog
    • Google India Blog
    • Google LatLong Blog
    • Google New Zealand Blog
    • Google News Blog
    • Google Scholar Blog
    • Google Testing Blog
    • Google Translate Blog
    • Google Travel Blog
    • Google Web Fonts Blog
    • Google Webmaster Central Blog
    • Inside AdSense
    • Inside AdWords
    • Inside Search Blog
    • Official Gmail Blog
    • Official Google Blog
    • Online Security Blog
    • Open Source Blog
    • Politics & Elections Blog
    • Public Policy Blog
    • Research Blog
    • Student Blog
    • Uncategorized
    • YouTube Blog
    • YouTube Blog – Australia
    • YouTube Blog – U.K.
    • YouTube Blogs
    • YouTube Creators
    • YouTube Creators – UK
    • YouTube Engineering and Developers Blog
    Proudly powered by WordPress