My entity class:
#Entity(tableName = "student")
data class Student(
#PrimaryKey(autoGenerate = true)
val id: Long,
val name: String,
val age: Int,
val gpa: Double,
val isSingle: Boolean
)
The problem is, since the id is auto-generated by the Room database - means no matther what I put for the id into the constructor it will be overridden anyway, and because it is one of the parameter in the constructor, I have to give the id every time like this:
val student = Student(0L, "Sam", 27, 3.5, true)
How can I avoid making up the id so I can just put in the neccessary data like this:
val student = Student("Sam", 27, 3.5, true)
Don't place the id in the constructor:
#Entity(tableName = "student")
data class Student(
val name: String,
val age: Int,
val gpa: Double,
val isSingle: Boolean
) {
#PrimaryKey(autoGenerate = true)
var id: Long? = null
}
How can I avoid making up the id
Just set default value to 0 (or null)
#Entity(tableName = "student")
data class Student(
#PrimaryKey(autoGenerate = true)
val id: Long = 0, <-- default value (or use null)
id is auto-generated by the Room database - means no matther what I put for the id into the constructor it will be overridden anyway
Not really like that. If you set id explicitly then this id will be used on insert.
If you want the auto increment id for your case just in your entity set the type of id to Int and for the id value parameter in your constructor use the null value and this handle the work for you .
Related
I have an entity class (PipeLine) that has a Mutable list of a parcelabel class (DamagePoint )
the main class PipeLine has a field val id:Int=0, primary key set to autoGerat=true it's working fine
the subclass DamagePoint also has a primary key val no:Int=1, I use it for points sequence it's not working!!
all the points generated has the no=0
Just to clarify, each PipeLine has a list of DamagePoints, sequenced by numbers
how would I do that !!
the class PipeLine.kt
#Parcelize
#Entity(tableName = "table_Lines")
data class PipeLine(
#PrimaryKey(autoGenerate = true)
val id:Int=0,
var name: String?,
var ogm: String?,
var length: String?,
var type: String?,
var i_start: String?,
var i_end: String?,
var start_point: String?,
var end_point: String?,
var work_date:String?,
var points: MutableList<DamagePoint>
):Parcelable
#Parcelize
data class DamagePoint(
#PrimaryKey(autoGenerate = true)
val no:Int=1,
var db: String? = null,
var depth: String? = null,
var current1: String? = null,
var current2: String? = null,
var gps_x: String? = null,
var gps_y: String? = null
):Parcelable
class DataConverter {
#TypeConverter
fun fromPoints(points: MutableList<DamagePoint>?): String? {
if (points == null) {
return null
}
val gson = Gson()
val type = object : TypeToken<MutableList<DamagePoint>?>() {}.type
return gson.toJson(points, type)
}
#TypeConverter
fun toPoints(points: String?): MutableList<DamagePoint>? {
if (points == null) {
return null
}
val gson = Gson()
val type = object : TypeToken<MutableList<DamagePoint>?>() {}.type
return gson.fromJson<MutableList<DamagePoint>>(points, type)
}
}
output :
id for PipeLine working and increasing
no for the DamagePoint not working and didn't increase
another output from emulator showing the point list int recyclerView
is the primaryKey don't work in the type converter
or there is another way to do it and make it increase?
please anything would help
I annotate it with #PrimaryKey and set autoGenerate to true
tried to switch between val and var , same thing
The problem is with the 1 integer which you set for the no. In this way, it does not work as auto-increment since there is a specified integer value (1) instead of 0.
So it means that if you set no to 0, it will work as unset and will increase the values.
val no:Int = 0
Also, you may need to change the no name to id.
the problem is I was trying to add a full list of DamagePoint every time I wanted to add a point to the points list in the PipeLine entity so I fetch the list from the object add new points to it and update the entire list for that pipeline object so it's normal to find all the point (no) equal to 1 because it's the default value for it
now I handle the no of each point I add to the list before updating it and adding it back to the pipeline object
I found a way to update only the points list in the pipeline object without updating the entire object
#Query("UPDATE table_Lines SET points=:points WHERE id=:id ")
fun updatePointsList(id:Int,points:MutableList)
if someone knows how to add an item to a mutable list which is a property of the entity class
without updating the list or updating the entity object
please add an answer
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.
I'm learning Android Jetpack, the following code is from a sample project at https://github.com/android/sunflower.
The GardenPlanting.kt code is to design a table, I'm very strange why the author define the table fields in two position, you see that #PrimaryKey(autoGenerate = true) #ColumnInfo(name = "id") is located the inner of the class.
I think that Code B is easy to understand, right?
GardenPlanting.kt
#Entity(
tableName = "garden_plantings",
foreignKeys = [
ForeignKey(entity = Plant::class, parentColumns = ["id"], childColumns = ["plant_id"])
],
indices = [Index("plant_id")]
)
data class GardenPlanting(
#ColumnInfo(name = "plant_id") val plantId: String,
#ColumnInfo(name = "plant_date") val plantDate: Calendar = Calendar.getInstance(),
#ColumnInfo(name = "last_watering_date")
val lastWateringDate: Calendar = Calendar.getInstance()
) {
#PrimaryKey(autoGenerate = true)
#ColumnInfo(name = "id")
var gardenPlantingId: Long = 0
}
Code B
data class GardenPlanting(
#PrimaryKey(autoGenerate = true) #ColumnInfo(name = "id") val id: String,
#ColumnInfo(name = "plant_id") val plantId: String,
#ColumnInfo(name = "plant_date") val plantDate: Calendar = Calendar.getInstance(),
#ColumnInfo(name = "last_watering_date")
val lastWateringDate: Calendar = Calendar.getInstance()
) {
var gardenPlantingId: Long = 0
}
The declaration of a property inside the data class constructor is used to :
Generate component function for destructing
Use these fields inside the toString(), equals(), hashCode(), and copy()
So if you want to avoid copying fields with the copy method then the easy way is to declare the fields inside the body of the class.
Example:
fun main() {
val user = User("Pavneet", "29k+")
user.id = kotlin.random.Random.nextInt(10, 20)
val userCopy = user.copy()
println(userCopy) // id won't be printed 'cuz it's not a part of toString method
userCopy.id = 99
print(userCopy.equals(user)) // true, 'cuz id is not being used by the equals method
//var(name, repo, id) = userCopy // error, User must have a 'component3()' function
var(name, repo) = userCopy
}
data class User(val name: String = "", val repo:String="0"){
var id:Int = 0
}
Advantages:
Create a copies of the object excluding specific fields
Exclude specific fields to compare two objects with equals
Exclude specific fields in destructuring declarations
Note: copy and component methods cannot be provided explicitly(inside data class). In example B, gardenPlantingId is replaced with id so can be removed.
The answer is very simple:
Because in this sample code, the author wants to show that we can use both #PrimaryKey and #ColumnInfo annotation to ANY member inside an entity class, regardless its position (it can be in the inside the constructor or at the outside).
To experiment with this, you can just do exactly what you did on Code B. It is also valid, but in that case, gardenPlantingId will not have a custom column name because we don't use #ColumnInfo annotation. Also, it is not necessary to declare #PrimaryKey outside the constructor (like the given example). You can just declare your the primary key annotation inside the constructor.
I am trying to create a room database and I want each item inserted into it to have its own unique id without me having to provide it, The problem is when I try to insert new items into the database I get an error asking me to provide an id.
Here is my entity:
#Entity(tableName = "notes_table")
data class Note(
#PrimaryKey(autoGenerate = true)
val id: Int = 0,
#ColumnInfo(name = "description")
val description: String,
#ColumnInfo(name = "priority")
var priority: Int)
Is there a way to have the database create its own auto-generated auto-increasing id column without having me having to add it like this:
val item = Note(id, item, priority)
insert(item)
And instead do this:
val item = Note(item, priority)
insert(item)
Create a constructor that takes item and priority as arguments
#Entity(tableName = "notes_table")
data class Note (var item: String,
#ColumnInfo(name = "priority")
var priority: String) {
#PrimaryKey(autoGenerate = true)
var id: Long = 0,
//.....
}
You can just simply give the id a default value and put that at the end:
#Entity(tableName = "notes_table")
data class Note(
#ColumnInfo(name = "description")
val description: String,
#ColumnInfo(name = "priority")
var priority: Int)
#PrimaryKey(autoGenerate = true) //must be at the end
val id: Int = 0 //Long type recommend
)
Then you can:
val item = Note(item, priority)
insert(item)
Because your data class Note has three parameter.
So you you have to create Note by passing three parameter.
It is nothing to do with autogenerate or room.
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.