I'm encountering a java.lang.RuntimeException in my Android application, specifically:
java.lang.RuntimeException: Cannot find implementation for com.daviddev.passwordmanager.Room.AccountDataDataBase. AccountDataDataBase_Impl does not exist
This error suggests that Room is unable to locate the generated implementation for my database class, AccountDataDataBase. I've already tried cleaning and rebuilding my project, syncing Gradle, and ensuring my Room dependencies are correctly configured. However, the issue persists.I've made sure that my database class is abstract and extends RoomDatabase. It's annotated with @Database and includes the entities, version, and exportSchema properties. I have a DAO defined with appropriate annotations. My Room dependencies in build.gradle seem correct. The complete project is at: https://github.com/bdavidgm/test
@Database(entities = [AccountData::class, AccountName::class], version = 1, exportSchema = false)
abstract class AccountDataDataBase: RoomDatabase() {
abstract fun accountDataDao() : AccountDataDatabaseDao
}
class accountDataRepository @Inject constructor(private val accountDataDatabaseDao: AccountDataDatabaseDao) {
suspend fun addAccountData(accountData: AccountData) = accountDataDatabaseDao.insert(accountData)
suspend fun updateAccountData(accountData: AccountData) = accountDataDatabaseDao.update(accountData)
suspend fun deleteAccountData(accountData: AccountData) = accountDataDatabaseDao.delete(accountData)
fun getAllAccountData() = accountDataDatabaseDao.getAllAccountData().flowOn(Dispatchers.IO).conflate()
fun getAccountDataById(id:Long): Flow<AccountData> = accountDataDatabaseDao.getAccountDataById(id).flowOn(Dispatchers.IO).conflate()
suspend fun getLastAccountData(accountId: Long): Flow<AccountData> = accountDataDatabaseDao.getLastAccountData(accountId).flowOn(Dispatchers.IO).conflate()
suspend fun getAllAccountData(accountId: Long): Flow<List<AccountData>> = accountDataDatabaseDao.getAllAccountData(accountId).flowOn(Dispatchers.IO).conflate()
}
@Dao // Data Access Observer
interface AccountDataDatabaseDao {
// Crud
@Query("SELECT * FROM AccountData")
fun getAllAccountData(): Flow<List<AccountData>>
@Query("SELECT * FROM AccountData WHERE id = :id")
fun getAccountDataById(id: Long): Flow<AccountData>
@Query("SELECT * FROM AccountData WHERE account_id = :accountId ORDER BY creation_date DESC LIMIT 1")
suspend fun getLastAccountData(accountId: Long): Flow<AccountData>
@Query("SELECT * FROM AccountData WHERE account_id = :accountId ORDER BY creation_date DESC")
suspend fun getAllAccountData(accountId: Long): Flow<List<AccountData>>
/* @Query("SELECT account_id FROM AccountName WHERE account_name = :accountName")
suspend fun getAccountID(accountName: String): Long
@Query("SELECT account_name FROM AccountName")
suspend fun getAllAccountName(): flow<List<String>>*/
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(accountData: AccountData)
@Update(onConflict = OnConflictStrategy.REPLACE)
suspend fun update(accountData: AccountData)
@Delete
suspend fun delete(accountData: AccountData)
}
@Entity(tableName = "AccountData")
data class AccountData(
@PrimaryKey(autoGenerate = true)
val id : Long = 0,
@ColumnInfo(name = "account_id")
val account_id : Long =0,
@ColumnInfo(name = "user_name")
val user_name: String="",
@ColumnInfo(name = "password")
val password: String="",
@ColumnInfo(name = "creation_date")
val creation_date : LocalDateTime = LocalDateTime.now(),
@ColumnInfo(name = "expiration_date")
val expiration_date : LocalDateTime = LocalDateTime.now()
)
@Entity(tableName = "AccountName")
data class AccountName(
@PrimaryKey(autoGenerate = true)
val account_id : Long = 0,
@ColumnInfo(name = "account_name")
val account_name: String
)
So I checked and cloned the GitHub repo you mentioned and noticed that you're using:
annotationProcessor("androidx.room:room-compiler:$room_version")
in your Gradle build file. Since your project is Kotlin-based, you should replace that with:
kapt("androidx.room:room-compiler:$room_version")
**
Note:** Using kapt
might throw a warning like:
w: Kapt currently doesn't support language version 2.0+. Falling back to 1.9.
If you encounter that, you can consider using KSP (Kotlin Symbol Processing) as an alternative.
Additionally, I noticed your entity class defines columns like this:
@ColumnInfo(name = "creation_date")
val creation_date: LocalDateTime = LocalDateTime.now(),
@ColumnInfo(name = "expiration_date")
val expiration_date: LocalDateTime = LocalDateTime.now(),
Room doesn't know how to store LocalDateTime
values by default, so you should use TypeConverters. Here's how you can do it:
Create a Converter.kt
:
import androidx.room.TypeConverter
import java.time.LocalDateTime
class Converter {
@TypeConverter
fun fromDate(date: LocalDateTime): String {
return date.toString()
}
@TypeConverter
fun toDate(date: String): LocalDateTime {
return LocalDateTime.parse(date)
}
}
Update your database class:
@Database(entities = [AccountData::class, AccountName::class], version = 1, exportSchema = false)
@TypeConverters(Converter::class)
abstract class AccountDataDataBase : RoomDatabase() {
abstract fun accountDataDao(): AccountDataDatabaseDao
}
Additionally, In your DAO, you have these functions:
// CRUD operations
@Query("SELECT * FROM AccountData")
fun getAllAccountData(): Flow<List<AccountData>>
@Query("SELECT * FROM AccountData WHERE id = :id")
fun getAccountDataById(id: Long): Flow<AccountData>
@Query("SELECT * FROM AccountData WHERE account_id = :accountId ORDER BY creation_date DESC LIMIT 1")
suspend fun getLastAccountData(accountId: Long): Flow<AccountData>
@Query("SELECT * FROM AccountData WHERE account_id = :accountId ORDER BY creation_date DESC")
suspend fun getAllAccountData(accountId: Long): Flow<List<AccountData>>
You should remove suspend
from the functions that return a Flow
Why Remove suspend
?
Functions that return a Flow
are already designed to work asynchronously and lazily. Marking them as suspend
is unnecessary and may confuse the intended usage because:
Flows are Asynchronous by Design:
Flow
builds in the mechanism for handling asynchronous streams of data. You don’t need to mark these functions as suspend functions because the flow itself handles the asynchronous processing.
Avoiding Redundancy:
The suspend
keyword is useful for functions that need to suspend execution (like network calls or long-running database operations) but since flows already do that, adding suspend
is redundant and might lead to unintended behavior or confusion.
So, you should change the DAO functions that return Flow to be normal functions without the suspend modifier:
@Query("SELECT * FROM AccountData WHERE account_id = :accountId ORDER BY creation_date DESC LIMIT 1")
fun getLastAccountData(accountId: Long): Flow<AccountData>
@Query("SELECT * FROM AccountData WHERE account_id = :accountId ORDER BY creation_date DESC")
fun getAllAccountData(accountId: Long): Flow<List<AccountData>>
Then in your repository, remove the suspend
keyword from the functions that wrap these flow-returning DAO calls:
class AccountDataRepository @Inject constructor(
private val accountDataDatabaseDao: AccountDataDatabaseDao
) {
suspend fun addAccountData(accountData: AccountData) = accountDataDatabaseDao.insert(accountData)
suspend fun updateAccountData(accountData: AccountData) = accountDataDatabaseDao.update(accountData)
suspend fun deleteAccountData(accountData: AccountData) = accountDataDatabaseDao.delete(accountData)
fun getAllAccountData(): Flow<List<AccountData>> =
accountDataDatabaseDao.getAllAccountData().flowOn(Dispatchers.IO).conflate()
fun getAccountDataById(id: Long): Flow<AccountData> =
accountDataDatabaseDao.getAccountDataById(id).flowOn(Dispatchers.IO).conflate()
fun getLastAccountData(accountId: Long): Flow<AccountData> =
accountDataDatabaseDao.getLastAccountData(accountId).flowOn(Dispatchers.IO).conflate()
fun getAllAccountData(accountId: Long): Flow<List<AccountData>> =
accountDataDatabaseDao.getAllAccountData(accountId).flowOn(Dispatchers.IO).conflate()
}
Reason for Removing suspend
in Repository:
Consistency with DAO:
Since the DAO functions that return Flow
do not need to be suspend
, the repository functions wrapping them shouldn’t be suspend
either.
Proper Use of Dispatchers:
You're already switching to a background dispatcher (Dispatchers.IO
) using flowOn
. This ensures that the flow is collected on the appropriate thread, making the operation safe and asynchronous without needing the suspend modifier at the function level.
Cleaner API:
By not marking these functions as suspend
, you’re making it clear that they return a reactive stream that can be observed over time rather than a one-time asynchronous result.