सामग्री पर जाएं
सभी पोस्ट

2026 में Room डेटाबेस माइग्रेशन: बिना एक भी रो खोए स्कीमा बदलाव शिप करना

Android पर Room डेटाबेस माइग्रेशन के लिए एक व्यावहारिक गाइड — AutoMigration, हाथ से लिखे Migration ऑब्जेक्ट्स, और अपने यूज़र्स के बग पाने से पहले माइग्रेशन को कैसे टेस्ट करें।

MFKAPPS 5 मिनट पढ़ना

हर लोकल-फर्स्ट ऐप को आखिरकार एक ऐसे स्कीमा की ज़रूरत पड़ती है जिसके साथ वह वर्शन 1 में शिप नहीं हुआ था। एक नया कॉलम, एक नाम बदली हुई टेबल, एक फ़ील्ड जो पहले String थी और अब उसे Int बनना है। जिस पल ऐसा होता है, आप एक लापरवाह एनोटेशन की दूरी पर होते हैं कि अगली अपडेट में हर यूज़र का डेटा मिट जाए। Room माइग्रेशन इसी को रोकने के लिए बने हैं, और इनसे होने वाली ज़्यादातर तकलीफ उन हिस्सों को छोड़ देने से आती है जो वैकल्पिक लगते हैं लेकिन होते नहीं।

जाल: fallbackToDestructiveMigration()

जिस पल आपका @Database वर्शन नंबर बिना किसी मिलते-जुलते माइग्रेशन पथ के बढ़ता है, Room एक IllegalStateException फेंकता है। हर Stack Overflow जवाब में दिखने वाला समाधान एक लाइन का है:

Room.databaseBuilder(context, AppDatabase::class.java, "app.db")
    .fallbackToDestructiveMigration()
    .build()

यह कंपाइल होता है, शिप होता है, और बिल्कुल ठीक काम करता है — डीबग बिल्ड में, आपकी अपनी डिवाइस पर, जहाँ टेस्ट डेटा खोने से आपको फ़र्क़ नहीं पड़ता। प्रोडक्शन में इसका मतलब है: अगली बार जब आप स्कीमा वर्शन बढ़ाएँगे, Room हर टेबल को मिटा देगा और उन्हें खाली दोबारा बना देगा। Granyn के लिए इसका मतलब है कि किसी की दो साल की बजट एंट्रीज़ एक ऐसी अपडेट में चुपचाप गायब हो जाएँ जो उसने माँगी भी नहीं थी। न कोई डायलॉग, न कोई चेतावनी, न कोई अनडू। यह बस अगली बार ऐप खुलने पर चुपचाप हो जाता है। अगर यह लाइन किसी भी डीबग-बाहर की कॉन्फ़िगरेशन में मौजूद है, तो इसे सुविधा नहीं, बग मानें।

AutoMigration जितना लोग मानते हैं उससे ज़्यादा कवर करता है

आम मामलों के लिए — डिफ़ॉल्ट वैल्यू के साथ कॉलम जोड़ना, टेबल जोड़ना या हटाना, कॉलम का नाम बदलना — Room का @AutoMigration दो स्कीमा स्नैपशॉट्स से आपके लिए माइग्रेशन जनरेट कर सकता है:

@Database(
    version = 2,
    entities = [Entry::class],
    autoMigrations = [
        AutoMigration(from = 1, to = 2)
    ]
)
abstract class AppDatabase : RoomDatabase()

यह तभी काम करता है जब आपने स्कीमा एक्सपोर्ट चालू किया हो (Gradle कॉन्फ़िगरेशन में room.schemaLocation), ताकि Room के पास तुलना के लिए हर वर्शन का JSON स्नैपशॉट हो। यह सेटअप छोड़ दें तो AutoMigration के पास तुलना करने के लिए कुछ नहीं होता, और आपको यह बिल्ड टाइम पर पता चलेगा, रात 2 बजे किसी क्रैश रिपोर्ट में नहीं। नाम बदले या मिटाए गए कॉलम के लिए, आपको Room को पुराने और नए नाम बताने वाली एक छोटी @RenameColumn या @DeleteColumn स्पेक क्लास भी चाहिए — यह सिर्फ़ एक diff से मंशा नहीं समझ सकता।

जहाँ AutoMigration काम आना बंद कर देता है

जिस भी चीज़ को मौजूदा डेटा को दोबारा आकार देने की ज़रूरत हो — एक कॉलम को दो में बाँटना, स्टोर की गई String राशि को सेंट्स में Int में बदलना, एक नई अनिवार्य फ़ील्ड को दूसरी रो से भरना — वह AutoMigration के दायरे से बाहर है। यह कोई सीमा नहीं जिसे टाला जाए; यह संकेत है कि आपको असली SQL वाला असली Migration ऑब्जेक्ट चाहिए।

माइग्रेशन हाथ से लिखना

हाथ से लिखा Migration बस एक वर्शन जोड़ा और SQL का एक ब्लॉक है जिसकी ज़िम्मेदारी आपकी है:

val MIGRATION_2_3 = object : Migration(2, 3) {
    override fun migrate(db: SupportSQLiteDatabase) {
        db.execSQL(
            "ALTER TABLE entries ADD COLUMN currency TEXT NOT NULL DEFAULT 'USD'"
        )
        db.execSQL(
            "UPDATE entries SET currency = (SELECT default_currency FROM user_prefs LIMIT 1)"
        )
    }
}

इसे डेटाबेस बिल्डर पर .addMigrations(MIGRATION_2_3) के ज़रिए, अगर कोई AutoMigration हों तो उनके साथ रजिस्टर करें। SQLite का ALTER TABLE सीमित है — न कॉलम मिटाना संभव है, न किसी कॉलम का टाइप वहीं बदलना — इसलिए कॉलम जोड़ने से आगे की कोई भी चीज़ आमतौर पर वही क्लासिक तीन-चरण नृत्य माँगती है: चाहा गया आकार लिए एक नई टेबल बनाएँ, INSERT INTO ... SELECT से डेटा कॉपी करें, पुरानी टेबल मिटाएँ, नई का नाम बदलें। यह उससे ज़्यादा SQL है जितना आप लिखना चाहेंगे, और यही वह SQL है जिसका सही होना ज़रूरी है, क्योंकि यह हर डिवाइस पर उसके अगले लॉन्च पर एक बार, चुपचाप चलता है।

माइग्रेशन को टेस्ट करें, सिर्फ़ अंतिम स्कीमा को नहीं

यहाँ सबसे ज़्यादा बग पैदा करने वाली ग़लती SQL में नहीं है — यह है माइग्रेशन को कभी एक असली, पुरानी हो चुकी डेटाबेस के खिलाफ़ न चलाना। Room का MigrationTestHelper बिल्कुल इसी के लिए है:

@get:Rule
val helper = MigrationTestHelper(
    InstrumentationRegistry.getInstrumentation(),
    AppDatabase::class.java
)

@Test
fun migrate2To3_preservesExistingRows() {
    helper.createDatabase(TEST_DB, 2).apply {
        execSQL("INSERT INTO entries (id, amount) VALUES (1, 4200)")
        close()
    }
    helper.runMigrationsAndValidate(TEST_DB, 3, true, MIGRATION_2_3)
}

यह वर्शन-2 की एक डेटाबेस बनाता है, इसे उसी तरह डेटा से भरता है जैसे किसी असली यूज़र की डिवाइस दिखेगी, फिर आपके माइग्रेशन को उसके खिलाफ़ चलाता है और जाँचता है कि नतीजे का स्कीमा वही है जो Room अपेक्षा करता है। यह उन दो फ़ेल्योर मोड्स को पकड़ता है जो असल में प्रोडक्शन में होते हैं: एक माइग्रेशन जो खाली डेटाबेस पर काम करता है लेकिन रो वाली डेटाबेस पर फेल हो जाता है, और एक माइग्रेशन जिसका SQL तो ठीक है लेकिन जिसका नतीजा स्कीमा आपकी एंटिटी परिभाषाओं से मेल नहीं खाता (Room इसे कॉलम क्रम और डिफ़ॉल्ट वैल्यूज़ तक बहुत सख़्ती से जाँचता है)।

चेकलिस्ट

किसी भी स्कीमा वर्शन बढ़ोतरी को शिप करने से पहले: स्कीमा एक्सपोर्ट चालू है, हर वर्शन छलांग के पास या तो एक AutoMigration है या एक रजिस्टर किया हुआ हाथ से लिखा Migration, fallbackToDestructiveMigration() टेस्ट कोड के अलावा कहीं नहीं दिखता, और कम से कम एक MigrationTestHelper टेस्ट असली रो भरता है और यह जाँचता है कि वे बची रहती हैं। इनमें से कुछ भी रोमांचक काम नहीं है। लेकिन यही फ़र्क़ है एक स्कीमा बदलाव के बीच जिसे कोई नोटिस नहीं करता, और रिलीज़ के अगली सुबह “मेरा डेटा गायब हो गया” से भरे सपोर्ट इनबॉक्स के बीच।

// संबंधित पठन

जर्नल से और भी

MFKAPPS 6 मिनट पढ़ना

2026 में Jetpack Glance: एक ऐसा होम-स्क्रीन विजेट बनाना जो आपके डेटा के बारे में कभी झूठ न बोले

Jetpack Glance विजेट्स के लिए एक व्यावहारिक गाइड — स्टेट, क्लिक एक्शन्स, और वह अपडेट-कोटा जाल जो विजेट्स को पुराना डेटा दिखाने पर मजबूर कर देता है।

#android #engineering #kotlin
MFKAPPS 5 मिनट पढ़ना

OldSchool का एडहेरेंस कैलेंडर बनाना: दवा के टैप्स को भरोसेमंद दिन में बदलना

एक महीने का एडहेरेंस कैलेंडर हर दिन के लिए बस एक रंगीन डॉट जैसा दिखता है। जानिए इसके पीछे का इवेंट मॉडल, दिन के स्टेटस के नियम, और समय-पालन का गणित, वह भी डिवाइस पर।

#android #engineering #room
MFKAPPS 5 मिनट पढ़ना

Subly बनाना: सब्सक्रिप्शन रिन्यूअल तारीखों के पीछे का कैलेंडर गणित

किसी सब्सक्रिप्शन की अगली चार्ज तारीख का अनुमान लगाना तब तक साधारण लगता है जब तक आप महीने के अंत की बिलिंग, लीप ईयर और ट्रायल कन्वर्शन से नहीं टकराते। यहाँ बताया गया है कि Subly इसे डिवाइस पर ही कैसे सही करता है।

#android #engineering #subscriptions