I have different methods in my app to log in:
For the question I'll focus on the first 2 ones. When the user logs in with Facebook the providers look like this:
That's fine but if I log out and log in again, this time with a new Google account but using same email, the providers look like this:
Now, if I log out and log in again with Facebook I face the account-exists-with-different-credential
error. Something for which I have the logic prepared and show its provider login method, but this user should have both provider available and he should be able to log in with both methods.
This is my code:
Future facebookSignIn(BuildContext context) async {
final LoginResult result = await FacebookAuth.instance.login();
if (result.status == LoginStatus.success) {
final AccessToken accessToken = result.accessToken!;
AuthCredential credential =
FacebookAuthProvider.credential(accessToken.token);
await _firebaseCredential(context, credential);
}
}
Future googleSignIn(BuildContext context,
[String? email, facebookCredential]) async {
try {
GoogleSignInAccount googleUser;
dynamic popup = await _googleSignIn.signIn();
// cancelled login
if (popup == null) {
return null;
}
googleUser = popup;
GoogleSignInAuthentication googleAuth = await googleUser.authentication;
final AuthCredential credential = GoogleAuthProvider.credential(
accessToken: googleAuth.accessToken,
idToken: googleAuth.idToken,
);
await _firebaseCredential(context, credential);
} on FirebaseAuthException catch (e) {
// await FirebaseCrashlytics.instance.recordError(
// e,
// StackTrace.fromString("/googleSignIn"),
// reason: e.message,
// );
// return null;
}
}
_firebaseCredential(BuildContext context, credential) async {
try {
User user =
(await FirebaseAuth.instance.signInWithCredential(credential)).user!;
// Provider.of<MyRents>(context, listen: false).updateUI();
await firebaseProfile.updateUserData(context, user);
} on FirebaseAuthException catch (error) {
// final error = e as FirebaseAuthException;
if (error.code == 'account-exists-with-different-credential') {
String email = error.email!;
// AuthCredential pendingCredential = e.credential;
List<String> signInMethods =
await FirebaseAuth.instance.fetchSignInMethodsForEmail(email);
// If the user has several sign-in methods,
// the first method in the list will be the "recommended" method to use.
if (signInMethods.first == 'google.com' ||
signInMethods.first == 'facebook.com') {
// TODO: fix facebook
return await googleSignIn(context, email, credential);
} else {
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text(error.message!)));
}
} else {
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text(error.message!)));
}
}
}
Am I missing something?
flutter_facebook_auth: ^4.3.3
google_sign_in: ^5.2.1
Future googleSignIn(BuildContext context,
[String? email, facebookCredential]) async {
try {
GoogleSignInAccount googleUser;
dynamic popup = await _googleSignIn.signIn();
// cancelled login
if (popup == null) {
return null;
}
googleUser = popup;
GoogleSignInAuthentication googleAuth = await googleUser.authentication;
final AuthCredential credential = GoogleAuthProvider.credential(
accessToken: googleAuth.accessToken,
idToken: googleAuth.idToken,
);
await _firebaseCredential(context, credential);
} on FirebaseAuthException catch (e) {
// await FirebaseCrashlytics.instance.recordError(
// e,
// StackTrace.fromString("/googleSignIn"),
// reason: e.message,
// );
// return null;
}
}
Future facebookSignIn(BuildContext context) async {
final LoginResult result = await FacebookAuth.instance.login();
if (result.status == LoginStatus.success) {
final AccessToken accessToken = result.accessToken!;
AuthCredential credential =
FacebookAuthProvider.credential(accessToken.token);
await _firebaseCredential(context, credential);
}
}
// other methods...
_firebaseCredential(BuildContext context, credential) async {
try {
User user =
(await FirebaseAuth.instance.signInWithCredential(credential)).user!;
await firebaseProfile.updateUserData(context, user);
} on FirebaseAuthException catch (error) {
if (error.code == 'account-exists-with-different-credential') {
String email = error.email!;
List<String> signInMethods =
await FirebaseAuth.instance.fetchSignInMethodsForEmail(email);
// bool newUser = (signInMethods.length > 0) ? false : true;
// If the user has several sign-in methods,
// the first method in the list will be the "recommended" method to use.
var user;
switch (signInMethods.first) {
case 'google.com':
user = await googleSignIn(context, email, credential);
break;
case 'facebook.com':
user = await facebookSignIn(context);
break;
case 'apple.com':
user = await appleSignIn(context);
break;
case 'password':
// since password is managed by user we force have email provider only
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text(translate('auth.signInMethods_password'))));
break;
// TODO: apple
}
await linkProvider(context, credential);
return user;
}
return ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text(error.message!)));
}
}
// just some extra error covering
Future linkProvider(BuildContext context, credential) async {
try {
await FirebaseAuth.instance.currentUser?.linkWithCredential(credential);
} on FirebaseAuthException catch (e) {
switch (e.code) {
case "provider-already-linked":
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text(translate('auth.provider_already_linked'))));
break;
case "invalid-credential":
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(translate('auth.invalid_credential'))));
break;
case "credential-already-in-use":
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text(translate('auth.credential_already_in_use'))));
break;
default:
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(translate('auth.something_happened'))));
}
}
}
If you do Google -> Facebook it will look like this:
Other way around only Google will be present if your Google email is a trusted email (gmail). More info about that: https://groups.google.com/g/firebase-talk/c/ms_NVQem_Cw/m/8g7BFk1IAAAJ