feat(mobile): implement centralized error handling and project cleanup

- Implemented centralized error handling system (#377)
- Unified UIErrorSnackbar and BlocErrorHandler mixin
- Migrated ClientAuthBloc and ClientHubsBloc
- Consolidated documentation
- Addresses Mobile Apps: Project Cleanup (#378)
This commit is contained in:
2026-02-05 15:35:35 +05:30
parent 6dc700f226
commit 3924801f70
11 changed files with 783 additions and 213 deletions

View File

@@ -1,6 +1,5 @@
import 'dart:developer' as developer;
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:krow_core/core.dart';
import 'package:krow_domain/krow_domain.dart';
import '../../domain/arguments/sign_in_with_email_arguments.dart';
@@ -24,7 +23,8 @@ import 'client_auth_state.dart';
/// * Business Account Registration
/// * Social Authentication
/// * Session Termination
class ClientAuthBloc extends Bloc<ClientAuthEvent, ClientAuthState> {
class ClientAuthBloc extends Bloc<ClientAuthEvent, ClientAuthState>
with BlocErrorHandler<ClientAuthState> {
final SignInWithEmailUseCase _signInWithEmail;
final SignUpWithEmailUseCase _signUpWithEmail;
final SignInWithSocialUseCase _signInWithSocial;
@@ -53,28 +53,20 @@ class ClientAuthBloc extends Bloc<ClientAuthEvent, ClientAuthState> {
Emitter<ClientAuthState> emit,
) async {
emit(state.copyWith(status: ClientAuthStatus.loading));
try {
final User user = await _signInWithEmail(
SignInWithEmailArguments(email: event.email, password: event.password),
);
emit(state.copyWith(status: ClientAuthStatus.authenticated, user: user));
} on AppException catch (e) {
developer.log('Error ${e.code}: ${e.technicalMessage}', name: 'ClientAuthBloc');
emit(
state.copyWith(
status: ClientAuthStatus.error,
errorMessage: e.messageKey,
),
);
} catch (e) {
developer.log('Unexpected error: $e', name: 'ClientAuthBloc');
emit(
state.copyWith(
status: ClientAuthStatus.error,
errorMessage: 'errors.generic.unknown',
),
);
}
await handleError(
emit: emit,
action: () async {
final user = await _signInWithEmail(
SignInWithEmailArguments(email: event.email, password: event.password),
);
emit(state.copyWith(status: ClientAuthStatus.authenticated, user: user));
},
onError: (errorKey) => state.copyWith(
status: ClientAuthStatus.error,
errorMessage: errorKey,
),
);
}
/// Handles the [ClientSignUpRequested] event.
@@ -83,32 +75,24 @@ class ClientAuthBloc extends Bloc<ClientAuthEvent, ClientAuthState> {
Emitter<ClientAuthState> emit,
) async {
emit(state.copyWith(status: ClientAuthStatus.loading));
try {
final User user = await _signUpWithEmail(
SignUpWithEmailArguments(
companyName: event.companyName,
email: event.email,
password: event.password,
),
);
emit(state.copyWith(status: ClientAuthStatus.authenticated, user: user));
} on AppException catch (e) {
developer.log('Error ${e.code}: ${e.technicalMessage}', name: 'ClientAuthBloc');
emit(
state.copyWith(
status: ClientAuthStatus.error,
errorMessage: e.messageKey,
),
);
} catch (e) {
developer.log('Unexpected error: $e', name: 'ClientAuthBloc');
emit(
state.copyWith(
status: ClientAuthStatus.error,
errorMessage: 'errors.generic.unknown',
),
);
}
await handleError(
emit: emit,
action: () async {
final user = await _signUpWithEmail(
SignUpWithEmailArguments(
companyName: event.companyName,
email: event.email,
password: event.password,
),
);
emit(state.copyWith(status: ClientAuthStatus.authenticated, user: user));
},
onError: (errorKey) => state.copyWith(
status: ClientAuthStatus.error,
errorMessage: errorKey,
),
);
}
/// Handles the [ClientSocialSignInRequested] event.
@@ -117,28 +101,20 @@ class ClientAuthBloc extends Bloc<ClientAuthEvent, ClientAuthState> {
Emitter<ClientAuthState> emit,
) async {
emit(state.copyWith(status: ClientAuthStatus.loading));
try {
final User user = await _signInWithSocial(
SignInWithSocialArguments(provider: event.provider),
);
emit(state.copyWith(status: ClientAuthStatus.authenticated, user: user));
} on AppException catch (e) {
developer.log('Error ${e.code}: ${e.technicalMessage}', name: 'ClientAuthBloc');
emit(
state.copyWith(
status: ClientAuthStatus.error,
errorMessage: e.messageKey,
),
);
} catch (e) {
developer.log('Unexpected error: $e', name: 'ClientAuthBloc');
emit(
state.copyWith(
status: ClientAuthStatus.error,
errorMessage: 'errors.generic.unknown',
),
);
}
await handleError(
emit: emit,
action: () async {
final user = await _signInWithSocial(
SignInWithSocialArguments(provider: event.provider),
);
emit(state.copyWith(status: ClientAuthStatus.authenticated, user: user));
},
onError: (errorKey) => state.copyWith(
status: ClientAuthStatus.error,
errorMessage: errorKey,
),
);
}
/// Handles the [ClientSignOutRequested] event.
@@ -147,25 +123,17 @@ class ClientAuthBloc extends Bloc<ClientAuthEvent, ClientAuthState> {
Emitter<ClientAuthState> emit,
) async {
emit(state.copyWith(status: ClientAuthStatus.loading));
try {
await _signOut();
emit(state.copyWith(status: ClientAuthStatus.signedOut, user: null));
} on AppException catch (e) {
developer.log('Error ${e.code}: ${e.technicalMessage}', name: 'ClientAuthBloc');
emit(
state.copyWith(
status: ClientAuthStatus.error,
errorMessage: e.messageKey,
),
);
} catch (e) {
developer.log('Unexpected error: $e', name: 'ClientAuthBloc');
emit(
state.copyWith(
status: ClientAuthStatus.error,
errorMessage: 'errors.generic.unknown',
),
);
}
await handleError(
emit: emit,
action: () async {
await _signOut();
emit(state.copyWith(status: ClientAuthStatus.signedOut, user: null));
},
onError: (errorKey) => state.copyWith(
status: ClientAuthStatus.error,
errorMessage: errorKey,
),
);
}
}

View File

@@ -1,7 +1,6 @@
import 'dart:developer' as developer;
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_modular/flutter_modular.dart';
import 'package:krow_core/core.dart';
import 'package:krow_domain/krow_domain.dart';
import '../../domain/arguments/assign_nfc_tag_arguments.dart';
import '../../domain/arguments/create_hub_arguments.dart';
@@ -18,6 +17,7 @@ import 'client_hubs_state.dart';
/// It orchestrates the flow between the UI and the domain layer by invoking
/// specific use cases for fetching, creating, deleting, and assigning tags to hubs.
class ClientHubsBloc extends Bloc<ClientHubsEvent, ClientHubsState>
with BlocErrorHandler<ClientHubsState>
implements Disposable {
final GetHubsUseCase _getHubsUseCase;
final CreateHubUseCase _createHubUseCase;
@@ -66,26 +66,18 @@ class ClientHubsBloc extends Bloc<ClientHubsEvent, ClientHubsState>
Emitter<ClientHubsState> emit,
) async {
emit(state.copyWith(status: ClientHubsStatus.loading));
try {
final List<Hub> hubs = await _getHubsUseCase();
emit(state.copyWith(status: ClientHubsStatus.success, hubs: hubs));
} on AppException catch (e) {
developer.log('Error ${e.code}: ${e.technicalMessage}', name: 'ClientHubsBloc');
emit(
state.copyWith(
status: ClientHubsStatus.failure,
errorMessage: e.messageKey,
),
);
} catch (e) {
developer.log('Unexpected error: $e', name: 'ClientHubsBloc');
emit(
state.copyWith(
status: ClientHubsStatus.failure,
errorMessage: 'errors.generic.unknown',
),
);
}
await handleError(
emit: emit,
action: () async {
final hubs = await _getHubsUseCase();
emit(state.copyWith(status: ClientHubsStatus.success, hubs: hubs));
},
onError: (errorKey) => state.copyWith(
status: ClientHubsStatus.failure,
errorMessage: errorKey,
),
);
}
Future<void> _onAddRequested(
@@ -93,47 +85,39 @@ class ClientHubsBloc extends Bloc<ClientHubsEvent, ClientHubsState>
Emitter<ClientHubsState> emit,
) async {
emit(state.copyWith(status: ClientHubsStatus.actionInProgress));
try {
await _createHubUseCase(
CreateHubArguments(
name: event.name,
address: event.address,
placeId: event.placeId,
latitude: event.latitude,
longitude: event.longitude,
city: event.city,
state: event.state,
street: event.street,
country: event.country,
zipCode: event.zipCode,
),
);
final List<Hub> hubs = await _getHubsUseCase();
emit(
state.copyWith(
status: ClientHubsStatus.actionSuccess,
hubs: hubs,
successMessage: 'Hub created successfully',
showAddHubDialog: false,
),
);
} on AppException catch (e) {
developer.log('Error ${e.code}: ${e.technicalMessage}', name: 'ClientHubsBloc');
emit(
state.copyWith(
status: ClientHubsStatus.actionFailure,
errorMessage: e.messageKey,
),
);
} catch (e) {
developer.log('Unexpected error: $e', name: 'ClientHubsBloc');
emit(
state.copyWith(
status: ClientHubsStatus.actionFailure,
errorMessage: 'errors.generic.unknown',
),
);
}
await handleError(
emit: emit,
action: () async {
await _createHubUseCase(
CreateHubArguments(
name: event.name,
address: event.address,
placeId: event.placeId,
latitude: event.latitude,
longitude: event.longitude,
city: event.city,
state: event.state,
street: event.street,
country: event.country,
zipCode: event.zipCode,
),
);
final hubs = await _getHubsUseCase();
emit(
state.copyWith(
status: ClientHubsStatus.actionSuccess,
hubs: hubs,
successMessage: 'Hub created successfully',
showAddHubDialog: false,
),
);
},
onError: (errorKey) => state.copyWith(
status: ClientHubsStatus.actionFailure,
errorMessage: errorKey,
),
);
}
Future<void> _onDeleteRequested(
@@ -141,33 +125,25 @@ class ClientHubsBloc extends Bloc<ClientHubsEvent, ClientHubsState>
Emitter<ClientHubsState> emit,
) async {
emit(state.copyWith(status: ClientHubsStatus.actionInProgress));
try {
await _deleteHubUseCase(DeleteHubArguments(hubId: event.hubId));
final List<Hub> hubs = await _getHubsUseCase();
emit(
state.copyWith(
status: ClientHubsStatus.actionSuccess,
hubs: hubs,
successMessage: 'Hub deleted successfully',
),
);
} on AppException catch (e) {
developer.log('Error ${e.code}: ${e.technicalMessage}', name: 'ClientHubsBloc');
emit(
state.copyWith(
status: ClientHubsStatus.actionFailure,
errorMessage: e.messageKey,
),
);
} catch (e) {
developer.log('Unexpected error: $e', name: 'ClientHubsBloc');
emit(
state.copyWith(
status: ClientHubsStatus.actionFailure,
errorMessage: 'errors.generic.unknown',
),
);
}
await handleError(
emit: emit,
action: () async {
await _deleteHubUseCase(DeleteHubArguments(hubId: event.hubId));
final hubs = await _getHubsUseCase();
emit(
state.copyWith(
status: ClientHubsStatus.actionSuccess,
hubs: hubs,
successMessage: 'Hub deleted successfully',
),
);
},
onError: (errorKey) => state.copyWith(
status: ClientHubsStatus.actionFailure,
errorMessage: errorKey,
),
);
}
Future<void> _onNfcTagAssignRequested(
@@ -175,36 +151,28 @@ class ClientHubsBloc extends Bloc<ClientHubsEvent, ClientHubsState>
Emitter<ClientHubsState> emit,
) async {
emit(state.copyWith(status: ClientHubsStatus.actionInProgress));
try {
await _assignNfcTagUseCase(
AssignNfcTagArguments(hubId: event.hubId, nfcTagId: event.nfcTagId),
);
final List<Hub> hubs = await _getHubsUseCase();
emit(
state.copyWith(
status: ClientHubsStatus.actionSuccess,
hubs: hubs,
successMessage: 'NFC tag assigned successfully',
clearHubToIdentify: true,
),
);
} on AppException catch (e) {
developer.log('Error ${e.code}: ${e.technicalMessage}', name: 'ClientHubsBloc');
emit(
state.copyWith(
status: ClientHubsStatus.actionFailure,
errorMessage: e.messageKey,
),
);
} catch (e) {
developer.log('Unexpected error: $e', name: 'ClientHubsBloc');
emit(
state.copyWith(
status: ClientHubsStatus.actionFailure,
errorMessage: 'errors.generic.unknown',
),
);
}
await handleError(
emit: emit,
action: () async {
await _assignNfcTagUseCase(
AssignNfcTagArguments(hubId: event.hubId, nfcTagId: event.nfcTagId),
);
final hubs = await _getHubsUseCase();
emit(
state.copyWith(
status: ClientHubsStatus.actionSuccess,
hubs: hubs,
successMessage: 'NFC tag assigned successfully',
clearHubToIdentify: true,
),
);
},
onError: (errorKey) => state.copyWith(
status: ClientHubsStatus.actionFailure,
errorMessage: errorKey,
),
);
}
void _onMessageCleared(