# Flutter - Authentication BLoC

Style: Wyatt Analysis SDK: Flutter

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 changes - `stop()` to cancel listener - SignUpCubit * Handles email/password validation and password confirmation * Handles register with email/password * Handles custom form fields thanks `wyatt_form_bloc` - Use `entries` to pass a `FormData` object - You can use several pre configured `FormInput` for validation - You can use `updateFormData()` to change FormData and validators during runtime (intersection, union, difference or replace) - 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 ```dart import 'package:wyatt_authentication_bloc/wyatt_authentication_bloc.dart'; ``` ## Usage Create an authentication repository: ```dart final AuthenticationRepositoryInterface _authenticationRepository = AuthenticationRepositoryFirebase(); ``` Create an authentication cubit: ```dart final AuthenticationCubit _authenticationCubit = AuthenticationCubit( authenticationRepository: _authenticationRepository, ); ``` Create a sign up cubit: ```dart final SignUpCubit _signUpCubit = SignUpCubit( authenticationRepository: _authenticationRepository, authenticationCubit: _authenticationCubit, ); ``` You can use `AuthenticationBloc` to route your app. ```dart return MultiRepositoryProvider( providers: [ RepositoryProvider( create: (context) => _authenticationRepository, ), ], child: MultiBlocProvider( providers: [ BlocProvider( create: (context) => _authenticationCubit..init(), ), BlocProvider( create: (context) => _signUpCubit, ), ], child: const AppView(), ), ); ``` > Don't forget to call `init()` on authentication cubit. And in `AppView` use an `AuthenticationBuilder`: ```dart 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`: ```dart BlocProvider( create: (_) => SignInCubit(context.read()), child: const LoginForm(), ), ``` > In practice it's better to create it in the main `MultiBlocProvider` because 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: ```dart 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: - `ConfirmedPassword` validator need password value and confirm password value to compare. ```dart return BlocBuilder( builder: (context, state) { return TextField( onChanged: (confirmPassword) => context .read() .dataChanged( 'form_field_confirmPassword', ConfirmedPassword.dirty( password: context.read().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_confirmPassword` is 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 ! ```dart return BlocBuilder( builder: (context, state) { return TextField( onChanged: (password) { context.read().passwordChanged(password); context.read().dataChanged( 'form_field_confirmPassword', ConfirmedPassword.dirty( password: password, value: context .read() .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()` AND `dataChanged()`. 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: ```dart Future 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: ```dart 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 `fieldName` and `export` to control `.toMap()` result on FormData ! Useful to disable exportation of sensible data like passwords. Create widgets for each inputs: ```dart class _PhoneInput extends StatelessWidget { @override Widget build(BuildContext context) { return BlocBuilder( builder: (context, state) { return TextField( onChanged: (phone) => context .read() .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: ```dart context.read().signUpFormSubmitted() ``` And voilĂ , a document with `uid` as id, and fields `email`, `name`, `phone`, `uid` will be create in `users` collection.