Android room select distinct table.calender from table - android

The problem faced is to get calendar field from table.
My actual Data class is:
#TypeConverters(CalendarConverters::class)
#Entity(tableName = MY_TABLE)
data class MyEpg(
val updatedAt: Calendar,
val epgName: String,
val epgStartTime: Calendar,
val epgEndTime: Calendar,
#ColumnInfo(name = "calendar")
val calendar: Calendar,
val chName: String
) {
#ColumnInfo(name = "id")
#PrimaryKey(autoGenerate = true)
var id: Long = 0
}
I've used typeconverter as:
object CalendarConverters {
#TypeConverter
#JvmStatic
fun fromTimestamp(value: Long?): Calendar? = value?.let { value ->
GregorianCalendar().also { calendar ->
calendar.timeInMillis = value
}
}
#TypeConverter
#JvmStatic
fun toTimestamp(timestamp: Calendar?): Long? = timestamp?.timeInMillis
}
The query I used is:
#Query("select distinct ${MY_TABLE}.calendar from $MY_TABLE order by ${MY_TABLE}.calendar asc")
suspend fun getCalendars(): List<Calendar>
Error Shown in build view is
The query returns some columns [calendar] which are not used by
java.lang.Object. You can use #ColumnInfo annotation on the fields to
specify the mapping. You can suppress this warning by annotating the
method with #SuppressWarnings(RoomWarnings.CURSOR_MISMATCH). Columns
returned by the query: calendar. Fields in java.lang.Object: .
public abstract java.lang.Object getCalendars(#org.jetbrains.annotations.NotNull()
error: Not sure how to convert a Cursor to this method's return type
(java.lang.Object).
public abstract java.lang.Object getCalendars(#org.jetbrains.annotations.NotNull()
Note:
I've noticed that type converter to convert to long is being used while converting from long to date is not being used.
There is no any problem on getting any single fields or the complete object except for the single calendar item.

I reproduced your code but I'm not getting that error. However, I have some observations for you:
I noticed that as table name you use MY_TABLE but in the query you use My_TABLE with "lowercase y". Is this a typo?
You can avoid the use of #JvmStatic by replacing your object CalendarConverters to simple class. Object works with singleton pattern.
I'm supposing that the cause could be that you have invalid data inserted in the table MyEpg table, so then, when you do a request using your query, the column calendar contains a null value. That's could be the reason why you receive the next:
error: Not sure how to convert a Cursor to this method's return type (java.lang.Object). public abstract java.lang.Object getCalendars(#org.jetbrains.annotations.NotNull()
You could solve the problem by adding a nullable indicator to the return type:
#Query("select distinct ${MY_TABLE}.calendar from $MY_TABLE order by ${MY_TABLE}.calendar asc")
suspend fun getCalendars(): List<Calendar?>

Maybe you've forgotten to apply your TypeConverter (or have chosen wrong place to put the annotation)? There are two ways to do that in your case:
Apply it on Database-level:
#Database(entities = arrayOf(YourEntity::class), version = 1)
#TypeConverters(CalendarConverters::class)
abstract class AppDatabase : RoomDatabase() {
...................
Apply it on Entity level:
#Entity(tableName = "your_table")
#TypeConverters(CalendarConverters::class)
data class YourEntity (
and Dao level:
#TypeConverters(CalendarConverters::class)
suspend fun getCalendars(): List<Calendar>

Related

Kotlin Room SUM operation

I need to perform column sum operation
But I don't understand what exactly I need to insert into fun getSumItem(): Flow<Cursor>
this is my Dao
#Dao
interface Dao {
#Insert
fun InsertItem(item: Income)
#Query("SELECT SUM(sum) AS value FROM income")
fun getSumItem(): Flow<Cursor>
}
this is my table
#Entity(tableName = "income")
data class Income(
#PrimaryKey(autoGenerate = true)
var id: Int? = null,
#ColumnInfo(name = "date")
var date: String,
#ColumnInfo(name = "type")
var type: String,
#ColumnInfo(name = "sum")
var sum: Float,
)
I did not find a similar example and any detailed information on the Internet.
when I use Cursor i get this error:
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). - android.database.Cursor
all other options that I found on the Internet also gave errors
When you say SELECT SUM(sum) AS value FROM income the output that Room handles will be a Cursor with a single column named value. Room does not know how to extract this value and build a Cursor object from that single value.
Room expects to extract data from the underlying Cursor that is generated by the SQLite API, which Room is a wrapper to/around, and deliver the data according to the object that the function returns (and thus must be able to ascertain how to build the resultant object).
As Room extracts a single value then you simply need to retrieve the value returned as a Float.
fun getSumItem(): Flow<Float>

How to resolve "Entities and POJOs must have a usable public constructor"

I have seen this question several times on SO. however the solution doesn't seem to apply to my problem.
I have a Kotlin data-class that is used as an Entity in Room
#Entity(tableName = "training_session")
data class SessionEntity(
#PrimaryKey(autoGenerate = false) val id: Long,
#ColumnInfo(name = "current_state_marker") val currentState: Short,
#Embedded val states: List<Int>
)
It is producing
> Task :training-infrastructure:kaptDebugKotlin FAILED
error: 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.List
error: 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.List
In the same project I have a very similar entity which also has a list and that doesn't produce any errors.
Tried out the answer provided by MikeT, for me it required a small change in the way the converters were defined
data class SessionStateList (val stateList : List<Int>)
class SessionStateListConverter {
#TypeConverter
fun fromArraySessionStateList(sh: List<Int>?): String? {
return Gson().toJson(sh)
}
#TypeConverter
fun toArraySessionStateList(sh: String?): List<Int>? {
val listType: Type = object : TypeToken<ArrayList<Int?>?>() {}.type
return Gson().fromJson(sh,listType)
}
}
A quick follow-up. I had mentioned that I have another Entity that has an Embedded val something: List<Int> and I had not noticed any compiler errors.
The reason, I had not noticed any compiler errors was because the entity was not included in the #Database annotation.
You cannot have a List/Array etc as a column type. So your issue is centred on #Embedded val states: List<Int>
You could have a POJO e.g. StatesHolder :-
data class StatesHolder(
val stateList: List<Int>
)
and then have
#Entity(tableName = "training_session")
data class SessionEntity(
#PrimaryKey(autoGenerate = false) val id: Long,
#ColumnInfo(name = "current_state_marker") val currentState: Short,
val states: StatesHolder
)
Note that you cannot Embed StatesHolder as then that just inserts List. If you want to Embed then you have to Embed a wrapper that uses a StatesHolder.
You will then need TypeConverters to convert to and from a StatesHolder object to a type that can be stored. Probably a String and Probably a JSON respresentation of the StatesHold object e.g.
class Converters {
#TypeConverter
fun fromStatesHolder(sh: StatesHolder): String {
return Gson().toJson(sh)
}
#TypeConverter
fun toStatesHolder(sh: String): StatesHolder {
return Gson().fromJson(sh,StatesHolder::class.java)
}
}
You additionally need to use #TypeConverters annotation that defines the Converts::class. If coded at the #Database level the converters have full scope.
So after #Database(.....) you could have :-
#TypeConverters(Converters::class)

How to create a TypeConverter that converts LocalDate to format that Room can understand/save?

I am using Room. I need guidence on how to convert LocalDate from java.time to a format (may be Long, TimeStamp, sql.Date or I do not even know what else) so that I can save a date to database.
I have a book entity:
#Entity
data class Book(
#ColumnInfo(name = "date") val date: LocalDate,
#ColumnInfo(name = "count") val count: Int,
#PrimaryKey(autoGenerate = true)
val id: Int? = null
)
I have also created Book DAO:
#Dao
interface BookDao {
#Query("SELECT * FROM book WHERE :date >= book.date")
fun getBooks(date: LocalDate): List<Book>
}
Now, I am not sure how to create converter that converts LocalDate to . . . (again, I do not even know to what I should convert my LocalDate. Is it Long, TimeStamp, sql.Date or anything else).
I thought I should be converting LocalDate to sql.Date so that Room can save it. So, I created my converter like this:
Converters.kt
class Converters {
#TypeConverter
fun convertLocalDateToSqlDate(localDate: LocalDate?): Date? {
return localDate?.let { Date.valueOf(localDate.toString()) }
}
#TypeConverter
fun convertSqlDateToLocalDate(sqlDate: Date?): LocalDate? {
val defaultZoneId = systemDefault()
val instant = sqlDate?.toInstant()
return instant?.atZone(defaultZoneId)?.toLocalDate()
}
}
But, I am getting error:
error: Cannot figure out how to save this field into database. You can consider adding a type converter for it.
private final java.time.LocalDate date = null;
The issue you have is that a TypeConverter should convert from/to a set of specific types that Room supports for the insertion/extraction of Data.
Some of the more common ones are Int, Long Byte, Byte[], Float, Double, Decimal, String, BigInt, BigDecimal.
there may be other types but the types that can be used are limited
So the message is saying please convert your LocalDate or Date as it cannot handle them.
I'd suggest storing dates as unix dates (unless you want microsecond accuracy when you can store the time with milliseconds (this can be a little awkward when using date/time functions in sqlite and may need casts)) i.e. as a Long or Int.
In doing so you:
will minimise the amount of storage used to store the values,
will be able to take advantage of the SQLite Date and Time functions,
can easily extract them into very flexible formats,
you can extract them as is and then use class functions to format convert them,
you will not need TypeConverters for the above.
Here's an example showing some of the functionality, storing them as unix timestamps
First a version of your Book Entity utilising a default for the date :-
#Entity
data class Book(
#ColumnInfo(name = "date", defaultValue = "(strftime('%s','now','localtime'))")
val date: Long? = null,
#ColumnInfo(name = "count")
val count: Int,
#PrimaryKey(autoGenerate = true)
val id: Int? = null
)
see SQLITE Date and Time Functions for explanation of the above. In short the above allows you to insert a row and have the date and time set to the current date time
see the insertBook function for doing inserting with just the count provided.
To accompany the Book Entity the Book Dao :-
#Dao
interface BookDao {
#Insert /* insert via a book object */
fun insert(book: Book): Long
#Query("INSERT INTO BOOK (count) VALUES(:count)")
/* insert supplying just the count id and date will be generated */
fun insertBook(count: Long): Long
#Query("SELECT * FROM book")
/* get all the books as they are stored into a list of book objects*/
fun getAllFromBook(): List<Book>
/* get the dates as a list of dates in yyyy-mm-dd format using the SQLite date function */
#Query("SELECT date(date, 'unixepoch', 'localtime') AS mydateAsDate FROM book")
fun getDatesFromBook(): List<String>
/* get the date + 7 days */
#Query("SELECT date(date,'unixepoch','localtime','+7 days') FROM book")
fun getDatesFromBook1WeekLater(): List<String>
}
see the comments (and activity and results)
The activity (pretty standard BookDatabase class so not included)
class MainActivity : AppCompatActivity() {
lateinit var db: BookDatabase
lateinit var dao: BookDao
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
db = BookDatabase.getInstance(this)
dao = db.getBookDao()
/* Insert two rows */
dao.insert(Book(System.currentTimeMillis() / 1000,10)) /* don't want millisecs */
dao.insertBook(20) /* use the date columns default value (similar to the above) */
/* get and log the date objects */
for(b: Book in dao.getAllFromBook()) {
Log.d("BOOKINFO_BOOK", " Date = ${b.date} Count = ${b.count} ID = ${b.id}")
}
/* get the dates in yyy-mm-dd format */
for(s: String in dao.getDatesFromBook()) {
Log.d("BOOKINFO_DATE",s)
}
/* get the dates but with a week added on */
for(s: String in dao.getDatesFromBook1WeekLater()) {
Log.d("BOOKINFO_1WEEKLATER",s)
}
}
}
The Log after running :-
2021-04-16 14:28:50.225 D/BOOKINFO_BOOK: Date = 1618547330 Count = 10 ID = 1
2021-04-16 14:28:50.225 D/BOOKINFO_BOOK: Date = 1618583330 Count = 20 ID = 2
2021-04-16 14:28:50.226 D/BOOKINFO_DATE: 2021-04-16
2021-04-16 14:28:50.227 D/BOOKINFO_DATE: 2021-04-17
2021-04-16 14:28:50.228 D/BOOKINFO_1WEEKLATER: 2021-04-23
2021-04-16 14:28:50.228 D/BOOKINFO_1WEEKLATER: 2021-04-24
(note that the adjustment for local time (albeit it incorrectly used and thus jumping forward))
first 2 rows data as stored in the Book object and the database
next 2 rows the date converted to YYYY-MM-DD format by SQLite Date/Time function
last 2 rows the date a week after the stored date converted to YYYY-MM-DD
The Database as per Database Inspector :-
if you want to use converters, you can follow the documentation's way. In your example, you try to convert to SQLdate, but Android suggests to Long.
class Converters {
#TypeConverter
fun fromTimestamp(value: Long?): Date? {
return value?.let { Date(it) }
}
#TypeConverter
fun dateToTimestamp(date: Date?): Long? {
return date?.time?.toLong()
}
}
and then add the #TypeConverters annotation to the AppDatabase.
With LocalDate it should be something like this:
class Converters {
#TypeConverter
fun fromTimestamp(value: Long?): LocalDate? {
return value?.let { LocalDate.ofEpochDay(it) }
}
#TypeConverter
fun dateToTimestamp(date: LocalDate?): Long? {
val zoneId: ZoneId = ZoneId.systemDefault()
return date?.atStartOfDay(zoneId)?.toEpochSecond()
}
}
source

kotlin Not sure how to handle insert method's return type

i am developing an android application using kotlin and room library. when i try to get the id of the inserted item i am getting the following error
Not sure how to handle insert method's return type.
public abstract java.lang.Object insertReceipt(#org.jetbrains.annotations.NotNull()
i have tried other solutions like this but it is not working
here is my code
Receipt.kt
#Entity
data class Receipt(
#ColumnInfo() val receipt_timestamp: Timestamp,
#ColumnInfo() val receipt_items: Int,
#ColumnInfo() val receipt_total_price: BigDecimal
){
#PrimaryKey(autoGenerate = true)
var receiptNo: Int = 0
}
ReceiptDao.kt
#Insert()
suspend fun insertReceipt(receipt: Receipt): Int
ReceiptRepository.kt
suspend fun insertReceipt(receipt: Receipt): Int{
return receiptDAO.insertReceipt(receipt)
}
That is because you cannot get the id like this. You can get an id with this function but it will not be the one you want.
cf: https://developer.android.com/training/data-storage/room/accessing-data#convenience-insert the id returned is an internal id used by sqlite.
To achieve what you want, you need something like:
#Query("SELECT receiptNo FROM receipt WHERE receipt_timestamp = :receipt_timestamp AND receipt_items = : receipt_items AND receipt_total_price = :receipt_total_price LIMIT 1")
fun getId(receipt_timestamp: Timestamp, receipt_items: Int, receipt_total_price: BigDecimal): Int
A method annotated with the #Insert annotation can return:
long for single insert operation
long[] or Long[] or List for multiple insert operations
void if you don't care about the inserted id(s)
So you must change your return type of insertReceipt from Int to Long:
return receiptDAO.insertReceipt(receipt).toLong()

TypeConverters not working for Collections in #Query

I've an entity called Events which is defined as follows:
#Entity(tableName = "Events")
data class Event(#PrimaryKey val id: Long, val name: String, val venues: Set<String>, val rating: Int)
I've a pair of #TypeConverter methods for handling Set<String>:
#TypeConverter
fun fromStringToStringSet(str: String): Set<String> = str.split("<|>")
#TypeConverter
fun fromStringSetToString(set: Set<String>): String = set.joinToString("<|>")
In my Dao, I've a method annotated with #Query as follows:
#Query("UPDATE Events SET name = :name, venues = :venues WHERE id = :id")
fun updateAndRetainRating(id: Long, name: String, venues: Set<String>)
When I try to update an event with 2 venues, I get a runtime error telling me that the SQL couldn't be compiled. The generated SQL is:
UPDATE Events SET name = ?, venues = ?,? WHERE id = ?
This is obviously wrong. Looking into the generated code, Room is getting the size of Set<String> and adding the same number of ?s.
Why isn't my TypeConverter being used? I don't face this issue in other queries(#Insert and #Query(for SELECT)). Other TypeConverters are also working fine.
EDIT: The same issue also occurs if I use List<String> + TypeConverters instead of Set<String>.
Looking into the Room documentation, it appears that whenever we use a Collection in #Query, it bypasses TypeConverters and is straight away flattened.
For example, this:
#Query("SELECT * FROM Customers WHERE city IN (:cities)")
fun getCustomersInCities(cities: List<String>): Flowable<List<Customer>>
results in SELECT * FROM Customers WHERE city IN ('london', 'paris') if cities contains "london" and "paris".
Unfortunately, the following is also converted in a similar way:
#Query("UPDATE Customers SET phoneNumbers = :phoneNumbers WHERE id = :id")
fun updateCustomerPhoneNumbers(id: Long, phoneNumbers: List<String>)
The result is UPDATE Customers SET phoneNumbers = 1234567890, 9876543210 WHERE id = 23 if id is 23 and the phoneNumbers contains "1234567890" and "9876543210".
While this does make sense, it is really inconvenient and should be documented more clearly.
This solution worked for me:
TypeConverter not working when updating List<Boolean> in Room Database
Basically, replace List<String> by ArrayList<String>.

Categories

Resources