Omid
Omid

Reputation: 25

problem with creating type converter for room database

hey guys I'm trying to make a database with room and I have a data class as Entity Like this:

@Entity(tableName = "forecast")
data class WeatherForecastEntity(
    @PrimaryKey(autoGenerate = false)
    val id:Int?,
    val city:String?,
    val country:String?,
    val timeZone:Int?,
    val sunrise: Int?,
    val sunset: Int?,
    val detailList:List<Detail>?
)

as you can see I have a value called detailList that is a List of a data class called Detail that is like this:

data class Detail(
    val clouds: Clouds?,
    val dt: Int?,
    val dt_txt: String?,
    val main: Main?,
    val pop: Int?,
    val sys: Sys?,
    val visibility: Int?,
    val weather: List<Weather>?,
    val wind: Wind?
)

and in this class I have Instance of some other data classes like for example weather value that is a list of Weather data class that is like this:

data class Weather(
    val description: String?,
    val icon: String?,
    val id: Int?,
    val main: String?
)

or main that is instance of another data class called Main that is like this:

data class Main(
    val feels_like: Double?,
    val grnd_level: Int?,
    val humidity: Int?,
    val pressure: Int?,
    val sea_level: Int?,
    val temp: Double?
)

and when I run my app I recieve an error that says I must create a type converter and I really have no idea how should I do that with these many data classes that have instance of each other. I'll be really appreciate it if you can help me with it. by the way I'm using RxJava and Gson in my app.

Upvotes: 2

Views: 1302

Answers (1)

MikeT
MikeT

Reputation: 56938

First. You cannot (I believe) have a val detailList:List<Detail>?, you need a single (not a List) item.

So (1) add a new Class that embodies the List e.g. :-

data class DetailList(
    val detailList: List<Detail>
)

and (2) in WeatherForecastEntity use :-

/* val detailList:List<Detail>? */
val detailList: DetailList?
  • the original line has been commented out

So now you have a single item/variable/field that needs to be converted by a TypeConverter.

then (3) create a class such as (see note re placement/scope) :-

class DetailListTypeConverter {

    @TypeConverter
    fun toDetailList(value: String): DetailList {
        Log.d("DBINFO_FROMJSON","Extracted>>${value}") /* just for demonstration */
        return Gson().fromJson(value,DetailList::class.java)
    }
    @TypeConverter
    fun fromDetailList(value: DetailList): String {
        return  Gson().toJson(value)
    }
}
  • The fromDetailList function converts a DetailList (a List<Detail>) to a String (a json string of the DetailList object, which embodies all the underlying objects such as Clouds, Sys, Main within the string). This is used when inserting into the database.
  • The toDetailList function does the reverse and builds the DetailList object (and the underlying objects) from the stored String. This is used when extracting the data from the database.
  • In both cases Room knows to use the respective TypeConverter (Room knows due to the Types used by the functions (i.e the Type passed and the Type returned)). You would always have a pair of functions (or many pairs within the class for different TypeConverters)

Then (4) add the following to the @Database class after the @Database annotation :-

@TypeConverters(DetailListTypeConverter::class)

Demo

For Example (as used to demo) :-

@Database(entities = [WeatherForecastEntity::class],version = 1)
@TypeConverters(DetailListTypeConverter::class) /*<<<<<<<<<< ADDED >>>>>>>>>>*/
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,
                    "weather.db"
                )
                    .allowMainThreadQueries()
                    .build()
            }
            return instance as TheDatabase
        }
    }
}
  • i.e. the @TypeConverters annotation has been added
  • Note that for convenience/brevity .allowMainThreadQueries() has been used, so the demo runs on the main thread (not recommended for an App that will be distributed)

Now using your classes (Classes Sys, Clouds and Wind were created as they aren't included in the question, so they will very likely NOT reflect your code) and the following @Dao class AllDao (to allow insertion and extraction in the demo):-

@Dao
abstract class AllDao {
    @Insert(onConflict = OnConflictStrategy.IGNORE)
    abstract fun insert(weatherForecastEntity: WeatherForecastEntity): Long
    @Query("SELECT * FROM forecast")
    abstract fun getAllForecasts(): List<WeatherForecastEntity>
}

Then using the following code in an activity to demonstrate that the above works :-

class MainActivity : AppCompatActivity() {
    lateinit var db: TheDatabase
    lateinit var dao: AllDao
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        db = TheDatabase.getInstance(this)
        dao = db.getAllDao()
        dao.insert(createSomeData())
        for(wf: WeatherForecastEntity in dao.getAllForecasts()) {
            Log.d("DBINFO","City is ${wf.city}")
            for (dl: Detail in wf.detailList!!.detailList) {
                Log.d("DBINFO_DETAIL","Detail is ${dl.dt} Clouds is ${dl.clouds!!.name}")
            }
        }
    }

    fun createSomeData(): WeatherForecastEntity {
        val dtlList: ArrayList<Detail> = arrayListOf()
        val dtl1 = Detail(
            clouds = Clouds(name = "Cirrus", value = 10.2),
            dt = 10,
            dt_txt = "The dt for dt1",
            main = Main(feels_like = 1.1,grnd_level = 15, humidity = 65,sea_level = 100,pressure = 14,temp = 30.24),pop = 23000,visibility = 300,
            weather = listOf(Weather(description = "wet","weticon",0,"wet")),
            sys = Sys("sysname",10.333),
            wind = Wind(22.5,10.23)
        )
        val dtl2 = dtl1
        dtlList.add(dtl1)
        dtlList.add(dtl2)
        val detailList = DetailList(dtlList)
        return WeatherForecastEntity(null,"London","England",0,100,100,detailList)
    }
}
  • The above code will insert a new row each time it is run and then extract all the rows outputting some of the extracted data.

So after 3 runs the log includes :-

2021-09-18 08:19:33.825 D/DBINFO_FROMJSON: Extracted>>{"detailList":[{"clouds":{"name":"Cirrus","value":10.2},"dt":10,"dt_txt":"The dt for dt1","main":{"feels_like":1.1,"grnd_level":15,"humidity":65,"pressure":14,"sea_level":100,"temp":30.24},"pop":23000,"sys":{"name":"sysname","value":10.333},"visibility":300,"weather":[{"description":"wet","icon":"weticon","id":0,"main":"wet"}],"wind":{"direction":22.5,"speed":10.23}},{"clouds":{"name":"Cirrus","value":10.2},"dt":10,"dt_txt":"The dt for dt1","main":{"feels_like":1.1,"grnd_level":15,"humidity":65,"pressure":14,"sea_level":100,"temp":30.24},"pop":23000,"sys":{"name":"sysname","value":10.333},"visibility":300,"weather":[{"description":"wet","icon":"weticon","id":0,"main":"wet"}],"wind":{"direction":22.5,"speed":10.23}}]}
2021-09-18 08:19:33.858 I/chatty: uid=10200(a.a.so69210605kotlinroomgsontypeconverter) identical 4 lines
2021-09-18 08:19:33.864 D/DBINFO_FROMJSON: Extracted>>{"detailList":[{"clouds":{"name":"Cirrus","value":10.2},"dt":10,"dt_txt":"The dt for dt1","main":{"feels_like":1.1,"grnd_level":15,"humidity":65,"pressure":14,"sea_level":100,"temp":30.24},"pop":23000,"sys":{"name":"sysname","value":10.333},"visibility":300,"weather":[{"description":"wet","icon":"weticon","id":0,"main":"wet"}],"wind":{"direction":22.5,"speed":10.23}},{"clouds":{"name":"Cirrus","value":10.2},"dt":10,"dt_txt":"The dt for dt1","main":{"feels_like":1.1,"grnd_level":15,"humidity":65,"pressure":14,"sea_level":100,"temp":30.24},"pop":23000,"sys":{"name":"sysname","value":10.333},"visibility":300,"weather":[{"description":"wet","icon":"weticon","id":0,"main":"wet"}],"wind":{"direction":22.5,"speed":10.23}}]}
2021-09-18 08:19:33.871 D/DBINFO: City is London
2021-09-18 08:19:33.871 D/DBINFO_DETAIL: Detail is 10 Clouds is Cirrus
2021-09-18 08:19:33.871 D/DBINFO_DETAIL: Detail is 10 Clouds is Cirrus
2021-09-18 08:19:33.871 D/DBINFO: City is London
2021-09-18 08:19:33.871 D/DBINFO_DETAIL: Detail is 10 Clouds is Cirrus
2021-09-18 08:19:33.871 D/DBINFO_DETAIL: Detail is 10 Clouds is Cirrus
2021-09-18 08:19:33.872 D/DBINFO: City is London
2021-09-18 08:19:33.872 D/DBINFO_DETAIL: Detail is 10 Clouds is Cirrus
2021-09-18 08:19:33.872 D/DBINFO_DETAIL: Detail is 10 Clouds is Cirrus
  • the first 3 lines being from the TypeConverter showing that the json has been extracted.
  • The subsequent lines showing that the data has been successfully extracted and that the detailList includes the detail (2 per row) (albeit identical data).

The database itself, as per Android Studio's App Inspector :-

enter image description here

Upvotes: 4

Related Questions