0

I have 3 entitites (Location, Current and Forecast) with following relationships:

1.) Location - Current (one-one)

2.) Location - Forecast (one-one)

Now to retrieve data from it i wrote a query

@Transaction
    @Query(
        "SELECT weather_location.id, name, current_weather.*, weather_forecast.* FROM weather_location " +
                "INNER JOIN current_weather ON current_weather.locationId = weather_location.id " +
                "INNER JOIN weather_forecast ON weather_forecast.locationId = weather_location.id "
    )
    fun getWeather(): Flow<List<WeatherModel>>

WeatherModel:

data class WeatherModel(
    val id: String,
    val name: String,

    @Relation(
        parentColumn = "id",  <- line 12
        entityColumn = "locationId"
    )
    val currentWeather: CurrentWeatherEntity,

    @Relation(
        parentColumn = "id",
        entityColumn = "locationId"
    )
    val weatherForecast: WeatherForecastEntity

)

Error:

java.lang.NullPointerException: Parameter specified as non-null is null: method com.anshtya.weatherapp.data.local.model.WeatherModel.<init>, parameter currentWeather
                                                                                                        at com.anshtya.weatherapp.data.local.model.WeatherModel.<init>(Unknown Source:12)
                                                                                                        at com.anshtya.weatherapp.data.local.dao.WeatherDao_Impl$16.call(WeatherDao_Impl.java:609)
                                                                                                        at com.anshtya.weatherapp.data.local.dao.WeatherDao_Impl$16.call(WeatherDao_Impl.java:568)

Why i am getting this error? Note: Query works in App Inspection of Android Studio

Ansh T
  • 13
  • 3

1 Answers1

0

1.) Location - Current (one-one) and Location - Forecast (one-one)

Really you have a 1-many, which may be 0 and hence potentially null. You could eliminate the error by allowing nulls using val currentWeather: CurrentWeatherEntity?, and val weatherForecast: WeatherForecastEntity?

However, you will then find that you get nulls, and hence why you are getting the NPE, rather than the expected related data (see example below)

When you use @Relation annotation you are effectively saying use a subquery to build the related object (and the warning if you do not use @Transaction). That is the JOIN's are effectively ignored other than if they effect the non @Relation fields, i.e. the parents (WeatherLocationEntity's) that are extracted.

Typically when using an @Relation you would expect a List, which may be empty (as opposed to null).

So if you use:-

data class WeatherModel(
    val id: String,
    val name: String,

    @Relation(
        parentColumn = "id",
        entityColumn = "locationId"
    )
    val currentWeather: List<CurrentWeatherEntity>,

    @Relation(
        parentColumn = "id",
        entityColumn = "locationId"
    )
    val weatherForecast: List<WeatherForecastEntity>
)

Along with for example (instead of your getWeather()) :-

@Transaction
@Query("SELECT * FROM weather_location")
fun getWeatherAlt(): List<WeatherModel>

Then the result will be as expected (unlike as when using the getWeather). Consider the following in the database:-

enter image description here enter image description here enter image description here

  • not the purposeful lack of a WeatherForecast for the WeatherLocation w2

Then the output obtained using:-

fun logWeatherModels(wml: List<WeatherModel>,tagsuffix: String) {
    for (wm in wml) {
        val cwsb = StringBuilder()
        for (cw in wm.currentWeather) {
            cwsb.append("\n\tCW ID is ${cw.id} CW_WMID is ${cw.locationId} CW OTHR is ${cw.otherCurrentData}")
        }
        val wfsb = StringBuilder()
        for (wf in wm.weatherForecast) {
            wfsb.append("\n\tWF ID is ${wf.id} WF_WMID is ${wf.locationId} WF OTHR is ${wf.otherForecastData}")
        }
        Log.d("DBINFO_$tagsuffix","Name = ${wm.name} ID  is ${wm.id} $cwsb$wfsb")
    }
}

will be:-

2023-05-14 10:45:38.279 D/DBINFO_STG1: Name = Weather 1 ID  is WF1W1 

2023-05-14 10:45:38.284 D/DBINFO_ALTSTG1: Name = Weather 1 ID  is w1 
        CW ID is CW1W1 CW_WMID is w1 CW OTHR is blah cw1
        WF ID is WF1W1 WF_WMID is w1 WF OTHR is blah wf1
        
        
2023-05-14 10:45:38.295 D/DBINFO_STG2: Name = Weather 1 ID  is WF1W1 

2023-05-14 10:45:38.301 D/DBINFO_STG2ALT: Name = Weather 1 ID  is w1 
        CW ID is CW1W1 CW_WMID is w1 CW OTHR is blah cw1
        WF ID is WF1W1 WF_WMID is w1 WF OTHR is blah wf1
2023-05-14 10:45:38.301 D/DBINFO_STG2ALT: Name = Weather 2 ID  is w2 
        CW ID is CW2W2 CW_WMID is w2 CW OTHR is clah cw2
  • STG1 and ALTSTG1 were invoked after the insertion of the first set (1 Location with a Current and Forecast related to the Location), whilst STG2 and ALTSTG2 were after the insertion of a partial set (1 new Location and just a related Current)
  • That is the STG1 and STG results (using your getWeather function) has not obtained the respective related CurrentWeatherEntity data nor the WeatherForecastEntity data, but an empty list. However, the ALTSTG? results have correctly obtained the related data (albeit that Weather 2 has no related WatherForecast).

Of course for a 1-1, you might as well include all the data in a single table.

MikeT
  • 51,415
  • 16
  • 49
  • 68