Skip to content

Cleanup + make sure IdpResponse is never null if resultCode == ResultCodes.OK #469

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Dec 22, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 16 additions & 17 deletions app/src/main/java/com/firebase/uidemo/auth/SignedInActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,6 @@
import butterknife.OnClick;

public class SignedInActivity extends AppCompatActivity {
private static final String EXTRA_IDP_RESPONSE = "extra_idp_response";

@BindView(android.R.id.content)
View mRootView;

Expand Down Expand Up @@ -78,7 +76,7 @@ public void onCreate(Bundle savedInstanceState) {
return;
}

mIdpResponse = getIntent().getParcelableExtra(EXTRA_IDP_RESPONSE);
mIdpResponse = IdpResponse.fromResultIntent(getIntent());

setContentView(R.layout.signed_in_layout);
ButterKnife.bind(this);
Expand Down Expand Up @@ -181,19 +179,21 @@ private void populateProfile() {
}

private void populateIdpToken() {
String token = null;
String secret = null;
if (mIdpResponse != null) {
String token = mIdpResponse.getIdpToken();
String secret = mIdpResponse.getIdpSecret();
if (token == null) {
findViewById(R.id.idp_token_layout).setVisibility(View.GONE);
} else {
((TextView) findViewById(R.id.idp_token)).setText(token);
}
if (secret == null) {
findViewById(R.id.idp_secret_layout).setVisibility(View.GONE);
} else {
((TextView) findViewById(R.id.idp_secret)).setText(secret);
}
token = mIdpResponse.getIdpToken();
secret = mIdpResponse.getIdpSecret();
}
if (token == null) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only show the token/secret if we have one.

findViewById(R.id.idp_token_layout).setVisibility(View.GONE);
} else {
((TextView) findViewById(R.id.idp_token)).setText(token);
}
if (secret == null) {
findViewById(R.id.idp_secret_layout).setVisibility(View.GONE);
} else {
((TextView) findViewById(R.id.idp_secret)).setText(secret);
}
}

Expand All @@ -204,9 +204,8 @@ private void showSnackbar(@StringRes int errorMessageRes) {
}

public static Intent createIntent(Context context, IdpResponse idpResponse) {
Intent in = new Intent();
Intent in = IdpResponse.getIntent(idpResponse);
in.setClass(context, SignedInActivity.class);
in.putExtra(EXTRA_IDP_RESPONSE, idpResponse);
return in;
}
}
15 changes: 9 additions & 6 deletions app/src/main/res/layout/auth_ui_layout.xml
Original file line number Diff line number Diff line change
Expand Up @@ -80,29 +80,32 @@
android:text="@string/auth_providers_header"/>

<CheckBox
android:id="@+id/email_provider"
android:id="@+id/google_provider"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="true"
android:text="@string/email_label"/>
android:text="@string/google_label"/>

<CheckBox
android:id="@+id/facebook_provider"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="true"
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make all providers be checked by default and use standard order: Google, Facebook, Twitter, Email.

android:text="@string/facebook_label"/>

<CheckBox
android:id="@+id/google_provider"
android:id="@+id/twitter_provider"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/google_label"/>
android:checked="true"
android:text="@string/twitter_label"/>

<CheckBox
android:id="@+id/twitter_provider"
android:id="@+id/email_provider"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/twitter_label"/>
android:checked="true"
android:text="@string/email_label"/>

<TextView
style="@style/Base.TextAppearance.AppCompat.Subhead"
Expand Down
8 changes: 4 additions & 4 deletions auth/src/main/java/com/firebase/ui/auth/ErrorCodes.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,6 @@
* Error codes retrieved from {@link IdpResponse#getErrorCode()}.
*/
public final class ErrorCodes {
private ErrorCodes() {
// We don't want people to initialize this class
}

/**
* Sign in failed due to lack of network connection
**/
Expand All @@ -17,4 +13,8 @@ private ErrorCodes() {
* An unknown error has occurred
**/
public static final int UNKNOWN_ERROR = 20;

private ErrorCodes() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: why do we need a constructor for this class at all?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't, this was just formalizing the class as a "Constants" class that shouldn't be instantiated.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it. Had not seen that technique before but I like it.

// no instance
}
}
8 changes: 4 additions & 4 deletions auth/src/main/java/com/firebase/ui/auth/ResultCodes.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,6 @@
* {@code startActivityForResult}.
*/
public final class ResultCodes {
private ResultCodes() {
// We don't want people to initialize this class
}

/**
* Sign in succeeded
**/
Expand All @@ -20,4 +16,8 @@ private ResultCodes() {
* Sign in canceled by user
**/
public static final int CANCELED = Activity.RESULT_CANCELED;

private ResultCodes() {
// no instance
}
}
2 changes: 1 addition & 1 deletion auth/src/main/java/com/firebase/ui/auth/ui/BaseHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ public void saveCredentialsOrFinish(
@Nullable String password,
IdpResponse response) {
if (saveSmartLock == null) {
finishActivity(activity, ResultCodes.OK, new Intent());
finishActivity(activity, ResultCodes.OK, IdpResponse.getIntent(response));
} else {
saveSmartLock.saveCredentialsOrFinish(
firebaseUser,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,9 @@
* Constants used for passing Intent extra params between authentication flow activities.
*/
public class ExtraConstants {
public static final String EXTRA_EMAIL = "extra_email";
public static final String EXTRA_USER = "extra_user";
public static final String EXTRA_ERROR_MESSAGE = "extra_error_msg";
public static final String EXTRA_FLOW_PARAMS = "extra_flow_params";
public static final String EXTRA_IDP_RESPONSE = "extra_idp_response";
public static final String EXTRA_PROVIDER = "extra_provider";
public static final String EXTRA_USER = "extra_user";
public static final String EXTRA_EMAIL = "extra_email";
public static final String HAS_EXISTING_INSTANCE = "has_existing_instance";
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.firebase.ui.auth.ui.email;
package com.firebase.ui.auth.ui;

import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.NonNull;
Expand Down Expand Up @@ -67,6 +69,14 @@ public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeParcelable(mPhotoUri, flags);
}

public static User getUser(Intent intent) {
return intent.getParcelableExtra(ExtraConstants.EXTRA_USER);
}

public static User getUser(Bundle arguments) {
return arguments.getParcelable(ExtraConstants.EXTRA_USER);
}

public static final class Builder {
private String mEmail;
private String mName;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,38 +40,40 @@
import com.firebase.ui.auth.ui.ExtraConstants;
import com.firebase.ui.auth.ui.FlowParameters;
import com.firebase.ui.auth.ui.TaskFailureLogger;
import com.firebase.ui.auth.ui.User;
import com.google.android.gms.tasks.OnCompleteListener;
import com.google.android.gms.tasks.OnFailureListener;
import com.google.android.gms.tasks.OnSuccessListener;
import com.google.android.gms.tasks.Task;
import com.google.firebase.auth.AuthCredential;
import com.google.firebase.auth.AuthResult;
import com.google.firebase.auth.FacebookAuthProvider;
import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.auth.FirebaseUser;
import com.google.firebase.auth.GoogleAuthProvider;
import com.google.firebase.auth.TwitterAuthProvider;

public class WelcomeBackIdpPrompt extends AppCompatBase
implements View.OnClickListener, IdpCallback {
public class WelcomeBackIdpPrompt extends AppCompatBase implements IdpCallback {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one was a mess!!! I've been struggling to understand what's going on in #309 for a while now so I decided it was time for a rewrite. If you look at the class without all the crazy diff stuff, it's much clearer what's going on now.

private static final String TAG = "WelcomeBackIdpPrompt";

private static final String TAG = "WelcomeBackIDPPrompt";
private IdpProvider mIdpProvider;
private IdpResponse mPrevIdpResponse;
private AuthCredential mPrevCredential;


@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
String providerId = getProviderIdFromIntent();
mPrevIdpResponse = IdpResponse.fromResultIntent(getIntent());
setContentView(R.layout.welcome_back_idp_prompt_layout);

mIdpProvider = null;
IdpResponse newUserIdpResponse = IdpResponse.fromResultIntent(getIntent());
mPrevCredential = AuthCredentialHelper.getAuthCredential(newUserIdpResponse);

User oldUser = User.getUser(getIntent());

String providerId = oldUser.getProvider();
for (IdpConfig idpConfig : mActivityHelper.getFlowParams().providerInfo) {
if (providerId.equals(idpConfig.getProviderId())) {
switch (providerId) {
case GoogleAuthProvider.PROVIDER_ID:
mIdpProvider = new GoogleProvider(this, idpConfig, getEmailFromIntent());
mIdpProvider = new GoogleProvider(this, idpConfig, oldUser.getEmail());
break;
case FacebookAuthProvider.PROVIDER_ID:
mIdpProvider = new FacebookProvider(
Expand All @@ -89,21 +91,16 @@ protected void onCreate(Bundle savedInstanceState) {
}
}

if (mPrevIdpResponse != null) {
mPrevCredential = AuthCredentialHelper.getAuthCredential(mPrevIdpResponse);
}

if (mIdpProvider == null) {
getIntent().putExtra(
ExtraConstants.EXTRA_ERROR_MESSAGE,
"Firebase login successful." +
" Account linking failed due to provider not enabled by application");
Log.w(TAG, "Firebase login unsuccessful."
+ " Account linking failed due to provider not enabled by application: "
+ providerId);
finish(ResultCodes.CANCELED, IdpResponse.getErrorCodeIntent(ErrorCodes.UNKNOWN_ERROR));
return;
}

((TextView) findViewById(R.id.welcome_back_idp_prompt))
.setText(getIdpPromptString(getEmailFromIntent()));
.setText(getIdpPromptString(oldUser.getEmail()));

mIdpProvider.setAuthenticationCallback(this);
findViewById(R.id.welcome_back_idp_button).setOnClickListener(new OnClickListener() {
Expand All @@ -127,83 +124,71 @@ public void onActivityResult(int requestCode, int resultCode, Intent data) {
}

@Override
public void onClick(View view) {
next(mPrevIdpResponse);
}

@Override
public void onSuccess(IdpResponse idpResponse) {
next(idpResponse);
}

@Override
public void onFailure(Bundle extra) {
Toast.makeText(getApplicationContext(), "Error signing in", Toast.LENGTH_LONG).show();
finish(ResultCodes.CANCELED, IdpResponse.getErrorCodeIntent(ErrorCodes.UNKNOWN_ERROR));
}

private String getProviderIdFromIntent() {
return getIntent().getStringExtra(ExtraConstants.EXTRA_PROVIDER);
}

private String getEmailFromIntent() {
return getIntent().getStringExtra(ExtraConstants.EXTRA_EMAIL);
}

private void next(final IdpResponse newIdpResponse) {
if (newIdpResponse == null) {
public void onSuccess(final IdpResponse idpResponse) {
if (idpResponse == null) {
return; // do nothing
}

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahhhhhh!!! It took me so long in #309 to "understand" what was going on only to find out I was wrong: we never even used this click listener! That kills me!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah it took me a little while to see what was going on here too. Good find! This stray listener could have bitten us down the road.

AuthCredential newCredential = AuthCredentialHelper.getAuthCredential(newIdpResponse);
AuthCredential newCredential = AuthCredentialHelper.getAuthCredential(idpResponse);
if (newCredential == null) {
Log.e(TAG, "No credential returned");
finish(ResultCodes.CANCELED, IdpResponse.getErrorCodeIntent(ErrorCodes.UNKNOWN_ERROR));
return;
}

final FirebaseAuth firebaseAuth = mActivityHelper.getFirebaseAuth();
FirebaseUser currentUser = firebaseAuth.getCurrentUser();

FirebaseUser currentUser = mActivityHelper.getCurrentUser();
if (currentUser == null) {
Task<AuthResult> authResultTask = firebaseAuth.signInWithCredential(newCredential);
authResultTask.addOnCompleteListener(new OnCompleteListener<AuthResult>() {
@Override
public void onComplete(@NonNull Task<AuthResult> task) {
if (task.isSuccessful() && mPrevCredential != null) {
FirebaseUser firebaseUser = task.getResult().getUser();
firebaseUser.linkWithCredential(mPrevCredential);
firebaseAuth.signOut();
firebaseAuth
.signInWithCredential(mPrevCredential)
.addOnFailureListener(new TaskFailureLogger(
TAG, "Error signing in with previous credential"))
.addOnCompleteListener(new FinishListener(newIdpResponse));
} else {
finish(ResultCodes.OK, IdpResponse.getIntent(newIdpResponse));
}
}
}).addOnFailureListener(
new TaskFailureLogger(TAG, "Error signing in with new credential"));
mActivityHelper.getFirebaseAuth()
.signInWithCredential(newCredential)
.addOnSuccessListener(new OnSuccessListener<AuthResult>() {
@Override
public void onSuccess(AuthResult result) {
if (mPrevCredential != null) {
result.getUser()
.linkWithCredential(mPrevCredential)
.addOnFailureListener(new TaskFailureLogger(
TAG, "Error signing in with previous credential"))
.addOnCompleteListener(new FinishListener(idpResponse));
} else {
finish(ResultCodes.OK, IdpResponse.getIntent(idpResponse));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This finish() was previously for failure of the Task but not it occurs when the task succeeds but mPrevCredential == null. Is this a behavior change for developers?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this was the original intention: if we have a previous credential, link it, else, finish ok. Previously, if we either didn't have a previous credential or the task failed, we would finish ok.

Because we would finish ok if we didn't have a previous credential, this isn't a behavior change.

}
}
})
.addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(@NonNull Exception e) {
finishWithError();
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On failure, we fail.

}
})
.addOnFailureListener(
new TaskFailureLogger(TAG, "Error signing in with new credential"));
} else {
Task<AuthResult> authResultTask = currentUser.linkWithCredential(newCredential);
authResultTask
currentUser
.linkWithCredential(newCredential)
.addOnFailureListener(
new TaskFailureLogger(TAG, "Error linking with credential"))
.addOnCompleteListener(new FinishListener(newIdpResponse));
.addOnCompleteListener(new FinishListener(idpResponse));
}
}

@Override
public void onFailure(Bundle extra) {
finishWithError();
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😢

}

private void finishWithError() {
Toast.makeText(this, R.string.general_error, Toast.LENGTH_LONG).show();
finish(ResultCodes.CANCELED, IdpResponse.getErrorCodeIntent(ErrorCodes.UNKNOWN_ERROR));
}

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BAD! (I think)
Here's the if: if (task.isSuccessful() && mPrevCredential != null)
What if the task fails!? We'll just finish with result ok? I'm pretty sure this was a mistake.

public static Intent createIntent(
Context context,
FlowParameters flowParams,
String providerId,
IdpResponse idpResponse,
String email) {
User existingUser,
IdpResponse newUserResponse) {
return BaseHelper.createBaseIntent(context, WelcomeBackIdpPrompt.class, flowParams)
.putExtra(ExtraConstants.EXTRA_PROVIDER, providerId)
.putExtra(ExtraConstants.EXTRA_IDP_RESPONSE, idpResponse)
.putExtra(ExtraConstants.EXTRA_EMAIL, email);
.putExtra(ExtraConstants.EXTRA_USER, existingUser)
.putExtra(ExtraConstants.EXTRA_IDP_RESPONSE, newUserResponse);
}

private class FinishListener implements OnCompleteListener<AuthResult> {
Expand Down
Loading