10

Whenever I run the code for the very first time at my 1st app launched cycle

GoogleApiClient mGoogleApiClient = new GoogleApiClient.Builder(context)
    .addApi(Drive.API)
    .addScope(Drive.SCOPE_APPFOLDER) // required for App Folder sample
    .addConnectionCallbacks(this)
    .addOnConnectionFailedListener(this)
    .build();

mGoogleApiClient.connect();

I can see the following account chooser.

enter image description here

However, if previous connect is success, and I run the same code again for first time at my 2nd app launched cycle.

The account chooser will not pop up again. GoogleApiClient is going to use the account name, I choose in previous app launch cycle.

I wish to have my account chooser pop up every-time.

I came across How to Clear GoogleApiClient Default Account and Credentials

The proposed solution doesn't work for my case.

mGoogleApiClient.clearDefaultAccountAndReconnect()

If I had been connected for my previous app cycle, and I call the above code first time in my current app cycle, I will get the following exception.

java.lang.IllegalStateException: GoogleApiClient is not connected yet.
    at com.google.android.gms.common.internal.zzx.zza(Unknown Source)
    at com.google.android.gms.common.api.internal.zzj.clearDefaultAccountAndReconnect(Unknown Source)

The following code won't work either.

if (mGoogleApiClient.isConnected()) {
    // No chance to execute this code, if you run this code during app launch.
    mGoogleApiClient.clearDefaultAccountAndReconnect();
} else {
    // No account chooser will pop up if you had been connected in previous app life cycle
    mGoogleApiClient.connect();
}

May I know, how can I enforce GoogleApiClient to prompt account chooser UI each time I call connect?

Community
  • 1
  • 1
Cheok Yan Cheng
  • 47,586
  • 132
  • 466
  • 875

8 Answers8

13

In both GDAA and the REST Api, you have two options:
1/ you do not specify the account and the underlying system will manage it.
2/ you manage the account yourself.

If you use the first approach, you will never know who the user of your app selected. You can only 'clean' the account by means of clearDefaultAccountAndReconnect. The selection dialog pops up again and the user can select (add) another account.

If you need to know the current selected user account (i.e. for caching /persistence), you must manage the account selection yourself as you can see here (for REST) or here (for GDAA) - just follow the REQ_ACCPICK trail and the UT.AM class. This way you'll be in full control.

So, the short answer to your question is that you pop the

startActivityForResult(AccountPicker.newChooseAccountIntent(null,
        null, new String[]{GoogleAuthUtil.GOOGLE_ACCOUNT_TYPE}, true, null, null, null, null),
        REQ_ACCPICK);

activity yourself and deliver the resulting

email = data.getStringExtra(AccountManager.KEY_ACCOUNT_NAME)

to your setAccountName(email) as in:

   GAC = new GoogleApiClient.Builder(act)
      .addApi(Drive.API)
      .addScope(Drive.SCOPE_FILE)
      .addScope(Drive.SCOPE_APPFOLDER)
      .addConnectionCallbacks(...)
      .addOnConnectionFailedListener(...)
      ....
      .setAccountName(email)
      ....
      .build();

Good Luck

seanpj
  • 6,735
  • 2
  • 33
  • 54
  • 2
    I didn't realize that we can setAccountName directly. Thanks. – Cheok Yan Cheng Jan 11 '16 at 18:35
  • But bear in mind it has to be one of the emails from your account picker (valid accounts on the device). And there is NO 'getAccountName()', so you have to persist them yourself (the UT.AM class I mentioned above).. – seanpj Jan 11 '16 at 18:48
  • @seanpj, the answer really helped me, but not understand UT.AM class. Not sure what is't, and purpose of it. Please let me know. Thank you. – praveenb May 10 '16 at 20:17
  • Unfortunately I have retired half a year ago and can't be too specific (poor memory). But as I recollect, the AM class is just for persisting the current account info (the app must remember it and decide if to take the account from it's persistence repository or ask user to select one). I know, it is a poor answer, but the best I can give you from a train travelling around Europe :-) Also I really don't know how the GDAA layer changed since the original answer (always watch the dates of the answers). – seanpj May 14 '16 at 05:49
  • 3
    Another point: AccountPicker.newChooseAccountIntent() creates picker dialog that is completely different than that built in GooglePlayServices. It gets even worse: it is not themed correctly, dark background on light theme, widgets are black on black background. Looks like those guys at google don't bother with even the most trivial testing. –  Feb 14 '17 at 10:46
  • Any updates on how to use this with `GoogleSignInAccount` instead of `GoogleApiClient` since the latter is deprecated by now? I can't manage to make this work with `GoogleSignInClient`! – Finni Oct 28 '19 at 14:54
5

I think I am late to this but this is how I solved it.

Before I start the Google SignIn Intent which will show the builder. Intent signInIntent = Auth.GoogleSignInApi.getSignInIntent(mGoogleApiClient); startActivityForResult(signInIntent, RC_SIGN_IN);

Do this:

if (mGoogleApiClient.hasConnectedApi(Auth.GOOGLE_SIGN_IN_API)) { mGoogleApiClient.clearDefaultAccountAndReconnect(); }

Since mGoogleApiClient for some reason stores the logged in user. So if you do mGoogleApiClient.isConnected() it might not work always, even if you are recreating the instance of mGoogleApiClient. However, this will force it to clear the already selected user.

Tested and it works.

Kamil Kamili
  • 1,757
  • 5
  • 24
  • 39
2

Looks like currently you can not clear the default account unless GoogleApiClient is already connected. We can work around this by introducing a boolean flag to tell us if we need to clear the default account.

private boolean mClearDefaultAccount;

@Override
public void onConnectionFailed(ConnectionResult result) {
    if (!mIntentInProgress && result.hasResolution()) {
        try {
            mIntentInProgress = true;
            mClearDefaultAccount = false;
            startIntentSenderForResult(result.getResolution().getIntentSender(),
                    GOOGLE_PLUS, null, 0, 0, 0);
        } catch (IntentSender.SendIntentException e) {
            mIntentInProgress = false;
            // report error
        }
    } else if (!mIntentInProgress) {
       // report error
    }
}

@Override
public void onConnected(Bundle bundle) {
    if (mClearDefaultAccount) {
        mClearDefaultAccount = false;
        mGoogleApiClient.clearDefaultAccountAndReconnect();
        return;
    }
    // connected...
}

Before calling mGoogleApiClient.connect() set the boolean state flag appropriately to prompt for account to use to use the current default account as necessary. Note that you may incur a redundant connect() call if user has only one account on the device.

protected void connectToGoogleApi(final boolean clearDefaultAccount) {
    if (clearDefaultAccount && mGoogleApiClient.isConnected()) {
        mClearDefaultAccount = false;
        mGoogleApiClient.clearDefaultAccountAndReconnect();
    } else {
        mGoogleApiClient.connect();
    }
}
2

With a mix and match of solutions above, i got it working with this:

mGoogleApiClient = new GoogleApiClient.Builder(this)
.enableAutoManage(this, new GoogleApiClient.OnConnectionFailedListener() {
    @Override
    public void onConnectionFailed(@NonNull ConnectionResult connectionResult) {
    }
})
.addConnectionCallbacks(new GoogleApiClient.ConnectionCallbacks() {
    @Override
    public void onConnected(@Nullable Bundle bundle) {
        mGoogleApiClient.clearDefaultAccountAndReconnect(); // To remove to previously selected user's account so that the choose account UI will show
    }

    @Override
    public void onConnectionSuspended(int i) {

    }
})
.addApi(Auth.GOOGLE_SIGN_IN_API, new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN).requestEmail().build())
.build();

Hope it helps others save some time. =)

twelvester
  • 149
  • 1
  • 3
1

I found it easier to just recreate the API client if it's not connected or connecting, then it prompts for the account again.

SVD
  • 4,743
  • 2
  • 26
  • 38
0

My method of enforcing GoogleApiClient to prompt account chooser UI each time I call connect is pretty hackish. If you know any better way, please let us know.

It is built around the idea.

  1. If the account chooser UI is shown, onConnectionFailed must be triggered at least once.
  2. In onConnected, we can check whether onConnectionFailed has been triggered at least once. If not, we will call clearDefaultAccountAndReconnect.

Here's the complete code for such idea.

public class GoogleApiClientFragment extends Fragment implements
        GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener {

    public static GoogleApiClientFragment newInstance() {
        return new GoogleApiClientFragment();
    }

    /**
     * Handles resolution callbacks.
     */
    @Override
    public void onActivityResult(int requestCode, int resultCode,
                                    Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == RequestCode.REQUEST_GOOGLE_API_CLIENT_CONNECT && resultCode == Activity.RESULT_OK) {
            mGoogleApiClient.connect();
        }
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setRetainInstance(true);
    }

    @Override
    public void onResume() {
        super.onResume();
        if (mGoogleApiClient == null) {
            mGoogleApiClient = new GoogleApiClient.Builder(this.getContext())
                .addApi(Drive.API)
                .addScope(Drive.SCOPE_APPFOLDER)
                .addConnectionCallbacks(this)
                .addOnConnectionFailedListener(this)
                .build();

            accountPickerShown = false;
            mGoogleApiClient.connect();
        }
    }

    @Override
    public void onPause() {
        super.onPause();
        if (mGoogleApiClient != null) {
            mGoogleApiClient.disconnect();
        }
    }

    @Override
    public void onConnected(Bundle bundle) {
        Log.i(TAG, "GoogleApiClient connected");

        if (accountPickerShown == false && mGoogleApiClient.isConnected()) {
            mGoogleApiClient.clearDefaultAccountAndReconnect();
        }
    }

    @Override
    public void onConnectionSuspended(int i) {
        Log.i(TAG, "GoogleApiClient connection suspended");
    }

    @Override
    public void onConnectionFailed(ConnectionResult connectionResult) {
        Log.i(TAG, "GoogleApiClient connection failed: " + connectionResult.toString());

        if (!connectionResult.hasResolution()) {
            // show the localized error dialog.
            GoogleApiAvailability.getInstance().getErrorDialog(this.getActivity(), connectionResult.getErrorCode(), 0).show();
            return;
        }
        try {
            accountPickerShown = (connectionResult.getErrorCode() == ConnectionResult.SIGN_IN_REQUIRED);
            connectionResult.startResolutionForResult(this.getActivity(), RequestCode.REQUEST_GOOGLE_API_CLIENT_CONNECT);
        } catch (IntentSender.SendIntentException e) {
            Log.e(TAG, "Exception while starting resolution activity", e);
        }
    }

    private boolean accountPickerShown = false;
    private static final String TAG = "GoogleApiClientFragment";
    private GoogleApiClient mGoogleApiClient;
}
Cheok Yan Cheng
  • 47,586
  • 132
  • 466
  • 875
0

The accepted solution does work (though you will also have to include the google-play-services-auth api if you would like to import a more up-to-date version of the API into your application), but the account picker will show up in a different style which could confuse or concern users.

I offer 2 alternate solutions that are enticing if you want to keep your UI uniform and do not want to include the google-play-services-auth API.

You can call mGoogleApiClient.clearDefaultAccountAndReconnect() then immediately disconnect by calling mGoogleApiClient.disconnect() whenever you want to force a user to pick an account. By doing this, you can just call mGoogleApiClient.connect() to connect (forcing the account picker) the next time the user has to connect. This is useful when there is a specific event that you want to trigger the user picking an account again.

Another (more generally useful) solution in this same fashion would be to do the following (in a background thread, probably AsyncTask) instead of your final code block:

mGoogleApiClient.blockingConnect();
if (mGoogleApiClient.isConnected()) {
    mGoogleApiClient.clearDefaultAccountAndReconnect();
}

This will avoid the null pointer exception you were seeing and will force the account picker explicitly. It is a bit weird since you are connecting twice, but it works.

It would be nice if Google provided this functionality in the GoogleApiClient explicitly.

Smalls
  • 352
  • 3
  • 13
0

The problem is you signOut of the inner activity, but not from the signIn one, but if you signOut everytime before signing in then it would be solved. Simple! , just add

mGoogleSignInClient.signOut();

before your sign in starts, i.e:

Intent signInIntent = mGoogleSignInClient.getSignInIntent(); 

And this will logout any user just before you login.

Yashovardhan99
  • 887
  • 11
  • 26