Question
I am creating a Room Persistence Library Entity class named Food, and I want foodId to behave as an auto-incrementing primary key.
@Entity
class Food(
var foodName: String,
var foodDesc: String,
var protein: Double,
var carbs: Double,
var fat: Double
) {
@PrimaryKey
var foodId: Int = 0
var calories: Double = 0.0
}
How can I make foodId an auto-increment field in Room?
Short Answer
By the end of this page, you will understand how Room handles primary keys, how to enable automatic ID generation with @PrimaryKey(autoGenerate = true), how inserts behave, and how to avoid common mistakes when designing Room entities in Kotlin.
Concept
Room is Android's ORM layer on top of SQLite. When you define an @Entity, Room maps that Kotlin class to a database table.
A primary key is a column whose value uniquely identifies each row. In many apps, you do not want to manually assign this value every time you insert data. Instead, you want the database to generate a new unique ID automatically.
In Room, this is done with:
@PrimaryKey(autoGenerate = true)
When autoGenerate = true is enabled:
- Room treats the field as a generated primary key.
- You usually pass
0for anIntkey when inserting a new object. - SQLite generates the actual ID value for the row.
- Room can return the inserted row ID from the DAO.
This matters because auto-generated IDs are very common in real apps:
- users
- products
- notes
- meals
- messages
Without auto-generation, you would need to manually manage unique IDs, which is error-prone and unnecessary in most local database use cases.
Mental Model
Think of a Room table like a notebook where each row needs a unique ticket number.
- The row data is the actual note you write.
- The primary key is the ticket number.
autoGenerate = truemeans the notebook assigns the next ticket number automatically.
So instead of saying, "This food must have ID 17," you say, "Please add this food," and Room gives it the next available ID.
Syntax and Examples
The key syntax is:
@PrimaryKey(autoGenerate = true)
var foodId: Int = 0
A common Room entity looks like this:
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity
data class Food(
val foodName: String,
val foodDesc: String,
val protein: Double,
val carbs: Double,
val fat: Double,
val calories: Double = 0.0,
@PrimaryKey(autoGenerate = true)
val foodId: Int = 0
)
Why 0?
For an Int primary key with auto-generation, 0 is commonly used as the default value for new objects. Room understands that this row does not yet have a real ID from the database.
DAO example
import androidx.room.Dao
import androidx.room.Insert
@Dao
interface FoodDao {
@Insert
suspend fun insert(food: Food): Long
}
Inserting data
val food = Food(
foodName = "Rice",
foodDesc = "Boiled white rice",
protein = 2.7,
carbs = 28.0,
fat = 0.3,
calories = 130.0
)
val newId = foodDao.insert(food)
Here:
foodIdis not manually set.- Room inserts the row.
- SQLite generates the ID.
- The DAO returns that generated row ID as
Long.
Direct answer
To make your field auto-increment in Room, write:
@PrimaryKey(autoGenerate = true)
var foodId: Int = 0
Step by Step Execution
Consider this entity and insert call:
@Entity
data class Food(
val foodName: String,
val foodDesc: String,
val protein: Double,
val carbs: Double,
val fat: Double,
@PrimaryKey(autoGenerate = true)
val foodId: Int = 0
)
val food = Food(
foodName = "Egg",
foodDesc = "Boiled egg",
protein = 6.0,
carbs = 0.6,
fat = 5.0
)
val id = foodDao.insert(food)
What happens step by step:
- A
Foodobject is created. foodIdis left at its default value of0.foodDao.insert(food)sends the object to Room.- Room sees that
foodIdis the primary key and hasautoGenerate = true. - Because the ID is unset for a new row, SQLite generates a unique numeric ID.
- The row is stored in the database.
- The DAO returns the generated row ID, for example
1.
After more inserts, generated IDs might look like:
- first row ->
1 - second row ->
2 - third row ->
3
The important idea is that your app does not have to calculate or track those IDs manually.
Real World Use Cases
Auto-generated primary keys are useful in many real applications:
- Food tracking apps: each meal or food item gets a unique local ID.
- Notes apps: every note needs a stable identifier.
- Offline-first apps: items created locally need unique IDs before syncing.
- Inventory apps: each product record needs a distinct key.
- Task managers: every task should be uniquely identifiable for updates and deletes.
For example, if you want to edit or delete a food item later, the generated foodId gives you a reliable way to find that exact row.
Real Codebase Usage
In real projects, developers often combine auto-generated IDs with a few common patterns.
1. Insert and get the generated ID
@Insert
suspend fun insert(food: Food): Long
This is useful when you need to immediately navigate to a detail screen or save related data.
2. Use the ID for updates and deletes
@Update
suspend fun update(food: Food)
@Delete
suspend fun delete(food: Food)
The primary key helps Room know which row to update.
3. Keep entity creation simple
Developers usually create new entities without manually assigning IDs:
val food = Food("Apple", "Fresh apple", 0.3, 14.0, 0.2)
4. Validation before insert
Apps often validate values before saving:
if (food.foodName.isBlank()) {
throw IllegalArgumentException("Food name cannot be blank")
}
5. Separate database IDs from server IDs
In larger apps, a Room entity may contain:
- a local auto-generated primary key
- a remote API ID from the backend
Example:
data class Food(
@PrimaryKey(autoGenerate = true)
val foodId: Int = 0,
val serverId: String?,
val foodName: String
)
This is common in sync-enabled apps.
Common Mistakes
1. Forgetting autoGenerate = true
Broken example:
@PrimaryKey
var foodId: Int = 0
Problem:
- Room treats
foodIdas a normal primary key. - If you keep inserting rows with
0, you can get conflicts.
Fix:
@PrimaryKey(autoGenerate = true)
var foodId: Int = 0
2. Manually setting IDs for new rows unnecessarily
Broken example:
val food = Food("Milk", "Low fat milk", 3.4, 5.0, 1.0, foodId = 99)
Problem:
- You may create collisions with existing rows.
- It defeats the purpose of auto-generation.
Fix:
- Let Room assign the ID unless you have a specific reason not to.
3. Expecting the object to automatically update its property after insert
Depending on your code structure, the returned ID is often the safest value to use immediately:
val insertedId = foodDao.insert(food)
Do not assume your original object instance will always be refreshed automatically.
4. Using the wrong type carelessly
Room commonly uses Int or Long for generated IDs. If your table may grow large, Long can be safer.
Example:
@PrimaryKey(autoGenerate = true)
val foodId: Long = 0L
5. Putting mutable database state everywhere
Beginners often make every field var. In many Room entities, val is cleaner unless mutation is needed.
data class Food(
val foodName: String,
@PrimaryKey(autoGenerate = true)
val foodId: Int = 0
)
Comparisons
| Concept | What it does | When to use |
|---|---|---|
@PrimaryKey | Marks a field as the unique row identifier | Use when you will provide the ID yourself |
@PrimaryKey(autoGenerate = true) | Marks a field as the primary key and lets Room/SQLite generate it | Use for most local entities |
| Manual ID assignment | You set the value before insert | Use only when IDs come from another source |
| Auto-generated local ID | Database creates the value | Best for offline/local records |
Int vs Long for Room primary keys
| Type | Example | Notes |
|---|---|---|
Int | val foodId: Int = 0 | Fine for many apps |
Long | val foodId: Long = 0L | Common and safer for larger datasets |
Class vs data class
| Option | Notes |
|---|---|
| Regular class | Works, but often more verbose |
data class | Common in Room because it is concise and readable |
Cheat Sheet
@PrimaryKey(autoGenerate = true)
val id: Int = 0
Rules
- Add
autoGenerate = trueto the primary key annotation. - Use
0forIntor0LforLongas the default value. - Do not manually assign IDs for normal inserts.
- Use the DAO
@Insertreturn value to get the generated row ID.
Example entity
@Entity
data class Food(
val foodName: String,
val foodDesc: String,
val protein: Double,
val carbs: Double,
val fat: Double,
val calories: Double = 0.0,
@PrimaryKey(autoGenerate = true)
val foodId: Int = 0
)
Example DAO
@Dao
interface FoodDao {
@Insert
suspend fun insert(food: Food): Long
}
Common gotchas
- Missing
autoGenerate = true - Manually setting IDs for new rows
- Expecting duplicate IDs to work
- Forgetting that Room uses the primary key for updates and deletes
FAQ
How do I make a Room primary key auto-increment?
Use @PrimaryKey(autoGenerate = true) on the ID field.
Should I use Int or Long for a Room primary key?
Both work, but Long is often preferred for safety in larger databases.
What default value should I use for an auto-generated ID in Room?
Use 0 for Int or 0L for Long.
Do I need to set the ID before inserting a row?
No. For auto-generated keys, let Room and SQLite create it.
Can I still update a row if the ID is auto-generated?
Yes. Once the row exists, its generated primary key identifies it for updates and deletes.
Does Room automatically return the generated ID after insert?
It can, if your @Insert DAO method returns Long or List<Long>.
Is auto-increment the same as a unique key?
Not exactly. A primary key must be unique. Auto-generation is just a way to create that unique value automatically.
Mini Project
Description
Build a simple food storage feature for an Android app using Room. Each food item should be saved with a name and nutrition values, while the database automatically assigns a unique ID. This demonstrates how auto-generated primary keys work in a practical setup.
Goal
Create a Room entity and DAO that can insert food items and return the generated ID without manually setting the primary key.
Requirements
- Create a
FoodRoom entity with an auto-generated primary key. - Add fields for name, description, protein, carbs, fat, and calories.
- Create a DAO with an insert method that returns the generated ID.
- Insert at least one
Foodobject without manually setting its ID.
Keep learning
Related questions
Accessing Kotlin Extension Functions from Java
Learn how Kotlin extension functions are compiled and how to call them correctly from Java with clear examples and common pitfalls.
Add a Maven Repository URL in Gradle Kotlin DSL (build.gradle.kts)
Learn how to add Maven Central and a custom Maven repository URL in Gradle Kotlin DSL using build.gradle.kts syntax.
Allow HTTP and HTTPS in Android 9 Pie with Network Security Configuration
Learn how Android 9 Pie handles cleartext HTTP traffic and how to allow HTTP and HTTPS safely using network security config.