Skip to content

Repository Data Cleaning

Raw Model to Domain Model

Repositories are responsible for transforming raw GQL models into clean domain models using Convertor-based model convertors.

Data cleaning is the process of transforming GQL raw models (which may contain nulls, inconsistent types, and GQL-specific structures) into clean domain models ready for UI consumption. This is done in the repository layer using model convertors.

@riverpod
Convertor<AttendeeModel, GQueryAttendeeData_attendee_attendeeItems>
attendeeModelConvertor(Ref ref) {
return Convertor((raw) {
return AttendeeModel(
id: raw.id ?? '', // Null -> empty string
fullName: raw.fullName ?? 'Unknown', // Null -> default value
email: raw.email ?? '',
jobTitle: raw.jobTitle ?? '',
avatarUrl: raw.avatar?.mediaUrl, // Nested null-safe access
createdAt: raw.createdAt ?? DateTime.now(), // Null -> current time
tags: raw.tags?.nonNulls // Filter nulls from lists
.map((t) => t.name ?? '')
.where((name) => name.isNotEmpty)
.toList() ?? [],
);
});
}

For single items, use .thenMap():

Stream<AttendeeModel> queryItem({AttendeeFilterCriteria? filter}) {
final params = SingleRequestParams<GattendeeFilters>(
filter: filterConvertor.mightExecute(filter),
);
return itemDatasource
.map((item) {
if (item == null) throw AttendeeNotFoundException();
return item;
})
.thenMap(modelConvertor)
.execute(params);
}

For lists, use .thenEach():

Stream<List<AttendeeModel>> queryList({
int? skip, int? take,
AttendeeFilterCriteria? filter,
AttendeeSortCriteria? sort,
}) {
final params = ListRequestParams<GattendeeFilters, GattendeeOrder>(
skip: skip, take: take,
filter: filterConvertor.mightExecute(filter),
sort: sortConvertor.mightExecute(sort),
);
return listDatasource.thenEach(modelConvertor).execute(params);
}

The model convertor handles:

ConcernExample
Null handlingraw.name ?? 'Unknown'
Default valuesraw.count ?? 0
Type conversionDateTime.parse(raw.dateString)
Nested flatteningraw.organization?.name ?? ''
List null filteringraw.items?.nonNulls.toList() ?? []
Enum mappingStatusEnum.fromString(raw.status)
Field renamingfullName: raw.full_name
  1. Never expose raw GQL types above the repository - Notifiers and UI only see domain models
  2. Domain models should be “ready to display” - No further cleaning in the UI layer
  3. Model convertors are testable - Test each conversion as a pure function
  4. Use .mightExecute() for nullable conversions - Handles null input gracefully