Android Room with a View - Kotlin

Architecture Components का मकसद, ऐप्लिकेशन के आर्किटेक्चर के बारे में दिशा-निर्देश देना है. साथ ही, इसमें लाइफ़साइकल मैनेजमेंट और डेटा परसिस्टेंस जैसे सामान्य टास्क के लिए लाइब्रेरी भी शामिल हैं. आर्किटेक्चर कॉम्पोनेंट की मदद से, अपने ऐप्लिकेशन को इस तरह से स्ट्रक्चर किया जा सकता है कि वह मज़बूत हो, उसकी जांच की जा सके, और उसे मैनेज किया जा सके. साथ ही, इसमें छोटे-मोटे बदलाव वाले कोड कम हों. आर्किटेक्चर कॉम्पोनेंट लाइब्रेरी, Android Jetpack का हिस्सा हैं.

यह कोडलैब का Kotlin वर्शन है. Java प्रोग्रामिंग भाषा में उपलब्ध वर्शन यहां देखा जा सकता है.

अगर आपको इस कोडलैब को पूरा करते समय कोई समस्या (कोड में गड़बड़ियां, व्याकरण से जुड़ी गलतियां, शब्दों का सही इस्तेमाल न होना वगैरह) आती है, तो कृपया कोडलैब के सबसे नीचे बाएं कोने में मौजूद गड़बड़ी की शिकायत करें लिंक के ज़रिए समस्या की शिकायत करें.

ज़रूरी शर्तें

आपको Kotlin, ऑब्जेक्ट ओरिएंटेड डिज़ाइन के कॉन्सेप्ट, और Android डेवलपमेंट की बुनियादी बातों के बारे में पता होना चाहिए. खास तौर पर:

इसके अलावा, सॉफ़्टवेयर आर्किटेक्चरल पैटर्न के बारे में जानकारी होना भी ज़रूरी है. ये पैटर्न, डेटा को यूज़र इंटरफ़ेस से अलग करते हैं. जैसे, MVP या MVC. इस कोडलैब में, ऐप्लिकेशन के आर्किटेक्चर की गाइड में बताए गए आर्किटेक्चर को लागू किया गया है.

यह कोडलैब, Android के आर्किटेक्चर कॉम्पोनेंट पर आधारित है. आपको विषय से हटकर कॉन्सेप्ट और कोड दिए जाते हैं, ताकि आप उन्हें आसानी से कॉपी करके चिपका सकें.

अगर आपको Kotlin के बारे में जानकारी नहीं है, तो इस कोडलैब का एक वर्शन Java प्रोग्रामिंग लैंग्वेज में यहां दिया गया है.

आपको क्या करना होगा

इस कोडलैब में, आपको Architecture Components Room, ViewModel, और LiveData का इस्तेमाल करके, ऐप्लिकेशन को डिज़ाइन और तैयार करने का तरीका मिलेगा. साथ ही, आपको ऐसा ऐप्लिकेशन बनाने का तरीका मिलेगा जो ये काम करता हो:

  • Android आर्किटेक्चर कॉम्पोनेंट का इस्तेमाल करके, सुझाए गए आर्किटेक्चर को लागू करता है.
  • यह डेटा पाने और सेव करने के लिए, डेटाबेस के साथ काम करता है. साथ ही, डेटाबेस में कुछ शब्द पहले से भर देता है.
  • MainActivity में मौजूद RecyclerView के सभी शब्दों को दिखाता है.
  • जब उपयोगकर्ता, + बटन पर टैप करता है, तब यह दूसरी गतिविधि खोलता है. जब उपयोगकर्ता कोई शब्द डालता है, तो उस शब्द को डेटाबेस और सूची में जोड़ता है.

यह ऐप्लिकेशन बहुत आसान है, लेकिन इसमें इतनी सुविधाएँ हैं कि इसे टेंप्लेट के तौर पर इस्तेमाल करके, अपनी ज़रूरत के हिसाब से बनाया जा सकता है. यहां इसकी झलक दी गई है:

आपको किन चीज़ों की ज़रूरत होगी

  • Android Studio 3.0 या इसके बाद का वर्शन और इसे इस्तेमाल करने का तरीका पता होना चाहिए. पक्का करें कि Android Studio, एसडीके, और Gradle अपडेट हो.
  • Android डिवाइस या एम्युलेटर.

इस कोडलैब में, पूरा ऐप्लिकेशन बनाने के लिए ज़रूरी कोड दिया गया है.

आर्किटेक्चर कॉम्पोनेंट का इस्तेमाल करने और सुझाए गए आर्किटेक्चर को लागू करने के लिए, कई चरण पूरे करने होते हैं. सबसे ज़रूरी यह है कि आप एक मेंटल मॉडल बनाएं, ताकि आपको यह पता चल सके कि क्या हो रहा है. साथ ही, आपको यह भी पता चल सके कि अलग-अलग कॉम्पोनेंट एक साथ कैसे काम करते हैं और डेटा कैसे फ़्लो होता है. इस कोडलैब को पूरा करते समय, सिर्फ़ कोड को कॉपी करके चिपकाने के बजाय, इसे समझने की कोशिश करें.

शब्दावली के बारे में बताने के लिए, यहां आर्किटेक्चर कॉम्पोनेंट और उनके एक साथ काम करने के तरीके के बारे में कम शब्दों में जानकारी दी गई है. ध्यान दें कि यह कोडलैब, कॉम्पोनेंट के सबसेट पर फ़ोकस करता है. जैसे, LiveData, ViewModel, और Room. हर कॉम्पोनेंट के बारे में ज़्यादा जानकारी, उसे इस्तेमाल करते समय मिलती है.

इस डायग्राम में, आर्किटेक्चर का बेसिक फ़ॉर्म दिखाया गया है:

इकाई: यह एनोटेट की गई क्लास होती है. Room के साथ काम करते समय, यह डेटाबेस टेबल के बारे में बताती है.

SQLite डेटाबेस: डिवाइस के स्टोरेज में. Room परसिस्टेंस लाइब्रेरी, आपके लिए इस डेटाबेस को बनाती है और इसे मैनेज करती है.

डीएओ: डेटा ऐक्सेस ऑब्जेक्ट. एसक्यूएल क्वेरी को फ़ंक्शन से मैप करना. DAO का इस्तेमाल करने पर, आपको सिर्फ़ तरीकों को कॉल करना होता है. बाकी का काम Room करता है.

Room डेटाबेस: यह डेटाबेस से जुड़े काम को आसान बनाता है. साथ ही, यह SQLite डेटाबेस को ऐक्सेस करने के लिए एक ऐक्सेस पॉइंट के तौर पर काम करता है (SQLiteOpenHelper) को छिपाता है. Room डेटाबेस, SQLite डेटाबेस को क्वेरी जारी करने के लिए DAO का इस्तेमाल करता है.

रिपॉज़िटरी: यह एक क्लास होती है, जिसे बनाया जाता है. इसका इस्तेमाल मुख्य रूप से कई डेटा सोर्स को मैनेज करने के लिए किया जाता है.

ViewModel: यह रिपॉज़िटरी (डेटा) और यूज़र इंटरफ़ेस (यूआई) के बीच कम्यूनिकेशन सेंटर के तौर पर काम करता है. यूज़र इंटरफ़ेस (यूआई) को अब डेटा के सोर्स के बारे में चिंता करने की ज़रूरत नहीं है. ViewModel इंस्टेंस, ऐक्टिविटी/फ़्रैगमेंट के फिर से बनाए जाने पर भी बने रहते हैं.

LiveData: यह डेटा होल्डर क्लास है, जिसे ऑब्ज़र्व किया जा सकता है. यह हमेशा डेटा के सबसे नए वर्शन को सेव/कैश करता है. साथ ही, डेटा में बदलाव होने पर, इसकी सूचना ऑब्ज़र्वर को देता है. LiveData को लाइफ़साइकल की जानकारी होती है. यूज़र इंटरफ़ेस (यूआई) कॉम्पोनेंट सिर्फ़ काम का डेटा देखते हैं. वे डेटा देखना बंद या फिर से शुरू नहीं करते. LiveData इन सभी को अपने-आप मैनेज करता है, क्योंकि इसे ऑब्ज़र्व करते समय लाइफ़साइकल के स्टेटस में होने वाले ज़रूरी बदलावों के बारे में पता होता है.

RoomWordSample के आर्किटेक्चर के बारे में खास जानकारी

यहां दिए गए डायग्राम में, ऐप्लिकेशन के सभी कॉम्पोनेंट दिखाए गए हैं. चारों ओर से बंद किए गए हर बॉक्स (SQLite डेटाबेस को छोड़कर) में एक क्लास मौजूद है, जिसे आपको बनाना होगा.

  1. Android Studio खोलें और Start a new Android Studio project पर क्लिक करें.
  2. 'नया प्रोजेक्ट बनाएं' विंडो में, बिना ऐक्टिविटी वाला टेंप्लेट चुनें और आगे बढ़ें पर क्लिक करें.
  3. अगली स्क्रीन पर, ऐप्लिकेशन का नाम RoomWordSample रखें और Finish पर क्लिक करें.

इसके बाद, आपको अपनी Gradle फ़ाइलों में कॉम्पोनेंट लाइब्रेरी जोड़नी होंगी.

  1. Android Studio में, प्रोजेक्ट टैब पर क्लिक करें और Gradle Scripts फ़ोल्डर को बड़ा करें.

build.gradle (Module: app) खोलें.

  1. kapt annotation processor Kotlin प्लग इन लागू करें. इसके लिए, इसे अपनी build.gradle (Module: app) फ़ाइल में सबसे ऊपर तय किए गए अन्य प्लग इन के बाद जोड़ें.
apply plugin: 'kotlin-kapt'
  1. एटॉमिक फ़ंक्शन मॉड्यूल को पैकेज से बाहर रखने और चेतावनियों को रोकने के लिए, android ब्लॉक के अंदर packagingOptions ब्लॉक जोड़ें.
android {
    // other configuration (buildTypes, defaultConfig, etc.)

    packagingOptions {
        exclude 'META-INF/atomicfu.kotlin_module'
    }
}
  1. dependencies ब्लॉक के आखिर में यह कोड जोड़ें.
// Room components
implementation "androidx.room:room-runtime:$rootProject.roomVersion"
kapt "androidx.room:room-compiler:$rootProject.roomVersion"
androidTestImplementation "androidx.room:room-testing:$rootProject.roomVersion"

// Lifecycle components
implementation "androidx.lifecycle:lifecycle-extensions:$rootProject.archLifecycleVersion"
kapt "androidx.lifecycle:lifecycle-compiler:$rootProject.archLifecycleVersion"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$rootProject.archLifecycleVersion"

// Kotlin components
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
api "org.jetbrains.kotlinx:kotlinx-coroutines-core:$rootProject.coroutines"
api "org.jetbrains.kotlinx:kotlinx-coroutines-android:$rootProject.coroutines"

// Material design
implementation "com.google.android.material:material:$rootProject.materialVersion"

// Testing
testImplementation 'junit:junit:4.12'
androidTestImplementation "androidx.arch.core:core-testing:$rootProject.coreTestingVersion"
  1. अपनी build.gradle (Project: RoomWordsSample) फ़ाइल में, फ़ाइल के आखिर में वर्शन नंबर जोड़ें. इसके लिए, नीचे दिया गया कोड इस्तेमाल करें.
ext {
    roomVersion = '2.2.5'
    archLifecycleVersion = '2.2.0'
    coreTestingVersion = '2.1.0'
    materialVersion = '1.1.0'
    coroutines = '1.3.4'
}

इस ऐप्लिकेशन का डेटा शब्द हैं. इन वैल्यू को सेव करने के लिए, आपको एक सामान्य टेबल की ज़रूरत होगी:

Room की मदद से, इकाई के ज़रिए टेबल बनाई जा सकती हैं. चलिए, अब इसे शुरू करते हैं.

  1. Word नाम की नई Kotlin क्लास फ़ाइल बनाएं. इसमें Word data class शामिल हो.
    यह क्लास, आपके शब्दों के लिए Entity (जो SQLite टेबल को दिखाता है) के बारे में बताएगी. क्लास की हर प्रॉपर्टी, टेबल में मौजूद एक कॉलम को दिखाती है. Room इन प्रॉपर्टी का इस्तेमाल करके, टेबल बनाएगा. साथ ही, डेटाबेस की लाइनों से ऑब्जेक्ट इंस्टैंटिएट करेगा.

यहां कोड दिया गया है:

data class Word(val word: String)

Word क्लास को रूम डेटाबेस के लिए बेहतर बनाने के लिए, आपको उसके एनोटेशन देने पड़ेंगे. एनोटेशन से पता चलता है कि इस क्लास का हर हिस्सा, डेटाबेस की किसी एंट्री से कैसे जुड़ा है. Room इस जानकारी का इस्तेमाल करके कोड जनरेट करता है.

अगर आपको एनोटेशन खुद टाइप करने हैं (चिपकाने के बजाय), तो Android Studio एनोटेशन क्लास को अपने-आप इंपोर्ट कर देगा.

  1. इस कोड में दिखाए गए एनोटेशन के साथ अपनी Word क्लास अपडेट करें:
@Entity(tableName = "word_table")
class Word(@PrimaryKey @ColumnInfo(name = "word") val word: String)

आइए, देखते हैं कि ये एनोटेशन क्या करते हैं:

  • @Entity(tableName = "word_table")
    हर @Entity क्लास, SQLite टेबल को दिखाती है. अपनी क्लास के एलान को एनोटेट करें, ताकि यह पता चल सके कि यह एक इकाई है. अगर आपको टेबल का नाम, क्लास के नाम से अलग रखना है, तो टेबल का नाम तय किया जा सकता है. इससे टेबल का नाम "word_table" हो जाता है.
  • @PrimaryKey
    हर इकाई के लिए, प्राइमरी कुंजी होना ज़रूरी है. आसान शब्दों में कहें, तो हर शब्द अपनी प्राइमरी कुंजी के तौर पर काम करता है.
  • @ColumnInfo(name = "word")
    अगर आपको टेबल में कॉलम का नाम, मेंबर वैरिएबल के नाम से अलग रखना है, तो इस विकल्प का इस्तेमाल करें. इससे कॉलम का नाम "word" हो जाता है.
  • डेटाबेस में सेव की गई हर प्रॉपर्टी को सार्वजनिक तौर पर देखा जा सकता है. Kotlin में, यह डिफ़ॉल्ट सेटिंग होती है.

आपको रूम पैकेज की खास जानकारी वाले रेफ़रंस में, एनोटेशन की पूरी सूची मिल सकती है.

डीएओ क्या है?

DAO (डेटा ऐक्सेस ऑब्जेक्ट) में, एसक्यूएल क्वेरी तय की जाती हैं और उन्हें मैथड कॉल से जोड़ा जाता है. कंपाइलर, SQL की जांच करता है और सामान्य क्वेरी के लिए, सुविधा से जुड़े एनोटेशन से क्वेरी जनरेट करता है. जैसे, @Insert. Room, DAO का इस्तेमाल करके आपके कोड के लिए एक क्लीन एपीआई बनाता है.

डीएओ एक इंटरफ़ेस या ऐब्सट्रैक्ट क्लास होना चाहिए.

डिफ़ॉल्ट रूप से, सभी क्वेरी को अलग थ्रेड पर एक्ज़ीक्यूट किया जाना चाहिए.

Room, कोरूटीन के साथ काम करता है. इससे आपकी क्वेरी को suspend मॉडिफ़ायर के साथ एनोटेट किया जा सकता है. इसके बाद, उन्हें किसी कोरूटीन या अन्य सस्पेंशन फ़ंक्शन से कॉल किया जा सकता है.

डीएओ लागू करना

आइए, एक ऐसा DAO लिखें जो इनके लिए क्वेरी उपलब्ध कराता हो:

  • सभी शब्दों को वर्णमाला के क्रम में लगाना
  • कोई शब्द डालना
  • सभी शब्द मिटाए जा रहे हैं
  1. WordDao नाम की नई Kotlin क्लास फ़ाइल बनाएं.
  2. यहां दिए गए कोड को कॉपी करके, WordDao में चिपकाएं. साथ ही, इसे कंपाइल करने के लिए, इंपोर्ट में ज़रूरी बदलाव करें.
@Dao
interface WordDao {

    @Query("SELECT * from word_table ORDER BY word ASC")
    fun getAlphabetizedWords(): List<Word>

    @Insert(onConflict = OnConflictStrategy.IGNORE)
    suspend fun insert(word: Word)

    @Query("DELETE FROM word_table")
    suspend fun deleteAll()
}

आइए, इसके बारे में जानते हैं:

  • WordDao एक इंटरफ़ेस है. डीएओ, इंटरफ़ेस या ऐब्सट्रैक्ट क्लास होने चाहिए.
  • @Dao एनोटेशन से, इसे Room के लिए डीएओ क्लास के तौर पर पहचाना जाता है.
  • suspend fun insert(word: Word) : यह एक सस्पेंड फ़ंक्शन का एलान करता है, ताकि एक शब्द डाला जा सके.
  • @Insert एनोटेशन, DAO के तरीके का एक खास एनोटेशन है. इसमें आपको कोई एसक्यूएल देने की ज़रूरत नहीं होती! (लाइनों को मिटाने और अपडेट करने के लिए, @Delete और @Update एनोटेशन भी मौजूद हैं. हालांकि, इस ऐप्लिकेशन में इनका इस्तेमाल नहीं किया जा रहा है.)
  • onConflict = OnConflictStrategy.IGNORE: onConflict के लिए चुनी गई रणनीति, सूची में पहले से मौजूद किसी शब्द के बिलकुल समान होने पर, नए शब्द को अनदेखा कर देती है. उपलब्ध टकराव की रणनीतियों के बारे में ज़्यादा जानने के लिए, दस्तावेज़ देखें.
  • suspend fun deleteAll(): यह सभी शब्दों को मिटाने के लिए, सस्पेंड फ़ंक्शन का एलान करता है.
  • एक से ज़्यादा इकाइयों को मिटाने के लिए, कोई सुविधा एनोटेशन नहीं है. इसलिए, इसे सामान्य @Query के साथ एनोटेट किया गया है.
  • @Query("DELETE FROM word_table"): @Query के लिए, आपको एनोटेशन में स्ट्रिंग पैरामीटर के तौर पर एसक्यूएल क्वेरी देनी होगी. इससे, जटिल रीड क्वेरी और अन्य कार्रवाइयां की जा सकती हैं.
  • fun getAlphabetizedWords(): List<Word>: यह सभी शब्दों को पाने का एक तरीका है. इससे Words का List मिलता है.
  • @Query("SELECT * from word_table ORDER BY word ASC"): यह क्वेरी, शब्दों की सूची को बढ़ते क्रम में दिखाती है.

डेटा में बदलाव होने पर, आम तौर पर आपको कोई कार्रवाई करनी होती है. जैसे, यूज़र इंटरफ़ेस (यूआई) में अपडेट किया गया डेटा दिखाना. इसका मतलब है कि आपको डेटा पर नज़र रखनी होगी, ताकि बदलाव होने पर आप कार्रवाई कर सकें.

डेटा को स्टोर करने के तरीके के आधार पर, यह मुश्किल हो सकता है. आपके ऐप्लिकेशन के कई कॉम्पोनेंट में डेटा में होने वाले बदलावों को मॉनिटर करने से, कॉम्पोनेंट के बीच साफ़ तौर पर और मुश्किल डिपेंडेंसी पाथ बन सकते हैं. इससे, टेस्टिंग और डीबग करने के साथ-साथ अन्य काम करने में भी मुश्किल होती है.

LiveData, डेटा ऑब्ज़र्वेशन के लिए लाइफ़साइकल लाइब्रेरी क्लास है. इससे इस समस्या को हल किया जा सकता है. अपने तरीके के ब्यौरे में, LiveData टाइप की रिटर्न वैल्यू का इस्तेमाल करें. डेटाबेस अपडेट होने पर, Room LiveData को अपडेट करने के लिए ज़रूरी कोड जनरेट करता है.

WordDao में, getAlphabetizedWords() के तरीके के सिग्नेचर को बदलें, ताकि वापस लौटाया गया List<Word>, LiveData के साथ रैप हो जाए.

   @Query("SELECT * from word_table ORDER BY word ASC")
   fun getAlphabetizedWords(): LiveData<List<Word>>

इस कोडलैब में बाद में, MainActivity में मौजूद Observer की मदद से, डेटा में हुए बदलावों को ट्रैक किया जाता है.

रूम डेटाबेस क्या होता है?

  • Room, SQLite डेटाबेस के ऊपर एक डेटाबेस लेयर है.
  • Room, रोज़मर्रा के उन कामों को मैनेज करता है जिन्हें पहले SQLiteOpenHelper की मदद से मैनेज किया जाता था.
  • Room, अपने डेटाबेस में क्वेरी करने के लिए डीएओ का इस्तेमाल करता है.
  • डिफ़ॉल्ट रूप से, Room आपको मुख्य थ्रेड पर क्वेरी जारी करने की अनुमति नहीं देता है. ऐसा इसलिए, ताकि यूज़र इंटरफ़ेस (यूआई) की परफ़ॉर्मेंस खराब न हो. जब कमरे से जुड़ी क्वेरी LiveData दिखाती हैं, तब क्वेरी को बैकग्राउंड थ्रेड पर एसिंक्रोनस तरीके से अपने-आप चलाया जाता है.
  • Room, कंपाइल होने में लगने वाले समय के दौरान SQLite स्टेटमेंट की जांच करता है.

रूम डेटाबेस लागू करना

आपकी Room डेटाबेस क्लास ऐब्सट्रैक्ट होनी चाहिए और RoomDatabase से एक्सटेंड होनी चाहिए. आम तौर पर, पूरे ऐप्लिकेशन के लिए Room डेटाबेस का सिर्फ़ एक इंस्टेंस ज़रूरी होता है.

चलिए, अब एक बनाते हैं.

  1. WordRoomDatabase नाम की Kotlin क्लास फ़ाइल बनाएं और इसमें यह कोड जोड़ें:
// Annotates class to be a Room Database with a table (entity) of the Word class
@Database(entities = arrayOf(Word::class), version = 1, exportSchema = false)
public abstract class WordRoomDatabase : RoomDatabase() {

   abstract fun wordDao(): WordDao

   companion object {
        // Singleton prevents multiple instances of database opening at the
        // same time. 
        @Volatile
        private var INSTANCE: WordRoomDatabase? = null

        fun getDatabase(context: Context): WordRoomDatabase {
            val tempInstance = INSTANCE
            if (tempInstance != null) {
                return tempInstance
            }
            synchronized(this) {
                val instance = Room.databaseBuilder(
                        context.applicationContext,
                        WordRoomDatabase::class.java, 
                        "word_database"
                    ).build()
                INSTANCE = instance
                return instance
            }
        }
   }
}

आइए, कोड के बारे में जानते हैं:

  • Room के लिए डेटाबेस क्लास, abstract होनी चाहिए और RoomDatabase से एक्सटेंड होनी चाहिए
  • क्लास को Room डेटाबेस के तौर पर एनोटेट करने के लिए, @Database का इस्तेमाल करें. साथ ही, एनोटेशन पैरामीटर का इस्तेमाल करके, डेटाबेस में मौजूद इकाइयों का एलान करें और वर्शन नंबर सेट करें. हर इकाई, डेटाबेस में बनाई जाने वाली टेबल से मेल खाती है. डेटाबेस माइग्रेशन, इस कोडलैब के दायरे से बाहर है. इसलिए, हमने यहां exportSchema को फ़ॉल्स पर सेट किया है, ताकि बिल्ड की चेतावनी न मिले. किसी असली ऐप्लिकेशन में, आपको Room के लिए एक डायरेक्ट्री सेट करनी चाहिए, ताकि वह स्कीमा को एक्सपोर्ट कर सके. इससे, अपने वर्शन कंट्रोल सिस्टम में मौजूदा स्कीमा की जांच की जा सकती है.
  • डेटाबेस, हर @Dao के लिए ऐब्स्ट्रैक्ट "getter" तरीके से डीएओ दिखाता है.
  • हमने सिंगलटोन, WordRoomDatabase, को तय किया है, ताकि एक ही समय में डेटाबेस के कई इंस्टेंस न खुलें.
  • getDatabase सिंगलटन दिखाता है. पहली बार ऐक्सेस करने पर, यह डेटाबेस बनाएगा. इसके लिए, Room के डेटाबेस बिल्डर का इस्तेमाल करके, ऐप्लिकेशन कॉन्टेक्स्ट में WordRoomDatabase क्लास से RoomDatabase ऑब्जेक्ट बनाया जाएगा और उसका नाम "word_database" रखा जाएगा.

रिपॉज़िटरी क्या होती है?

रिपॉज़िटरी क्लास, कई डेटा सोर्स के ऐक्सेस को ऐब्स्ट्रैक्ट करती है. रिपॉज़िटरी, आर्किटेक्चर कॉम्पोनेंट लाइब्रेरी का हिस्सा नहीं है. हालांकि, कोड को अलग करने और आर्किटेक्चर के लिए, इसे सबसे सही तरीका माना जाता है. रिपॉज़िटरी क्लास, ऐप्लिकेशन के बाकी हिस्सों को डेटा ऐक्सेस करने के लिए एक साफ़ एपीआई उपलब्ध कराती है.

रिपॉज़िटरी का इस्तेमाल क्यों करें?

रिपॉज़िटरी, क्वेरी मैनेज करती है. साथ ही, आपको एक से ज़्यादा बैकएंड इस्तेमाल करने की अनुमति देती है. सबसे सामान्य उदाहरण में, रिपॉज़िटरी यह तय करने के लिए लॉजिक लागू करती है कि नेटवर्क से डेटा फ़ेच करना है या स्थानीय डेटाबेस में कैश मेमोरी में सेव किए गए नतीजों का इस्तेमाल करना है.

रिपॉज़िटरी लागू करना

WordRepository नाम की Kotlin क्लास फ़ाइल बनाएं और उसमें यह कोड चिपकाएं:

// Declares the DAO as a private property in the constructor. Pass in the DAO
// instead of the whole database, because you only need access to the DAO
class WordRepository(private val wordDao: WordDao) {

    // Room executes all queries on a separate thread.
    // Observed LiveData will notify the observer when the data has changed.
    val allWords: LiveData<List<Word>> = wordDao.getAlphabetizedWords()
 
    suspend fun insert(word: Word) {
        wordDao.insert(word)
    }
}

मुख्य बातें:

  • पूरे डेटाबेस के बजाय, डीएओ को रिपॉज़िटरी कंस्ट्रक्टर में पास किया जाता है. ऐसा इसलिए है, क्योंकि इसे सिर्फ़ डीएओ का ऐक्सेस चाहिए. डीएओ में डेटाबेस के लिए सभी रीड/राइट मैथड होते हैं. पूरे डेटाबेस को रिपॉज़िटरी के लिए उपलब्ध कराने की ज़रूरत नहीं है.
  • शब्दों की सूची, सार्वजनिक प्रॉपर्टी होती है. इसे Room से शब्दों की LiveData सूची मिलती है. हम ऐसा इसलिए कर सकते हैं, क्योंकि हमने "The LiveData class" चरण में getAlphabetizedWords मेथड को LiveData वापस करने के लिए तय किया है. Room, सभी क्वेरी को अलग थ्रेड पर एक्ज़ीक्यूट करता है. इसके बाद, LiveData में बदलाव होने पर, मुख्य थ्रेड पर ऑब्ज़र्वर को सूचना दी जाएगी.
  • suspend मॉडिफ़ायर, कंपाइलर को बताता है कि इसे किसी कोरूटीन या अन्य सस्पेंडिंग फ़ंक्शन से कॉल किया जाना चाहिए.

ViewModel क्या है?

ViewModel की भूमिका, यूज़र इंटरफ़ेस (यूआई) को डेटा उपलब्ध कराना और कॉन्फ़िगरेशन में होने वाले बदलावों को मैनेज करना है. ViewModel, रिपॉज़िटरी और यूज़र इंटरफ़ेस (यूआई) के बीच कम्यूनिकेशन सेंटर के तौर पर काम करता है. फ़्रैगमेंट के बीच डेटा शेयर करने के लिए, ViewModel का इस्तेमाल भी किया जा सकता है. ViewModel, लाइफ़साइकल लाइब्रेरी का हिस्सा है.

इस विषय के बारे में शुरुआती जानकारी पाने के लिए, ViewModel Overview या ViewModels: A Simple Example ब्लॉग पोस्ट देखें.

ViewModel का इस्तेमाल क्यों करना चाहिए?

ViewModel, आपके ऐप्लिकेशन के यूज़र इंटरफ़ेस (यूआई) डेटा को लाइफ़साइकल के हिसाब से सेव करता है. इससे कॉन्फ़िगरेशन में बदलाव होने पर भी डेटा सुरक्षित रहता है. अपने ऐप्लिकेशन के यूज़र इंटरफ़ेस (यूआई) डेटा को Activity और Fragment क्लास से अलग करने पर, आपको सिंगल रिस्पॉन्सबिलिटी प्रिंसिपल को बेहतर तरीके से फ़ॉलो करने में मदद मिलती है: आपकी ऐक्टिविटी और फ़्रैगमेंट, स्क्रीन पर डेटा दिखाने के लिए ज़िम्मेदार होते हैं. वहीं, ViewModel, यूज़र इंटरफ़ेस (यूआई) के लिए ज़रूरी सभी डेटा को सेव करने और प्रोसेस करने का काम कर सकता है.

ViewModel में, LiveData का इस्तेमाल ऐसे डेटा के लिए करें जिसे बदला जा सकता है. यूज़र इंटरफ़ेस (यूआई) इस डेटा का इस्तेमाल करेगा या इसे दिखाएगा. LiveData का इस्तेमाल करने के कई फ़ायदे हैं:

  • डेटा में बदलावों के लिए पोल करने के बजाय, डेटा पर एक ऑब्ज़र्वर रखा जा सकता है. साथ ही, जब डेटा में बदलाव हो, तब ही यूज़र इंटरफ़ेस (यूआई) को अपडेट किया जा सकता है.
  • रिपॉज़िटरी और यूज़र इंटरफ़ेस (यूआई) को ViewModel से अलग किया जाता है.
  • ViewModel से कोई डेटाबेस कॉल नहीं होता है. यह सब Repository में मैनेज किया जाता है. इससे कोड को टेस्ट करना आसान हो जाता है.

viewModelScope

Kotlin में, सभी कोरूटीन CoroutineScope में चलते हैं. स्कोप, अपने जॉब के ज़रिए कोरूटीन की लाइफ़टाइम को कंट्रोल करता है. किसी स्कोप के जॉब को रद्द करने पर, उस स्कोप में शुरू किए गए सभी कोरूटीन रद्द हो जाते हैं.

AndroidX lifecycle-viewmodel-ktx लाइब्रेरी, ViewModel क्लास के एक्सटेंशन फ़ंक्शन के तौर पर viewModelScope को जोड़ती है. इससे आपको स्कोप के साथ काम करने में मदद मिलती है.

ViewModel में कोरूटीन का इस्तेमाल करने के बारे में ज़्यादा जानने के लिए, अपने Android ऐप्लिकेशन में Kotlin कोरूटीन का इस्तेमाल करना कोडलैब का पांचवां चरण या Android में आसान कोरूटीन: viewModelScope ब्लॉग पोस्ट देखें.

ViewModel लागू करना

WordViewModel के लिए Kotlin क्लास फ़ाइल बनाएं और उसमें यह कोड जोड़ें:

class WordViewModel(application: Application) : AndroidViewModel(application) {

    private val repository: WordRepository
    // Using LiveData and caching what getAlphabetizedWords returns has several benefits:
    // - We can put an observer on the data (instead of polling for changes) and only update the
    //   the UI when the data actually changes.
    // - Repository is completely separated from the UI through the ViewModel.
    val allWords: LiveData<List<Word>>

    init {
        val wordsDao = WordRoomDatabase.getDatabase(application).wordDao()
        repository = WordRepository(wordsDao)
        allWords = repository.allWords
    }

    /**
     * Launching a new coroutine to insert the data in a non-blocking way
     */
    fun insert(word: Word) = viewModelScope.launch(Dispatchers.IO) {
        repository.insert(word)
    }
}

यहां हमने:

  • WordViewModel नाम की एक क्लास बनाई गई है, जो Application को पैरामीटर के तौर पर लेती है और AndroidViewModel को बढ़ाती है.
  • डेटा स्टोर करने की जगह का रेफ़रंस सेव करने के लिए, एक निजी सदस्य वैरिएबल जोड़ा गया.
  • शब्दों की सूची को कैश मेमोरी में सेव करने के लिए, एक सार्वजनिक LiveData सदस्य वैरिएबल जोड़ा गया.
  • init ब्लॉक बनाया गया है, जो WordRoomDatabase से WordDao का रेफ़रंस पाता है.
  • init ब्लॉक में, WordRoomDatabase के आधार पर WordRepository बनाया गया.
  • init ब्लॉक में, रिपॉज़िटरी का इस्तेमाल करके allWords LiveData को शुरू किया गया है.
  • एक रैपर insert() मेथड बनाया गया है, जो Repository के insert() मेथड को कॉल करता है. इस तरह, insert() को लागू करने की प्रोसेस, यूज़र इंटरफ़ेस (यूआई) से अलग हो जाती है. हम नहीं चाहते कि इंसर्ट करने की प्रोसेस, मुख्य थ्रेड को ब्लॉक करे. इसलिए, हम एक नया कोरूटीन लॉन्च कर रहे हैं और रिपॉज़िटरी के इंसर्ट फ़ंक्शन को कॉल कर रहे हैं. यह एक सस्पेंड फ़ंक्शन है. जैसा कि बताया गया है, ViewModels में उनकी लाइफ़साइकल के आधार पर कोरूटीन स्कोप होता है. इसे viewModelScope कहा जाता है. इसका इस्तेमाल यहां किया जाता है.

इसके बाद, आपको सूची और आइटम के लिए एक्सएमएल लेआउट जोड़ना होगा.

इस कोडलैब में यह माना गया है कि आपको एक्सएमएल में लेआउट बनाने के बारे में जानकारी है. इसलिए, हम आपको सिर्फ़ कोड दे रहे हैं.

अपने ऐप्लिकेशन की थीम को मटीरियल थीम बनाने के लिए, AppTheme पैरंट को Theme.MaterialComponents.Light.DarkActionBar के तौर पर सेट करें. values/styles.xml में सूची के आइटम के लिए स्टाइल जोड़ें:

<resources>

    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.MaterialComponents.Light.DarkActionBar">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
    </style>

    <!-- The default font for RecyclerView items is too small.
    The margin is a simple delimiter between the words. -->
    <style name="word_title">
        <item name="android:layout_width">match_parent</item>
        <item name="android:layout_marginBottom">8dp</item>
        <item name="android:paddingLeft">8dp</item>
        <item name="android:background">@android:color/holo_orange_light</item>
        <item name="android:textAppearance">@android:style/TextAppearance.Large</item>
    </style>
</resources>

layout/recyclerview_item.xml लेआउट जोड़ें:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" 
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <TextView
        android:id="@+id/textView"
        style="@style/word_title"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@android:color/holo_orange_light" />
</LinearLayout>

layout/activity_main.xml में, TextView को RecyclerView से बदलें और फ़्लोटिंग ऐक्शन बटन (एफ़एबी) जोड़ें. अब आपका लेआउट ऐसा दिखना चाहिए:

<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyclerview"
        android:layout_width="0dp"
        android:layout_height="0dp"
        tools:listitem="@layout/recyclerview_item"
        android:padding="@dimen/big_padding"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <com.google.android.material.floatingactionbutton.FloatingActionButton
        android:id="@+id/fab"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="16dp"
        android:contentDescription="@string/add_word"/>

</androidx.constraintlayout.widget.ConstraintLayout>

आपके FAB का लुक, उपलब्ध कार्रवाई के हिसाब से होना चाहिए. इसलिए, हम आइकॉन को '+' सिंबल से बदल देंगे.

सबसे पहले, हमें एक नई वेक्टर ऐसेट जोड़नी होगी:

  1. फ़ाइल > नया > वेक्टर ऐसेट चुनें.
  2. क्लिप आर्ट: फ़ील्ड में, Android रोबोट आइकॉन पर क्लिक करें.
  3. "जोड़ें" खोजें और '+' ऐसेट चुनें. ठीक है
    पर क्लिक करें
  4. इसके बाद, आगे बढ़ें पर क्लिक करें.
  5. ऐसेट जोड़ने के लिए, आइकॉन पाथ की पुष्टि करें कि वह main > drawable है. इसके बाद, हो गया पर क्लिक करें.
  6. layout/activity_main.xml में जाकर, नए ड्रॉएबल को शामिल करने के लिए, FAB को अपडेट करें:
<com.google.android.material.floatingactionbutton.FloatingActionButton
        android:id="@+id/fab"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="16dp"
        android:contentDescription="@string/add_word"
        android:src="@drawable/ic_add_black_24dp"/>

आपको डेटा को RecyclerView में दिखाना है. यह डेटा को सिर्फ़ TextView में दिखाने से ज़्यादा बेहतर है. इस कोडलैब में यह मान लिया गया है कि आपको RecyclerView, RecyclerView.LayoutManager, RecyclerView.ViewHolder, और RecyclerView.Adapter के काम करने के तरीके के बारे में पता है.

ध्यान दें कि अडैप्टर में मौजूद words वैरिएबल, डेटा को कैश मेमोरी में सेव करता है. अगले टास्क में, आपको ऐसा कोड जोड़ना होगा जो डेटा को अपने-आप अपडेट करता है.

WordListAdapter के लिए एक Kotlin क्लास फ़ाइल बनाएं, जो RecyclerView.Adapter को बढ़ाती है. यहां कोड दिया गया है:

class WordListAdapter internal constructor(
        context: Context
) : RecyclerView.Adapter<WordListAdapter.WordViewHolder>() {

    private val inflater: LayoutInflater = LayoutInflater.from(context)
    private var words = emptyList<Word>() // Cached copy of words

    inner class WordViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        val wordItemView: TextView = itemView.findViewById(R.id.textView)
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): WordViewHolder {
        val itemView = inflater.inflate(R.layout.recyclerview_item, parent, false)
        return WordViewHolder(itemView)
    }

    override fun onBindViewHolder(holder: WordViewHolder, position: Int) {
        val current = words[position]
        holder.wordItemView.text = current.word
    }

    internal fun setWords(words: List<Word>) {
        this.words = words
        notifyDataSetChanged()
    }

    override fun getItemCount() = words.size
}

MainActivity के onCreate() तरीके में RecyclerView जोड़ें.

onCreate() तरीके में setContentView के बाद:

   val recyclerView = findViewById<RecyclerView>(R.id.recyclerview)
   val adapter = WordListAdapter(this)
   recyclerView.adapter = adapter
   recyclerView.layoutManager = LinearLayoutManager(this)

अपने ऐप्लिकेशन को चलाकर देखें कि सब कुछ ठीक से काम कर रहा है या नहीं. कोई आइटम नहीं है, क्योंकि आपने अभी तक डेटा को हुक अप नहीं किया है.

डेटाबेस में कोई डेटा नहीं है. डेटा को दो तरीकों से जोड़ा जा सकता है: डेटाबेस खुलने पर कुछ डेटा जोड़ें और शब्द जोड़ने के लिए Activity जोड़ें.

ऐप्लिकेशन शुरू होने पर, डेटाबेस में मौजूद सारा कॉन्टेंट मिटाने और उसे फिर से भरने के लिए, RoomDatabase.Callback बनाएं और onOpen() को बदलें. यूज़र इंटरफ़ेस (यूआई) थ्रेड पर Room डेटाबेस के ऑपरेशन नहीं किए जा सकते. इसलिए, onOpen(), IO Dispatcher पर एक कोरूटीन लॉन्च करता है.

कोरूटीन लॉन्च करने के लिए, हमें CoroutineScope की ज़रूरत होती है. WordRoomDatabase क्लास के getDatabase तरीके को अपडेट करें, ताकि पैरामीटर के तौर पर कोरूटीन स्कोप भी मिल सके:

fun getDatabase(
       context: Context,
       scope: CoroutineScope
  ): WordRoomDatabase {
...
}

स्कोप को भी पास करने के लिए, WordViewModel के init ब्लॉक में डेटाबेस से डेटा वापस पाने की सुविधा को शुरू करने वाले फ़ंक्शन को अपडेट करें:

val wordsDao = WordRoomDatabase.getDatabase(application, viewModelScope).wordDao()

WordRoomDatabase में, हम RoomDatabase.Callback() का कस्टम तरीके से लागू किया गया वर्शन बनाते हैं. इसे कंस्ट्रक्टर पैरामीटर के तौर पर CoroutineScope भी मिलता है. इसके बाद, हम डेटाबेस में डेटा भरने के लिए, onOpen तरीके को बदल देते हैं.

WordRoomDatabase क्लास में कॉलबैक बनाने का कोड यहां दिया गया है:

private class WordDatabaseCallback(
    private val scope: CoroutineScope
) : RoomDatabase.Callback() {

    override fun onOpen(db: SupportSQLiteDatabase) {
        super.onOpen(db)
        INSTANCE?.let { database ->
            scope.launch {
                populateDatabase(database.wordDao())
            }
        }
    }

    suspend fun populateDatabase(wordDao: WordDao) {
        // Delete all content here.
        wordDao.deleteAll()

        // Add sample words.
        var word = Word("Hello")
        wordDao.insert(word)
        word = Word("World!")
        wordDao.insert(word)

        // TODO: Add your own words!
    }
}

आखिर में, डेटाबेस बनाने के क्रम में कॉलबैक को Room.databaseBuilder() को कॉल करने से ठीक पहले जोड़ें:.build()

.addCallback(WordDatabaseCallback(scope))

फ़ाइनल कोड ऐसा दिखना चाहिए:

@Database(entities = arrayOf(Word::class), version = 1, exportSchema = false)
abstract class WordRoomDatabase : RoomDatabase() {

   abstract fun wordDao(): WordDao

   private class WordDatabaseCallback(
       private val scope: CoroutineScope
   ) : RoomDatabase.Callback() {

       override fun onOpen(db: SupportSQLiteDatabase) {
           super.onOpen(db)
           INSTANCE?.let { database ->
               scope.launch {
                   var wordDao = database.wordDao()

                   // Delete all content here.
                   wordDao.deleteAll()

                   // Add sample words.
                   var word = Word("Hello")
                   wordDao.insert(word)
                   word = Word("World!")
                   wordDao.insert(word)

                   // TODO: Add your own words!
                   word = Word("TODO!")
                   wordDao.insert(word)
               }
           }
       }
   }

   companion object {
       @Volatile
       private var INSTANCE: WordRoomDatabase? = null

       fun getDatabase(
           context: Context,
           scope: CoroutineScope
       ): WordRoomDatabase {
            // if the INSTANCE is not null, then return it,
            // if it is, then create the database
            return INSTANCE ?: synchronized(this) {
                val instance = Room.databaseBuilder(
                        context.applicationContext,
                        WordRoomDatabase::class.java,
                        "word_database"
                )
                 .addCallback(WordDatabaseCallback(scope))
                 .build()
                INSTANCE = instance
                // return instance
                instance
        }
     }
   }
}

values/strings.xml में ये स्ट्रिंग रिसॉर्स जोड़ें:

<string name="hint_word">Word...</string>
<string name="button_save">Save</string>
<string name="empty_not_saved">Word not saved because it is empty.</string>

value/colors.xml में यह कलर रिसॉर्स जोड़ें:

<color name="buttonLabel">#FFFFFF</color>

नई डाइमेंशन रिसॉर्स फ़ाइल बनाएं:

  1. प्रोजेक्ट विंडो में, ऐप्लिकेशन मॉड्यूल पर क्लिक करें.
  2. फ़ाइल > नया > Android रिसॉर्स फ़ाइल को चुनें
  3. उपलब्ध क्वालिफ़ायर में जाकर, डाइमेंशन चुनें
  4. फ़ाइल का नाम सेट करें: dimens

values/dimens.xml में ये डाइमेंशन रिसॉर्स जोड़ें:

<dimen name="small_padding">8dp</dimen>
<dimen name="big_padding">16dp</dimen>

बिना ऐक्टिविटी वाले टेंप्लेट का इस्तेमाल करके, एक नया खाली Android Activity बनाएं:

  1. फ़ाइल > नया > गतिविधि > खाली गतिविधि चुनें
  2. गतिविधि के नाम के लिए NewWordActivity डालें.
  3. पुष्टि करें कि नई गतिविधि को Android मेनिफ़ेस्ट में जोड़ा गया है.
<activity android:name=".NewWordActivity"></activity>

लेआउट फ़ोल्डर में मौजूद activity_new_word.xml फ़ाइल को इस कोड से अपडेट करें:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <EditText
        android:id="@+id/edit_word"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:minHeight="@dimen/min_height"
        android:fontFamily="sans-serif-light"
        android:hint="@string/hint_word"
        android:inputType="textAutoComplete"
        android:layout_margin="@dimen/big_padding"
        android:textSize="18sp" />

    <Button
        android:id="@+id/button_save"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@color/colorPrimary"
        android:text="@string/button_save"
        android:layout_margin="@dimen/big_padding"
        android:textColor="@color/buttonLabel" />

</LinearLayout>

गतिविधि के लिए कोड अपडेट करें:

class NewWordActivity : AppCompatActivity() {

    private lateinit var editWordView: EditText

    public override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_new_word)
        editWordView = findViewById(R.id.edit_word)

        val button = findViewById<Button>(R.id.button_save)
        button.setOnClickListener {
            val replyIntent = Intent()
            if (TextUtils.isEmpty(editWordView.text)) {
                setResult(Activity.RESULT_CANCELED, replyIntent)
            } else {
                val word = editWordView.text.toString()
                replyIntent.putExtra(EXTRA_REPLY, word)
                setResult(Activity.RESULT_OK, replyIntent)
            }
            finish()
        }
    }

    companion object {
        const val EXTRA_REPLY = "com.example.android.wordlistsql.REPLY"
    }
}

आखिरी चरण में, यूज़र इंटरफ़ेस (यूआई) को डेटाबेस से कनेक्ट करना होता है. इसके लिए, उपयोगकर्ता के डाले गए नए शब्दों को सेव किया जाता है. साथ ही, शब्द डेटाबेस के मौजूदा कॉन्टेंट को RecyclerView में दिखाया जाता है.

डेटाबेस के मौजूदा कॉन्टेंट को दिखाने के लिए, एक ऐसा ऑब्ज़र्वर जोड़ें जो LiveData में मौजूद ViewModel को ऑब्ज़र्व करता हो.

डेटा में बदलाव होने पर, onChanged() कॉलबैक को शुरू किया जाता है. यह कॉलबैक, अडैप्टर के setWords() तरीके को कॉल करता है, ताकि अडैप्टर के कैश किए गए डेटा को अपडेट किया जा सके और दिखाई गई सूची को रीफ़्रेश किया जा सके.

MainActivity में, ViewModel के लिए सदस्य वैरिएबल बनाएं:

private lateinit var wordViewModel: WordViewModel

अपने ViewModel को Activity के साथ असोसिएट करने के लिए, ViewModelProvider का इस्तेमाल करें.

जब आपका Activity पहली बार शुरू होगा, तब ViewModelProviders, ViewModel बनाएगा. जब ऐक्टिविटी खत्म हो जाती है, तब भी ViewModel बना रहता है. उदाहरण के लिए, कॉन्फ़िगरेशन में बदलाव होने पर. जब गतिविधि को फिर से बनाया जाता है, तो ViewModelProviders मौजूदा ViewModel को वापस भेजता है. ज़्यादा जानकारी के लिए, ViewModel देखें.

RecyclerView कोड ब्लॉक के नीचे मौजूद onCreate() में, ViewModelProvider से ViewModel पाएं:

wordViewModel = ViewModelProvider(this).get(WordViewModel::class.java)

साथ ही, onCreate() में, WordViewModel से allWords LiveData प्रॉपर्टी के लिए एक ऑब्ज़र्वर जोड़ें.

onChanged() तरीका (यह हमारे Lambda के लिए डिफ़ॉल्ट तरीका है) तब ट्रिगर होता है, जब देखे गए डेटा में बदलाव होता है और गतिविधि फ़ोरग्राउंड में होती है:

wordViewModel.allWords.observe(this, Observer { words ->
            // Update the cached copy of the words in the adapter.
            words?.let { adapter.setWords(it) }
})

हम चाहते हैं कि FAB पर टैप करने पर NewWordActivity खुले. साथ ही, जब हम MainActivity पर वापस आएं, तो डेटाबेस में नया शब्द डाला जाए या Toast दिखाया जाए. इसके लिए, अनुरोध कोड को इस तरह से तय करें:

private val newWordActivityRequestCode = 1

MainActivity में, NewWordActivity के लिए onActivityResult() कोड जोड़ें.

अगर गतिविधि RESULT_OK के साथ वापस आती है, तो WordViewModel के insert() तरीके को कॉल करके, लौटाए गए शब्द को डेटाबेस में डालें:

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    super.onActivityResult(requestCode, resultCode, data)

    if (requestCode == newWordActivityRequestCode && resultCode == Activity.RESULT_OK) {
        data?.getStringExtra(NewWordActivity.EXTRA_REPLY)?.let {
            val word = Word(it)
            wordViewModel.insert(word)
        }
    } else {
        Toast.makeText(
            applicationContext,
            R.string.empty_not_saved,
            Toast.LENGTH_LONG).show()
    }
}

जब उपयोगकर्ता, फ़्लोटिंग ऐक्शन बटन पर टैप करता है, तब MainActivity,start NewWordActivity में. MainActivity onCreate में, फ़्लोटिंग ऐक्शन बटन ढूंढें और इस कोड का इस्तेमाल करके onClickListener जोड़ें:

val fab = findViewById<FloatingActionButton>(R.id.fab)
fab.setOnClickListener {
  val intent = Intent(this@MainActivity, NewWordActivity::class.java)
  startActivityForResult(intent, newWordActivityRequestCode)
}

आपका पूरा कोड ऐसा दिखना चाहिए:

class MainActivity : AppCompatActivity() {

   private const val newWordActivityRequestCode = 1
   private lateinit var wordViewModel: WordViewModel

   override fun onCreate(savedInstanceState: Bundle?) {
       super.onCreate(savedInstanceState)
       setContentView(R.layout.activity_main)
       setSupportActionBar(toolbar)

       val recyclerView = findViewById<RecyclerView>(R.id.recyclerview)
       val adapter = WordListAdapter(this)
       recyclerView.adapter = adapter
       recyclerView.layoutManager = LinearLayoutManager(this)

       wordViewModel = ViewModelProvider(this).get(WordViewModel::class.java)
       wordViewModel.allWords.observe(this, Observer { words ->
           // Update the cached copy of the words in the adapter.
           words?.let { adapter.setWords(it) }
       })

       val fab = findViewById<FloatingActionButton>(R.id.fab)
       fab.setOnClickListener {
           val intent = Intent(this@MainActivity, NewWordActivity::class.java)
           startActivityForResult(intent, newWordActivityRequestCode)
       }
   }

   override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
       super.onActivityResult(requestCode, resultCode, data)

       if (requestCode == newWordActivityRequestCode && resultCode == Activity.RESULT_OK) {
           data?.getStringExtra(NewWordActivity.EXTRA_REPLY)?.let {
               val word = Word(it)
               wordViewModel.insert(word)
           }
       } else {
           Toast.makeText(
               applicationContext,
               R.string.empty_not_saved,
               Toast.LENGTH_LONG).show()
       }
   }
}

अब अपना ऐप्लिकेशन चलाएं! NewWordActivity में डेटाबेस में कोई शब्द जोड़ने पर, यूज़र इंटरफ़ेस अपने-आप अपडेट हो जाएगा.

अब आपके पास काम करने वाला एक ऐप्लिकेशन है. आइए, एक बार फिर से देख लें कि आपने क्या बनाया है. ऐप्लिकेशन का स्ट्रक्चर यहां फिर से दिया गया है:

ऐप्लिकेशन के कॉम्पोनेंट ये हैं:

  • MainActivity: यह RecyclerView और WordListAdapter का इस्तेमाल करके, शब्दों को सूची में दिखाता है. MainActivity में, एक Observer है. यह डेटाबेस से LiveData शब्दों को देखता है और उनमें बदलाव होने पर सूचना देता है.
  • NewWordActivity: से सूची में एक नया शब्द जुड़ जाता है.
  • WordViewModel: यह डेटा लेयर को ऐक्सेस करने के तरीके उपलब्ध कराता है. साथ ही, यह LiveData दिखाता है, ताकि MainActivity ऑब्ज़र्वर रिलेशनशिप सेट अप कर सके.*
  • LiveData<List<Word>>: इससे यूज़र इंटरफ़ेस (यूआई) कॉम्पोनेंट में अपने-आप अपडेट होने की सुविधा मिलती है. MainActivity में, एक Observer है. यह डेटाबेस से LiveData शब्दों को देखता है और जब वे बदलते हैं, तो इसे सूचना मिलती है.
  • Repository: एक या एक से ज़्यादा डेटा सोर्स मैनेज करता है. Repository, ViewModel के लिए ऐसे तरीके दिखाता है जिनसे वह डेटा उपलब्ध कराने वाले सोर्स के साथ इंटरैक्ट कर सकता है. इस ऐप्लिकेशन में, वह बैकएंड एक रूम डेटाबेस है.
  • Room: is a wrapper around and implements a SQLite database. Room आपके लिए कई ऐसे काम करता है जिन्हें आपको पहले खुद करना पड़ता था.
  • डीएओ: यह डेटाबेस क्वेरी के लिए, मैथड कॉल को मैप करता है. इससे जब रिपॉज़िटरी, getAlphabetizedWords() जैसे किसी मैथड को कॉल करती है, तब Room, SELECT * from word_table ORDER BY word ASC को लागू कर सकता है.
  • Word: यह एक ऐसी इकाई क्लास है जिसमें एक शब्द होता है.

* Views और Activities (और Fragments) सिर्फ़ ViewModel के ज़रिए डेटा के साथ इंटरैक्ट करते हैं. इसलिए, इससे कोई फ़र्क़ नहीं पड़ता कि डेटा कहां से आता है.

यूज़र इंटरफ़ेस (यूआई) अपने-आप अपडेट होने (रीऐक्टिव यूज़र इंटरफ़ेस) के लिए डेटा का फ़्लो

LiveData का इस्तेमाल करने की वजह से, अपने-आप अपडेट होने की सुविधा काम करती है. MainActivity में, एक Observer है. यह डेटाबेस से LiveData शब्दों को देखता है और जब वे बदलते हैं, तो इसे सूचना मिलती है. बदलाव होने पर, ऑब्ज़र्वर का onChange() तरीका लागू होता है और WordListAdapter में mWords अपडेट होता है.

डेटा को देखा जा सकता है, क्योंकि यह LiveData है. WordViewModel allWords प्रॉपर्टी से मिले LiveData<List<Word>> को ऑब्ज़र्व किया जाता है.

WordViewModel, यूज़र इंटरफ़ेस (यूआई) लेयर से बैकएंड के बारे में पूरी जानकारी छिपाता है. यह डेटा लेयर को ऐक्सेस करने के तरीके उपलब्ध कराता है. साथ ही, यह LiveData दिखाता है, ताकि MainActivity ऑब्ज़र्वर रिलेशनशिप सेट अप कर सके. Views और Activities (और Fragments) सिर्फ़ ViewModel के ज़रिए डेटा के साथ इंटरैक्ट करते हैं. इसलिए, इससे कोई फ़र्क़ नहीं पड़ता कि डेटा कहां से आता है.

इस मामले में, डेटा Repository से आता है. ViewModel को यह जानने की ज़रूरत नहीं है कि वह रिपॉज़िटरी किसके साथ इंटरैक्ट करती है. इसे सिर्फ़ यह पता होना चाहिए कि Repository के साथ कैसे इंटरैक्ट करना है. इसके लिए, Repository के ज़रिए उपलब्ध कराए गए तरीकों का इस्तेमाल किया जाता है.

रिपॉज़िटरी, एक या उससे ज़्यादा डेटा सोर्स मैनेज करती है. WordListSample ऐप्लिकेशन में, वह बैकएंड एक रूम डेटाबेस है. Room, SQLite डेटाबेस के चारों ओर एक रैपर है और इसे लागू करता है. Room आपके लिए कई ऐसे काम करता है जिन्हें आपको पहले खुद करना पड़ता था. उदाहरण के लिए, Room में वे सभी काम किए जा सकते हैं जो SQLiteOpenHelper क्लास में किए जाते थे.

डीएओ, मैथड कॉल को डेटाबेस क्वेरी पर मैप करता है, ताकि जब रिपॉज़िटरी getAllWords() जैसे किसी मैथड को कॉल करे, तो Room SELECT * from word_table ORDER BY word ASC को लागू कर सके.

क्वेरी से मिले नतीजे को LiveData के तौर पर देखा जाता है. इसलिए, Room में डेटा बदलने पर, Observer इंटरफ़ेस का onChanged() तरीका लागू होता है और यूज़र इंटरफ़ेस (यूआई) अपडेट हो जाता है.

[ज़रूरी नहीं] समाधान कोड डाउनलोड करना

अगर आपने अब तक ऐसा नहीं किया है, तो कोडलैब के लिए समाधान कोड देखें. GitHub रिपॉज़िटरी देखी जा सकती है. इसके अलावा, यहां से कोड डाउनलोड किया जा सकता है:

सोर्स कोड डाउनलोड करें

डाउनलोड की गई ज़िप फ़ाइल को अनपैक करें. इससे एक रूट फ़ोल्डर, android-room-with-a-view-kotlin अनपैक हो जाएगा. इसमें पूरा ऐप्लिकेशन मौजूद होगा.