I'm working on a Compose Multiplatform project using Koin for dependency injection, Voyager for Navigation and Room for Database Management. I'm encountering a NoDefinitionFoundException when trying to provide the ForgoDatabase instance using a RoomDatabase.Builder.
It's my first time working in a Compose Multiplatform. My main source for all libraries is their documentation and some implementations on Github for guidance. I've checked many implementations in different approaches and I can't figure out what my mistake is.
My build.gradle:
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
plugins {
alias(libs.plugins.kotlinMultiplatform)
alias(libs.plugins.androidApplication)
alias(libs.plugins.composeMultiplatform)
alias(libs.plugins.composeCompiler)
alias(libs.plugins.ksp.plugin)
alias(libs.plugins.room.gradle.plugin)
}
kotlin {
androidTarget {
compilerOptions {
jvmTarget.set(JvmTarget.JVM_11)
}
}
listOf(
iosX64(),
iosArm64(),
iosSimulatorArm64()
).forEach { iosTarget ->
iosTarget.binaries.framework {
baseName = "ComposeApp"
isStatic = true
linkerOpts.add("-lsqlite3")
}
}
room {
schemaDirectory("$projectDir/schemas")
}
sourceSets {
commonTest.dependencies {
implementation(libs.kotlin.test)
}
androidMain.dependencies {
implementation(compose.preview)
implementation(libs.androidx.activity.compose)
implementation(libs.koin.android)
implementation(libs.koin.androidx.compose)
}
commonMain.dependencies {
implementation(compose.runtime)
implementation(compose.foundation)
implementation(compose.material3)
implementation(compose.ui)
implementation(compose.components.resources)
implementation(compose.components.uiToolingPreview)
implementation(libs.androidx.lifecycle.viewmodel)
implementation(libs.androidx.lifecycle.runtime.compose)
implementation(libs.kotlinx.date.time)
implementation(libs.kotlinx.coroutines)
// Voyager
implementation(libs.bundles.voyager)
// Koin
implementation(libs.koin.compose)
implementation(libs.koin.viewmodel)
api(libs.koin.core)
// Room
implementation(libs.room.runtime)
implementation(libs.sqlite)
}
all {
languageSettings.optIn("kotlin.ExperimentalMultiplatform")
languageSettings.optIn("kotlin.RequiresOptIn")
}
}
compilerOptions {
freeCompilerArgs.add("-Xexpect-actual-classes")
}
}
android {
namespace = "com.feeltheboard.forgo"
compileSdk = libs.versions.android.compileSdk.get().toInt()
sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml")
sourceSets["main"].res.srcDirs("src/androidMain/res")
sourceSets["main"].resources.srcDirs("src/commonMain/composeResources")
defaultConfig {
applicationId = "com.feeltheboard.forgo"
minSdk = libs.versions.android.minSdk.get().toInt()
targetSdk = libs.versions.android.targetSdk.get().toInt()
versionCode = 2
versionName = "1.0.2"
}
packaging {
resources {
excludes += "/META-INF/{AL2.0,LGPL2.1}"
}
}
buildTypes {
getByName("release") {
isMinifyEnabled = false
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
}
dependencies {
debugImplementation(compose.uiTooling)
add("kspCommonMainMetadata", libs.room.ksp)
add("kspAndroid", libs.room.ksp)
add("kspIosSimulatorArm64", libs.room.ksp)
add("kspIosX64", libs.room.ksp)
add("kspIosArm64", libs.room.ksp)
}
Libraries Versions:
[versions]
agp = "8.9.0"
android-compileSdk = "35"
android-minSdk = "24"
android-targetSdk = "35"
androidx-activityCompose = "1.10.1"
androidx-appcompat = "1.7.0"
androidx-constraintlayout = "2.2.1"
androidx-core-ktx = "1.15.0"
androidx-espresso-core = "3.6.1"
androidx-lifecycle = "2.8.4"
androidx-material = "1.12.0"
androidx-test-junit = "1.2.1"
compose-multiplatform = "1.7.0"
kotlinx-coroutines="1.8.1"
kotlinx-date-time="0.6.2"
junit = "4.13.2"
koin = "4.0.2"
kotlin = "2.1.0"
ksp = "2.1.0-1.0.29"
room = "2.7.0-rc02"
sqlite = "2.5.0-alpha01"
voyager = "1.1.0-beta02"
Here's my SharedModule in CommonMain:
expect val platformModule: Module
val sharedModule = module {
single { getRoomDatabase(get()) }
single<ForgoRepository> { ForgoRepositoryImpl(get()) }
factory { HomeViewModel(get()) }
factory { TaskViewModel(get()) }
}
fun appModules() = listOf(sharedModule, platformModule)
The Room database implementation + database builder on CommonMain:
@Database(entities = [Task::class], version = 5, exportSchema = true)
@ConstructedBy(ForgoDatabaseConstructor::class)
abstract class ForgoDatabase : RoomDatabase() {
abstract fun taskDao(): TaskDao
}
@Suppress("NO_ACTUAL_FOR_EXPECT")
expect object ForgoDatabaseConstructor : RoomDatabaseConstructor<ForgoDatabase> {
override fun initialize(): ForgoDatabase
}
fun getRoomDatabase(
builder: RoomDatabase.Builder<ForgoDatabase>
): ForgoDatabase {
return builder
.fallbackToDestructiveMigrationOnDowngrade(dropAllTables = true)
.setDriver(BundledSQLiteDriver())
.setQueryCoroutineContext(Dispatchers.IO)
.build()
}
The Android Main implementation with builder, modules, mainActivity and application (added to manifest):
fun getDatabaseBuilder(
ctx: Context
): RoomDatabase.Builder<ForgoDatabase> {
val appContext = ctx.applicationContext
val dbFile = appContext.getDatabasePath("task.db")
return Room.databaseBuilder<ForgoDatabase>(
context = appContext,
name = dbFile.absolutePath
)
}
actual val platformModule: Module = module {
single { getDatabaseBuilder(ctx = get()) }
}
class MainApplication : Application() {
override fun onCreate() {
super.onCreate()
initializeKoin {
androidContext(this@MainApplication)
}
}
}
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
App()
}
}
}
Ultimately, the main problem was not making my DAO instance available.
Here's my updated KoinModules.kt
expect val platformModule: Module
val sharedModule = module {
single { getRoomDatabase(get()) }
single { get<ForgoDatabase>().taskDao() }
single { get<ForgoDatabase>().tagDao() }
single<ForgoRepository> { ForgoRepositoryImpl(get(), get()) }
factory { HomeViewModel(get()) }
factory { TaskViewModel(get()) }
}
fun appModules() = listOf(sharedModule, platformModule)
After creating the database instance, following the dependency chain, I could create the Dao instance and use it in repositories. Making available for Viewmodels.