Skip to content

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

Merged
merged 32 commits into from
Oct 21, 2016
Merged

Refactor SaveCredentialsActivity #343

merged 32 commits into from
Oct 21, 2016

Conversation

SUPERCILEX
Copy link
Collaborator

  • If this has been discussed in an issue, make sure to mention the issue number here. If not, go file an issue about this to make sure this is a desirable change.

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.

@@ -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
Copy link
Collaborator Author

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.

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.

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(
Copy link
Collaborator Author

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);
Copy link
Collaborator Author

@SUPERCILEX SUPERCILEX Oct 7, 2016

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?

Copy link
Contributor

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,
Copy link
Contributor

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(...) }.

Copy link
Collaborator Author

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,
Copy link
Contributor

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;
    }

Copy link
Collaborator Author

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;
Copy link
Contributor

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.

Copy link
Collaborator Author

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?

Copy link
Contributor

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());
Copy link
Contributor

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?

Copy link
Collaborator Author

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.

Copy link
Contributor

Choose a reason for hiding this comment

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

SGTM, thanks for clarifying 👍

@samtstern
Copy link
Contributor

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.

@SUPERCILEX
Copy link
Collaborator Author

I think the SaveCredentialsActivity refactor is good now.

Also, I'll create separate PRs for the other zombie activities in #254.

@SUPERCILEX SUPERCILEX changed the title Refactor zombie activities Refactor SaveCredentialsActivity Oct 14, 2016
@SUPERCILEX SUPERCILEX mentioned this pull request Oct 14, 2016
7 tasks
@@ -139,10 +132,8 @@ public void onSuccess(AuthResult authResult) {
new OnSuccessListener<AuthResult>() {
@Override
public void onSuccess(AuthResult authResult) {
mActivityHelper.dismissDialog();
Copy link
Contributor

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()?

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 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.

@samtstern
Copy link
Contributor

LGTM! I'll let @amandle take a look though in case I missed anything. This came out really clean in the end.

Copy link
Contributor

@amandle amandle left a 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();
Copy link
Contributor

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.

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'll try that again, but I keep getting a null pointer exception at line 221 in smartlock.java.

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 figured that part out

Copy link
Collaborator Author

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.

Copy link
Contributor

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.

Copy link
Collaborator Author

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() {
Copy link
Contributor

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
Copy link
Contributor

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
Copy link
Contributor

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);
Copy link
Collaborator Author

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?

@SUPERCILEX
Copy link
Collaborator Author

Do you guys want to merge the refactor of SaveCredentialsActivity and ChooseAccountActivity separately? For the ChooseAccountActivity refactor, I pulled changes from this PR because I'm completely reworking the Smart Lock stuff.

@SUPERCILEX
Copy link
Collaborator Author

SUPERCILEX commented Oct 16, 2016

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.

@SUPERCILEX SUPERCILEX mentioned this pull request Oct 16, 2016
10 tasks
@SUPERCILEX
Copy link
Collaborator Author

This PR and #357 should be released together because I'm checking how configuration changes affect the smart lock stuff.

@SUPERCILEX SUPERCILEX changed the base branch from master to version-1.0.0-dev October 17, 2016 22:40
…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
@SUPERCILEX
Copy link
Collaborator Author

@amandle I finished up the PR, what do you think of the tests? BTW, to make them work, I had to remove the mSmartLock initialization from onCreate to where saveCredentialsOrFinish is called because I'm using a sneeky version of injection to make the tests work. Feel free to ask me any questions!

…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
@amandle
Copy link
Contributor

amandle commented Oct 19, 2016

@SUPERCILEX Tests look good to me, thanks for doing that!

@SUPERCILEX
Copy link
Collaborator Author

SUPERCILEX commented Oct 19, 2016

@amandle Awesome! You're welcome!

@amandle
Copy link
Contributor

amandle commented Oct 21, 2016

@samstern, any objections to me merging this now?

@amandle amandle merged commit baab107 into firebase:version-1.0.0-dev Oct 21, 2016
@SUPERCILEX SUPERCILEX deleted the refactor-zombie-activities branch October 21, 2016 22:27
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.

3 participants