Common problems with Google+ Sign-In on Android

It has been fantastic to see so many people trying out Google+ Sign-In, and through the bootcamps and other events I've had a chance to talk to some people who are actually implementing it in their apps. The Android integration is pretty straightforward, thanks to Google Play Services, but there are still some issues I've seem come up a couple of times.

tl;dr: make sure your app is set up in the API console, and make sure you can handle multiple onStart events coming in during sign-in.

1. Consent screen appears more than once. 

The basic life cycle of the PlusClient looks a bit like this:

When we call the onStart, we immediately call mPlusClient.connect(). Google Play Services checks whether the user is already signed in to the application, and, if so the onConnected method is instantly called and the user seamlessly signed in.

If the connect() call fails, then onConnectionFailed() is called with a ConnectionResult object, which represents the error. The error is probably RESOLUTION_REQUIRED, which unsurprisingly means it also has a resolution. That is generally an intent that can be kicked off. The first one that is likely to be seen is that the account needs to be chosen.

Starting the resolution for that result will display the chooser activity, and when it completed our onActivityResult() method will be called.  The onActivityResult() can then call connect() on the PlusClient again. If all the errors have been resolved, the onConnected() method is called, but we'll likely get another error, requiring the consent dialogue to be displayed. Once this has been accepted, and a token retrieved, the activity result is called, we connect() again and the onConnected() call is reached.

Two things throw flies into this ointment:

a) We generally don't want to resolve on the onConnectionFailed() error until the user presses a button
b) While the resolution is happening onStart() may be called multiple times on our activity

The first means we need to care about a bit of state in onConnectionFailed(). If the user hasn't pressed the button, we need to not start the error resolution, and in fact kick that off in response to the onClick(). Once they have started though, we should resolve every error we reach.

The second complicates this, because if we just resolve whenever the result has a resolution, if onStart gets called again, we'll get another connect and start kicking off the resolution again, displaying two dialogs!

The solution is just to put a flag around the functionality based on whether we're in the middle of a resolution - you can see that as the mResolveOnFail in the code above. This defaults to off, so that when the activity starts and calls the mPlusClient.connect() it doesn't immediately display the account chooser or consent dialogue to the user. We turn it on in response to the sign-in button being pressed.

We also flip it off again when we get the onConnected() callback, so that if the user signs out we're in the right state.

2. Sign-in succeeds, but we can't retrieve any profile information.

One of the often overlooked steps when setting up Google+ Sign-In on Android is that the project must be associated with a client ID from the API console. On the web and in iOS the developer has to specify this client ID and the application will show an error if it is not given, but on Android it is inferred automatically from the combination of the Android package name, and the SHA-1 fingerprint of the signing key.

The API console is where the developer enables various services, and where they can manage the quota they have assigned. If an application runs without a matching API console project, it is effectively assigned 0 quota. This means that an un-linked application can go through the sign-in flow, retrieve an oAuth 2.0 access token for the user, but not actually be able to make any calls. So, for example, calling a lot of the PlusClient method will result in unexciting nulls instead of exciting profile data.

Of course, it doesn't have to be that someone hasn't made a client ID! It can equally happen with a typo of the packagename, or (as in one case I saw earlier this week), the system having more than one Android keystore on it - so the fingerprint was from one, but the APK was signed with another. Either way, setting up the correct details in the console will resolve it.

If you're not sure whether this is the problem that you're having, there is a relatively easy way to check. Just sign-in as normal, and in you onConnected() callback retrieve the oAuth 2.0 access token. You need to do this off the main thread, else it can cause deadlocks:
You can then enter the access token into tokeninfo endpoint:
This should display a client ID that matches your project. If it displays something else it's probably not set up right. If it's, and looks a bit like this:

 "issued_to": "",
 "audience": "",
 "user_id": "104824858261236811362",
 "scope": "",
 "expires_in": 1353,
 "access_type": "online" 

Then the app hasn't matched a project at all,  so you'll need to configure it in the API console. If you see the error INVALID_KEY in the logs, it may also indicate that the API console project is not properly configured - though I must admit I haven't yet quite grokked under what circumstances that one does occur.

There's a full example activity with a plethora of comments in this Android Google+ Sign-In gist, which hopefully will be useful as a reference to one way of implementing sign in.

UPDATE - one additional bug that surfaced quite a lot around the release of the latest version of Google Play Services was the onConnectionFailed method being called with a ConnectionResult which does not have a resolution, and has an error code of ConnectionResult.SERVICE_VERSION_UPDATE_REQUIRED.

As you might guess from the name, this indicates that the version of Google Play Services on the device is too low. Normally new versions will be updated automatically, but there is always a time delay in the roll out, so it is quite possible to get this error as updates are released.

You can handle this in the onConnectionFailed by calling getErrorDialog on GooglePlayServicesUtil, but the best way is to actually check whether Google Play Services is installed and up to date before even trying to connect. You can see a snippet of how to do this in the documentation.

Popular posts from this blog

Client-Server Authentication with ID tokens

TLS and ZeroMQ