Universal email sign in is a flow that will resolve connected auth providers with a given email. This flow is intended to solve the problem where the user doesn't remember which provider was previously used to authenticate.
Firebase UI provides a pre-built UniversalEmailSignInScreen
.
UniversalEmailSignInScreen(
// optional, shows a dialog with a sign in ui
// with all connected providers.
onProvidersFound: (email, providers) {
// navigate to a custom sign in that provides
// a UI for authentication for received providers.
}
);
If the pre-built screens don't suit the app's needs, you could use a FindProvidersForEmailView
to build your custom screen:
class MyLoginScreen extends StatelessWidget {
@override
Widget build(BuildContext) {
return Scaffold(
body: Row(
children: [
MyCustomSideBar(),
Padding(
padding: const EdgeInsets.all(16),
child: FindProvidersForEmailView(
onProvidersFound: (email, providers) {
// navigate to a custom sign in that provides
// a UI for authentication for received providers.
},
),
)
],
),
);
}
}
You could also use AuthFlowBuilder
to facilitate the functionality of the UniversalEmailSignInFlow
:
class MyCustomWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return AuthFlowBuilder<UniversalEmailSignInController>(
listener: (oldState, newState, controller) {
if (newState is DifferentSignInMethodsFound) {
showDifferentMethodSignInDialog(
context: context,
availableProviders: newState.methods,
providers: FirebaseUIAuth.providersFor(
FirebaseAuth.instance.app,
),
);
}
},
builder: (context, state, ctrl, child) {
if (state is Uninitialized) {
return TextField(
decoration: InputDecoration(
labelText: 'Email',
),
onSubmitted: (email) {
ctrl.findProvidersForEmail(email);
},
);
} else if (state is FetchingProvidersForEmail) {
return CircularProgressIndicator();
} else if (state is AuthFailed) {
return ErrorText(exception: state.exception);
} else {
return Text('Unknown state $state');
}
},
);
}
}
For full control over every phase of the authentication lifecycle, you could build a stateful widget which implements UniversalEmailSignInListener
:
class CustomUniversalEmailSignIn extends StatefulWidget {
const CustomUniversalEmailSignIn({super.key});
@override
State<CustomUniversalEmailSignIn> createState() =>
_CustomUniversalEmailSignInState();
}
class _CustomUniversalEmailSignInState extends State<CustomUniversalEmailSignIn>
implements UniversalEmailSignInListener {
final auth = FirebaseAuth.instance;
late final UniversalEmailSignInProvider provider =
UniversalEmailSignInProvider()..authListener = this;
late Widget child = TextField(
decoration: const InputDecoration(
labelText: 'Email',
),
onSubmitted: provider.findProvidersForEmail,
);
@override
void onBeforeProvidersForEmailFetch() {
setState(() {
child = CircularProgressIndicator();
});
}
@override
void onDifferentProvidersFound(
String email,
List<String> providers,
AuthCredential? credential,
) {
showDifferentMethodSignInDialog(
context: context,
availableProviders: providers,
providers: FirebaseUIAuth.providersFor(FirebaseAuth.instance.app),
);
}
@override
Widget build(BuildContext context) {
return Center(child: child);
}
@override
void onBeforeCredentialLinked(AuthCredential credential) {
setState(() {
child = CircularProgressIndicator();
});
}
@override
void onBeforeSignIn() {
setState(() {
child = CircularProgressIndicator();
});
}
@override
void onCanceled() {
setState(() {
child = Text('Authenticated cancelled');
});
}
@override
void onCredentialLinked(AuthCredential credential) {
Navigator.of(context).pushReplacementNamed('/profile');
}
@override
void onError(Object error) {
try {
// tries default recovery strategy
defaultOnAuthError(provider, error);
} catch (err) {
setState(() {
defaultOnAuthError(provider, error);
});
}
}
@override
void onSignedIn(UserCredential credential) {
Navigator.of(context).pushReplacementNamed('/profile');
}
}