Android Google+ sign in and Game services

Wanted to try out the Google Game Services for Android. Well, I guess there are more than just Game Services since they include Google+ in general, Google Drive, etc…

So to use them I need to get the user to sign in to their Google+ account. Can’t be too hard, can it? I mean basically to set up the device you need to log in using a Google account. So you should have a Google account if you get here, and pretty much be logged in already as mostly using the device requires this. Unless you wear a tin foil hat, rooted your phone, and declined to install any Google services (so much fun on Android without them.. I tried it :)).

Well not so simple. Here is what worked for me finally. This is at this time on Android Lollipop, a.k.a. Android 5.0.

The documentation tells me to do something like this:

...
private GoogleApiClient client;
...
client = new GoogleApiClient.Builder(this)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.addApi(Plus.API).addScope(Plus.SCOPE_PLUS_LOGIN)
.addApi(Games.API).addScope(Games.SCOPE_GAMES)
.build();
client.connect();

To me this seems to say, I want to use Google+ sign in service and the Game services, connect to the servers to set up the API access. But it does not seem to indicate anything about signing in anywhere. Then what? How do I make the user sign in? The docs just show this and leave me to it. To me that is what it seems like anyway.

Well, apparently the “client.connect()” invocation tries to make the user sign in. But typically the user is not signed in (for the app) when they start to use the app (meaning you are not allowed to access their Google info and services). So then what happens? I have to define the listener interfaces given to the builder above as in “addConnectionCallbacks()” and “addConnectionFailedListener()”. Something like:

...
  private boolean boolean resolving = false;
...
  @Override
  public void onConnected(Bundle bundle) {
    //if we ever get here, we can actually use the Google services we asked for whoppee
    String achId = getString(R.string.achievement1);
    Games.Achievements.unlock(client, achId);
  }

  @Override
  public void onConnectionSuspended(int i) {
    //retry to give user error to resolve
    client.connect();
  }

  @Override
  public void onConnectionFailed(ConnectionResult result) {
    if (resolving) return;
    resolving = true;
    if (result.hasResolution()) {
      try {
        IntentSender sender = result.getResolution().getIntentSender();
        startIntentSenderForResult(sender, RC_SIGN_IN, null, 0, 0, 0);
      } catch (IntentSender.SendIntentException e) {
        // The intent was canceled before it was sent.  Return to the default
        // state and attempt to connect to get an updated ConnectionResult.
        client.connect();
      }
    } else {
      // not resolvable... so show an error message
      int errorCode = result.getErrorCode();
      Dialog dialog = GooglePlayServicesUtil.getErrorDialog(errorCode, this, RC_SIGN_IN);
      if (dialog != null) {
        dialog.show();
      } else {
        // no built-in dialog: show the fallback error message
        showAlert(this, "Error in signing in.");
      }
    }
  }
...

So what does all this mean? When we call the “connect()” method on the GoogleApiClient, if the user has not previously used the app and signed in for it, the “onConnectionFailed” is called. This has the “hasResolution” flag set if the user can log in (why couldn’t they? I dunno maybe some service is not available?).

If you then start the “IntentSender” attached to the response, it opens up the actual Google+ sign-in dialog. Using this the user can finally sign in to enable the Google services for the app.

Then there are all the weird error conditions and their handling. If the sign-in error has no resolution, there might be some way to fix it by user action, such as installing Google Play services. Getting the “GooglePlayServicesUtil.getErrorDialog()” gives you the dialog that enables the user to perform this action, if any are available. It also enables shows the user a message telling what went wrong. But hey, it gets better, this dialog is not always available, in which case you should create your own generic dialog to tell the user “something went wrong” (the “showAlert()” above).

Of course, you have to track if the resolving action is already going on and this just happens to be called again and this is what the “resolving” flag is for. Wonderfully complicated, no? Well luckily it is not this simple.

Using the PendingIntent to show the sign-in dialog does not actually end up with the user signed in to your app. It just enables you to connect while the user has accepted your app for access to their Google services. So we need to listen to the events from the sign in dialog. This needs:

  @Override
  protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (requestCode == RC_SIGN_IN) {
      resolving = false;
      if (resultCode == Activity.RESULT_OK) {
        // Make sure the app is not already connected or attempting to connect
        if (!client.isConnecting() && !client.isConnected()) {
          client.connect();
        }
      }
    }
  }

This gets called after the user had “interacted” with the sign in dialog. So what is the RC_SIGN_IN value? Anything you like (I think it was 9001 and 0 in different examples). This is an identifier we gave earlier in the “startIntent..” call higher above to identify what activity just arrived here. Activity.RESULT_OK is a value that indicates the user chose “OK” for the sign in dialog and we now have the permission. Then (if we are not already doing so…) we try to do “GoogleApiClient.connect()” again. Why, because now we should have the permission to do so and now the connection should happen successfully (as in signing in and enabling the Google services).

But is this all? Well of course it cannot be so simple. You can get all sorts of resultCode values. Not just from Activity.XXX but also, for example, from GamesActivityResultCodes.XXX. I got 10002, which refers to an error in signing in. Sweet. Which can be if you have not correctly set up the application id in the Google Developer console. But there is more. It can also be if you have not published your app but are developing it using a Google ID that is not the owner of the application id on the Developer console. Then you must add it to the list of allowed test accounts for the application under the Developer console (Testing/Add Testers). After this it finally worked.

Few extra notes.

All these errors can be really cryptic as it might seem that nothing is happening when any of the callbacks are not called, and no error is visible. Yet nothing happens. Well, to see the errors one has to remove all filters from logcat and try again. At least on IntelliJ, the IDE by default filters non-app related stuff out. So good luck debugging that if you dont happen to notice this.

Some of the Google example also show using a Fragment to show the error dialog. Somewhere in the GameUtils classes they host (at this time) on github (hey, they dont like Google code?) I found something along these lines:

  /**
   * Show an {@link android.app.AlertDialog} with an 'OK' button and a message.
   *
   * @param activity the Activity in which the Dialog should be displayed.
   * @param message the message to display in the Dialog.
   */
  public static void showAlert(Activity activity, String message) {
    AlertDialog.Builder builder = new AlertDialog.Builder(activity);
    builder.setMessage(message)
            .setNeutralButton(android.R.string.ok, null)
            .create()
            .show();
  }

And finally, it seems that if there is an issue, the “onConnectionSuspended()” gets called and you can then use the “connect()” method again to show some error dialog (by triggering the onConnectionFailed() method..).

And once the user has signed in once, the initial connect() seems to work and they are not automatically signed in without all the extra methods etc. Whee.

Just to note, the stuff in these examples is inside the main Activity class. Otherwise some of the method calls and overrides will not work. Otherwise you can implement if in other ways of course..

Somehow I was hoping for something like “GooglePlus.signIn()”. Would that be so hard? Well, just hoping this works now.. I am sure I still got plenty of things wrong. But lets see.

Advertisement

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s