Files
PlantGuide/Docs/save_shit.md
Trey t 136dfbae33 Add PlantGuide iOS app with plant identification and care management
- Implement camera capture and plant identification workflow
- Add Core Data persistence for plants, care schedules, and cached API data
- Create collection view with grid/list layouts and filtering
- Build plant detail views with care information display
- Integrate Trefle botanical API for plant care data
- Add local image storage for captured plant photos
- Implement dependency injection container for testability
- Include accessibility support throughout the app

Bug fixes in this commit:
- Fix Trefle API decoding by removing duplicate CodingKeys
- Fix LocalCachedImage to load from correct PlantImages directory
- Set dateAdded when saving plants for proper collection sorting

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-23 12:18:01 -06:00

6.2 KiB

Plan: Persist PlantCareInfo in Core Data

Overview

Cache Trefle API care info locally so API is only called once per plant. Preserves all timing info (watering frequency, fertilizer schedule) for proper notification scheduling.

Current Problem

  • PlantCareInfo is fetched from Trefle API every time PlantDetailView appears
  • No local caching - unnecessary API calls and poor offline experience

Solution

Add PlantCareInfoMO Core Data entity with cache-first logic in FetchPlantCareUseCase.


Implementation Steps

Step 1: Add Value Transformers for Complex Types

File: PlantGuide/Core/Utilities/ValueTransformers.swift

Add JSON-based transformers (following existing IdentificationResultArrayTransformer pattern):

  • WateringScheduleTransformer - encodes WateringSchedule struct
  • TemperatureRangeTransformer - encodes TemperatureRange struct
  • FertilizerScheduleTransformer - encodes FertilizerSchedule struct
  • SeasonArrayTransformer - encodes [Season] array

Register all transformers in PlantGuideApp.swift init.

Step 2: Update Core Data Model

File: PlantGuide/Data/DataSources/Local/CoreData/PlantGuideModel.xcdatamodeld

Add new entity PlantCareInfoMO:

Attribute Type Notes
id UUID Required, unique
scientificName String Required
commonName String Optional
lightRequirement String Enum rawValue
wateringScheduleData Binary JSON-encoded WateringSchedule
temperatureRangeData Binary JSON-encoded TemperatureRange
fertilizerScheduleData Binary Optional, JSON-encoded
humidity String Optional, enum rawValue
growthRate String Optional, enum rawValue
bloomingSeasonData Binary Optional, JSON-encoded [Season]
additionalNotes String Optional
sourceURL URI Optional
trefleID Integer 32 Optional
fetchedAt Date Required, for cache expiration

Relationships:

  • plantPlantMO (optional, one-to-one, inverse: plantCareInfo)

Update PlantMO:

  • Add relationship plantCareInfoPlantCareInfoMO (optional, cascade delete)

Step 3: Create PlantCareInfoMO Managed Object

File: PlantGuide/Data/DataSources/Local/CoreData/ManagedObjects/PlantCareInfoMO.swift (NEW)

  • Define @NSManaged properties
  • Add toDomainModel() -> PlantCareInfo? - decodes JSON data to domain structs
  • Add static func fromDomainModel(_:context:) -> PlantCareInfoMO? - encodes domain to MO
  • Add func update(from:) - updates existing MO

Step 4: Create Repository Protocol and Implementation

File: PlantGuide/Domain/RepositoryInterfaces/PlantCareInfoRepositoryProtocol.swift (NEW)

protocol PlantCareInfoRepositoryProtocol: Sendable {
    func fetch(scientificName: String) async throws -> PlantCareInfo?
    func fetch(trefleID: Int) async throws -> PlantCareInfo?
    func fetch(for plantID: UUID) async throws -> PlantCareInfo?
    func save(_ careInfo: PlantCareInfo, for plantID: UUID?) async throws
    func isCacheStale(scientificName: String, cacheExpiration: TimeInterval) async throws -> Bool
    func delete(for plantID: UUID) async throws
}

File: PlantGuide/Data/DataSources/Local/CoreData/CoreDataPlantCareInfoStorage.swift (NEW)

Implement repository with Core Data queries.

Step 5: Update FetchPlantCareUseCase with Cache-First Logic

File: PlantGuide/Domain/UseCases/PlantCare/FetchPlantCareUseCase.swift

Modify to:

  1. Inject PlantCareInfoRepositoryProtocol
  2. Check cache first before API call
  3. Validate cache freshness (7-day expiration)
  4. Save API response to cache after fetch
func execute(scientificName: String) async throws -> PlantCareInfo {
    // 1. Check cache
    if let cached = try await repository.fetch(scientificName: scientificName),
       !(try await repository.isCacheStale(scientificName: scientificName, cacheExpiration: 7 * 24 * 60 * 60)) {
        return cached
    }

    // 2. Fetch from API
    let careInfo = try await fetchFromAPI(scientificName: scientificName)

    // 3. Cache result
    try await repository.save(careInfo, for: nil)

    return careInfo
}

Step 6: Update DIContainer

File: PlantGuide/Core/DI/DIContainer.swift

  • Add _plantCareInfoStorage lazy service
  • Add plantCareInfoRepository accessor
  • Update _fetchPlantCareUseCase to inject repository
  • Add to resetAll() method

Step 7: Update PlantMO

File: PlantGuide/Data/DataSources/Local/CoreData/ManagedObjects/PlantMO.swift

Add relationship property:

@NSManaged public var plantCareInfo: PlantCareInfoMO?

Migration Strategy

Lightweight migration - no custom mapping model needed:

  • New entity with no existing data
  • New relationship is optional (nil default)
  • shouldMigrateStoreAutomatically and shouldInferMappingModelAutomatically already enabled

Files to Create/Modify

File Action
Core/Utilities/ValueTransformers.swift Add 4 transformers
PlantGuideModel.xcdatamodel/contents Add PlantCareInfoMO entity
ManagedObjects/PlantCareInfoMO.swift NEW - managed object + mappers
RepositoryInterfaces/PlantCareInfoRepositoryProtocol.swift NEW - protocol
CoreData/CoreDataPlantCareInfoStorage.swift NEW - implementation
UseCases/PlantCare/FetchPlantCareUseCase.swift Add cache-first logic
DI/DIContainer.swift Register new dependencies
ManagedObjects/PlantMO.swift Add relationship
App/PlantGuideApp.swift Register new transformers

Verification

  1. Build verification: xcodebuild -scheme PlantGuide build

  2. Test cache behavior:

    • Add a new plant → view details (should call Trefle API)
    • Navigate away and back to details (should NOT call API - use cache)
    • Check console logs for API calls
  3. Test timing preservation:

    • Verify watering frequency intervalDays property works after cache retrieval
    • Create care schedule from cached info → verify notifications scheduled correctly
  4. Test cache expiration:

    • Manually set fetchedAt to 8 days ago
    • View plant details → should re-fetch from API
  5. Run existing tests: xcodebuild test -scheme PlantGuide -destination 'platform=iOS Simulator,name=iPhone 17'