# 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 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.