Skip to content

Complete rewrite/refactor of email sign in flow #418

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 15 commits into from
Dec 6, 2016
Merged

Complete rewrite/refactor of email sign in flow #418

merged 15 commits into from
Dec 6, 2016

Conversation

SUPERCILEX
Copy link
Collaborator

@SUPERCILEX SUPERCILEX commented Nov 24, 2016

I'm really excited about this one!!! The performance/ui/code is massively improved: we've gone from jumping through 3 activities to just saying on RegisterEmailActivity and everything regarding email flow is so much simpler and easier to understand now! Here are some of the important changes:

RC_WELCOME_BACK_IDP);
}
}
} // TODO: 11/23/2016 what happens if we fail?
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Thoughts? We didn't do anything before, but I think we should display a message or at least do something.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah not sure what the best course of action is here ... since we didn't do anything before let's leave this for another time.

@@ -59,6 +59,7 @@
<string name="progress_dialog_sending">Sending…</string>
<string name="progress_dialog_signing_in">Signing in…</string>
<string name="progress_dialog_signing_up">Signing up…</string>
<string name="progress_dialog_checking_accounts">Checking for existing accounts…</string>
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Does this sound good?

Copy link
Contributor

Choose a reason for hiding this comment

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

SGTM.

@SUPERCILEX
Copy link
Collaborator Author

I've been off and on trying to figure out if a memory leak was caused by my changes in this PR and I finally figured it out! Turns out this PR wasn't the cause, but RegisterEmailActivity helped me figure it out because it creates an instance of SaveSmartLock.

Full explanation:

I've stubbed my toe several times now with setRetainInstance(true) and I think I've finally eradicated all memory leaks related to the smart lock stuff. (Yay! 😄) If you look at the commit fixing the smart lock leak, you'll notice that I was passing in Fragment#getContext() to BaseHelper's constructor which stores the context in mContext. (BaseHelper's subclass, FragmentHelper is initialized in Fragment#onCreate which only occurs once because of setRetainInstance). In this case, mContext is the activity associated with SaveSmartLock. This would work fine without setRetainInstance(true), however, because the fragment lives longer than its activity, we leak SaveSmartLock's activity aka mContext. A simple fix is to use Fragment#getContext()#getApplicationContext(). Unfortunately, ProgressDialogs don't work with getApplicationContext() so I'm using fragment.getContext() only when needed (to show the dialog).

TLDR

Keep an eye out for getContext() leaks in setRetainInstance(true) fragments!

Button createButton = (Button) findViewById(R.id.button_create);
createButton.setOnClickListener(this);

showEmailAutoCompleteHint();
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Me previously:

Why are we doing this only if smart lock is enabled? I think users should get autocomplete whether or not devs have an aversion to smart lock.

I went through with that idea so unless anyone has any objections, I really think autocomplete should be available whether or not smart lock is enabled.

Copy link
Contributor

Choose a reason for hiding this comment

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

Technically the email hint is provided by SmartLock, so this would make the API somewhat dishonest. Although I agree with you that enabling the hint for everyone may be a superior UX, I want to respect the intent of the developer when they say "disable smartlock".

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Sounds fair, but still it makes me sad inside. 😄

@SUPERCILEX SUPERCILEX mentioned this pull request Dec 1, 2016
Copy link
Contributor

@samtstern samtstern left a comment

Choose a reason for hiding this comment

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

Mostly LGTM! Just double-checking that I understand the full scope here: rather than have SignInNoPasswordActivity which takes an email alone and checks for existing accounts, we are moving all of that into RegisterEmailActivity and doing the check right there behind a progress dialog.

Any other major changes?

Button createButton = (Button) findViewById(R.id.button_create);
createButton.setOnClickListener(this);

showEmailAutoCompleteHint();
Copy link
Contributor

Choose a reason for hiding this comment

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

Technically the email hint is provided by SmartLock, so this would make the API somewhat dishonest. Although I agree with you that enabling the hint for everyone may be a superior UX, I want to respect the intent of the developer when they say "disable smartlock".


@Override
public void onFocusChange(View view, boolean hasFocus) {
if (hasFocus) return;
Copy link
Contributor

Choose a reason for hiding this comment

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

Just a general style comment (first noticed it here) but let's add some more comments in meaty methods like this. For instance in this method the first line could use a comment like // Only consider fields losing focus.

if (task.isSuccessful()) {
List<String> providers = task.getResult().getProviders();
if (providers != null && !providers.isEmpty()) {
// Account does exist
Copy link
Contributor

Choose a reason for hiding this comment

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

Recommended to expand comments to something like this:

// There is an account tied to this email.  If only the email provider is associated with the account, direct the user
// to the email sign in flow.  Otherwise, direct the user to sign in with the IDP they previously selected.

new OnCompleteListener<ProviderQueryResult>() {
@Override
public void onComplete(@NonNull Task<ProviderQueryResult> task) {
if (task.isSuccessful()) {
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: use separate success and failure listeners rather than checking task.isSuccessful()

RC_WELCOME_BACK_IDP);
}
}
} // TODO: 11/23/2016 what happens if we fail?
Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah not sure what the best course of action is here ... since we didn't do anything before let's leave this for another time.

password);
}
});
}
})
.addOnFailureListener(new OnFailureListener() {
.addOnFailureListener(this, new OnFailureListener() {
Copy link
Contributor

Choose a reason for hiding this comment

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

Any reason we're only tying the failure listener to the Activity and not the success listener?

Copy link
Collaborator Author

@SUPERCILEX SUPERCILEX Dec 2, 2016

Choose a reason for hiding this comment

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

Here was my logic:

If the request succeeds, we to continue whether or not the original activity is still there because mSaveSmartLock is already initialized and not tied to the activity lifecycles. That way, the activity will finish and the sign in will proceed.

If it fails, the only thing we are doing in the failure listener is updating views. I'm not sure how that works, but I would think something bad will happen if we try to update a view from a previous instance of an activity. Am I wrong?

Update:

I found RecoverEmailActivity doing the same thing, so I think my logic is correct.

Copy link
Contributor

Choose a reason for hiding this comment

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

SGTM.

@@ -59,6 +59,7 @@
<string name="progress_dialog_sending">Sending…</string>
<string name="progress_dialog_signing_in">Signing in…</string>
<string name="progress_dialog_signing_up">Signing up…</string>
<string name="progress_dialog_checking_accounts">Checking for existing accounts…</string>
Copy link
Contributor

Choose a reason for hiding this comment

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

SGTM.

Copy link
Collaborator Author

@SUPERCILEX SUPERCILEX left a comment

Choose a reason for hiding this comment

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

Yup, this is mostly just unifying the email sign in experience with some small improvements peppered here and there.

@@ -50,7 +50,7 @@
public class AuthUiActivity extends AppCompatActivity {
private static final String UNCHANGED_CONFIG_VALUE = "CHANGE-ME";
private static final String GOOGLE_TOS_URL = "https://www.google.com/policies/terms/";
private static final String FIREBASE_TOS_URL = "https://www.firebase.com/terms/terms-of-service.html";
private static final String FIREBASE_TOS_URL = "https://firebase.google.com/terms/";
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Just noticed this was using the old Firebase.

Copy link
Contributor

Choose a reason for hiding this comment

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

Wow, good catch :-)

.build()
.launchUrl(
RegisterEmailActivity.this,
Uri.parse(mActivityHelper.getFlowParams().termsOfServiceUrl));
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Since Facebook forces us to use CCT, I don't see why we shouldn't modernize.

Copy link
Contributor

Choose a reason for hiding this comment

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

Agreed, looks great in your screencast!

startActivity(intent);
// Getting default color
TypedValue typedValue = new TypedValue();
getTheme().resolveAttribute(R.attr.colorPrimary, typedValue, true);
Copy link
Collaborator Author

@SUPERCILEX SUPERCILEX Dec 2, 2016

Choose a reason for hiding this comment

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

This works, but what if they don't have a colorPrimary. (Actually, is it even possible to not have a colorPrimary? I'm pretty sure AppCompatActivity uses that for all default theming)

Copy link
Contributor

Choose a reason for hiding this comment

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

I think this should be fine ... and if people see a crash I think saying "you must define colorPrimary" is a reasonable response so not too worried.

@@ -168,6 +334,7 @@ public void onFailure(@NonNull Exception e) {
} else if (e instanceof FirebaseAuthUserCollisionException) {
// Collision with existing user email
emailInput.setError(getString(R.string.error_user_collision));
checkAccountExists(mEmailEditText.getText().toString());
Copy link
Collaborator Author

@SUPERCILEX SUPERCILEX Dec 2, 2016

Choose a reason for hiding this comment

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

Come to think of it, this could be our error handling if fetchProvidersForEmail fails: the user can't sign up if a user already exists and we will retry loading that user.

Copy link
Contributor

@samtstern samtstern left a comment

Choose a reason for hiding this comment

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

LGTM! I think I will create a 1.1.0 branch for your three outstanding PRs.

@@ -50,7 +50,7 @@
public class AuthUiActivity extends AppCompatActivity {
private static final String UNCHANGED_CONFIG_VALUE = "CHANGE-ME";
private static final String GOOGLE_TOS_URL = "https://www.google.com/policies/terms/";
private static final String FIREBASE_TOS_URL = "https://www.firebase.com/terms/terms-of-service.html";
private static final String FIREBASE_TOS_URL = "https://firebase.google.com/terms/";
Copy link
Contributor

Choose a reason for hiding this comment

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

Wow, good catch :-)

startActivity(intent);
// Getting default color
TypedValue typedValue = new TypedValue();
getTheme().resolveAttribute(R.attr.colorPrimary, typedValue, true);
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this should be fine ... and if people see a crash I think saying "you must define colorPrimary" is a reasonable response so not too worried.

.build()
.launchUrl(
RegisterEmailActivity.this,
Uri.parse(mActivityHelper.getFlowParams().termsOfServiceUrl));
Copy link
Contributor

Choose a reason for hiding this comment

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

Agreed, looks great in your screencast!

password);
}
});
}
})
.addOnFailureListener(new OnFailureListener() {
.addOnFailureListener(this, new OnFailureListener() {
Copy link
Contributor

Choose a reason for hiding this comment

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

SGTM.

@samtstern
Copy link
Contributor

I just tried to test all of your PRs together and there are some small lingering conflicts. So I made a branch called version-1.1.0-dev from master.

Let's move PRs #418, #420, and #426 to target that branch and then merge them into there one by one (fixing conflicts as we go). Then we can do some poke-around QA, make sure all dependencies are up to date, and cut a version 1.1.0 release. Sound good?

@SUPERCILEX SUPERCILEX changed the base branch from master to version-1.1.0-dev December 6, 2016 17:23
@SUPERCILEX
Copy link
Collaborator Author

@samtstern Sounds good, I changed the base for all my PRs and pulled from master.

@samtstern samtstern merged commit 5bb61d1 into firebase:version-1.1.0-dev Dec 6, 2016
@SUPERCILEX SUPERCILEX deleted the register-email-refactor branch December 6, 2016 17:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants