feat(database): enhance Room database annotations and add new color properties#109
feat(database): enhance Room database annotations and add new color properties#109
Conversation
|
Note Reviews pausedUse the following commands to manage reviews:
📝 WalkthroughWalkthroughThis PR restructures the cross-platform Room database abstraction by consolidating Room annotations into a central expect file with corresponding actual typealiases, adds fixed color tokens to the design system, updates theme extensions and default colors, and refines various utility APIs across the codebase. Changes
Poem
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes 🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
@coderabbitai disable |
There was a problem hiding this comment.
Actionable comments posted: 6
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (4)
core/ui/src/commonMain/kotlin/org/mifos/core/ui/RevealSwipe.kt (1)
536-548:⚠️ Potential issue | 🟠 MajorMigrate RevealState away from deprecated AnchoredDraggableState constructor parameters.
The
@Suppress("DEPRECATION")annotation masks use of deprecatedAnchoredDraggableStateconstructor parameters that were deprecated in Compose Foundation 1.8.0-alpha06 (November 2024). The affected parameters are:
positionalThreshold,velocityThresholdsnapAnimationSpec,decayAnimationSpecconfirmValueChangePer Compose Foundation guidance, migrate this configuration to use
AnchoredDraggableDefaults.flingBehavior()and pass it viaModifier.anchoredDraggable()instead of constructor parameters. ForconfirmValueChange, model allowed states by controlling which anchors exist in the active anchor set rather than using a callback veto.core-base/designsystem/src/commonMain/kotlin/template/core/base/designsystem/component/KptTopAppBar.kt (1)
383-394:⚠️ Potential issue | 🟠 Major
onNavigationIconClickparameter is accepted but ignored.Both
KptMediumTopAppBarandKptLargeTopAppBardeclareonNavigationIconClickas a parameter but never pass it to the underlyingKptTopAppBarcall. This creates a misleading API where callers expect navigation behavior that won't occur.🐛 Proposed fix to use the navigation callback
`@Composable` fun KptMediumTopAppBar( title: String, onNavigationIconClick: (() -> Unit)? = null, modifier: Modifier = Modifier, -) = KptTopAppBar(title, modifier, TopAppBarVariant.Medium) +) = onNavigationIconClick?.let { + KptTopAppBar( + title = title, + onNavigationIconClick = it, + modifier = modifier, + variant = TopAppBarVariant.Medium, + ) +} ?: KptTopAppBar(title, modifier, TopAppBarVariant.Medium) `@Composable` fun KptLargeTopAppBar( title: String, onNavigationIconClick: (() -> Unit)? = null, modifier: Modifier = Modifier, -) = KptTopAppBar(title, modifier, TopAppBarVariant.Large) +) = onNavigationIconClick?.let { + KptTopAppBar( + title = title, + onNavigationIconClick = it, + modifier = modifier, + variant = TopAppBarVariant.Large, + ) +} ?: KptTopAppBar(title, modifier, TopAppBarVariant.Large)core-base/designsystem/src/commonMain/kotlin/template/core/base/designsystem/theme/KptColorSchemeImpl.kt (1)
259-310:⚠️ Potential issue | 🟡 Minor
KptColorSchemeBuilderis missing the new fixed color properties.The builder doesn't expose the newly added fixed color tokens (
primaryFixed,primaryFixedDim,onPrimaryFixed, etc.), preventing users from customizing these values through the DSL. This creates an inconsistency where the implementation supports these colors but the builder API doesn't.🔧 Proposed fix to add missing properties
Add the following properties to
KptColorSchemeBuilder:var primaryFixed: Color = Color(0xFFD0BCFF) var primaryFixedDim: Color = Color(0xFFA694D1) var onPrimaryFixed: Color = Color(0xFF21005D) var onPrimaryFixedVariant: Color = Color(0xFF49307B) var secondaryFixed: Color = Color(0xFFCCC2DC) var secondaryFixedDim: Color = Color(0xFF9A8F9E) var onSecondaryFixed: Color = Color(0xFF1D192B) var onSecondaryFixedVariant: Color = Color(0xFF51445D) var tertiaryFixed: Color = Color(0xFFFFD8E4) var tertiaryFixedDim: Color = Color(0xFFFFB1C8) var onTertiaryFixed: Color = Color(0xFF31111D) var onTertiaryFixedVariant: Color = Color(0xFF4D2733)And update
build()to include them in theKptColorSchemeImplconstructor call.core-base/database/src/nonJsCommonMain/kotlin/template/core/base/database/Room.nonJsCommon.kt (1)
28-139:⚠️ Potential issue | 🔴 CriticalRemove the
TypeConverteractual typealias or add the corresponding expect declaration in commonMain.The nonJsCommon file defines
actual typealias TypeConverter = TypeConverter(line 138), but commonMain contains no correspondingexpectdeclaration. OnlyTypeConverters(plural) exists in commonMain. This mismatch will cause a multiplatform compilation error. Either addexpect typealias TypeConverterto commonMain, or remove the actual typealias from nonJsCommon.
🤖 Fix all issues with AI agents
In @.gitignore:
- Line 93: Remove the redundant ignore entry "fastlane/fastlane/metadata/" from
.gitignore — it targets a non-existent directory and duplicates the existing
rule "fastlane/metadata/"; open .gitignore, delete the line containing
"fastlane/fastlane/metadata/", save, and ensure the intended
"fastlane/metadata/" ignore rule remains intact.
In
`@core-base/database/src/commonMain/kotlin/template/core/base/database/Room.kt`:
- Around line 323-329: Update the KDoc example to reference the correct
collation constants by replacing the incorrect ColumnInfo.NOCASE with
CollationSequence.NOCASE (or the appropriate constant defined in
CollationSequence); ensure the example uses ColumnInfo(collate =
CollationSequence.NOCASE) or similar so the symbols ColumnInfo.collate and
CollationSequence are consistent with the declarations in this file and the User
data class example shows the correct usage.
- Around line 334-686: The annotation expect declarations lack default parameter
values required to match androidx.room usage; update the signatures to provide
defaults: add defaults for ColumnInfo (typeAffinity, index, collate,
defaultValue), Embedded(prefix = ""), Relation(associateBy = Junction(),
projection = arrayOf()), Junction(value = Any::class /* or KClass default
placeholder */, parentColumn = "", entityColumn = ""),
TypeConverters(builtInTypeConverters = BuiltInTypeConverters()),
BuiltInTypeConverters(enums = State.INHERITED, uuid = State.INHERITED,
byteBuffer = State.INHERITED), Database(exportSchema = true, views =
emptyArray(), autoMigrations = emptyArray()), AutoMigration(spec = Any::class /*
or KClass default placeholder */), and DatabaseView(viewName = ""); change the
expect annotation parameter defaults for ColumnInfo, Embedded, Relation,
Junction, TypeConverters, BuiltInTypeConverters, Database, AutoMigration, and
DatabaseView so their signatures (e.g., ColumnInfo, Embedded, Relation,
Junction, TypeConverters, BuiltInTypeConverters, Database, AutoMigration,
DatabaseView) match Room's defaults and allow parameter omission. Ensure
defaults use valid types (KClass placeholders where needed) consistent with
Kotlin expect declarations.
- Around line 205-280: The expect annotations Update, Delete, and Upsert require
parameters but should provide Room-equivalent defaults so no-arg usage (e.g.
`@Update`) compiles; update the signatures for Update, Delete, and Upsert to give
entity a default (e.g. val entity: KClass<*> = Any::class) and set Update's
onConflict default to Room's default (val onConflict: Int =
OnConflictStrategy.ABORT), ensuring the annotation declarations (Update, Delete,
Upsert) accept no arguments as in Room.
In
`@core-base/designsystem/src/commonMain/kotlin/template/core/base/designsystem/KptThemeExtensions.kt`:
- Line 298: The theme mapping is hardcoding surfaceTint to primary; update the
mapping to use the KptColorScheme.surfaceTint property instead of primary so
consumers that override surfaceTint are respected—locate the mapping function
(e.g., where KptColorScheme is converted to the platform/Material color scheme
in KptThemeExtensions.kt) and replace the literal primary assignment
(surfaceTint = primary) with the corresponding source property (surfaceTint =
kptColorScheme.surfaceTint or the local variable that refers to the
KptColorScheme instance).
In `@core/common/src/commonMain/kotlin/org/mifos/core/common/FormatDate.kt`:
- Around line 15-24: The function formatDate fails because it imports
kotlin.time.Instant and then calls toLocalDateTime which expects
kotlinx.datetime.Instant; fix by importing kotlinx.datetime.Instant and
kotlinx.datetime.TimeZone (or convert the kotlin.time.Instant to a
kotlinx.datetime.Instant before calling toLocalDateTime), and change the day
access from dateTime.day to dateTime.date.day; keep month.number and year as-is.
Ensure the symbols referenced are formatDate, Instant, toLocalDateTime,
TimeZone, LocalDateTime/date, and use dateTime.date.day for the day component.
| /** | ||
| * Cross-platform annotation for marking DAO methods that update entities in the database. | ||
| * | ||
| * This annotation is used to update existing entities in the database. | ||
| * The method can accept single entities, lists of entities, or arrays of entities. | ||
| * By default, it uses the primary key to identify which rows to update. | ||
| * | ||
| * @param entity The entity class that this method updates (used for type checking) | ||
| * @param onConflict Strategy to use when there's a conflict during update | ||
| * | ||
| * Example: | ||
| * ```kotlin | ||
| * @Update(onConflict = OnConflictStrategy.REPLACE) | ||
| * suspend fun updateUser(user: User): Int | ||
| * | ||
| * @Update | ||
| * suspend fun updateUsers(users: List<User>): Int | ||
| * ``` | ||
| */ | ||
| @Suppress("NO_ACTUAL_FOR_EXPECT") | ||
| @Target(AnnotationTarget.FUNCTION) | ||
| @Retention(AnnotationRetention.BINARY) | ||
| expect annotation class Update( | ||
| val entity: KClass<*>, | ||
| val onConflict: Int, | ||
| ) | ||
|
|
||
| /** | ||
| * Cross-platform annotation for marking DAO methods that delete entities from the database. | ||
| * | ||
| * This annotation is used to delete entities from the database. | ||
| * The method can accept single entities, lists of entities, or arrays of entities. | ||
| * The entities are matched by their primary key. | ||
| * | ||
| * @param entity The entity class that this method deletes (used for type checking) | ||
| * | ||
| * Example: | ||
| * ```kotlin | ||
| * @Delete | ||
| * suspend fun deleteUser(user: User): Int | ||
| * | ||
| * @Delete | ||
| * suspend fun deleteUsers(users: List<User>): Int | ||
| * ``` | ||
| */ | ||
| @Suppress("NO_ACTUAL_FOR_EXPECT") | ||
| @Target(AnnotationTarget.FUNCTION) | ||
| @Retention(AnnotationRetention.BINARY) | ||
| expect annotation class Delete( | ||
| val entity: KClass<*>, | ||
| ) | ||
|
|
||
| /** | ||
| * Cross-platform annotation for marking DAO methods that insert or update entities. | ||
| * | ||
| * This annotation combines insert and update operations. If the entity doesn't exist | ||
| * (based on primary key), it will be inserted. If it exists, it will be updated. | ||
| * This is useful for synchronization scenarios. | ||
| * | ||
| * @param entity The entity class that this method upserts (used for type checking) | ||
| * | ||
| * Example: | ||
| * ```kotlin | ||
| * @Upsert | ||
| * suspend fun upsertUser(user: User): Long | ||
| * | ||
| * @Upsert | ||
| * suspend fun upsertUsers(users: List<User>): List<Long> | ||
| * ``` | ||
| */ | ||
| @Suppress("NO_ACTUAL_FOR_EXPECT") | ||
| @Target(AnnotationTarget.FUNCTION) | ||
| @Retention(AnnotationRetention.BINARY) | ||
| expect annotation class Upsert( | ||
| val entity: KClass<*>, | ||
| ) |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
androidx.room.Update annotation default parameters documentation
💡 Result:
androidx.room.Update has two parameters, and both have defaults:
entity: defaults toAny::class(Kotlin) /Object.class(Java), meaning Room infers the target entity from the DAO method parameter types unless you explicitly setentity(used for “partial entity” updates). [1], [2]onConflict: defaults toOnConflictStrategy.ABORT. [1], [2]
Sources
[1] (developer.android.com)
[2] (developer.android.com)
Citations:
- 1: https://developer.android.com/reference/kotlin/androidx/room/Update?utm_source=openai
- 2: https://developer.android.com/reference/androidx/room/Update?utm_source=openai
🌐 Web query:
androidx.room.Delete androidx.room.Upsert annotation default parameters
💡 Result:
@Delete and @Upsert each have a single optional parameter: entity, and its default means “infer the target entity from the DAO method parameter types”.
-
@Delete- Java signature / default:
Class<?> entity() default Object.class;(i.e., “no explicit entity”) [1] - Usage:
@Deleteor@Delete(entity = MyEntity.class)[1]
- Java signature / default:
-
@Upsert- Kotlin signature / default:
Upsert(entity: KClass<*> = Any::class)(i.e., “no explicit entity”) [2] - Usage:
@Upsertor@Upsert(entity = MyEntity::class)[2]
- Kotlin signature / default:
So, in both cases, the default entity is Object.class (Java) / Any::class (Kotlin), which tells Room to use the method parameter entity type(s) unless you explicitly set entity for partial-entity POJOs. [1][2]
Add Room-equivalent defaults for Update/Delete/Upsert parameters.
The expect annotations for Update, Delete, and Upsert currently require parameters, but Room's actual API provides defaults for all of them. This causes the KDoc examples using no-arg forms (@Update, @Delete, @Upsert) to fail compilation in common code. The Update annotation should also default onConflict to OnConflictStrategy.ABORT to match Room's behavior.
💡 Suggested fix (apply Room defaults)
expect annotation class Update(
- val entity: KClass<*>,
- val onConflict: Int,
+ val entity: KClass<*> = Any::class,
+ val onConflict: Int = OnConflictStrategy.ABORT,
)
expect annotation class Delete(
- val entity: KClass<*>,
+ val entity: KClass<*> = Any::class,
)
expect annotation class Upsert(
- val entity: KClass<*>,
+ val entity: KClass<*> = Any::class,
)🤖 Prompt for AI Agents
In `@core-base/database/src/commonMain/kotlin/template/core/base/database/Room.kt`
around lines 205 - 280, The expect annotations Update, Delete, and Upsert
require parameters but should provide Room-equivalent defaults so no-arg usage
(e.g. `@Update`) compiles; update the signatures for Update, Delete, and Upsert to
give entity a default (e.g. val entity: KClass<*> = Any::class) and set Update's
onConflict default to Room's default (val onConflict: Int =
OnConflictStrategy.ABORT), ensuring the annotation declarations (Update, Delete,
Upsert) accept no arguments as in Room.
| * @Entity | ||
| * data class User( | ||
| * @PrimaryKey(autoGenerate = true) | ||
| * val id: Long = 0, | ||
| * @ColumnInfo(name = "user_name", collate = ColumnInfo.NOCASE) | ||
| * val name: String, | ||
| * @ColumnInfo(name = "created_at", defaultValue = "CURRENT_TIMESTAMP") |
There was a problem hiding this comment.
Fix KDoc constant reference for ColumnInfo.collate.
The example uses ColumnInfo.NOCASE, but this file defines collation constants in CollationSequence. Update the example to avoid confusion.
✏️ Doc fix
- * `@ColumnInfo`(name = "user_name", collate = ColumnInfo.NOCASE)
+ * `@ColumnInfo`(name = "user_name", collate = CollationSequence.NOCASE)📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| * @Entity | |
| * data class User( | |
| * @PrimaryKey(autoGenerate = true) | |
| * val id: Long = 0, | |
| * @ColumnInfo(name = "user_name", collate = ColumnInfo.NOCASE) | |
| * val name: String, | |
| * @ColumnInfo(name = "created_at", defaultValue = "CURRENT_TIMESTAMP") | |
| * `@Entity` | |
| * data class User( | |
| * `@PrimaryKey`(autoGenerate = true) | |
| * val id: Long = 0, | |
| * `@ColumnInfo`(name = "user_name", collate = CollationSequence.NOCASE) | |
| * val name: String, | |
| * `@ColumnInfo`(name = "created_at", defaultValue = "CURRENT_TIMESTAMP") |
🤖 Prompt for AI Agents
In `@core-base/database/src/commonMain/kotlin/template/core/base/database/Room.kt`
around lines 323 - 329, Update the KDoc example to reference the correct
collation constants by replacing the incorrect ColumnInfo.NOCASE with
CollationSequence.NOCASE (or the appropriate constant defined in
CollationSequence); ensure the example uses ColumnInfo(collate =
CollationSequence.NOCASE) or similar so the symbols ColumnInfo.collate and
CollationSequence are consistent with the declarations in this file and the User
data class example shows the correct usage.
| @Suppress("NO_ACTUAL_FOR_EXPECT") | ||
| @Target(AnnotationTarget.FIELD, AnnotationTarget.FUNCTION) | ||
| @Retention(AnnotationRetention.BINARY) | ||
| expect annotation class ColumnInfo( | ||
| val name: String, | ||
| val typeAffinity: Int, | ||
| val index: Boolean, | ||
| val collate: Int, | ||
| val defaultValue: String, | ||
| ) | ||
|
|
||
| /** | ||
| * Cross-platform annotation for embedding objects within entities. | ||
| * | ||
| * This annotation allows you to include all fields from another object | ||
| * directly into the parent entity's table. This is useful for normalizing | ||
| * data models without creating separate tables. | ||
| * | ||
| * @param prefix Prefix to add to embedded field names to avoid naming conflicts | ||
| * | ||
| * Example: | ||
| * ```kotlin | ||
| * data class Address( | ||
| * val street: String, | ||
| * val city: String, | ||
| * val zipCode: String | ||
| * ) | ||
| * | ||
| * @Entity | ||
| * data class User( | ||
| * @PrimaryKey val id: Long, | ||
| * val name: String, | ||
| * @Embedded(prefix = "home_") | ||
| * val homeAddress: Address, | ||
| * @Embedded(prefix = "work_") | ||
| * val workAddress: Address | ||
| * ) | ||
| * // This creates columns: id, name, home_street, home_city, home_zipCode, work_street, work_city, work_zipCode | ||
| * ``` | ||
| */ | ||
| @Suppress("NO_ACTUAL_FOR_EXPECT") | ||
| @Target(AnnotationTarget.FIELD, AnnotationTarget.FUNCTION) | ||
| @Retention(AnnotationRetention.BINARY) | ||
| expect annotation class Embedded( | ||
| val prefix: String, | ||
| ) | ||
|
|
||
| /** | ||
| * Cross-platform annotation for defining relationships between entities. | ||
| * | ||
| * This annotation is used to establish one-to-many or many-to-many relationships | ||
| * between entities. It's typically used in data classes that represent joined | ||
| * queries and must be used within methods annotated with @Transaction. | ||
| * | ||
| * @param entity The entity class that this relation points to | ||
| * @param parentColumn The column name in the parent entity | ||
| * @param entityColumn The column name in the related entity | ||
| * @param associateBy Junction table information for many-to-many relationships | ||
| * @param projection List of columns to fetch from the related entity | ||
| * | ||
| * Example: | ||
| * ```kotlin | ||
| * data class UserWithPosts( | ||
| * @Embedded val user: User, | ||
| * @Relation( | ||
| * parentColumn = "id", | ||
| * entityColumn = "userId" | ||
| * ) | ||
| * val posts: List<Post> | ||
| * ) | ||
| * | ||
| * @Dao | ||
| * interface UserDao { | ||
| * @Transaction | ||
| * @Query("SELECT * FROM users WHERE id = :userId") | ||
| * suspend fun getUserWithPosts(userId: Long): UserWithPosts | ||
| * } | ||
| * ``` | ||
| */ | ||
| @Suppress("NO_ACTUAL_FOR_EXPECT") | ||
| @Target(AnnotationTarget.FIELD, AnnotationTarget.FUNCTION) | ||
| @Retention(AnnotationRetention.BINARY) | ||
| expect annotation class Relation( | ||
| val entity: KClass<*>, | ||
| val parentColumn: String, | ||
| val entityColumn: String, | ||
| val associateBy: Junction, | ||
| val projection: Array<String>, | ||
| ) | ||
|
|
||
| /** | ||
| * Cross-platform annotation for defining junction tables in many-to-many relationships. | ||
| * | ||
| * This annotation is used within @Relation to specify the junction table | ||
| * that connects two entities in a many-to-many relationship. | ||
| * | ||
| * @param value The junction entity class | ||
| * @param parentColumn The column in the junction table that references the parent entity | ||
| * @param entityColumn The column in the junction table that references the child entity | ||
| * | ||
| * Example: | ||
| * ```kotlin | ||
| * @Entity(primaryKeys = ["userId", "playlistId"]) | ||
| * data class UserPlaylistCrossRef( | ||
| * val userId: Long, | ||
| * val playlistId: Long | ||
| * ) | ||
| * | ||
| * data class UserWithPlaylists( | ||
| * @Embedded val user: User, | ||
| * @Relation( | ||
| * parentColumn = "id", | ||
| * entityColumn = "id", | ||
| * associateBy = Junction( | ||
| * value = UserPlaylistCrossRef::class, | ||
| * parentColumn = "userId", | ||
| * entityColumn = "playlistId" | ||
| * ) | ||
| * ) | ||
| * val playlists: List<Playlist> | ||
| * ) | ||
| * ``` | ||
| */ | ||
| @Suppress("NO_ACTUAL_FOR_EXPECT") | ||
| @Target(allowedTargets = []) | ||
| @Retention(AnnotationRetention.BINARY) | ||
| expect annotation class Junction( | ||
| val value: KClass<*>, | ||
| val parentColumn: String, | ||
| val entityColumn: String, | ||
| ) | ||
|
|
||
| /** | ||
| * Cross-platform annotation for specifying which type converters to use. | ||
| * | ||
| * This annotation tells Room which type converter classes to use for an entity, | ||
| * DAO, or database. It can be applied at different scopes to control where | ||
| * converters are available. | ||
| * | ||
| * @param value Array of type converter classes | ||
| * @param builtInTypeConverters Configuration for built-in type converters | ||
| * | ||
| * Example: | ||
| * ```kotlin | ||
| * @Database( | ||
| * entities = [User::class, Post::class], | ||
| * version = 1 | ||
| * ) | ||
| * @TypeConverters(Converters::class) | ||
| * abstract class AppDatabase : RoomDatabase() { | ||
| * abstract fun userDao(): UserDao | ||
| * } | ||
| * | ||
| * @Entity | ||
| * @TypeConverters(DateConverters::class) | ||
| * data class Event( | ||
| * @PrimaryKey val id: Long, | ||
| * val date: Date | ||
| * ) | ||
| * ``` | ||
| */ | ||
| @Suppress("NO_ACTUAL_FOR_EXPECT") | ||
| @Target( | ||
| AnnotationTarget.FUNCTION, | ||
| AnnotationTarget.CLASS, | ||
| AnnotationTarget.FIELD, | ||
| ) | ||
| @Retention(AnnotationRetention.BINARY) | ||
| expect annotation class TypeConverters( | ||
| val value: Array<KClass<*>>, | ||
| val builtInTypeConverters: BuiltInTypeConverters, | ||
| ) | ||
|
|
||
| /** | ||
| * Cross-platform annotation for configuring built-in type converters. | ||
| * | ||
| * This annotation allows you to enable or disable Room's built-in type converters | ||
| * for specific types like enums and UUID. Use it within @TypeConverters annotation. | ||
| * | ||
| * @param enums State of the built-in enum converter (INHERITED, ENABLED, DISABLED) | ||
| * @param uuid State of the built-in UUID converter (INHERITED, ENABLED, DISABLED) | ||
| * @param byteBuffer State of the built-in ByteBuffer converter (INHERITED, ENABLED, DISABLED) | ||
| * | ||
| * Example: | ||
| * ```kotlin | ||
| * @TypeConverters( | ||
| * value = [CustomConverters::class], | ||
| * builtInTypeConverters = BuiltInTypeConverters( | ||
| * enums = BuiltInTypeConverters.State.ENABLED, | ||
| * uuid = BuiltInTypeConverters.State.ENABLED | ||
| * ) | ||
| * ) | ||
| * ``` | ||
| */ | ||
| @Suppress("NO_ACTUAL_FOR_EXPECT") | ||
| @Target(allowedTargets = []) | ||
| @Retention(AnnotationRetention.BINARY) | ||
| expect annotation class BuiltInTypeConverters( | ||
| val enums: State, | ||
| val uuid: State, | ||
| val byteBuffer: State, | ||
| ) { | ||
| /** | ||
| * State values for built-in type converters. | ||
| */ | ||
| enum class State { | ||
| /** Inherit the converter state from the parent scope */ | ||
| INHERITED, | ||
| /** Enable the built-in converter */ | ||
| ENABLED, | ||
| /** Disable the built-in converter */ | ||
| DISABLED, | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Cross-platform annotation for marking a class as a Room database. | ||
| * | ||
| * This annotation defines the database configuration, including the list of entities, | ||
| * database version, and whether to export the schema. The annotated class must be | ||
| * abstract and extend RoomDatabase. | ||
| * | ||
| * @param entities Array of entity classes that belong to this database | ||
| * @param version Database version number (increment when schema changes) | ||
| * @param exportSchema Whether to export the database schema to a folder | ||
| * @param views Array of database view classes | ||
| * @param autoMigrations Array of automatic migration specifications | ||
| * | ||
| * Example: | ||
| * ```kotlin | ||
| * @Database( | ||
| * entities = [User::class, Post::class], | ||
| * version = 2, | ||
| * exportSchema = true | ||
| * ) | ||
| * @TypeConverters(Converters::class) | ||
| * abstract class AppDatabase : RoomDatabase() { | ||
| * abstract fun userDao(): UserDao | ||
| * abstract fun postDao(): PostDao | ||
| * } | ||
| * ``` | ||
| */ | ||
| @Suppress("NO_ACTUAL_FOR_EXPECT") | ||
| @Target(AnnotationTarget.CLASS) | ||
| @Retention(AnnotationRetention.BINARY) | ||
| expect annotation class Database( | ||
| val entities: Array<KClass<*>>, | ||
| val version: Int, | ||
| val exportSchema: Boolean, | ||
| val views: Array<KClass<*>>, | ||
| val autoMigrations: Array<AutoMigration>, | ||
| ) | ||
|
|
||
| /** | ||
| * Cross-platform annotation for defining automatic migrations between database versions. | ||
| * | ||
| * This annotation specifies how Room should automatically migrate the database | ||
| * from one version to another. It's used within the @Database annotation. | ||
| * | ||
| * @param from The starting version of the migration | ||
| * @param to The target version of the migration | ||
| * @param spec Optional migration specification class for complex migrations | ||
| * | ||
| * Example: | ||
| * ```kotlin | ||
| * @Database( | ||
| * entities = [User::class], | ||
| * version = 3, | ||
| * autoMigrations = [ | ||
| * AutoMigration(from = 1, to = 2), | ||
| * AutoMigration(from = 2, to = 3, spec = Migration2to3::class) | ||
| * ] | ||
| * ) | ||
| * abstract class AppDatabase : RoomDatabase() | ||
| * ``` | ||
| */ | ||
| @Suppress("NO_ACTUAL_FOR_EXPECT") | ||
| @Target(allowedTargets = []) | ||
| @Retention(AnnotationRetention.BINARY) | ||
| expect annotation class AutoMigration( | ||
| val from: Int, | ||
| val to: Int, | ||
| val spec: KClass<*>, | ||
| ) | ||
|
|
||
| /** | ||
| * Cross-platform annotation for ignoring fields in entities. | ||
| * | ||
| * This annotation marks fields that should not be persisted to the database. | ||
| * Use it for computed properties, transient state, or any data that shouldn't | ||
| * be stored. | ||
| * | ||
| * Example: | ||
| * ```kotlin | ||
| * @Entity | ||
| * data class User( | ||
| * @PrimaryKey val id: Long, | ||
| * val firstName: String, | ||
| * val lastName: String, | ||
| * @Ignore | ||
| * val fullName: String = "$firstName $lastName" | ||
| * ) | ||
| * | ||
| * @Entity | ||
| * data class Product( | ||
| * @PrimaryKey val id: Long, | ||
| * val name: String, | ||
| * val price: Double, | ||
| * @Ignore | ||
| * var isSelected: Boolean = false | ||
| * ) | ||
| * ``` | ||
| */ | ||
| @Suppress("NO_ACTUAL_FOR_EXPECT") | ||
| @Target( | ||
| AnnotationTarget.FIELD, | ||
| AnnotationTarget.CONSTRUCTOR, | ||
| ) | ||
| @Retention(AnnotationRetention.BINARY) | ||
| expect annotation class Ignore() | ||
|
|
||
| /** | ||
| * Cross-platform annotation for defining database views. | ||
| * | ||
| * This annotation creates a read-only database view based on a SQL query. | ||
| * Views are useful for creating reusable query results that can be treated | ||
| * like entities but are computed from other tables. | ||
| * | ||
| * @param value The SQL SELECT query that defines the view | ||
| * @param viewName Custom name for the database view | ||
| * | ||
| * Example: | ||
| * ```kotlin | ||
| * @DatabaseView( | ||
| * "SELECT user.id, user.name, COUNT(post.id) as postCount " + | ||
| * "FROM users as user LEFT JOIN posts as post ON user.id = post.userId " + | ||
| * "GROUP BY user.id", | ||
| * viewName = "user_summary" | ||
| * ) | ||
| * data class UserSummary( | ||
| * val id: Long, | ||
| * val name: String, | ||
| * val postCount: Int | ||
| * ) | ||
| * ``` | ||
| */ | ||
| @Suppress("NO_ACTUAL_FOR_EXPECT") | ||
| @Target(AnnotationTarget.CLASS) | ||
| @Retention(AnnotationRetention.BINARY) | ||
| expect annotation class DatabaseView( | ||
| val value: String, | ||
| val viewName: String, | ||
| ) |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
androidx.room ColumnInfo Embedded Relation TypeConverters Database annotation default parameters kotlin
💡 Result:
@ColumnInfo (Kotlin annotation params + defaults)
ColumnInfo(name: String = INHERIT_FIELD_NAME, typeAffinity: Int = UNDEFINED, index: Boolean = false, collate: Int = UNSPECIFIED, defaultValue: String = VALUE_UNSPECIFIED).[1]
Notes on defaultValue:
- It becomes part of the generated
CREATE TABLE(SQLite default), and is not applied when you use@Insert, because Room inserts whatever values are in your object; to use the SQLite default you must omit the column in an explicitINSERTstatement (e.g., via@Query).[1]
@Embedded (defaults)
prefixdefaults to the empty string; use it to avoid column-name conflicts when flattening nested objects.[2]
@Relation (key parameters + defaults/behavior)
- Used in POJOs to have Room automatically fetch related entities when the POJO is returned from a query.[3]
parentColumnandentityColumndefine the join mapping; for 1‑many / many‑many the field type must beListorSet.[3]- By default, the related
entityis inferred from the field type; you can override it withentity = ..., restrict returned columns viaprojection = ..., and specify a junction table viaassociateByfor many‑to‑many.[3]
@TypeConverters (where you can put it + scope)
@TypeConverterscan be applied to a Database, DAO/class, field, function, or value parameter; converters apply to that element’s scope.[4]- If you put it on the
@Database, it’s available to all DAOs and entities in that database.[4][5]
@Database (annotation params + defaults)
Database(entities: Array<KClass<*>> = [], views: Array<KClass<*>> = [], version: Int, exportSchema: Boolean = true, autoMigrations: Array<AutoMigration> = []).[6]
exportSchemadefaults totrueand works with the processor argumentroom.schemaLocationto export schema files.[6]- If you define
autoMigrations, Room recommends/requiresexportSchema = true(so schema history exists).[6]
Sources: AndroidX/Android Developers API refs and guide.[1][2][3][4][5][6]
Add defaults to match androidx.room API and make annotations practical.
All annotations require parameters that have defaults in the actual Room library: ColumnInfo needs defaults for typeAffinity, index, collate, and defaultValue; Embedded needs prefix = ""; Relation needs defaults for associateBy and projection; Junction needs defaults for all parameters; TypeConverters needs builtInTypeConverters = BuiltInTypeConverters(); BuiltInTypeConverters needs all State parameters defaulting to State.INHERITED; Database needs exportSchema = true, views = emptyArray(), and autoMigrations = emptyArray(); AutoMigration needs a default for spec; and DatabaseView needs viewName = "". Without these defaults, the cross-platform API will be incompatible with how Room's annotations are actually used.
Suggested defaults
expect annotation class ColumnInfo(
val name: String,
- val typeAffinity: Int,
- val index: Boolean,
- val collate: Int,
- val defaultValue: String,
+ val typeAffinity: Int = UNDEFINED,
+ val index: Boolean = false,
+ val collate: Int = UNSPECIFIED,
+ val defaultValue: String = VALUE_UNSPECIFIED,
)
expect annotation class Embedded(
- val prefix: String,
+ val prefix: String = "",
)
expect annotation class Relation(
val entity: KClass<*>,
val parentColumn: String,
val entityColumn: String,
- val associateBy: Junction,
- val projection: Array<String>,
+ val associateBy: Junction = Junction(),
+ val projection: Array<String> = emptyArray(),
)
expect annotation class Junction(
val value: KClass<*>,
- val parentColumn: String,
- val entityColumn: String,
+ val parentColumn: String = "",
+ val entityColumn: String = "",
)
expect annotation class TypeConverters(
val value: Array<KClass<*>>,
- val builtInTypeConverters: BuiltInTypeConverters,
+ val builtInTypeConverters: BuiltInTypeConverters = BuiltInTypeConverters(),
)
expect annotation class BuiltInTypeConverters(
- val enums: State,
- val uuid: State,
- val byteBuffer: State,
+ val enums: State = State.INHERITED,
+ val uuid: State = State.INHERITED,
+ val byteBuffer: State = State.INHERITED,
)
expect annotation class Database(
val entities: Array<KClass<*>>,
val version: Int,
- val exportSchema: Boolean,
- val views: Array<KClass<*>>,
- val autoMigrations: Array<AutoMigration>,
+ val exportSchema: Boolean = true,
+ val views: Array<KClass<*>> = emptyArray(),
+ val autoMigrations: Array<AutoMigration> = emptyArray(),
)
expect annotation class AutoMigration(
val from: Int,
val to: Int,
- val spec: KClass<*>,
+ val spec: KClass<*> = Any::class,
)
expect annotation class DatabaseView(
val value: String,
- val viewName: String,
+ val viewName: String = "",
)🤖 Prompt for AI Agents
In `@core-base/database/src/commonMain/kotlin/template/core/base/database/Room.kt`
around lines 334 - 686, The annotation expect declarations lack default
parameter values required to match androidx.room usage; update the signatures to
provide defaults: add defaults for ColumnInfo (typeAffinity, index, collate,
defaultValue), Embedded(prefix = ""), Relation(associateBy = Junction(),
projection = arrayOf()), Junction(value = Any::class /* or KClass default
placeholder */, parentColumn = "", entityColumn = ""),
TypeConverters(builtInTypeConverters = BuiltInTypeConverters()),
BuiltInTypeConverters(enums = State.INHERITED, uuid = State.INHERITED,
byteBuffer = State.INHERITED), Database(exportSchema = true, views =
emptyArray(), autoMigrations = emptyArray()), AutoMigration(spec = Any::class /*
or KClass default placeholder */), and DatabaseView(viewName = ""); change the
expect annotation parameter defaults for ColumnInfo, Embedded, Relation,
Junction, TypeConverters, BuiltInTypeConverters, Database, AutoMigration, and
DatabaseView so their signatures (e.g., ColumnInfo, Embedded, Relation,
Junction, TypeConverters, BuiltInTypeConverters, Database, AutoMigration,
DatabaseView) match Room's defaults and allow parameter omission. Ensure
defaults use valid types (KClass placeholders where needed) consistent with
Kotlin expect declarations.
| onSurface = onSurface, | ||
| surfaceVariant = surfaceVariant, | ||
| onSurfaceVariant = onSurfaceVariant, | ||
| surfaceTint = primary, |
There was a problem hiding this comment.
Use surfaceTint property instead of hardcoding primary.
KptColorScheme defines a surfaceTint property, but here it's hardcoded to primary. This could cause inconsistency if surfaceTint is customized to a different value.
🔧 Proposed fix
- surfaceTint = primary,
+ surfaceTint = surfaceTint,📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| surfaceTint = primary, | |
| surfaceTint = surfaceTint, |
🤖 Prompt for AI Agents
In
`@core-base/designsystem/src/commonMain/kotlin/template/core/base/designsystem/KptThemeExtensions.kt`
at line 298, The theme mapping is hardcoding surfaceTint to primary; update the
mapping to use the KptColorScheme.surfaceTint property instead of primary so
consumers that override surfaceTint are respected—locate the mapping function
(e.g., where KptColorScheme is converted to the platform/Material color scheme
in KptThemeExtensions.kt) and replace the literal primary assignment
(surfaceTint = primary) with the corresponding source property (surfaceTint =
kptColorScheme.surfaceTint or the local variable that refers to the
KptColorScheme instance).
| import kotlin.time.Instant | ||
|
|
||
| fun formatDate(millis: Long): String { | ||
| val dateTime = Instant | ||
| .fromEpochMilliseconds(millis) | ||
| .toLocalDateTime(TimeZone.currentSystemDefault()) | ||
|
|
||
| val day = dateTime.dayOfMonth.toString().padStart(2, '0') | ||
| val month = dateTime.monthNumber.toString().padStart(2, '0') | ||
| val day = dateTime.day.toString().padStart(2, '0') | ||
| val month = dateTime.month.number.toString().padStart(2, '0') | ||
| val year = dateTime.year |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cat -n core/common/src/commonMain/kotlin/org/mifos/core/common/FormatDate.ktRepository: openMF/kmp-project-template
Length of output: 1089
🏁 Script executed:
fd -t f "build.gradle" -o "gradle.properties" -o "pom.xml" | head -5Repository: openMF/kmp-project-template
Length of output: 224
🌐 Web query:
kotlinx-datetime LocalDateTime API properties dayOfMonth day
💡 Result:
In kotlinx-datetime, LocalDateTime does not have a dayOfMonth (or day) property directly. It exposes its components as:
val date: LocalDateval time: LocalTime[1]
The day-of-month is on LocalDate as:
val day: Int— “Returns the day-of-month (1..31) component of the date.” [2]
So you’d write:
val dom: Int = localDateTime.date.day // day-of-month (1..31)
val dow = localDateTime.date.dayOfWeek // day-of-weekSources: LocalDateTime API [1], LocalDate.day API [2].
References
- https://kotlinlang.org/api/kotlinx-datetime/kotlinx-datetime/kotlinx.datetime/-local-date-time/
- https://kotlinlang.org/api/kotlinx-datetime/kotlinx-datetime/kotlinx.datetime/-local-date/
🌐 Web query:
kotlinx.datetime LocalDateTime month property API
💡 Result:
In kotlinx.datetime.LocalDateTime, the month is exposed as:
val month: Month— the month-of-year as theMonthenum (JANUARY…DECEMBER). (kotlinlang.org)- To get a numeric month, use
localDateTime.month.number(returns 1..12). (kotlinlang.org)
Notes:
- When constructing
LocalDateTimewith anIntmonth, thatmonthparameter is 1-based (1..12). (kotlinlang.org)
Citations:
- 1: https://kotlinlang.org/api/kotlinx-datetime/kotlinx-datetime/kotlinx.datetime/-local-date-time/
- 2: https://kotlinlang.org/api/kotlinx-datetime/kotlinx-datetime/kotlinx.datetime/-month/?utm_source=openai
- 3: https://kotlinlang.org/api/kotlinx-datetime/kotlinx-datetime/kotlinx.datetime/-local-date-time/-local-date-time.html?utm_source=openai
🌐 Web query:
kotlin.time.Instant kotlinx.datetime extension toLocalDateTime compatibility
💡 Result:
toLocalDateTime(...) is an extension for kotlinx.datetime.Instant, not for kotlin.time.Instant. In kotlinx-datetime 0.7.1 you can use either:
instant.toLocalDateTime(timeZone: TimeZone)(kotlinlang.org)- or, with an implicit
TimeZonereceiver:with(zone) { instant.toLocalDateTime() }(kotlinlang.org)
If you currently have a kotlin.time.Instant (stdlib), convert it using the compatibility functions added in kotlinx-datetime 0.7.x:
kotlin.time.Instant.toDeprecatedInstant(): kotlinx.datetime.Instantkotlinx.datetime.Instant.toStdlibInstant(): kotlin.time.Instant(github.com)
After converting to kotlinx.datetime.Instant, you can call toLocalDateTime(...) normally. (kotlinlang.org)
Citations:
- 1: https://kotlinlang.org/api/kotlinx-datetime/kotlinx-datetime/kotlinx.datetime/to-local-date-time.html?utm_source=openai
- 2: https://kotlinlang.org/api/kotlinx-datetime/kotlinx-datetime/kotlinx.datetime/-time-zone/to-local-date-time.html?utm_source=openai
- 3: https://github.com/Kotlin/kotlinx-datetime?utm_source=openai
- 4: https://kotlinlang.org/api/kotlinx-datetime/kotlinx-datetime/kotlinx.datetime/to-local-date-time.html?utm_source=openai
Compilation errors: Type mismatch with Instant and incorrect property access paths.
This code will not compile. There are two critical issues:
-
Type mismatch: Line 15 imports
kotlin.time.Instant, but line 20 callstoLocalDateTime(TimeZone)which is an extension function onkotlinx.datetime.Instant, notkotlin.time.Instant. Requires explicit conversion. -
Incorrect property access: Line 22 accesses
dateTime.day, butLocalDateTimedoes not have adayproperty directly. The day component is accessed viadateTime.date.day.
Line 23 (dateTime.month.number) is correct and does not need changes—LocalDateTime has a month property of type Month, and .number correctly extracts the numeric value (1–12).
Suggested fix
-import kotlin.time.Instant
+import kotlinx.datetime.Instant
fun formatDate(millis: Long): String {
val dateTime = Instant
.fromEpochMilliseconds(millis)
.toLocalDateTime(TimeZone.currentSystemDefault())
- val day = dateTime.day.toString().padStart(2, '0')
+ val day = dateTime.date.day.toString().padStart(2, '0')
val month = dateTime.month.number.toString().padStart(2, '0')
val year = dateTime.year
return "$day/$month/$year"
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| import kotlin.time.Instant | |
| fun formatDate(millis: Long): String { | |
| val dateTime = Instant | |
| .fromEpochMilliseconds(millis) | |
| .toLocalDateTime(TimeZone.currentSystemDefault()) | |
| val day = dateTime.dayOfMonth.toString().padStart(2, '0') | |
| val month = dateTime.monthNumber.toString().padStart(2, '0') | |
| val day = dateTime.day.toString().padStart(2, '0') | |
| val month = dateTime.month.number.toString().padStart(2, '0') | |
| val year = dateTime.year | |
| import kotlinx.datetime.Instant | |
| fun formatDate(millis: Long): String { | |
| val dateTime = Instant | |
| .fromEpochMilliseconds(millis) | |
| .toLocalDateTime(TimeZone.currentSystemDefault()) | |
| val day = dateTime.date.day.toString().padStart(2, '0') | |
| val month = dateTime.month.number.toString().padStart(2, '0') | |
| val year = dateTime.year | |
| return "$day/$month/$year" | |
| } |
🤖 Prompt for AI Agents
In `@core/common/src/commonMain/kotlin/org/mifos/core/common/FormatDate.kt` around
lines 15 - 24, The function formatDate fails because it imports
kotlin.time.Instant and then calls toLocalDateTime which expects
kotlinx.datetime.Instant; fix by importing kotlinx.datetime.Instant and
kotlinx.datetime.TimeZone (or convert the kotlin.time.Instant to a
kotlinx.datetime.Instant before calling toLocalDateTime), and change the day
access from dateTime.day to dateTime.date.day; keep month.number and year as-is.
Ensure the symbols referenced are formatDate, Instant, toLocalDateTime,
TimeZone, LocalDateTime/date, and use dateTime.date.day for the day component.
|
If you'd like to disable CodeRabbit reviews more broadly (for specific paths, file types, or the entire repository), you can configure this through the CodeRabbit UI settings or by updating the ✅ Actions performedReviews paused. |
Summary by CodeRabbit
New Features
Bug Fixes
Chores