💾 Data, Network और Architecture · Lesson 30
Room Database (SQLite)
Room क्या है?
Room SQLite (हर Android phone में built-in database) के ऊपर Jetpack की library है। आप Kotlin classes से tables और annotations से queries describe करते हैं; Room आपकी SQL compile time पर check करता है और boring code generate करता है।
Step 1: Dependencies (KSP)
TOML + Kotlin DSL
# gradle/libs.versions.toml
ksp = "2.0.21-1.0.28"
room = "2.6.1"
androidx-room-runtime = { group = "androidx.room", name = "room-runtime", version.ref = "room" }
androidx-room-ktx = { group = "androidx.room", name = "room-ktx", version.ref = "room" }
androidx-room-compiler = { group = "androidx.room", name = "room-compiler", version.ref = "room" }
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" } # under [plugins]
// app/build.gradle.kts
plugins { alias(libs.plugins.ksp) }
dependencies {
implementation(libs.androidx.room.runtime)
implementation(libs.androidx.room.ktx) // coroutines + Flow support
ksp(libs.androidx.room.compiler) // generates the DAO code
}Step 2: Entity = table
Kotlin – data/Note.kt
package com.codingeasily.studentdiary.data
import androidx.room.Entity
import androidx.room.PrimaryKey
/** One row of the "notes" table. */
@Entity(tableName = "notes")
data class Note(
@PrimaryKey(autoGenerate = true) val id: Long = 0,
val title: String,
val content: String = "",
val subject: String = "General",
val priority: Int = PRIORITY_MEDIUM, // 0 = low, 1 = medium, 2 = high
val dueAt: Long? = null, // reminder time in millis, null = no reminder
val isDone: Boolean = false,
val isFavorite: Boolean = false,
val imagePath: String? = null, // photo copied into app storage
val createdAt: Long = System.currentTimeMillis()
) {
companion object {
const val PRIORITY_LOW = 0
const val PRIORITY_MEDIUM = 1
const val PRIORITY_HIGH = 2
}
}
/** Result of a GROUP BY query: how many notes each subject has. */
data class SubjectCount(val subject: String, val total: Int)SQL – Room की बनाई table
-- What Room creates for the Note entity CREATE TABLE notes ( id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, title TEXT NOT NULL, content TEXT NOT NULL, subject TEXT NOT NULL, priority INTEGER NOT NULL, dueAt INTEGER, -- Long? → nullable isDone INTEGER NOT NULL, -- Boolean → 0/1 isFavorite INTEGER NOT NULL, imagePath TEXT, createdAt INTEGER NOT NULL );
Step 3: DAO = queries
Kotlin – data/NoteDao.kt
package com.codingeasily.studentdiary.data
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.Query
import androidx.room.Update
import kotlinx.coroutines.flow.Flow
/** All SQL for the notes table lives here. Room generates the implementation. */
@Dao
interface NoteDao {
// Flow = the UI is updated automatically whenever the table changes
@Query("SELECT * FROM notes ORDER BY isDone ASC, priority DESC, createdAt DESC")
fun getAll(): Flow<List<Note>>
@Query("SELECT * FROM notes WHERE isDone = :done ORDER BY priority DESC, createdAt DESC")
fun getByDone(done: Boolean): Flow<List<Note>>
@Query("SELECT * FROM notes WHERE isFavorite = 1 ORDER BY createdAt DESC")
fun getFavorites(): Flow<List<Note>>
@Query("SELECT * FROM notes WHERE subject = :subject ORDER BY createdAt DESC")
fun getBySubject(subject: String): Flow<List<Note>>
@Query("SELECT * FROM notes WHERE title LIKE '%' || :q || '%' OR content LIKE '%' || :q || '%' ORDER BY createdAt DESC")
fun search(q: String): Flow<List<Note>>
@Query("SELECT subject, COUNT(*) AS total FROM notes GROUP BY subject ORDER BY total DESC")
fun subjectCounts(): Flow<List<SubjectCount>>
@Query("SELECT COUNT(*) FROM notes")
fun countAll(): Flow<Int>
@Query("SELECT COUNT(*) FROM notes WHERE isDone = 1")
fun countDone(): Flow<Int>
@Query("SELECT * FROM notes WHERE id = :id")
suspend fun getById(id: Long): Note?
@Insert
suspend fun insert(note: Note): Long
@Update
suspend fun update(note: Note)
@Delete
suspend fun delete(note: Note)
@Query("DELETE FROM notes WHERE isDone = 1")
suspend fun deleteDone(): Int
}| Return type | मतलब |
|---|---|
Flow<List<Note>> | Observed: हर बदलाव पर फिर emit (suspend नहीं) |
suspend fun … : Note? | Coroutine के अंदर एक बार पढ़ना |
suspend fun insert(…): Long | नई row की id लौटाता है |
suspend fun deleteDone(): Int | Delete हुई rows की संख्या |
Step 4: Database class
Kotlin – data/AppDatabase.kt
package com.codingeasily.studentdiary.data
import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
@Database(entities = [Note::class], version = 1, exportSchema = false)
abstract class AppDatabase : RoomDatabase() {
abstract fun noteDao(): NoteDao
companion object {
@Volatile private var INSTANCE: AppDatabase? = null
/** Singleton: the whole app shares one database connection. */
fun get(context: Context): AppDatabase =
INSTANCE ?: synchronized(this) {
INSTANCE ?: Room.databaseBuilder(
context.applicationContext,
AppDatabase::class.java,
"student_diary.db"
).build().also { INSTANCE = it }
}
}
}Step 5: Repository
Kotlin – data/NoteRepository.kt
package com.codingeasily.studentdiary.data
import kotlinx.coroutines.flow.Flow
/**
* The single source of truth for notes. ViewModels talk to the repository,
* never directly to the DAO. Later you could add a network source here.
*/
class NoteRepository(private val dao: NoteDao) {
val allNotes: Flow<List<Note>> = dao.getAll()
val pendingNotes: Flow<List<Note>> = dao.getByDone(false)
val doneNotes: Flow<List<Note>> = dao.getByDone(true)
val favorites: Flow<List<Note>> = dao.getFavorites()
val subjectCounts: Flow<List<SubjectCount>> = dao.subjectCounts()
val countAll: Flow<Int> = dao.countAll()
val countDone: Flow<Int> = dao.countDone()
fun bySubject(subject: String) = dao.getBySubject(subject)
fun search(q: String) = dao.search(q)
suspend fun get(id: Long) = dao.getById(id)
suspend fun save(note: Note): Long =
if (note.id == 0L) dao.insert(note) else { dao.update(note); note.id }
suspend fun delete(note: Note) = dao.delete(note)
suspend fun deleteDone() = dao.deleteDone()
}UI से use करना
Kotlin
// Quick test inside an Activity (later this moves into a ViewModel)
val dao = AppDatabase.get(this).noteDao()
lifecycleScope.launch {
dao.insert(Note(title = "Maths homework", subject = "Maths", priority = Note.PRIORITY_HIGH))
}
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
dao.getAll().collect { notes -> // runs again after every change
adapter.submitList(notes)
}
}
}अपना data देखें: App Inspection
App चलाइए, फिर View → Tool Windows → App Inspection → Database Inspector खोलिए। notes table browse कर सकते हैं और live SQL queries भी चला सकते हैं।
Table बदलना (migrations)
Kotlin
// Version 2 adds a "color" column to notes
@Database(entities = [Note::class], version = 2, exportSchema = false)
abstract class AppDatabase : RoomDatabase() { /* … */ }
val MIGRATION_1_2 = object : Migration(1, 2) {
override fun migrate(db: SupportSQLiteDatabase) {
db.execSQL("ALTER TABLE notes ADD COLUMN color INTEGER NOT NULL DEFAULT 0")
}
}
Room.databaseBuilder(context, AppDatabase::class.java, "student_diary.db")
.addMigrations(MIGRATION_1_2)
.build()
// While developing only (deletes all data!): .fallbackToDestructiveMigration()आम गलतियाँ
- Main thread पर non-suspend DAO function → "Cannot access database on the main thread"।
versionबढ़ाए बिना Entity बदलना → crash "Room cannot verify the data integrity"।- हर screen में नया database instance → memory बर्बाद; singleton use करें।
- @Query में column names के typos → Room compile error दिखाता है (यह अच्छी बात है — पढ़ें)।
सारांश
- @Entity (table) + @Dao (queries) + @Database (singleton) + Repository।
- अपने आप update होने वाली lists के लिए Flow; एक बार read/write के लिए suspend।
- Schema बदले तो version बढ़ाएँ और Migration जोड़ें।
✅ Practice
Expense(id, title, amount, category, date) table बनाइए जिसमें सारे expenses (Flow), हर category का total (GROUP BY) और delete की queries हों। List RecyclerView में दिखाइए जो हर insert के बाद update हो।
अक्सर पूछे जाने वाले प्रश्न (FAQ)
Room queries main thread पर क्यों नहीं चला सकते?
Database काम में समय लग सकता है और UI अटक जाएगा। Main-thread access पर Room exception देता है। Coroutines में suspend functions use करें या Flow लौटाएँ।
DAO से Flow लौटाने का क्या फ़ायदा?
Table बदलते ही Room query दोबारा चलाकर नई list भेजता है, इसलिए insert, update या delete के बाद RecyclerView अपने आप update होता है।
Room के लिए KSP या kapt?
KSP use करें। यह तेज़ है और Kotlin के साथ Room के लिए recommended processor है; kapt maintenance mode में है।
