Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android google+ login in a fragment

Currently I am trying to implement google+ login using fragment so I can use it from different activities. I have created fragment like this

public class GoogleSignUpFragment extends Fragment implements
    ConnectionCallbacks, OnConnectionFailedListener, OnClickListener {

// PlusClient Variables
private static final int REQUEST_CODE_RESOLVE_ERR = 9000;
private ProgressDialog mConnectionProgressDialog;
private PlusClient mPlusClient;
private ConnectionResult mConnectionResult;

@Override
public void onCreate(Bundle savedInstanceState) {
    // TODO Auto-generated method stub
    super.onCreate(savedInstanceState);

    mPlusClient = new PlusClient.Builder(getActivity()
            .getApplicationContext(), this, this)
            .setActions("http://schemas.google.com/AddActivity",
                    "http://schemas.google.com/BuyActivity")
            .setScopes(Scopes.PLUS_LOGIN, Scopes.PLUS_PROFILE).build();

    getActivity().findViewById(R.id.sign_in_button)
            .setOnClickListener(this);

    // Progress bar to be displayed if the connection failure is not
    // resolved.
    mConnectionProgressDialog = new ProgressDialog(getActivity());
    mConnectionProgressDialog.setMessage("Signing in...");

}

@Override
public void onStart() {
    // TODO Auto-generated method stub
    super.onStart();
    mPlusClient.connect();
}

@Override
public void onStop() {
    // TODO Auto-generated method stub
    super.onStop();
    mPlusClient.disconnect();
}

@Override
public void onActivityResult(int requestCode, int resultCode, Intent intent) {
    if (requestCode == REQUEST_CODE_RESOLVE_ERR
            && resultCode == Activity.RESULT_OK) {
        mConnectionResult = null;
        mPlusClient.connect();
    }
}

@Override
public void onClick(View view) {
    if (view.getId() == R.id.sign_in_button && !mPlusClient.isConnected()) {
        if (mConnectionResult == null) {
            mConnectionProgressDialog.show();

        } else {
            try {
                mConnectionResult.startResolutionForResult(getActivity(),
                        REQUEST_CODE_RESOLVE_ERR);
            } catch (SendIntentException e) {
                // Try connecting again.
                mConnectionResult = null;
                mPlusClient.connect();
            }
        }
    }
}

@Override
public void onConnectionFailed(ConnectionResult result) {
    if (mConnectionProgressDialog.isShowing()) {
        // The user clicked the sign-in button already. Start to resolve
        // connection errors. Wait until onConnected() to dismiss the
        // connection dialog.
        if (result.hasResolution()) {
            try {
                result.startResolutionForResult(getActivity(),
                        REQUEST_CODE_RESOLVE_ERR);
            } catch (SendIntentException e) {
                mPlusClient.connect();
            }
        }
    }

}

@Override
public void onConnected(Bundle connectionHint) {
    mConnectionProgressDialog.dismiss();
    Toast.makeText(getActivity().getApplicationContext(),
            "User is connected!", Toast.LENGTH_LONG).show();
    Intent intent = new Intent(getActivity().getApplicationContext(),
            LogoutActivity.class);
    startActivity(intent);

}

@Override
public void onDisconnected() {
    // TODO Auto-generated method stub

}

}

I was doing it with the official tutorial, and then reworking a little to use it inside fragment.

In main activity I have:

FragmentManager fragmentManager = getSupportFragmentManager();
    android.support.v4.app.FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();

    googleSignUpFragment = new GoogleSignUpFragment();
    fragmentTransaction.add(R.id.detailFragment, googleSignUpFragment);
    fragmentTransaction.commit();

However whenever I click the log-in button, there is only dialog that says "Signing in..." and nothing else happens. Any ideas how to fix that problem? It was working just fine inside normal activity.

Thanks for any help.

@Edit. I have figured out that the onConnectionFailed method is not launched, and so there is no onActivityResult it seems, however when I put directly mPlusClient.connect() inside my click it works, but I feel like its not a proper way to do that. Maybe the problem is about that onConnectionFailed not beign launched

@Edit 2. Now I have figured out that the problem seems to be with dialog.isShowing() as, when I remove the line, account chooser is showing, right after start, tried with bool value, that I change after click however this does not work too...

like image 381
user2141889 Avatar asked Nov 26 '13 12:11

user2141889


2 Answers

In order to use Fragments with the official tutorial, you have to know that the onActivityResult method will be called within the Activity owning the Fragment.

in the fragment then implement the public connect() method, which you call within the activity's onActivityResult instead of calling plus.connect(); directly. This way turotial will work with using fragments, exactly as intended to work when using Activity. If you need detailed example I can add the edit to this answer.

I would suggest using Activity and not Fragment due to code modularity and easier implementation and handling the on key back buttons to prevent going into the app, etc.

like image 195
Luka Bradeško Avatar answered Oct 20 '22 11:10

Luka Bradeško


For those who want to see an example working in Android Studio, just create a LoginActivity, check "Include Google+ sig in".
If you are working in a Fragment just add a call to onActivityResult from your Activity to Fragment like this:

FragmentManager fragmentManager = getSupportFragmentManager();
    fragmentManager.findFragmentById(R.id.login_fragment_leftPanel).onActivityResult(requestCode, resultCode, data);

In the example created by AS you should not implement the abstract functions in your activity... just use in fragment. Worked for me with com.google.android.gms:play-services:6.1.11, if you need the code, just ask for it.

CODE FOR USER @SHADOW

Sure Shadow, what I did was make the following code works. After that I modified it for my project.
They use 2 class (PlusBaseActivity and LoginActivity).

/**
* A login screen that offers login via email/password and via Google+ sign in.
* * ************ IMPORTANT SETUP NOTES: ************
* In order for Google+ sign in to work with your app, you must first go to:
* https://developers.google.com/+/mobile/android/getting-started#step_1_enable_the_google_api
* and follow the steps in "Step 1" to create an OAuth 2.0 client for your package.
*/

public class LoginActivity extends PlusBaseActivity implements LoaderCallbacks<Cursor>{

/**
 * A dummy authentication store containing known user names and passwords.
 * TODO: remove after connecting to a real authentication system.
 */
private static final String[] DUMMY_CREDENTIALS = new String[]{
        "[email protected]:hello", "[email protected]:world"
};
/**
 * Keep track of the login task to ensure we can cancel it if requested.
 */
private UserLoginTask mAuthTask = null;

// UI references.
private AutoCompleteTextView mEmailView;
private EditText mPasswordView;
private View mProgressView;
private View mEmailLoginFormView;
private SignInButton mPlusSignInButton;
private View mSignOutButtons;
private View mLoginFormView;

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

    // Find the Google+ sign in button.
    mPlusSignInButton = (SignInButton) findViewById(R.id.plus_sign_in_button);
    if (supportsGooglePlayServices()) {
        // Set a listener to connect the user when the G+ button is clicked.
        mPlusSignInButton.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View view) {
                signIn();
            }
        });
    } else {
        // Don't offer G+ sign in if the app's version is too low to support Google Play
        // Services.
        mPlusSignInButton.setVisibility(View.GONE);
        return;
    }

    // Set up the login form.
    mEmailView = (AutoCompleteTextView) findViewById(R.id.email);
    populateAutoComplete();

    mPasswordView = (EditText) findViewById(R.id.password);
    mPasswordView.setOnEditorActionListener(new TextView.OnEditorActionListener() {
        @Override
        public boolean onEditorAction(TextView textView, int id, KeyEvent keyEvent) {
            if (id == R.id.login || id == EditorInfo.IME_NULL) {
                attemptLogin();
                return true;
            }
            return false;
        }
    });

    Button mEmailSignInButton = (Button) findViewById(R.id.email_sign_in_button);
    mEmailSignInButton.setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View view) {
            attemptLogin();
        }
    });

    mLoginFormView = findViewById(R.id.login_form);
    mProgressView = findViewById(R.id.login_progress);
    mEmailLoginFormView = findViewById(R.id.email_login_form);
    mSignOutButtons = findViewById(R.id.plus_sign_out_buttons);
}

private void populateAutoComplete() {
    getLoaderManager().initLoader(0, null, this);
}


/**
 * Attempts to sign in or register the account specified by the login form.
 * If there are form errors (invalid email, missing fields, etc.), the
 * errors are presented and no actual login attempt is made.
 */
public void attemptLogin() {
    if (mAuthTask != null) {
        return;
    }

    // Reset errors.
    mEmailView.setError(null);
    mPasswordView.setError(null);

    // Store values at the time of the login attempt.
    String email = mEmailView.getText().toString();
    String password = mPasswordView.getText().toString();

    boolean cancel = false;
    View focusView = null;


    // Check for a valid password, if the user entered one.
    if (!TextUtils.isEmpty(password) && !isPasswordValid(password)) {
        mPasswordView.setError(getString(R.string.error_invalid_password));
        focusView = mPasswordView;
        cancel = true;
    }

    // Check for a valid email address.
    if (TextUtils.isEmpty(email)) {
        mEmailView.setError(getString(R.string.error_field_required));
        focusView = mEmailView;
        cancel = true;
    } else if (!isEmailValid(email)) {
        mEmailView.setError(getString(R.string.error_invalid_email));
        focusView = mEmailView;
        cancel = true;
    }

    if (cancel) {
        // There was an error; don't attempt login and focus the first
        // form field with an error.
        focusView.requestFocus();
    } else {
        // Show a progress spinner, and kick off a background task to
        // perform the user login attempt.
        showProgress(true);
        mAuthTask = new UserLoginTask(email, password);
        mAuthTask.execute((Void) null);
    }
}
private boolean isEmailValid(String email) {
    //TODO: Replace this with your own logic
    return email.contains("@");
}

private boolean isPasswordValid(String password) {
    //TODO: Replace this with your own logic
    return password.length() > 4;
}

/**
 * Shows the progress UI and hides the login form.
 */
@TargetApi(Build.VERSION_CODES.HONEYCOMB_MR2)
public void showProgress(final boolean show) {
    // On Honeycomb MR2 we have the ViewPropertyAnimator APIs, which allow
    // for very easy animations. If available, use these APIs to fade-in
    // the progress spinner.
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) {
        int shortAnimTime = getResources().getInteger(android.R.integer.config_shortAnimTime);

        mLoginFormView.setVisibility(show ? View.GONE : View.VISIBLE);
        mLoginFormView.animate().setDuration(shortAnimTime).alpha(
                show ? 0 : 1).setListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                mLoginFormView.setVisibility(show ? View.GONE : View.VISIBLE);
            }
        });

        mProgressView.setVisibility(show ? View.VISIBLE : View.GONE);
        mProgressView.animate().setDuration(shortAnimTime).alpha(
                show ? 1 : 0).setListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                mProgressView.setVisibility(show ? View.VISIBLE : View.GONE);
            }
        });
    } else {
        // The ViewPropertyAnimator APIs are not available, so simply show
        // and hide the relevant UI components.
        mProgressView.setVisibility(show ? View.VISIBLE : View.GONE);
        mLoginFormView.setVisibility(show ? View.GONE : View.VISIBLE);
    }
}

@Override
protected void onPlusClientSignIn() {
    //Set up sign out and disconnect buttons.
    Button signOutButton = (Button) findViewById(R.id.plus_sign_out_button);
    signOutButton.setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View view) {
            signOut();
        }
    });
    Button disconnectButton = (Button) findViewById(R.id.plus_disconnect_button);
    disconnectButton.setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View view) {
            revokeAccess();
        }
    });
}

@Override
protected void onPlusClientBlockingUI(boolean show) {
    showProgress(show);
}

@Override
protected void updateConnectButtonState() {
    //TODO: Update this logic to also handle the user logged in by email.
    boolean connected = getPlusClient().isConnected();

    mSignOutButtons.setVisibility(connected ? View.VISIBLE : View.GONE);
    mPlusSignInButton.setVisibility(connected ? View.GONE : View.VISIBLE);
    mEmailLoginFormView.setVisibility(connected ? View.GONE : View.VISIBLE);
}

@Override
protected void onPlusClientRevokeAccess() {
    // TODO: Access to the user's G+ account has been revoked.  Per the developer terms, delete
    // any stored user data here.
}

@Override
protected void onPlusClientSignOut() {

}

/**
 * Check if the device supports Google Play Services.  It's best
 * practice to check first rather than handling this as an error case.
 *
 * @return whether the device supports Google Play Services
 */
private boolean supportsGooglePlayServices() {
    return GooglePlayServicesUtil.isGooglePlayServicesAvailable(this) ==
            ConnectionResult.SUCCESS;
}

@Override
public Loader<Cursor> onCreateLoader(int i, Bundle bundle) {
    return new CursorLoader(this,
            // Retrieve data rows for the device user's 'profile' contact.
            Uri.withAppendedPath(ContactsContract.Profile.CONTENT_URI,
                    ContactsContract.Contacts.Data.CONTENT_DIRECTORY), ProfileQuery.PROJECTION,

            // Select only email addresses.
            ContactsContract.Contacts.Data.MIMETYPE +
                    " = ?", new String[]{ContactsContract.CommonDataKinds.Email
                                                                 .CONTENT_ITEM_TYPE},

            // Show primary email addresses first. Note that there won't be
            // a primary email address if the user hasn't specified one.
            ContactsContract.Contacts.Data.IS_PRIMARY + " DESC");
}

@Override
public void onLoadFinished(Loader<Cursor> cursorLoader, Cursor cursor) {
    List<String> emails = new ArrayList<String>();
    cursor.moveToFirst();
    while (!cursor.isAfterLast()) {
        emails.add(cursor.getString(ProfileQuery.ADDRESS));
        cursor.moveToNext();
    }

    addEmailsToAutoComplete(emails);
}

@Override
public void onLoaderReset(Loader<Cursor> cursorLoader) {

}

private interface ProfileQuery {
    String[] PROJECTION = {
            ContactsContract.CommonDataKinds.Email.ADDRESS,
            ContactsContract.CommonDataKinds.Email.IS_PRIMARY,
    };

    int ADDRESS = 0;
    int IS_PRIMARY = 1;
}


private void addEmailsToAutoComplete(List<String> emailAddressCollection) {
    //Create adapter to tell the AutoCompleteTextView what to show in its dropdown list.
    ArrayAdapter<String> adapter =
            new ArrayAdapter<String>(LoginActivity.this,
                    android.R.layout.simple_dropdown_item_1line, emailAddressCollection);

    mEmailView.setAdapter(adapter);
}

/**
 * Represents an asynchronous login/registration task used to authenticate
 * the user.
 */
public class UserLoginTask extends AsyncTask<Void, Void, Boolean> {

    private final String mEmail;
    private final String mPassword;

    UserLoginTask(String email, String password) {
        mEmail = email;
        mPassword = password;
    }

    @Override
    protected Boolean doInBackground(Void... params) {
        // TODO: attempt authentication against a network service.

        try {
            // Simulate network access.
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            return false;
        }

        for (String credential : DUMMY_CREDENTIALS) {
            String[] pieces = credential.split(":");
            if (pieces[0].equals(mEmail)) {
                // Account exists, return true if the password matches.
                return pieces[1].equals(mPassword);
            }
        }

        // TODO: register the new account here.
        return true;
    }

    @Override
    protected void onPostExecute(final Boolean success) {
        mAuthTask = null;
        showProgress(false);

        if (success) {
            finish();
        } else {
            mPasswordView.setError(getString(R.string.error_incorrect_password));
            mPasswordView.requestFocus();
        }
    }

    @Override
    protected void onCancelled() {
        mAuthTask = null;
        showProgress(false);
    }
}

}

public abstract class PlusBaseActivity extends Activity
    implements GooglePlayServicesClient.ConnectionCallbacks,
    GooglePlayServicesClient.OnConnectionFailedListener {

private static final String TAG = PlusBaseActivity.class.getSimpleName();

// A magic number we will use to know that our sign-in error resolution activity has completed
private static final int OUR_REQUEST_CODE = 49404;

// A flag to stop multiple dialogues appearing for the user
private boolean mAutoResolveOnFail;

// A flag to track when a connection is already in progress
public boolean mPlusClientIsConnecting = false;

// This is the helper object that connects to Google Play Services.
private PlusClient mPlusClient;

// The saved result from {@link #onConnectionFailed(ConnectionResult)}.  If a connection
// attempt has been made, this is non-null.
// If this IS null, then the connect method is still running.
private ConnectionResult mConnectionResult;


/**
 * Called when the {@link PlusClient} revokes access to this app.
 */
protected abstract void onPlusClientRevokeAccess();

/**
 * Called when the PlusClient is successfully connected.
 */
protected abstract void onPlusClientSignIn();

/**
 * Called when the {@link PlusClient} is disconnected.
 */
protected abstract void onPlusClientSignOut();

/**
 * Called when the {@link PlusClient} is blocking the UI.  If you have a progress bar widget,
 * this tells you when to show or hide it.
 */
protected abstract void onPlusClientBlockingUI(boolean show);

/**
 * Called when there is a change in connection state.  If you have "Sign in"/ "Connect",
 * "Sign out"/ "Disconnect", or "Revoke access" buttons, this lets you know when their states
 * need to be updated.
 */
protected abstract void updateConnectButtonState();

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // Initialize the PlusClient connection.
    // Scopes indicate the information about the user your application will be able to access.
    mPlusClient =
            new PlusClient.Builder(this, this, this).setScopes(Scopes.PLUS_LOGIN,
                    Scopes.PLUS_ME).build();
}

/**
 * Try to sign in the user.
 */
public void signIn() {
    if (!mPlusClient.isConnected()) {
        // Show the dialog as we are now signing in.
        setProgressBarVisible(true);
        // Make sure that we will start the resolution (e.g. fire the intent and pop up a
        // dialog for the user) for any errors that come in.
        mAutoResolveOnFail = true;
        // We should always have a connection result ready to resolve,
        // so we can start that process.
        if (mConnectionResult != null) {
            startResolution();
        } else {
            // If we don't have one though, we can start connect in
            // order to retrieve one.
            initiatePlusClientConnect();
        }
    }

    updateConnectButtonState();
}

/**
 * Connect the {@link PlusClient} only if a connection isn't already in progress.  This will
 * call back to {@link #onConnected(android.os.Bundle)} or
 * {@link #onConnectionFailed(com.google.android.gms.common.ConnectionResult)}.
 */
private void initiatePlusClientConnect() {
    if (!mPlusClient.isConnected() && !mPlusClient.isConnecting()) {
        mPlusClient.connect();
    }
}

/**
 * Disconnect the {@link PlusClient} only if it is connected (otherwise, it can throw an error.)
 * This will call back to {@link #onDisconnected()}.
 */
private void initiatePlusClientDisconnect() {
    if (mPlusClient.isConnected()) {
        mPlusClient.disconnect();
    }
}

/**
 * Sign out the user (so they can switch to another account).
 */
public void signOut() {

    // We only want to sign out if we're connected.
    if (mPlusClient.isConnected()) {
        // Clear the default account in order to allow the user to potentially choose a
        // different account from the account chooser.
        mPlusClient.clearDefaultAccount();

        // Disconnect from Google Play Services, then reconnect in order to restart the
        // process from scratch.
        initiatePlusClientDisconnect();

        Log.v(TAG, "Sign out successful!");
    }

    updateConnectButtonState();
}

/**
 * Revoke Google+ authorization completely.
 */
public void revokeAccess() {

    if (mPlusClient.isConnected()) {
        // Clear the default account as in the Sign Out.
        mPlusClient.clearDefaultAccount();

        // Revoke access to this entire application. This will call back to
        // onAccessRevoked when it is complete, as it needs to reach the Google
        // authentication servers to revoke all tokens.
        mPlusClient.revokeAccessAndDisconnect(new PlusClient.OnAccessRevokedListener() {
            public void onAccessRevoked(ConnectionResult result) {
                updateConnectButtonState();
                onPlusClientRevokeAccess();
            }
        });
    }

}

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

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

public boolean isPlusClientConnecting() {
    return mPlusClientIsConnecting;
}

private void setProgressBarVisible(boolean flag) {
    mPlusClientIsConnecting = flag;
    onPlusClientBlockingUI(flag);
}

/**
 * A helper method to flip the mResolveOnFail flag and start the resolution
 * of the ConnectionResult from the failed connect() call.
 */
private void startResolution() {
    try {
        // Don't start another resolution now until we have a result from the activity we're
        // about to start.
        mAutoResolveOnFail = false;
        // If we can resolve the error, then call start resolution and pass it an integer tag
        // we can use to track.
        // This means that when we get the onActivityResult callback we'll know it's from
        // being started here.
        mConnectionResult.startResolutionForResult(this, OUR_REQUEST_CODE);
    } catch (IntentSender.SendIntentException e) {
        // Any problems, just try to connect() again so we get a new ConnectionResult.
        mConnectionResult = null;
        initiatePlusClientConnect();
    }
}

/**
 * An earlier connection failed, and we're now receiving the result of the resolution attempt
 * by PlusClient.
 *
 * @see #onConnectionFailed(ConnectionResult)
 */
@Override
protected void onActivityResult(int requestCode, int responseCode, Intent intent) {
    updateConnectButtonState();
    if (requestCode == OUR_REQUEST_CODE && responseCode == RESULT_OK) {
        // If we have a successful result, we will want to be able to resolve any further
        // errors, so turn on resolution with our flag.
        mAutoResolveOnFail = true;
        // If we have a successful result, let's call connect() again. If there are any more
        // errors to resolve we'll get our onConnectionFailed, but if not,
        // we'll get onConnected.
        initiatePlusClientConnect();
    } else if (requestCode == OUR_REQUEST_CODE && responseCode != RESULT_OK) {
        // If we've got an error we can't resolve, we're no longer in the midst of signing
        // in, so we can stop the progress spinner.
        setProgressBarVisible(false);
    }
}

/**
 * Successfully connected (called by PlusClient)
 */
@Override
public void onConnected(Bundle connectionHint) {
    updateConnectButtonState();
    setProgressBarVisible(false);
    onPlusClientSignIn();
}

/**
 * Successfully disconnected (called by PlusClient)
 */
@Override
public void onDisconnected() {
    updateConnectButtonState();
    onPlusClientSignOut();
}

/**
 * Connection failed for some reason (called by PlusClient)
 * Try and resolve the result.  Failure here is usually not an indication of a serious error,
 * just that the user's input is needed.
 *
 * @see #onActivityResult(int, int, Intent)
 */
@Override
public void onConnectionFailed(ConnectionResult result) {
    updateConnectButtonState();

    // Most of the time, the connection will fail with a user resolvable result. We can store
    // that in our mConnectionResult property ready to be used when the user clicks the
    // sign-in button.
    if (result.hasResolution()) {
        mConnectionResult = result;
        if (mAutoResolveOnFail) {
            // This is a local helper function that starts the resolution of the problem,
            // which may be showing the user an account chooser or similar.
            startResolution();
        }
    }
}

public PlusClient getPlusClient() {
    return mPlusClient;
}

}

like image 21
MiguelHincapieC Avatar answered Oct 20 '22 11:10

MiguelHincapieC