Search code examples
javaandroidkotlinandroid-room

Room doesn't autogenerate Primary Key


I have the following Model for my API Response:

@Entity(tableName = TABLE_NAME)
class WeatherEntry {

    @PrimaryKey(autoGenerate = true)
    var wID: Long? = null

    @SerializedName("dt")
    @ColumnInfo(name = COLUMN_DATE)
    var date: String = ""

    @SerializedName("city")
    @Embedded(prefix = "location_")
    var location: Location? = null

    @SerializedName("main")
    @Embedded(prefix = "main_")
    var main: Main? = null

    @SerializedName("weather")
    @TypeConverters(Converters::class)
    @Embedded(prefix = "weather_")
    var weather: ArrayList<Weather>? = null

    @SerializedName("wind")
    @Embedded(prefix = "wind_")
    var wind: Wind? = null

}

Weather Repo Fetches Data from Local or Remote Data Source, I set forceRemote to true, because otherwise there would be no data show in the first place.

class WeatherRepository @Inject constructor(@Local var localDataSource: WeatherDataSource, @Remote var remoteDataSource: WeatherDataSource) :
    WeatherDataSource {

   private var caches: MutableList<WeatherEntry> = mutableListOf()
   override fun getWeatherEntries(location: String, forceRemote: Boolean): Flowable<MutableList<WeatherEntry>> {

        if (forceRemote) {
            return refreshData(location)
        } else {
            return if (caches.isNotEmpty()) {
                // if cache is available, return it immediately
                Flowable.just(caches)
            } else {
                // else return data from local storage
                localDataSource.getWeatherEntries(location, false)
                    .take(1)
                    .flatMap(({ Flowable.fromIterable(it) }))
                    .doOnNext { question -> caches.add(question) }
                    .toList()
                    .toFlowable()
                    .filter({ list -> !list.isEmpty() })
                    .switchIfEmpty(refreshData(location)) // If local data is empty, fetch from remote source instead.
            }
        }
    }

    /**
     * Fetches data from remote source.
     * Save it into both local database and cache.
     *
     * @return the Flowable of newly fetched data.
     */
    private fun refreshData(location: String): Flowable<MutableList<WeatherEntry>> {

        return remoteDataSource.getWeatherEntries(location,true).doOnNext({

            // Clear cache
            caches.clear()
            // Clear data in local storage
            localDataSource.deleteAllWeatherEntries()
        }).flatMap(({ Flowable.fromIterable(it) })).doOnNext({ entry ->
            caches.add(entry)
            localDataSource.insertWeatherEntry(entry)
        }).toList().toFlowable()
    }

Local Data Source

class WeatherLocalDataSource @Inject constructor(private var weatherDao: WeatherDao): WeatherDataSource {

    override fun insertWeatherEntry(weatherEntry: WeatherEntry) {
        return weatherDao.insert(weatherEntry)
    }

    ...
}

Remote Data Source This one definitely works as I'm getting all information from the api.

class WeatherRemoteDataSource @Inject constructor(var weatherService: WeatherService) :
    WeatherDataSource {

    override fun getWeatherEntries(location: String, forceRemote: Boolean): Flowable<MutableList<WeatherEntry>> {
        return weatherService.getForecast(
            location,
            "json",
            "metric",
            BuildConfig.OPEN_WEATHER_MAP_API_KEY
        ).map(WeatherForecastResponse::weatherEntries)
    }
}

DAO

@Dao
interface WeatherDao {

    ...

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    fun insert(weatherEntry: WeatherEntry)
}

Database

@Database(
    entities = [(WeatherEntry::class)],
    version = 1
)
abstract class WeatherDatabase : RoomDatabase() {

    abstract fun weatherDao(): WeatherDao
}

All other fields work correctly, but wID is always null. What is wrong with my implementation?

I already tried to change it's default value to 0 and change the type to Int but that's not working either.


Solution

  • Try making the id non-nullable:

     @PrimaryKey(autoGenerate = true)
        var wID: Long = 0
    

    EDIT: I've found this in the sample code here. you can make your @Insert methods return the id of the inserted row object, so you could do this:

    In your Dao:

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    fun insert(weatherEntry: WeatherEntry) : Long
    

    In your refresh data method:

    private fun refreshData(location: String): Flowable<MutableList<WeatherEntry>> {
    
            return remoteDataSource.getWeatherEntries(location,true).doOnNext({
    
                // Clear cache
                caches.clear()
                // Clear data in local storage
                localDataSource.deleteAllWeatherEntries()
            }).flatMap(({ Flowable.fromIterable(it) })).doOnNext({ entry ->
    
                val entryID = localDataSource.insertWeatherEntry(entry)
                entry.wID = entryID
                caches.add(entry)
            }).toList().toFlowable()
        }