Avatar ·

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

Всем привет! Я начинающий Android-разработчик на Kotlin и пытаюсь разобраться с библиотекой Room. У меня есть сущность (Entity) для хранения заметок. Я хочу, чтобы поле id автоматически увеличивалось при вставке новой записи, как в обычных SQLite базах данных с `AUTOINCREMENT`. Но у меня ничего не получается. Вот мой код: ```kotlin @Entity(tableName = "notes") data class Note( @PrimaryKey val id: Int = 0, val title: String, val content: String ) ``` Когда я вставляю новую заметку через DAO, я указываю id = 0, но Room выдает ошибку, что такой id уже существует (или конфликт). Я пробовал ставить `autoGenerate = true`, но тогда компилятор ругается, что `autoGenerate` можно использовать только с `Long` или `Int`. Я прочитал несколько ответов на Stack Overflow, но они на английском, и не все понятно. Подскажите, пожалуйста, как правильно сделать автоинкрементный первичный ключ в Room? Нужно ли использовать `Long` вместо `Int`? Или нужно добавлять аннотацию `@NonNull`? Буду благодарен за пример кода. <<>>

Правильный способ создания автоинкрементного первичного ключа в Room

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

Вот исправленный код вашей сущности:

@Entity(tableName = "notes")
data class Note(
    @PrimaryKey(autoGenerate = true)
    val id: Long = 0,
    val title: String,
    val content: String
)

Что изменилось и почему?

  • Тип данных: Long вместо Int. Room рекомендует Long для автоинкрементных ключей, так как это позволяет избежать переполнения при большом количестве записей.
  • Параметр autoGenerate = true: Указывает Room автоматически генерировать уникальное значение при вставке.
  • Значение по умолчанию 0: При создании объекта Note без указания id (или с id = 0) Room проигнорирует это значение и сгенерирует новое.

Как это работает в DAO?

Ваш DAO-интерфейс может выглядеть так:

@Dao
interface NoteDao {
    @Insert
    suspend fun insert(note: Note): Long  // Возвращает сгенерированный id

    @Insert
    suspend fun insertAll(notes: List<Note>): List<Long>

    @Query("SELECT * FROM notes ORDER BY id DESC")
    suspend fun getAllNotes(): List<Note>
}

Метод insert возвращает Long — это и есть сгенерированный автоинкрементный id. Если вы вставляете список, то возвращается список id.

Пример использования в ViewModel или Repository:

class NoteRepository(private val noteDao: NoteDao) {
    suspend fun addNote(title: String, content: String): Long {
        val note = Note(
            title = title,
            content = content
            // id не указываем — будет 0, Room сгенерирует новый
        )
        return noteDao.insert(note)
    }
}

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

  • Не используйте @NonNull с автоинкрементным ключом. Room сам управляет значением.
  • Если вы хотите использовать Int вместо Long, это тоже возможно: @PrimaryKey(autoGenerate = true) val id: Int = 0. Но для безопасности используйте Long.
  • Проверьте версию Room: Убедитесь, что у вас в build.gradle подключена актуальная версия (например, 2.6.0+). В старых версиях могли быть баги.

Теперь при каждой вставке Room будет автоматически увеличивать id, начиная с 1. Ошибки с дублированием ключа больше не будет.

<<>>

Альтернативный подход: использование Int и ручное управление

Если по каким-то причинам вы не хотите использовать Long, можно оставить Int с автоинкрементом. Но учтите, что максимальное значение Int — 2 147 483 647, что для большинства приложений более чем достаточно.

@Entity(tableName = "notes")
data class Note(
    @PrimaryKey(autoGenerate = true)
    val id: Int = 0,
    val title: String,
    val content: String
)

Также можно использовать @ColumnInfo(name = "id") для явного указания имени столбца, если оно отличается от имени поля.

Дополнительный совет: отладка

Если Room всё равно выдает ошибку при вставке, проверьте:

  • Не удалили ли вы базу данных вручную (например, через fallbackToDestructiveMigration())?
  • Не пытаетесь ли вы вставить объект с явно указанным id, который уже существует? Room не будет перезаписывать записи, если не использовать аннотацию @Upsert (доступна с Room 2.5.0+).

В большинстве случаев решение с Long и autoGenerate = true является стандартом для Room. Используйте его как основной подход.