Refactor PersonalInfoRepository to use Firebase Auth for user profile retrieval and remove mock implementation

This commit is contained in:
Achintha Isuru
2026-01-26 20:35:34 -05:00
parent 6915b8b005
commit 098ae1d476
7 changed files with 52 additions and 119 deletions

View File

@@ -1,3 +1,4 @@
import 'package:firebase_auth/firebase_auth.dart';
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,27 +15,36 @@ import '../../domain/repositories/personal_info_repository_interface.dart';
/// - Containing no business logic
class PersonalInfoRepositoryImpl implements PersonalInfoRepositoryInterface {
final ExampleConnector _dataConnect;
final FirebaseAuth _firebaseAuth;
/// Creates a [PersonalInfoRepositoryImpl].
///
/// Requires the Firebase Data Connect connector instance.
/// Requires the Firebase Data Connect connector instance and Firebase Auth.
PersonalInfoRepositoryImpl({
required ExampleConnector dataConnect,
}) : _dataConnect = dataConnect;
required FirebaseAuth firebaseAuth,
}) : _dataConnect = dataConnect,
_firebaseAuth = firebaseAuth;
@override
Future<Staff> getStaffProfile(String staffId) async {
// Query staff data from Firebase Data Connect
final QueryResult<GetStaffByIdData, GetStaffByIdVariables> result =
await _dataConnect.getStaffById(id: staffId).execute();
final staff = result.data.staff;
if (staff == null) {
throw Exception('Staff profile not found for ID: $staffId');
Future<Staff> getStaffProfile() async {
final user = _firebaseAuth.currentUser;
if (user == null) {
throw Exception('User not authenticated');
}
// Query staff data from Firebase Data Connect
final QueryResult<GetStaffByUserIdData, GetStaffByUserIdVariables> result =
await _dataConnect.getStaffByUserId(userId: user.uid).execute();
if (result.data.staffs.isEmpty) {
throw Exception('Staff profile not found for User ID: ${user.uid}');
}
final rawStaff = result.data.staffs.first;
// Map from data_connect DTO to domain entity
return _mapToStaffEntity(staff);
return _mapToStaffEntity(rawStaff);
}
@override
@@ -54,7 +64,7 @@ class PersonalInfoRepositoryImpl implements PersonalInfoRepositoryInterface {
}
// Fetch the updated staff profile to return complete entity
return getStaffProfile(staff.id);
return getStaffProfile();
}
@override
@@ -69,16 +79,22 @@ class PersonalInfoRepositoryImpl implements PersonalInfoRepositoryInterface {
/// Maps a data_connect Staff DTO to a domain Staff entity.
///
/// This mapping isolates the domain from data layer implementation details.
Staff _mapToStaffEntity(GetStaffByIdStaff dto) {
Staff _mapToStaffEntity(GetStaffByUserIdStaffs dto) {
return Staff(
id: dto.id,
authProviderId: dto.userId,
name: dto.fullName,
email: dto.email ?? '',
phone: dto.phone,
status: StaffStatus.active, // TODO: Map from actual status field when available
address: dto.addres,
avatar: dto.photoUrl,
status: StaffStatus.active,
address: dto.addres,
totalShifts: dto.totalShifts,
averageRating: dto.averageRating,
onTimeRate: dto.onTimeRate,
noShowCount: dto.noShowCount,
cancellationCount: dto.cancellationCount,
reliabilityScore: dto.reliabilityScore,
);
}
}

View File

@@ -1,59 +0,0 @@
import 'package:krow_domain/krow_domain.dart';
import '../../domain/repositories/personal_info_repository_interface.dart';
/// Mock implementation of [PersonalInfoRepositoryInterface].
///
/// This mock repository returns hardcoded data for development
/// and will be replaced with [PersonalInfoRepositoryImpl] when
/// Firebase Data Connect is fully configured.
///
/// Following Clean Architecture, this mock:
/// - Implements the domain repository interface
/// - Returns domain entities (Staff)
/// - Simulates async operations with delays
/// - Provides realistic test data
class PersonalInfoRepositoryMock implements PersonalInfoRepositoryInterface {
// Simulated in-memory storage
Staff? _cachedStaff;
@override
Future<Staff> getStaffProfile(String staffId) async {
// Simulate network delay
await Future.delayed(const Duration(milliseconds: 500));
// Return cached staff or create mock data
return _cachedStaff ??
const Staff(
id: 'mock-staff-1',
authProviderId: 'mock-auth-1',
name: 'Krower',
email: 'worker@krow.com',
phone: '',
status: StaffStatus.active,
address: 'Montreal, Quebec',
avatar: null,
);
}
@override
Future<Staff> updateStaffProfile(Staff staff) async {
// Simulate network delay
await Future.delayed(const Duration(milliseconds: 800));
// Store in cache
_cachedStaff = staff;
// Return the updated staff
return staff;
}
@override
Future<String> uploadProfilePhoto(String filePath) async {
// Simulate upload delay
await Future.delayed(const Duration(seconds: 2));
// Return a mock URL
return 'https://example.com/photos/${DateTime.now().millisecondsSinceEpoch}.jpg';
}
}

View File

@@ -8,10 +8,10 @@ import 'package:krow_domain/krow_domain.dart';
/// Implementations must delegate all data operations through
/// the data_connect layer, following Clean Architecture principles.
abstract interface class PersonalInfoRepositoryInterface {
/// Retrieves the staff profile for the specified staff ID.
/// Retrieves the staff profile for the current authenticated user.
///
/// Returns the complete [Staff] entity with all profile information.
Future<Staff> getStaffProfile(String staffId);
Future<Staff> getStaffProfile();
/// Updates the staff profile information.
///

View File

@@ -2,23 +2,12 @@ import 'package:krow_core/core.dart';
import 'package:krow_domain/krow_domain.dart';
import '../repositories/personal_info_repository_interface.dart';
/// Arguments for getting staff profile information.
class GetPersonalInfoArguments extends UseCaseArgument {
/// The staff member's ID.
final String staffId;
const GetPersonalInfoArguments({required this.staffId});
@override
List<Object?> get props => [staffId];
}
/// Use case for retrieving staff profile information.
///
/// This use case fetches the complete staff profile from the repository,
/// which delegates to the data_connect layer for data access.
class GetPersonalInfoUseCase
implements UseCase<GetPersonalInfoArguments, Staff> {
implements NoInputUseCase<Staff> {
final PersonalInfoRepositoryInterface _repository;
/// Creates a [GetPersonalInfoUseCase].
@@ -27,7 +16,7 @@ class GetPersonalInfoUseCase
GetPersonalInfoUseCase(this._repository);
@override
Future<Staff> call(GetPersonalInfoArguments arguments) {
return _repository.getStaffProfile(arguments.staffId);
Future<Staff> call() {
return _repository.getStaffProfile();
}
}

View File

@@ -2,23 +2,12 @@ import 'package:krow_core/core.dart';
import 'package:krow_domain/krow_domain.dart';
import '../repositories/personal_info_repository_interface.dart';
/// Arguments for updating staff profile information.
class UpdatePersonalInfoArguments extends UseCaseArgument {
/// The staff entity with updated information.
final Staff staff;
const UpdatePersonalInfoArguments({required this.staff});
@override
List<Object?> get props => [staff];
}
/// Use case for updating staff profile information.
///
/// This use case updates the staff profile information
/// through the repository, which delegates to the data_connect layer.
class UpdatePersonalInfoUseCase
implements UseCase<UpdatePersonalInfoArguments, Staff> {
implements UseCase<Staff, Staff> {
final PersonalInfoRepositoryInterface _repository;
/// Creates an [UpdatePersonalInfoUseCase].
@@ -27,7 +16,7 @@ class UpdatePersonalInfoUseCase
UpdatePersonalInfoUseCase(this._repository);
@override
Future<Staff> call(UpdatePersonalInfoArguments arguments) {
return _repository.updateStaffProfile(arguments.staff);
Future<Staff> call(Staff arguments) {
return _repository.updateStaffProfile(arguments);
}
}

View File

@@ -1,5 +1,6 @@
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/usecases/get_personal_info_usecase.dart';
@@ -16,18 +17,15 @@ class PersonalInfoBloc extends Bloc<PersonalInfoEvent, PersonalInfoState>
implements Disposable {
final GetPersonalInfoUseCase _getPersonalInfoUseCase;
final UpdatePersonalInfoUseCase _updatePersonalInfoUseCase;
final String _staffId;
/// Creates a [PersonalInfoBloc].
///
/// Requires the staff ID to load and update the correct profile.
/// Requires the use cases to load and update the profile.
PersonalInfoBloc({
required GetPersonalInfoUseCase getPersonalInfoUseCase,
required UpdatePersonalInfoUseCase updatePersonalInfoUseCase,
required String staffId,
}) : _getPersonalInfoUseCase = getPersonalInfoUseCase,
_updatePersonalInfoUseCase = updatePersonalInfoUseCase,
_staffId = staffId,
super(const PersonalInfoState()) {
on<PersonalInfoLoadRequested>(_onLoadRequested);
on<PersonalInfoFieldUpdated>(_onFieldUpdated);
@@ -42,9 +40,7 @@ class PersonalInfoBloc extends Bloc<PersonalInfoEvent, PersonalInfoState>
) async {
emit(state.copyWith(status: PersonalInfoStatus.loading));
try {
final Staff staff = await _getPersonalInfoUseCase(
GetPersonalInfoArguments(staffId: _staffId),
);
final Staff staff = await _getPersonalInfoUseCase();
emit(state.copyWith(
status: PersonalInfoStatus.loaded,
staff: staff,
@@ -115,7 +111,7 @@ class PersonalInfoBloc extends Bloc<PersonalInfoEvent, PersonalInfoState>
emit(state.copyWith(status: PersonalInfoStatus.saving));
try {
final Staff updatedStaff = await _updatePersonalInfoUseCase(
UpdatePersonalInfoArguments(staff: state.staff!),
state.staff!,
);
emit(state.copyWith(
status: PersonalInfoStatus.saved,

View File

@@ -1,7 +1,9 @@
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:flutter_modular/flutter_modular.dart';
import 'package:krow_data_connect/krow_data_connect.dart';
import 'data/repositories/personal_info_repository_mock.dart';
import 'data/repositories/personal_info_repository_impl.dart';
import 'domain/repositories/personal_info_repository_interface.dart';
import 'domain/usecases/get_personal_info_usecase.dart';
import 'domain/usecases/update_personal_info_usecase.dart';
@@ -14,17 +16,19 @@ import 'presentation/pages/personal_info_page.dart';
/// personal information functionality following Clean Architecture.
///
/// The module:
/// - Registers repository implementations (mock for now, will use real impl later)
/// - Registers repository implementations
/// - Registers use cases that contain business logic
/// - Registers BLoC for state management
/// - Defines routes for navigation
class StaffProfileInfoModule extends Module {
@override
void binds(Injector i) {
// Repository - using mock for now
// TODO: Replace with PersonalInfoRepositoryImpl when Firebase Data Connect is configured
// Repository
i.addLazySingleton<PersonalInfoRepositoryInterface>(
PersonalInfoRepositoryMock.new,
() => PersonalInfoRepositoryImpl(
dataConnect: ExampleConnector.instance,
firebaseAuth: FirebaseAuth.instance,
),
);
// Use Cases - delegate business logic to repository
@@ -36,12 +40,10 @@ class StaffProfileInfoModule extends Module {
);
// BLoC - manages presentation state
// TODO: Get actual staffId from authentication state
i.addLazySingleton<PersonalInfoBloc>(
() => PersonalInfoBloc(
getPersonalInfoUseCase: i.get<GetPersonalInfoUseCase>(),
updatePersonalInfoUseCase: i.get<UpdatePersonalInfoUseCase>(),
staffId: 'mock-staff-1', // TODO: Get from auth
),
);
}