Repository Data Cleaning
Problem
You need to clean and validate data from GraphQL responses before providing it to the UI, handling missing fields and providing consistent defaults.
Repository Data Cleaning Pattern
Section titled “Repository Data Cleaning Pattern”The repository layer handles data cleaning, validation, and exception handling:
class HotelBookingsRepository with PaginationRepository< PaginatedHotelBookingsEntity, HotelBookingFilterCriteria, HotelBookingSortCriteria > implements QueryableRepository< PaginatedHotelBookingsEntity, HotelBookingsEntity, HotelBookingFilterCriteria, HotelBookingSortCriteria >, WritableRepository<HotelBookingsEntity, HotelBookingsUpsertVars> { const HotelBookingsRepository({required this.datasource});
final HotelBookingsDatasource datasource;
@override Stream<PaginatedHotelBookingsEntity> queryList({ int? skip, int? take, HotelBookingFilterCriteria? filter, HotelBookingSortCriteria? sort, Object? context, }) { return datasource .queryList( skip: skip, take: take, filter: filter?.toGql, sort: sort?.toGql, ) .map((response) => response.data?.hotelbookings) .map(convertToPaginatedHotelBookings); }
@override Stream<HotelBookingsEntity?> queryOne({ required HotelBookingFilterCriteria filter, Object? context, }) { return datasource .queryOne(filter: filter.toGql) .map((response) => response.data?.hotelbookings) .map(convertToHotelBooking); }}Data Conversion with Validation
Section titled “Data Conversion with Validation”Implement robust data conversion with proper error handling:
HotelBookingsEntity? _convertToHotelBookingEntity( GQueryHotelBookingsData_hotelbookings_hotelbookingsItems? booking,) { if (booking == null) return null;
try { final bookingNumber = booking.bookingNumber; final roomNumber = booking.roomNumber; final roomCapacity = booking.roomCapacity;
final validRoomCapacity = (roomCapacity == null || roomCapacity.isEmpty) ? '1' : roomCapacity;
final bookingStatus = booking.bookingStatus?.firstOrNull; final bookingStatusEntity = bookingStatus?.toEntity() ?? HotelBookingStatusEntity( id: 'unknown', title: 'Unknown Status', status: HotelBookingStatusEnum.requested, );
final roomType = booking.roomType?.firstOrNull; final roomTypeEntity = roomType?.toEntity() ?? RoomTypeEntity(id: 'unknown', title: 'Unknown Room Type');
final hotel = booking.hotel?.firstOrNull; final hotelEntity = hotel?.toEntity() ?? HotelEntity(id: 'unknown', title: 'Unknown Hotel', roomTypes: []);
final billingInstruction = booking.billingInstruction?.firstOrNull; final billingInstructionEntity = billingInstruction?.toEntity() ?? BillingInstructionsEntity( id: 'unknown', title: 'Unknown Billing Instruction', );
final bookingFiles = booking.bookingFiles?.toList() ?? []; final bookingFilesEntities = bookingFiles .map( (file) => AttachementEntity( id: file?.id, mediaUrl: file?.mediaUrl, title: file?.title, ), ) .toList();
return HotelBookingsEntity( id: booking.id, title: booking.title, bookingNumber: bookingNumber, roomNumber: roomNumber, bookingStatus: bookingStatusEntity, roomType: roomTypeEntity, guests: booking.guests! .map((guest) => guest!.toEntity()) .whereType<GuestEntity>() .toList(), roomCapacity: validRoomCapacity, checkIn: booking.checkIn?.toDto, checkOut: booking.checkOut?.toDto, billingInstruction: billingInstructionEntity, hotel: hotelEntity, bookingFiles: bookingFilesEntities, nights: booking.checkIn?.toDto?.nights(booking.checkOut?.toDto) ?? 0, guestsToRoommCapacityRatio: booking.guests!.length ~/ (int.tryParse(validRoomCapacity) ?? 1), ); } catch (e) { print('Error converting hotel booking: $e'); return null; }}Mutation Response Cleaning
Section titled “Mutation Response Cleaning”Handle mutation responses with proper validation:
HotelBookingsEntity? convertFromMutationToHotelBooking( GMutateHotelBookingsSaveData_hotelbookingsSave? response,) { if (response == null) return null;
final id = response.id; if (id == null) return null;
try { final roomCapacity = response.roomCapacity; final validRoomCapacity = (roomCapacity == null || roomCapacity.isEmpty) ? '1' : roomCapacity;
final bookingStatus = response.bookingStatus?.firstOrNull; final roomType = response.roomType?.firstOrNull; final hotel = response.hotel?.firstOrNull; final billingInstruction = response.billingInstruction?.firstOrNull; final guests = response.guests?.toList() ?? [];
final bookingFiles = response.bookingFiles?.toList() ?? []; final bookingFilesEntities = bookingFiles .map( (file) => AttachementEntity( id: file?.id, mediaUrl: file?.mediaUrl, title: file?.title, ), ) .toList();
return HotelBookingsEntity( id: id, title: response.title, bookingNumber: response.bookingNumber, roomNumber: response.roomNumber, bookingFiles: bookingFilesEntities, bookingStatus: bookingStatus != null && bookingStatus.id != null && bookingStatus.title != null ? HotelBookingStatusEntity( id: bookingStatus.id!, title: bookingStatus.title!, status: _mapTitleToStatusEnum(bookingStatus.title!), ) : HotelBookingStatusEntity( id: 'unknown', title: 'Unknown Status', status: HotelBookingStatusEnum.requested, ), roomType: roomType != null && roomType.id != null && roomType.title != null ? RoomTypeEntity(id: roomType.id!, title: roomType.title!) : RoomTypeEntity(id: 'unknown', title: 'Unknown Room Type'), guests: guests .map( (guest) => guest != null ? GuestEntity( id: guest.id!, fullName: '${guest.firstName} ${guest.lastName}', firstName: guest.firstName!, lastName: guest.lastName!, jobTitle: guest.jobTitle ?? '', organization: guest.organizationName ?? '', ) : null, ) .whereType<GuestEntity>() .toList(), roomCapacity: validRoomCapacity, checkIn: response.checkIn?.toDto, checkOut: response.checkOut?.toDto, billingInstruction: billingInstruction != null && billingInstruction.id != null && billingInstruction.title != null ? BillingInstructionsEntity( id: billingInstruction.id!, title: billingInstruction.title!, ) : BillingInstructionsEntity( id: 'unknown', title: 'Unknown Billing Instruction', ), hotel: hotel != null && hotel.id != null && hotel.title != null ? HotelEntity( id: hotel.id!, title: hotel.title!, roomTypes: hotel.roomTypes ?.map( (rt) => RoomTypeEntity(id: rt!.id!, title: rt.title!), ) .toList() ?? [], ) : HotelEntity(id: 'unknown', title: 'Unknown Hotel', roomTypes: []), nights: response.checkIn?.toDto?.nights(response.checkOut?.toDto) ?? 0, guestsToRoommCapacityRatio: guests.length ~/ (int.tryParse(validRoomCapacity) ?? 1), notes: response.notes, ); } catch (e) { print('Error converting hotel booking from mutation: $e'); return null; }}Domain Model Extensions
Section titled “Domain Model Extensions”Use extensions for consistent data cleaning:
extension StringExtensions on String? { String get NA { if (this == null || this!.trim().isEmpty) return 'N/A'; return this!; }
String get emptyIfNull { return this ?? ''; }
bool get isNullOrEmpty { return this == null || this!.trim().isEmpty; }}
extension DateTimeExtensions on DateTime? { int nights(DateTime? endDate) { if (this == null || endDate == null) return 0; return endDate.difference(this!).inDays; }
String get formattedDate { if (this == null) return 'N/A'; return DateFormat('MMM dd, yyyy').format(this!); }}
extension ListExtensions on List? { bool get isNullOrEmpty => this == null || this!.isEmpty;
int get safeLength => this?.length ?? 0;}Exception Handling
Section titled “Exception Handling”Implement proper exception handling in repository methods:
@overrideStream<HotelBookingsEntity> upsert({ required HotelBookingsUpsertVars vars, Object? context,}) { return datasource.upsert(vars: vars.toMutationVars).map((response) { final result = convertFromMutationToHotelBooking( response.data?.hotelbookingsSave, );
if (result == null) { throw HotelBookingUpsertException( code: CRUDExceptionCode.NO_DATA, stackTrace: StackTrace.current.toString(), originalException: Exception( 'No data received from server $vars, $result', ), ); }
return result; });}Data Validation Patterns
Section titled “Data Validation Patterns”Common validation patterns for data cleaning:
class DataValidator { static String validateAndCleanString(String? value, String defaultValue) { if (value == null || value.trim().isEmpty) { return defaultValue; } return value.trim(); }
static int validatePositiveInt(int? value, int defaultValue) { if (value == null || value <= 0) { return defaultValue; } return value; }
static List<T> validateList<T>(List<T>? list) { return list?.where((item) => item != null).toList() ?? []; }}Next Steps
Section titled “Next Steps”- Explore repository context patterns