Room, ViewModel и Compose — список заметок
Room, ViewModel и Compose — список заметок
Compose — первый экран хранил счётчик в памяти: при убийстве процесса или переустановке данные пропадают. Заметки, настройки, корзина обычно сохраняют в локальной базе на телефоне.
Стек из этой статьи — рекомендуемый для новых Android-экранов:
| Слой | Библиотека | Задача |
|---|---|---|
| UI | Compose | Показать список, поле ввода, кнопки |
| Логика экрана | ViewModel | Пережить поворот, запустить корутины |
| Данные | Room | SQLite без ручного SQL на каждый запрос |
Перед стартом: первая программа, Compose, корутины. Маршрут раздела: intro.
Словарь терминов
| Термин | Простыми словами |
|---|---|
| SQLite | Встроенная база данных в файле на устройстве. |
| Room | Обёртка над SQLite: аннотации вместо длинного SQL в коде. |
| Entity | Класс = одна таблица (NoteEntity). |
| DAO | Интерфейс с методами «вставить», «прочитать всё», «удалить». |
@Query | SQL-запрос, который Room проверяет на этапе компиляции. |
| KSP | Генератор кода для Room (наследник kapt). |
Слои (как думать о коде)
Compose (NotesScreen)
↓ collectAsState
ViewModel (NotesViewModel)
↓ Flow<List<Note>>
DAO (NoteDao) → Room → SQLite (файл notes.db)
Пользователь нажимает «Добавить» → ViewModel вызывает dao.insert в корутине → Room обновляет таблицу → observeAll() эмитит новый список → Compose перерисовывает LazyColumn.
Что получится
Экран: поле ввода, кнопка «Добавить», список заметок. Данные остаются после закрытия приложения.
Зависимости (app/build.gradle.kts)
plugins {
id("com.google.devtools.ksp") version "2.0.0-1.0.24" // версию подставьте под Kotlin
}
dependencies {
val room = "2.6.1"
implementation("androidx.room:room-runtime:$room")
implementation("androidx.room:room-ktx:$room")
ksp("androidx.room:room-compiler:$room")
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.8.0")
implementation("androidx.compose.material3:material3")
}
room-ktx добавляет suspend-функции и Flow в DAO.
Сущность и DAO
@Entity(tableName = "notes")
data class NoteEntity(
@PrimaryKey(autoGenerate = true) val id: Long = 0,
@ColumnInfo(name = "text") val text: String
)
@Dao
interface NoteDao {
@Query("SELECT * FROM notes ORDER BY id DESC")
fun observeAll(): Flow<List<NoteEntity>>
@Insert
suspend fun insert(note: NoteEntity)
}
Разбор:
| Аннотация | Смысл |
|---|---|
@Entity | Эта data class — таблица notes. |
@PrimaryKey(autoGenerate = true) | id задаёт SQLite при вставке. |
observeAll(): Flow<...> | При любом изменении таблицы подписчики получают новый список. |
suspend fun insert | Вставка в фоне, без блокировки UI-потока. |
База данных
@Database(entities = [NoteEntity::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
abstract fun noteDao(): NoteDao
}
Создание (учебный вариант в Activity):
val db = Room.databaseBuilder(
applicationContext,
AppDatabase::class.java,
"notes.db"
).build()
В продакшене базу обычно создают один раз (Application, Hilt) — см. экосистему.
ViewModel
class NotesViewModel(private val dao: NoteDao) : ViewModel() {
val notes: StateFlow<List<NoteEntity>> =
dao.observeAll()
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5_000),
initialValue = emptyList()
)
fun addNote(text: String) {
if (text.isBlank()) return
viewModelScope.launch {
dao.insert(NoteEntity(text = text.trim()))
}
}
}
Разбор:
| Строка | Смысл |
|---|---|
dao.observeAll() | Cold Flow из Room — обновления при изменении таблицы. |
stateIn(..., WhileSubscribed(5000)) | Горячий StateFlow для UI; отписка через 5 с после ухода с экрана. |
initialValue = emptyList() | До первой эмиссии UI видит пустой список. |
viewModelScope.launch | insert выполняется в корутине, не на Main. |
Подробнее про Flow: 226.md.
Compose-экран
@Composable
fun NotesScreen(vm: NotesViewModel = viewModel(
factory = /* фабрика с dao — см. ниже */
)) {
val notes by vm.notes.collectAsState()
var input by remember { mutableStateOf("") }
Column(Modifier.padding(16.dp)) {
OutlinedTextField(
value = input,
onValueChange = { input = it },
label = { Text("Заметка") },
modifier = Modifier.fillMaxWidth()
)
Button(
onClick = {
vm.addNote(input)
input = ""
},
modifier = Modifier.padding(vertical = 8.dp)
) { Text("Добавить") }
LazyColumn {
items(notes, key = { it.id }) { note ->
Text(note.text, modifier = Modifier.padding(8.dp))
}
}
}
}
inputвremember— текст поля только на UI-слое.notesиз ViewModel — источник правды из БД.items(..., key = { it.id })— стабильные ключи для анимаций и производительности.
Фабрика ViewModel (упрощённо)
setContent {
val vm: NotesViewModel = viewModel(factory = object : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
@Suppress("UNCHECKED_CAST")
return NotesViewModel(db.noteDao()) as T
}
})
MaterialTheme { NotesScreen(vm) }
}
Частые ошибки
| Симптом | Причина |
|---|---|
| Список пустой после добавления | Нет Flow в DAO или insert не в корутине |
Cannot access database on the main thread | insert вызван синхронно с Main |
| KSP не генерирует код | Не применён plugin ksp |
| Сброс при повороте | Список только в remember, без ViewModel |
Что попробовать
- Метод
@Deleteв DAO и удаление по свайпу. - Navigation Compose — экран деталей заметки.
- Синхронизация с сервером: Ktor Client + запись в Room.
Дальше
Jetpack Compose · Flow · мобильный раздел
См. также
Другие статьи этого же раздела в боковом меню (как на странице «О разделе»). История Kotlin - создание языка JetBrains для плавной совместимости с Java и современной разработки. Экосистема Kotlin-приложений - инструменты, фреймворки и мультиплатформенные сценарии разработки. Kotlin — это современный язык программирования общего назначения, который работает на платформе Java Virtual Machine (JVM), а также компилируется в JavaScript или нативный код. Набор советов, правил, принципов и обычаев в разработке на этом языке. Основы Kotlin - архитектура проекта, платформенные модули и базовые принципы разработки на языке. Типизация, набор правил определения типа данных значений языка. Операторы и выражения в Kotlin - логика вычислений, приоритеты операций и идиоматичный стиль записи условий. Циклы и управляющие конструкции в Kotlin - идиоматичные способы итерации, условия и управление выполнением. ООП в Kotlin - классы, модификаторы доступа, наследование и идиоматичное проектирование объектных структур. Кавычки, точки, запятые, скобки и прочие знаки препинания. Kotlin использует набор зарезервированных слов для построения синтаксических конструкций языка. Все ключевые слова разделены на категории по назначению. Набор функций, которые включены в стандартную библиотеку языка.История языка Kotlin
Экосистема Kotlin-приложений
Что требуется знать перед началом изучения языка программирования Kotlin
Рекомендации по разработке на Kotlin
Основы языка Kotlin
Типы данных и объявление переменных
Операторы и выражения в Kotlin
Циклы и управляющие конструкции
Объектно-ориентированное программирование в Kotlin
Синтаксис и пунктуация в Kotlin
Ключевые слова языка Kotlin
Встроенные функции и расширения Kotlin