feat(shared): add shared module with common DTO models

New Gradle module :shared (pure Kotlin/JVM) containing @Serializable
DTO classes for use by both the Android app and future Ktor server.

shared/src/main/kotlin/de/krisenvorrat/shared/model/:
- InventoryDto: root DTO replacing ExportData (version, categories,
  locations, items, settings)
- CategoryDto, LocationDto, ItemDto, SettingDto: extracted from
  the former *Export data classes in :app

Migration in :app:
- ExportData.kt deleted (classes moved to :shared)
- ImportExportRepositoryImpl now imports from de.krisenvorrat.shared.model
- app/build.gradle.kts adds implementation(project(:shared))

Build config:
- libs.versions.toml: added kotlin-jvm plugin entry
- build.gradle.kts (root): registered kotlin-jvm plugin
- settings.gradle.kts: include(:shared)

JSON wire format is unchanged; all 165 existing tests pass.

Closes #39
This commit is contained in:
Jens Reinemann 2026-05-14 19:50:23 +02:00
parent 309587bc36
commit c0c4978ccf
12 changed files with 100 additions and 56 deletions

View file

@ -71,6 +71,9 @@ dependencies {
// Serialization
implementation(libs.kotlinx.serialization.json)
// Shared module
implementation(project(":shared"))
testImplementation(libs.junit)
testImplementation(libs.kotlinx.coroutines.test)
testImplementation(libs.mockk)

View file

@ -1,49 +0,0 @@
package de.krisenvorrat.app.data.export
import kotlinx.serialization.EncodeDefault
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.Serializable
@OptIn(ExperimentalSerializationApi::class)
@Serializable
internal data class ExportData(
@EncodeDefault(EncodeDefault.Mode.ALWAYS) val version: Int = 1,
val categories: List<CategoryExport>,
val locations: List<LocationExport>,
val items: List<ItemExport>,
val settings: List<SettingExport>
)
@Serializable
internal data class CategoryExport(
val id: Int,
val name: String
)
@Serializable
internal data class LocationExport(
val id: Int,
val name: String
)
@Serializable
internal data class ItemExport(
val id: String,
val name: String,
val categoryId: Int,
val quantity: Double,
val unit: String,
val unitPrice: Double,
val kcalPer100g: Int?,
val expiryDate: String?,
val locationId: Int,
val minStock: Double,
val notes: String,
val lastUpdated: Long
)
@Serializable
internal data class SettingExport(
val key: String,
val value: String
)

View file

@ -9,6 +9,11 @@ import de.krisenvorrat.app.data.db.entity.ItemEntity
import de.krisenvorrat.app.data.db.entity.LocationEntity
import de.krisenvorrat.app.data.db.entity.SettingsEntity
import de.krisenvorrat.app.domain.repository.ImportExportRepository
import de.krisenvorrat.shared.model.CategoryDto
import de.krisenvorrat.shared.model.InventoryDto
import de.krisenvorrat.shared.model.ItemDto
import de.krisenvorrat.shared.model.LocationDto
import de.krisenvorrat.shared.model.SettingDto
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.withContext
@ -37,11 +42,11 @@ internal class ImportExportRepositoryImpl @Inject constructor(
val items = itemDao.getAll().first()
val settings = settingsDao.getAll().first()
val exportData = ExportData(
categories = categories.map { CategoryExport(id = it.id, name = it.name) },
locations = locations.map { LocationExport(id = it.id, name = it.name) },
val exportData = InventoryDto(
categories = categories.map { CategoryDto(id = it.id, name = it.name) },
locations = locations.map { LocationDto(id = it.id, name = it.name) },
items = items.map { item ->
ItemExport(
ItemDto(
id = item.id,
name = item.name,
categoryId = item.categoryId,
@ -56,14 +61,14 @@ internal class ImportExportRepositoryImpl @Inject constructor(
lastUpdated = item.lastUpdated
)
},
settings = settings.map { SettingExport(key = it.key, value = it.value) }
settings = settings.map { SettingDto(key = it.key, value = it.value) }
)
jsonSerializer.encodeToString(ExportData.serializer(), exportData)
jsonSerializer.encodeToString(InventoryDto.serializer(), exportData)
}
override suspend fun importFromJson(json: String): Result<Unit> = withContext(Dispatchers.IO) {
runCatching {
val exportData = jsonSerializer.decodeFromString<ExportData>(json)
val exportData = jsonSerializer.decodeFromString<InventoryDto>(json)
check(exportData.version == 1) { "Unsupported export format version: ${exportData.version}" }
transaction.execute {
categoryDao.upsertAll(exportData.categories.map { CategoryEntity(id = it.id, name = it.name) })

View file

@ -1,6 +1,7 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
alias(libs.plugins.android.application) apply false
alias(libs.plugins.kotlin.jvm) apply false
alias(libs.plugins.kotlin.android) apply false
alias(libs.plugins.kotlin.compose) apply false
alias(libs.plugins.kotlin.serialization) apply false

View file

@ -48,6 +48,7 @@ mockk = { group = "io.mockk", name = "mockk", version.ref = "mockk" }
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }
kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }

View file

@ -21,3 +21,4 @@ dependencyResolutionManagement {
rootProject.name = "krisenvorrat"
include(":app")
include(":shared")

21
shared/build.gradle.kts Normal file
View file

@ -0,0 +1,21 @@
plugins {
alias(libs.plugins.kotlin.jvm)
alias(libs.plugins.kotlin.serialization)
}
java {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
kotlin {
compilerOptions {
jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_11)
}
}
dependencies {
implementation(libs.kotlinx.serialization.json)
testImplementation(libs.junit)
}

View file

@ -0,0 +1,9 @@
package de.krisenvorrat.shared.model
import kotlinx.serialization.Serializable
@Serializable
data class CategoryDto(
val id: Int,
val name: String
)

View file

@ -0,0 +1,15 @@
package de.krisenvorrat.shared.model
import kotlinx.serialization.EncodeDefault
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.Serializable
@OptIn(ExperimentalSerializationApi::class)
@Serializable
data class InventoryDto(
@EncodeDefault(EncodeDefault.Mode.ALWAYS) val version: Int = 1,
val categories: List<CategoryDto>,
val locations: List<LocationDto>,
val items: List<ItemDto>,
val settings: List<SettingDto>
)

View file

@ -0,0 +1,19 @@
package de.krisenvorrat.shared.model
import kotlinx.serialization.Serializable
@Serializable
data class ItemDto(
val id: String,
val name: String,
val categoryId: Int,
val quantity: Double,
val unit: String,
val unitPrice: Double,
val kcalPer100g: Int?,
val expiryDate: String?,
val locationId: Int,
val minStock: Double,
val notes: String,
val lastUpdated: Long
)

View file

@ -0,0 +1,9 @@
package de.krisenvorrat.shared.model
import kotlinx.serialization.Serializable
@Serializable
data class LocationDto(
val id: Int,
val name: String
)

View file

@ -0,0 +1,9 @@
package de.krisenvorrat.shared.model
import kotlinx.serialization.Serializable
@Serializable
data class SettingDto(
val key: String,
val value: String
)