-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Request Twitter email #452
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
Conversation
Signed-off-by: Alex Saveau <[email protected]>
private final String mProviderId; | ||
@Nullable |
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 don't think this should be nullable: we always know the email address and provider id and since having it be nullable caused a crash, I made it @nonnull.
|
||
@Override | ||
public void failure(TwitterException exception) { | ||
TwitterProvider.this.failure(exception); |
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 problem:
Even if the user grants access to her email address, it is not guaranteed you will get an email address. For example, if someone signed up for Twitter with a phone number instead of an email address, the email field may be empty. When this happens, the failure method will be called because there is no email address available.
It will be confusing to the user if they press authorize and the sign in just fails unexpectedly because they didn't give Twitter an email. The only place we need to display a message is in AuthMethodPickerActivity
, but what should we say and how? A snackbar? An alert dialog?
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 problem ... I need to look into this and ask someone who knows more than I do :-)
@samtstern The twitter tests are failing because we are waiting for a callback that will never come (the ok from the user in twitter's request email activity). Because of that, I don't think it's possible to test the TwitterProvider, but it also doesn't look like we really need the tests: we manually call @Override
public void onSuccess(IdpResponse idpResponse) {
assertTrue(mAssertSuccess);
mCountDownLatch.countDown();
} Is it ok for me to remove the test? Or how could I get it to work with Here's the test in question: https://github.com/firebase/FirebaseUI-Android/blob/master/auth/src/test/java/com/firebase/ui/auth/ui/provider/TwitterProviderTest.java#L68 |
Signed-off-by: Alex Saveau <[email protected]>
Signed-off-by: Alex Saveau <[email protected]>
Signed-off-by: Alex Saveau <[email protected]>
Signed-off-by: Alex Saveau <[email protected]>
Signed-off-by: Alex Saveau <[email protected]>
@@ -60,8 +74,36 @@ public void startLogin(Activity activity) { | |||
} | |||
|
|||
@Override | |||
public void success(Result<TwitterSession> result) { | |||
mCallbackObject.onSuccess(createIdpResponse(result.data)); | |||
public void success(final Result<TwitterSession> sessionResult) { |
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.
Twitter caused a leak by storing our callback in a result receiver:
In com.firebase.uidemo:1.0:1.
* com.firebase.ui.auth.ui.idp.AuthMethodPickerActivity has leaked:
* GC ROOT android.os.ResultReceiver$MyResultReceiver.this$0
* references com.twitter.sdk.android.core.identity.ShareEmailResultReceiver.callback
* references com.firebase.ui.auth.provider.TwitterProvider$EmailCallback.this$0
* references com.firebase.ui.auth.provider.TwitterProvider.mCallbackObject
* leaks com.firebase.ui.auth.ui.idp.AuthMethodPickerActivity instance
* Retaining: 11 KB.
* Reference Key: 1437fccd-e7e7-4aa3-9154-b8421f639999
* Device: Google google Pixel XL marlin
* Android Version: 7.1 API: 25 LeakCanary: 1.5 00f37f5
* Durations: watch=5020ms, gc=196ms, heap dump=14858ms, analysis=156016ms
and their RequestEmail
activity was bothering me because it's useless: devs already have access to the email since they requested it in the Twitter console. Sigh to twitter 😄
Anyway, I dug through the source code and ripped Twitter's GET request so that we can handle that stuff ourselves. Now if we didn't get an email, we know whether or not the dev hasn't requested the email permission in the twitter dashboard or if the Twitter user just doesn't have an email.
@samtstern Since we now have control over what's going on, we can decide what to do in the two failure cases.
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.
@SUPERCILEX I appreciate all the investigation but I have a few reservations about this approach:
- We are supposed to be implementing a best-practices UI layer on top of various auth SDKs, but this is clearly not what Twitter recommends as best practice.
- This adds a lot of complexity by introducing Retrofit into our code (I know it was a dependency before, but now we are using it directly)
- The benefit here is real, but it's not huge. I think I'd rather use the Twitter SDK as it stands and just deal with the ambiguity of their errors.
As for the leak, I think we can work around that ourselves by making the callback a static
inner class with a WeakReference
to the 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.
@samtstern Whoa, you just blew my mind! The main reason I didn't want to use requestEmail
was because of the leak, but now that you showed me how to use WeakReference
s... Wow! This is why I love contributing to FirebaseUI so much: I've read a few Medium posts on weak references, but I need people like you to show me where I can apply the concepts. Anyway, thanks for the tip! (I had to go back and rewrite some code in my app to use weak references 😄)
@@ -50,6 +50,9 @@ dependencies { | |||
// library when updating com.android.support libraries: | |||
compile "com.android.support:cardview-v7:$support_library_version" | |||
|
|||
compile 'com.squareup.retrofit2:retrofit:2.1.0' | |||
compile 'com.squareup.retrofit2:converter-gson:2.1.0' |
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.
No worries about these, Twitter's already includes them and I'm just upgrading it for them:
+--- com.twitter.sdk.android:twitter:2.3.0
| +--- com.twitter.sdk.android:tweet-composer:2.3.0
| | +--- com.twitter:twitter-text:1.13.0
| | +--- io.fabric.sdk.android:fabric:1.3.14
| | +--- com.twitter.sdk.android:twitter-core:2.3.0
| | | +--- com.google.code.gson:gson:2.6.1 -> 2.7
| | | +--- com.squareup.retrofit2:converter-gson:2.0.2 -> 2.1.0 <<<<<<<<<<<<
| | | | +--- com.squareup.retrofit2:retrofit:2.1.0
| | | | | \--- com.squareup.okhttp3:okhttp:3.3.0
| | | | | \--- com.squareup.okio:okio:1.8.0
| | | | \--- com.google.code.gson:gson:2.7
| | | +--- com.squareup.okhttp3:okhttp:3.2.0 -> 3.3.0 (*)
| | | +--- io.fabric.sdk.android:fabric:1.3.14
| | | \--- com.squareup.retrofit2:retrofit:2.0.2 -> 2.1.0 (*)
| | \--- com.squareup.picasso:picasso:2.5.2
| +--- io.fabric.sdk.android:fabric:1.3.14
| +--- com.twitter.sdk.android:twitter-core:2.3.0 (*)
| \--- com.twitter.sdk.android:tweet-ui:2.3.0
| +--- io.fabric.sdk.android:fabric:1.3.14
| +--- com.twitter.sdk.android:twitter-core:2.3.0 (*)
| +--- com.android.support:support-v4:23.1.1 -> 25.0.1 (*)
| \--- com.squareup.picasso:picasso:2.5.2
Signed-off-by: Alex Saveau <[email protected]>
Signed-off-by: Alex Saveau <[email protected]>
Signed-off-by: Alex Saveau <[email protected]>
|
||
@Override | ||
public void success(Result<String> emailResult) { | ||
mCallbackObject.get().onSuccess(createIdpResponse(emailResult.data)); |
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.
We need to check if mCallbackObject.get()
is null here before calling onSuccess
to avoid a NPE.
When it's null
I guess we can just log a warning and do nothing, it means the activity waiting for the callback was dead so there's nowhere to deliver it.
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.
@samtstern so does this mean that on a low memory device onSuccess
won't be called?
Log.e(TAG, "Failure retrieving Twitter email. " + exception.getMessage()); | ||
// If retrieving the email fails, we should still be able to sign in, but Smart Lock | ||
// and account linking won't work. | ||
mCallbackObject.get().onSuccess(createIdpResponse(null)); |
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 thing here.
Signed-off-by: Alex Saveau <[email protected]>
OK I just checked out this PR at the latest commit and tried a number of situations:
So now I am wondering: when does this request email screen ever do anything? What sort of account setup would result in Firebase Auth only getting the email when that screen is presented. |
@samtstern do you mean the welcome back screen still showed up? The account linking/welcome back screen isn't showing up for me when I press deny on Twitter's email screen. If account linking didn't occur, but
This is working according to Twitter's documentation: we have to show the email screen to know if the Twitter user has an email.
That was one of the reasons I chose to use the retrofit hack, we can't. Firebase auth doesn't care what we do because they get the Twitter email some other way. @samtstern Do you want to proceed with this PR or go back to the retrofit hack? Twitter does want us to use their request email activity, but it seems redundant in most cases because Firebase Auth gets the email anyway. |
@SUPERCILEX sorry maybe I am confused but I think my question is why do we need either the retrofit hack or the request email screen? It seems to me that Firebase Auth is already going to get us the email address whenever possible and all we need to do in FirebaseUI is handle the case where twitter email is |
@samtstern Now I'm all confused too! 😄 From what I understand, we need the email in if (!task.isSuccessful() && task.getException() instanceof FirebaseAuthUserCollisionException) {
// At this point, the user isn't signed in right?
mHelper.getFirebaseAuth()
// If the user isn't signed in, this will always fail whether or not the Twitter user has an email because we can't use FirebaseUser#getEmail() yet.
.fetchProvidersForEmail(mIdpResponse.getEmail())
// ...
} @samtstern For account linking, we need the email from |
@SUPERCILEX I think the account linking cases is not relevant here, right? Would our flow ever initiate account linking for an account with no email? |
@samtstern Well no, but this PR was originally created to handle the case where a Twitter user with an email tries to link their account: user creates email account -> user tries to sign into Twitter with the same email they created their email account with -> we need the email before the user is signed in to perform account linking. |
I finally get it! Your last comment turned on the lightbulb for me. I'm going to summarize below just in case I forget again:
But with your change we have: 3a. Use the twitter SDK to request the email and check for collisions, launching the welcome back flow if necessary. Now that I finally understand the edge case here I think the way you are doing it now (launching the twitter email screen) is the correct approach. |
@samtstern do you want to wait for your email refactor to go in before merging this PR or are there a few changes that still need to be addressed before merging? |
Nope this LGTM! |
Awesome! |
#436