--- name: kmp-starter-data description: Data layer on the KMP Starter Template — repositories and data sources, Logics guidance, DataStore persistence, Room database, StarterFileManager, and the Calf file picker. author: DevAtrii license: MIT --- # Data Layer ## Repositories & data sources - Domain defines the interface; data implements it. See the onboarding trio: - `features/core/domain/.../repositories/OnboardingRepository.kt` - `features/core/data/.../repositories/OnboardingRepositoryImpl.kt` - Bind impl to interface via Koin (`singleOf(::Impl) bind Interface::class`). ## Logics (use cases) — only when meaningful Logics are use cases. Create them **only** for meaningful user-driven actions. Good: `RegisterAccountLogic`, `LogoutLogic`, `PurchaseSubscriptionLogic`, `DeleteAccountLogic`, `ExportNotesLogic`, `BackupDatabaseLogic`. Bad: `ValidateEmailLogic`, `FormatDateLogic`, `GetUsersLogic`, `ParseJsonLogic`, `SavePreferenceLogic`. Simple operations belong in repositories, extension functions, or utilities. Do not wrap every repository call in a Logic. A `Logic` is a class with `suspend operator fun invoke()`; aggregate multiple Logics into a `data class XLogics(...)` if the ViewModel needs several. ## Persistence — DataStore Use Starter DataStores; do not introduce another preference framework. Non-Compose (ViewModels/repos) — inject `AppDataStore` and use delegates: ```kotlin class AuthViewModel(appDataStore: AppDataStore) : ViewModel() { private val accessToken = appDataStore.stringDataStore("access_token") private val isLoggedIn = appDataStore.booleanDataStore("is_logged_in", default = false) private val settings = appDataStore.serializableDataStore("settings", default = Settings()) // API: flow, get(), set(value), clear() } ``` Factories: `stringDataStore`, `intDataStore`, `longDataStore`, `booleanDataStore`, `floatDataStore`, `doubleDataStore`, `stringSetDataStore`, `byteArrayDataStore`, `serializableDataStore`. Compose — `remember*DataStore` helpers: ```kotlin val themeMode by rememberStringDataStore("theme_mode", "LIGHT") var counter by rememberMutableIntDataStore("counter", 0) ``` ## Database (Room) If `features/database` exists, define entities, DAOs, and migrations there; other features consume it. - Entity: `@Entity` data class with `@PrimaryKey`. - DAO: `@Dao` interface with `@Query` / `@Insert` / `@Update` / `@Delete`. - Register entity + DAO in `KmpStarterDatabase` (`@Database(entities=[...], version=DB_VERSION)`). - Bump `DB_VERSION` on schema change; add migrations to `KmpStarterDatabaseMigrations.SUPPORTED_MIGRATIONS`. If the project was generated without the database feature, ask the user to regenerate with Database enabled. ## StarterFileManager Cross-platform file API in `starter:utils`; returns `Result` for every operation. Marked `@ExperimentalStarterApi` — opt in with `@OptIn(ExperimentalStarterApi::class)`. ```kotlin // Compose (Activity-bound; required for saveFileIn / shareFile / openFile) val fileManager = rememberStarterFileManager() // Repos / background (no Activity needed) class ReportRepository(private val fileManager: StarterFileManager) { suspend fun cacheReport(bytes: ByteArray) = fileManager.saveInCache( file = "report", folderPath = "exports", extension = "pdf", content = bytes, mimeType = "application/pdf", ) } ``` Key types: pass `file` (name, no extension) and `extension` (no dot) separately. `StarterFile` holds `path`, `name`, `extension`, `mimeType`, `sizeBytes`, timestamps. - Downloads: `saveFileIntoDownloads`, `getFilesFromDownloads`, `getFileFromDownloads`, `readFromDownloads`, `renameFromDownloads`, `deleteFromDownloads` (Android → public Downloads; iOS → Documents). - Cache: `saveInCache`, `getFilesFromCache`, `getFileFromCache`, `readFromCache`, `renameFromCache`, `deleteFromCache` (paths relative to cache dir). - System picker: `saveFileIn(suggestedName, extension, content, mimeType)`. - Share/open: `shareFile(path)`, `openFile(path)` — need Activity on Android (use `rememberStarterFileManager()`). - Android host app owns `FileProvider` for local paths (`androidApp/.../AndroidManifest.xml` + `res/xml/starter_file_paths.xml`). Pass an Activity-bound instance into a ViewModel with Koin parameters (`parametersOf(fileManager)`). See `https://starter.atherio.dev/fundamentals/13-starter-file-manager/` for full API. ## File picker Use the bundled Calf library for file picking. Do not introduce another picker library.