I am using Room Database to store data in my application. But, trying to compile the project, I get the following error: "Invalid query argument". I tried to find the reason in the official documentation and also googling cause of this error, but it was all to no avail. Please, could you help by explaining what this error is in general, what causes it and what I need to fix in the project?
Exact error message
C:\Users\***\ParsedDatabase.java:10: error: Invalid query argument: int. It must be a class or an interface.
public final class ParsedDatabase {
^
Model - ParsedDatabase (Room Database entity, class with problem)
#Entity(tableName = "parsed_database")
data class ParsedDatabase(
#PrimaryKey()
val id: Int = 1,
#ColumnInfo(name = "app_config")
val appConfigSection: HashMap<String, String>,
#ColumnInfo(name = "loans")
val loansList: ArrayList<Offer>,
#ColumnInfo(name = "cards")
val cardsList: ArrayList<ArrayList<Offer>>,
#ColumnInfo(name = "credits")
val creditsList: ArrayList<Offer>,
) {
enum class AppConfigKeys {
app_config,
user_term_html,
extra_field_,
}
enum class LoansAndCreditsKeys {
loans,
credits,
}
enum class CardsKeys {
cards,
}
}
Dao class (I attach just in case)
#Dao
interface LocalDatabaseDao {
#Insert(onConflict = REPLACE)
fun save(parsedDatabase: ParsedDatabase)
#Query("SELECT * FROM parsed_database WHERE id = :id")
fun load(id: Int = 1): ParsedDatabase?
#Delete
fun delete(parsedDatabase: ParsedDatabase)
#Delete(entity = ParsedDatabase::class)
fun delete(id: Int = 1)
}
Database class
#Database(entities = [ParsedDatabase::class], version = 1, exportSchema = false)
#TypeConverters(Converters::class)
abstract class LocalDatabase : RoomDatabase() {
abstract fun getLocalDatabaseDao(): LocalDatabaseDao
}
#Delete(entity = ParsedDatabase::class)
fun delete(id: Int = 1)
I do not think that Room knows what to do with this. If you need that operation, you could try:
#Query("DELETE FROM parsed_database WHERE id = :id")
fun delete(id: Int = 1)
I am trying to understand how to use Room with relational tables. I have created a Job model, that has a list of locations and therefore needs a 1-to-many relation between the Job and Location object. For that, I have created a JobWrapper data class to hold both the Job and the locations. But, when building I get the following error:
The class must be either #Entity or #DatabaseView. -
java.util.Collectionerror: Entities and POJOs must have a usable
public constructor. You can have an empty constructor or a constructor
whose parameters match the fields (by name and type). -
java.util.Collection \models\JobWrapper.java:12: error: Cannot find
the child entity column parentId in java.util.Collection.
Options:
private java.util.Collection<models.Location> locations;
public final class JobWrapper {
^ Tried the following constructors but they failed to match:
JobWrapper(models.Job,java.util.Collection<models.Location>)
-> [param:job -> matched field:job, param:locations -> matched field:unmatched] models\JobWrapper.java:9:
error: Cannot find setter for field.
I notice that it at least cannot find the locations table. But, I do not know how to handle the problem. The problem did not appear while reading from the database - it first appeared when I was trying to put data into the database with my JobDAO. I have already spent a day trying to solve it and are therefore searching for a solution or some advise on how to solve it.
Note: I have been following the following guides:
https://developer.android.com/training/data-storage/room/relationships#one-to-many
https://dev.to/normanaspx/android-room-how-works-one-to-many-relationship-example-5ad0
Here follows some relevant code snippets from my projects:
JobWrapper.kt
data class JobWrapper(
#Embedded val job: Job,
#Relation(
parentColumn = "jobid",
entityColumn = "parentId"
) var locations : Collection<Location>
)
Job
#Entity
data class Job (
#PrimaryKey
#NonNull
var jobid : String,
#NonNull
#ColumnInfo(name = "job_status")
var status : JobStatus,
#NonNull
#SerializedName("createdByAuth0Id")
var creator : String,
#SerializedName("note")
var note : String?,
#NonNull
var organisationId : String,
#NonNull
var type : JobType,
#SerializedName("atCustomerId")
#NonNull
#ColumnInfo(name = "working_at_customer_id")
var workingAtCustomerId : String,
#SerializedName("toCustomerId")
#NonNull
#ColumnInfo(name = "working_to_customer_id")
var workingToCustomerId : String,
)
JobStatus.kt
enum class JobStatus {
CREATED,
READY,
IN_PROGRESS,
FINISHED
}
Location.kt
#Entity
data class Location (
#PrimaryKey(autoGenerate = true)
var entityId: Long,
#NonNull
var parentId: String,
#NonNull
var locationId: String,
#NonNull
var type: String
) {
constructor() : this(0, "", "", "")
}
JobDao.kt
#Dao
interface JobDAO {
#Transaction
#Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(job: JobWrapper)
#Transaction
#Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertAll(jobs: List<JobWrapper>)
#Transaction
#Update
fun update(job: JobWrapper)
#Transaction
#Delete
fun delete(job: JobWrapper)
#Transaction
#Query("DELETE FROM Job")
fun deleteAll()
#Transaction
#Query("SELECT * FROM Job")
fun getAll(): LiveData<List<JobWrapper>>
}
As Kraigolas has pointed out you can only use JobWrapper directly to extract data you need to insert/delete/update via the actual underlying Entities.
Consider the following
(unlike Kraigolas's solultion the extended functions are in the JobDao rather than in a repository(swings and roundabouts argument as to which is better))
note to test I've made some changes for brevity and convenience so the you would have to amend to suit.
JobDao
#Dao
interface JobDAO {
/* Core/Underlying DAO's */
#Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(job: Job): Long
#Insert()
fun insert(location: List<Location>): List<Long>
#Transaction
#Delete
fun delete(location: List<Location>)
#Delete
fun delete(job: Job)
#Update
fun update(job: Job)
#Update
fun update(location: List<Location>)
#Transaction
#Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(job: JobWrapper): Long {
val rv =insert(job.job)
insert(job.locations)
return rv
}
#Transaction
#Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertAll(jobs: List<JobWrapper>) {
for (jw: JobWrapper in jobs) {
insert(jw)
}
}
#Transaction
#Update
fun update(job: JobWrapper) {
update(job.locations)
update(job.job)
}
/* Delete according to JobWrapper allowing optional deletion of locations */
#Transaction
#Delete
fun delete(job: JobWrapper, deleteChildLocations: Boolean) {
if (deleteChildLocations) {
delete(job.locations)
}
delete(job.job)
}
/* will orphan locations as is */
/* Note using Foreign Keys in Location (to Job) with ON DELETE CASCADE */
#Transaction
#Query("DELETE FROM Job")
fun deleteAll()
#Transaction
#Query("SELECT * FROM Job")
fun getAll(): List<JobWrapper>
#Transaction
#Query("SELECT * FROM job WHERE jobid = :jobid")
fun getJobWrapperByJobId(jobid: String ): JobWrapper
}
As can be seen the Core Dao's include Job and Location actions
The JobWrapper actions invoke the Core actions
List have been used instead of Collections for my convenience (aka I only dabble with Kotlin)
Instead of JobType, type has been changed to use String for convenience
As I've tested this the demo/example used follows (obviously JobDao is as above)
The POJO's and Entities used are/were :-
JobWrapper
data class JobWrapper(
#Embedded val job: Job,
#Relation(
parentColumn = "jobid",
entityColumn = "parentId",
entity = Location::class
) var locations : List<Location>
)
List instead of Collection
Job
#Entity
data class Job (
#PrimaryKey
#NonNull
var jobid : String,
#NonNull
#ColumnInfo(name = "job_status")
var status : String,
#NonNull
var creator : String,
var note : String?,
#NonNull
var organisationId : String,
#NonNull
var type : String,
#NonNull
#ColumnInfo(name = "working_at_customer_id")
var workingAtCustomerId : String,
#NonNull
#ColumnInfo(name = "working_to_customer_id")
var workingToCustomerId : String,
)
largely the same but String rather than objects for convenience
Location
#Entity(
foreignKeys = [
ForeignKey(
entity = Job::class,
parentColumns = ["jobid"],
childColumns = ["parentId"],
onUpdate = ForeignKey.CASCADE,
onDelete = ForeignKey.CASCADE
)
])
data class Location (
#PrimaryKey(autoGenerate = true)
var entityId: Long,
#NonNull
var parentId: String,
#NonNull
var locationId: String,
#NonNull
var type: String
) {
constructor() : this(0, "", "", "")
}
Foreign Key added for referential integrity and also CASCADE deletes (UPDATE CASCADE not really required unless you change a jobid, other updates aren't cascaded (and wouldn't need to be))
#Database used JobDatabase
#Database(entities = [Location::class,Job::class],version = 1)
abstract class JobDatabase: RoomDatabase() {
abstract fun getJobDao(): JobDAO
companion object {
var instance: JobDatabase? = null
fun getInstance(context: Context): JobDatabase {
if (instance == null) {
instance = Room.databaseBuilder(context, JobDatabase::class.java, "job.db")
.allowMainThreadQueries()
.build()
}
return instance as JobDatabase
}
}
}
allowMainThreadQueries used to allow testing on main thread
The test/demo activity MainActivity
class MainActivity : AppCompatActivity() {
lateinit var db: JobDatabase
lateinit var dao: JobDAO
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
db = JobDatabase.getInstance(this)
dao = db.getJobDao()
var jobId: String = "Job1"
var jw = JobWrapper(
Job(
jobId,
"x",
"Fred",
"Note for Job1",
"Org1",
"A","Cust1",
"Cust1"),
listOf(
Location(0,jobId,"loc1","J"),
Location(0,jobId,"Loc2","K"),
Location(0,jobId,"Loc3","L")
)
)
dao.insert(jw)
dao.insertAll(createJobWrapperList(10))
dao.delete(dao.getJobWrapperByJobId("job6"),true)
var jobWrapper = dao.getJobWrapperByJobId("job7")
jobWrapper.job.creator = "update creator"
for (l in jobWrapper.locations) {
if (l.type == "M") l.type= "UPDATED"
}
dao.update(jobWrapper)
}
fun createJobWrapperList(numberToCreate: Int): List<JobWrapper> {
val l = mutableListOf<JobWrapper>()
for(i in 1..numberToCreate) {
var jid = "job$i"
l.add(
JobWrapper(
Job(jid,"X","Creator$i","Note for $jid","org$jid","T","custA","custT"),
arrayListOf(
Location(0,jid,"loc_$jid.1","L"),
Location(0,jid,"loc_$jid.2","M"),
Location(0,jid,"loc_$jid.3","N")
)
)
)
}
return l.toList()
}
}
This :-
gets DB instance and the dao.
adds a job and it's location via a single JobWrapper
adds x (10) jobs and 3 locations per Job via a List of JobWrappers generated via the createJobWrapperList function.
4.deletes the a JobWrapper obtained via getJobWrapperByJobId including deleting the underlying locations (true) using delete for the JobWrapper associated with the jobid "job6".
obtains the JobWrapper associated with "job7" changes the creator and changes the locations that have a type of "M" to "UPDATED" (just the one) and then uses the update(JobWrapper) to apply the updates.
WARNING
Inserting using a JobWrapper, as it has REPLACE conflict strategy will result in additional locations if it replaces as the entityId will always be generated.
Result
Runinng the above results in :-
Job Table :-
As can be seen job6 has been deleted (11 rows added 10 left) and job7's creator has been updated.
Location Table :-
As can be seen no job6 locations (was 33 locations (11 * 3) now 30) and the location that was type M has been updated according to the JobWrapper passed.
You asked :-
How do I ensure the relationship to the right parent (job) when inserting the childs (locations)?
I think the above demonstrates how.
JobWrapper does not have an associated table with it. Note that it is nice for pulling from your database because it will grab from the Location table and the Job table, but it makes no sense to insert into your database with a JobWrapper because there is no associated table for it.
Instead, you need to insert Jobs, and Locations separately. The database can query for them together because they are related by jobid and parentid, so you don't have to worry about them losing their relationship to each other, and you can still query for your JobWrapper.
If given my above explanation you still think that you should be able to insert a JobWrapper, then you might create a repository with a method like:
suspend fun insertWrapper(wrapper : JobWrapper){
dao.insertJob(wrapper.job)
dao.insertLocations(wrapper.locations)
}
Where insertJob inserts a single Job, and insertLocations inserts a list of Location.
I am building a database using Room and I can't figure out how to insert the new elements that have a relationship (one to many in my case) into the database. No solution has ever talked about the insertion (they only talk about querying the data).
Here is the DAO:
#Dao
abstract class ShoppingListsDao {
#Insert
abstract suspend fun addNewShoppingList(newShoppingList: ShoppingList)
#Insert
abstract suspend fun addNewItem(newItem: Item)
// This is how I thought it would work but it didn't
#Insert
#Transaction
abstract suspend fun addNewShoppingListWithItems(newShoppingListWithItems: ShoppingListWithItems)
}
Here are my entities:
#Entity
class ShoppingList(
#PrimaryKey(autoGenerate = true)
val listID: Int,
val ListName: String
)
#Entity(foreignKeys = [ForeignKey(
entity = ShoppingList::class,
parentColumns = ["listID"],
childColumns = ["parentListID"]
)])
class Item(
#PrimaryKey(autoGenerate = true)
var itemID: Int,
val name: String,
val quantity: Int,
val parentListID: Int
)
There isn't a way that I am aware of that lets you directly insert a compound entity (like ShoppingListWithItems). You have to just insert the individual entities to their tables.
In your example, you would want to define an insert method for your ShoppingList entity which returns the generated primary key (so you can use it for your other items) and an insert method for your Item entities which can insert a whole list of them.
#Insert
suspend fun addNewShoppingList(newShoppingList: ShoppingList): Long
#Insert
suspend fun addNewItems(newItems: List<Item>)
Then you can run a transaction to insert them in a batch.
#Transaction
suspend fun addNewShoppingListWithItems(shoppingList: ShoppingList, items: List<Item>) {
val listId = addNewShoppingList(shoppingList)
items.forEach { it.parentListId = listId }
addNewItems(items)
}
Here's a good resource to understand one-to-many relationships-> https://developer.android.com/training/data-storage/room/relationships#one-to-many
You can create an embedded object for 'ShoppingListWithItems' as (more on embedded objects - https://developer.android.com/training/data-storage/room/relationships#nested-objects):
data class ShoppingListWithItems(
#Embedded val shoppingList: ShoppingList,
#Relation(parentColumn = "listID", entityColumn = "parentListID") val itemList: List<Item>
)
To store them in the database, you can simply use a transaction:
#Transaction
suspend fun createTransaction(shoppingList: ShoppingList, itemList: List<Item>) {
addNewShoppingList(shoppingList)
addNewItem(*itemList) // or create an alternate to accept the list itself.
}
To retrieve the 'ShoppingListWithItems' instance:
#Query("SELECT * from ShoppingList where listID =:id")
suspend fun getShoppingListWithItemsById(id: String): List<ShoppingListWithItems>
According to the documentation an #Insert function can return a long, which is the new rowId for the inserted item. How can I use the return value?
#Dao
interface TodoDao {
#Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(todo: TodoItem): Long
}
Just to note the id for my #Entity is autogenerated.
#PrimaryKey(autoGenerate = true)
Here is the whole TodoItem entity.
#Entity(tableName = "todos")
#Parcelize
data class TodoItem(val text: String, val priority: Priority) : Parcelable {
#PrimaryKey(autoGenerate = true)
var todoId: Long = 0
}
If id onTodoItemis avar, you could assign the return value toid`, so now your entity has its generated primary key, for use with future DAO operations.
If you are going to use #Parcelize, ensure that all essential properties are in the data class constructor. As it stands, your todoId property would not get put into the Parcel.
#Entity(tableName = "todos")
#Parcelize
data class TodoItem(
val text: String,
val priority: Priority,
#PrimaryKey(autoGenerate = true) var todoId: Long = 0
) : Parcelable
Then, given an entity named entity and a DAO named dao:
entity.todoId = dao.insert(entity)
I want to store Strings (uids) in my Room DB. I want these uids to be able to be:
Queried (check to see if a uid exists in my list of strings)
Added to (add a uid to the list of strings)
What is the most efficient way to achieve this?
Here is my skeleton to give you some context, though my understanding of Room is very basic so there will be mistakes:
#Entity(tableName = "user_data")
data class UserData(
#PrimaryKey(autoGenerate = true) val uid: Int,
#ColumnInfo(name = "matched_users") var matchedUsers: Set<String>
)
#Dao
interface UserDataDao {
// Check to see if the uid is in matchedUsers Set/List
#Query("SELECT * FROM user_data WHERE :matchId IN matched_users")
fun matchedBefore(matchId: String): Boolean
// Add uid to Set/List
#Insert(onConflict = OnConflictStrategy.ABORT)
fun addMatchUid(uid: String)
}
Any suggestions appreciated.
According to your context, it seems you want to build one-to-many relationship, to do so you can follow the bellow codes. If not let me know what exactly you want.
#Entity(tableName = "user_data")
data class UserData(
#PrimaryKey(autoGenerate = true) val uid: Int,
#ColumnInfo(name = "name") var name: String
)
#Entity(tableName = "user_matcher")
data class UserMatcher(
#PrimaryKey(autoGenerate = true) val uid: Int,
#ColumnInfo(name = "userId") var userId: Int
)
#Dao
interface UserMatcherDao {
// Check to see if the uid is in matchedUsers Set/List
#Query("SELECT m.id as mid, u.id as uid, u.name FROM user_matcher m INNER JOIN user_data u ON m.userId = u.id WHERE m.id = :matchId")
fun getMatchedUsers(matchId: Int): List<UserData>
}