Google recently has deprecated the Google+ Sign in and process of obtaining oauth access tokens via GoogleAuthUtil.getToken API. Now, they reccomend a single entry point via new Google Sign-In API. The major reasons for doing so are 1. It enhances user experience and 2. It improves security, more here. Also starting with android 6.0, the GET_ACCOUNTS permission has to be requested at runtime and if you implement this API, it eliminates the need for requiring this permission.
The feature that is really exciting is that it introduces new silentSignIn API, which allows for cross device silent sign in (essentially if a user has signed into your application on another platform, he won't be shown the sign in prompt) provided that the requested scopes are same, so this improves the user experience. In addition, you don't have to use the GoogleAuthUtil.getToken API to obtain the tokens as they are granted on the initial sign-in.
So if you have an android application in which you had previously implemented the Google+ sign in and used other Google plus features and want to migrate your android applications to new Google Sign in implementation, this post explains how to do so. Depending upon whether you choose to automate the lifecycle for GoogleAPIClient (Use enableAutoManage, this approach is recommended as it avoids boilerplate code) or manage the lifecycle for GoogleAPIClient by implementing the ConnectionCallbacks interface, the code might slightly differ. But, as the latter approach requires a bit more code, I will explain the process using it.
What needs to be changed
- Replace mGoogleApiClient.connect() with mGoogleApiClient.connect(GoogleApiClient.SIGN_IN_MODE_OPTIONAL), this is basically required to allow the client to transition between authenticated and unauthenticated states and for use with GoogleSignInApi.
- Build a GoogleSignInOptions instance. While building the instance, request the additional scopes via requestScopes method,( this is where you can request scopes such as SCOPE_PLUS_LOGIN and SCOPE_PLUS_PROFILE). Also, if you need to authenticate the user with the backend and want to obtain the authorization token to access the API's using your backend use requestIdToken(serverToken) and requestServerAuthCode(serverToken) methods. Here unlike Google plus sign in the serverToken is just the clientId of the web application.
- Build the GoogleApiClient instance, use the addApi method to add the Auth.GOOGLE_SIGN_IN_API and Plus.API.
- In the onStart method connect the client using mGoogleApiClient.connect(GoogleApiClient.SIGN_IN_MODE_OPTIONAL) and in onStop method disconnect the client. (You may do this in onResume and onPause methods also).
- After the client is connected, first attempt the silentSignIn and if it fails with code SIGN_IN_REQUIRED, attempt to do a fresh sign in for the user.
- After the sign in is completed, then you can invoke the Plus.PeopleApi with user accountId to obtain users google profile information.
- To Sign out the user use Auth.GoogleSignInApi.signOut method and to revoke access use Auth.GoogleSignInApi.revokeAccess method.
- Remove the android.permission.GET_ACCOUNTS permission from android manifest.
Here's the relevant code.
@Override
protected void onCreate(Bundle savedInstanceState) {
..
//Here unlike Google plus sign in the serverToken is just the clientId of the web application
GoogleSignInOptions gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
.requestIdToken(serverToken).requestServerAuthCode(serverToken).
requestEmail().
requestScopes(Plus.SCOPE_PLUS_LOGIN, Plus.SCOPE_PLUS_PROFILE)
.build();
mGoogleApiClient = new GoogleApiClient.Builder(this)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this).
addApi(Auth.GOOGLE_SIGN_IN_API, gso).
addApi(Plus.API)
.build();
}
protected void onResume() {
super.onResume();
//Here isConnected is just a flag that checks whether user is connected to internet.
/*To avoid execution of this block you can check whether user previously signedIn on this device by storing a userIdToken and checking whether user needs to be signedIn automatically or not. */
if (!mGoogleApiClient.isConnecting() && !mGoogleApiClient.isConnected() && isConnected) {
mGoogleApiClient.connect(GoogleApiClient.SIGN_IN_MODE_OPTIONAL);
} //Here isSignedIn is a boolean flag that tracks whether the user is signedIn or not.
else if(isConnected&&mGoogleApiClient.isConnected()&&!isSignedIn){
signInUsingNewAPI();
}
}
private void signInUsingNewAPI() {
if (!isSignedIn&&isConnected) {
attemptSilentSignIn();
}
}
private void attemptSilentSignIn(){
OptionalPendingResult<GoogleSignInResult> opr = Auth.GoogleSignInApi.silentSignIn(mGoogleApiClient);
if (opr.isDone()) {
// If the user's cached credentials are valid, the OptionalPendingResult will be "done"
// and the GoogleSignInResult will be available instantly.
Log.d(TAG, "Got cached sign-in");
GoogleSignInResult result = opr.get();
handleSignInResult(result);
} else {
// If the user has not previously signed in on this device or the sign-in has expired,
// this asynchronous branch will attempt to sign in the user silently. Cross-device
// single sign-on will occur in this branch.
showProgressDialog();
opr.setResultCallback(new ResultCallback<GoogleSignInResult>() {
@Override
public void onResult(GoogleSignInResult googleSignInResult) {
hideProgressDialog();
handleSignInResult(googleSignInResult);
}
});
}
}
private void handleSignInResult(GoogleSignInResult result){
if (!result.getStatus().isSuccess()) {
isSignedIn = false;
mIntentInProgress = false;
if(result.getStatus().hasResolution()||result.getStatus().getStatusCode()== CommonStatusCodes.SIGN_IN_REQUIRED){
freshSignIn(); //Rather than using startResolutionForResult, we invoke our method which attempts to do a fresh sign in and if there is error it is handled in onActivityResult method.
}
}
else {
mIntentInProgress = false;
isSignedIn = true;
final GoogleSignInAccount account = result.getSignInAccount();
//Maybe save this result.
SharedPreferences.Editor editor = preferences.edit();
editor.putString("client_id_token", account.getIdToken());
editor.putString("auth_code",account.getServerAuthCode());
editor.apply();
//You can pass these credentials to your server from here.
//Invoke the GPlus People API
Plus.PeopleApi.load(mGoogleApiClient, account.getId()).setResultCallback(new ResultCallback<People.LoadPeopleResult>() {
@Override
public void onResult(@NonNull People.LoadPeopleResult loadPeopleResult) {
Person person = loadPeopleResult.getPersonBuffer().get(0);
//Method that obtains the userInfo
getProfileInfo(person, account.getEmail());
}
});
}
}
private void freshSignIn(){
Intent signInIntent = Auth.GoogleSignInApi.getSignInIntent(mGoogleApiClient);
showProgressDialog();
startActivityForResult(signInIntent, RC_SIGN_IN);
}
@Override
protected void onActivityResult(int requestCode, int responseCode,
Intent intent) {
if(requestCode==RC_RESOLVE_ERROR){
mIntentInProgress = false;
if (responseCode != RESULT_OK) {
isSignedIn = false;
//Maybe show a dialog to user ?
return;
}
//Attemp connection again.
if (!mGoogleApiClient.isConnecting()) {
mGoogleApiClient.connect();
}
}
else if (requestCode == RC_SIGN_IN) {
hideProgressDialog();
GoogleSignInResult result = Auth.GoogleSignInApi.getSignInResultFromIntent(intent);
handleSignInResult(result,false);
}
}
// Connection callbacks
@Override
public void onConnected(Bundle bundle) {
if(!isSignedIn) {
signInUsingNewAPI();
}
}
@Override
public void onConnectionSuspended(int i) {
isSignedIn = false;
if (!isSignedIn&&isConnected) {
mGoogleApiClient.connect(GoogleApiClient.SIGN_IN_MODE_OPTIONAL);
}
}
@Override
public void onConnectionFailed(ConnectionResult result) {
if (!result.hasResolution()) {
GoogleApiAvailability.getInstance().getErrorDialog(
this, result.getErrorCode(), RC_SIGN_IN).show();
return;
}
if (!mIntentInProgress) {
// Store the ConnectionResult for later usage
mConnectionResult = result;
if (!isSignedIn) {
// The user has already clicked 'sign-in' so we attempt to
// resolve all
// errors until the user is signed in, or they cancel.
resolveSignInError();
}
}
}
/**
* Method to resolve any signin errors
*/
private void resolveSignInError() {
if (mConnectionResult.hasResolution()) {
try {
mIntentInProgress = true;
mConnectionResult.startResolutionForResult(this, RC_RESOLVE_ERROR);
} catch (IntentSender.SendIntentException e) {
mIntentInProgress = false;
mGoogleApiClient.connect(GoogleApiClient.SIGN_IN_MODE_OPTIONAL);
}
}
}
//Sign out and revoke methods
/**
* Sign-out from google
*/
public void signOutFromGoogle() {
if (mGoogleApiClient.isConnected()) {
Auth.GoogleSignInApi.signOut(mGoogleApiClient).setResultCallback(
new ResultCallback<Status>() {
@Override
public void onResult(Status status) {
isSignedIn = false;
//do other stuff here.
mGoogleApiClient.disconnect();
//Builds a fresh instance of GoogleApiClient
buildGoogleApiClient();
}
});
}
}
/**
* Revoking access from google
*/
public void revokeGplusAccess() {
if (mGoogleApiClient.isConnected()) {
Auth.GoogleSignInApi.revokeAccess(mGoogleApiClient).setResultCallback(
new ResultCallback<Status>() {
@Override
public void onResult(Status status) {
isSignedIn = false;
//do other stuff here.
mGoogleApiClient.disconnect();
//Builds a fresh instance of GoogleApiClient
buildGoogleApiClient();
//You can inform your server of this change
}
});
}
}
//Other utility methods
private void hideProgressDialog() {
if (mProgressDialog != null && mProgressDialog.isShowing()) {
mProgressDialog.hide();
}
}
private void showProgressDialog() {
if (mProgressDialog == null) {
mProgressDialog = new ProgressDialog(this);
mProgressDialog.setMessage("Signing In");
mProgressDialog.setIndeterminate(true);
}
mProgressDialog.show();
}
Hope this code is helpful in helping you move to the new Google sign in implementation.