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 APIs 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.
