feat(authentication): add custom routine, and documentation

This commit is contained in:
Hugo Pointcheval 2023-02-06 18:54:45 +01:00
parent eafe4dc7c6
commit 3faceeebb6
Signed by: hugo
GPG Key ID: 3AAC487E131E00BC
19 changed files with 331 additions and 424 deletions

View File

@ -65,22 +65,23 @@ class AppRouter {
),
),
GoRoute(
path: '/home',
name: HomePage.pageName,
pageBuilder: (context, state) => defaultTransition(
context,
state,
const HomePage(),
),
),
GoRoute(
path: '/home/sub',
name: SubPage.pageName,
pageBuilder: (context, state) => defaultTransition(
context,
state,
const SubPage(),
),
),
path: '/home',
name: HomePage.pageName,
pageBuilder: (context, state) => defaultTransition(
context,
state,
const HomePage(),
),
routes: [
GoRoute(
path: 'sub',
name: SubPage.pageName,
pageBuilder: (context, state) => defaultTransition(
context,
state,
const SubPage(),
),
),
]),
];
}

View File

@ -30,19 +30,6 @@ import 'package:go_router/go_router.dart';
import 'package:wyatt_authentication_bloc/wyatt_authentication_bloc.dart';
import 'package:wyatt_form_bloc/wyatt_form_bloc.dart';
// FutureOrResult<int?> onAccountChanges(
// AuthenticationRepository<int> repo,
// AuthChangeEvent? authEvent,
// ) async {
// final id = Random().nextInt(1000);
// final token =
// await repo.getIdentityToken().fold((value) => value, (error) => 'null');
// debugPrint(
// 'onAccountChanges: ${authEvent?.account}, type: ${authEvent.runtimeType}, token: $token, generatedId: $id');
// return Ok<int, AppException>(id);
// }
class App extends StatelessWidget {
final AuthenticationRepository<int> authenticationRepository =
AuthenticationRepositoryImpl(
@ -64,7 +51,9 @@ class App extends StatelessWidget {
Widget build(BuildContext context) {
AuthenticationState<int>? previous;
final AuthenticationCubit<int> authenticationCubit = ExampleAuthenticationCubit(authenticationRepository: authenticationRepository);
final AuthenticationCubit<int> authenticationCubit =
ExampleAuthenticationCubit(
authenticationRepository: authenticationRepository);
final GoRouter router = GoRouter(
initialLocation: '/',
@ -91,7 +80,7 @@ class App extends StatelessWidget {
return '/home';
}
}
return null;
return state.name;
},
);

View File

@ -49,4 +49,11 @@ class ExampleAuthenticationCubit extends AuthenticationCubit<int> {
return const Ok(null);
}
@override
FutureOrResult<void> onDelete() {
print('onDelete');
return const Ok(null);
}
}

View File

@ -35,14 +35,20 @@ class SubPage extends StatelessWidget {
icon: const Icon(Icons.logout_rounded)),
IconButton(
onPressed: () =>
context.read<AuthenticationRepository<int>>().refresh(),
context.read<AuthenticationCubit<int>>().refresh(),
icon: const Icon(Icons.refresh))
],
),
body: const Padding(
padding: EdgeInsets.all(8),
child: SingleChildScrollView(
child: Text('Another page'),
body: Padding(
padding: const EdgeInsets.all(8),
child: ListView(
children: [
const Text('Another page'),
ElevatedButton(
onPressed: () => context.read<AuthenticationCubit<int>>().delete(),
child: const Text('Delete account'),
),
],
),
),
);

View File

@ -35,7 +35,7 @@ class CustomRoutine<R, Data> {
final FutureOr<Result<Data?, AppException>> Function(
Result<R, AppException> routineResult,
) attachedLogic;
final void Function(AppException? exception) onError;
final void Function(AppException exception) onError;
final void Function(R result, Data? data) onSuccess;
FutureOr<void> call() async {
@ -46,17 +46,17 @@ class CustomRoutine<R, Data> {
// Check for errors
if (result.isErr) {
onError.call(result.err);
onError.call(result.err!);
return;
}
if (customRoutineResult.isErr) {
onError.call(customRoutineResult.err);
onError.call(customRoutineResult.err!);
return;
}
// If no error
return onSuccess.call(result.ok as Input, customRoutineResult.ok);
return onSuccess.call(result.ok as R, customRoutineResult.ok);
}
}

View File

@ -42,7 +42,7 @@ abstract class AuthenticationCubit<Data>
}
final AuthenticationRepository<Data> authenticationRepository;
SessionWrapper<Data>? latestSession;
SessionWrapper<Data>? _latestSession;
void _listenForAuthenticationChanges() {
authenticationRepository.sessionStream().asyncMap((wrapper) async {
@ -67,7 +67,7 @@ abstract class AuthenticationCubit<Data>
}
return wrapper;
}).listen((wrapper) async {
latestSession = wrapper;
_latestSession = wrapper;
final session = wrapper.session;
if (session != null) {
emit(AuthenticationState<Data>.authenticated(wrapper));
@ -79,143 +79,59 @@ abstract class AuthenticationCubit<Data>
}
/// {@macro refresh}
FutureOr<void> refresh() async {
final refreshedAccount = await authenticationRepository.refresh();
final customRoutineResult = await onRefresh(refreshedAccount);
if (refreshedAccount.isOk && customRoutineResult.isOk) {
final account = refreshedAccount.ok!;
final sessionData = customRoutineResult.ok;
final refreshedSession = SessionWrapper(
event: RefreshedEvent(account: account),
session: Session<Data>(
account: account,
data: sessionData,
FutureOr<void> refresh() async => CustomRoutine<Account, Data?>(
routine: authenticationRepository.refresh,
attachedLogic: onRefresh,
onError: addError,
onSuccess: (result, data) => authenticationRepository.addSession(
SessionWrapper(
event: RefreshedEvent(account: result),
session: Session<Data>(
account: result,
data: data,
),
),
);
authenticationRepository.addSession(refreshedSession);
return;
}
if (refreshedAccount.isErr) {
addError(refreshedAccount.err!);
return;
}
if (customRoutineResult.isErr) {
addError(customRoutineResult.err!);
return;
}
}
),
).call();
/// {@macro reauthenticate}
FutureOr<void> reauthenticate() async {
final reauthenticatedAccount =
await authenticationRepository.reauthenticate();
final customRoutineResult = await onReauthenticate(reauthenticatedAccount);
if (reauthenticatedAccount.isOk && customRoutineResult.isOk) {
final account = reauthenticatedAccount.ok!;
final sessionData = customRoutineResult.ok;
final reauthenticatedSession = SessionWrapper(
event: ReauthenticatedEvent(account: account),
session: Session<Data>(
account: account,
data: sessionData,
FutureOr<void> reauthenticate() async => CustomRoutine<Account, Data?>(
routine: authenticationRepository.reauthenticate,
attachedLogic: onReauthenticate,
onError: addError,
onSuccess: (result, data) => authenticationRepository.addSession(
SessionWrapper(
event: ReauthenticatedEvent(account: result),
session: Session<Data>(
account: result,
data: data,
),
),
),
);
authenticationRepository.addSession(reauthenticatedSession);
return;
}
if (reauthenticatedAccount.isErr) {
addError(reauthenticatedAccount.err!);
return;
}
if (customRoutineResult.isErr) {
addError(customRoutineResult.err!);
return;
}
}
).call();
/// {@macro signout}
FutureOr<void> signOut() async {
final customRoutine = CustomRoutine<void, void>(
// ignore: unnecessary_lambdas
routine: () => authenticationRepository.signOut(),
attachedLogic: (routineResult) => onSignOut(),
onError: (error) => addError(error!),
onSuccess: (result, data) => authenticationRepository
.addSession(const SessionWrapper(event: SignedOutEvent())),
);
customRoutine.call();
final signOut = await authenticationRepository.signOut();
final customRoutineResult = await onSignOut();
if (signOut.isOk && customRoutineResult.isOk) {
authenticationRepository
.addSession(const SessionWrapper(event: SignedOutEvent()));
return;
}
if (signOut.isErr) {
addError(signOut.err!);
return;
}
if (customRoutineResult.isErr) {
addError(customRoutineResult.err!);
return;
}
}
FutureOr<void> signOut() async => CustomRoutine<void, void>(
routine: authenticationRepository.signOut,
attachedLogic: (routineResult) => onSignOut(),
onError: addError,
onSuccess: (result, data) => authenticationRepository
.addSession(SessionWrapper<Data>(event: const SignedOutEvent())),
).call();
/// {@macro delete}
FutureOr<void> delete() async {
final signOut = await authenticationRepository.delete();
final customRoutineResult = await onDelete();
if (signOut.isOk && customRoutineResult.isOk) {
authenticationRepository
.addSession(const SessionWrapper(event: DeletedEvent()));
return;
}
if (signOut.isErr) {
addError(signOut.err!);
return;
}
if (customRoutineResult.isErr) {
addError(customRoutineResult.err!);
return;
}
}
FutureOr<void> delete() async => CustomRoutine<void, void>(
routine: authenticationRepository.delete,
attachedLogic: (routineResult) => onDelete(),
onError: addError,
onSuccess: (result, data) => authenticationRepository
.addSession(SessionWrapper<Data>(event: const DeletedEvent())),
).call();
/// Returns latest session wrapper.
///
/// Contains latest event and latest session data (account + extra data)
SessionWrapper<Data>? currentSession() => latestSession;
SessionWrapper<Data>? currentSession() => _latestSession;
/// This callback is triggered when the user is automaticcaly logged in from
/// the cache.

View File

@ -24,28 +24,22 @@ import 'package:wyatt_type_utils/wyatt_type_utils.dart';
part 'password_reset_state.dart';
/// Cubit that allows user to reset his password
class PasswordResetCubit<Extra> extends FormDataCubit<PasswordResetState> {
PasswordResetCubit({
required this.authenticationRepository,
}) :
super(
}) : super(
PasswordResetState(
form: authenticationRepository.formRepository
.accessForm(AuthFormName.passwordResetForm),
),
);
final AuthenticationRepository<Extra> authenticationRepository;
FormRepository get formRepository =>
authenticationRepository.formRepository;
FormRepository get formRepository => authenticationRepository.formRepository;
@override
String get formName => AuthFormName.passwordResetForm;
void emailChanged(String value) {
final Email email = Email.dirty(value);
dataChanged(AuthFormField.email, email);
}
@override
FutureOr<void> dataChanged<Value>(
String key,
@ -74,6 +68,33 @@ class PasswordResetCubit<Extra> extends FormDataCubit<PasswordResetState> {
);
}
@override
FutureOr<void> update(
WyattForm form, {
SetOperation operation = SetOperation.replace,
}) {
final WyattForm current = formRepository.accessForm(formName).clone();
final WyattForm newForm = operation.operation.call(current, form);
formRepository.updateForm(newForm);
emit(
state.copyWith(
form: newForm,
status: newForm.validate(),
),
);
}
@override
FutureOr<void> validate() {
emit(
state.copyWith(
status: formRepository.accessForm(formName).validate(),
),
);
}
/// Sends a password reset email to the user
@override
FutureOr<void> submit() async {
if (!state.status.isValidated) {
@ -109,29 +130,29 @@ class PasswordResetCubit<Extra> extends FormDataCubit<PasswordResetState> {
);
}
@override
FutureOr<void> update(
WyattForm form, {
SetOperation operation = SetOperation.replace,
}) {
final WyattForm current = formRepository.accessForm(formName).clone();
final WyattForm newForm = operation.operation.call(current, form);
formRepository.updateForm(newForm);
emit(
state.copyWith(
form: newForm,
status: newForm.validate(),
),
void emailChanged(String value) {
final emailValidatorType = formRepository
.accessForm(formName)
.validatorOf(AuthFormField.email)
.runtimeType;
assert(
emailValidatorType == Email,
'Use emailCustomChanged(...) with validator $emailValidatorType',
);
final Email email = Email.dirty(value);
dataChanged(AuthFormField.email, email);
}
@override
FutureOr<void> validate() {
emit(
state.copyWith(
status: formRepository.accessForm(formName).validate(),
),
);
/// Same as [emailChanged] but with a custom [Validator].
///
/// Sort of short hand for [dataChanged].
void emailCustomChanged<
Validator extends FormInputValidator<String?, ValidationError>>(
Validator validator,
) {
dataChanged(AuthFormField.email, validator);
}
// TODO(wyatt): create base_password_reset_cubit and create mixins
}

View File

@ -16,6 +16,8 @@
part of 'sign_in_cubit.dart';
/// Abstract sign in cubit useful for implementing a cubit with fine
/// granularity by adding only the required mixins.
abstract class BaseSignInCubit<Data> extends FormDataCubit<SignInState> {
BaseSignInCubit({
required this.authenticationRepository,

View File

@ -17,17 +17,26 @@
import 'dart:async';
import 'package:wyatt_architecture/wyatt_architecture.dart';
import 'package:wyatt_authentication_bloc/src/core/utils/custom_routine.dart';
import 'package:wyatt_authentication_bloc/src/domain/entities/entities.dart';
import 'package:wyatt_authentication_bloc/src/features/sign_in/cubit/sign_in_cubit.dart';
import 'package:wyatt_form_bloc/wyatt_form_bloc.dart';
import 'package:wyatt_type_utils/wyatt_type_utils.dart';
/// Sign in mixin.
///
/// Allows the user to sign in anonymously
///
/// Gives access to the `signInAnonymously` method and
/// `onSignInAnonymously` callback.
mixin SignInAnonymously<Data> on BaseSignInCubit<Data> {
/// This callback is triggered when a user signs in anonymously.
FutureOrResult<Data?> onSignInAnonymously(
Result<Account, AppException> result,
WyattForm form,
);
/// {@macro signin_anom}
FutureOr<void> signInAnonymously() async {
if (state.status.isSubmissionInProgress) {
return;
@ -36,57 +45,39 @@ mixin SignInAnonymously<Data> on BaseSignInCubit<Data> {
final form = formRepository.accessForm(formName);
emit(SignInState(form: form, status: FormStatus.submissionInProgress));
final result = await authenticationRepository.signInAnonymously();
// Custom routine
final customRoutineResult = await onSignInAnonymously(result, form);
if (customRoutineResult.isErr) {
final error = customRoutineResult.err!;
emit(
SignInState(
form: form,
errorMessage: error.message,
status: FormStatus.submissionFailure,
),
);
addError(error);
}
// Check result
if (result.isErr) {
final error = result.err!;
emit(
SignInState(
form: form,
errorMessage: error.message,
status: FormStatus.submissionFailure,
),
);
addError(error);
}
final account = result.ok!;
final signedInSession = SessionWrapper(
event: SignedInEvent(account: account),
session: Session<Data>(
account: account,
data: customRoutineResult.ok,
return CustomRoutine<Account, Data?>(
routine: authenticationRepository.signInAnonymously,
attachedLogic: (routineResult) => onSignInAnonymously(
routineResult,
form,
),
);
authenticationRepository.addSession(signedInSession);
emit(
result.fold(
(value) =>
SignInState(form: form, status: FormStatus.submissionSuccess),
(error) => SignInState(
form: form,
errorMessage: error.message,
status: FormStatus.submissionFailure,
),
),
);
onError: (error) {
emit(
SignInState(
form: form,
errorMessage: error.message,
status: FormStatus.submissionFailure,
),
);
addError(error);
},
onSuccess: (account, data) {
authenticationRepository.addSession(
SessionWrapper(
event: SignedInEvent(account: account),
session: Session<Data>(
account: account,
data: data,
),
),
);
emit(
SignInState(
form: form,
status: FormStatus.submissionSuccess,
),
);
},
).call();
}
}

View File

@ -18,12 +18,20 @@ import 'dart:async';
import 'package:wyatt_architecture/wyatt_architecture.dart';
import 'package:wyatt_authentication_bloc/src/core/constants/form_field.dart';
import 'package:wyatt_authentication_bloc/src/core/utils/custom_routine.dart';
import 'package:wyatt_authentication_bloc/src/domain/domain.dart';
import 'package:wyatt_authentication_bloc/src/features/sign_in/cubit/sign_in_cubit.dart';
import 'package:wyatt_form_bloc/wyatt_form_bloc.dart';
import 'package:wyatt_type_utils/wyatt_type_utils.dart';
/// Sign in mixin.
///
/// Allows the user to sign in with email and password
///
/// Gives access to the `signInWithEmailAndPassword` method and
/// `onSignInWithEmailAndPassword` callback.
mixin SignInWithEmailPassword<Data> on BaseSignInCubit<Data> {
/// This callback is triggered when a user signs in with email and password.
FutureOrResult<Data?> onSignInWithEmailAndPassword(
Result<Account, AppException> result,
WyattForm form,
@ -76,6 +84,7 @@ mixin SignInWithEmailPassword<Data> on BaseSignInCubit<Data> {
dataChanged(AuthFormField.password, validator);
}
/// {@macro signin_pwd}
FutureOr<void> signInWithEmailAndPassword() async {
if (state.status.isSubmissionInProgress) {
return;
@ -106,63 +115,42 @@ mixin SignInWithEmailPassword<Data> on BaseSignInCubit<Data> {
);
}
final result = await authenticationRepository.signInWithEmailAndPassword(
email: email!,
password: password!,
);
// Custom routine
final customRoutineResult = await onSignInWithEmailAndPassword(
result,
form,
);
if (customRoutineResult.isErr) {
final error = customRoutineResult.err!;
emit(
SignInState(
form: form,
errorMessage: error.message,
status: FormStatus.submissionFailure,
),
);
addError(error);
}
// Check result
if (result.isErr) {
final error = result.err!;
emit(
SignInState(
form: form,
errorMessage: error.message,
status: FormStatus.submissionFailure,
),
);
addError(error);
}
final account = result.ok!;
final signedInSession = SessionWrapper(
event: SignedInEvent(account: account),
session: Session<Data>(
account: account,
data: customRoutineResult.ok,
return CustomRoutine<Account, Data?>(
routine: () => authenticationRepository.signInWithEmailAndPassword(
email: email!,
password: password!,
),
);
authenticationRepository.addSession(signedInSession);
emit(
result.fold(
(value) =>
SignInState(form: form, status: FormStatus.submissionSuccess),
(error) => SignInState(
form: form,
errorMessage: error.message,
status: FormStatus.submissionFailure,
),
attachedLogic: (routineResult) => onSignInWithEmailAndPassword(
routineResult,
form,
),
);
onError: (error) {
emit(
SignInState(
form: form,
errorMessage: error.message,
status: FormStatus.submissionFailure,
),
);
addError(error);
},
onSuccess: (account, data) {
authenticationRepository.addSession(
SessionWrapper(
event: SignedInEvent(account: account),
session: Session<Data>(
account: account,
data: data,
),
),
);
emit(
SignInState(
form: form,
status: FormStatus.submissionSuccess,
),
);
},
).call();
}
}

View File

@ -17,17 +17,26 @@
import 'dart:async';
import 'package:wyatt_architecture/wyatt_architecture.dart';
import 'package:wyatt_authentication_bloc/src/core/utils/custom_routine.dart';
import 'package:wyatt_authentication_bloc/src/domain/entities/entities.dart';
import 'package:wyatt_authentication_bloc/src/features/sign_in/cubit/sign_in_cubit.dart';
import 'package:wyatt_form_bloc/wyatt_form_bloc.dart';
import 'package:wyatt_type_utils/wyatt_type_utils.dart';
/// Sign in mixin.
///
/// Allows the user to sign in with google
///
/// Gives access to the `signInWithGoogle` method and
/// `onSignInWithGoogle` callback.
mixin SignInWithGoogle<Data> on BaseSignInCubit<Data> {
/// This callback is triggered when a user signs in with google.
FutureOrResult<Data?> onSignInWithGoogle(
Result<Account, AppException> result,
WyattForm form,
);
/// {@macro signin_google}
FutureOr<void> signInWithGoogle() async {
if (state.status.isSubmissionInProgress) {
return;
@ -35,60 +44,39 @@ mixin SignInWithGoogle<Data> on BaseSignInCubit<Data> {
final form = formRepository.accessForm(formName);
emit(SignInState(form: form, status: FormStatus.submissionInProgress));
final result = await authenticationRepository.signInWithGoogle();
// Custom routine
final customRoutineResult = await onSignInWithGoogle(
result,
form,
);
if (customRoutineResult.isErr) {
final error = customRoutineResult.err!;
emit(
SignInState(
form: form,
errorMessage: error.message,
status: FormStatus.submissionFailure,
),
);
addError(error);
}
// Check result
if (result.isErr) {
final error = result.err!;
emit(
SignInState(
form: form,
errorMessage: error.message,
status: FormStatus.submissionFailure,
),
);
addError(error);
}
final account = result.ok!;
final signedInSession = SessionWrapper(
event: SignedInEvent(account: account),
session: Session<Data>(
account: account,
data: customRoutineResult.ok,
return CustomRoutine<Account, Data?>(
routine: authenticationRepository.signInWithGoogle,
attachedLogic: (routineResult) => onSignInWithGoogle(
routineResult,
form,
),
);
authenticationRepository.addSession(signedInSession);
emit(
result.fold(
(value) =>
SignInState(form: form, status: FormStatus.submissionSuccess),
(error) => SignInState(
form: form,
errorMessage: error.message,
status: FormStatus.submissionFailure,
),
),
);
onError: (error) {
emit(
SignInState(
form: form,
errorMessage: error.message,
status: FormStatus.submissionFailure,
),
);
addError(error);
},
onSuccess: (account, data) {
authenticationRepository.addSession(
SessionWrapper(
event: SignedInEvent(account: account),
session: Session<Data>(
account: account,
data: data,
),
),
);
emit(
SignInState(
form: form,
status: FormStatus.submissionSuccess,
),
);
},
).call();
}
}

View File

@ -32,6 +32,8 @@ part 'base_sign_in_cubit.dart';
part 'sign_in_state.dart';
/// Fully featured sign in cubit.
///
/// Sufficient in most cases. (Where fine granularity is not required.)
class SignInCubit<Data> extends BaseSignInCubit<Data>
with
SignInAnonymously<Data>,

View File

@ -16,6 +16,7 @@
part of 'sign_in_cubit.dart';
/// Sign in cubit state to manage the form.
class SignInState extends FormDataState {
const SignInState({
required super.form,

View File

@ -19,7 +19,9 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:wyatt_authentication_bloc/src/features/sign_in/sign_in.dart';
import 'package:wyatt_form_bloc/wyatt_form_bloc.dart';
class SignInListener<Extra> extends StatelessWidget {
/// Widget that listens and builds a child based on the state of
/// the sign in cubit
class SignInListener<Data> extends StatelessWidget {
const SignInListener({
required this.child,
this.onProgress,
@ -41,7 +43,7 @@ class SignInListener<Extra> extends StatelessWidget {
@override
Widget build(BuildContext context) =>
BlocListener<SignInCubit<Extra>, SignInState>(
BlocListener<SignInCubit<Data>, SignInState>(
listener: (context, state) {
if (customBuilder != null) {
return customBuilder!(context, state);

View File

@ -16,6 +16,8 @@
part of 'sign_up_cubit.dart';
/// Abstract sign up cubit useful for implementing a cubit with fine
/// granularity by adding only the required mixins.
abstract class BaseSignUpCubit<Data> extends FormDataCubit<SignUpState> {
BaseSignUpCubit({
required this.authenticationRepository,

View File

@ -17,13 +17,20 @@
import 'dart:async';
import 'package:wyatt_architecture/wyatt_architecture.dart';
import 'package:wyatt_authentication_bloc/src/core/constants/form_field.dart';
import 'package:wyatt_authentication_bloc/src/domain/entities/entities.dart';
import 'package:wyatt_authentication_bloc/src/features/sign_up/cubit/sign_up_cubit.dart';
import 'package:wyatt_authentication_bloc/wyatt_authentication_bloc.dart';
import 'package:wyatt_form_bloc/wyatt_form_bloc.dart';
import 'package:wyatt_type_utils/wyatt_type_utils.dart';
/// Sign up mixin.
///
/// Allows the user to register with an email and a password.
///
/// Gives access to the `signUpWithEmailPassword` method and
/// `onSignUpWithEmailAndPassword` callback.
mixin SignUpWithEmailPassword<Data> on BaseSignUpCubit<Data> {
/// This callback is triggered when a user creates an account.
///
/// For example: when a user sign up in firebase.
FutureOrResult<Data?> onSignUpWithEmailAndPassword(
Result<Account, AppException> result,
WyattForm form,
@ -76,6 +83,7 @@ mixin SignUpWithEmailPassword<Data> on BaseSignUpCubit<Data> {
dataChanged(AuthFormField.password, validator);
}
/// {@macro signup_pwd}
FutureOr<void> signUpWithEmailPassword() async {
if (!state.status.isValidated) {
return;
@ -97,65 +105,42 @@ mixin SignUpWithEmailPassword<Data> on BaseSignUpCubit<Data> {
);
}
final result = await authenticationRepository.signUpWithEmailAndPassword(
email: email!,
password: password!,
);
// Custom routine
final customRoutineResult = await onSignUpWithEmailAndPassword(
result,
form,
);
if (customRoutineResult.isErr) {
final error = customRoutineResult.err!;
emit(
SignUpState(
form: form,
errorMessage: error.message,
status: FormStatus.submissionFailure,
),
);
addError(error);
}
// Check result
if (result.isErr) {
final error = result.err!;
emit(
SignUpState(
form: form,
errorMessage: error.message,
status: FormStatus.submissionFailure,
),
);
addError(error);
}
final account = result.ok!;
final signedUpSession = SessionWrapper(
event: SignedUpEvent(account: account),
session: Session<Data>(
account: account,
data: customRoutineResult.ok,
return CustomRoutine<Account, Data?>(
routine: () => authenticationRepository.signUpWithEmailAndPassword(
email: email!,
password: password!,
),
);
authenticationRepository.addSession(signedUpSession);
emit(
result.fold(
(value) => SignUpState(
form: form,
status: FormStatus.submissionSuccess,
),
(error) => SignUpState(
form: form,
errorMessage: error.message,
status: FormStatus.submissionFailure,
),
attachedLogic: (routineResult) => onSignUpWithEmailAndPassword(
routineResult,
form,
),
);
onError: (error) {
emit(
SignUpState(
form: form,
errorMessage: error.message,
status: FormStatus.submissionFailure,
),
);
addError(error);
},
onSuccess: (account, data) {
authenticationRepository.addSession(
SessionWrapper(
event: SignedUpEvent(account: account),
session: Session<Data>(
account: account,
data: data,
),
),
);
emit(
SignUpState(
form: form,
status: FormStatus.submissionSuccess,
),
);
},
).call();
}
}

View File

@ -28,6 +28,9 @@ import 'package:wyatt_type_utils/wyatt_type_utils.dart';
part 'base_sign_up_cubit.dart';
part 'sign_up_state.dart';
/// Fully featured sign up cubit.
///
/// Sufficient in most cases. (Where fine granularity is not required.)
class SignUpCubit<Data> extends BaseSignUpCubit<Data>
with SignUpWithEmailPassword<Data> {
SignUpCubit({required super.authenticationRepository});

View File

@ -16,6 +16,7 @@
part of 'sign_up_cubit.dart';
/// Sign up cubit state to manage the form.
class SignUpState extends FormDataState {
const SignUpState({
required super.form,

View File

@ -19,7 +19,9 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:wyatt_authentication_bloc/src/features/sign_up/cubit/sign_up_cubit.dart';
import 'package:wyatt_form_bloc/wyatt_form_bloc.dart';
class SignUpListener<Extra> extends StatelessWidget {
/// Widget that listens and builds a child based on the state of
/// the sign up cubit
class SignUpListener<Data> extends StatelessWidget {
const SignUpListener({
required this.child,
this.onProgress,
@ -41,7 +43,7 @@ class SignUpListener<Extra> extends StatelessWidget {
@override
Widget build(BuildContext context) =>
BlocListener<SignUpCubit<Extra>, SignUpState>(
BlocListener<SignUpCubit<Data>, SignUpState>(
listener: (context, state) {
if (customBuilder != null) {
return customBuilder!(context, state);