I have added a data class and try to save it into Room. I went through stackoverflow and didn't find an answer.
So, the error is:
Caused by: com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected BEGIN_OBJECT but was BEGIN_ARRAY at line 1 column 2 path $
Caused by: java.lang.IllegalStateException: Expected BEGIN_OBJECT but was BEGIN_ARRAY at line 1 column 2 path $
I am using Room 2.4.2 so enum is supposed to be supported.
The model I am using is :
@Entity(tableName = "userpreferencestable")
class UserPreferencesEntity (
@PrimaryKey()
var vin: String,
@ColumnInfo(name = "control")
var command: List<CommandTile?>
)
and CommandTile
is defined as below:
data class CommandTile(
@SerializedName("name")
var name: DashboardTile.Name,
@SerializedName("state")
var state: DashboardTile.State
)
State
and Name
are enum and defined as below:
enum class Name {
NAME1,
NAME2...
}
enum class State {
TAT,
TOT
}
I have tried to add a DataConverter but it's not working.
@TypeConverter
fun fromName(name: Name): String {
return name.name
}
@TypeConverter
fun toName(name: String): Name {
return Name.valueOf(name)
}
@TypeConverter
fun fromState(state: State): String {
return state.name
}
@TypeConverter
fun toState(state: String):State {
return State.valueOf(state)
}
It still not working. I cannot figure out how to save the List of data class with enum.
Any idea ?
CodePudding user response:
To convert enum value you have to do something like this
@TypeConverter
fun fromName(name: Name): String {
return name.name
}
@TypeConverter
fun toName(name: String): Name {
return enumValueOf<Name>(name)
}
CodePudding user response:
You issue is not the Enums, rather it is with the command List<CommandTile>
(according to the disclosed code).
TypeConverters are for converting from/to a column for the data to be stored/retrieved.
As you have no @Entity
annotation for the CommandTile class BUT instead have List<CommandTile?>
as a column in the UserPrefrences class, which does have @Entity
annotation, then Room will want to convert the List of CommandTiles to an acceptable type (in SQLite along with Room's restrictions this would have to be a type that resolves to one of TEXT (String
), INTEGER (Int
, Long
...), REAL (Double
, Float
....) or BLOB (ByteArray
).
- types in parenthesise are Kotlin Types, they are examples and are not fully comprehensive.
As an example, overcoming issue that you may encounter using List, consider the following:-
A new class CommandTileList
data class CommandTileList(
val commandTileList: List<CommandTile>
)
- to avoid a column that is a List
A modified UserPreferencesEntity class to use a CommandTileList rather than List<CommandTile>
@Entity(tableName = "userpreferencestable")
class UserPreferencesEntity (
@PrimaryKey()
var vin: String,
@ColumnInfo(name = "control")
var command: CommandTileList
)
the TypeConverters class, with appropriate TypeConverters
class TypeConverters {
@TypeConverter
fun fromCommandTileToString(commandTileList: CommandTileList): String {
return Gson().toJson(commandTileList)
}
@TypeConverter
fun fromStringToCommandTile(string: String): CommandTileList {
return Gson().fromJson(string,CommandTileList::class.java)
}
}
A suitable @Dao annotated class AllDAO (for demo)
@Dao
interface AllDAO {
@Insert(onConflict = OnConflictStrategy.IGNORE)
fun insert(userPreferencesEntity: UserPreferencesEntity): Long
@Query("SELECT * FROM userpreferencestable")
fun getAllUserPreferences(): List<UserPreferencesEntity>
}
A suitable @Database annotated class TheDatabase (for demo) noting the TypeConverters class being defined with full scope via the @TypeConverters annotation (not the plural rather than singular form)
@TypeConverters(value = [TypeConverters::class])
@Database(entities = [UserPreferencesEntity::class], version = 1, exportSchema = false)
abstract class TheDatabase: RoomDatabase() {
abstract fun getAllDAO(): AllDAO
companion object {
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
}
}
}
- .allowMainThreadQueries for convenience/brevity
Finally putting it all into action witin an Activty MainActivity
class MainActivity : AppCompatActivity() {
lateinit var db: TheDatabase
lateinit var dao: AllDAO
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val c1 = CommandTile(DashboardTile.Name.NAME1,DashboardTile.State.TAT)
val c2 = CommandTile(DashboardTile.Name.NAME2,DashboardTile.State.TOT)
db = TheDatabase.getInstance(this)
dao = db.getAllDAO()
dao.insert(userPreferencesEntity = UserPreferencesEntity("VIN1", CommandTileList(listOf(
c1,c2))))
for(u in dao.getAllUserPreferences()) {
Log.d("DBINFO","VIV = ${u.vin} CommandTiles in Command = ${u.command.commandTileList.size} They Are:-")
for (ct in u.command.commandTileList) {
Log.d("DBINFO","\tName = ${ct.name} State = ${ct.state}")
}
}
}
}
Result
The log includes:-
D/DBINFO: VIV = VIN1 CommandTiles in Command = 2 They Are:-
D/DBINFO: Name = NAME1 State = TAT
D/DBINFO: Name = NAME2 State = TOT
The Database, via App Inspection :-
As you can see the list of 2 CommandTiles (a CommandTileList) has been converted to a String (SQLite type TEXT) and stored and subsequently retrieved.
Note from a database perspective this is not ideal it
limits/complicates the usefulness of the stored data.
- For example (simple) if you wanted to select all States that start with A then
SELECT * FROM userpreferencestable WHERE command LIKE 'A%'
would find all columns you would have to use some likeSELECT * FROM userpreferencestable WHERE 'state":A%'
.
- For example (simple) if you wanted to select all States that start with A then
introduces bloat with all that extra data and thus introduces inefficiencies.
breaks normalisation as the same values are stored multiple times
The database way would be to have a table based upon the CommandTile incorporating the suitable relationship between CommandTiles and the UserPreferences.
- Then there would be no need for the TypeConverter.