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
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:-
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
Of course for a 1-1, you might as well include all the data in a single table.