- wyatt_architecture@0.0.2 - wyatt_authentication_bloc@0.2.1+6 - wyatt_form_bloc@0.1.0+1
Flutter - Authentication BLoC
Authentication Bloc for Flutter.
Features
- UserInterface
- UserFirebase : FirebaseAuth user implementation
- AuthenticationRepositoryInterface
- AuthenticationRepositoryFirebase : FirebaseAuth implementation
- ExceptionsInterface
- ExceptionsFirebase : FirebaseAuth Exception parsing implementation
- AuthenticationBloc
- Tracks every user changes
- Right after the listener has been registered.
- When a user is signed in.
- When the current user is signed out.
- When there is a change in the current user's token.
- On
refresh()
- Start/Stop listening on demand
start()to listen to user changesstop()to cancel listener
- Tracks every user changes
- SignUpCubit
- Handles email/password validation and password confirmation
- Handles register with email/password
- Handles custom form fields thanks
wyatt_form_bloc- Use
entriesto pass aFormDataobject - You can use several pre configured
FormInputfor validation - You can use
updateFormData()to change FormData and validators during runtime (intersection, union, difference or replace)
- Use
- SignInCubit
- Handles email/password validation
- Handles login with email/password
- EmailVerificationCubit
- Handles send email verification process
- Handles email verification check
- PasswordResetCubit
- Handles send password reset email process
- Builders
- AuthenticationBuilder to build widgets on user state changes
- Consistent
- Every class have same naming convention
- Tested
- Partially tested with bloc_test
Getting started
Simply add wyatt_authentication_bloc in pubspec.yaml, then
import 'package:wyatt_authentication_bloc/wyatt_authentication_bloc.dart';
Usage
Create an authentication repository:
final AuthenticationRepositoryInterface _authenticationRepository = AuthenticationRepositoryFirebase();
Create an authentication cubit:
final AuthenticationCubit _authenticationCubit = AuthenticationCubit(
authenticationRepository: _authenticationRepository,
);
Create a sign up cubit:
final SignUpCubit _signUpCubit = SignUpCubit(
authenticationRepository: _authenticationRepository,
authenticationCubit: _authenticationCubit,
);
You can use AuthenticationBloc to route your app.
return MultiRepositoryProvider(
providers: [
RepositoryProvider<AuthenticationRepositoryInterface>(
create: (context) => _authenticationRepository,
),
],
child: MultiBlocProvider(
providers: [
BlocProvider<AuthenticationCubit>(
create: (context) => _authenticationCubit..init(),
),
BlocProvider<SignUpCubit>(
create: (context) => _signUpCubit,
),
],
child: const AppView(),
),
);
Don't forget to call
init()on authentication cubit.
And in AppView use an AuthenticationBuilder:
AuthenticationBuilder(
unknown: (context) => const LoadingPage(),
unauthenticated: (context) => const LoginPage(),
authenticated: (context, user, userData) => const HomePage(),
)
To create a SignInCubit you'll need the same AuthenticationRepository, you can use the context:
BlocProvider(
create: (_) => SignInCubit(context.read<AuthenticationRepositoryInterface>()),
child: const LoginForm(),
),
In practice it's better to create it in the main
MultiBlocProviderbecause the LoginPage can be destroyed, and cubit closed, before login flow ends
Recipes
Password confirmation
In this recipe we'll se how to create a custom FormEntry to confirm password.
First, create an entry at the SignUpCubit creation:
SignUpCubit _signUpCubit = SignUpCubit(
authenticationRepository: _authenticationRepository,
authenticationCubit: _authenticationCubit,
entries: const FormData([
FormEntry('form_field_confirmPassword', ConfirmedPassword.pure()),
]),
);
Then, in the sign up form, create an input for password confirmation:
ConfirmedPasswordvalidator need password value and confirm password value to compare.
return BlocBuilder<SignUpCubit, SignUpState>(
builder: (context, state) {
return TextField(
onChanged: (confirmPassword) => context
.read<SignUpCubit>()
.dataChanged(
'form_field_confirmPassword',
ConfirmedPassword.dirty(
password: context.read<SignUpCubit>().state.password.value,
value: confirmPassword,
),
),
obscureText: true,
decoration: InputDecoration(
labelText: 'confirm password',
errorText: state.data!.input('form_field_confirmPassword').invalid
? 'passwords do not match'
: null,
),
);
},
);
form_field_confirmPasswordis the field identifier used in all application to retrieve data. You can use a constant to avoid typos.
You'll need to update password input to update confirm state on password update !
return BlocBuilder<SignUpCubit, SignUpState>(
builder: (context, state) {
return TextField(
onChanged: (password) {
context.read<SignUpCubit>().passwordChanged(password);
context.read<SignUpCubit>().dataChanged(
'form_field_confirmPassword',
ConfirmedPassword.dirty(
password: password,
value: context
.read<SignUpCubit>()
.state
.data!
.input('form_field_confirmPassword')
.value,
),
);
},
obscureText: true,
decoration: InputDecoration(
labelText: 'password',
errorText: state.password.invalid ? 'invalid password' : null,
),
);
},
);
Here you call standard
passwordChanged()ANDdataChanged().
And voilà !
Create Firestore Document on Sign Up
In this recipe we'll se how to create a Firestore Document on sign up success.
First create a callback function:
Future<void> onSignUpSuccess(SignUpState state, String? uid) async {
if (uid != null) {
final user = {
'uid': uid,
'email': state.email.value,
...state.data.toMap(),
};
await FirebaseFirestore.instance.collection('users').doc(uid).set(user);
}
}
Then create SignUpCubit with custom entries and register callback:
SignUpCubit _signUpCubit = SignUpCubit(
authenticationRepository: _authenticationRepository,
authenticationCubit: _authenticationCubit,
entries: const FormData([
FormEntry('form_field_name', Name.pure(), fieldName: 'name'),
FormEntry('form_field_phone', Phone.pure(), fieldName: 'phone'),
FormEntry('form_field_confirmPassword', ConfirmedPassword.pure(), export: false),
]),
onSignUpSuccess: onSignUpSuccess,
);
Use
fieldNameandexportto control.toMap()result on FormData ! Useful to disable exportation of sensible data like passwords.
Create widgets for each inputs:
class _PhoneInput extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocBuilder<SignUpCubit, SignUpState>(
builder: (context, state) {
return TextField(
onChanged: (phone) => context
.read<SignUpCubit>()
.dataChanged('form_field_phone', Phone.dirty(phone)),
keyboardType: TextInputType.phone,
decoration: InputDecoration(
labelText: 'phone',
helperText: '',
errorText: state.data!.input('form_field_phone').invalid
? 'invalid phone'
: null,
),
);
},
);
}
}
Create widgets for Name and ConfirmedPassword too.
Then add a sign up button with:
context.read<SignUpCubit>().signUpFormSubmitted()
And voilà, a document with uid as id, and fields email, name, phone, uid will be create in users collection.