Search code examples
androidandroid-jetpack-composeandroid-roomdagger-hilt

Why is Android app with Jetpack Compose, Room and Dagger-Hilt crashing?


I'm making an Android app with Jetpack Compose, Room and Dagger-Hilt.
The app crashes with following exception:

java.lang.RuntimeException: Cannot find implementation for com.pbpu.android.data.local.source.Database. Database_Impl does not exist

Project level build.gradle.kts:

plugins {
    id("com.android.application") version "8.1.0" apply false
    id("org.jetbrains.kotlin.android") version "1.8.10" apply false
    id("com.google.devtools.ksp") version "1.8.10-1.0.9" apply false
    id("com.google.dagger.hilt.android") version "2.47" apply false
}

The dependencies used are:

//    DI
    implementation("com.google.dagger:hilt-android:2.47")
    ksp("com.google.dagger:hilt-compiler:2.47")
    implementation("androidx.hilt:hilt-navigation-compose:1.0.0")
//    Room
    implementation("androidx.room:room-ktx:2.5.2")
    implementation("androidx.room:room-runtime:2.5.2")
    ksp("androidx.room:room-compiler:2.5.2")
//    Other dependencies

If I change dependencies to:

//    DI
    implementation("com.google.dagger:hilt-android:2.47")
    ksp("com.google.dagger:hilt-compiler:2.47")
    implementation("androidx.hilt:hilt-navigation-compose:1.0.0")
//    Room
    implementation("androidx.room:room-ktx:2.5.2")
    implementation("androidx.room:room-runtime:2.5.2")
    kapt("androidx.room:room-compiler:2.5.2")
//    Other dependencies

Then compilation fails with message:

path\to\file\RecordWithMarkingsDao.java:17: error: Type of the parameter must be a class annotated with @Entity or a collection/array of it.
    com.pbpu.android.domain.model.RecordWithMarkings entity, @org.jetbrains.annotations.NotNull
                                                     ^

But, same code compiles if I use ksp("androidx.room:room-compiler:2.5.2") or even annotationProcessor("androidx.room:room-compiler:2.5.2") but then, the app crashes with exception as stated before!

Record.kt

@Entity(tableName = "records")
data class Record(
    @PrimaryKey(autoGenerate = true)
    @ColumnInfo(index = true)
    var idRecord: Long = 0,
//    Other fields
)

Marking.kt

@Entity(
    tableName = "markings",
    foreignKeys = [
        ForeignKey(
            entity = Record::class,
            parentColumns = ["idRecord"],
            childColumns = ["fkIdRecord"],
            onDelete = ForeignKey.CASCADE,
            onUpdate = ForeignKey.CASCADE,
        ),
    ],
)
data class Marking(
    @PrimaryKey(autoGenerate = true)
    @ColumnInfo(index = true)
    var idMarking: Long = 0,
    @ColumnInfo(index = true)
    val fkIdRecord: Long = 0,
//    Other fields
)

RecordWithMarkings.kt

data class RecordWithMarkings(
    @Embedded
    val record: Record,
    @Relation(
        parentColumn = "idRecord",
        entityColumn = "fkIdRecord",
    )
    val markings: List<Marking>,
)

How to solve this problem?

Any help will be appreciated and ask for any additional information if required.

Thanks.


Solution

  • Looks like the issue is in the DAO, please ensure correctness:

    • The RecordWithMarkingsDao.java should have methods that deal with fetching RecordWithMarkings objects. These methods should return RecordWithMarkings or a list of RecordWithMarkings but shouldn't take RecordWithMarkings as an argument. Here is an example of what the DAO might look like:
    @Dao
    interface RecordWithMarkingsDao {
    
        @Transaction
        @Query("SELECT * FROM records")
        fun getAllRecordsWithMarkings(): List<RecordWithMarkings>
    
        @Transaction
        @Query("SELECT * FROM records WHERE idRecord = :recordId")
        fun getRecordWithMarkings(recordId: Long): RecordWithMarkings
    }
    
    • for CRUD operations use separate DAOs (or methods within the same DAO) that work directly with the Record and Marking entities, for example:
    @Dao
    interface RecordDao {
    
        @Insert
        fun insertRecord(record: Record): Long
    
        @Update
        fun updateRecord(record: Record)
    
        @Delete
        fun deleteRecord(record: Record)
    }
    
    @Dao
    interface MarkingDao {
    
        @Insert
        fun insertMarking(marking: Marking): Long
    
        @Update
        fun updateMarking(marking: Marking)
    
        @Delete
        fun deleteMarking(marking: Marking)
    }
    

    This should help :)