LFMM - Implementing Google Sign In on Android
LFMM = Learn From My Mistakes
I decided I want to try a new thematic set of blog posts that showcase some silly mistakes I've made and how I eventually fixed them.
This first one is about an issue I had recently when trying to implement Google Sign In on Android.
The Problem
I was trying to implement Firebase Authentication with Google Sign In. So I followed all the usual tutorials and documentation provided by Google and everything was working great except that after the Sign In process was complete the GoogleSignInAccount
object I received from the library had no data on it. The object existed, but every property on it was null
.
This was very odd and extremely frustrating to me. I could not find anyone posting a similar issue on StackOverflow or Google. It took me more hours than I care to admit, but I eventually fixed it. I felt dumb about how simple a fix it was but hopefully now someone can learn from my mistake.
The Broken Code
So the first thing I did was add all the correct dependencies and configured the services online and downloaded and added the correct google-services.json
file, but I'm not going to go into those details since the problem was elsewhere.
Next I started with creating an instance of GoogleSignInOptions
. This class uses a Builder
pattern and is all fairly simple.
GoogleSignInOptions googleSignInOptions =
new GoogleSignInOptions.Builder(
GoogleSignInOptions.DEFAULT_SIGN_IN)
.requestIdToken(gsoClientId)
.requestEmail()
.build();
Next I created a GoogleApiClient
like so:
GoogleApiClient apiClient =
new GogleApiClient.Builder(context)
.enableAutoManage(fragmentActivity, listener)
.addApi(Auth.GOOGLE_SIGN_IN_API)
.build();
Then launched a sign in Intent
:
Intent signInIntent =
Auth.GoogleSignInApi.getSignInIntent(apiClient);
startActivityForResult(signInIntent, RC_SIGN_IN);
And handled the results in onActivityResult
:
@Override public void onActivityResult(
int requestCode,
int resultCode,
Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == RC_SIGN_IN) {
GoogleSignInResult result = Auth.GoogleSignInApi
.getSignInResultFromIntent(data);
if (result.isSuccess()) {
GoogleSignInAccount account = result
.getSignInAccount();
firebaseAuthWithGoogle(account);
} else {
// Google Sign In failed, update UI appropriately
}
}
}
And my implementation for firebaseAuthWithGoogle
was like this:
private void firebaseAuthWithGoogle(
GoogleSignInAccount account) {
AuthCredential credential = GoogleAuthProvider
.getCredential(account.getIdToken(), null);
firebaseAuth
.signInWithCredential(credential)
.addOnCompleteListener(task -> {
// firebase status checks truncated
});
}
And this is where the problem would manifest (in a crash). Like I said above, the GoogleSignInAccount
object existed, but all it's properties were null
, including ID token from getIdToken()
. Sending a null
to GoogleAuthProvider.getCredential
results in a lovely IllegalArgumentException
crash. And of course not having a valid ID token also means you didn't really autheticate the user either.
I tried a lot of different things, but we'll just skip ahead to the fix.
The Solution
So yeah, the problem was that I never actually used the GoogleSignInOptions
instance. You're supposed to pass it to your GoogleApiClient
instance like this:
GoogleApiClient apiClient =
new GogleApiClient.Builder(context)
.enableAutoManage(fragmentActivity, listener)
.addApi(Auth.GOOGLE_SIGN_IN_API, googleSignInOptions)
.build();
Yeah yeah... you probably already saw the problem before I even got to here. This was rather silly of me. It was compounded by my usage of MVP to try and keep as much of this logic in a Presenter as possible without caching a Context
or FragmentActivity
reference and while still dealing with onActivityResult
callbacks. This all made it difficult to see that my instance wasn't actually being used.
It also doesn't help that the addApi
method is overloaded with an option for recieving just the Auth
with no additional parameters, but that appears to be used for API
s that don't require any options. I accidentally choose this one while re-typing the code from the documentation (since I try to avoid copy/paste in situations like this).
Side Note about GoogleSignInOptions
Turns out that instance is a one-shot deal, so don't cache this and attempt to sign in again with the same instance or your app will crash. I only discovered this because of my goof above though so I guess I'll call this lesson another silver lining.