The following code is an MVVM artchitecture based ROOM implementation in Kotlin for Android.
I have a problem with the prepopulation of database. My database don't have any data or I can't find the solution to get or insert data.
The IDE doesn't throw any compilation problems.
I think the problem is in the viewModel but I'm at a dead end.
EDITED: I have seen the db and it's populated. The insert is fine but not the data read. I will try to modify the ViewModel and its associated livedata and mutablelive data.
This is my data class:
@Entity(tableName = Constants.DATABASE_NAME)
data class Card(
@PrimaryKey(autoGenerate = true) @ColumnInfo(name = Constants.COLUMN_NAME_ID) val id: Int?,
@ColumnInfo(name = Constants.COLUMN_NAME_FAV) val fav: Boolean,
@ColumnInfo(name = Constants.COLUMN_NAME_DATE) val date: Date,
@ColumnInfo(name = Constants.COLUMN_NAME_RAW_VALUE) val rawValue: String,
@ColumnInfo(name = Constants.COLUMN_NAME_STORE_TYPE) val storeType: String,
@ColumnInfo(name = Constants.COLUMN_NAME_STORE_NAME) val storeName: String,
@ColumnInfo(name = Constants.COLUMN_NAME_STORE_NOTES) val storeNotes: String
)
This is my interface dao
@Insert
suspend fun insertNewCard(card: Card) {
}
@Delete
suspend fun deleteCard(card: Card)
@Query("SELECT * FROM ${Constants.DATABASE_NAME}")
fun getAllCards(): LiveData<List<Card>>
- UPDATE: This is my Database class
@Database(
entities = [Card::class],
version = 1,
exportSchema = true,
//autoMigrations = [AutoMigration (from = 1, to = 2)]
)
@TypeConverters(Converters::class)
abstract class CardsDatabase : RoomDatabase() {
abstract fun cardsDao(): CardsDao
private class CardsDatabaseCallback(private val scope: CoroutineScope)
: RoomDatabase.Callback() {
override fun onCreate(db: SupportSQLiteDatabase) {
super.onCreate(db)
INSTANCE?.let { database ->
scope.launch {
populateDatabase(database.cardsDao())
}
}
}
suspend fun populateDatabase(cardsDao: CardsDao) {
val testCard = Card(null,
false,
((Calendar.getInstance()).time),
"test",
"codebar test 1",
"Prueba tienda",
"Esta es una tienda de prueba"
)
val testCardTwo = Card(null,
false,
((Calendar.getInstance()).time),
"barcode test 2",
"codebar",
"Prueba tienda 2",
"Esta es una segunda prueba de tienda"
)
Log.d("ROOM", "$testCard")
cardsDao.insertNewCard(testCard)
}
}
companion object {
@Volatile
private var INSTANCE: CardsDatabase? = null
fun getDatabase(context: Context, scope: CoroutineScope): CardsDatabase {
return INSTANCE ?: synchronized(this) {
val instance = Room.databaseBuilder(
context.applicationContext,
CardsDatabase::class.java,
Constants.DATABASE_NAME)
.addCallback(CardsDatabaseCallback(scope))
.build()
INSTANCE = instance
instance
}
}
}
This is the repository class
val allCards: LiveData<List<Card>> = cardsDao.getAllCards()
@Suppress("RedundantSuspendModifier")
@WorkerThread
suspend fun insertCard(card: Card) {
cardsDao.insertNewCard(card)
}
suspend fun deleteCard(card: Card) {
cardsDao.deleteCard(card)
}
This is my viewModel:
The Logcat shows as null the LiveData at this point.
private val _allCards = MutableLiveData<List<Card>>()
val allCards : LiveData<List<Card>> = _allCards
init {
getAllCards()
Log.d("ROOM", "${allCards.value}")
}
private fun getAllCards() = viewModelScope.launch {
_allCards.value = cardRepository.allCards.value
Log.d("ROOM", "_ : ${_allCards.value}")
}
fun insertCard(card: Card) = viewModelScope.launch {
cardRepository.insertCard(card)
Log.d("ROOM", "inserted: $card")
}
}
class CardsViewModelFactory(private val repository: CardRepository) :
ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(CardsViewModel::class.java)) {
@Suppress("UNCHECKED_CAST")
return CardsViewModel(repository) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
This is the application class:
private val applicationScope = CoroutineScope(SupervisorJob())
private val database by lazy { CardsDatabase.getDatabase(this, applicationScope) }
val repository by lazy { CardRepository(database.cardsDao()) }
For last, the observe in MainActivity:
cardsViewModel.allCards.observe(this) { cards ->
cards?.let { adapter.setData(it) }
}
CodePudding user response:
The problem is the LiveData created in DAO. To fix it:
- Remove LiveData from DAO to return a List:
@Query("SELECT * FROM ${Constants.DATABASE_NAME}")
suspend fun getAllCards(): List<Card>
- From repository, make a suspend fun that get access to DAO and get the List and remove LiveData type.
suspend fun getAllCards() : List<Card> = cardsDao.getAllCards()
- Change the method to access repository in ViewModel. Its works fine.
private fun getAllCards() = viewModelScope.launch {
_allCards.value = cardRepository.getAllCards()
}
Solved.
CodePudding user response:
My version.
It works with classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.21'
add to build.gradle
:
plugins {
id 'kotlin-kapt'
}
dependencies {
def room_version = "2.4.2"
implementation "androidx.room:room-runtime:$room_version"
kapt "androidx.room:room-compiler:$room_version"
implementation "androidx.room:room-ktx:$room_version"
testImplementation "androidx.room:room-testing:$room_version"
}
create Entity.class:
@Entity(tableName = "cards")
data class CardEntity(
@PrimaryKey(autoGenerate = true)
val id: Int = 0,
val data: String
)
create Dao.class
@Dao
interface CardDAO {
@Insert
suspend fun insert(cardEntity: CardEntity): Long
@Delete
suspend fun delete(cardEntity: CardEntity): Int
@Update
suspend fun update(cardEntity: CardEntity): Int
@Query("SELECT * FROM cards WHERE id = :id")
fun getDataById(id:Int): Flow<CardEntity?>
@Query("SELECT * FROM cards")
fun getAll(): Flow<List<CardEntity>>
}
create Database.class
:
for work you should to use singleton
@Database(
entities = [
CardEntity::class
],
version = 1
)
abstract class MyDatabase : RoomDatabase() {
companion object {
private var INSTANCE: MyDatabase? = null
fun get(context: Context): MyDatabase {
if (INSTANCE == null) {
INSTANCE = Room.databaseBuilder(context, MyDatabase::class.java,
"testBase").build()
}
return INSTANCE as MyDatabase
}
}
abstract fun cardDAO(): CardDAO
}
test TestDatabase.class
:
@RunWith(AndroidJUnit4::class)
class TestDatabase {
lateinit var db: MyDatabase
@Before
fun createDB() {
val appContext =
InstrumentationRegistry.getInstrumentation().targetContext
db = Room.inMemoryDatabaseBuilder(appContext,
MyDatabase::class.java).build()
}
@After
@Throws(IOException::class)
fun closeDB() {
db.close()
}
private fun setData() = runBlocking {
db.cardDAO().insert(CardEntity(data = "1"))
db.cardDAO().insert(CardEntity(data = "2"))
db.cardDAO().insert(CardEntity(data = "3"))
db.cardDAO().insert(CardEntity(data = "4"))
db.cardDAO().insert(CardEntity(data = "5"))
}
@Test
fun test() = runBlocking {
setData()
Assert.assertEquals(db.cardDAO().getAll().first().size, 5)
val flowIdFirst = db.cardDAO().getDataById(1)
Assert.assertEquals(flowIdFirst.first()?.data, "1")
Assert.assertEquals(db.cardDAO().getDataById(2).first()?.data, "2")
Assert.assertEquals(db.cardDAO().getDataById(3).first()?.data, "3")
Assert.assertEquals(db.cardDAO().getDataById(4).first()?.data, "4")
Assert.assertEquals(db.cardDAO().getDataById(5).first()?.data, "5")
Assert.assertTrue(db.cardDAO().update(CardEntity(1, "my new data")) > 0)
Assert.assertEquals(flowIdFirst.first()?.data, "my new data")
Assert.assertTrue(db.cardDAO().delete(CardEntity(1,"")) > 0)
Assert.assertEquals(flowIdFirst.first(), null)
}
}