simple Room query in Android app doesn't work - android

I'm following an Android development tutorial that uses Room to store data and retrieve it.
I have the following entity:
#Entity
data class DogBreed(
#ColumnInfo(name = "breed_id")
val breedId: String?,
#ColumnInfo(name = "dog_name")
val dogBreed: String?,
#ColumnInfo(name = "life_span")
val lifeSpan: String?,
#ColumnInfo(name = "breed_group")
val breedGroup: String?,
#ColumnInfo(name = "bred_for")
val bredFor: String?,
val temperament: String?,
#ColumnInfo(name = "dog_url")
val imageUrl: String?
) {
#PrimaryKey(autoGenerate = true)
var uuid: Int = 0
}
and the following Room DAO:
#Dao
interface DogDao {
#Insert
suspend fun insertAll(vararg dogs: DogBreed): List<Long>
#Query("SELECT * FROM dogbreed")
suspend fun getAllDogs(): List<DogBreed>
#Query("SELECT * FROM dogbreed WHERE uuid = :dogId")
suspend fun getDog(dogId: Int): DogBreed?
#Query("DELETE FROM dogbreed")
suspend fun deleteAllDogs()
}
The insertAll method works fine. I can retrieve all data using getAllDogs() and the uuid is auto-generated as intended. However, the getDog(dogId) method does not work. It always returns null.
It's a simple query and I don't see anything wrong with it. What could be the issue?
The query is used in the following code:
val dao = DogDatabase(getApplication()).dogDao()
val dog = dao.getDog(uuid)
Toast.makeText(getApplication(), "Dog from DB ${uuid} / $dog", Toast.LENGTH_SHORT).show()
The toast shows something like "Dog from DB 12 / null".

It turned out that the uuid, which was supplied as an action parameter when navigating from a different fragment, was not the correct value. It was the breedId instead of the uuid property.

I used Room a couple of time but it seems your uuId is an Int type and you are using a String type in your query. As far as I remember you have to use like for sql queries.
My guess is that you have to use both Int values or String values but not a mixed of it. And if you are using String type use Like instead of equal.
"SELECT * FROM dogbreed WHERE uuid like :dogId"

Related

Android - Room query always returns row's id=0?

I have a query like this in my room Dao
#Query("SELECT * FROM my_data ORDER BY id ASC LIMIT 1")
suspend fun getFirstItem(): MyEntity?
and MyEntity is just a data class with an auto generated id.
#Entity(tableName = "my_data")
data class MyEntity(
#PrimaryKey(autoGenerate = true)
val id: Int = 0,
#ColumnInfo(name = "date_created")
var dateCreated: String? = null,
#ColumnInfo(name = "description")
var description: String? = null
)
When I run the query the entity object that is returned has always id=0.
How can I get the actual id of the row using a query in room?
EDIT: This is the function I'm calling the query
suspend fun getFirstItem(context: Context): MyEntity? {
val db = MyDatabase.getDb(context)
return db.MyDao().getFirstItem()
}
The returned MyEntity object is something like this.
MyEntity(id=0, dateCreated="2022-12-07 09:38:37", description="some description")
Where the id is always returned as 0, although it isn't actually 0
In My System, Your Query works Properly.
All Available Entries
Query Result
Do invalid catch/Restart and Try again

How to insert a set of nullable entites in Room?

I have an Room Entity called City:
#Entity(tableName = "cities")
class City(
#PrimaryKey
#ColumnInfo(name = "unique_city_id")
val id: Long,
#ColumnInfo(name = "city_name")
val name: String,
#ColumnInfo(name = "city_code")
val code: String,
)
And I have a list of objects of this class type:
data class CoffeeHouse(
override val id: Long,
override val latitude: Double,
override val longitude: Double,
override val city: City?,
override val address: String,
)
I need to save both CoffeeHouse and City classes. Because there are a lot of identical cities, I map a list of coffeehouses to a set of cities to get only unique ones:
val cities = coffeeHouses.map { it.city?.toPersistenceType() }.toSet()
(.toPersistenceType() just maps domain type to persistence)
And then I'm inserting coffeeHouses and cities into Room Database using these DAOs:
#Dao
abstract class CoffeeHouseDao(val cacheDatabase: CacheDatabase) {
private val cityDao = cacheDatabase.cityDao()
#Insert(onConflict = REPLACE)
abstract suspend fun insertAllCoffeeHouses(coffeeHouses: List<CoffeeHouse>)
#Transaction
open suspend fun insertAllCoffeeHousesInfo(
coffeeHouses: List<CoffeeHouse>,
cities: Set<City?>,
) {
insertAllCoffeeHouses(coffeeHouses)
cityDao.setCities(cities)
}
}
#Dao
interface CityDao {
#Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun setCities(cities: Set<City?>)
The problem is when I'm trying to insert Set<City?> app crashes with an exception:
Uncaught exception java.lang.NullPointerException:
Attempt to invoke virtual method 'long com.coffeeapp.android.persistence.entity.City.getId()'
on a null object reference
Stacktrace points on the line of cities insertion, so I don't understand how to make it right.
This is happening because you have set the ID field in city as the Primary Key for that table and it cannot be null.
You can try changing your annotation to
#PrimaryKey(autoGenerate = true)
Or if you do not want auto increment you have to make sure that the id is not null whenever you are inserting a City.
I think it is because of the city: City? and cities: Set<City?> in the CofeeHouse entity. Try to make them not nullable.
To allow for inserting a with null you can use :-
#Entity(tableName = "cities")
class City(
#PrimaryKey
#ColumnInfo(name = "unique_city_id")
val id: Long?, //<<<<<<<< ? ADDED
#ColumnInfo(name = "city_name")
val name: String,
#ColumnInfo(name = "city_code")
val code: String,
)
As such id will be generated when it is inserted. e.g. the following (based upon for reduced for convenience).
However, the REPLACE conflict strategy will never result in replacement as null will generate a unique id.
What I believe you want is that either city name, the city code or both (together or independently) constitutes a unique entry.
As such :-
#Entity(
tableName = "cities",
indices = [
/*
probably just one of these all three is overkill
*/
Index(value = ["city_name"],unique = true),
Index(value = ["city_code"], unique = true),
Index(value = ["city_name","city_code"],unique = true)
]
)
class City(
#PrimaryKey
#ColumnInfo(name = "unique_city_id")
val id: Long?,
#ColumnInfo(name = "city_name")
val name: String,
#ColumnInfo(name = "city_code")
val code: String,
)
As an example consider the following :-
cityDao.setCities(setOf<City>(City(null,"Sydney","SYD1"),City(null,"New York","NY1")))
cityDao.setCities(setOf<City>(City(null,"Sydney","SYD1"),City(null,"New York","NY1")))
So an attempt is made to add the same set of cities The result is:-
i.e. The first added Sydney and New York with id's 1 and 2, the second attempt replaced due to the conflict which deletes the originals so you end up with id's 3 and 4. Without the unique index(s) then the result would have been 4 rows with id's 1,2,3 and 4.

How can I query a record of part fields using Room in Android Studio with Kotlin?

It works well to query record MVoice using fun listVoice():LiveData<List<MVoice>> with Room framework in Android Studio with Kotlin.
Now I hope to query record of part fields (Such as ID and name) of MVoice, how can I do?
interface DBVoiceDao{
#Query("SELECT * FROM voice_table ORDER BY createdDate desc")
fun listVoice():LiveData<List<MVoice>>
/* How can I do this?
#Query("SELECT id, name FROM voice_table ORDER BY createdDate desc")
fun listVoiceOfPartField():LiveData< ??? >
*/
}
#Entity(tableName = "voice_table", indices = [Index("createdDate")])
data class MVoice(
#PrimaryKey (autoGenerate = true) #ColumnInfo(name = "id") var id: Int = 0,
var name: String = "Untitled",
var path: String = "My path",
var createdDate: Calendar = Calendar.getInstance(),
var isStar: Boolean = false,
var description: String="My description"
)
As "Florina Muntenescu" suggested that Read only what you need in this article 7 Pro-tips for Room
You can achieve it by making a new Model Class:
data class VoiceMinimal(#ColumnInfo(name = "id") val id: Int,
#ColumnInfo(name = "name") val name: String)
In the DAO class, we define the query and select the right columns from the voice table.
#Dao
interface DBVoiceDao {
#Query(“SELECT id, name FROM voice_table ORDER BY createdDate dESC)
fun getVoiceMinimal(): List<VoiceMinimal>
}

Room update query

I'm using Room persistence library for Android and trying to make 'update' query for the boolean field.
#Update
suspend fun updateProduct(product: Product)
Product entity:
#Entity(tableName = "products")
data class Product(
#ColumnInfo(name = "name") val name: String = "",
#ColumnInfo(name = "price") val price: Int = 0,
#ColumnInfo(name = "count") val count: Int = 0,
#ColumnInfo(name = "description") val description: String = "",
#ColumnInfo(name = "isPurchased") val isPurchased : Boolean = false
) {
#PrimaryKey var id: String = UUID.randomUUID().toString()
#ColumnInfo(name = "date") var date: Long = Date().time
}
Similar queries as delete, insert work fine. The underhood query should find the id of product and update all fields but it doesn't work. Please don't write about insert query instead update, it's a dirty trick.
Update: update method returns 0 and it means it doesn't work, according to docs it should return num of updated record:
Although usually not necessary, you can have this method return an int
value instead, indicating the number of rows updated in the database.
you can try this
#Query("UPDATE products SET price=:price WHERE id = :id")
void update(Float price, int id);
Regarding the docs it says you have to do something like this:
#Update
fun updateProduct(product: Product) // no need of suspend
also you can control what happen onConflict. Note that if you don't specify by default it is OnConflictStrategy.ABORT which roll back the transaction and does nothing. So you might wanna add something like #Update(onConflict = OnConflictStrategy.REPLACE).

Check if an entry is already in a livedata list wihout looping through the list

I am trying to build an app to help me track some of the tasks we have to do in the game.
I have a Firebase Firestore database that store all the tasks and I download at the application launch the data and add only the one I don't have.
Here is my entry model:
#Entity(tableName = "entry_table")
data class Entry(
#PrimaryKey(autoGenerate = true) var uid: Long?,
#ColumnInfo(name = "title") val title: String,
#ColumnInfo(name = "description") val description: String,
#ColumnInfo(name = "target") val target: Int = 0,
#ColumnInfo(name = "position") val position: Int = 0,
#ColumnInfo(name = "starred") val starred: Boolean = false
) {
constructor(): this(null, "", "", 0, 0, starred = false)
}
Since I download the document from the firestore database I cannot set an ID before inserting the entries in my SQLite database.
This means that I cannot use the "contains" method on my livedata list (since the entries I recieve has a "null" id and the one from the database has an id). I need to loop though all the data, here is the code:
#WorkerThread
suspend fun insertEntry(entry: Entry) {
for (doc in entriesList.value!!){
if (doc.description == entry.description && doc.title == entry.title) {
Log.d("MAIN_AC", "Entry already saved $entry")
return
}
}
entryDAO.insertEntry(entry)
}
My code works but I am not satisfied with it, is there a better way to make this happen? I was hoping that the contains method could ignore some arguments (in my case the autogenerated ID)
One way you can go about, assuming you are using Room, it is to annotate your insert function (in the relevant DAO) with OnConflictStrategy.IGNORE.
e.g.
#Dao
interface EntryDao {
#Insert(onConflict = OnConflictStrategy.IGNORE)
fun insert(list: List<Entry>)
// or (if you want the inserted IDs)
// fun insert(list: List<Entry>) : LongArray
}
Be sure to also annotate your entity with the relevant unique index.
e.g.
#Entity(tableName = "entry_table",
indices = [Index(value = ["title", "description"], unique = true)]
)
data class Entry(
#PrimaryKey(autoGenerate = true) var uid: Long,
#ColumnInfo(name = "title") val title: String,
#ColumnInfo(name = "description") val description: String
//...
)
Primary keys should not be null-able, you can .map to Entry wit uid = 0. If you are using the same entity model both locally and remotely that is probably not the best idea.

Categories

Resources