How I Fix a Android Google Play Game Services and LibGDX

Recently, I published a mobile game built using LibGDX to the Google Play Console. Naturally, the next step was to integrate Google Play Games Services (GPGS) to add a Leaderboard something I thought would be a plug-and-play process. I followed the steps: created a game in the Play Console, linked my already published app, set up OAuth credentials, and thought I had everything wired.

But then came this frustrating error:

The Error I Encounter

com.google.android.gms.common.api.ApiException: 4:
at com.google.android.gms.common.internal.ApiExceptionUtil.fromStatus(Unknown Source)
at com.google.android.gms.auth.api.signin.GoogleSignIn.getSignedInAccountFromIntent(Unknown Source)
at com.jdstudio.flappyplane.AndroidLauncher.onActivityResult(AndroidLauncher.java:123)

No matter what I tried, the leaderboard wouldn’t show up, and the app kept failing to sign in

What Does ApiException: 4

After some digging, I learned that error code 4 stands for SIGN_IN_REQUIRED. This means that either:

  • The user isn’t signed in,
  • The app doesn’t have the correct authentication setup,
  • Or there’s a configuration mismatch between your code and the Play Console.

In other words, something was broken in the sign in flow or the Google configuration.

The Fix Code

Here’s a breakdown of what I double checked and adjusted to resolve the issue:

SHA-1 Mismatch

This was the root cause in my case.

  • I had only uploaded the debug SHA-1 to the Play Console. For production, you must use your release keystore’s SHA-1.
  • I went to Google Play Console Game Services Linked Apps Android App and made sure the release SHA-1 was added.

You can get the release SHA-1 with this command:

keytool -list -v -keystore your-release-key.jks

Google Sign In Setup

Make sure you initialize Google Sign-In with the correct scopes. For games, use:

GoogleSignInOptions signInOptions = 
new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_GAMES_SIGN_IN).build();

This is not the same as normal Firebase or Google account sign ins

OAuth2 Web Client ID and Package Name

  • Double-check that your OAuth 2.0 Web Client ID (auto-generated by Google) is present in your res/values/strings.xml.
  • Your package name must match exactly with what’s in the Play Console.
  • Also, the applicationId in build.gradle must be identical.

Dependencies

Ensure you’ve added the required libraries to your build.gradle:

implementation 'com.google.android.gms:play-services-auth:20.7.0'
implementation 'com.google.android.gms:play-services-games:23.1.0'

Setting Up GPGS in LibGDX

Here’s a complete working example of how I integrated sign-in, leaderboard display, and score submission in my AndroidLauncher.java:

AndroidLauncher.java

public class AndroidLauncher extends AndroidApplication implements GameServices {

private static final int RC_SIGN_IN = 9001;
private GoogleSignInClient googleSignInClient;
private GoogleSignInAccount signedInAccount;

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

AndroidApplicationConfiguration config = new AndroidApplicationConfiguration();
initialize(new YourLibGDXGame(this), config);

GoogleSignInOptions signInOptions =
new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_GAMES_SIGN_IN).build();

googleSignInClient = GoogleSignIn.getClient(this, signInOptions);
signInSilently();
}

private void signInSilently() {
googleSignInClient.silentSignIn().addOnCompleteListener(this, task -> {
if (task.isSuccessful()) {
signedInAccount = task.getResult();
Gdx.app.log("GPGS", "Signed in silently");
} else {
startSignInIntent();
}
});
}

private void startSignInIntent() {
startActivityForResult(googleSignInClient.getSignInIntent(), RC_SIGN_IN);
}

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);

if (requestCode == RC_SIGN_IN) {
Task<GoogleSignInAccount> task = GoogleSignIn.getSignedInAccountFromIntent(data);
try {
signedInAccount = task.getResult(ApiException.class);
Gdx.app.log("GPGS", "Sign-in successful");
} catch (ApiException e) {
Gdx.app.log("GPGS", "Sign-in failed: " + e.getStatusCode());
}
}
}

public void showLeaderboard() {
if (signedInAccount != null) {
Games.getLeaderboardsClient(this, signedInAccount)
.getLeaderboardIntent(getString(R.string.leaderboard_id))
.addOnSuccessListener(intent -> startActivityForResult(intent, 9002));
} else {
startSignInIntent();
}
}

public void submitScore(long score) {
if (signedInAccount != null) {
Games.getLeaderboardsClient(this, signedInAccount)
.submitScore(getString(R.string.leaderboard_id), score);
}
}
}

How I Used It in My Game

From my core game logic (LibGDX project), I added a GameServices interface. Whenever the player reached a high score or game over, I called:

gameServices.submitScore(currentScore);

And to show the leaderboard button:

gameServices.showLeaderboard();

Simple, clean, and now thanks to debugging that ApiException: 4 it works reliably.

Final Thoughts

Adding Google Play Games Services to a LibGDX game is completely possible, but far from intuitive. It’s easy to miss small configuration steps that cause vague errors like ApiException: 4. In my case, the release SHA-1 and OAuth client mismatch were the culprits. If you’re facing the same problem, double-check every credential, ensure your release signing config is correct, and follow the code patterns shown above. Don’t forget to test on an internal track from the Play Console to simulate production conditions.

Related blog posts