Avatar ·

Как сделать автоинкремент первичного ключа в Room (Kotlin)?

Всем привет! Я новичок в Android-разработке на Kotlin и использую Room для локального хранения данных. У меня есть сущность (Entity) для пользователей: ```kotlin @Entity(tableName = "users") data class User( @PrimaryKey val id: Int, val name: String, val age: Int ) ``` Я хочу, чтобы поле `id` автоматически увеличивалось при вставке новой записи (как AUTOINCREMENT в SQLite). Но когда я пытаюсь вставить пользователя без указания id, Room выдаёт ошибку, потому что id — обязательное поле (non-null). Я пробовал сделать id nullable: ```kotlin @PrimaryKey val id: Int? = null ``` Но тогда Room жалуется, что первичный ключ не может быть nullable. Также пробовал аннотацию `@NonNull`, но это не помогает. Как правильно настроить автоинкремент? Нужно ли менять тип данных? Или добавлять какую-то аннотацию? Я читал документацию, но запутался. Помогите, пожалуйста, разобраться! P.S. Использую последнюю версию Room (2.6.x) и Kotlin 1.9.x. <<>>

Решение: используйте `@PrimaryKey(autoGenerate = true)`

В Room для автоинкремента первичного ключа нужно добавить параметр autoGenerate = true в аннотацию @PrimaryKey. При этом тип поля должен быть целочисленным (Int или Long) и не nullable. Room сам позаботится о генерации значения при вставке.

Вот правильная реализация вашей сущности:

@Entity(tableName = "users")
data class User(
    @PrimaryKey(autoGenerate = true)
    val id: Int = 0,  // Значение по умолчанию не важно, Room перезапишет его
    val name: String,
    val age: Int
)

Теперь при вставке объекта User вы можете не указывать id:

val newUser = User(name = "Иван", age = 25)
val insertedId = userDao.insert(newUser)  // Room вернёт сгенерированный id

Важные моменты:

  • Тип поля: Int или Long. Room автоматически преобразует их в INTEGER в SQLite.
  • Значение по умолчанию: Можно указать любое (0, -1, null — если использовать nullable тип, но лучше не надо). Room всё равно заменит его сгенерированным значением.
  • Nullable не нужен: В отличие от некоторых ORM, Room позволяет использовать non-null поля с autoGenerate = true. Просто задайте значение по умолчанию (например, 0).

Что происходит под капотом?

Когда вы указываете autoGenerate = true, Room создаёт SQLite таблицу с INTEGER PRIMARY KEY AUTOINCREMENT. Это гарантирует, что каждый новый id будет больше предыдущего (даже после удаления записей). Если вам не нужна такая гарантия, можно опустить autoGenerate — Room всё равно будет использовать INTEGER PRIMARY KEY, что тоже даёт автоинкремент, но может повторно использовать id удалённых записей.

Полный пример DAO и вызова:

@Dao
interface UserDao {
    @Insert
    suspend fun insert(user: User): Long  // Возвращает сгенерированный id
    
    @Query("SELECT * FROM users")
    suspend fun getAllUsers(): List<User>
}

// В репозитории или ViewModel:
val user = User(name = "Анна", age = 30)
val id = userDao.insert(user)
Log.d("Room", "Новый пользователь с id = $id")

Если вы используете @Insert без возвращаемого значения, ничего страшного — id всё равно будет сгенерирован и сохранён в объекте User. Но чтобы получить его сразу, лучше возвращать Long.

Надеюсь, это поможет! Если остались вопросы — пишите в комментариях.

<<>>

Альтернативный подход: использование Long и автоинкремент без autoGenerate

Если вам не нужна строгая гарантия возрастания id (например, при удалении записи id может быть переиспользован), можно вообще не указывать autoGenerate = true. Room по умолчанию использует INTEGER PRIMARY KEY в SQLite, что уже даёт автоинкремент:

@Entity(tableName = "users")
data class User(
    @PrimaryKey
    val id: Long = 0,  // Long вместо Int — более надёжно для больших таблиц
    val name: String,
    val age: Int
)

В этом случае Room сам назначит id при вставке, но если вы удалите запись с id=5, следующая вставка может получить id=5 (а не 6, как с AUTOINCREMENT). Это экономит место в базе и ускоряет запись.

Для большинства приложений (список задач, контакты, заметки) такой подход абсолютно нормален. Выбирайте autoGenerate = true только если критично, чтобы id всегда увеличивались (например, для синхронизации с сервером).