feat(authentication): remove session wrapper for AuthenticationSession

This commit is contained in:
Hugo Pointcheval 2023-02-08 13:37:52 +01:00
parent 2e5aafdb5a
commit b458f8c0c8
Signed by: hugo
GPG Key ID: 3AAC487E131E00BC
32 changed files with 319 additions and 262 deletions

View File

@ -37,7 +37,7 @@ class ExampleAuthenticationCubit extends AuthenticationCubit<int> {
}
@override
FutureOrResult<int?> onSignInFromCache(SessionWrapper<int> wrapper) {
FutureOrResult<int?> onSignInFromCache(AuthenticationSession<int> session) {
print('onSignInFromCache');
return const Ok(1);

View File

@ -30,8 +30,7 @@ class HomePage extends StatelessWidget {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(
'Home | ${context.account<AuthenticationCubit<int>, int>()?.email}'),
title: Text('Home | ${context.watchAccount<int>()?.email}'),
actions: [
IconButton(
onPressed: () =>
@ -44,8 +43,8 @@ class HomePage extends StatelessWidget {
child: ListView(
children: [
AuthenticationBuilder<int>(
authenticated: (context, sessionWrapper) => Text(
'Logged as ${sessionWrapper.session?.account.email} | GeneratedId is ${sessionWrapper.session?.data}'),
authenticated: (context, session) => Text(
'Logged as ${session.account?.email} | GeneratedId is ${session.data}'),
unauthenticated: (context) =>
const Text('Not logged (unauthenticated)'),
unknown: (context) => const Text('Not logged (unknown)'),

View File

@ -17,26 +17,82 @@
import 'package:flutter/widgets.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:wyatt_authentication_bloc/src/domain/entities/account.dart';
import 'package:wyatt_authentication_bloc/src/domain/entities/session.dart';
import 'package:wyatt_authentication_bloc/src/domain/entities/session_wrapper.dart';
import 'package:wyatt_authentication_bloc/src/domain/entities/auth_session.dart';
import 'package:wyatt_authentication_bloc/src/domain/entities/authentication_change_event/authentication_change_event.dart';
import 'package:wyatt_authentication_bloc/src/features/authentication/cubit/authentication_cubit.dart';
/// Extension that helps to quickly access useful resources like wrapper,
/// session, account or data.
extension BuildContextExtension on BuildContext {
/// Returns session wrapper
SessionWrapper<Data>? wrapper<T extends AuthenticationCubit<Data>, Data>() =>
watch<T>().currentSession();
/// Read session in context from a specific AuthenticationCubit type [T]
AuthenticationSession<Data>?
readSessionFrom<T extends AuthenticationCubit<Data>, Data>() =>
read<T>().currentSession();
/// Returns session
Session<Data>? session<T extends AuthenticationCubit<Data>, Data>() =>
watch<T>().currentSession()?.session;
/// Watch session in context from a specific AuthenticationCubit type [T]
AuthenticationSession<Data>?
watchSessionFrom<T extends AuthenticationCubit<Data>, Data>() =>
watch<T>().currentSession();
/// Returns account
Account? account<T extends AuthenticationCubit<Data>, Data>() =>
watch<T>().currentSession()?.session?.account;
/// Read session in context from generic AuthenticationCubit type
AuthenticationSession<Data>? readSession<Data>() =>
read<AuthenticationCubit<Data>>().currentSession();
/// Returns associated data
Data? data<T extends AuthenticationCubit<Data>, Data>() =>
watch<T>().currentSession()?.session?.data;
/// Watch session in context from generic AuthenticationCubit type
AuthenticationSession<Data>? watchSession<Data>() =>
watch<AuthenticationCubit<Data>>().currentSession();
/// Read event in context from a specific AuthenticationCubit type [T]
AuthenticationChangeEvent?
readEventFrom<T extends AuthenticationCubit<Data>, Data>() =>
readSessionFrom<T, Data>()?.latestEvent;
/// Watch event in context from a specific AuthenticationCubit type [T]
AuthenticationChangeEvent?
watchEventFrom<T extends AuthenticationCubit<Data>, Data>() =>
watchSessionFrom<T, Data>()?.latestEvent;
/// Read event in context from generic AuthenticationCubit type
AuthenticationChangeEvent? readEvent<Data>() =>
readSession<Data>()?.latestEvent;
/// Watch event in context from generic AuthenticationCubit type
AuthenticationChangeEvent? watchEvent<Data>() =>
watchSession<Data>()?.latestEvent;
/// Read account in context from a specific AuthenticationCubit type [T]
Account? readAccountFrom<T extends AuthenticationCubit<Data>, Data>() =>
readSessionFrom<T, Data>()?.account;
/// Watch account in context from a specific AuthenticationCubit type [T]
Account? watchAccountFrom<T extends AuthenticationCubit<Data>, Data>() =>
watchSessionFrom<T, Data>()?.account;
/// Read account in context from generic AuthenticationCubit type
Account? readAccount<Data>() => readSession<Data>()?.account;
/// Watch account in context from generic AuthenticationCubit type
Account? watchAccount<Data>() => watchSession<Data>()?.account;
/// Read data in context from a specific AuthenticationCubit type [T]
Data? readDataFrom<T extends AuthenticationCubit<Data>, Data>() =>
readSessionFrom<T, Data>()?.data;
/// Watch data in context from a specific AuthenticationCubit type [T]
Data? watchDataFrom<T extends AuthenticationCubit<Data>, Data>() =>
watchSessionFrom<T, Data>()?.data;
/// Read data in context from generic AuthenticationCubit type
Data? readData<Data>() => readSession<Data>()?.data;
/// Watch data in context from generic AuthenticationCubit type
Data? watchData<Data>() => watchSession<Data>()?.data;
/// Check if user is authenticated from a
/// specific AuthenticationCubit type [T]
bool isAuthenticatedFrom<T extends AuthenticationCubit<Data>, Data>() =>
readEventFrom<T, Data>() is AuthenticatedChangeEvent;
/// Check if user is authenticated from generic AuthenticationCubit type
bool isAuthenticated<Data>() => readEvent<Data>() is AuthenticatedChangeEvent;
}

View File

@ -23,8 +23,8 @@ import 'package:wyatt_authentication_bloc/src/core/exceptions/exceptions.dart';
import 'package:wyatt_authentication_bloc/src/data/models/models.dart';
import 'package:wyatt_authentication_bloc/src/domain/data_sources/remote/authentication_remote_data_source.dart';
import 'package:wyatt_authentication_bloc/src/domain/entities/account.dart';
import 'package:wyatt_authentication_bloc/src/domain/entities/auth_session.dart';
import 'package:wyatt_authentication_bloc/src/domain/entities/authentication_change_event/authentication_change_event.dart';
import 'package:wyatt_authentication_bloc/src/domain/entities/session_wrapper.dart';
import 'package:wyatt_type_utils/wyatt_type_utils.dart';
class AuthenticationFirebaseDataSourceImpl<Data>
@ -41,7 +41,7 @@ class AuthenticationFirebaseDataSourceImpl<Data>
_checkForCachedAccount();
}
late StreamController<SessionWrapper<Data>> _sessionStream;
late StreamController<AuthenticationSession<Data>> _sessionStream;
late StreamController<UserCredential?> _latestCredentials;
final FirebaseAuth _firebaseAuth;
@ -51,8 +51,11 @@ class AuthenticationFirebaseDataSourceImpl<Data>
final currentUser = _firebaseAuth.currentUser;
if (currentUser == null) {
_sessionStream
.add(const SessionWrapper(event: UnknownAuthenticationEvent()));
_sessionStream.add(
const AuthenticationSession(
latestEvent: UnknownAuthenticationEvent(),
),
);
return;
}
@ -62,7 +65,9 @@ class AuthenticationFirebaseDataSourceImpl<Data>
accessToken: jwt,
);
_sessionStream.add(
SessionWrapper(event: SignedInFromCacheEvent(account: currentAccount)),
AuthenticationSession.fromEvent(
SignedInFromCacheEvent(account: currentAccount),
),
);
return;
}
@ -78,19 +83,23 @@ class AuthenticationFirebaseDataSourceImpl<Data>
return account;
}
// Stream related methods ===================================================
// Session related methods ===================================================
/// {@macro add_session}
@override
void addSession(SessionWrapper<Data> wrapper) {
_sessionStream.add(wrapper);
void addSession(AuthenticationSession<Data> session) {
_sessionStream.add(session);
}
/// {@macro session_stream}
@override
Stream<SessionWrapper<Data>> sessionStream() =>
Stream<AuthenticationSession<Data>> sessionStream() =>
_sessionStream.stream.asBroadcastStream();
/// {@macro current_session}
@override
Future<AuthenticationSession<Data>> currentSession() => sessionStream().last;
// SignUp/SignIn methods ====================================================
/// {@macro signup_pwd}

View File

@ -18,7 +18,7 @@ import 'package:wyatt_architecture/wyatt_architecture.dart';
import 'package:wyatt_authentication_bloc/src/core/utils/forms.dart';
import 'package:wyatt_authentication_bloc/src/domain/data_sources/remote/authentication_remote_data_source.dart';
import 'package:wyatt_authentication_bloc/src/domain/entities/account.dart';
import 'package:wyatt_authentication_bloc/src/domain/entities/session_wrapper.dart';
import 'package:wyatt_authentication_bloc/src/domain/entities/auth_session.dart';
import 'package:wyatt_authentication_bloc/src/domain/repositories/authentication_repository.dart';
import 'package:wyatt_form_bloc/wyatt_form_bloc.dart';
import 'package:wyatt_type_utils/wyatt_type_utils.dart';
@ -73,18 +73,30 @@ class AuthenticationRepositoryImpl<Data extends Object>
@override
FormRepository get formRepository => _formRepository;
// Stream related methods ===================================================
// Session related methods ===================================================
/// {@macro add_session}
@override
void addSession(SessionWrapper<Data> wrapper) =>
authenticationRemoteDataSource.addSession(wrapper);
void addSession(AuthenticationSession<Data> session) =>
authenticationRemoteDataSource.addSession(session);
/// {@macro session_stream}
@override
Stream<SessionWrapper<Data>> sessionStream() =>
Stream<AuthenticationSession<Data>> sessionStream() =>
authenticationRemoteDataSource.sessionStream();
/// {@macro current_session}
@override
FutureOrResult<AuthenticationSession<Data>> currentSession() =>
Result.tryCatchAsync<AuthenticationSession<Data>, AppException,
AppException>(
() async {
final session = await authenticationRemoteDataSource.currentSession();
return session;
},
(error) => error,
);
// SignUp/SignIn methods ====================================================
/// {@macro signup_pwd}

View File

@ -16,16 +16,16 @@
import 'package:wyatt_architecture/wyatt_architecture.dart';
import 'package:wyatt_authentication_bloc/src/domain/entities/account.dart';
import 'package:wyatt_authentication_bloc/src/domain/entities/session_wrapper.dart';
import 'package:wyatt_authentication_bloc/src/domain/entities/auth_session.dart';
/// Is responsible for abstracting the provenance of the data.
abstract class AuthenticationRemoteDataSource<Data>
extends BaseRemoteDataSource {
// Stream related methods ===================================================
// Session related methods ===================================================
void addSession(SessionWrapper<Data> wrapper);
Stream<SessionWrapper<Data>> sessionStream();
void addSession(AuthenticationSession<Data> session);
Stream<AuthenticationSession<Data>> sessionStream();
Future<AuthenticationSession<Data>> currentSession();
// SignUp/SignIn methods ====================================================

View File

@ -0,0 +1,60 @@
// Copyright (C) 2023 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import 'package:equatable/equatable.dart';
import 'package:wyatt_authentication_bloc/wyatt_authentication_bloc.dart';
/// The [AuthenticationSession] object is used to transport and propagate
/// the last event issued by an authentication state change, a user account
/// if connected, and the associated data.
class AuthenticationSession<Data> extends Equatable {
const AuthenticationSession({
required this.latestEvent,
this.account,
this.data,
});
factory AuthenticationSession.fromEvent(
AuthenticationChangeEvent latestEvent, {
Data? data,
}) {
if (latestEvent is AuthenticatedChangeEvent) {
return AuthenticationSession(
latestEvent: latestEvent,
account: latestEvent.account,
data: data,
);
}
return AuthenticationSession(
latestEvent: latestEvent,
data: data,
);
}
final AuthenticationChangeEvent latestEvent;
final Account? account;
final Data? data;
@override
List<Object?> get props => [
latestEvent,
account,
data,
];
@override
bool? get stringify => true;
}

View File

@ -14,23 +14,15 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import 'package:equatable/equatable.dart';
import 'package:wyatt_authentication_bloc/src/domain/entities/account.dart';
part of 'authentication_change_event.dart';
/// Represents every event where user is authenticated.
abstract class AuthenticatedChangeEvent extends AuthenticationChangeEvent {
const AuthenticatedChangeEvent({required this.account});
/// The [Session] object is used to transport and propagate
/// the connected user [Account] and personalized [Data] in the application.
class Session<Data> extends Equatable {
const Session({
required this.account,
this.data,
});
final Account account;
final Data? data;
@override
List<Object?> get props => [account, data];
@override
bool? get stringify => true;
List<Object?> get props => [account];
}

View File

@ -18,15 +18,16 @@ import 'package:equatable/equatable.dart';
import 'package:wyatt_architecture/wyatt_architecture.dart';
import 'package:wyatt_authentication_bloc/src/domain/entities/account.dart';
part 'authenticated_change_event.dart';
part 'deleted_event.dart';
part 'reauthenticated_event.dart';
part 'refreshed_event.dart';
part 'signed_in_event.dart';
part 'signed_in_from_cache_event.dart';
part 'signed_out_event.dart';
part 'signed_up_event.dart';
part 'refreshed_event.dart';
part 'reauthenticated_event.dart';
part 'updated_event.dart';
part 'unknown_authentication_event.dart';
part 'signed_in_from_cache_event.dart';
part 'deleted_event.dart';
part 'updated_event.dart';
/// Represents an event initiated by a change in
/// the user's authentication status.

View File

@ -16,14 +16,9 @@
part of 'authentication_change_event.dart';
/// When a user re-authenticates (from the logged in state to the
/// logged in state with a different and fresh access
/// When a user re-authenticates (from the logged in state to the
/// logged in state with a different and fresh access
/// token and a different login time)
class ReauthenticatedEvent extends AuthenticationChangeEvent {
const ReauthenticatedEvent({required this.account});
final Account account;
@override
List<Object?> get props => [account];
class ReauthenticatedEvent extends AuthenticatedChangeEvent {
const ReauthenticatedEvent({required super.account});
}

View File

@ -16,13 +16,8 @@
part of 'authentication_change_event.dart';
/// When a user access token is refreshed (from the logged in state to the
/// When a user access token is refreshed (from the logged in state to the
/// logged in state with a different access token)
class RefreshedEvent extends AuthenticationChangeEvent {
const RefreshedEvent({required this.account});
final Account account;
@override
List<Object?> get props => [account];
class RefreshedEvent extends AuthenticatedChangeEvent {
const RefreshedEvent({required super.account});
}

View File

@ -17,11 +17,6 @@
part of 'authentication_change_event.dart';
/// When a user authenticates (from not logged in to logged in).
class SignedInEvent extends AuthenticationChangeEvent {
const SignedInEvent({required this.account});
final Account account;
@override
List<Object?> get props => [account];
class SignedInEvent extends AuthenticatedChangeEvent {
const SignedInEvent({required super.account});
}

View File

@ -17,11 +17,6 @@
part of 'authentication_change_event.dart';
/// When a user authenticates automatically (from not logged in to logged in).
class SignedInFromCacheEvent extends AuthenticationChangeEvent {
const SignedInFromCacheEvent({required this.account});
final Account account;
@override
List<Object?> get props => [account];
class SignedInFromCacheEvent extends AuthenticatedChangeEvent {
const SignedInFromCacheEvent({required super.account});
}

View File

@ -17,11 +17,6 @@
part of 'authentication_change_event.dart';
/// When a user creates an account.
class SignedUpEvent extends AuthenticationChangeEvent {
const SignedUpEvent({required this.account});
final Account account;
@override
List<Object?> get props => [account];
class SignedUpEvent extends AuthenticatedChangeEvent {
const SignedUpEvent({required super.account});
}

View File

@ -17,11 +17,6 @@
part of 'authentication_change_event.dart';
/// When the user's account has been updated.
class UpdatedEvent extends AuthenticationChangeEvent {
const UpdatedEvent({required this.account});
final Account account;
@override
List<Object?> get props => [account];
class UpdatedEvent extends AuthenticatedChangeEvent {
const UpdatedEvent({required super.account});
}

View File

@ -15,6 +15,5 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
export 'account.dart';
export 'auth_session.dart';
export 'authentication_change_event/authentication_change_event.dart';
export 'session.dart';
export 'session_wrapper.dart';

View File

@ -1,38 +0,0 @@
// Copyright (C) 2023 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import 'package:equatable/equatable.dart';
import 'package:wyatt_architecture/wyatt_architecture.dart';
import 'package:wyatt_authentication_bloc/src/domain/entities/authentication_change_event/authentication_change_event.dart';
import 'package:wyatt_authentication_bloc/src/domain/entities/session.dart';
/// Contains the [AuthenticationChangeEvent] initiating the state
/// change and the current [Session].
class SessionWrapper<Data> extends Equatable implements Entity {
const SessionWrapper({
required this.event,
this.session,
});
final AuthenticationChangeEvent event;
final Session<Data>? session;
@override
List<Object?> get props => [event, session];
@override
bool get stringify => true;
}

View File

@ -16,7 +16,7 @@
import 'package:wyatt_architecture/wyatt_architecture.dart';
import 'package:wyatt_authentication_bloc/src/domain/entities/account.dart';
import 'package:wyatt_authentication_bloc/src/domain/entities/session_wrapper.dart';
import 'package:wyatt_authentication_bloc/src/domain/entities/auth_session.dart';
import 'package:wyatt_form_bloc/wyatt_form_bloc.dart';
abstract class AuthenticationRepository<Data> extends BaseRepository {
@ -30,12 +30,17 @@ abstract class AuthenticationRepository<Data> extends BaseRepository {
/// {@template add_session}
/// Add a new authentication event.
/// {@endtemplate}
void addSession(SessionWrapper<Data> wrapper);
void addSession(AuthenticationSession<Data> session);
/// {@template session_stream}
/// Authentication state change event stream.
/// {@endtemplate}
Stream<SessionWrapper<Data>> sessionStream();
Stream<AuthenticationSession<Data>> sessionStream();
/// {@template current_session}
/// Latest session issued by the session stream.
/// {@endtemplate}
FutureOrResult<AuthenticationSession<Data>> currentSession();
// SignUp/SignIn methods ====================================================

View File

@ -17,7 +17,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:wyatt_authentication_bloc/src/core/enums/authentication_status.dart';
import 'package:wyatt_authentication_bloc/src/domain/entities/session_wrapper.dart';
import 'package:wyatt_authentication_bloc/src/domain/entities/auth_session.dart';
import 'package:wyatt_authentication_bloc/src/features/authentication/cubit/authentication_cubit.dart';
class AuthenticationBuilder<Data> extends StatelessWidget {
@ -30,7 +30,7 @@ class AuthenticationBuilder<Data> extends StatelessWidget {
final Widget Function(
BuildContext context,
SessionWrapper<Data> sessionWrapper,
AuthenticationSession<Data> session,
) authenticated;
final Widget Function(BuildContext context) unauthenticated;
final Widget Function(BuildContext context) unknown;
@ -40,8 +40,8 @@ class AuthenticationBuilder<Data> extends StatelessWidget {
BlocBuilder<AuthenticationCubit<Data>, AuthenticationState<Data>>(
builder: (context, state) {
if (state.status == AuthenticationStatus.authenticated) {
if (state.wrapper != null) {
return authenticated(context, state.wrapper!);
if (state.session != null) {
return authenticated(context, state.session!);
} else {
return unauthenticated(context);
}

View File

@ -42,35 +42,31 @@ abstract class AuthenticationCubit<Data>
}
final AuthenticationRepository<Data> authenticationRepository;
SessionWrapper<Data>? _latestSession;
AuthenticationSession<Data>? _latestSession;
void _listenForAuthenticationChanges() {
authenticationRepository.sessionStream().asyncMap((wrapper) async {
final event = wrapper.event;
authenticationRepository.sessionStream().asyncMap((session) async {
final event = session.latestEvent;
if (event is SignedInFromCacheEvent) {
final customRoutineResult = await onSignInFromCache(wrapper);
final customRoutineResult = await onSignInFromCache(session);
if (customRoutineResult.isOk) {
final account = event.account;
final sessionData = customRoutineResult.ok;
final signedInSession = SessionWrapper(
event: SignedInFromCacheEvent(account: account),
session: Session<Data>(
account: account,
data: sessionData,
),
final signedInSession = AuthenticationSession.fromEvent(
SignedInFromCacheEvent(account: account),
data: sessionData,
);
return signedInSession;
}
}
return wrapper;
}).listen((wrapper) async {
_latestSession = wrapper;
final session = wrapper.session;
if (session != null) {
emit(AuthenticationState<Data>.authenticated(wrapper));
return session;
}).listen((session) async {
_latestSession = session;
if (session.account != null) {
emit(AuthenticationState<Data>.authenticated(session));
return;
}
emit(AuthenticationState<Data>.unauthenticated());
@ -80,19 +76,16 @@ abstract class AuthenticationCubit<Data>
/// {@macro refresh}
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,
routine: authenticationRepository.refresh,
attachedLogic: onRefresh,
onError: addError,
onSuccess: (result, data) => authenticationRepository.addSession(
AuthenticationSession.fromEvent(
RefreshedEvent(account: result),
data: data,
),
),
),
).call();
).call();
/// {@macro reauthenticate}
FutureOr<void> reauthenticate() async => CustomRoutine<Account, Data?>(
@ -100,12 +93,9 @@ abstract class AuthenticationCubit<Data>
attachedLogic: onReauthenticate,
onError: addError,
onSuccess: (result, data) => authenticationRepository.addSession(
SessionWrapper(
event: ReauthenticatedEvent(account: result),
session: Session<Data>(
account: result,
data: data,
),
AuthenticationSession.fromEvent(
ReauthenticatedEvent(account: result),
data: data,
),
),
).call();
@ -115,8 +105,11 @@ abstract class AuthenticationCubit<Data>
routine: authenticationRepository.signOut,
attachedLogic: (routineResult) => onSignOut(),
onError: addError,
onSuccess: (result, data) => authenticationRepository
.addSession(SessionWrapper<Data>(event: const SignedOutEvent())),
onSuccess: (result, data) => authenticationRepository.addSession(
const AuthenticationSession(
latestEvent: SignedOutEvent(),
),
),
).call();
/// {@macro delete}
@ -124,20 +117,23 @@ abstract class AuthenticationCubit<Data>
routine: authenticationRepository.delete,
attachedLogic: (routineResult) => onDelete(),
onError: addError,
onSuccess: (result, data) => authenticationRepository
.addSession(SessionWrapper<Data>(event: const DeletedEvent())),
onSuccess: (result, data) => authenticationRepository.addSession(
const AuthenticationSession(
latestEvent: DeletedEvent(),
),
),
).call();
/// Returns latest session wrapper.
/// Returns latest session.
///
/// Contains latest event and latest session data (account + extra data)
SessionWrapper<Data>? currentSession() => _latestSession;
AuthenticationSession<Data>? currentSession() => _latestSession;
/// This callback is triggered when the user is automaticcaly logged in from
/// the cache.
///
/// For example: when the user is sign in from the Firebase cache.
FutureOrResult<Data?> onSignInFromCache(SessionWrapper<Data> wrapper);
FutureOrResult<Data?> onSignInFromCache(AuthenticationSession<Data> session);
/// This callback is triggered when the account is refreshed.
///

View File

@ -17,25 +17,25 @@
part of 'authentication_cubit.dart';
class AuthenticationState<Data> extends Equatable {
const AuthenticationState._(this.status, this.wrapper);
const AuthenticationState._(this.status, this.session);
const AuthenticationState.unauthenticated()
: this._(AuthenticationStatus.unauthenticated, null);
const AuthenticationState.authenticated(SessionWrapper<Data> sessionWrapper)
const AuthenticationState.authenticated(AuthenticationSession<Data> session)
: this._(
AuthenticationStatus.authenticated,
sessionWrapper,
session,
);
const AuthenticationState.unknown()
: this._(AuthenticationStatus.unknown, null);
final AuthenticationStatus status;
final SessionWrapper<Data>? wrapper;
final AuthenticationSession<Data>? session;
@override
List<Object?> get props => [status, wrapper];
List<Object?> get props => [status, session];
@override
bool? get stringify => true;

View File

@ -111,12 +111,9 @@ mixin UpdateEmail<Data> on BaseEditAccountCubit<Data> {
},
onSuccess: (account, data) {
authenticationRepository.addSession(
SessionWrapper(
event: UpdatedEvent(account: account),
session: Session<Data>(
account: account,
data: data,
),
AuthenticationSession.fromEvent(
UpdatedEvent(account: account),
data: data,
),
);
emit(

View File

@ -110,12 +110,9 @@ mixin UpdatePassword<Data> on BaseEditAccountCubit<Data> {
},
onSuccess: (account, data) {
authenticationRepository.addSession(
SessionWrapper(
event: SignedInEvent(account: account),
session: Session<Data>(
account: account,
data: data,
),
AuthenticationSession.fromEvent(
UpdatedEvent(account: account),
data: data,
),
);
emit(

View File

@ -31,12 +31,13 @@ class EmailVerificationCubit<Data> extends Cubit<EmailVerificationState> {
final AuthenticationRepository<Data> authenticationRepository;
FutureOr<void> sendEmailVerification() async {
emit(state.copyWith(status: FormStatus.submissionInProgress));
emit(const EmailVerificationState(status: FormStatus.submissionInProgress));
final response = await authenticationRepository.sendEmailVerification();
emit(
response.fold(
(value) => state.copyWith(status: FormStatus.submissionSuccess),
(error) => state.copyWith(
(value) =>
const EmailVerificationState(status: FormStatus.submissionSuccess),
(error) => EmailVerificationState(
errorMessage: error.message,
status: FormStatus.submissionFailure,
),
@ -45,7 +46,7 @@ class EmailVerificationCubit<Data> extends Cubit<EmailVerificationState> {
}
FutureOr<void> checkEmailVerification() async {
emit(state.copyWith(status: FormStatus.submissionInProgress));
emit(const EmailVerificationState(status: FormStatus.submissionInProgress));
final refresh = await authenticationRepository.refresh();
if (refresh.isErr) {
@ -59,11 +60,11 @@ class EmailVerificationCubit<Data> extends Cubit<EmailVerificationState> {
return;
}
final wrapper = await authenticationRepository.sessionStream().last;
final currentAccount = wrapper.session?.account;
final session = await authenticationRepository.currentSession();
final currentAccount = session.ok?.account;
if (currentAccount != null) {
emit(
state.copyWith(
EmailVerificationState(
isVerified: currentAccount.emailVerified,
status: FormStatus.submissionSuccess,
),

View File

@ -1,4 +1,3 @@
// ignore_for_file: public_member_api_docs, sort_constructors_first
// Copyright (C) 2023 WYATT GROUP
// Please see the AUTHORS file for details.
//
@ -18,16 +17,16 @@
part of 'email_verification_cubit.dart';
class EmailVerificationState extends Equatable {
final FormStatus status;
final bool isVerified;
final String? errorMessage;
const EmailVerificationState({
this.isVerified = false,
this.status = FormStatus.pure,
this.errorMessage,
});
final FormStatus status;
final bool isVerified;
final String? errorMessage;
EmailVerificationState copyWith({
FormStatus? status,
bool? isVerified,

View File

@ -63,12 +63,9 @@ mixin SignInAnonymously<Data> on BaseSignInCubit<Data> {
},
onSuccess: (account, data) {
authenticationRepository.addSession(
SessionWrapper(
event: SignedInEvent(account: account),
session: Session<Data>(
account: account,
data: data,
),
AuthenticationSession.fromEvent(
SignedInEvent(account: account),
data: data,
),
);
emit(

View File

@ -136,12 +136,9 @@ mixin SignInWithEmailPassword<Data> on BaseSignInCubit<Data> {
},
onSuccess: (account, data) {
authenticationRepository.addSession(
SessionWrapper(
event: SignedInEvent(account: account),
session: Session<Data>(
account: account,
data: data,
),
AuthenticationSession.fromEvent(
SignedInEvent(account: account),
data: data,
),
);
emit(

View File

@ -62,12 +62,9 @@ mixin SignInWithGoogle<Data> on BaseSignInCubit<Data> {
},
onSuccess: (account, data) {
authenticationRepository.addSession(
SessionWrapper(
event: SignedInEvent(account: account),
session: Session<Data>(
account: account,
data: data,
),
AuthenticationSession.fromEvent(
SignedInEvent(account: account),
data: data,
),
);
emit(

View File

@ -126,12 +126,9 @@ mixin SignUpWithEmailPassword<Data> on BaseSignUpCubit<Data> {
},
onSuccess: (account, data) {
authenticationRepository.addSession(
SessionWrapper(
event: SignedUpEvent(account: account),
session: Session<Data>(
account: account,
data: data,
),
AuthenticationSession.fromEvent(
SignedUpEvent(account: account),
data: data,
),
);
emit(

View File

@ -43,7 +43,9 @@ class TestAuthenticationCubit extends AuthenticationCubit<int> {
const Ok(null);
@override
FutureOrResult<int?> onSignInFromCache(SessionWrapper<int> wrapper) async =>
FutureOrResult<int?> onSignInFromCache(
AuthenticationSession<int> session,
) async =>
const Ok(null);
@override
@ -53,9 +55,10 @@ class TestAuthenticationCubit extends AuthenticationCubit<int> {
void main() {
group('AuthenticationCubit<T>', () {
final MockAccount account = MockAccount();
final SessionWrapper<int> wrapper = SessionWrapper(
event: const UnknownAuthenticationEvent(),
session: Session(account: account, data: 10),
final AuthenticationSession<int> session = AuthenticationSession<int>(
latestEvent: const UnknownAuthenticationEvent(),
account: account,
data: 10,
);
late AuthenticationRepository<int> authenticationRepository;
@ -80,14 +83,14 @@ void main() {
'emits authenticated when stream contains session',
setUp: () {
when(() => authenticationRepository.sessionStream()).thenAnswer(
(_) => Stream.fromIterable([wrapper]),
(_) => Stream.fromIterable([session]),
);
},
build: () => TestAuthenticationCubit(
authenticationRepository: authenticationRepository,
),
seed: () => const AuthenticationState.unknown(),
expect: () => [AuthenticationState<int>.authenticated(wrapper)],
expect: () => [AuthenticationState<int>.authenticated(session)],
);
blocTest<AuthenticationCubit<int>, AuthenticationState<int>>(
@ -95,7 +98,8 @@ void main() {
setUp: () {
when(() => authenticationRepository.sessionStream()).thenAnswer(
(_) => Stream.fromIterable(
[const SessionWrapper(event: SignedOutEvent())],),
[const AuthenticationSession(latestEvent: SignedOutEvent())],
),
);
},
build: () => TestAuthenticationCubit(

View File

@ -27,7 +27,7 @@ void main() {
const AuthenticationState<void> state =
AuthenticationState.unauthenticated();
expect(state.status, AuthenticationStatus.unauthenticated);
expect(state.wrapper, null);
expect(state.session, null);
});
});
@ -36,13 +36,12 @@ void main() {
final MockAccount account = MockAccount();
final AuthenticationState<void> state =
AuthenticationState.authenticated(
SessionWrapper<void>(
event: SignedInEvent(account: account),
session: Session(account: account),
AuthenticationSession.fromEvent(
SignedInEvent(account: account),
),
);
expect(state.status, AuthenticationStatus.authenticated);
expect(state.wrapper?.session?.account, account);
expect(state.session?.account, account);
});
});
@ -52,14 +51,14 @@ void main() {
const String extra = 'AwesomeExtraData';
final AuthenticationState<String> state =
AuthenticationState.authenticated(
SessionWrapper<String>(
event: SignedInEvent(account: account),
session: Session(account: account, data: extra),
AuthenticationSession.fromEvent(
SignedInEvent(account: account),
data: extra,
),
);
expect(state.status, AuthenticationStatus.authenticated);
expect(state.wrapper?.session?.account, account);
expect(state.wrapper?.session?.data, extra);
expect(state.session?.account, account);
expect(state.session?.data, extra);
});
});
});

View File

@ -43,13 +43,24 @@ void main() {
when(() => authenticationRepository.sessionStream()).thenAnswer(
(_) => Stream.fromIterable([
SessionWrapper<int>(
event: SignedInFromCacheEvent(account: account),
session: Session<int>(account: account, data: 10),
AuthenticationSession.fromEvent(
SignedInFromCacheEvent(account: account),
data: 10,
)
]),
);
when(
() => authenticationRepository.currentSession(),
).thenAnswer(
(_) async => Ok(
AuthenticationSession.fromEvent(
SignedInFromCacheEvent(account: account),
data: 10,
),
),
);
when(
() => authenticationRepository.refresh(),
).thenAnswer((_) async => Ok(account));