Skip to main content
Version: 2026.07.0-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, 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.