niclaszll
niclaszll

Reputation: 390

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.

Upvotes: 9

Views: 11453

Answers (3)

Sem Kaminskyi
Sem Kaminskyi

Reputation: 11

solutions for my problem is change type of primarykey from "long" to "Long" (java)

@PrimaryKey(autoGenerate = true) @ColumnInfo(name = "task_id") private Long taskID;

Upvotes: 1

chen
chen

Reputation: 192

Read https://developer.android.com/reference/androidx/room/PrimaryKey?hl=en#autoGenerate() , you'll get the answer

public boolean autoGenerate ()
Set to true to let SQLite generate the unique id.

When set to true, the SQLite type affinity for the field should be INTEGER.

If the field type is long or int (or its TypeConverter converts it to a long or int), Insert methods treat 0 as not-set while inserting the item.

If the field's type is Integer or Long (or its TypeConverter converts it to an Integer or a Long), Insert methods treat null as not-set while inserting the item.

Upvotes: 4

Levi Moreira
Levi Moreira

Reputation: 12007

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()
    }

Upvotes: 8

Related Questions