{"id":25507426,"url":"https://github.com/coder-chekunkov/article-room","last_synced_at":"2025-07-25T15:02:29.316Z","repository":{"id":189534524,"uuid":"594735010","full_name":"coder-chekunkov/article-room","owner":"coder-chekunkov","description":"article. library \"room\" for beginner android developer.","archived":false,"fork":false,"pushed_at":"2023-01-29T21:03:35.000Z","size":2015,"stargazers_count":11,"open_issues_count":0,"forks_count":5,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-05-20T03:09:58.689Z","etag":null,"topics":["android","article","kotlin","room","room-database","sqlite"],"latest_commit_sha":null,"homepage":"","language":"Kotlin","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/coder-chekunkov.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2023-01-29T13:40:56.000Z","updated_at":"2025-03-17T08:57:31.000Z","dependencies_parsed_at":"2023-08-20T17:45:33.539Z","dependency_job_id":null,"html_url":"https://github.com/coder-chekunkov/article-room","commit_stats":null,"previous_names":["coder-chekunkov/room-article","coder-chekunkov/article-room"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/coder-chekunkov/article-room","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/coder-chekunkov%2Farticle-room","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/coder-chekunkov%2Farticle-room/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/coder-chekunkov%2Farticle-room/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/coder-chekunkov%2Farticle-room/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/coder-chekunkov","download_url":"https://codeload.github.com/coder-chekunkov/article-room/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/coder-chekunkov%2Farticle-room/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":267022734,"owners_count":24022907,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-07-25T02:00:09.625Z","response_time":70,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["android","article","kotlin","room","room-database","sqlite"],"created_at":"2025-02-19T07:31:48.318Z","updated_at":"2025-07-25T15:02:29.222Z","avatar_url":"https://github.com/coder-chekunkov.png","language":"Kotlin","readme":"### :book: Библиотека \"Room\" для начинающего Android-разработчика.\n\nДанная статья была написана [coder-chekunkov](https://github.com/coder-chekunkov) для начинающих Android-разработчиков. Тема: Библиотека \"Room\". \u003cbr/\u003e\n[Ссылка](https://habr.com/ru/post/713518/) на статью на Habr.\n\n\u003cp align=\"center\"\u003e\n \u003cimg alt=\"GIF\" src=\"https://github.com/coder-chekunkov/Room-Article/blob/main/wiki_images/003.gif\" width=\"220\"/\u003e\n\u003c/p\u003e\n\n---\n\nЗдравствуй, дорогой читатель. Каждый Android-разработчик сталкивался (или столкнётся во время своей профессональной карьеры) с задачей, в которой необходимо хранить большое количество изменяемых данных. В данной статье будет разобрана библиотека от Google - Room.\n\nВ статье будет рассказано об основных компонентах библиотеки и будет разобран базовый, не очень сложный пример.\n\nСтатья предназначена для новичков, не знакомых с данной библиотекой, но, желательно, имеющих базовые знаниях о SQLite, Kotlin Coroutines, Kotlin Flow, MVVM.\n\n### Теоретическая часть\n\nДля хранения каких-либо данных, Android-разработчику предоставлены следующие способы: *Files, SharedPreferences, SQLite, Resources/Assets*. Но какой способ выбрать? Для ответа на данный вопрос можно воспользоваться алгоритмом, изображенном на рисунке ниже.\n\n\u003cp align=\"center\"\u003e\n \u003cimg alt=\"GIF\" src=\"https://github.com/coder-chekunkov/Room-Article/blob/main/wiki_images/001.png\"/\u003e\n\u003c/p\u003e\n\nВ случае, если данные, которые мы будем хранить, могут изменяться и имеют не простую структуру, следует выбрать SQLite.\n\n**SQLite** - это реляционная база данных, в которой все данные хранятся в таблицах, которые в свою очередь могут быть связаны между собой. Для взаимодействия с базой данных используется специальный язык запросов - SQL. В случае, если Вы незнакомы с данной реляционной базой данных, - рекомендую данный [источник](https://metanit.com/sql/sqlite/1.1.php).\n\nОдин из способов для работы с SQLite в Android - это, встроенный в Android SDK, *SQL API*. Данное API позволяет работать с базой данных, но, по-моему мнению, данная технология далеко не простая, а в некоторых моментах даже сложная. В данной статье мы не будем разбирать данную технологию, а сразу перейдем к библиотеке \"Room\".\n\n**Room** - это библиотека, представленная на *Google I/O* в 2017 году. Данная библиотека работает с базой данный SQLite и выполняет большую часть работы за Вас. Все что необходимо разработчику - это \"объяснить\" библиотеке как выглядят данные, их структуру и способы взаимодействия с помощью специальных аннотаций:\n\n`@Database` - аннотация для объявления базы данных. \u003cbr/\u003e\n`@Entity` - аннотация для объявления сущности базы данных. \u003cbr/\u003e\n`@Dao` - аннотация для объявления интерфейса, который будет заниматься манипулированием данными базы данных. \u003cbr/\u003e\n`@PrimaryKey` - аннотация для объявления первичного ключа сущности. \u003cbr/\u003e\n`@ColumnInfo` - аннотация для настроек конкретного столбца сущности. \u003cbr/\u003e\n`@Query` - аннотация, которая позволяет выполнить SQL-запрос в методах DAO-интерфейса. \u003cbr/\u003e\n`@Insert` - аннотация, которая позволяет выполнить вставку в таблицу базы данных. \u003cbr/\u003e\n`@Update` - аннотация, которая позволяет выполнить обновление некоторых строк в таблице базы данных. \u003cbr/\u003e\n`@Delete` - аннотация, которая позволяет выполнить удаление некоторых строк в таблице базы данных. \u003cbr/\u003e\n`@Transaction` - аннотация, которая помечает метод в DAO-интерфейсе как транзакция.\n\nЭто далеко не все аннотации, которые предоставляет \"Room\", но являющиеся основными. Более подробно аннотации будут рассмотрены в практическом примере.\n\nТакже следует отметить, что существуют специальные *Tuple-классы*, которые никак не помечаются, но являются важной частью при разработке. Данные классы используются при взаимодействии с базой данных (например, когда нам необходимо получить какую-то часть данных из таблицы, а не все данные сразу). Более подробно Tuple-классы будут рассмотрены в практическом примере.\n\n### Практическая часть\n\nВ качестве не сложного примера, создадим приложение, которое будет \"имитировать\" создание и отображение статистических данных какой-то игры (например, судоку). Приложение будет состоять из двух экранов: первый - заполнение и отправка данных в базу; второй - список со всеми данными из базы.\n\nСтатистические данные будут состоять из следующих компонентов: результат игры (победа / поражение), уровень сложности (легкая, сложная и т.д.), количество ошибок, количество набранных очков.\n\nВажное уточнение. В данной статье не будут приведены листинги кода с версткой xml-файлов и всех классов приложения. Полностью готовый проект можно найти [здесь](https://github.com/coder-chekunkov/Room-Article).\n\nВ первую очередь необходимо **указать все зависимости**, которые будут использованы приложением. Для этого в файл сборки `build.gradle` нашего приложения:\n\n```groovy\ndependencies {\n\n    ...\n\n    implementation 'androidx.room:room-runtime:2.5.0' // Библиотека \"Room\"\n    kapt \"androidx.room:room-compiler:2.5.0\" // Кодогенератор\n    implementation 'androidx.room:room-ktx:2.5.0' // Дополнительно для Kotlin Coroutines, Kotlin Flows\n}\n```\n\n```groovy\nplugins {\n\n    ...\n\n    id 'kotlin-kapt'\n}\n```\n\n```groovy\nandroid {\n\n    ...\n\n    defaultConfig {\n\n        ...\n\n        kapt {\n            arguments {arg(\"room.schemaLocation\", \"$projectDir/schemas\")}\n        }\n    }\n}\n```\n\n\nДанные блоки кода подключают нужные библиотеки (первый блок), добавляют нужный плагин (второй блок) и указывают нужный путь к каталогу, который будет хранить схему нашей базы данных (третий блок).\n\nПеред тем как перейти к описанию сущностей, необходимо **представить как база данных будет выглядеть**, из каких таблиц она будет состоять. В данном практическом примере база данных будет состоять из трех таблиц:\n\n- таблица \"difficulty_levels\", в которой будут хранится все доступные уровни сложности;\n- таблица \"results\", в которой будут хранится все доступные результаты игры;\n- таблица \"statistic\", в которой будут хранится все статистические данные.\n  \nСхематично база данных будет выглядеть следующим образом:\n\n\u003cp align=\"center\"\u003e\n \u003cimg alt=\"GIF\" src=\"https://github.com/coder-chekunkov/Room-Article/blob/main/wiki_images/002.png\"/\u003e\n\u003c/p\u003e\n\nSQL-скрипт для такой базы данных выглядел бы следующим образом:\n\n```sql\nCREATE TABLE difficulty_levels(\n    id INTEGER PRIMARY KEY,\n    difficulty_name TEXT\n);\n\nCREATE TABLE results (\n    id INTEGER PRIMARY KEY,\n    result_name TEXT\n);\n\nCREATE TABLE statistic (\n    id INTEGER PRIMARY KEY,\n    result_id INTEGER,\n    difficult_id INTEGER,\n    mistakes INTEGER,\n    points INTEGER,\n    FOREIGN KEY (result_id) REFERENCES results(id),\n    FOREIGN KEY (difficult_id) REFERENCES difficulty_levels(id)\n);\n```\n\n*Примечание: никогда не храните секретные данные (например, пароли) в открытом виде. Всегда хэшируйте их!*\n\nПосле того, как была разобрана схема базы данных, необходимо перейти к **созданию сущностей**. Создадим *data-class* `DifficultyLevelsDbEntity`, который будет описывать таблицу \"difficulty_levels\":\n\n```kotlin\n@Entity(tableName = \"difficulty_levels\")\ndata class DifficultyLevelsDbEntity(\n    @PrimaryKey val id: Long,\n    @ColumnInfo(name = \"difficulty_name\") val difficultyName: String\n)\n```\n\nВ данном случае мы пометили класс аннотацией `@Entity`, в которой переопределили свойство *tableName* - данное свойство задаёт имя таблицы. В случае, если бы свойство не было бы определенно, то таблица назвалась аналогично названию класса, т.е. DifficultyLevelsDbEntity.\n\nТакже поля класса были помечены аннотациями `@PrimaryKey` и `@ColumnInfo`. Первая аннотация помечает поле класса, как первичный ключ, а вторая задаёт название столбца отличное от названии переменной. У аннотации `@ColumnInfo` есть и другие свойства, например значение по умолчанию, более подробно про свойства данной аннотации можно узнать [здесь](https://developer.android.com/reference/androidx/room/ColumnInfo).\n\nАналогично создадим класс `ReultsDbEntity`:\n\n```kotlin\n@Entity(tableName = \"results\")\ndata class ResultsDbEntity(\n    @PrimaryKey val id: Long,\n    @ColumnInfo(name = \"result_name\") val resultName: String\n)\n```\n\nТеперь перейдем к созданию более сложной сущности - `StatisticDbEntity`:\n\n```kotlin\n@Entity(\n    tableName = \"statistic\",\n    indices = [Index(\"id\")],\n    foreignKeys = [\n        ForeignKey(\n            entity = ResultsDbEntity::class,\n            parentColumns = [\"id\"],\n            childColumns = [\"result_id\"]\n        ),\n        ForeignKey(\n            entity = DifficultyLevelsDbEntity::class,\n            parentColumns = [\"id\"],\n            childColumns = [\"difficult_id\"]\n        )\n    ]\n)\ndata class StatisticDbEntity(\n    @PrimaryKey(autoGenerate = true) val id: Long,\n    @ColumnInfo(name = \"result_id\") val resultId: Long,\n    @ColumnInfo(name = \"difficult_id\") val difficultId: Long,\n    val mistakes: Long,\n    val points: Long\n)\n```\n\nАннотации в свойствах data-class'а очень похожи на те, что были созданы ранее. Единственное отличие - в `@PrimaryKey` было определено свойство *autoGenerate = true*. Данное свойство \"объясняет\" библиотеке, что при вставке нового объекта в таблицу, необходимо сгенерировать индекс самостоятельно. Например, в таблице находится пять элементов, при вставке нового элемента поле *id* автоматически станет равно шести.\n\nТакже в аннотации `@Entity` было определенно больше свойств. Свойство *indices* \"объясняет\" библиотеки по каком полю производить индексацию, в данном случае - *id*.\n\nСвойство *foreignKeys* объявляет составные ключи. В данном случае составных ключа два - *result_id* и *difficult_id*. В объекте *ForeignKey* указываются сущность-родитель (entity), столбец-родитель (parentColumns) и столбец-ребенок (childColumns).\n\nПосле того как были созданы все сущности базы данных, создадим интерфейс, помеченный аннотацией `@Dao`. Данный интерфейс будет взаимодействовать с базой данных с помощью специальных методов. Пока оставим данный интерфейс пустым, к его реализации следует вернуться чуть позже:\n\n```kotlin\n@Dao\ninterface StatisticDao {\n\n} \n```\n\nКогда сущности были созданы, dao-интерфейс объявлен необходимо создать абстрактный класс `AppDatabase`, который будет описывать базу данных:\n\n```kotlin\n@Database(\n    version = 1,\n    entities = [\n        DifficultyLevelsDbEntity::class,\n        ResultsDbEntity::class,\n        StatisticDbEntity::class\n    ]\n)\nabstract class AppDatabase : RoomDatabase() {\n\n    abstract fun getStatisticDao(): StatisticDao\n\n}\n```\n\nДанный класс помечен аннотацией `@Database`, в которой необходимо обязательно описать два свойства: *entities* и *version*. Первое свойство принимает все сущности, которые были описаны выше, а второе свойство задаёт версию базы данных.\n\nВерсия базы данных используется для контроля базы данных, ее данных и т.д. Зачастую это используется при миграции базы данных с одной версии на другую, но это уже совсем другая история...\n\nВ самом классе создан абстрактный метод, который возвращает dao-интерфейс.\n\nТак же **создадим tupple-класс** `StatisticInfoTuple`, который будет использоваться при \"вытягивании\" статистических данных из таблицы. Данный класс очень похож на *StatisticDbEntity*, но поля, которые хранят результат и уровень сложности, уже имеют тип String, так как там будут хранится значения результата и уровня сложности.\n\n```kotlin\ndata class StatisticInfoTuple(\n    val id: Long,\n    @ColumnInfo(name = \"result_name\") val result: String,\n    @ColumnInfo(name = \"difficulty_name\") val difficult: String,\n    val mistakes: Long,\n    val points: Long\n)   \n```\n\nПосле всех проделанных действий необходимо перейти к **реализации dao-интерфейса**. В данном интерфейсе создадим три метода, которые будут вставлять новые статистические данные, удалять данные по уникальному значению (id) и получать список всех данных из таблицы *statistic*:\n\n```kotlin\n    @Insert(entity = StatisticDbEntity::class)\n    fun insertNewStatisticData(statistic: StatisticDbEntity)\n\n    @Query(\"SELECT statistic.id, result_name, difficulty_name, mistakes, points FROM statistic\\n\" +\n            \"INNER JOIN results ON statistic.result_id = results.id\\n\" +\n            \"INNER JOIN difficulty_levels ON statistic.difficult_id = difficulty_levels.id;\")\n    fun getAllStatisticData(): List\u003cStatisticInfoTuple\u003e\n\n    @Query(\"DELETE FROM statistic WHERE id = :statisticId\")\n    fun deleteStatisticDataById(statisticId: Long) \n```\n\nМетод *insertNewStatisticData* принимает объект класса StatisticDbEntitty - объект, который необходимо вставить. Также данный метод помечен аннотацией `@Insert`, в которой определено свойство entity, благодаря которому происходит вставка в нужную таблицу.\n\nМетоды *getAllStatisticData* и *deleteStatisticDataById* помечены аннотацией `@Query`, которая принимает строку с SQL-запросом. Именно благодаря данному запросу выполняется получение всех элементов или удаление какого-то конкретного элемента.\n\nИ последнее, что необходимо сделать с базой данной - создать ее. Для этого выполним следующее:\n\n```kotlin\nobject Dependencies {\n\n    private lateinit var applicationContext: Context\n\n    fun init(context: Context) {\n        applicationContext = context\n    }\n\n    private val appDatabase: AppDatabase by lazy {\n        Room.databaseBuilder(applicationContext, AppDatabase::class.java, \"database.db\")\n            .createFromAsset(\"room_article.db\")\n            .build()\n    }\n}\n```\n\nВ данном примере создается база данных *appDatabase* с помощью специального билдера: `Room.databaseBuilder`, который принимает контекст, класс, содержащий описание нашей базы данных, и название самой базы.\n\nТак же у билдера вызван метод `createFromAssets`, данный метод заполняет базу данных приготовленными значениями. Т.е., если необходимо, чтобы при инициализации база данных хранила в себе какие-либо значения (например, уровни сложности и доступные результаты), нужно создать отдельно базу данных с помощью сторонних программ, таких как *DB Browser for SQLite*, заполнить ее и сохранить ее в папку *assets* приложения.\n\nПосле того, как все манипуляции с базой данных были реализованы, необходимо **создать data-класс** `Statistic`, который будет использоваться во всем приложении. В данном классе создан метод `toStatisticDbEntity`, конвертирующий данный класс в сущность:\n\n```kotlin\ndata class Statistic(\n    val resultId: Long,\n    val difficultId: Long,\n    val mistakes: Long,\n    val points: Long\n) {\n\n    fun toStatisticDbEntity(): StatisticDbEntity = StatisticDbEntity(\n        id = 0,\n        resultId = resultId,\n        difficultId = difficultId,\n        mistakes = mistakes,\n        points = points\n    )\n}\n```\n\nТеперь необходимо **создать репозиторий**, который будет обращаться к dao-интерфейсу и манипулировать данными базы данных:\n\n```kotlin\nclass StatisticRepository(private val statisticDao: StatisticDao) {\n\n    suspend fun insertNewStatisticData(statisticDbEntity: StatisticDbEntity) {\n        withContext(Dispatchers.IO) {\n            statisticDao.insertNewStatisticData(statisticDbEntity)\n        }\n    }\n\n    suspend fun getAllStatisticData(): List\u003cStatisticInfoTuple\u003e {\n        return withContext(Dispatchers.IO) {\n            return@withContext statisticDao.getAllStatisticData()\n        }\n    }\n\n    suspend fun removeStatisticDataById(id: Long) {\n        withContext(Dispatchers.IO) {\n            statisticDao.deleteStatisticDataById(id)\n        }\n    }\n}\n```\n\nВ данном репозитории три метода, которые вставляют новые данные, получают всю статистику и удаляют какой-то элемент по индетификатору. Все эти методы являются *suspend-функциями*, т.к. будут вызываться из корутин. Так же следует изменить Dispatcher (*withContext*), т.к. обращаться к базе данных из основного потока нельзя.\n\nВсе эти методы вызываются в корутинах, запущеных во ViewModel, например вставка нового значения:\n\n```kotlin\nfun insertNewStatisticDataInDatabase(mistakes: Long, points: Long) {\n        viewModelScope.launch {\n            val newStatistic = Statistic(currentResult, currentDifficultyLevel, mistakes, points)\n            statisticRepository.insertNewStatisticData(newStatistic.toStatisticDbEntity())\n        }\n    }\n```\n\n[Ссылка](https://habr.com/ru/sandbox/181814/) на статью на Habr.\n\n---\n\n🏆 Я надеюсь, что данная работа помогла Вам. \u003cbr/\u003e\n📧 При возникновении каких-либо вопросов и предложений - свяжитесь со мной. \u003cbr/\u003e\n🤝 Спасибо, что заинтересовались данной работой.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcoder-chekunkov%2Farticle-room","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcoder-chekunkov%2Farticle-room","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcoder-chekunkov%2Farticle-room/lists"}