Using Room Database in Compose Multiplatform (KMP/CMP): A Step-by-Step Guide- by Nimesh Vasani
- nimesh Vasani
- Feb 12
- 4 min read
Updated: Feb 13
Room Database is now officially supported in Compose Multiplatform (KMP/CMP), making it easier to manage structured data across Android, iOS, and desktop platforms. In this guide, we'll explore how to integrate Room Database into a Compose Multiplatform project, covering setup, entity creation, DAO implementation, and database initialization.

Setting up dependencies ⚙️🔗
The current version of Room that supports KMP is 2.7.0-alpha01 or higher.
Open libs.version.toml file and add following dependencies
First add version variables, You should check for latest version.
[versions]
room = "2.7.0-alpha13"
ksp = "2.1.0-1.0.29"
sqlite = "2.5.0-SNAPSHOT"
roomRuntimeJvm = "2.7.0-alpha13"
Then add followings to Library Section
[libraries]
room-runtime = { module = "androidx.room:room-runtime", version.ref = "room" }
room-runtime-android = { module = "androidx.room:room-runtime-android", version.ref = "room" }
room-compiler = { module = "androidx.room:room-compiler", version.ref = "room" }
sqlite-bundled = { module = "androidx.sqlite:sqlite-bundled", version.ref = "sqlite" }
androidx-room-runtime-jvm = { group = "androidx.room", name = "room-runtime-jvm", version.ref = "roomRuntimeJvm" }
Last for plugins
[plugins]
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
room = { id = "androidx.room", version.ref = "room" }
Open app level gradle file, Build.Gradle.kts
Then add following dependency: 💉
First of all in Plugin section add plugins for Room and Ksp
plugins {
alias(libs.plugins.ksp)
alias(libs.plugins.room)
}
Then scroll to SourceSets, and add dependencies to commonMain.dependencies block
commonMain.dependencies {
implementation(libs.room.runtime)
implementation(libs.sqlite.bundled)
}
For schema for Room database you need to specify manually, for that scroll down add room{} block and add following dependency
room {
schemaDirectory("$projectDir/schemas")
}
Last you will see another dependency section, which may have default UITooling dependecy there, add Ksp compiler dependency there.
dependencies {
ksp(libs.room.compiler)
debugImplementation(compose.uiTooling)
}
Sync the gradle file after adding all dependencies.
Note : - You may notice some red lines, indicating errors while adding dependencies, ignore it until build finish, if errors try clean project and rebuild.
Create database classes 🗄️
To enable seamless database access across all target platforms in your Compose Multiplatform project, you should define your database class, annotated with @Database, along with the necessary DAOs and entities inside the common source set of your shared KMP module.
By placing these classes in the common source set, they become accessible to all platforms, ensuring code reusability.
When defining an expect object implementing the RoomDatabaseConstructor interface, the Room compiler will generate the corresponding actual implementations. However, Android Studio may display a warning: "Expected object 'AppDatabaseConstructor' has no actual declaration in module." You can suppress this warning using @Suppress("NO_ACTUAL_FOR_EXPECT").
As an Android Developer, I am expecting my readers know about basics of Room Database.
So Let's focus more on KMP-Room set up rather than actual Room database work flow.
Here, I am using User class as data class which will be work as Table/Entity in our room database. If you have complex data structure in your table, use Convertors to store complex object.
Create package db in common/shared module create models package, then create kotlin data class "user.kt"
//add your package hierarchy
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "users")
data class User(
@PrimaryKey(autoGenerate = true) val id: Int = 0,
val name: String,
val age: Int,
val hobbies: List<String> // Room does not support lists directly, so we need a converter
)
Create Database access object first, annotate with Dao, create file "UserDao.kt"
import androidx.room.*
@Dao
interface UserDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertUser(user: User)
@Query("SELECT * FROM users")
suspend fun getAllUsers(): List<User>
@Delete
suspend fun deleteUser(user: User)
}
Now let's Create a new file in database package. "AppDatabase.kt" 📝
package ...
import androidx.room.ConstructedBy
import androidx.room.Database
import androidx.room.RoomDatabase
import androidx.room.RoomDatabaseConstructor
import androidx.room.TypeConverters
import androidx.sqlite.driver.bundled.BundledSQLiteDriver
@Database(entities = [User::class], version = 1)
@ConstructedBy(AppDatabaseConstructor::class)
@TypeConverters(Converters::class) // remove if you don't need.
abstract class AppDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
}
fun getRoomDatabaseBuilder(
builder: RoomDatabase.Builder<AppDatabase>
): AppDatabase {
return builder
.fallbackToDestructiveMigrationOnDowngrade(true)
.setDriver(BundledSQLiteDriver())
.build()
}
@Suppress("NO_ACTUAL_FOR_EXPECT")
expect object AppDatabaseConstructor :RoomDatabaseConstructor<AppDatabase> {
override fun initialize(): AppDatabase
}
As we spoke, to store complex data you will need a converter class, and you will need Kotlin serialization dependency too.
create new class "Converters.kt" //optional 🔨
import androidx.room.ProvidedTypeConverter
import androidx.room.TypeConverter
import kotlinx.serialization.encodeToString
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
@ProvidedTypeConverter
class Converters {
@TypeConverter
fun fromStringList(value: List<String>): String {
return Json.encodeToString(value)
}
@TypeConverter
fun toStringList(value: String): List<String> {
return Json.decodeFromString(value)
}
}
Now, Final set up for our platform specific Database Builders
First open AndroidMain module, use your same project structure, create db package and create kotlin file "AppDatabase.android.kt" and add following code. 🤖
import android.content.Context
import androidx.room.Room
import androidx.room.RoomDatabase
fun getDatabaseBuilder(
context: Context
): RoomDatabase.Builder<AppDatabase> {
val appContext = context.applicationContext
val dbFile = appContext.getDatabasePath("user_database.db")
return Room.databaseBuilder<AppDatabase>(
context = appContext,
name = dbFile.absolutePath
)
}
Now, for iOSMain, use same pattern and create file "AppDatabase.ios.kt" in iosMain Module.
import androidx.room.Room
import androidx.room.RoomDatabase
import kotlinx.cinterop.ExperimentalForeignApi
import platform.Foundation.NSDocumentDirectory
import platform.Foundation.NSFileManager
import platform.Foundation.NSUserDomainMask
fun getDatabaseBuilder() : RoomDatabase.Builder<AppDatabase> {
val dbFile = documentDirectory() + "/user_database.db"
return Room.databaseBuilder<AppDatabase>(
name = dbFile,
)
}
@OptIn(ExperimentalForeignApi::class)
private fun documentDirectory(): String {
val documentDirectory = NSFileManager.defaultManager.URLForDirectory(
directory = NSDocumentDirectory,
inDomain = NSUserDomainMask,
appropriateForURL = null,
create = false,
error = null,
)
return requireNotNull(documentDirectory?.path)
}
For JVM(Desktop), create file "AppDatabase.jvm.kt" in desktop module.
// shared/src/jvmMain/kotlin/Database.kt
fun getDatabaseBuilder(): RoomDatabase.Builder<AppDatabase> {
val dbFile = File(System.getProperty("java.io.tmpdir"), "my_room.db")
return Room.databaseBuilder<AppDatabase>(
name = dbFile.absolutePath,
)
}
Finally, pass this builder object to your main shared App() composable. Let's see How?
Go to Shared or common module and open your App.kt file. and add following parameters. And see how to get Dao and database object.
Pro Tip 💡 : You should use view model instead of accessing dao object direclty on UI.
@Composable
@Preview
fun App(
databaseBuilder: RoomDatabase.Builder<AppDatabase>
) {
val userDatabase = getRoomDatabase(databaseBuilder)
val userDao = userDatabase.userDao()
// your rest of code
}
Finally, don't forget to pass value of databaseBuilder parameter from each platform. for android module, go to MainActivity.kt and pass parameter.
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
App(
databaseBuilder = getDatabaseBuilder(this)
)
}
}
}
Go to MainViewController for iosMain and pass argument
fun MainViewController() = ComposeUIViewController {
val databaseBuilder = remember { getDatabaseBuilder() }
App(
databaseBuilder = databaseBuilder
)
}
Thank you for reading this artcial, Hope you learn something new today. If you find any errors/mistakes please feel free to reach me via chat button on my website. Happy coding ! 💻✨
Comments