Skip to main content
Version: 2026.06.5-alpha

App Architecture

Karter follows a clean-ish layered architecture with three main layers: domain/, data/, and presentation/. State management is handled by Riverpod, and local persistence by Drift (SQLite).


Overview​


Core Entities​

Vehicle​

The root aggregate of the domain.

Notes:

  • plate and vin are stored as plain String?, backed by value objects in domain/value_objects/ for validation.
  • currency is a 3-letter ISO code string (USD, ARS, EUR, etc.) used for fuel price and service cost display.
  • fuelVolumeUnit determines whether fuel-ups display in liters or gallons.

FuelLog​

Records a refueling event to track expenses and calculate consumption.

  • calculatedConsumption returns L/100km or MPG depending on units.
  • Volume unit is inherited from vehicle settings, not stored per entry.
  • pricePerUnit is displayed with the vehicle's currency symbol.

MaintenanceLog​

Records services or repairs performed on the vehicle.

  • photoPaths are stored as file paths to images in {appDocDir}/maintenance_photos/{logId}/.
  • costAmount/costCurrency store historical cost values independent of vehicle's current currency setting.

VehicleDocument​

Attaches files (receipts, photos, PDFs) to a vehicle.

  • Files are stored at {appDocDir}/documents/{vehicleId}/{uuid}.{ext}.
  • Document types are displayed with localized labels and icons.

MaintenanceInterval​

Tracks periodic maintenance tasks with km/time thresholds.

  • i18nKey/descI18nKey map to ARB translation keys for built-in intervals (oil change, filters, etc.).
  • isCustom distinguishes user-created intervals from seeded defaults.
  • lastResetKm/lastResetDate track the last service reset for "next in X km" calculations.

Enums​

EnumFileValues
VehicleTypedomain/enums/vehicle_type.dartcombustion, electric, motorcycle
DistanceUnitdomain/enums/distance_unit.dartkilometers, miles
VolumeUnitdomain/enums/volume_unit.dartliters, gallons
DocumentTypedomain/enums/document_type.dartfine, parkingFee, insurance, vehicleCheck, tax, complexInsurance, vehicleRegister, other
CoreErrordomain/enums/core_error.dartemptyLicensePlate, invalidLicensePlateFormat, negativeOdometer, negativeMoneyAmount, invalidVehicleYear, invalidVinFormat

Value Objects​

Immutable objects that enforce formatting and prevent primitive obsession.

Odometer​

Volume​

Plate​

Validates license plate format by country code. Currently supports Argentina (ABC·123, AB·123·CD) and generic alphanumeric formats.

VIN​

Validates 17-character VIN structure and extracts:

PositionSectionDescription
1 - 3WMIWorld Manufacturer Identifier
4 - 8VDSVehicle Descriptor Section
9VDSCheck Digit
10VISModel Year
11VISPlant Code
12 - 17VISSerial Number

Error Handling​

All business rule violations throw DomainException with a CoreError enum value. Presentation layer catches these to show localized error messages.


Data Services​

Template Resolution (data/services/template_resolver.dart)​

Loads vehicle maintenance templates from bundled JSON assets. Templates define default maintenance intervals for specific make/model/year combinations.

  • Templates are JSON files in the bundled templates/ directory.
  • findBestMatch() searches by make + model + year and returns the best matching template.
  • Used in the vehicle creation form ("Buscar plantilla" button).

PDF Export (data/services/pdf_export_service.dart)​

Generates maintenance report PDFs using the pdf and printing packages.

  • Output: in-memory PDF bytes, shared via Share.shareXFiles.
  • Uses Roboto font from PdfGoogleFonts (bundled with printing package) for Unicode support.
  • Linux desktop fallback: xdg-open since share_plus is unimplemented.

Data Export/Import (data/services/export_service.dart)​

Exports all vehicle data (vehicles, fuel logs, maintenance logs) as a JSON file for backup or transfer.


Persistence​

Drift Database (core/database/app_database.dart)​

TableSchema Version AddedNotes
Vehiclesv1Has fuelVolumeUnit (v9), currency (v10)
FuelLogsv1
MaintenanceLogsv1Has photoPaths, costAmount, costCurrency (v10)
MaintenanceIntervalsv1
VehicleDocumentsv7

Current schema version: 10

Migrations are handled incrementally in MigrationStrategy inside app_database.dart. Each schema change adds a migrate.fromCallback step.


Providers (Riverpod)​

All providers are defined in presentation/providers/vehicle_providers.dart and locale_provider.dart.

Repository Providers (singletons)​

ProviderTypeReturns
appDatabaseProviderProvider<AppDatabase>Database instance
vehicleRepositoryProviderProvider<VehicleRepository>VehicleRepositoryImpl
fuelLogRepositoryProviderProvider<FuelLogRepository>FuelLogRepositoryImpl
maintenanceLogRepositoryProviderProvider<MaintenanceLogRepository>MaintenanceLogRepositoryImpl
maintenanceIntervalRepositoryProviderProvider<MaintenanceIntervalRepository>MaintenanceIntervalRepositoryImpl
vehicleDocumentRepositoryProviderProvider<VehicleDocumentRepository>VehicleDocumentRepositoryImpl
exportServiceProviderProvider<ExportService>ExportService
templateResolverProviderProvider<TemplateResolver>TemplateResolver
pdfExportServiceProviderProvider<PdfExportService>PdfExportService

Data Providers (async, family by vehicleId)​

ProviderReturns
vehicleListProviderList<Vehicle>
vehicleProvider(vehicleId)Vehicle?
fuelLogsProvider(vehicleId)List<FuelLog>
maintenanceLogsProvider(vehicleId)List<MaintenanceLog>
maintenanceIntervalsProvider(vehicleId)List<MaintenanceInterval>
vehicleDocumentsProvider(vehicleId)List<VehicleDocument>

Locale Provider​

ProviderPurpose
localeProviderRead/write current locale (en/es), persisted in SharedPreferences

Data Flow Example​

User taps "Add fuel log"
→ vehicle_detail_page calls showAddFuelLogModal(context, vehicleId)
→ Modal reads vehicle from ref (for odometer, volume unit, currency)
→ User fills form, taps "Save"
→ Modal calls fuelLogRepositoryProvider.save(FuelLog(...))
→ Repository impl converts to Drift companion, inserts row
→ Modal pops with result=true
→ Vehicle detail page invalidates fuelLogsProvider to refresh UI
  • There is no use-case/orchestrator layer — providers call repositories directly.
  • Modals return bool to signal success; the caller invalidates the relevant provider.