Task 3 Adapters Consolidation
SmartCrew Admin - Task 3 Adapters Consolidation
Task 3: Adapters Layer Consolidation and Optimization
Isolation Scope
This task is completely isolated and focuses ONLY on:
- Optimizing adapter transformation logic for performance
- Consolidating adapters with similar patterns
- Creating base adapter classes for common transformations
- NO changes to providers, repositories, services, or UI components
Dependencies: None (completely independent of Task 1 and Task 2)
Affected Files: Only files in lib/util/adapter/ directory (17 adapter files)
Not Included: Providers, repositories, services, or UI components
Goal
Reduce adapter code duplication by 60% and improve transformation performance by 25% while maintaining identical transformation results and public APIs.
Current State - Adapter Analysis
Adapter Categories (17 files total in lib/util/adapter/)
Simple Model-to-UI Adapters (12 files)
Current Files:
equipment_adapter.dart- Equipment to EquipmentRow transformationlabor_adapter.dart- AppUser to Labor transformationmaterial_orders_adapter.dart- Material orders to UI modelscustomer_projects_adapter.dart- Customer project transformationsdocument_adapter.dart- Document to UI document modelsequipment_service_adapter.dart- Service record transformationsfield_note_item_adapter.dart- Field notes to UI itemstime_off_adapter.dart- Time off to UI models
Common Patterns Identified:
- All follow same
create()method pattern - Simple field mapping and data type conversion
- Default value handling for null fields
- Date formatting for UI display
Complex Report Adapters (5 files)
Current Files:
payroll_dailylog_adapter.dart- Complex payroll calculationsequipment_dot_reports_adapter.dart- DOT compliance reportsmaterial_chart_adapter.dart- Chart data generationequipment_hauling_adapter.dart- Hauling report dataadmin_log_activity_adapter.dart- Admin activity reports
Performance Issues Identified:
- Multiple iterations over same datasets
- Inefficient date calculations
- Redundant data transformations
- No caching of expensive operations
Implementation Steps
Step 1: Create Base Adapter Infrastructure (Isolated)
1.1 Base Adapter Interface
File: lib/util/adapter/base_adapter.dart
abstract class BaseAdapter<TSource, TTarget> {
/// Transforms a single source object to target type
TTarget create(TSource source);
/// Transforms a list of source objects to target type
List<TTarget> createList(Iterable<TSource> sources) {
return sources.map((source) => create(source)).toList();
}
/// Provides default value for null or invalid inputs
TTarget createDefault();
/// Validates source data before transformation
bool isValidSource(TSource source);
}
abstract class CachedAdapter<TSource, TTarget> extends BaseAdapter<TSource, TTarget> {
final Map<String, TTarget> _cache = {};
/// Get cache key for source object (override for custom caching)
String getCacheKey(TSource source);
/// Cached transformation with automatic cache management
TTarget createCached(TSource source) {
if (!isValidSource(source)) return createDefault();
final cacheKey = getCacheKey(source);
return _cache.putIfAbsent(cacheKey, () => create(source));
}
/// Clear cache when needed
void clearCache() => _cache.clear();
}1.2 UI Adapter Base Class
File: lib/util/adapter/base_ui_adapter.dart
abstract class BaseUIAdapter<TDomain, TUI> extends CachedAdapter<TDomain, TUI> {
/// Common date formatting for UI display
String formatDate(DateTime? date) {
return date?.toLocal().toString().split(' ')[0] ?? 'N/A';
}
/// Common time formatting for UI display
String formatTime(DateTime? dateTime) {
return dateTime != null
? '${dateTime.hour.toString().padLeft(2, '0')}:${dateTime.minute.toString().padLeft(2, '0')}'
: 'N/A';
}
/// Common duration formatting
String formatDuration(Duration? duration) {
if (duration == null) return '0:00';
final hours = duration.inHours;
final minutes = duration.inMinutes.remainder(60);
return '${hours}:${minutes.toString().padLeft(2, '0')}';
}
/// Common null-safe string extraction
String safeString(String? value, [String defaultValue = '']) {
return value?.isNotEmpty == true ? value! : defaultValue;
}
/// Common number formatting
String formatNumber(num? value, [int decimals = 2]) {
return value?.toStringAsFixed(decimals) ?? '0.00';
}
}Step 2: Consolidate Similar Simple Adapters
2.1 Generic Model-to-UI Adapter
File: lib/util/adapter/model_to_ui_adapter.dart
enum UIAdapterType {
equipment,
labor,
material,
customer,
document,
timeOff,
}
class ModelToUIAdapterFactory {
static BaseUIAdapter create(UIAdapterType type) {
switch (type) {
case UIAdapterType.equipment:
return EquipmentUIAdapter();
case UIAdapterType.labor:
return LaborUIAdapter();
case UIAdapterType.material:
return MaterialUIAdapter();
// ... other types
}
}
}
class EquipmentUIAdapter extends BaseUIAdapter<Equipment, EquipmentRow> {
@override
EquipmentRow create(Equipment source) {
return EquipmentRow(
equipment: source,
name: safeString(source.name),
category: safeString(source.type),
year: source.year ?? DateTime.now().year,
hours: source.hourRate.toInt(),
lastService: source.lastServiceDate ?? DateTime.now(),
);
}
@override
String getCacheKey(Equipment source) => source.firebaseUid;
@override
bool isValidSource(Equipment source) => source.firebaseUid.isNotEmpty;
@override
EquipmentRow createDefault() => EquipmentRow.empty();
}2.2 Consolidate Labor and User Adapters
Current Issues:
labor_adapter.dartcreates Labor from AppUser with placeholder values- Similar pattern repeated in multiple adapters
Optimized Version:
class UserToLaborAdapter extends BaseUIAdapter<AppUser, Labor> {
final bool includeTimeData;
final DateTime? calculationDate;
const UserToLaborAdapter({
this.includeTimeData = false,
this.calculationDate,
});
@override
Labor create(AppUser source) {
return Labor(
firebaseUid: source.firebaseUid,
phoneNumber: safeString(source.phoneNumber),
employee: safeString(source.fullName),
role: safeString(source.role),
status: source.active,
totalHours: includeTimeData ? _calculateTotalHours(source) : 'no total hours',
shopHours: includeTimeData ? _calculateShopHours(source) : 'no shop hours',
travelHours: includeTimeData ? _calculateTravelHours(source) : 'no travel hours',
machineHours: includeTimeData ? _calculateMachineHours(source) : 'no machine hours',
);
}
@override
String getCacheKey(AppUser source) =>
'${source.firebaseUid}_${includeTimeData}_${calculationDate?.millisecondsSinceEpoch}';
// Optimized time calculations with caching
String _calculateTotalHours(AppUser user) {
if (calculationDate == null) return 'no total hours';
// Use optimized service layer (from Task 2) if available
// Otherwise maintain current calculation logic
return _cachedTimeCalculation(user, 'total');
}
String _cachedTimeCalculation(AppUser user, String type) {
final cacheKey = '${user.firebaseUid}_${type}_${calculationDate?.millisecondsSinceEpoch}';
return _timeCache.putIfAbsent(cacheKey, () => _performCalculation(user, type));
}
static final Map<String, String> _timeCache = {};
}Step 3: Optimize Complex Report Adapters
3.1 Optimize Payroll Report Adapter
Current Issues in payroll_dailylog_adapter.dart:
- Multiple iterations over same data
- Expensive date calculations repeated
- No caching of intermediate results
Optimized Version:
class PayrollDailylogAdapter extends CachedAdapter<List<DailyLog>, PayrollReport> {
final DateTime payrollDate;
PayrollDailylogAdapter({required this.payrollDate});
@override
PayrollReport create(List<DailyLog> source) {
if (source.isEmpty) return PayrollReport.empty();
// Single-pass data processing instead of multiple iterations
final processedData = _processDailyLogsEfficiently(source);
return PayrollReport(
totalHours: processedData.totalHours,
regularHours: processedData.regularHours,
overtimeHours: processedData.overtimeHours,
employeeBreakdowns: processedData.employeeBreakdowns,
periodStart: processedData.periodStart,
periodEnd: processedData.periodEnd,
);
}
// Optimized single-pass processing
ProcessedPayrollData _processDailyLogsEfficiently(List<DailyLog> logs) {
final Map<String, EmployeeHours> employeeHours = {};
DateTime? earliestDate;
DateTime? latestDate;
for (final log in logs) {
// Update date range
earliestDate = earliestDate == null || log.logDate.isBefore(earliestDate)
? log.logDate
: earliestDate;
latestDate = latestDate == null || log.logDate.isAfter(latestDate)
? log.logDate
: latestDate;
// Process employee hours in single pass
final employeeId = log.userId;
final hours = employeeHours.putIfAbsent(employeeId, () => EmployeeHours.zero());
// Calculate hours for this log
final logHours = _calculateLogHours(log);
hours.addHours(logHours);
}
return ProcessedPayrollData(
employeeBreakdowns: employeeHours,
periodStart: earliestDate ?? payrollDate,
periodEnd: latestDate ?? payrollDate,
);
}
@override
String getCacheKey(List<DailyLog> source) {
// Create cache key from log IDs and payroll date
final logIds = source.map((log) => log.id).join(',');
return '${logIds}_${payrollDate.millisecondsSinceEpoch}'.hashCode.toString();
}
@override
bool isValidSource(List<DailyLog> source) => source.isNotEmpty;
@override
PayrollReport createDefault() => PayrollReport.empty();
}3.2 Optimize Material Chart Adapter
Current Issues: Inefficient data aggregation and chart point generation
Optimized Version:
class MaterialChartAdapter extends CachedAdapter<List<MaterialOrder>, MaterialChartData> {
final DateRange dateRange;
final ChartType chartType;
@override
MaterialChartData create(List<MaterialOrder> source) {
if (source.isEmpty) return MaterialChartData.empty();
// Filter data once, then reuse for multiple chart calculations
final filteredOrders = _filterOrdersByDateRange(source);
return MaterialChartData(
dataPoints: _generateChartPoints(filteredOrders),
totalValue: _calculateTotalValue(filteredOrders),
averageOrderValue: _calculateAverageValue(filteredOrders),
peakOrderDate: _findPeakOrderDate(filteredOrders),
);
}
// Optimized chart point generation
List<ChartPoint> _generateChartPoints(List<MaterialOrder> orders) {
final Map<DateTime, double> dailyTotals = {};
// Single pass to aggregate daily totals
for (final order in orders) {
final date = order.orderDate.dateOnly;
dailyTotals[date] = (dailyTotals[date] ?? 0.0) + order.totalValue;
}
// Convert to chart points efficiently
return dailyTotals.entries
.map((entry) => ChartPoint(date: entry.key, value: entry.value))
.toList()
..sort((a, b) => a.date.compareTo(b.date));
}
@override
String getCacheKey(List<MaterialOrder> source) {
return '${chartType.name}_${dateRange.hashCode}_${source.length}_${source.first.id}';
}
}Step 4: Create Adapter Factory Pattern
4.1 Universal Adapter Factory
File: lib/util/adapter/adapter_factory.dart
class AdapterFactory {
// Singleton pattern for performance
static final Map<String, BaseAdapter> _adapters = {};
static T getAdapter<T extends BaseAdapter>(
AdapterType type, {
Map<String, dynamic>? config,
}) {
final key = '${type.name}_${config?.hashCode ?? 0}';
return _adapters.putIfAbsent(key, () {
switch (type) {
case AdapterType.equipmentToUI:
return EquipmentUIAdapter();
case AdapterType.userToLabor:
return UserToLaborAdapter(
includeTimeData: config?['includeTimeData'] ?? false,
calculationDate: config?['calculationDate'],
);
case AdapterType.payrollReport:
return PayrollDailylogAdapter(
payrollDate: config!['payrollDate'],
);
// ... other adapters
}
}) as T;
}
// Bulk transformation for performance
static List<TTarget> transformList<TSource, TTarget>(
List<TSource> sources,
AdapterType adapterType, {
Map<String, dynamic>? config,
}) {
final adapter = getAdapter<BaseAdapter<TSource, TTarget>>(adapterType, config: config);
return adapter.createList(sources);
}
// Clear all cached adapters
static void clearAdapterCache() {
for (final adapter in _adapters.values) {
if (adapter is CachedAdapter) {
adapter.clearCache();
}
}
}
}
enum AdapterType {
equipmentToUI,
userToLabor,
materialChart,
payrollReport,
documentToUI,
timeOffToUI,
// ... other adapter types
}Testing & Validation Metrics
Pre-Optimization Baseline Tests
# 1. Capture Current Transformation Results
void captureTransformationBaseline() {
// Equipment adapter baseline
final equipment = createTestEquipment();
final equipmentRow = EquipmentAdapter(equipments: [equipment]).create();
storeBaseline('equipment_transform', equipmentRow);
// Labor adapter baseline
final user = createTestUser();
final labor = LaborAdapter(user: user).create();
storeBaseline('labor_transform', labor);
// Complex report baseline
final logs = createTestDailyLogs();
final payrollReport = PayrollDailylogAdapter().create(logs);
storeBaseline('payroll_transform', payrollReport);
}
# 2. Performance Baseline
void captureAdapterPerformance() {
final stopwatch = Stopwatch()..start();
// Simple adapter performance
final equipmentTime = measureEquipmentAdapterPerformance();
print('Equipment adapter baseline: ${equipmentTime}ms');
// Complex adapter performance
final payrollTime = measurePayrollAdapterPerformance();
print('Payroll adapter baseline: ${payrollTime}ms');
// Bulk transformation performance
final bulkTime = measureBulkTransformationPerformance();
print('Bulk transformation baseline: ${bulkTime}ms');
}
# 3. Memory Usage Baseline
void captureAdapterMemoryBaseline() {
final memoryBefore = getMemoryUsage();
runAllAdapterTransformations();
final memoryAfter = getMemoryUsage();
final baseline = memoryAfter - memoryBefore;
print('Adapter memory baseline: ${baseline}MB');
}Post-Optimization Validation Metrics
Metric 1: Identical Transformation Results
# All transformations must return EXACTLY the same results
void testTransformationAccuracy() {
final beforeResults = runBaselineTransformations();
final afterResults = runOptimizedTransformations();
// Equipment transformation accuracy
expect(afterResults.equipmentRow.name, equals(beforeResults.equipmentRow.name));
expect(afterResults.equipmentRow.hours, equals(beforeResults.equipmentRow.hours));
expect(afterResults.equipmentRow.category, equals(beforeResults.equipmentRow.category));
// Labor transformation accuracy
expect(afterResults.labor.employee, equals(beforeResults.labor.employee));
expect(afterResults.labor.totalHours, equals(beforeResults.labor.totalHours));
// Complex report accuracy
expect(afterResults.payrollReport.totalHours, equals(beforeResults.payrollReport.totalHours));
expect(afterResults.payrollReport.overtimeHours, equals(beforeResults.payrollReport.overtimeHours));
// Test with edge cases
testTransformationWithNullData();
testTransformationWithEmptyData();
testTransformationWithLargeDatasets();
}Metric 2: Performance Improvement (25% target)
void testAdapterPerformanceImprovement() {
// Simple adapters should be 25% faster
final equipmentTime = measureEquipmentAdapterTime();
expect(equipmentTime, lessThan(baselineEquipmentTime * 0.75));
// Complex adapters should be 30% faster
final payrollTime = measurePayrollAdapterTime();
expect(payrollTime, lessThan(baselinePayrollTime * 0.70));
// Bulk transformations should be 40% faster
final bulkTime = measureBulkTransformationTime();
expect(bulkTime, lessThan(baselineBulkTime * 0.60));
// Cache performance - second call should be 90% faster
final firstCall = measureAdapterCall();
final secondCall = measureAdapterCall(); // Should hit cache
expect(secondCall, lessThan(firstCall * 0.1));
}Metric 3: Code Duplication Reduction (60% target)
void testCodeDuplicationReduction() {
final beforeDuplicateLines = countAdapterDuplicateLines();
final afterDuplicateLines = countAdapterDuplicateLines();
final reduction = (beforeDuplicateLines - afterDuplicateLines) / beforeDuplicateLines;
expect(reduction, greaterThan(0.6)); // 60% reduction target
// Verify base classes are being used
final equipmentAdapter = AdapterFactory.getAdapter(AdapterType.equipmentToUI);
expect(equipmentAdapter, isA<BaseUIAdapter>());
// Verify factory pattern consolidation
expect(AdapterFactory._adapters.length, greaterThan(0));
}Metric 4: Memory Usage Reduction (20% target)
void testAdapterMemoryOptimization() {
final memoryUsage = measureAdapterMemoryUsage();
expect(memoryUsage, lessThan(baselineAdapterMemoryUsage * 0.8));
// Test memory efficiency with large datasets
final largeDatasetMemory = measureLargeDatasetTransformation();
expect(largeDatasetMemory, lessThan(acceptableMemoryThreshold));
// Cache memory management
AdapterFactory.clearAdapterCache();
final memoryAfterClear = getMemoryUsage();
expect(memoryAfterClear, lessThan(memoryBeforeClear * 1.1));
}Metric 5: Adapter API Compatibility
void testAdapterAPICompatibility() {
// All existing adapter constructors must work
expect(() => EquipmentAdapter(equipments: [equipment]), returnsNormally);
expect(() => LaborAdapter(user: user), returnsNormally);
expect(() => PayrollDailylogAdapter(), returnsNormally);
// All create() methods must work unchanged
final equipmentResult = EquipmentAdapter(equipments: [equipment]).create();
expect(equipmentResult, isA<List<EquipmentRow>>());
final laborResult = LaborAdapter(user: user).create();
expect(laborResult, isA<Labor>());
// Factory methods should provide same results
final factoryResult = AdapterFactory.getAdapter(AdapterType.equipmentToUI).create(equipment);
final directResult = EquipmentAdapter(equipments: [equipment]).create().first;
expect(factoryResult.name, equals(directResult.name));
}Metric 6: Edge Case and Error Handling
void testAdapterEdgeCaseHandling() {
// Null input handling
final nullResult = adapter.create(null);
expect(nullResult, equals(adapter.createDefault()));
// Empty list handling
final emptyResult = adapter.createList([]);
expect(emptyResult, isEmpty);
// Invalid data handling
final invalidResult = adapter.create(invalidData);
expect(invalidResult, equals(baseline_InvalidResult));
// Large dataset handling
final largeDatasetTime = measureLargeDatasetAdapterTime();
expect(largeDatasetTime, lessThan(acceptableMaxTime));
}Success Validation Checklist
Performance Validation
- 25% improvement in simple adapter performance
- 30% improvement in complex adapter performance
- 40% improvement in bulk transformation performance
- Cache hit rate > 85% for repeated transformations
- Memory usage reduced by 20%
Accuracy Validation
- All transformations return identical results to baseline
- Edge cases handled identically
- Null and empty data handled correctly
- Data type conversions maintain precision
- Date/time formatting identical
Code Quality Validation
- 60% reduction in duplicate code lines
- All adapters use appropriate base classes
- Factory pattern implemented correctly
- Proper error handling maintained
- Documentation updated for new patterns
Compatibility Validation
- All existing adapter APIs unchanged
- Constructor signatures preserved
- Return types identical
- Exception handling behavior preserved
- Thread safety maintained
Rollback Strategy
Immediate Rollback
- Keep original adapter files as
.backupduring development - Restore individual adapters if any metric fails
- Git branch strategy allows atomic rollbacks per adapter
Selective Rollback
- Rollback can be done per adapter type:
- Simple UI adapters independently
- Complex report adapters independently
- Factory pattern can be disabled
Validation Gates
- Each adapter optimization must pass all metrics before proceeding
- Automated testing prevents broken transformations
- Performance regression detection stops deployment
Files Modified (Complete List)
New Base Adapter Files
lib/util/adapter/base_adapter.dartlib/util/adapter/base_ui_adapter.dartlib/util/adapter/adapter_factory.dart
New Consolidated Adapters
lib/util/adapter/model_to_ui_adapter.dartlib/util/adapter/report_adapter.dart
Modified Simple Adapters (8 files optimized)
lib/util/adapter/equipment_adapter.dart(uses base class)lib/util/adapter/labor_adapter.dart(uses base class)lib/util/adapter/material_orders_adapter.dart(consolidated)lib/util/adapter/customer_projects_adapter.dart(uses base class)lib/util/adapter/document_adapter.dart(uses base class)lib/util/adapter/time_off_adapter.dart(uses base class)lib/util/adapter/field_note_item_adapter.dart(uses base class)lib/util/adapter/equipment_service_adapter.dart(uses base class)
Modified Complex Adapters (5 files optimized)
lib/util/adapter/payroll_dailylog_adapter.dart(performance optimized)lib/util/adapter/equipment_dot_reports_adapter.dart(optimized)lib/util/adapter/material_chart_adapter.dart(optimized)lib/util/adapter/equipment_hauling_adapter.dart(optimized)lib/util/adapter/admin_log_activity_adapter.dart(optimized)
Deprecated Adapters (4 files)
- Simple adapters replaced by factory pattern
- Kept for backward compatibility during transition
Total Files: 20 (5 new, 13 modified, 2 consolidated) Scope: Only adapter files - no providers, repositories, services, or UI touched