-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Refactor SaveCredentialsActivity #343
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
Refactor SaveCredentialsActivity #343
Conversation
won't compile
@@ -265,13 +265,13 @@ private AuthUI(FirebaseApp app) { | |||
* ({@code !result.isSuccess()}). | |||
*/ | |||
public Task<Void> signOut(@NonNull Activity activity) { | |||
// Get helper for Google Sign In and Credentials API | |||
// Get helper for Google Sign In and SmartLock API |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ignore this weirdness: intellij make some mistakes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This PR isn't ready at all yet, but I have a few basic questions. Since the calling activities are expecting an activity result code that no longer exists, how should I handle this? startResolutionForResult
needs an activity and finishes in an onActivityResult
where stuff needs to be sorted out. To not copy code, I've put this logic in SmartLock
, but this makes things a pain (see below).
@@ -137,10 +137,8 @@ public void onSuccess(AuthResult authResult) { | |||
new OnSuccessListener<AuthResult>() { | |||
@Override | |||
public void onSuccess(AuthResult authResult) { | |||
mActivityHelper.dismissDialog(); | |||
SmartlockUtil.saveCredentialOrFinish( | |||
mSmartLock = SmartLock.saveCredentialOrFinish( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any thoughts on how to do this better?
if (requestCode == RC_CREDENTIAL_SAVE) { | ||
finish(RESULT_OK, new Intent()); | ||
if (mSmartLock != null) { | ||
mSmartLock.onActivityResult(requestCode, resultCode); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I really don't like this, but I can't think of another way to do this without copying code between Activities. Thoughts?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's nothing wrong with this pattern (having a non-Activity object that has an onActivityResult method) but I'd suggest having that method return a boolean
that signifies whether or not mSmartLock
was able to handle the result or if it should be delegated back to the Activity.
.getStringExtra(ExtraConstants.EXTRA_PROFILE_PICTURE_URI); | ||
|
||
mCredentialsApiClient = new GoogleApiClient.Builder(this) | ||
public SmartLock(AppCompatBase activity, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If this class is going to be tied to an Activity, then one way to structure it would be a headless Fragment
. You'd call SmartLock.getInstance(activity, ...)
which would attach a Fragment
to the Activity. Then that Fragment
can actually launch activities for result and receive the results directly, rather than the constant if (mSmartLock != null) { smartLock.onActivityResult(...) }
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great idea! I didn't know you could do that. Things are way nicer now.
* @param password (optional) password for email credential. | ||
* @param provider (optional) provider string for provider credential. | ||
*/ | ||
public static SmartLock getInstance(AppCompatBase activity, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's more conventional to have this getInstance()
method actually do the Fragment transaction and add itself to the Activity (first checking if there is already an instance of SmartLock
attached to the Activity).
You can attach a SmartLock fragment to each Activity that needs it in onCreate
. Then SmartLock
could expose methods to kick off the various flows, rather than starting on Fragment attach.
So getInstance()
would look more like this:
SmartLock result;
FragmentManager fm = activity.getSupportFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
Fragment fragment = fm.findFragmentByTag(SMARTLOCK_TAG);
if (fragment == null || (!fragment instanceof SmartLock)) {
shouldAdd = true;
} else {
result = (SmartLock) fragment;
}
if (shouldAdd) {
result = new SmartLock(...);
ft.add(result, SMARTLOCK_TAG).disallowAddToBackStack().commit();
}
return result;
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ohhhhh. I think I'm getting it now. You can pass in things like Activities to a fragment because they are a more conventional Java object: you can just do method injection.
@Nullable String password, | ||
@Nullable String provider) { | ||
SmartLock smartLock = new SmartLock(); | ||
smartLock.mActivity = activity; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For parameters of the Fragment like this it's better to use setArguments()
to attach them to the Fragment in a way that the Android system can keep track of.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How would I pass in things like an activity though?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can just do getActivity() in the fragment, and if you need methods on AppCompatBase
you can just do:
if (!(getActivity() instanceof AppCompatBase)) {
throw new IllegalStateException("SmartLock can only be attached to AppCompatBase!");
} else {
// Cast and call method
}
Realistically you can just do the check --> throw
thing once in the Fragment
onAttach
.
public void onConnected(@Nullable Bundle bundle) { | ||
if (mEmail == null) { | ||
Log.e(TAG, "Unable to save null credential!"); | ||
finish(RESULT_FIRST_USER, getIntent()); | ||
mActivity.finish(RESULT_OK, mActivity.getIntent()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a reason we changed this to RESULT_OK here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes. When this was an activity we finished with result RESULT_FIRST_USER
, but the calling activity didn't actually care what the result was (all the calling activities finished with RESULT_OK
). Since the fragment it closing the activity for it, we just finish everything with RESULT_OK
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
SGTM, thanks for clarifying 👍
Looks like you're still working on the tests, but this is starting to look very close to done! Thanks for all the work so far. @amandle could you take a look through this as well? Removes the headless smartlock activity in favor of a Fragment-based system. Should be a no-op in terms of logic and functionality but will make the back stack nicer and the whole thing a little more modular. |
I think the Also, I'll create separate PRs for the other zombie activities in #254. |
@@ -139,10 +132,8 @@ public void onSuccess(AuthResult authResult) { | |||
new OnSuccessListener<AuthResult>() { | |||
@Override | |||
public void onSuccess(AuthResult authResult) { | |||
mActivityHelper.dismissDialog(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just noticed this, why are we removing the calls to dismissDialog()?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I decided to get rid of them because when you close the activity, they get closed with it. Leaving them open means the loading dialog stays open during the credential save.
LGTM! I'll let @amandle take a look though in case I missed anything. This came out really clean in the end. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a very nice clean up. A few comments about the tests and test intent.
ShadowActivity.IntentForResult nextIntent = | ||
Shadows.shadowOf(authMethodPickerActivity).getNextStartedActivityForResult(); | ||
verifySaveCredentialIntent(nextIntent, FacebookAuthProvider.PROVIDER_ID); | ||
Intent smartLockIntent = SmartLock.getInstance(authMethodPickerActivity).getIntentForTest(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These tests verify the intent when the intent is only created for the purposes of the test. It would be nice to update the tests to reflect the changes in SmartLock, ideally checking that the SmartLock API is called with the correct credential.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll try that again, but I keep getting a null pointer exception at line 221 in smartlock.java.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I figured that part out
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need the tests? All they were doing before is checking to see if the intent was passed in correctly... Right now I'm stuck on how to wait for SmartLock
to save the credentials.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, the test before was testing the boundary between this activity and the smartlock activity, so a similar test doesn't make sense here. Perhaps it would make the most sense to test the smartlock capabilities in CredentialSignInHandlerTest
where you can provide a mock smartlock instance and verify that the correct method is called with the right parameters.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Noooooooooooo!!!!!! I just spent two hours figuring this out!!
I'm almost done and then I'll update the status of this PR.
.build(); | ||
} | ||
|
||
public Intent getIntentForTest() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is returning an intent when the SmartLock doesn't actually use intents. This makes the tests slightly misleading IMO. See the comments on the tests below.
if (mProfilePictureUri != null) { | ||
builder.setProfilePictureUri(Uri.parse(mProfilePictureUri)); | ||
} | ||
mActivityHelper.getCredentialsApi() | ||
|
||
Auth.CredentialsApi |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This change makes it difficult to use a mock CredentialsApi in the tests.
mActivityHelper.getCredentialsApi() | ||
Credential credential = new Credential.Builder(mEmail).setPassword(mPassword) | ||
.build(); | ||
Auth.CredentialsApi |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same comment here about the mocking
verifySaveCredentialIntent(nextIntent, GoogleAuthProvider.PROVIDER_ID); | ||
assertEquals( | ||
Shadows.shadowOf(authMethodPickerActivity).getResultCode(), | ||
Activity.RESULT_OK); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I could get robolectric to wait for SmartLock
to finish, Shadows.shadowOf(authMethodPickerActivity).getResultCode()
would change from Activity.RESULT_CANCELED
to Activity.RESULT_OK
which would confirm that the something happened if not the credentials being saved. Any ideas?
Do you guys want to merge the refactor of |
Nevermind, I'm going to submit a separate PR because the changes are massive. It's mind blowing how fast and beautiful things are now!!! Pretty exciting! The one downside is that there are breaking changes to AuthUI, but I think it's definitely worth it. |
This PR and #357 should be released together because I'm checking how configuration changes affect the smart lock stuff. |
…or-zombie-activities # Conflicts: # auth/src/main/java/com/firebase/ui/auth/util/SmartlockUtil.java # auth/src/main/res/values/styles.xml # auth/src/test/java/com/firebase/ui/auth/ui/idp/AuthMethodPickerActivityTest.java
…or-zombie-activities # Conflicts: # auth/src/main/AndroidManifest.xml # auth/src/main/java/com/firebase/ui/auth/ui/account_link/WelcomeBackPasswordPrompt.java # auth/src/main/java/com/firebase/ui/auth/ui/idp/AuthMethodPickerActivity.java # auth/src/main/java/com/firebase/ui/auth/ui/idp/CredentialSignInHandler.java # auth/src/main/java/com/firebase/ui/auth/ui/idp/IdpSignInContainerActivity.java # auth/src/main/java/com/firebase/ui/auth/util/SmartLock.java # auth/src/main/java/com/firebase/ui/auth/util/SmartlockUtil.java # auth/src/test/java/com/firebase/ui/auth/ui/idp/AuthMethodPickerActivityTest.java # auth/src/test/java/com/firebase/ui/auth/ui/idp/CredentialSignInHandlerTest.java
@amandle I finished up the PR, what do you think of the tests? BTW, to make them work, I had to remove the |
…or-zombie-activities # Conflicts: # auth/src/test/java/com/firebase/ui/auth/ui/email/RegisterEmailActivityTest.java # auth/src/test/java/com/firebase/ui/auth/ui/idp/AuthMethodPickerActivityTest.java
@SUPERCILEX Tests look good to me, thanks for doing that! |
@amandle Awesome! You're welcome! |
@samstern, any objections to me merging this now? |
Placeholder PR. This refactoring is huge and in no way ready yet (as in it won't even compile), but I thought I'd inform you guys that I'm working on this and ask a few questions.