Hello everyone!
I am developing an API based mobile application and I want to save data from this API to room db. I wrote everything and got it done, but when I run the application it gives me an error like this:
error: I can't figure out how to save this field in the database. You might consider adding some kind of converter for this. custom final java.util.List<java.lang.Integer> type_ids = null;
The data class is as above. There is a problem with the type_ids parameter here. When I check the API, there is only integer data in this parameter and it comes as a list. When I delete this type_ids parameter, the application opens but my data is not loaded. When there is a parameter, it gives me the above error. By the way it gives the same error when exporting ColumInfo. I was able to conclude that the problem is in this parameter. When I researched the error a bit, I saw that it could be solved by doing something like TypeConverter, but I couldn't find how to write a converter for it. How should I proceed sir, can you help me? Fixed bitmap etc in the sources I researched. there is. They wrote converters on it.
CodePudding user response:
Here's an example of a TypeConverter and an example of it being used.
For brevity and convenience I used a cut down version of the Result class as per:-
@Entity(tableName = "result")
data class Result(
val adult: Boolean,
val backdrop_path: String,
val genre_ids: List<Int>,
@PrimaryKey
val id: Int?=null
/* .... */
)
The screenshot you showed, did not indicate a Primary so the id column has been made the Primary Key (in Room ALL tables must have a primary key FTS being the only exception).
Now the need for TypeConverters is because there are only a few types of data that can be stored in an SQLite database (which actually covers any data, but there is no concept of objects, just some data in a column). The Types are TEXT (typically String), INTEGER (Int, Long, Byte ....) REAL (Double, Float ....), BLOB (a byte stream such as ByteArray).
You need a pair of TypeConverters per a type that needs to be converted (i.e. ANY object that is not automatically handle by Room). One is to convert from the type to a type that can be stored, so the data can be stored. The other is used to convert the data stored into the data back into the type used for the column.
In this example the TypeConveter will convert the List using the List's .toString method which is a comma-separated list of the numbers with a leading [
and a trailing ']'.
Room requires you to define the TypeConverters in a class as functions and for each to be annotated with @TypeConverter
so:-
class TheTypeConverters {
@TypeConverter
fun fromListIntToString(intList: List<Int>): String = intList.toString()
@TypeConverter
fun toListIntFromString(stringList: String): List<Int> {
val result = ArrayList<Int>()
val split =stringList.replace("[","").replace("]","").replace(" ","").split(",")
for (n in split) {
try {
result.add(n.toInt())
} catch (e: Exception) {
}
}
return result
}
}
The class(es) that have the TypeConverters have to be defined to Room using the @TypeConverters
annotation (NOTE PLURAL AS OPPOSED TO SINGULAR).
Where you code the @TypeConverters determines the scope (where they can be used). The largest (most comprehensive scope is at the @Database level). As this answer includes as working example (albeit based upon only part of your screen image) then here's the @Database
annotated class:-
@TypeConverters(TheTypeConverters::class)
@Database(entities = [Result::class], exportSchema = false, version = 1)
abstract class AppDatabase: RoomDatabase() {
abstract fun getresultDao(): ResultDao
companion object {
private var instance: AppDatabase?=null
fun getInstance(context: Context): AppDatabase {
if (instance==null) {
instance= Room.databaseBuilder(context,AppDatabase::class.java,"app_database.db")
.allowMainThreadQueries()
.build()
}
return instance as AppDatabase
}
}
}
As the demo has to do something then an @Dao annotated interface (or abstract class) is required, so ResultDao:-
@Dao
interface ResultDao {
@Insert(onConflict = OnConflictStrategy.IGNORE)
fun insert(result: Result): Long
@Query("SELECT * FROM result")
fun getAllResultRows(): List<Result>
}
- just the 2 functions to allow a Result to be inserted into the database and for all rows to be extracted as a List of Result objects.
Finally to actually demonstrate, some code in an activity. (Note for brevity and convenience the main thread is used, it is recommended to not use the main thread in an App, discussion of running off the main thread is something that would need another question if needed):-
class MainActivity : AppCompatActivity() {
lateinit var appdb: AppDatabase
lateinit var appDao: ResultDao
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
appdb = AppDatabase.getInstance(this)
appDao = appdb.getresultDao()
appDao.insert(Result(true,"the_path", listOf(1,2,3,10,99,50000,100000,999999999)))
val strgbld = StringBuilder()
for (r in appDao.getAllResultRows()) {
strgbld.clear()
for (n in r.genre_ids) {
strgbld.append("\n\tGenre_ID is ${n}")
}
Log.d("RESULTINFO","IsAdult = ${r.adult} it has ${r.genre_ids.size} genre_ids. They are:-$strgbld")
}
}
}
This:-
- gets an instance of the AppDatabase
- gets an instance of the ResultDao from the AppDatabase instance
- inserts 1 row (1 Result object) that has a list of 8
Int
's in the genre_ids column.- Room uses the Type converter
fromListIntToString
to convert List into the string[1, 2, 3, 10, 99, 50000, 100000, 999999999]
i.e. uses the .toString of the List method.
- Room uses the Type converter
- extracts all the rows in the result table (just the one exists).
- Room uses the TypeConverter
toListIntFromString
to build the List from the extracted String.
- Room uses the TypeConverter
- for each row (again only the one) :-
- the genre_ids, now a List is traversed and the value extracted and appended to the StringBuilder along with a newline and tab and a description of the value. For each row the StringBuilder is cleared.
- Output is written to the log detailing some of the values of the Result with the number of genre_ids followed by each gener_id on a new line and tabbed in by 1 tab stop.
Result
D/RESULTINFO: IsAdult = true it has 8 genre_ids. They are:-
Genre_ID is 1
Genre_ID is 2
Genre_ID is 3
Genre_ID is 10
Genre_ID is 99
Genre_ID is 50000
Genre_ID is 100000
Genre_ID is 999999999