android-roomandroid-room-relationkotlin-android

NullPointerException while retrieving data from room


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


Solution

  • 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

    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.