Skip to content

feat(database): enhance Room database annotations and add new color properties#109

Merged
niyajali merged 1 commit intodevfrom
db-enhancement
Feb 5, 2026
Merged

feat(database): enhance Room database annotations and add new color properties#109
niyajali merged 1 commit intodevfrom
db-enhancement

Conversation

@niyajali
Copy link
Copy Markdown
Collaborator

@niyajali niyajali commented Feb 5, 2026

Summary by CodeRabbit

  • New Features

    • Extended design system with new fixed color variants for enhanced theming flexibility.
  • Bug Fixes

    • Updated database configuration rules to ensure proper preservation of core database components across platforms.
  • Chores

    • Improved gitignore patterns.
    • Refactored design system typography and color scheme initialization.
    • Updated date formatting implementation.
    • Enhanced swipe interaction component API.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Feb 5, 2026

Note

Reviews paused

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.
📝 Walkthrough

Walkthrough

This 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

Cohort / File(s) Summary
ProGuard/R8 Configuration
cmp-android/proguard-rules.pro, cmp-desktop/compose-desktop.pro
Added keep rules to preserve no-arg constructors for classes extending androidx.room.RoomDatabase across Android and desktop configurations.
Room Database Abstraction - Core Expect Declarations
core-base/database/src/commonMain/kotlin/template/core/base/database/Room.kt
Introduced comprehensive expect annotations for Room API surface: Dao, Query, Insert, PrimaryKey, ForeignKey, Index, Entity, Database, AutoMigration, Ignore, DatabaseView, ColumnInfo, Embedded, Relation, Junction, TypeConverters, BuiltInTypeConverters (with State enum), Transaction, Upsert, Delete, Update, and constants (OnConflictStrategy, ColumnInfoTypeAffinity, CollationSequence).
Room Database Abstraction - Platform Implementations
core-base/database/src/nonJsCommonMain/kotlin/template/core/base/database/Room.nonJsCommon.kt
Added actual typealias mappings for all new Room annotations to their androidx.room counterparts for non-JS platforms.
Room Database Abstraction - Consolidation
core-base/database/src/commonMain/kotlin/template/core/base/database/TypeConverter.kt, core-base/database/src/nonJsCommonMain/kotlin/template/core/base/database/TypeConverter.nonJsCommon.kt
Deleted separate TypeConverter files; functionality consolidated into Room.kt expect/actual pattern.
Design System Color Tokens
core-base/designsystem/src/commonMain/kotlin/template/core/base/designsystem/core/KptComponent.kt, core-base/designsystem/src/commonMain/kotlin/template/core/base/designsystem/theme/KptColorSchemeImpl.kt
Extended KptColorScheme interface and implementation with 12 fixed color variants: primaryFixed, primaryFixedDim, onPrimaryFixed, onPrimaryFixedVariant, and corresponding secondary/tertiary variants.
Theme Extensions & Top App Bar
core-base/designsystem/src/commonMain/kotlin/template/core/base/designsystem/KptThemeExtensions.kt, core-base/designsystem/src/commonMain/kotlin/template/core/base/designsystem/component/KptTopAppBar.kt
Updated Typography.toKptTypography() with enhanced lineHeightStyle and fontFamily propagation; unified default colors across TopAppBar variants (CenterAligned, Medium, Large) to use base TopAppBarColors().
Utility API Updates
core/common/src/commonMain/kotlin/org/mifos/core/common/FormatDate.kt, core/ui/src/commonMain/kotlin/org/mifos/core/ui/RevealSwipe.kt
Migrated FormatDate from kotlinx.datetime.Instant to kotlin.time.Instant API; renamed RevealSwipe callback parameter from confirmStateChange to confirmValueChange with updated RevealState data class signature.
Build Configuration
.gitignore
Added fastlane/fastlane/metadata/ to ignored paths.
Function Signature Formatting
core-base/database/src/androidMain/kotlin/template/core/base/database/AppDatabaseFactory.kt
Reformatted createDatabase function parameters to multi-line style with trailing comma (no logic changes).

Poem

🐰 With databases aligned across each land,
Room annotations now span, hand in hand—
Colors fixed and shining bright,
Typography refined just right.
The codebase hops toward harmony,
hop hop ✨📚

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 42.86% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the primary changes: enhancing Room database annotations and adding new color properties. Both aspects are substantially addressed in the changeset.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch db-enhancement

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@niyajali niyajali enabled auto-merge (squash) February 5, 2026 16:59
@niyajali
Copy link
Copy Markdown
Collaborator Author

niyajali commented Feb 5, 2026

@coderabbitai disable

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 | 🟠 Major

Migrate RevealState away from deprecated AnchoredDraggableState constructor parameters.

The @Suppress("DEPRECATION") annotation masks use of deprecated AnchoredDraggableState constructor parameters that were deprecated in Compose Foundation 1.8.0-alpha06 (November 2024). The affected parameters are:

  • positionalThreshold, velocityThreshold
  • snapAnimationSpec, decayAnimationSpec
  • confirmValueChange

Per Compose Foundation guidance, migrate this configuration to use AnchoredDraggableDefaults.flingBehavior() and pass it via Modifier.anchoredDraggable() instead of constructor parameters. For confirmValueChange, 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

onNavigationIconClick parameter is accepted but ignored.

Both KptMediumTopAppBar and KptLargeTopAppBar declare onNavigationIconClick as a parameter but never pass it to the underlying KptTopAppBar call. 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

KptColorSchemeBuilder is 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 the KptColorSchemeImpl constructor call.

core-base/database/src/nonJsCommonMain/kotlin/template/core/base/database/Room.nonJsCommon.kt (1)

28-139: ⚠️ Potential issue | 🔴 Critical

Remove the TypeConverter actual typealias or add the corresponding expect declaration in commonMain.

The nonJsCommon file defines actual typealias TypeConverter = TypeConverter (line 138), but commonMain contains no corresponding expect declaration. Only TypeConverters (plural) exists in commonMain. This mismatch will cause a multiplatform compilation error. Either add expect typealias TypeConverter to 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.

Comment on lines +205 to +280
/**
* 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<*>,
)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

androidx.room.Update annotation default parameters documentation

💡 Result:

androidx.room.Update has two parameters, and both have defaults:

  • entity: defaults to Any::class (Kotlin) / Object.class (Java), meaning Room infers the target entity from the DAO method parameter types unless you explicitly set entity (used for “partial entity” updates). [1], [2]
  • onConflict: defaults to OnConflictStrategy.ABORT. [1], [2]

Sources
[1] (developer.android.com)
[2] (developer.android.com)

Citations:


🌐 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: @Delete or @Delete(entity = MyEntity.class) [1]
  • @Upsert

    • Kotlin signature / default: Upsert(entity: KClass<*> = Any::class) (i.e., “no explicit entity”) [2]
    • Usage: @Upsert or @Upsert(entity = MyEntity::class) [2]

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.

Comment on lines +323 to +329
* @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")
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

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.

Suggested change
* @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.

Comment on lines +334 to +686
@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,
)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 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 explicit INSERT statement (e.g., via @Query).[1]

@Embedded (defaults)

  • prefix defaults 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]
  • parentColumn and entityColumn define the join mapping; for 1‑many / many‑many the field type must be List or Set.[3]
  • By default, the related entity is inferred from the field type; you can override it with entity = ..., restrict returned columns via projection = ..., and specify a junction table via associateBy for many‑to‑many.[3]

@TypeConverters (where you can put it + scope)

  • @TypeConverters can 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]

  • exportSchema defaults to true and works with the processor argument room.schemaLocation to export schema files.[6]
  • If you define autoMigrations, Room recommends/requires exportSchema = 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,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

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.

Suggested change
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).

Comment on lines +15 to 24
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
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

cat -n core/common/src/commonMain/kotlin/org/mifos/core/common/FormatDate.kt

Repository: openMF/kmp-project-template

Length of output: 1089


🏁 Script executed:

fd -t f "build.gradle" -o "gradle.properties" -o "pom.xml" | head -5

Repository: 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: LocalDate
  • val 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-week

Sources: LocalDateTime API [1], LocalDate.day API [2].

References

  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/-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 the Month enum (JANUARY…DECEMBER). (kotlinlang.org)
  • To get a numeric month, use localDateTime.month.number (returns 1..12). (kotlinlang.org)

Notes:

  • When constructing LocalDateTime with an Int month, that month parameter is 1-based (1..12). (kotlinlang.org)

Citations:


🌐 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 TimeZone receiver: 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.Instant
  • kotlinx.datetime.Instant.toStdlibInstant(): kotlin.time.Instant (github.com)

After converting to kotlinx.datetime.Instant, you can call toLocalDateTime(...) normally. (kotlinlang.org)

Citations:


Compilation errors: Type mismatch with Instant and incorrect property access paths.

This code will not compile. There are two critical issues:

  1. Type mismatch: Line 15 imports kotlin.time.Instant, but line 20 calls toLocalDateTime(TimeZone) which is an extension function on kotlinx.datetime.Instant, not kotlin.time.Instant. Requires explicit conversion.

  2. Incorrect property access: Line 22 accesses dateTime.day, but LocalDateTime does not have a day property directly. The day component is accessed via dateTime.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.

Suggested change
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.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Feb 5, 2026

@niyajali, I've paused automatic reviews for this PR. You can resume them anytime by mentioning @coderabbitai resume.

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 .coderabbit.yaml configuration file.

✅ Actions performed

Reviews paused.

@niyajali niyajali merged commit c6527ff into dev Feb 5, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant