Search code examples
androidkotlindatabase-designandroid-sqliteandroid-room

How to get the list of record that is a part of another record in the network response, after successully designing the room database schema


What i am trying to achieve

i am developing a weather forcast application that is having room as a local cache database for the json response that i am getting from the openweathermap api is as follow

{
   "cod":"200",
   "message":0,
   "cnt":40,
   "list":[
      {
         "dt":1590602400,
         "main":{
            "temp":306.2,
            "feels_like":306.4,
            "temp_min":306.07,
            "temp_max":306.2,
            "pressure":1006,
            "sea_level":1004,
            "grnd_level":950,
            "humidity":41,
            "temp_kf":0.13
         },
         "weather":[
            {
               "id":802,
               "main":"Clouds",
               "description":"scattered clouds",
               "icon":"03n"
            }
         ],
         "clouds":{
            "all":48
         },
         "wind":{
            "speed":3.72,
            "deg":183
         },
         "sys":{
            "pod":"n"
         },
         "dt_txt":"2020-05-27 18:00:00"
      },
      {
         "dt":1590613200,
         "main":{
            "temp":305.05,
            "feels_like":303.65,
            "temp_min":304.7,
            "temp_max":305.05,
            "pressure":1004,
            "sea_level":1003,
            "grnd_level":949,
            "humidity":38,
            "temp_kf":0.35
         },
         "weather":[
            {
               "id":803,
               "main":"Clouds",
               "description":"broken clouds",
               "icon":"04n"
            }
         ],
         "clouds":{
            "all":68
         },
         "wind":{
            "speed":4.72,
            "deg":197
         },
         "sys":{
            "pod":"n"
         },
         "dt_txt":"2020-05-27 21:00:00"
      },
      {
         "dt":1590624000,
         "main":{
            "temp":302.97,
            "feels_like":304.29,
            "temp_min":302.74,
            "temp_max":302.97,
            "pressure":1004,
            "sea_level":1004,
            "grnd_level":949,
            "humidity":46,
            "temp_kf":0.23
         },
         "weather":[
            {
               "id":802,
               "main":"Clouds",
               "description":"scattered clouds",
               "icon":"03n"
            }
         ],
         "clouds":{
            "all":47
         },
         "wind":{
            "speed":1.48,
            "deg":196
         },
         "sys":{
            "pod":"n"
         },
         "dt_txt":"2020-05-28 00:00:00"
      }
   ],
   "city":{
      "id":1269843,
      "name":"Hyderabad",
      "coord":{
         "lat":17.3753,
         "lon":78.4744
      },
      "country":"IN",
      "population":3597816,
      "timezone":19800,
      "sunrise":1590538296,
      "sunset":1590585318
   }
} 

on the basis of the json response i am having following data classes for the room schema

Record.class

@Entity(tableName = "Record")
data class Record(
    @PrimaryKey(autoGenerate = true)
    val rid:Long,
    @SerializedName("dt_txt")
    val dtTxt: String,
    @Embedded(prefix = "main_")
    val main: Main,
    @Embedded(prefix = "rain_")
    val rain: Rain,
    @Embedded(prefix = "wind_")
    val wind: Wind
)

Weather.class

@Entity(
    tableName = "weather",
    foreignKeys = [
    ForeignKey(
        entity = Record::class,
        parentColumns = ["rid"],
        childColumns = ["wid"],
        onDelete = CASCADE
    )
])
data class Weather(
    @PrimaryKey(autoGenerate = true)
    val wid:Long,
    val description: String,
    val icon: String,
    val id: Int,
    val main: String
)

WeatherRecord.class

class WeatherRecord (
    @Embedded
    val record: Record,
    @Relation(parentColumn = "rid", entityColumn = "wid")
    val weather:List<Weather>
)

and having other classes like Rain, Main, Wind, City, etc.

Now the Response class that i am having is

WeatherResponse.class

data class WeatherResponse(
    val city: City,
    val cnt: Int,
    val cod: String,
    val list: List<Record>,
    val message: Int
)

What i have done till now

as per the JSON response one can easily understand that Weather should be a field inside the Record class but it is not there, this is because of Weather is coming as a list, because of that i used foreign key and some small trickeries (as stated in the following answer(Android Room - Handling List of Objects in an Object and querying result)in my Dao Queries in order to store the list and fetch the weather list in the database

Dao Class

@Dao
interface WetherDataDao {
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    fun upsert(data: Record)
    @Insert
    fun insert(weather:List<Weather>)
    @Transaction
    fun insert(weatherRecord: WeatherRecord){
        upsert(weatherRecord.record)
        insert(weatherRecord.weather)
    }

    @Query("Select * from Record")
    fun getAllRecords():LiveData<WeatherRecord>
}

What issue i am having

So before implemnenting the room i am getting the correct response(ie weather is coming as part of record) but after that i am not getting the weather as a part of Record and no where else. i tried to resolve the problem by changing the list type from Record to WeatherRecord inside the WeatherRecord class but that also didn't resolved my problem (it is just printing the path of the class with some codes please refer to the screenshot Screenshot) so can anyone help me out to get i out of this problem you may suggest me any other possible solution that may change the whole database desing schema, i'll be my plesure to learn

As i am new to room so you can suggest me some interesting blogs on how i can desing the database schemas for this kind of complex databses

Thanks in advance


Solution

  • I don't know if my answer would be helpful. It is more about principles, not about practical instructions

    Some reflections

    There are several types of data classes' groups in application:

    • "Transfer" group. Structure of this group is formed by transfer interfaces. For example you have Android application, and there is back-end. You've got some JSON-protocol, you've decided to use Retrofit, so to get data in a proper form you have to build your classes according to JSON structure.

    • "Database" group. Structure of this group is not so strict, but if you use relational database (SQLite is one of them), there are some requirements you have to follow. For example you can't use another object as a field of table (just foreign key instead) and so on.

    • "Business logic" group. Structure of this group almost has no restrictions, it has to be efficient to use in the core of application's logic.

    In simple Android applications all 3 data groups could be implemented with the same set of data classes, in complicated applications - they could be different with some kind of mapping with each other. Each company could specify some pattern how to build classes' hierarchy. So as always I think there is no silver bullet here.

    In practice:

    • According to your JSON you have to build certain set of data classes for "Transfer" group (I think it's the most obvious part).
    • To persist data there are several choices. And it depends on your app's use-cases. There is no requirement here, for example, to hold "wind" into separate table (associated with corresponding "Transfer" data class). You can do that if that makes sense. But you can also choose the simplest model here - just two tables "main_data" (all data except weather block) and "additional_data" (only weather block with foreign key to main_table). Let's say, you've got with Retrofit list of Wind, Rain and you have only main_table in SQLite where to save these data lists. But it's not a problem - in your Room's DAO should be some function how to save all data you want in your main_table.
    • For your use-cases you can declare other classes with structure that would be efficient for you (may be some of them are the same with your "Transfer" data classes, may be not). Do you need to work with "Wind" or "Rain" separately - well, it's no problem. You declare your classes and use it in Room DAO's requests as types of returning value (two separate functions to one SQLite table). You can declare there classes with lists of objects as a field of class, Room can do that with Relations.

    .