I am implementing TypeConverters for date?
This seems wrong as java.util.date
is deprecated. Kotlinx.datetime.LocalDate
is supposed to be serializable but is also not working.
Task: add expiration and start dates to an Entity in Room that will come from the date picker so I imagine will return a Calendar
which I also tried to no avail.
I am getting this error
Bottle.java:23: error: Cannot figure out how to save this field into database. You can consider adding a type converter for it.
private final java.util.Date expirationDate = null;
On this generated portion
21 @org.jetbrains.annotations.Nullable
22 @androidx.room.ColumnInfo
23 private final java.util.Date expirationDate = null;
24 @org.jetbrains.annotations.Nullable
25 @androidx.room.ColumnInfo
26 private java.util.Date startDate;
Bottle class:
@Entity(tableName = "bottles")
@Serializable
data class Bottle(
@PrimaryKey (autoGenerate = true)
@ColumnInfo val bottleID : Int?,
@ColumnInfo val consumableID: String,
@ColumnInfo @Contextual val expirationDate : Date?,
@ColumnInfo @Contextual var startDate : Date?,
@ColumnInfo val cabinetID: String
)
DataBase
@Database(entities = [
Cabinet::class,
Bottle::class,
Consumable::class,
], version = 1)
@TypeConverters(Converters::class)
abstract class PillMinderDatabase : RoomDatabase() {
abstract val cabinetDAO: CabinetDAO
abstract val bottleDAO: BottleDAO
abstract val consumableDAO: ConsumableDAO
}
Converters
class Converters {
companion object {
//**************************** CALENDAR CONVERTERS ****************************//
@TypeConverter
@JvmStatic
fun dateToLong(date: Date?): Long? = date?.time
@TypeConverter
@JvmStatic
fun longToDate(dateInMillis: Long?): Date? = dateInMillis?.let { Date(dateInMillis)}
}
}
[1]: https://i.stack.imgur.com/PsO5F.png
CodePudding user response:
Your issue is that you are using a companion object and Room doesn't expect to utilise static methods when it builds the underlying code.
To resolve this issue, use :-
class Converters {
@TypeConverter
fun dateToLong(date: Date?): Long? = date?.time
@TypeConverter
fun longToDate(dateInMillis: Long?): Date? = dateInMillis?.let { Date(dateInMillis)}
}
However, if saving the date as a Long, then there is no need for TypeConverters you just pass and extract as a Long.
add expiration and start dates to an Entity in Room that will come from the date picker so I imagine will return a Calendar which I also tried to no avail.
You can use the DatePicker's year, month and dayOfMonth to get the values.
Working Example
Here's a working example that has your Bottle class and also an AltBottle class (with just Long's, so no TypeConverters) that has a DatePicker and a Button to insert both a Bottle and an AltBottle. It also includes a ListView that displays the startDate and the expirationDate.
The following are the components that the example is comprised of:-
The Bottle and AltBottle classes:-
@Entity(tableName = "bottles")
@Serializable
data class Bottle(
@PrimaryKey(autoGenerate = true)
@ColumnInfo val bottleID : Int?,
@ColumnInfo val consumableID: String,
@ColumnInfo @Contextual val expirationDate : Date?,
@ColumnInfo @Contextual var startDate : Date?,
@ColumnInfo val cabinetID: String
)
@Entity
data class AltBottle(
/* No need for @Serializable or for @ColumnInfo's without any parameters */
@PrimaryKey
var altBottleId: Long? = null, /* it is more correct to use a Long rather than Int for an id, it is 64bit signed and can outgrow an Int */
var altComsunableId: String,
var altExpirationDate: Long?,
var altStartDate: Long?,
var cabinetId: String
)
The Converters Class (not needed for the AltBottle):-
class Converters {
@TypeConverter
fun dateToLong(date: Date?): Long? = date?.time
@TypeConverter
fun longToDate(dateInMillis: Long?): Date? = dateInMillis?.let { Date(dateInMillis)}
}
An @Dao
annotated interface AllDao :-
@Dao
interface AllDao {
@Insert
fun insert(bottle: Bottle): Long
@Insert
fun insert(altBottle: AltBottle): Long
@Query("SELECT * FROM bottles")
fun getAllBottles(): List<Bottle>
@Query("SELECT * FROM altBottle")
fun getAllAltBottles(): List<AltBottle>
}
- Note that the 2 query functions aren't actually used as for simplicity the data is extracted as a Cursor for driving the ListView (see MainActivity)
An @Database
annotated class TheDatabase (note .allowMainThreadQueries used for convenience and brevity):-
@Database(entities = [Bottle::class,AltBottle::class], version = 1, exportSchema = false)
@TypeConverters(value = [Converters::class])
abstract class TheDatabase: RoomDatabase() {
abstract fun getAllDao(): AllDao
companion object {
private var instance: TheDatabase? = null
fun getInstance(context: Context): TheDatabase {
if (instance==null) {
instance = Room.databaseBuilder(context,TheDatabase::class.java,"the_database.db")
.allowMainThreadQueries()
.build()
}
return instance as TheDatabase
}
}
}
A Layout for MainActivity that includes a DatePicker, a Button and a ListView for the results :-
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!">
</TextView>
<DatePicker
android:id="@ id/datepicker"
android:datePickerMode="calendar"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
</DatePicker>
<Button
android:id="@ id/insert"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="INSERT"
>
</Button>
<ListView
android:id="@ id/listview"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
</ListView>
</LinearLayout>
Finally MainActivity :-
class MainActivity : AppCompatActivity() {
lateinit var db: TheDatabase
lateinit var dao: AllDao
lateinit var lv: ListView
lateinit var btn: Button
var sca: SimpleCursorAdapter? = null
var csr: Cursor? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val dp = this.findViewById<DatePicker>(R.id.datepicker)
lv = this.findViewById(R.id.listview)
btn = this.findViewById(R.id.insert)
/* Prepare DB access */
db = TheDatabase.getInstance(this)
dao = db.getAllDao()
/* Prepare the Button for inserting rows */
val button = this.findViewById<Button>(R.id.insert)
button.setOnClickListener { insertData(dp) }
/* setup the ListView */
setOrRefreshListView()
}
/* Insert the data (1 row for bottles table and an identical row the the altBottle table)
* Note that this also refreshes the ListView
* */
fun insertData(datePicker: DatePicker) {
var cal: Calendar = Calendar.getInstance()
cal.set(datePicker.year,datePicker.month,datePicker.dayOfMonth)
dao.insert(Bottle(null,"Blah",cal.time,cal.time,"blah"))
dao.insert(AltBottle(null,"AltBlah",cal.timeInMillis,cal.timeInMillis,"AltBlah"))
setOrRefreshListView()
}
/* Setup or Refresh the ListView */
fun setOrRefreshListView() {
csr = db.getOpenHelper().writableDatabase.query("SELECT *,bottleId AS ${BaseColumns._ID} FROM bottles UNION All SELECT *,altBottleId AS ${BaseColumns._ID} FROM altbottle")
if (sca== null) {
sca = SimpleCursorAdapter(this,android.R.layout.simple_list_item_2, csr,arrayOf("expirationDate","startDate"), intArrayOf(android.R.id.text1,android.R.id.text2),0)
lv.adapter = sca
} else {
sca!!.swapCursor(csr)
}
}
}
- See the insertData function for how the data is retrieved from the DatePicker
Results
When installed/started for the first time then the App displays:-
If 1st June is selected and then the INSERT button is clicked :-
As can be see two rows have been added.
Select 21 June and click on INSERT and another 2 rows are added :-
- Note that the rows from the bottles table appear together and are then followed by the rows from the altBottle table.
and so on ....
Using App Inspection then the bottles table is :-
and the altBottle table is :-
CodePudding user response:
import java.util.Date
class Converters {
@TypeConverter
fun dateToLong(date: Date?): Long? = date?.time
@TypeConverter
fun longToDate(dateInMillis: Long?): Date? = dateInMillis?.let { Date(dateInMillis) }
}