-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Account Linking Phase 1 #1185
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
Closed
Closed
Account Linking Phase 1 #1185
Changes from all commits
Commits
Show all changes
14 commits
Select commit
Hold shift + click to select a range
cab8213
Very basic linking
samtstern fcc57f1
Fix tests
samtstern faf6d3c
Basic sample app
samtstern a5ed59c
Standardize error handling
samtstern 5876159
Properly handle existing email users
samtstern 4be81ae
Clean up and simplify
samtstern aa42772
Fix lint
samtstern 0405aa9
Support phone auth with updated credential
samtstern 24781f9
Make tests pass
samtstern 8dc3eec
Lint found a context leak. Thanks lint
samtstern 67d6cef
Add upgrade flow in IdpContainer
samtstern 37a0929
Merge branch 'version-3.3.0-dev' into anonymous-upgrade-phase-1
samtstern 4edadf3
Upgrade task helper
samtstern 6fd0226
undo some changes
samtstern File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
197 changes: 197 additions & 0 deletions
197
app/src/main/java/com/firebase/uidemo/auth/AccountLinkActivity.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,197 @@ | ||
package com.firebase.uidemo.auth; | ||
|
||
import android.content.Intent; | ||
import android.os.Bundle; | ||
import android.support.annotation.NonNull; | ||
import android.support.v7.app.AppCompatActivity; | ||
import android.text.TextUtils; | ||
import android.util.Log; | ||
import android.widget.Button; | ||
import android.widget.TextView; | ||
import android.widget.Toast; | ||
|
||
import com.firebase.ui.auth.AuthUI; | ||
import com.firebase.ui.auth.ErrorCodes; | ||
import com.firebase.ui.auth.IdpResponse; | ||
import com.firebase.uidemo.R; | ||
import com.google.android.gms.tasks.OnCompleteListener; | ||
import com.google.android.gms.tasks.Task; | ||
import com.google.firebase.auth.AuthCredential; | ||
import com.google.firebase.auth.AuthResult; | ||
import com.google.firebase.auth.FirebaseAuth; | ||
import com.google.firebase.auth.FirebaseUser; | ||
|
||
import java.util.Arrays; | ||
import java.util.List; | ||
|
||
import butterknife.BindView; | ||
import butterknife.ButterKnife; | ||
import butterknife.OnClick; | ||
|
||
public class AccountLinkActivity extends AppCompatActivity { | ||
|
||
private static final String TAG = "AccountLink"; | ||
|
||
private static final int RC_SIGN_IN = 123; | ||
|
||
@BindView(R.id.status_text) | ||
TextView mStatus; | ||
|
||
@BindView(R.id.anon_sign_in) | ||
Button mAnonSignInButton; | ||
|
||
@BindView(R.id.begin_flow) | ||
Button mLaunchUIButton; | ||
|
||
@BindView(R.id.resolve_merge) | ||
Button mResolveMergeButton; | ||
|
||
@BindView(R.id.sign_out) | ||
Button mSignOutButton; | ||
|
||
private AuthCredential mPendingCredential; | ||
|
||
@Override | ||
protected void onCreate(Bundle savedInstanceState) { | ||
super.onCreate(savedInstanceState); | ||
setContentView(R.layout.activity_account_link); | ||
ButterKnife.bind(this); | ||
} | ||
|
||
@OnClick(R.id.anon_sign_in) | ||
public void signInAnonymously() { | ||
FirebaseAuth.getInstance().signInAnonymously() | ||
.addOnCompleteListener(this, new OnCompleteListener<AuthResult>() { | ||
@Override | ||
public void onComplete(@NonNull Task<AuthResult> task) { | ||
updateUI(); | ||
|
||
if (task.isSuccessful()) { | ||
setStatus("Signed in anonymously as user " | ||
+ getUserIdentifier(task.getResult().getUser())); | ||
} else { | ||
setStatus("Anonymous sign in failed."); | ||
} | ||
} | ||
}); | ||
} | ||
|
||
@OnClick(R.id.begin_flow) | ||
public void startAuthUI() { | ||
List<AuthUI.IdpConfig> providers = Arrays.asList( | ||
new AuthUI.IdpConfig.EmailBuilder().build(), | ||
new AuthUI.IdpConfig.PhoneBuilder().build(), | ||
new AuthUI.IdpConfig.GoogleBuilder().build()); | ||
|
||
Intent intent = AuthUI.getInstance().createSignInIntentBuilder() | ||
.setAvailableProviders(providers) | ||
.setIsSmartLockEnabled(false) | ||
.setUpgradeAnonymousAccounts(true) | ||
.build(); | ||
|
||
startActivityForResult(intent, RC_SIGN_IN); | ||
} | ||
|
||
@OnClick(R.id.resolve_merge) | ||
public void resolveMerge() { | ||
if (mPendingCredential == null) { | ||
Toast.makeText(this, "Nothing to resolve.", Toast.LENGTH_SHORT).show(); | ||
return; | ||
} | ||
|
||
// TODO: Show how to do good data moving | ||
|
||
FirebaseAuth.getInstance().signInWithCredential(mPendingCredential) | ||
.addOnCompleteListener(this, new OnCompleteListener<AuthResult>() { | ||
@Override | ||
public void onComplete(@NonNull Task<AuthResult> task) { | ||
mPendingCredential = null; | ||
updateUI(); | ||
|
||
if (task.isSuccessful()) { | ||
setStatus("Signed in as " + getUserIdentifier(task.getResult().getUser())); | ||
} else { | ||
Log.w(TAG, "Merge failed", task.getException()); | ||
setStatus("Failed to resolve merge conflict, see logs."); | ||
} | ||
} | ||
}); | ||
} | ||
|
||
@OnClick(R.id.sign_out) | ||
public void signOut() { | ||
AuthUI.getInstance().signOut(this) | ||
.addOnCompleteListener(new OnCompleteListener<Void>() { | ||
@Override | ||
public void onComplete(@NonNull Task<Void> task) { | ||
setStatus(null); | ||
updateUI(); | ||
} | ||
}); | ||
} | ||
|
||
@Override | ||
protected void onActivityResult(int requestCode, int resultCode, Intent data) { | ||
super.onActivityResult(requestCode, resultCode, data); | ||
if (requestCode == RC_SIGN_IN) { | ||
IdpResponse response = IdpResponse.fromResultIntent(data); | ||
if (resultCode == RESULT_OK) { | ||
setStatus("Signed in as " + getUserIdentifier(FirebaseAuth.getInstance().getCurrentUser())); | ||
} else { | ||
if (response.getError().getErrorCode() == ErrorCodes.ANONYMOUS_UPGRADE_MERGE_CONFLICT) { | ||
setStatus("Merge conflict: user already exists."); | ||
mResolveMergeButton.setEnabled(true); | ||
mPendingCredential = response.getPendingCredential(); | ||
} | ||
} | ||
|
||
updateUI(); | ||
} | ||
} | ||
|
||
private void updateUI() { | ||
FirebaseUser currentUser = FirebaseAuth.getInstance().getCurrentUser(); | ||
|
||
if (currentUser == null) { | ||
// Not signed in | ||
mAnonSignInButton.setEnabled(true); | ||
mLaunchUIButton.setEnabled(false); | ||
mResolveMergeButton.setEnabled(false); | ||
mSignOutButton.setEnabled(false); | ||
} else if (mPendingCredential == null && currentUser.isAnonymous()) { | ||
// Anonymous user, waiting for linking | ||
mAnonSignInButton.setEnabled(false); | ||
mLaunchUIButton.setEnabled(true); | ||
mResolveMergeButton.setEnabled(false); | ||
mSignOutButton.setEnabled(true); | ||
} else if (mPendingCredential == null && !currentUser.isAnonymous()) { | ||
// Fully signed in | ||
mAnonSignInButton.setEnabled(false); | ||
mLaunchUIButton.setEnabled(false); | ||
mResolveMergeButton.setEnabled(false); | ||
mSignOutButton.setEnabled(true); | ||
} else if (mPendingCredential != null) { | ||
// Signed in anonymous, awaiting merge conflict | ||
mAnonSignInButton.setEnabled(false); | ||
mLaunchUIButton.setEnabled(false); | ||
mResolveMergeButton.setEnabled(true); | ||
mSignOutButton.setEnabled(true); | ||
} | ||
} | ||
|
||
private void setStatus(String message) { | ||
mStatus.setText(message); | ||
} | ||
|
||
private String getUserIdentifier(FirebaseUser user) { | ||
if (user.isAnonymous()) { | ||
return user.getUid(); | ||
} else if (!TextUtils.isEmpty(user.getEmail())) { | ||
return user.getEmail(); | ||
} else if (!TextUtils.isEmpty(user.getPhoneNumber())) { | ||
return user.getPhoneNumber(); | ||
} else { | ||
return "unknown"; | ||
} | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
<?xml version="1.0" encoding="utf-8"?> | ||
<LinearLayout | ||
xmlns:android="http://schemas.android.com/apk/res/android" | ||
xmlns:tools="http://schemas.android.com/tools" | ||
android:id="@+id/root" | ||
android:layout_width="match_parent" | ||
android:layout_height="match_parent" | ||
android:layout_marginLeft="24dp" | ||
android:layout_marginRight="24dp" | ||
android:layout_marginTop="16dp" | ||
android:layout_marginBottom="16dp" | ||
android:orientation="vertical" | ||
tools:context=".auth.AuthUiActivity"> | ||
|
||
|
||
<TextView | ||
style="@style/Base.TextAppearance.AppCompat.Headline" | ||
android:layout_width="wrap_content" | ||
android:layout_height="wrap_content" | ||
android:layout_gravity="center_horizontal" | ||
android:drawableTop="@drawable/firebase_auth_120dp" | ||
android:text="@string/title_account_linking" /> | ||
|
||
<TextView | ||
android:id="@+id/status_text" | ||
android:layout_width="match_parent" | ||
android:layout_height="wrap_content" | ||
android:layout_margin="16dp" | ||
android:gravity="center" | ||
android:textIsSelectable="true" | ||
tools:text="This is the status view, sometimes it will have a very long status and other..." /> | ||
|
||
<Button | ||
android:id="@+id/anon_sign_in" | ||
style="@style/Widget.AppCompat.Button.Colored" | ||
android:layout_width="200dp" | ||
android:layout_height="wrap_content" | ||
android:layout_gravity="center" | ||
android:text="@string/anonymous_sign_in" /> | ||
|
||
<Button | ||
android:id="@+id/begin_flow" | ||
style="@style/Widget.AppCompat.Button.Colored" | ||
android:layout_width="200dp" | ||
android:layout_height="wrap_content" | ||
android:layout_gravity="center" | ||
android:text="@string/launch_auth_ui" | ||
android:enabled="false" /> | ||
|
||
<Button | ||
android:id="@+id/resolve_merge" | ||
style="@style/Widget.AppCompat.Button.Colored" | ||
android:layout_width="200dp" | ||
android:layout_height="wrap_content" | ||
android:layout_gravity="center" | ||
android:text="@string/resolve_merge_conflict" | ||
android:enabled="false" /> | ||
|
||
<Button | ||
android:id="@+id/sign_out" | ||
style="@style/Widget.AppCompat.Button.Colored" | ||
android:layout_width="200dp" | ||
android:layout_height="wrap_content" | ||
android:layout_gravity="center" | ||
android:text="@string/sign_out" | ||
android:enabled="false" /> | ||
|
||
</LinearLayout> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
Decided to do a new sample here since this is an "advanced" feature and I don't want to clutter the main sample.
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.
Ooof! You sure about this? In my PR, I just added a checkbox that said something like "Allow account linking" and a button to open the
AuthUiActivity
from the signed in one so devs could fully customize the linking experience (I noticed you hardcoded the providers and all that).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 we were going your way with this feature (service, data callbacks, etc) then I'd agree with merging the samples. However in this case since you have to write extra code and really understand the merge-failure-upgrade flow I wanted to give it dedicated UI and keep the code separate so as not so confuse users of the 'basic' AuthUI flow with all the extra stuff.
If you try out the sample, you can see how it makes the various states extra clear.
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.
Gotya, that totally makes sense so 👍.