Home > front end >  Exception while computing database live data with Enum
Exception while computing database live data with Enum

Time:03-31

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 :-

enter image description here

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 like SELECT * FROM userpreferencestable WHERE 'state":A%'.
  • 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.
  • Related