11

I am writing an app which requires user login.

I would like to implement it by Google+ and have followed the following articles to set-up my login activity LoginActivity:

The flow:

  1. User opens my app -- MainActivity
  2. It checks whether the user has signed-in
  3. If the user has not signed-in, it redirects the user to LoginActivity. Otherwise, it remains on the MainActivity.

How can I check, in MainActivity, that the user has signed-in?

Below is the code of LoginActivity.java: package com.noideabox.googlelogin;

import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.SignInButton;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.GoogleApiClient.ConnectionCallbacks;
import com.google.android.gms.common.api.GoogleApiClient.OnConnectionFailedListener;
import com.google.android.gms.plus.Plus;
import com.google.android.gms.plus.model.people.Person;

import android.app.PendingIntent;
import android.content.Intent;
import android.content.IntentSender.SendIntentException;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;

public class LoginActivity extends FragmentActivity implements
        ConnectionCallbacks, OnConnectionFailedListener, View.OnClickListener {

    private static final String TAG = "android-plus-quickstart";

    private static final int STATE_DEFAULT = 0;
    private static final int STATE_SIGN_IN = 1;
    private static final int STATE_IN_PROGRESS = 2;

    private static final int RC_SIGN_IN = 0;

    private static final String SAVED_PROGRESS = "sign_in_progress";

    private GoogleApiClient mGoogleApiClient;

    // We use mSignInProgress to track whether user has clicked sign in.
    // mSignInProgress can be one of three values:
    //
    // STATE_DEFAULT: The default state of the application before the user
    // has clicked 'sign in', or after they have clicked
    // 'sign out'. In this state we will not attempt to
    // resolve sign in errors and so will display our
    // Activity in a signed out state.
    // STATE_SIGN_IN: This state indicates that the user has clicked 'sign
    // in', so resolve successive errors preventing sign in
    // until the user has successfully authorized an account
    // for our app.
    // STATE_IN_PROGRESS: This state indicates that we have started an intent to
    // resolve an error, and so we should not start further
    // intents until the current intent completes.
    private int mSignInProgress;

    // Used to store the PendingIntent most recently returned by Google Play
    // services until the user clicks 'sign in'.
    private PendingIntent mSignInIntent;

    private SignInButton mSignInButton;
    private Button mSignOutButton;
    private Button mRevokeButton;
    private TextView mStatus;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);

        mSignInButton = (SignInButton) findViewById(R.id.sign_in_button);
        mSignOutButton = (Button) findViewById(R.id.sign_out_button);
        mRevokeButton = (Button) findViewById(R.id.revoke_access_button);
        mStatus = (TextView) findViewById(R.id.sign_in_status);

        mSignInButton.setOnClickListener(this);
        mSignOutButton.setOnClickListener(this);
        mRevokeButton.setOnClickListener(this);

        if (savedInstanceState != null) {
            mSignInProgress = savedInstanceState.getInt(SAVED_PROGRESS, STATE_DEFAULT);
        }

        mGoogleApiClient = build_GoogleApiClient();
    }

    @Override
    protected void onStart() {
        super.onStart();
        mGoogleApiClient.connect();
    }

    @Override
    protected void onStop() {
        super.onStop();

        if (mGoogleApiClient.isConnected()) {
            mGoogleApiClient.disconnect();
        }
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putInt(SAVED_PROGRESS, mSignInProgress);
    }

    @Override
    public void onClick(View v) {
        if (!mGoogleApiClient.isConnecting()) {
            // We only process button clicks when GoogleApiClient is not
            // transitioning
            // between connected and not connected.
            switch (v.getId()) {
            case R.id.sign_in_button:
                mStatus.setText(R.string.status_signing_in);
                resolveSignInError();
                break;
            case R.id.sign_out_button:
                // We clear the default account on sign out so that Google Play
                // services will not return an onConnected callback without user
                // interaction.
                Plus.AccountApi.clearDefaultAccount(mGoogleApiClient);
                mGoogleApiClient.disconnect();
                mGoogleApiClient.connect();
                break;
            case R.id.revoke_access_button:
                // After we revoke permissions for the user with a
                // GoogleApiClient
                // instance, we must discard it and create a new one.
                Plus.AccountApi.clearDefaultAccount(mGoogleApiClient);
                // Our sample has caches no user data from Google+, however we
                // would normally register a callback on
                // revokeAccessAndDisconnect
                // to delete user data so that we comply with Google developer
                // policies.
                Plus.AccountApi.revokeAccessAndDisconnect(mGoogleApiClient);
                mGoogleApiClient = build_GoogleApiClient();
                mGoogleApiClient.connect();
                break;
            }
        }
    }

    /*
     * onConnected is called when our Activity successfully connects to Google
     * Play services. onConnected indicates that an account was selected on the
     * device, that the selected account has granted any requested permissions
     * to our app and that we were able to establish a service connection to
     * Google Play services.
     */
    @Override
    public void onConnected(Bundle connectionHint) {
        // Reaching onConnected means we consider the user signed in.
        Log.i(TAG, "onConnected");

        // Update the user interface to reflect that the user is signed in.
        mSignInButton.setEnabled(false);
        mSignOutButton.setEnabled(true);
        mRevokeButton.setEnabled(true);

        // Retrieve some profile information to personalize our app for the
        // user.
        Person currentUser = Plus.PeopleApi.getCurrentPerson(mGoogleApiClient);

        mStatus.setText(String.format(
            getResources().getString(R.string.signed_in_as),
            currentUser.getDisplayName()
        ));

        // Indicate that the sign in process is complete.
        mSignInProgress = STATE_DEFAULT;

        Toast.makeText(this, mStatus.getText(), Toast.LENGTH_LONG).show();
    }

    /*
     * onConnectionFailed is called when our Activity could not connect to
     * Google Play services. onConnectionFailed indicates that the user needs to
     * select an account, grant permissions or resolve an error in order to sign
     * in.
     */
    @Override
    public void onConnectionFailed(ConnectionResult result) {
        // Refer to the javadoc for ConnectionResult to see what error codes
        // might
        // be returned in onConnectionFailed.
        Log.i(TAG, "onConnectionFailed: ConnectionResult.getErrorCode() = " + result.getErrorCode());

        if (mSignInProgress != STATE_IN_PROGRESS) {
            // We do not have an intent in progress so we should store the
            // latest
            // error resolution intent for use when the sign in button is
            // clicked.
            mSignInIntent = result.getResolution();

            if (mSignInProgress == STATE_SIGN_IN) {
                // STATE_SIGN_IN indicates the user already clicked the sign in
                // button
                // so we should continue processing errors until the user is
                // signed in
                // or they click cancel.
                resolveSignInError();
            }
        }

        // In this sample we consider the user signed out whenever they do not
        // have
        // a connection to Google Play services.
        onSignedOut();
    }

    /*
     * Starts an appropriate intent or dialog for user interaction to resolve
     * the current error preventing the user from being signed in. This could be
     * a dialog allowing the user to select an account, an activity allowing the
     * user to consent to the permissions being requested by your app, a setting
     * to enable device networking, etc.
     */
    private void resolveSignInError() {
        if (mSignInIntent != null) {
            // We have an intent which will allow our user to sign in or
            // resolve an error. For example if the user needs to
            // select an account to sign in with, or if they need to consent
            // to the permissions your app is requesting.

            try {
                // Send the pending intent that we stored on the most recent
                // OnConnectionFailed callback. This will allow the user to
                // resolve the error currently preventing our connection to
                // Google Play services.
                mSignInProgress = STATE_IN_PROGRESS;
                startIntentSenderForResult(
                    mSignInIntent.getIntentSender(),
                    RC_SIGN_IN, null, 0, 0, 0
                );
            } catch (SendIntentException e) {
                Log.i(TAG, "Sign in intent could not be sent: " + e.getLocalizedMessage());
                // The intent was canceled before it was sent. Attempt to
                // connect to
                // get an updated ConnectionResult.
                mSignInProgress = STATE_SIGN_IN;
                mGoogleApiClient.connect();
            }
        }
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        switch (requestCode) {
        case RC_SIGN_IN:
            if (resultCode == RESULT_OK) {
                // If the error resolution was successful we should continue
                // processing errors.
                mSignInProgress = STATE_SIGN_IN;
            } else {
                // If the error resolution was not successful or the user
                // canceled,
                // we should stop processing errors.
                mSignInProgress = STATE_DEFAULT;
            }

            if (!mGoogleApiClient.isConnecting()) {
                // If Google Play services resolved the issue with a dialog then
                // onStart is not called so we need to re-attempt connection
                // here.
                mGoogleApiClient.connect();
            }
            break;
        }
    }

    @Override
    public void onConnectionSuspended(int cause) {
        // The connection to Google Play services was lost for some reason.
        // We call connect() to attempt to re-establish the connection or get a
        // ConnectionResult that we can attempt to resolve.
        mGoogleApiClient.connect();
    }

    private GoogleApiClient build_GoogleApiClient() {
        // When we build the GoogleApiClient we specify where connected and
        // connection failed callbacks should be returned, which Google APIs our
        // app uses and which OAuth 2.0 scopes our app requests.
        return new GoogleApiClient.Builder(this)
            .addConnectionCallbacks(this)
            .addOnConnectionFailedListener(this)
            .addApi(Plus.API, null)
            .addScope(Plus.SCOPE_PLUS_LOGIN)
            .build();
    }

    private void onSignedOut() {
        // Update the UI to reflect that the user is signed out.
        mSignInButton.setEnabled(true);
        mSignOutButton.setEnabled(false);
        mRevokeButton.setEnabled(false);

        mStatus.setText(R.string.status_signed_out);
    }
}
CY Wong
  • 135
  • 1
  • 4

3 Answers3

13

The silentSignIn method of GoogleSignInApi can be used to check the validity of cached credential of the user.

We can use GoogleSignInApi.silentSignIn() method to check if the login credential is valid or not.
It returns an OptionalPendingResult object which is used to check whether the credential is valid or not. If the credential is valid OptionalPendingResult's isDone() method will return true.
The get method can then be used to obtain the result immediately (If it is available).

Android Documentation for GoogleSignInApi:
https://developers.google.com/android/reference/com/google/android/gms/auth/api/signin/GoogleSignInApi

Android Documentation for OptionalPendingResult:
https://developers.google.com/android/reference/com/google/android/gms/common/api/OptionalPendingResult

Here's the code for checking if the credentials are valid or not.

OptionalPendingResult<GoogleSignInResult> opr = Auth.GoogleSignInApi.silentSignIn(google_api_client);
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);

}

Note - The method requires a GoogleApiClient object so please make sure that the googleapiclient object is connected before validation.

Shubham025
  • 131
  • 1
  • 3
7

Connect a GoogleApiClient in your MainActivity as normal, but in the onConnectionFailed instead of storing the result or resolving the error enable the sign in button. When the user clicks that, start your LoginActivity, which should also implement GoogleApiClient and related interfaces, and do all of your ConnectionResult handling from there.

You might find it easier if you create a base activity that does the GoogleApiClient setup that both your activities implement. It's totally fine to have the GoogleApiClient connection started on each activity - its designed to be used that way.

BoltClock
  • 700,868
  • 160
  • 1,392
  • 1,356
Ian Barber
  • 19,765
  • 3
  • 58
  • 58
  • I have tried your method to connect GoogleApiClient in MainActivity [googleApiClient.connect() in onStart()]. However, it completely loads and displays the MainActivity before the GoogleApiClient determines a failed login. How can I make sure that it won't display the content before a login is guaranteed? – CY Wong Mar 18 '14 at 21:50
  • 1
    You can't. The whole model is asynchronous. This can incur a network operation. You wouldn't want your whole activity to block and hang (and get an ANR). Instead build your app to be tolerant of the potential lag. – Hounshell Mar 21 '14 at 17:24
3

What I have done is store a boolean value in SharedPreferences (so its persisted in case the app is finished), and use that to decide if I should present the G+ login or not.

So basically you do what you are already doing, but in the onCreate method of you MainActivity you check if the user has logged in already. If it has just continue the activity as usual. Otherwise do the same as the tutorial

Edit: Also, I would probably hide the revoke and signout buttons if the user has not authorized yet, as they will be useless. Only display them if it makes sense, i.e. if the user has logged in,

Acapulco
  • 3,373
  • 8
  • 38
  • 51
  • 4
    Just to round this out a bit more, if you get a connection failed you should clear that flag, as this probably means the user de-authed your app. – Hounshell Mar 21 '14 at 17:25
  • @Hounshell Oh yeah, definitely need to remember that. Actually what I do is clear that flag in the revoke method, just after the ApiClient call succeeds. Good to point that though. – Acapulco Mar 21 '14 at 22:18
  • 1
    Test your app with the user de-authing via other mechanisms too. You can deauthorize apps using the website or using the Google Settings app on the phone. – Hounshell Apr 18 '14 at 01:09