ok so im quite new to using room, ive looked in a lot of places but find it hard to find my answer, alot of places ive looked around seem to have outdated information.
I have setup a room database, i have all the information for it set up as well as a viewmodel to send the infomration from. However i can not for the life of me figure out how to process the information in to android compose.
here is what i have done,any feedback would be good.
#Entity
data class Notes(
#PrimaryKey val uid: Int?,
#ColumnInfo(name = "header") val header: String?,
#ColumnInfo(name = "note") val note: String?
)
#Dao
interface NotesDao {
#Query("SELECT * FROM notes")
fun getAll(): LiveData<List<Notes>>
#Insert (onConflict = OnConflictStrategy.IGNORE)
suspend fun insert(notes: Notes)
#Delete
suspend fun delete(notes: Notes)
}
#Database(entities = [Notes::class], version = 1)
abstract class NotesAppDatabase : RoomDatabase() {
abstract fun notesDao(): NotesDao
companion object {
#Volatile
private var INSTANCE: NotesAppDatabase? = null
fun getDatabase(context: Context): NotesAppDatabase {
// if the INSTANCE is not null, then return it,
// if it is, then create the database
return INSTANCE ?: synchronized(this) {
val instance = Room.databaseBuilder(
context.applicationContext,
NotesAppDatabase::class.java,
"items_database"
)
// Wipes and rebuilds instead of migrating if no Migration object.
// Migration is not part of this codelab.
.fallbackToDestructiveMigration()
.build()
INSTANCE = instance
// return instance
instance
}
}
}
}
class NotesRepository (private val notesDao: NotesDao) {
val readAllData: LiveData<List<Notes>> = notesDao.getAll()
suspend fun addNote(note: Notes){
notesDao.insert(note)
}
}
class NotesRepoViewModel (application: Application): AndroidViewModel(application) {
private val repo: NotesRepository
private val allNotes: LiveData<List<Notes>>
init {
val dao = NotesAppDatabase.getDatabase(application).notesDao()
repo = NotesRepository(dao)
allNotes = repo.readAllData
}
fun notes(notes: Notes){
viewModelScope.launch {
repo.addNote(notes)
}
}
}
Related
I have a simple room database with LiveData, i want to get (cartPriceI: Double) values by id to use it within specific methods.
CartItemsDatabase.kt
#Parcelize
#Entity(tableName = "cart_table")
data class CartItemsDatabase(
#PrimaryKey(autoGenerate = true) var cid: Int,
#ColumnInfo(name = "titleD") var cartTitle: String?,
#ColumnInfo(name = "imageD") var cartImage: Bitmap?,
#ColumnInfo(name = "priceLD") var cartPriceL: Double?,
#ColumnInfo(name = "priceID") var cartPriceI: Double?,
#ColumnInfo(name = "itemNumD") var cartNum: Int?,
#ColumnInfo(name = "descriptionD") var cartDes: String?): Parcelable
CartItemsDAO.kt
#Dao
interface CartItemsDAO {
#Insert(onConflict = OnConflictStrategy.IGNORE)
suspend fun addItem(cartItemsDatabase: CartItemsDatabase)
#Update
suspend fun updateCart(cartItemsDatabase: CartItemsDatabase)
#Query("SELECT * FROM cart_table ORDER BY cid ASC")
fun readAllData(): LiveData<List<CartItemsDatabase>>
#Delete
fun delete(cartItems: CartItemsDatabase)
}
CartRepository.kt
class CartRepository(private val cartItemsDAO: CartItemsDAO) {
val readAllData: LiveData<List<CartItemsDatabase>> = cartItemsDAO.readAllData()
suspend fun addItem(cartItemsDatabase: CartItemsDatabase){
cartItemsDAO.addItem(cartItemsDatabase)
}
suspend fun updateCart(cartItemsDatabase: CartItemsDatabase){
cartItemsDAO.updateCart(cartItemsDatabase)
}
}
CartViewModel.kt
class CartViewModel(application: Application): AndroidViewModel(application) {
val readAllData: LiveData<List<CartItemsDatabase>>
private val repository: CartRepository
init {
val cartDao = AppDatabase.getDatabase(application).cartDao()
repository = CartRepository(cartDao)
readAllData = repository.readAllData
}
fun addItem(cartItemsDatabase: CartItemsDatabase){
viewModelScope.launch(Dispatchers.IO){
repository.addItem(cartItemsDatabase)
}
}
fun updateCart(cartItemsDatabase: CartItemsDatabase){
viewModelScope.launch(Dispatchers.IO) {
repository.updateCart(cartItemsDatabase)
}
}
}
AppDatabase.kt
#Database(entities = [CartItemsDatabase::class], version = 1, exportSchema = false)
#TypeConverters(Converters::class)
abstract class AppDatabase : RoomDatabase() {
abstract fun cartDao(): CartItemsDAO
companion object {
#Volatile
private var INSTANCE: AppDatabase? = null
fun getDatabase(context: Context): AppDatabase {
// if the INSTANCE is not null, then return it,
// if it is, then create the database
val tempInstance = INSTANCE
if (tempInstance != null){
return tempInstance
}
synchronized(this){
val instance = Room.databaseBuilder(
context.applicationContext,
AppDatabase::class.java,
"cart_database"
).build()
INSTANCE = instance
return instance
}
}
private fun buildDatabase(context: Context): AppDatabase {
return Room.databaseBuilder(
context.applicationContext,
AppDatabase::class.java,
"cartItems_database"
)
.build()
}
}
}
I'm a beginner, i want to know if there is possible way to do that and retrieve specific values from database (ex. cartPriceI in Double) by id!
You can query the database by the given id and return it asynchronously either with a suspend fun or LiveData/Flow
In the CartItemsDAO:
#Query("SELECT cartPriceI FROM cart_table WHERE cid = :id")
suspend fun getPrice(id: Int): Double
#Query("SELECT cartPriceI FROM cart_table WHERE cid = :id")
fun getPrice(id: Int): LiveData<Double>
You can get a specific model by SELECT with WHERE statement
#Query("SELECT * FROM cart_table WHERE cid == : cid")
fun getCartItemsDatabase(cid: String): LiveData<CartItemsDatabase?>
Or you can Return a subset of a table's columns
#Query("SELECT priceID FROM cart_table WHERE cid == : cid")
fun getCartItemPrice(cid: String): LiveData<Double>
Image for reference
I am facing a situation whenever I add a new article into the database it will add the new article to the bottom of the RecyclerView, how can I implement it in such a way that it updates the top row instead?
Is it something got to do with the Adapter or do I have to tweak the Doa?
Room Database Implementation
**//ViewModel**
fun saveArticle(article: Article) = viewModelScope.launch {
newsRepository.upsert(article)
}
**
//NewsRepository**
suspend fun upsert(article: Article) = db.getArticleDao().upsert(article)
**//Implementation of the Database**
#Database(
entities = [Article::class],
version = 1
)
#TypeConverters(Converters::class)
abstract class ArticleDatabase : RoomDatabase() {
abstract fun getArticleDao(): ArticleDao
companion object {
#Volatile
private var instance: ArticleDatabase? = null
private val LOCK = Any()
operator fun invoke(context: Context) = instance ?: synchronized(LOCK) {
instance ?: createDatabase(context).also { instance = it }
}
private fun createDatabase(context: Context) =
Room.databaseBuilder(
context.applicationContext,
ArticleDatabase::class.java,
"article_db.db"
).build()
}
}
**//DOA**
#Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun upsert(article: Article): Long //returns the id's of the article
**//Entity**
#Entity(
tableName = "articles", indices = [Index(value = ["url","title"], unique = true)]
)
#Parcelize
data class Article(
#PrimaryKey(autoGenerate = true)
var id: Int? =null,
val author: String?,
val description: String?,
val source: Source?,
val title: String?,
val url: String?,
val urlToImage: String?,
val publishedAt: String?,
val content: String?
): Parcelable {
override fun hashCode(): Int {
var result = id.hashCode()
if(url.isNullOrEmpty()){
result = 31 * result + url.hashCode()
}
return result
}
}
Expecting the top row to be updated instead of the bottom row, the Room Database docs did not talk much about changing the way the article is inserted, tried to reference other people's work, couldn't find the solution
The following code is an MVVM architecture-based ROOM implementation in Kotlin for Android.
I have a problem with the pre-population of the database. My database doesn'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) }
}
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)
}
}
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 the repository, make a suspend fun that gets access to DAO and gets the List, and remove the LiveData type.
suspend fun getAllCards() : List<Card> = cardsDao.getAllCards()
Change the method to access the repository in ViewModel. It works fine.
private fun getAllCards() = viewModelScope.launch {
_allCards.value = cardRepository.getAllCards()
}
Solved.
I'm trying to observe changes in DB made from another fragment.
I have a fragment A (contains a recyclerView with items) with a ViewModel that has inside a LiveData property from the Room database.
Like this:
val allItems: LiveData<List <Item>> = repo.getAll()
If I open a new fragment (let's call it B) from fragment A and do repo.insert(item) there, I expect the LiveData's observer to fire on allItems when returning back to fragment A. But that doesn't happen.
How a can fix it nicely?
Of course I can get data in onViewCreated() every time I open the fragment A, but I believe there must be a better way.
class CharactersViewModel : BaseViewModel() {
private val db get() = AppDatabase.getDB(MyApplication.application)
private val repo = CharacterRepository(viewModelScope, db.characterDao())
val characters: LiveData<List<Character>> = repo.getAll()
}
class CharacterRepository(
private val scope: CoroutineScope,
private val dao: CharacterDao
) {
fun getAll() = dao.getAll()
fun getById(itemId: Int) = dao.getById(itemId)
fun insert(item: Character) = scope.launch {
dao.insert(item)
}
fun update(item: Character) = scope.launch {
dao.update(item)
}
fun delete(item: Character) = scope.launch {
dao.delete(item)
}
}
#Dao
interface CharacterDao {
fun getAll(): LiveData<List<Character>>
fun getById(itemId: Int) : LiveData<Character>
#Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(item: Character)
#Update(onConflict = OnConflictStrategy.REPLACE)
suspend fun update(item: Character)
#Delete
suspend fun delete(item: Character)
}
Note: It looks like this is because the ViewModel of Fragment A is currently inactive. And the issue is not due to viewLifecycleOwner, since the observeForever is also not notified.
Upd: Just found the issue, answer attached.
There was when getting the database instance.
fun getDB(appContext: Context) = Room.databaseBuilder(
appContext,
AppDatabase::class.java, DB_NAME
).build()
I solved the problem by making it a singleton, so now it returns the same instance of the DB.
companion object {
#Volatile
private var INSTANCE: AppDatabase? = null
#Synchronized
fun getDB(context: Context): AppDatabase {
// if the INSTANCE is not null, then return it, otherwise create the database
return INSTANCE ?: run {
val instance = Room.databaseBuilder(
context.applicationContext,
AppDatabase::class.java,
DB_NAME
).build()
INSTANCE = instance
instance
}
}
}
After inserting data into RoomDB when I fetch it using mindValleyDao.getCategories().value It returns null
DatabaseClass
#Database(entities = arrayOf(CategoryBO::class), version = 1, exportSchema = false)
abstract class MindValleyDatabase : RoomDatabase(){
abstract fun mindValleyDao(): MindValleyDao
companion object {
// Singleton prevents multiple instances of database opening at the
// same time.
#Volatile
private var INSTANCE: MindValleyDatabase? = null
fun getDatabase(context: Context): MindValleyDatabase {
val tempInstance = INSTANCE
if (tempInstance != null) {
return tempInstance
}
synchronized(this) {
val instance = Room.databaseBuilder(
context.applicationContext,
MindValleyDatabase::class.java,
"mindvalley_database"
).allowMainThreadQueries()
.fallbackToDestructiveMigration().build()
INSTANCE = instance
return instance
}
}
}
}
CategoryBO.kt
#Entity(tableName = "CategoryEntity")
data class CategoryBO( #PrimaryKey(autoGenerate = true) val id:Int, val name:String)
Doa
#Dao
interface MindValleyDao {
#Query("SELECT * from CategoryEntity ORDER BY id ASC")
fun getCategories(): LiveData<List<CategoryBO>>
#Insert(onConflict = OnConflictStrategy.IGNORE)
fun insert(categoryBO: CategoryBO)
//suspend fun insert(categoryBO: CategoryBO)
#Insert(onConflict = OnConflictStrategy.IGNORE)
fun insert(categoryBOList: List<CategoryBO>)
}
I am testing it by inserting Category and fetching list of categories like
class MindValleyViewModelNew #Inject constructor() : BaseViewModel() {
var categoryList: MutableLiveData<List<CategoryBO>> = MutableLiveData()
private lateinit var mindValleyDao:MindValleyDao
fun loadDatabase(mContext:Context){
mindValleyDao = MindValleyDatabase.getDatabase(mContext).mindValleyDao()
GlobalScope.launch(Dispatchers.IO) {
mindValleyDao.insert(CategoryBO(0,"first item"))
val cats = mindValleyDao.getCategories().value
categoryList.postValue(cats)
}
}
}
mindValleyDao.getCategories() has return type is LiveData, that's why it query value async, you shouldn't call .value
LiveData type in Room should only use for observe,
If you want to get value, modify your code to fun getCategories(): List<CategoryBO> instead