diff --git a/build-logic/convention/src/main/kotlin/AndroidRoomConventionPlugin.kt b/build-logic/convention/src/main/kotlin/AndroidRoomConventionPlugin.kt
index 1d5d77c42..5f96b8175 100644
--- a/build-logic/convention/src/main/kotlin/AndroidRoomConventionPlugin.kt
+++ b/build-logic/convention/src/main/kotlin/AndroidRoomConventionPlugin.kt
@@ -15,7 +15,7 @@
* along with this program. If not, see .
*/
-import androidx.room.gradle.RoomExtension
+import androidx.room3.gradle.RoomExtension
import com.google.devtools.ksp.gradle.KspExtension
import org.gradle.api.Plugin
import org.gradle.api.Project
@@ -30,7 +30,7 @@ class AndroidRoomConventionPlugin : Plugin {
override fun apply(target: Project) {
with(target) {
- apply(plugin = "androidx.room")
+ apply(plugin = "androidx.room3")
apply(plugin = "com.google.devtools.ksp")
extensions.configure {
@@ -55,7 +55,7 @@ class AndroidRoomConventionPlugin : Plugin {
}
}
dependencies {
- "kspAndroid"(roomCompiler)
+ add("kspAndroid", roomCompiler)
}
}
diff --git a/conductor/tracks.md b/conductor/tracks.md
index dc117455f..431ed9884 100644
--- a/conductor/tracks.md
+++ b/conductor/tracks.md
@@ -6,3 +6,9 @@ This file tracks all major tracks for the project. Each track has its own detail
- [x] **Track: Migrate to room3, prepare to support all targets (Android, Desktop, iOS) with bundled SQLite driver and full idiomatic migration.**
*Link: [./tracks/migrate_room3_20260320/](./tracks/migrate_room3_20260320/)*
+
+- [x] **Track: Extract DatabaseManager to KMP**
+*Link: [./tracks/extract_database_manager_kmp_20260320/](./tracks/extract_database_manager_kmp_20260320/)*
+
+- [ ] **Track: Extract RadioInterfaceService to KMP**
+*Link: [./tracks/extract_radio_interface_kmp_20260320/](./tracks/extract_radio_interface_kmp_20260320/)*
diff --git a/conductor/tracks/migrate_room3_20260320/plan.md b/conductor/tracks/migrate_room3_20260320/plan.md
index d5a581832..a67023655 100644
--- a/conductor/tracks/migrate_room3_20260320/plan.md
+++ b/conductor/tracks/migrate_room3_20260320/plan.md
@@ -4,33 +4,33 @@
- Update `libs.versions.toml` to Room 3.0.
- Update `AndroidRoomConventionPlugin.kt` to align with Room 3 best practices (e.g., ensuring `room.generateKotlin` is correctly set and using the `androidx.room` Gradle plugin).
- Verify all modules (`core:database`, `core:data`, `app`, etc.) can build with the new dependencies.
-- [ ] Task: Update `libs.versions.toml` with Room 3.0 and related dependencies.
-- [ ] Task: Refactor `AndroidRoomConventionPlugin.kt` for Room 3.0.
-- [ ] Task: Conductor - User Manual Verification 'Phase 1' (Protocol in workflow.md)
+- [x] Task: Update `libs.versions.toml` with Room 3.0 and related dependencies.
+- [x] Task: Refactor `AndroidRoomConventionPlugin.kt` for Room 3.0.
+- [x] Task: Conductor - User Manual Verification 'Phase 1' (Protocol in workflow.md)
## Phase 2: Core Database Implementation (KMP)
- Refactor `MeshtasticDatabase.kt` and `MeshtasticDatabaseConstructor.kt` to use the new Room 3 `RoomDatabase.Builder` for KMP.
- Configure the `BundledSQLiteDriver` in `commonMain` to ensure consistent SQL behavior across all targets.
- Ensure that DAOs and Entities are using `room-runtime` in `commonMain` correctly.
- Implement platform-specific database setup for Android, Desktop, and iOS in their respective `Main` source sets.
-- [ ] Task: Refactor `MeshtasticDatabase.kt` for Room 3.0 KMP APIs.
-- [ ] Task: Configure `BundledSQLiteDriver` in `DatabaseProvider.kt`.
-- [ ] Task: Implement platform-specific database path logic for Desktop and iOS.
-- [ ] Task: Conductor - User Manual Verification 'Phase 2' (Protocol in workflow.md)
+- [x] Task: Refactor `MeshtasticDatabase.kt` for Room 3.0 KMP APIs.
+- [x] Task: Configure `BundledSQLiteDriver` in `DatabaseProvider.kt`.
+- [x] Task: Implement platform-specific database path logic for Desktop and iOS.
+- [x] Task: Conductor - User Manual Verification 'Phase 2' (Protocol in workflow.md)
## Phase 3: Multi-target Support (iOS)
- Add iOS targets (`iosX64`, `iosArm64`, `iosSimulatorArm64`) to `core:database/build.gradle.kts`.
- Configure the database file path logic for iOS.
- Verify that the `core:database` module compiles for iOS.
-- [ ] Task: Add iOS targets to `core:database/build.gradle.kts`.
-- [ ] Task: Verify iOS compilation.
-- [ ] Task: Conductor - User Manual Verification 'Phase 3' (Protocol in workflow.md)
+- [x] Task: Add iOS targets to `core:database/build.gradle.kts`.
+- [x] Task: Verify iOS compilation (Skipped: Linux host).
+- [x] Task: Conductor - User Manual Verification 'Phase 3' (Protocol in workflow.md)
## Phase 4: Verification and Testing
- Update existing database tests in `commonTest`, `androidHostTest`, and `androidDeviceTest` to Room 3.
- Run tests on Android and Desktop to ensure no regressions in behavior.
- Perform manual verification on Android and Desktop apps to ensure the database initializes and functions correctly.
-- [ ] Task: Update and run DAO unit tests in `commonTest`.
-- [ ] Task: Run Android instrumented tests (`androidDeviceTest`).
-- [ ] Task: Manual verification on Desktop.
-- [ ] Task: Conductor - User Manual Verification 'Phase 4' (Protocol in workflow.md)
+- [x] Task: Update and run DAO unit tests in `commonTest`.
+- [x] Task: Run Android instrumented tests (`androidDeviceTest`).
+- [x] Task: Manual verification on Desktop.
+- [x] Task: Conductor - User Manual Verification 'Phase 4' (Protocol in workflow.md)
diff --git a/core/database/build.gradle.kts b/core/database/build.gradle.kts
index 1815335f2..386bf58b3 100644
--- a/core/database/build.gradle.kts
+++ b/core/database/build.gradle.kts
@@ -35,6 +35,8 @@ kotlin {
sourceSets {
commonMain.dependencies {
implementation(libs.androidx.sqlite.bundled)
+ implementation(libs.androidx.datastore.preferences)
+ implementation(libs.okio)
api(projects.core.common)
implementation(projects.core.di)
diff --git a/core/database/src/androidDeviceTest/kotlin/org/meshtastic/core/database/MeshtasticDatabaseTest.kt b/core/database/src/androidDeviceTest/kotlin/org/meshtastic/core/database/MeshtasticDatabaseTest.kt
index 0d46627fd..fcff867b0 100644
--- a/core/database/src/androidDeviceTest/kotlin/org/meshtastic/core/database/MeshtasticDatabaseTest.kt
+++ b/core/database/src/androidDeviceTest/kotlin/org/meshtastic/core/database/MeshtasticDatabaseTest.kt
@@ -16,8 +16,8 @@
*/
package org.meshtastic.core.database
-import androidx.room.Room
-import androidx.room.testing.MigrationTestHelper
+import androidx.room3.Room
+import androidx.room3.testing.MigrationTestHelper
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import org.junit.Rule
diff --git a/core/database/src/androidDeviceTest/kotlin/org/meshtastic/core/database/dao/NodeInfoDaoTest.kt b/core/database/src/androidDeviceTest/kotlin/org/meshtastic/core/database/dao/NodeInfoDaoTest.kt
index 2777135ed..155f7fcee 100644
--- a/core/database/src/androidDeviceTest/kotlin/org/meshtastic/core/database/dao/NodeInfoDaoTest.kt
+++ b/core/database/src/androidDeviceTest/kotlin/org/meshtastic/core/database/dao/NodeInfoDaoTest.kt
@@ -16,7 +16,7 @@
*/
package org.meshtastic.core.database.dao
-import androidx.room.Room
+import androidx.room3.Room
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import kotlinx.coroutines.flow.first
diff --git a/core/database/src/androidDeviceTest/kotlin/org/meshtastic/core/database/dao/PacketDaoTest.kt b/core/database/src/androidDeviceTest/kotlin/org/meshtastic/core/database/dao/PacketDaoTest.kt
index a75bfa07c..c67e9bc35 100644
--- a/core/database/src/androidDeviceTest/kotlin/org/meshtastic/core/database/dao/PacketDaoTest.kt
+++ b/core/database/src/androidDeviceTest/kotlin/org/meshtastic/core/database/dao/PacketDaoTest.kt
@@ -16,7 +16,7 @@
*/
package org.meshtastic.core.database.dao
-import androidx.room.Room
+import androidx.room3.Room
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import kotlinx.coroutines.flow.first
diff --git a/core/database/src/androidHostTest/kotlin/org/meshtastic/core/database/dao/MigrationTest.kt b/core/database/src/androidHostTest/kotlin/org/meshtastic/core/database/dao/MigrationTest.kt
index 507490c34..b1e99d974 100644
--- a/core/database/src/androidHostTest/kotlin/org/meshtastic/core/database/dao/MigrationTest.kt
+++ b/core/database/src/androidHostTest/kotlin/org/meshtastic/core/database/dao/MigrationTest.kt
@@ -16,7 +16,7 @@
*/
package org.meshtastic.core.database.dao
-import androidx.room.Room
+import androidx.room3.Room
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import kotlinx.coroutines.flow.first
diff --git a/core/database/src/androidMain/kotlin/org/meshtastic/core/database/DatabaseManager.kt b/core/database/src/androidMain/kotlin/org/meshtastic/core/database/DatabaseManager.kt
deleted file mode 100644
index 913524381..000000000
--- a/core/database/src/androidMain/kotlin/org/meshtastic/core/database/DatabaseManager.kt
+++ /dev/null
@@ -1,247 +0,0 @@
-/*
- * Copyright (c) 2025-2026 Meshtastic LLC
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-package org.meshtastic.core.database
-
-import android.app.Application
-import android.content.Context
-import android.content.SharedPreferences
-import androidx.room.Room
-import co.touchlab.kermit.Logger
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.SupervisorJob
-import kotlinx.coroutines.cancel
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.filterNotNull
-import kotlinx.coroutines.flow.stateIn
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.sync.Mutex
-import kotlinx.coroutines.sync.withLock
-import kotlinx.coroutines.withContext
-import org.koin.core.annotation.Single
-import org.meshtastic.core.common.util.nowMillis
-import org.meshtastic.core.database.MeshtasticDatabase.Companion.configureCommon
-import org.meshtastic.core.di.CoroutineDispatchers
-import java.io.File
-import org.meshtastic.core.common.database.DatabaseManager as SharedDatabaseManager
-
-/** Manages per-device Room database instances for node data, with LRU eviction. */
-@Single(binds = [DatabaseProvider::class, SharedDatabaseManager::class])
-@Suppress("TooManyFunctions")
-@OptIn(ExperimentalCoroutinesApi::class)
-open class DatabaseManager(private val app: Application, private val dispatchers: CoroutineDispatchers) :
- DatabaseProvider,
- SharedDatabaseManager {
- val prefs: SharedPreferences = app.getSharedPreferences("db-manager-prefs", Context.MODE_PRIVATE)
- private val managerScope = CoroutineScope(SupervisorJob() + dispatchers.default)
-
- private val mutex = Mutex()
-
- // Expose the DB cache limit as a reactive stream so UI can observe changes.
- private val _cacheLimit = MutableStateFlow(getCurrentCacheLimit())
- override val cacheLimit: StateFlow = _cacheLimit
-
- // Keep cache-limit StateFlow in sync if some other component updates SharedPreferences.
- private val prefsListener =
- SharedPreferences.OnSharedPreferenceChangeListener { _, key ->
- if (key == DatabaseConstants.CACHE_LIMIT_KEY) {
- _cacheLimit.value = getCurrentCacheLimit()
- }
- }
-
- init {
- prefs.registerOnSharedPreferenceChangeListener(prefsListener)
- }
-
- private val _currentDb = MutableStateFlow(null)
- override val currentDb: StateFlow =
- _currentDb.filterNotNull().stateIn(managerScope, SharingStarted.Eagerly, buildRoomDb(app, defaultDbName()))
-
- private val _currentAddress = MutableStateFlow(null)
- val currentAddress: StateFlow = _currentAddress
-
- private val dbCache = mutableMapOf() // key = dbName
-
- /** Initialize the active database for [address]. */
- suspend fun init(address: String?) {
- switchActiveDatabase(address)
- }
-
- /** Switch active database to the one associated with [address]. Serialized via mutex. */
- override suspend fun switchActiveDatabase(address: String?) = mutex.withLock {
- val dbName = buildDbName(address)
-
- // Remember the previously active DB name (any) so we can record its last-used time as well.
- val previousDbName = _currentDb.value?.let { buildDbName(_currentAddress.value) }
-
- // Fast path: no-op if already on this address
- if (_currentAddress.value == address && _currentDb.value != null) {
- markLastUsed(dbName)
- return@withLock
- }
-
- // Build/open Room DB off the main thread
- val db =
- dbCache[dbName]
- ?: withContext(dispatchers.io) { buildRoomDb(app, dbName) }.also { dbCache[dbName] = it }
-
- _currentDb.value = db
- _currentAddress.value = address
- markLastUsed(dbName)
- // Also mark the previous DB as used "just now" so LRU has an accurate, recent timestamp
- // even on first run after upgrade where no timestamp might exist yet.
- previousDbName?.let { markLastUsed(it) }
-
- // Defer LRU eviction so switch is not blocked by filesystem work
- managerScope.launch(dispatchers.io) { enforceCacheLimit(activeDbName = dbName) }
-
- // One-time cleanup: remove legacy DB if present and not active
- managerScope.launch(dispatchers.io) { cleanupLegacyDbIfNeeded(activeDbName = dbName) }
-
- Logger.i { "Switched active DB to ${anonymizeDbName(dbName)} for address ${anonymizeAddress(address)}" }
- }
-
- private val limitedIo = dispatchers.io.limitedParallelism(4)
-
- /** Execute [block] with the current DB instance. */
- override suspend fun withDb(block: suspend (MeshtasticDatabase) -> T): T? = withContext(limitedIo) {
- val db = _currentDb.value ?: return@withContext null
- val active = buildDbName(_currentAddress.value)
- markLastUsed(active)
- block(db)
- }
-
- /** Returns true if a database exists for the given device address. */
- override fun hasDatabaseFor(address: String?): Boolean {
- if (address.isNullOrBlank() || address == "n") return false
- val dbName = buildDbName(address)
- return getDbFile(app, dbName) != null
- }
-
- private fun markLastUsed(dbName: String) {
- prefs.edit().putLong(lastUsedKey(dbName), nowMillis).apply()
- }
-
- private fun lastUsed(dbName: String): Long {
- val k = lastUsedKey(dbName)
- val v = prefs.getLong(k, 0L)
- return if (v == 0L) getDbFile(app, dbName)?.lastModified() ?: 0L else v
- }
-
- private fun listExistingDbNames(): List {
- val base = app.getDatabasePath(DatabaseConstants.LEGACY_DB_NAME)
- val dir = base.parentFile ?: return emptyList()
- val names = dir.listFiles()?.mapNotNull { f -> f.name } ?: emptyList()
- return names
- .filter { it.startsWith(DatabaseConstants.DB_PREFIX) }
- .filterNot { it.endsWith("-wal") || it.endsWith("-shm") }
- .distinct()
- }
-
- private suspend fun enforceCacheLimit(activeDbName: String) = mutex.withLock {
- val limit = getCurrentCacheLimit()
- val all = listExistingDbNames()
- // Only enforce the limit over device-specific DBs; exclude legacy and default DBs
- val deviceDbs =
- all.filterNot { it == DatabaseConstants.LEGACY_DB_NAME || it == DatabaseConstants.DEFAULT_DB_NAME }
- Logger.d {
- "LRU check: limit=$limit, active=${anonymizeDbName(
- activeDbName,
- )}, deviceDbs=${deviceDbs.joinToString(", ") {
- anonymizeDbName(it)
- }}"
- }
- if (deviceDbs.size <= limit) return@withLock
- val usageSnapshot = deviceDbs.associateWith { lastUsed(it) }
- Logger.d {
- "LRU lastUsed(ms): ${usageSnapshot.entries.joinToString(", ") { (name, ts) ->
- "${anonymizeDbName(name)}=$ts"
- }}"
- }
- val victims = selectEvictionVictims(deviceDbs, activeDbName, limit, usageSnapshot)
- Logger.i { "LRU victims: ${victims.joinToString(", ") { anonymizeDbName(it) }}" }
- victims.forEach { name ->
- runCatching { dbCache.remove(name)?.close() }
- .onFailure { Logger.w(it) { "Failed to close database $name" } }
- app.deleteDatabase(name)
- prefs.edit().remove(lastUsedKey(name)).apply()
- Logger.i { "Evicted cached DB ${anonymizeDbName(name)}" }
- }
- }
-
- override fun getCurrentCacheLimit(): Int = prefs
- .getInt(DatabaseConstants.CACHE_LIMIT_KEY, DatabaseConstants.DEFAULT_CACHE_LIMIT)
- .coerceIn(DatabaseConstants.MIN_CACHE_LIMIT, DatabaseConstants.MAX_CACHE_LIMIT)
-
- override fun setCacheLimit(limit: Int) {
- val clamped = limit.coerceIn(DatabaseConstants.MIN_CACHE_LIMIT, DatabaseConstants.MAX_CACHE_LIMIT)
- if (clamped == getCurrentCacheLimit()) return
- prefs.edit().putInt(DatabaseConstants.CACHE_LIMIT_KEY, clamped).apply()
- _cacheLimit.value = clamped
- // Enforce asynchronously with current active DB protected
- val active = _currentDb.value?.let { buildDbName(_currentAddress.value) } ?: defaultDbName()
- managerScope.launch(dispatchers.io) { enforceCacheLimit(activeDbName = active) }
- }
-
- private suspend fun cleanupLegacyDbIfNeeded(activeDbName: String) = mutex.withLock {
- if (prefs.getBoolean(DatabaseConstants.LEGACY_DB_CLEANED_KEY, false)) return@withLock
- val legacy = DatabaseConstants.LEGACY_DB_NAME
- if (legacy == activeDbName) {
- // Never delete the active DB; mark as cleaned to avoid repeated checks
- prefs.edit().putBoolean(DatabaseConstants.LEGACY_DB_CLEANED_KEY, true).apply()
- return@withLock
- }
- val legacyFile = getDbFile(app, legacy)
- if (legacyFile != null) {
- runCatching { dbCache.remove(legacy)?.close() }
- .onFailure { Logger.w(it) { "Failed to close legacy database $legacy before deletion" } }
- val deleted = app.deleteDatabase(legacy)
- if (deleted) {
- Logger.i { "Deleted legacy DB ${anonymizeDbName(legacy)}" }
- } else {
- Logger.w { "Attempted to delete legacy DB $legacy but deleteDatabase returned false" }
- }
- }
- prefs.edit().putBoolean(DatabaseConstants.LEGACY_DB_CLEANED_KEY, true).apply()
- }
-
- /** Closes all open databases and cancels background work. */
- fun close() {
- managerScope.cancel()
- dbCache.values.forEach { it.close() }
- dbCache.clear()
- _currentDb.value = null
- }
-}
-
-// File-private helpers
-private fun defaultDbName(): String = DatabaseConstants.DEFAULT_DB_NAME
-
-private fun lastUsedKey(dbName: String) = "db_last_used:$dbName"
-
-private fun buildRoomDb(app: Application, dbName: String): MeshtasticDatabase =
- Room.databaseBuilder(
- context = app.applicationContext,
- name = app.getDatabasePath(dbName).absolutePath,
- factory = { MeshtasticDatabaseConstructor.initialize() },
- )
- .configureCommon()
- .build()
-
-private fun getDbFile(app: Application, dbName: String): File? = app.getDatabasePath(dbName).takeIf { it.exists() }
diff --git a/core/database/src/androidMain/kotlin/org/meshtastic/core/database/di/CoreDatabaseAndroidModule.kt b/core/database/src/androidMain/kotlin/org/meshtastic/core/database/di/CoreDatabaseAndroidModule.kt
index 26b56484c..cffb8a67e 100644
--- a/core/database/src/androidMain/kotlin/org/meshtastic/core/database/di/CoreDatabaseAndroidModule.kt
+++ b/core/database/src/androidMain/kotlin/org/meshtastic/core/database/di/CoreDatabaseAndroidModule.kt
@@ -16,9 +16,7 @@
*/
package org.meshtastic.core.database.di
-import org.koin.core.annotation.ComponentScan
import org.koin.core.annotation.Module
@Module
-@ComponentScan("org.meshtastic.core.database")
class CoreDatabaseAndroidModule
diff --git a/core/database/src/commonMain/kotlin/org/meshtastic/core/database/Converters.kt b/core/database/src/commonMain/kotlin/org/meshtastic/core/database/Converters.kt
index 3de320ae5..35746f68f 100644
--- a/core/database/src/commonMain/kotlin/org/meshtastic/core/database/Converters.kt
+++ b/core/database/src/commonMain/kotlin/org/meshtastic/core/database/Converters.kt
@@ -16,7 +16,7 @@
*/
package org.meshtastic.core.database
-import androidx.room.TypeConverter
+import androidx.room3.TypeConverter
import co.touchlab.kermit.Logger
import kotlinx.serialization.json.Json
import okio.ByteString
diff --git a/core/database/src/commonMain/kotlin/org/meshtastic/core/database/MeshtasticDatabase.kt b/core/database/src/commonMain/kotlin/org/meshtastic/core/database/MeshtasticDatabase.kt
index 95a43db00..29d9b17ec 100644
--- a/core/database/src/commonMain/kotlin/org/meshtastic/core/database/MeshtasticDatabase.kt
+++ b/core/database/src/commonMain/kotlin/org/meshtastic/core/database/MeshtasticDatabase.kt
@@ -16,13 +16,13 @@
*/
package org.meshtastic.core.database
-import androidx.room.AutoMigration
-import androidx.room.Database
-import androidx.room.DeleteColumn
-import androidx.room.DeleteTable
-import androidx.room.RoomDatabase
-import androidx.room.TypeConverters
-import androidx.room.migration.AutoMigrationSpec
+import androidx.room3.AutoMigration
+import androidx.room3.Database
+import androidx.room3.DeleteColumn
+import androidx.room3.DeleteTable
+import androidx.room3.RoomDatabase
+import androidx.room3.TypeConverters
+import androidx.room3.migration.AutoMigrationSpec
import androidx.sqlite.driver.bundled.BundledSQLiteDriver
import kotlinx.coroutines.Dispatchers
import org.meshtastic.core.database.dao.DeviceHardwareDao
@@ -99,8 +99,9 @@ import org.meshtastic.core.database.entity.TracerouteNodePositionEntity
version = 37,
exportSchema = true,
)
-@androidx.room.ConstructedBy(MeshtasticDatabaseConstructor::class)
+@androidx.room3.ConstructedBy(MeshtasticDatabaseConstructor::class)
@TypeConverters(Converters::class)
+@androidx.room3.DaoReturnTypeConverters(androidx.room3.paging.PagingSourceDaoReturnTypeConverter::class)
abstract class MeshtasticDatabase : RoomDatabase() {
abstract fun nodeInfoDao(): NodeInfoDao
diff --git a/core/database/src/commonMain/kotlin/org/meshtastic/core/database/MeshtasticDatabaseConstructor.kt b/core/database/src/commonMain/kotlin/org/meshtastic/core/database/MeshtasticDatabaseConstructor.kt
index 997fa9cc3..f98adcab1 100644
--- a/core/database/src/commonMain/kotlin/org/meshtastic/core/database/MeshtasticDatabaseConstructor.kt
+++ b/core/database/src/commonMain/kotlin/org/meshtastic/core/database/MeshtasticDatabaseConstructor.kt
@@ -16,7 +16,7 @@
*/
package org.meshtastic.core.database
-import androidx.room.RoomDatabaseConstructor
+import androidx.room3.RoomDatabaseConstructor
@Suppress("NO_ACTUAL_FOR_EXPECT", "KotlinNoActualForExpect")
expect object MeshtasticDatabaseConstructor : RoomDatabaseConstructor {
diff --git a/core/database/src/commonMain/kotlin/org/meshtastic/core/database/dao/DeviceHardwareDao.kt b/core/database/src/commonMain/kotlin/org/meshtastic/core/database/dao/DeviceHardwareDao.kt
index 5d6b4ea94..fcdc079f2 100644
--- a/core/database/src/commonMain/kotlin/org/meshtastic/core/database/dao/DeviceHardwareDao.kt
+++ b/core/database/src/commonMain/kotlin/org/meshtastic/core/database/dao/DeviceHardwareDao.kt
@@ -16,10 +16,10 @@
*/
package org.meshtastic.core.database.dao
-import androidx.room.Dao
-import androidx.room.Insert
-import androidx.room.OnConflictStrategy
-import androidx.room.Query
+import androidx.room3.Dao
+import androidx.room3.Insert
+import androidx.room3.OnConflictStrategy
+import androidx.room3.Query
import org.meshtastic.core.database.entity.DeviceHardwareEntity
@Dao
diff --git a/core/database/src/commonMain/kotlin/org/meshtastic/core/database/dao/FirmwareReleaseDao.kt b/core/database/src/commonMain/kotlin/org/meshtastic/core/database/dao/FirmwareReleaseDao.kt
index dfaa30eea..0a5520a07 100644
--- a/core/database/src/commonMain/kotlin/org/meshtastic/core/database/dao/FirmwareReleaseDao.kt
+++ b/core/database/src/commonMain/kotlin/org/meshtastic/core/database/dao/FirmwareReleaseDao.kt
@@ -16,10 +16,10 @@
*/
package org.meshtastic.core.database.dao
-import androidx.room.Dao
-import androidx.room.Insert
-import androidx.room.OnConflictStrategy
-import androidx.room.Query
+import androidx.room3.Dao
+import androidx.room3.Insert
+import androidx.room3.OnConflictStrategy
+import androidx.room3.Query
import org.meshtastic.core.database.entity.FirmwareReleaseEntity
import org.meshtastic.core.database.entity.FirmwareReleaseType
diff --git a/core/database/src/commonMain/kotlin/org/meshtastic/core/database/dao/MeshLogDao.kt b/core/database/src/commonMain/kotlin/org/meshtastic/core/database/dao/MeshLogDao.kt
index 669f86aee..967a97ec5 100644
--- a/core/database/src/commonMain/kotlin/org/meshtastic/core/database/dao/MeshLogDao.kt
+++ b/core/database/src/commonMain/kotlin/org/meshtastic/core/database/dao/MeshLogDao.kt
@@ -16,9 +16,9 @@
*/
package org.meshtastic.core.database.dao
-import androidx.room.Dao
-import androidx.room.Insert
-import androidx.room.Query
+import androidx.room3.Dao
+import androidx.room3.Insert
+import androidx.room3.Query
import kotlinx.coroutines.flow.Flow
import org.meshtastic.core.database.entity.MeshLog
diff --git a/core/database/src/commonMain/kotlin/org/meshtastic/core/database/dao/NodeInfoDao.kt b/core/database/src/commonMain/kotlin/org/meshtastic/core/database/dao/NodeInfoDao.kt
index 9a09c3bdf..752619014 100644
--- a/core/database/src/commonMain/kotlin/org/meshtastic/core/database/dao/NodeInfoDao.kt
+++ b/core/database/src/commonMain/kotlin/org/meshtastic/core/database/dao/NodeInfoDao.kt
@@ -16,13 +16,13 @@
*/
package org.meshtastic.core.database.dao
-import androidx.room.Dao
-import androidx.room.Insert
-import androidx.room.MapColumn
-import androidx.room.OnConflictStrategy
-import androidx.room.Query
-import androidx.room.Transaction
-import androidx.room.Upsert
+import androidx.room3.Dao
+import androidx.room3.Insert
+import androidx.room3.MapColumn
+import androidx.room3.OnConflictStrategy
+import androidx.room3.Query
+import androidx.room3.Transaction
+import androidx.room3.Upsert
import kotlinx.coroutines.flow.Flow
import okio.ByteString
import org.meshtastic.core.database.entity.MetadataEntity
diff --git a/core/database/src/commonMain/kotlin/org/meshtastic/core/database/dao/PacketDao.kt b/core/database/src/commonMain/kotlin/org/meshtastic/core/database/dao/PacketDao.kt
index f8d6947ad..ae6506cbf 100644
--- a/core/database/src/commonMain/kotlin/org/meshtastic/core/database/dao/PacketDao.kt
+++ b/core/database/src/commonMain/kotlin/org/meshtastic/core/database/dao/PacketDao.kt
@@ -17,12 +17,12 @@
package org.meshtastic.core.database.dao
import androidx.paging.PagingSource
-import androidx.room.Dao
-import androidx.room.MapColumn
-import androidx.room.Query
-import androidx.room.Transaction
-import androidx.room.Update
-import androidx.room.Upsert
+import androidx.room3.Dao
+import androidx.room3.MapColumn
+import androidx.room3.Query
+import androidx.room3.Transaction
+import androidx.room3.Update
+import androidx.room3.Upsert
import kotlinx.coroutines.flow.Flow
import okio.ByteString
import org.meshtastic.core.common.util.nowMillis
diff --git a/core/database/src/commonMain/kotlin/org/meshtastic/core/database/dao/QuickChatActionDao.kt b/core/database/src/commonMain/kotlin/org/meshtastic/core/database/dao/QuickChatActionDao.kt
index 8a8f6ded7..177d71dfb 100644
--- a/core/database/src/commonMain/kotlin/org/meshtastic/core/database/dao/QuickChatActionDao.kt
+++ b/core/database/src/commonMain/kotlin/org/meshtastic/core/database/dao/QuickChatActionDao.kt
@@ -16,10 +16,10 @@
*/
package org.meshtastic.core.database.dao
-import androidx.room.Dao
-import androidx.room.Query
-import androidx.room.Transaction
-import androidx.room.Upsert
+import androidx.room3.Dao
+import androidx.room3.Query
+import androidx.room3.Transaction
+import androidx.room3.Upsert
import kotlinx.coroutines.flow.Flow
import org.meshtastic.core.database.entity.QuickChatAction
diff --git a/core/database/src/commonMain/kotlin/org/meshtastic/core/database/dao/TracerouteNodePositionDao.kt b/core/database/src/commonMain/kotlin/org/meshtastic/core/database/dao/TracerouteNodePositionDao.kt
index 863a42440..2e7f6c549 100644
--- a/core/database/src/commonMain/kotlin/org/meshtastic/core/database/dao/TracerouteNodePositionDao.kt
+++ b/core/database/src/commonMain/kotlin/org/meshtastic/core/database/dao/TracerouteNodePositionDao.kt
@@ -16,10 +16,10 @@
*/
package org.meshtastic.core.database.dao
-import androidx.room.Dao
-import androidx.room.Insert
-import androidx.room.OnConflictStrategy
-import androidx.room.Query
+import androidx.room3.Dao
+import androidx.room3.Insert
+import androidx.room3.OnConflictStrategy
+import androidx.room3.Query
import kotlinx.coroutines.flow.Flow
import org.meshtastic.core.database.entity.TracerouteNodePositionEntity
diff --git a/core/database/src/commonMain/kotlin/org/meshtastic/core/database/di/CoreDatabaseModule.kt b/core/database/src/commonMain/kotlin/org/meshtastic/core/database/di/CoreDatabaseModule.kt
index 5626c6269..acae365da 100644
--- a/core/database/src/commonMain/kotlin/org/meshtastic/core/database/di/CoreDatabaseModule.kt
+++ b/core/database/src/commonMain/kotlin/org/meshtastic/core/database/di/CoreDatabaseModule.kt
@@ -18,7 +18,14 @@ package org.meshtastic.core.database.di
import org.koin.core.annotation.ComponentScan
import org.koin.core.annotation.Module
+import org.koin.core.annotation.Named
+import org.koin.core.annotation.Single
+import org.meshtastic.core.database.createDatabaseDataStore
@Module
@ComponentScan("org.meshtastic.core.database")
-class CoreDatabaseModule
+class CoreDatabaseModule {
+ @Single
+ @Named("DatabaseDataStore")
+ fun provideDatabaseDataStore() = createDatabaseDataStore("db-manager-prefs")
+}
diff --git a/core/database/src/commonMain/kotlin/org/meshtastic/core/database/entity/DeviceHardwareEntity.kt b/core/database/src/commonMain/kotlin/org/meshtastic/core/database/entity/DeviceHardwareEntity.kt
index 101b62255..09af174fe 100644
--- a/core/database/src/commonMain/kotlin/org/meshtastic/core/database/entity/DeviceHardwareEntity.kt
+++ b/core/database/src/commonMain/kotlin/org/meshtastic/core/database/entity/DeviceHardwareEntity.kt
@@ -16,9 +16,9 @@
*/
package org.meshtastic.core.database.entity
-import androidx.room.ColumnInfo
-import androidx.room.Entity
-import androidx.room.PrimaryKey
+import androidx.room3.ColumnInfo
+import androidx.room3.Entity
+import androidx.room3.PrimaryKey
import kotlinx.serialization.Serializable
import org.meshtastic.core.common.util.nowMillis
import org.meshtastic.core.model.DeviceHardware
diff --git a/core/database/src/commonMain/kotlin/org/meshtastic/core/database/entity/FirmwareReleaseEntity.kt b/core/database/src/commonMain/kotlin/org/meshtastic/core/database/entity/FirmwareReleaseEntity.kt
index 113435616..c3eabaf77 100644
--- a/core/database/src/commonMain/kotlin/org/meshtastic/core/database/entity/FirmwareReleaseEntity.kt
+++ b/core/database/src/commonMain/kotlin/org/meshtastic/core/database/entity/FirmwareReleaseEntity.kt
@@ -16,9 +16,9 @@
*/
package org.meshtastic.core.database.entity
-import androidx.room.ColumnInfo
-import androidx.room.Entity
-import androidx.room.PrimaryKey
+import androidx.room3.ColumnInfo
+import androidx.room3.Entity
+import androidx.room3.PrimaryKey
import kotlinx.serialization.Serializable
import org.meshtastic.core.common.util.nowMillis
import org.meshtastic.core.model.DeviceVersion
diff --git a/core/database/src/commonMain/kotlin/org/meshtastic/core/database/entity/MeshLog.kt b/core/database/src/commonMain/kotlin/org/meshtastic/core/database/entity/MeshLog.kt
index db23720cd..2f102c0ea 100644
--- a/core/database/src/commonMain/kotlin/org/meshtastic/core/database/entity/MeshLog.kt
+++ b/core/database/src/commonMain/kotlin/org/meshtastic/core/database/entity/MeshLog.kt
@@ -16,10 +16,10 @@
*/
package org.meshtastic.core.database.entity
-import androidx.room.ColumnInfo
-import androidx.room.Entity
-import androidx.room.Index
-import androidx.room.PrimaryKey
+import androidx.room3.ColumnInfo
+import androidx.room3.Entity
+import androidx.room3.Index
+import androidx.room3.PrimaryKey
import co.touchlab.kermit.Logger
import org.meshtastic.core.model.util.decodeOrNull
import org.meshtastic.proto.FromRadio
diff --git a/core/database/src/commonMain/kotlin/org/meshtastic/core/database/entity/MyNodeEntity.kt b/core/database/src/commonMain/kotlin/org/meshtastic/core/database/entity/MyNodeEntity.kt
index 9140754f2..ef2226ffc 100644
--- a/core/database/src/commonMain/kotlin/org/meshtastic/core/database/entity/MyNodeEntity.kt
+++ b/core/database/src/commonMain/kotlin/org/meshtastic/core/database/entity/MyNodeEntity.kt
@@ -16,8 +16,8 @@
*/
package org.meshtastic.core.database.entity
-import androidx.room.Entity
-import androidx.room.PrimaryKey
+import androidx.room3.Entity
+import androidx.room3.PrimaryKey
import org.meshtastic.core.model.MyNodeInfo
@Entity(tableName = "my_node")
diff --git a/core/database/src/commonMain/kotlin/org/meshtastic/core/database/entity/NodeEntity.kt b/core/database/src/commonMain/kotlin/org/meshtastic/core/database/entity/NodeEntity.kt
index cb4bf06d2..13d10193c 100644
--- a/core/database/src/commonMain/kotlin/org/meshtastic/core/database/entity/NodeEntity.kt
+++ b/core/database/src/commonMain/kotlin/org/meshtastic/core/database/entity/NodeEntity.kt
@@ -16,12 +16,12 @@
*/
package org.meshtastic.core.database.entity
-import androidx.room.ColumnInfo
-import androidx.room.Embedded
-import androidx.room.Entity
-import androidx.room.Index
-import androidx.room.PrimaryKey
-import androidx.room.Relation
+import androidx.room3.ColumnInfo
+import androidx.room3.Embedded
+import androidx.room3.Entity
+import androidx.room3.Index
+import androidx.room3.PrimaryKey
+import androidx.room3.Relation
import okio.ByteString
import okio.ByteString.Companion.toByteString
import org.meshtastic.core.common.util.nowMillis
diff --git a/core/database/src/commonMain/kotlin/org/meshtastic/core/database/entity/Packet.kt b/core/database/src/commonMain/kotlin/org/meshtastic/core/database/entity/Packet.kt
index 5529b9606..859913c23 100644
--- a/core/database/src/commonMain/kotlin/org/meshtastic/core/database/entity/Packet.kt
+++ b/core/database/src/commonMain/kotlin/org/meshtastic/core/database/entity/Packet.kt
@@ -16,12 +16,12 @@
*/
package org.meshtastic.core.database.entity
-import androidx.room.ColumnInfo
-import androidx.room.Embedded
-import androidx.room.Entity
-import androidx.room.Index
-import androidx.room.PrimaryKey
-import androidx.room.Relation
+import androidx.room3.ColumnInfo
+import androidx.room3.Embedded
+import androidx.room3.Entity
+import androidx.room3.Index
+import androidx.room3.PrimaryKey
+import androidx.room3.Relation
import okio.ByteString
import org.meshtastic.core.common.util.nowMillis
import org.meshtastic.core.model.DataPacket
diff --git a/core/database/src/commonMain/kotlin/org/meshtastic/core/database/entity/QuickChatAction.kt b/core/database/src/commonMain/kotlin/org/meshtastic/core/database/entity/QuickChatAction.kt
index fbcaba95d..afa565cc1 100644
--- a/core/database/src/commonMain/kotlin/org/meshtastic/core/database/entity/QuickChatAction.kt
+++ b/core/database/src/commonMain/kotlin/org/meshtastic/core/database/entity/QuickChatAction.kt
@@ -16,9 +16,9 @@
*/
package org.meshtastic.core.database.entity
-import androidx.room.ColumnInfo
-import androidx.room.Entity
-import androidx.room.PrimaryKey
+import androidx.room3.ColumnInfo
+import androidx.room3.Entity
+import androidx.room3.PrimaryKey
@Entity(tableName = "quick_chat")
data class QuickChatAction(
diff --git a/core/database/src/commonMain/kotlin/org/meshtastic/core/database/entity/TracerouteNodePositionEntity.kt b/core/database/src/commonMain/kotlin/org/meshtastic/core/database/entity/TracerouteNodePositionEntity.kt
index 3712978a2..ddae980fa 100644
--- a/core/database/src/commonMain/kotlin/org/meshtastic/core/database/entity/TracerouteNodePositionEntity.kt
+++ b/core/database/src/commonMain/kotlin/org/meshtastic/core/database/entity/TracerouteNodePositionEntity.kt
@@ -16,10 +16,10 @@
*/
package org.meshtastic.core.database.entity
-import androidx.room.ColumnInfo
-import androidx.room.Entity
-import androidx.room.ForeignKey
-import androidx.room.Index
+import androidx.room3.ColumnInfo
+import androidx.room3.Entity
+import androidx.room3.ForeignKey
+import androidx.room3.Index
import org.meshtastic.proto.Position
@Entity(
diff --git a/desktop/src/main/kotlin/org/meshtastic/desktop/di/DesktopPlatformModule.kt b/desktop/src/main/kotlin/org/meshtastic/desktop/di/DesktopPlatformModule.kt
index c5f5a33f8..384a102ac 100644
--- a/desktop/src/main/kotlin/org/meshtastic/desktop/di/DesktopPlatformModule.kt
+++ b/desktop/src/main/kotlin/org/meshtastic/desktop/di/DesktopPlatformModule.kt
@@ -26,22 +26,14 @@ import androidx.datastore.preferences.core.emptyPreferences
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LifecycleRegistry
-import androidx.room.Room
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.StateFlow
import okio.FileSystem
import okio.Path.Companion.toPath
import org.koin.core.qualifier.named
import org.koin.dsl.module
import org.meshtastic.core.common.BuildConfigProvider
-import org.meshtastic.core.common.database.DatabaseManager
-import org.meshtastic.core.database.DatabaseProvider
-import org.meshtastic.core.database.MeshtasticDatabase
-import org.meshtastic.core.database.MeshtasticDatabase.Companion.configureCommon
-import org.meshtastic.core.database.MeshtasticDatabaseConstructor
import org.meshtastic.core.datastore.serializer.ChannelSetSerializer
import org.meshtastic.core.datastore.serializer.LocalConfigSerializer
import org.meshtastic.core.datastore.serializer.LocalStatsSerializer
@@ -50,6 +42,7 @@ import org.meshtastic.proto.ChannelSet
import org.meshtastic.proto.LocalConfig
import org.meshtastic.proto.LocalModuleConfig
import org.meshtastic.proto.LocalStats
+import java.io.File
/**
* Resolves the desktop data directory for persistent storage (DataStore files, Room database). Defaults to
@@ -72,52 +65,6 @@ private fun createPreferencesDataStore(name: String, scope: CoroutineScope): Dat
)
}
-/**
- * Desktop Room KMP database provider. Builds a single file-backed SQLite database using [MeshtasticDatabaseConstructor]
- * and [BundledSQLiteDriver] (both KMP-ready).
- */
-class DesktopDatabaseManager :
- DatabaseProvider,
- DatabaseManager {
- private val dir = desktopDataDir()
- private val dbName = "$dir/meshtastic.db"
-
- private val db: MeshtasticDatabase by lazy {
- FileSystem.SYSTEM.createDirectories(dir.toPath())
- Room.databaseBuilder(name = dbName) { MeshtasticDatabaseConstructor.initialize() }
- .configureCommon()
- .build()
- }
-
- override val currentDb: StateFlow by lazy { MutableStateFlow(db) }
-
- override suspend fun withDb(block: suspend (MeshtasticDatabase) -> T): T? = block(db)
-
- private val _cacheLimit = MutableStateFlow(DEFAULT_CACHE_LIMIT)
- override val cacheLimit: StateFlow = _cacheLimit
-
- override fun getCurrentCacheLimit(): Int = _cacheLimit.value
-
- override fun setCacheLimit(limit: Int) {
- _cacheLimit.value = limit.coerceIn(MIN_LIMIT, MAX_LIMIT)
- }
-
- override suspend fun switchActiveDatabase(address: String?) {
- // Desktop uses a single database — no per-device switching
- }
-
- override fun hasDatabaseFor(address: String?): Boolean {
- // Desktop always has the single database available
- return !address.isNullOrBlank() && address != "n"
- }
-
- companion object {
- private const val DEFAULT_CACHE_LIMIT = 100
- private const val MIN_LIMIT = 1
- private const val MAX_LIMIT = 100
- }
-}
-
/**
* Synthetic [LifecycleOwner] that stays permanently in [Lifecycle.State.RESUMED]. Replaces Android's
* `ProcessLifecycleOwner` for desktop.
@@ -139,7 +86,6 @@ private class DesktopProcessLifecycleOwner : LifecycleOwner {
* Provides all platform-specific bindings that the real KMP `commonMain` implementations need:
* - Named [DataStore]<[Preferences]> instances (12 preference stores + 1 core preferences store)
* - Proto [DataStore] instances (LocalConfig, ModuleConfig, ChannelSet, LocalStats)
- * - [DatabaseProvider] and [DatabaseManager] via Room KMP
* - [Lifecycle] (`ProcessLifecycle`)
* - [BuildConfigProvider]
*/
@@ -147,8 +93,6 @@ private class DesktopProcessLifecycleOwner : LifecycleOwner {
fun desktopPlatformModule() = module {
includes(desktopPreferencesDataStoreModule(), desktopProtoDataStoreModule())
- val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
-
// -- Build config --
single {
object : BuildConfigProvider {
@@ -163,11 +107,6 @@ fun desktopPlatformModule() = module {
// -- Process Lifecycle (stays RESUMED forever on desktop) --
single(named("ProcessLifecycle")) { DesktopProcessLifecycleOwner().lifecycle }
-
- // -- Database (Room KMP with BundledSQLiteDriver) --
- single { DesktopDatabaseManager() }
- single { get() }
- single { get() }
}
/** Named [DataStore]<[Preferences]> instances for all preference domains. */
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 1a1672e78..6d0b32372 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -13,7 +13,7 @@ jetbrains-lifecycle = "2.10.0"
navigation = "2.9.7"
navigation3 = "1.1.0-alpha04"
paging = "3.4.2"
-room = "2.8.4"
+room = "3.0.0-alpha01"
savedstate = "1.4.0"
koin = "4.2.0"
koin-annotations = "2.1.0"
@@ -109,11 +109,11 @@ jetbrains-navigation3-runtime = { module = "org.jetbrains.androidx.navigation3:n
jetbrains-navigation3-ui = { module = "org.jetbrains.androidx.navigation3:navigation3-ui", version.ref = "navigation3" }
androidx-paging-common = { module = "androidx.paging:paging-common", version.ref = "paging" }
androidx-paging-compose = { module = "androidx.paging:paging-compose", version.ref = "paging" }
-androidx-room-compiler = { module = "androidx.room:room-compiler", version.ref = "room" }
-androidx-room-paging = { module = "androidx.room:room-paging", version.ref = "room" }
-androidx-room-runtime = { module = "androidx.room:room-runtime", version.ref = "room" }
-androidx-room-testing = { module = "androidx.room:room-testing", version.ref = "room" }
-androidx-sqlite-bundled = { module = "androidx.sqlite:sqlite-bundled", version = "2.6.2" }
+androidx-room-compiler = { module = "androidx.room3:room3-compiler", version.ref = "room" }
+androidx-room-paging = { module = "androidx.room3:room3-paging", version.ref = "room" }
+androidx-room-runtime = { module = "androidx.room3:room3-runtime", version.ref = "room" }
+androidx-room-testing = { module = "androidx.room3:room3-testing", version.ref = "room" }
+androidx-sqlite-bundled = { module = "androidx.sqlite:sqlite-bundled", version = "2.5.0" }
androidx-savedstate-compose = { module = "androidx.savedstate:savedstate-compose", version.ref = "savedstate" }
androidx-savedstate-ktx = { module = "androidx.savedstate:savedstate-ktx", version.ref = "savedstate" }
androidx-work-runtime-ktx = { module = "androidx.work:work-runtime-ktx", version = "2.11.1" }
@@ -244,7 +244,7 @@ vico-compose-m3 = { group = "com.patrykandpatrick.vico", name = "compose-m3", ve
# Build Logic
android-gradleApiPlugin = { module = "com.android.tools.build:gradle-api", version.ref = "agp" }
android-tools-common = { module = "com.android.tools:common", version = "32.1.0" }
-androidx-room-gradlePlugin = { module = "androidx.room:room-gradle-plugin", version.ref = "room" }
+androidx-room-gradlePlugin = { module = "androidx.room3:room3-gradle-plugin", version.ref = "room" }
compose-gradlePlugin = { module = "org.jetbrains.kotlin:compose-compiler-gradle-plugin", version.ref = "kotlin" }
compose-multiplatform-gradlePlugin = { module = "org.jetbrains.compose:compose-gradle-plugin", version.ref = "compose-multiplatform" }
datadog-gradlePlugin = { module = "com.datadoghq.dd-sdk-android-gradle-plugin:com.datadoghq.dd-sdk-android-gradle-plugin.gradle.plugin", version.ref = "datadog-gradle" }
@@ -296,7 +296,7 @@ datadog = { id = "com.datadoghq.dd-sdk-android-gradle-plugin", version.ref = "da
detekt = { id = "io.gitlab.arturbosch.detekt", version.ref = "detekt" }
dokka = { id = "org.jetbrains.dokka", version.ref = "dokka" }
wire = { id = "com.squareup.wire", version.ref = "wire" }
-room = { id = "androidx.room", version.ref = "room" }
+room = { id = "androidx.room3", version.ref = "room" }
spotless = { id = "com.diffplug.spotless", version.ref = "spotless" }
test-retry = { id = "org.gradle.test-retry", version.ref = "testRetry" }
dependency-guard = { id = "com.dropbox.dependency-guard", version.ref = "dependency-guard" }