Skip to content

Prep for final refactors #1188

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 8 commits into from
Mar 16, 2018
Merged

Prep for final refactors #1188

merged 8 commits into from
Mar 16, 2018

Conversation

SUPERCILEX
Copy link
Collaborator

@samtstern Alrighty, it's time to plow through that refactor!

This PR makes a few core changes:

  • Lots of view models will need a way to start intents so that's now natively supported through custom exceptions. This way, we don't have to subscribe to 3 different LiveDatas every time. I think that makes the most sense, what are your thoughts?
  • Thanks to the above, the auth view model now exposes a single LiveData for all subclasses to interact with.
  • Other random cleanup and changes I had in fork

Signed-off-by: Alex Saveau <[email protected]>
@SUPERCILEX SUPERCILEX requested a review from samtstern as a code owner March 15, 2018 23:56
Signed-off-by: Alex Saveau <[email protected]>
Signed-off-by: Alex Saveau <[email protected]>
private String mMessage;

static {
boolean log;
Copy link
Contributor

Choose a reason for hiding this comment

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

Huh this is a neat but also kind of nasty trick. Did you see this somewhere else? Reflection in a static block seems like it could really slow down some startup times.

Maybe we should just add a FirebaseUI.setLogLevel method or something?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yeah, I just noticed that some exceptions had sensitive info like emails and whatnot that we shouldn't be logging in production. I'll put this on the TODO list for later to figure out. 👍

throw new AssertionError("No instance for you!");
}

public static boolean handleError(@NonNull HelperActivityBase activity, @NonNull Exception e) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I am totally fine with this, but do you think it makes sense to just have all of our Resources return a common exception class with a code and a cause and then we could just switch on code instead of using instanceof?

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'd like to, but we already have code that sets the failure to task.getException(). I'm not sure if we'd want to wrap everything in a FirebaseUiException, but that would definitely make this stuff cleaner. Ideas?

private CredentialsClient mCredentialsClient;
private FirebaseAuth mAuth;
private PhoneAuthProvider mPhoneAuth;

private SingleLiveEvent<PendingResolution> mPendingResolution = new SingleLiveEvent<>();
private MutableLiveData<Resource<T>> mOperation = new MutableLiveData<>();
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you quickly explain how you're mitigating the risk that comes with removing the SingleLiveEvent for the errors?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

You got it below, it's the setUsed thing. I'm totally open to going back to our SingleLiveEvent, but that felt like the greater of the two evils. Thoughts?

@samtstern
Copy link
Contributor

This looks really great!

I wonder: can/should we enforce that Resource<> goes through states in a sane order? I am thinking that SUCCESS and FAILURE should be terminal and any attempt to go from one of those to the other or back to LOADING is some sort of problem.

But maybe that's not the case? Either way it would be good to know/define.

if (!(e instanceof StatefulException) || ((StatefulException) e).isUsed()) {
return false;
}
((StatefulException) e).setUsed(true);
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe the setUsed could be automatically called inside getPendingIntent() or getIntent() so that way you can really only get them once?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yeah, I actually did that to start but it felt kinda weird. If you had that idea too, then it can't be that weird. 😆 SGTM!

@SUPERCILEX
Copy link
Collaborator Author

@samtstern For the resource stuff, FAILURE and LOADING states can be called any number of times since the client can try to get around the failures or let the user try again, etc. However, it does make sense to me that we would block anything after SUCCESS, do you want to go with that?

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.

Very few remaining comments! Looking good.


public class StatefulException extends FirebaseUiException {
public StatefulException() {
super(ErrorCodes.UNKNOWN_ERROR);
Copy link
Contributor

Choose a reason for hiding this comment

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

So do we need this class anymore if it doesn't actually hold any state?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Nope, good catch!

if (!mHandler.onActivityResult(requestCode, resultCode, data)) {
super.onActivityResult(requestCode, resultCode, data);
}
super.onActivityResult(requestCode, resultCode, data);
Copy link
Contributor

Choose a reason for hiding this comment

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

Are we sure there's no issue unconditionally calling both?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yeah, as long as we have different request codes (we do: 100 and 101). I felt like we'd get nipped in the behind later if onActivityResult was called non-deterministically. (Especially since fragments rely on that call.)

}

public static boolean handleError(@NonNull HelperActivityBase activity, @NonNull Exception e) {
if (!(e instanceof StatefulException)) { return false; }
Copy link
Contributor

Choose a reason for hiding this comment

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

Not sure that StatefulException is really a useful / meaningful base class (as stated in a comment above). Why not just return true inside each of the if blocks and then return false at the end otherwise?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Totally agree, been there, done that. 👍😆

ArgumentCaptor<PendingResolution> resolveCaptor = ArgumentCaptor.forClass(PendingResolution.class);
verify(mResolutionObserver).onChanged(resolveCaptor.capture());
ArgumentCaptor<Resource<Void>> resolveCaptor = ArgumentCaptor.forClass(Resource.class);
verify(mResultObserver, times(2)).onChanged(resolveCaptor.capture());
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we change the test to actually verify the onChanged events separately? Kind of confusing to verify that the second time is a PendingIntentRequiredException but we don't assert/care what the first event is.

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 do below but I got an exception without the times(2). Is there a prettier way to do this with mockito?

Copy link
Contributor

Choose a reason for hiding this comment

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

Just for learning purposes (I am fine with your code) you can tell Mockito you want to do events in order.

Here is some code from another project of mine which is basically the same (over there we call Resource a Loadable):

    // Verify order of events
    InOrder inOrder = Mockito.inOrder(loadingObserver);

    // Start by loading
    liveData.postValue(Loadable.forLoading());
    inOrder.verify(loadingObserver).onChanged(true);

    // Move to success
    liveData.postValue(Loadable.forSuccess("Hello"));
    inOrder.verify(loadingObserver).onChanged(false);

    // Make sure that double-success does not make the observer get called again
    liveData.postValue(Loadable.forSuccess("This should have never been posted"));
    inOrder.verify(loadingObserver, never()).onChanged(anyBoolean());

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Whoa, that looks super cool!

Just for learning purposes

Says you, I definitely using that! 😆 Thanks for tip 👍

Copy link
Contributor

Choose a reason for hiding this comment

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

Want me to merge-as is or do you want to go and use InOrder? Up to you.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Sorry for the delay, my laptop died in the middle of running tests. 😭😆 Anyway, it's much prettier now!

Signed-off-by: Alex Saveau <[email protected]>
Signed-off-by: Alex Saveau <[email protected]>
@samtstern
Copy link
Contributor

image
😆

@samtstern samtstern merged commit 39c85f0 into firebase:version-3.3.0-dev Mar 16, 2018
@SUPERCILEX SUPERCILEX deleted the prep branch March 16, 2018 17:48
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