refactor: centralize data connect error handling and resolve build issues across applications

This commit addresses several critical issues across the mobile monorepo:

1. Centralized Error Handling: Integrated DataErrorHandler mixin into all repository implementations, ensuring consistent mapping of Data Connect exceptions to domain AppExceptions.
2. Build Stabilization: Fixed numerous type mismatches, parameter signature errors in widgets (e.g., google_places_flutter itemBuilder), and naming conflicts (StaffSession, FirebaseAuth).
3. Code Quality: Applied 'dart fix' across all modified packages and manually cleared debug print statements and UI clutter.
4. Mono-repo alignment: Standardized Data Connect usage and aliasing ('dc.') for better maintainability.

Signed-off-by: Suriya <suriya@tenext.in>
This commit is contained in:
2026-02-06 13:28:57 +05:30
parent e0636e46a3
commit 5e7bf0d5c0
150 changed files with 1506 additions and 2547 deletions

View File

@@ -1,4 +1,4 @@
import 'package:firebase_auth/firebase_auth.dart';
import 'package:firebase_auth/firebase_auth.dart' as firebase_auth;
import 'package:firebase_data_connect/firebase_data_connect.dart';
import 'package:krow_data_connect/krow_data_connect.dart';
import 'package:krow_domain/krow_domain.dart';
@@ -14,21 +14,21 @@ import '../../domain/repositories/personal_info_repository_interface.dart';
/// - Mapping between data_connect DTOs and domain entities
/// - Containing no business logic
class PersonalInfoRepositoryImpl implements PersonalInfoRepositoryInterface {
final ExampleConnector _dataConnect;
final FirebaseAuth _firebaseAuth;
/// Creates a [PersonalInfoRepositoryImpl].
///
/// Requires the Firebase Data Connect connector instance and Firebase Auth.
PersonalInfoRepositoryImpl({
required ExampleConnector dataConnect,
required FirebaseAuth firebaseAuth,
required firebase_auth.FirebaseAuth firebaseAuth,
}) : _dataConnect = dataConnect,
_firebaseAuth = firebaseAuth;
final ExampleConnector _dataConnect;
final firebase_auth.FirebaseAuth _firebaseAuth;
@override
Future<Staff> getStaffProfile() async {
final user = _firebaseAuth.currentUser;
final firebase_auth.User? user = _firebaseAuth.currentUser;
if (user == null) {
throw Exception('User not authenticated');
}
@@ -41,7 +41,7 @@ class PersonalInfoRepositoryImpl implements PersonalInfoRepositoryInterface {
throw Exception('Staff profile not found for User ID: ${user.uid}');
}
final rawStaff = result.data.staffs.first;
final GetStaffByUserIdStaffs rawStaff = result.data.staffs.first;
// Map from data_connect DTO to domain entity
return _mapToStaffEntity(rawStaff);
@@ -50,7 +50,7 @@ class PersonalInfoRepositoryImpl implements PersonalInfoRepositoryInterface {
@override
Future<Staff> updateStaffProfile({required String staffId, required Map<String, dynamic> data}) async {
// Start building the update mutation
var updateBuilder = _dataConnect.updateStaff(id: staffId);
UpdateStaffVariablesBuilder updateBuilder = _dataConnect.updateStaff(id: staffId);
// Apply updates from map if present
if (data.containsKey('name')) {

View File

@@ -8,12 +8,12 @@ import '../repositories/personal_info_repository_interface.dart';
/// which delegates to the data_connect layer for data access.
class GetPersonalInfoUseCase
implements NoInputUseCase<Staff> {
final PersonalInfoRepositoryInterface _repository;
/// Creates a [GetPersonalInfoUseCase].
///
/// Requires a [PersonalInfoRepositoryInterface] to fetch data.
GetPersonalInfoUseCase(this._repository);
final PersonalInfoRepositoryInterface _repository;
@override
Future<Staff> call() {

View File

@@ -4,19 +4,19 @@ import '../repositories/personal_info_repository_interface.dart';
/// Arguments for updating staff profile information.
class UpdatePersonalInfoParams extends UseCaseArgument {
const UpdatePersonalInfoParams({
required this.staffId,
required this.data,
});
/// The staff member's ID.
final String staffId;
/// The fields to update.
final Map<String, dynamic> data;
const UpdatePersonalInfoParams({
required this.staffId,
required this.data,
});
@override
List<Object?> get props => [staffId, data];
List<Object?> get props => <Object?>[staffId, data];
}
/// Use case for updating staff profile information.
@@ -25,12 +25,12 @@ class UpdatePersonalInfoParams extends UseCaseArgument {
/// through the repository, which delegates to the data_connect layer.
class UpdatePersonalInfoUseCase
implements UseCase<UpdatePersonalInfoParams, Staff> {
final PersonalInfoRepositoryInterface _repository;
/// Creates an [UpdatePersonalInfoUseCase].
///
/// Requires a [PersonalInfoRepositoryInterface] to update data.
UpdatePersonalInfoUseCase(this._repository);
final PersonalInfoRepositoryInterface _repository;
@override
Future<Staff> call(UpdatePersonalInfoParams params) {

View File

@@ -14,8 +14,6 @@ import 'personal_info_state.dart';
/// use cases following Clean Architecture principles.
class PersonalInfoBloc extends Bloc<PersonalInfoEvent, PersonalInfoState>
implements Disposable {
final GetPersonalInfoUseCase _getPersonalInfoUseCase;
final UpdatePersonalInfoUseCase _updatePersonalInfoUseCase;
/// Creates a [PersonalInfoBloc].
///
@@ -33,6 +31,8 @@ class PersonalInfoBloc extends Bloc<PersonalInfoEvent, PersonalInfoState>
add(const PersonalInfoLoadRequested());
}
final GetPersonalInfoUseCase _getPersonalInfoUseCase;
final UpdatePersonalInfoUseCase _updatePersonalInfoUseCase;
/// Handles loading staff profile information.
Future<void> _onLoadRequested(
@@ -45,13 +45,13 @@ class PersonalInfoBloc extends Bloc<PersonalInfoEvent, PersonalInfoState>
// Initialize form values from staff entity
// Note: Staff entity currently stores address as a string, but we want to map it to 'preferredLocations'
final Map<String, dynamic> initialValues = {
final Map<String, dynamic> initialValues = <String, dynamic>{
'name': staff.name,
'email': staff.email,
'phone': staff.phone,
'preferredLocations': staff.address != null
? [staff.address]
: [], // TODO: Map correctly when Staff entity supports list
? <String?>[staff.address]
: <dynamic>[], // TODO: Map correctly when Staff entity supports list
'avatar': staff.avatar,
};
@@ -95,13 +95,13 @@ class PersonalInfoBloc extends Bloc<PersonalInfoEvent, PersonalInfoState>
);
// Update local state with the returned staff and keep form values in sync
final Map<String, dynamic> newValues = {
final Map<String, dynamic> newValues = <String, dynamic>{
'name': updatedStaff.name,
'email': updatedStaff.email,
'phone': updatedStaff.phone,
'preferredLocations': updatedStaff.address != null
? [updatedStaff.address]
: [],
? <String?>[updatedStaff.address]
: <dynamic>[],
'avatar': updatedStaff.avatar,
};

View File

@@ -5,7 +5,7 @@ abstract class PersonalInfoEvent extends Equatable {
const PersonalInfoEvent();
@override
List<Object?> get props => [];
List<Object?> get props => <Object?>[];
}
/// Event to load personal information.
@@ -15,16 +15,16 @@ class PersonalInfoLoadRequested extends PersonalInfoEvent {
/// Event to update a field value.
class PersonalInfoFieldChanged extends PersonalInfoEvent {
final String field;
final dynamic value;
const PersonalInfoFieldChanged({
required this.field,
required this.value,
});
final String field;
final dynamic value;
@override
List<Object?> get props => [field, value];
List<Object?> get props => <Object?>[field, value];
}
/// Event to submit the form.
@@ -34,9 +34,9 @@ class PersonalInfoFormSubmitted extends PersonalInfoEvent {
/// Event when an address is selected from autocomplete.
class PersonalInfoAddressSelected extends PersonalInfoEvent {
final String address;
const PersonalInfoAddressSelected(this.address);
final String address;
@override
List<Object?> get props => [address];
List<Object?> get props => <Object?>[address];
}

View File

@@ -29,6 +29,21 @@ enum PersonalInfoStatus {
///
/// Uses the shared [Staff] entity from the domain layer.
class PersonalInfoState extends Equatable {
/// Creates a [PersonalInfoState].
const PersonalInfoState({
this.status = PersonalInfoStatus.initial,
this.staff,
this.formValues = const <String, dynamic>{},
this.errorMessage,
});
/// Initial state.
const PersonalInfoState.initial()
: status = PersonalInfoStatus.initial,
staff = null,
formValues = const <String, dynamic>{},
errorMessage = null;
/// The current status of the operation.
final PersonalInfoStatus status;
@@ -41,21 +56,6 @@ class PersonalInfoState extends Equatable {
/// Error message if an error occurred.
final String? errorMessage;
/// Creates a [PersonalInfoState].
const PersonalInfoState({
this.status = PersonalInfoStatus.initial,
this.staff,
this.formValues = const {},
this.errorMessage,
});
/// Initial state.
const PersonalInfoState.initial()
: status = PersonalInfoStatus.initial,
staff = null,
formValues = const {},
errorMessage = null;
/// Creates a copy of this state with the given fields replaced.
PersonalInfoState copyWith({
PersonalInfoStatus? status,
@@ -72,5 +72,5 @@ class PersonalInfoState extends Equatable {
}
@override
List<Object?> get props => [status, staff, formValues, errorMessage];
List<Object?> get props => <Object?>[status, staff, formValues, errorMessage];
}

View File

@@ -18,14 +18,14 @@ import 'save_button.dart';
/// following Clean Architecture's separation of concerns principle and the design system guidelines.
/// Works with the shared [Staff] entity from the domain layer.
class PersonalInfoContent extends StatefulWidget {
/// The staff profile to display and edit.
final Staff staff;
/// Creates a [PersonalInfoContent].
const PersonalInfoContent({
super.key,
required this.staff,
});
/// The staff profile to display and edit.
final Staff staff;
@override
State<PersonalInfoContent> createState() => _PersonalInfoContentState();
@@ -81,8 +81,8 @@ class _PersonalInfoContentState extends State<PersonalInfoContent> {
// The backend expects List<AnyValue> (JSON/List) for preferredLocations
final List<String> locations = _locationsController.text
.split(',')
.map((e) => e.trim())
.where((e) => e.isNotEmpty)
.map((String e) => e.trim())
.where((String e) => e.isNotEmpty)
.toList();
context.read<PersonalInfoBloc>().add(

View File

@@ -9,6 +9,17 @@ import 'package:design_system/design_system.dart';
/// and editable fields for phone and address.
/// Uses only design system tokens for colors, typography, and spacing.
class PersonalInfoForm extends StatelessWidget {
/// Creates a [PersonalInfoForm].
const PersonalInfoForm({
super.key,
required this.fullName,
required this.email,
required this.emailController,
required this.phoneController,
required this.locationsController,
this.enabled = true,
});
/// The staff member's full name (read-only).
final String fullName;
@@ -27,17 +38,6 @@ class PersonalInfoForm extends StatelessWidget {
/// Whether the form fields are enabled for editing.
final bool enabled;
/// Creates a [PersonalInfoForm].
const PersonalInfoForm({
super.key,
required this.fullName,
required this.email,
required this.emailController,
required this.phoneController,
required this.locationsController,
this.enabled = true,
});
@override
Widget build(BuildContext context) {
final TranslationsStaffOnboardingPersonalInfoEn i18n = t.staff.onboarding.personal_info;
@@ -57,7 +57,7 @@ class PersonalInfoForm extends StatelessWidget {
hint: i18n.email_label,
enabled: enabled,
keyboardType: TextInputType.emailAddress,
autofillHints: const [AutofillHints.email],
autofillHints: const <String>[AutofillHints.email],
),
const SizedBox(height: UiConstants.space4),
@@ -85,9 +85,9 @@ class PersonalInfoForm extends StatelessWidget {
/// A label widget for form fields.
/// A label widget for form fields.
class _FieldLabel extends StatelessWidget {
final String text;
const _FieldLabel({required this.text});
final String text;
@override
Widget build(BuildContext context) {
@@ -101,9 +101,9 @@ class _FieldLabel extends StatelessWidget {
/// A read-only field widget for displaying non-editable information.
/// A read-only field widget for displaying non-editable information.
class _ReadOnlyField extends StatelessWidget {
final String value;
const _ReadOnlyField({required this.value});
final String value;
@override
Widget build(BuildContext context) {
@@ -129,11 +129,6 @@ class _ReadOnlyField extends StatelessWidget {
/// An editable text field widget.
/// An editable text field widget.
class _EditableField extends StatelessWidget {
final TextEditingController controller;
final String hint;
final bool enabled;
final TextInputType? keyboardType;
final Iterable<String>? autofillHints;
const _EditableField({
required this.controller,
@@ -142,6 +137,11 @@ class _EditableField extends StatelessWidget {
this.keyboardType,
this.autofillHints,
});
final TextEditingController controller;
final String hint;
final bool enabled;
final TextInputType? keyboardType;
final Iterable<String>? autofillHints;
@override
Widget build(BuildContext context) {

View File

@@ -9,14 +9,6 @@ import 'package:design_system/design_system.dart';
/// Includes a camera icon button for changing the photo.
/// Uses only design system tokens for colors, typography, and spacing.
class ProfilePhotoWidget extends StatelessWidget {
/// The URL of the staff member's photo.
final String? photoUrl;
/// The staff member's full name (used for initial avatar).
final String fullName;
/// Callback when the photo/camera button is tapped.
final VoidCallback? onTap;
/// Creates a [ProfilePhotoWidget].
const ProfilePhotoWidget({
@@ -25,6 +17,14 @@ class ProfilePhotoWidget extends StatelessWidget {
required this.fullName,
required this.onTap,
});
/// The URL of the staff member's photo.
final String? photoUrl;
/// The staff member's full name (used for initial avatar).
final String fullName;
/// Callback when the photo/camera button is tapped.
final VoidCallback? onTap;
@override
Widget build(BuildContext context) {

View File

@@ -7,14 +7,6 @@ import 'package:design_system/design_system.dart';
/// Displays a full-width button with a save icon and customizable label.
/// Uses only design system tokens for colors, typography, and spacing.
class SaveButton extends StatelessWidget {
/// Callback when the button is pressed.
final VoidCallback? onPressed;
/// The button label text.
final String label;
/// Whether to show a loading indicator.
final bool isLoading;
/// Creates a [SaveButton].
const SaveButton({
@@ -23,6 +15,14 @@ class SaveButton extends StatelessWidget {
required this.label,
this.isLoading = false,
});
/// Callback when the button is pressed.
final VoidCallback? onPressed;
/// The button label text.
final String label;
/// Whether to show a loading indicator.
final bool isLoading;
@override
Widget build(BuildContext context) {

View File

@@ -1,2 +1,3 @@
/// Export the modular feature definition.
library;
export 'src/staff_profile_info_module.dart';

View File

@@ -28,6 +28,8 @@ dependencies:
krow_data_connect:
path: ../../../../../data_connect
firebase_auth: any
firebase_data_connect: any
dev_dependencies:
flutter_test:
sdk: flutter