Android Kotlin Fundamentals 05.1: ViewModel और ViewModelFactory

यह कोडलैब, Android Kotlin Fundamentals कोर्स का हिस्सा है. अगर कोडलैब को क्रम से पूरा किया जाता है, तो आपको इस कोर्स से सबसे ज़्यादा फ़ायदा मिलेगा. कोर्स के सभी कोडलैब, Android Kotlin Fundamentals कोडलैब के लैंडिंग पेज पर दिए गए हैं.

शीर्षक स्क्रीन

गेम की स्क्रीन

स्कोर स्क्रीन

परिचय

इस कोडलैब में, आपको Android आर्किटेक्चर के कॉम्पोनेंट में से एक, ViewModel के बारे में जानकारी मिलेगी:

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

आपको पहले से क्या पता होना चाहिए

  • Kotlin में बुनियादी Android ऐप्लिकेशन बनाने का तरीका.
  • अपने ऐप्लिकेशन में नेविगेशन लागू करने के लिए, नेविगेशन ग्राफ़ का इस्तेमाल करने का तरीका.
  • अपने ऐप्लिकेशन के डेस्टिनेशन के बीच नेविगेट करने और नेविगेशन डेस्टिनेशन के बीच डेटा भेजने के लिए, कोड जोड़ने का तरीका.
  • ऐक्टिविटी और फ़्रैगमेंट की लाइफ़साइकल कैसे काम करती हैं.
  • किसी ऐप्लिकेशन में लॉगिंग की जानकारी जोड़ने और Android Studio में Logcat का इस्तेमाल करके लॉग पढ़ने का तरीका.

आपको क्या सीखने को मिलेगा

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

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

  • ऐप्लिकेशन में ViewModel जोड़ें, ताकि ऐप्लिकेशन का डेटा सेव किया जा सके. इससे कॉन्फ़िगरेशन में बदलाव होने पर भी डेटा सुरक्षित रहेगा.
  • कंस्ट्रक्टर पैरामीटर के साथ ViewModel ऑब्जेक्ट को इंस्टैंशिएट करने के लिए, ViewModelFactory और फ़ैक्ट्री-मेथड डिज़ाइन पैटर्न का इस्तेमाल करें.

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

पहला खिलाड़ी, ऐप्लिकेशन में दिए गए शब्दों को देखता है और एक-एक करके उन पर ऐक्ट करता है. वह यह पक्का करता है कि दूसरा खिलाड़ी उन शब्दों को न देख पाए. दूसरा खिलाड़ी, शब्द का अनुमान लगाने की कोशिश करता है.

गेम खेलने के लिए, पहला खिलाड़ी डिवाइस पर ऐप्लिकेशन खोलता है और उसे एक शब्द दिखता है. उदाहरण के लिए, "गिटार". यह शब्द, नीचे दिए गए स्क्रीनशॉट में दिखाया गया है.

पहला खिलाड़ी, शब्द को बोलकर नहीं, बल्कि ऐक्टिंग करके बताता है.

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

इस टास्क में, आपको स्टार्टर ऐप्लिकेशन डाउनलोड करके चलाना होगा. साथ ही, कोड की जांच करनी होगी.

पहला चरण: शुरू करें

  1. GuessTheWord का स्टार्टर कोड डाउनलोड करें और Android Studio में प्रोजेक्ट खोलें.
  2. ऐप्लिकेशन को Android डिवाइस या Emulator पर चलाएं.
  3. बटन पर टैप करें. ध्यान दें कि छोड़ें बटन पर क्लिक करने से, अगला शब्द दिखता है और स्कोर में एक अंक की कमी आती है. वहीं, ठीक है बटन पर क्लिक करने से, अगला शब्द दिखता है और स्कोर में एक अंक की बढ़ोतरी होती है. गेम खत्म करें बटन लागू नहीं किया गया है. इसलिए, इस पर टैप करने से कुछ नहीं होता.

दूसरा चरण: कोड की बारीकी से जाँच करना

  1. Android Studio में, कोड को एक्सप्लोर करें. इससे आपको यह समझने में मदद मिलेगी कि ऐप्लिकेशन कैसे काम करता है.
  2. नीचे दी गई फ़ाइलों को ज़रूर देखें. ये फ़ाइलें खास तौर पर ज़रूरी हैं.

MainActivity.kt

इस फ़ाइल में सिर्फ़ डिफ़ॉल्ट और टेंप्लेट से जनरेट किया गया कोड होता है.

res/layout/main_activity.xml

इस फ़ाइल में ऐप्लिकेशन का मुख्य लेआउट होता है. उपयोगकर्ता के ऐप्लिकेशन में नेविगेट करने पर, NavHostFragment अन्य फ़्रैगमेंट होस्ट करता है.

यूज़र इंटरफ़ेस (यूआई) फ़्रैगमेंट्स

स्टार्टर कोड में तीन फ़्रैगमेंट हैं. ये com.example.android.guesstheword.screens पैकेज के तहत तीन अलग-अलग पैकेज में मौजूद हैं:

  • टाइटल स्क्रीन के लिए title/TitleFragment
  • game/GameFragment गेम की स्क्रीन के लिए
  • score/ScoreFragment स्कोर स्क्रीन के लिए

screens/title/TitleFragment.kt

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

screens/game/GameFragment.kt

यह मुख्य फ़्रैगमेंट है, जहाँ गेम की ज़्यादातर गतिविधियाँ होती हैं:

  • मौजूदा शब्द और मौजूदा स्कोर के लिए वैरिएबल तय किए जाते हैं.
  • resetList() तरीके में तय की गई wordList, गेम में इस्तेमाल किए जाने वाले शब्दों की एक सैंपल सूची है.
  • onSkip() तरीका, Skip बटन के लिए क्लिक हैंडलर है. इससे स्कोर में एक की कमी आती है. इसके बाद, nextWord() तरीके का इस्तेमाल करके अगला शब्द दिखाया जाता है.
  • onCorrect() तरीका, ठीक है बटन के लिए क्लिक हैंडलर है. इस तरीके को onSkip() तरीके की तरह ही लागू किया जाता है. इन दोनों तरीकों में सिर्फ़ यह अंतर है कि इस तरीके में स्कोर से 1 घटाने के बजाय जोड़ा जाता है.

screens/score/ScoreFragment.kt

ScoreFragment गेम की आखिरी स्क्रीन है. इसमें खिलाड़ी का फ़ाइनल स्कोर दिखता है. इस कोडलैब में, आपको इस स्क्रीन को दिखाने और फ़ाइनल स्कोर दिखाने के लिए, कोड लागू करना होगा.

res/navigation/main_navigation.xml

नेविगेशन ग्राफ़ दिखाता है कि फ़्रैगमेंट, नेविगेशन के ज़रिए कैसे कनेक्ट होते हैं:

  • टाइटल फ़्रैगमेंट से, उपयोगकर्ता गेम फ़्रैगमेंट पर जा सकता है.
  • गेम फ़्रैगमेंट से, उपयोगकर्ता स्कोर फ़्रैगमेंट पर जा सकता है.
  • स्कोर फ़्रैगमेंट से, उपयोगकर्ता गेम फ़्रैगमेंट पर वापस जा सकता है.

इस टास्क में, आपको GuessTheWord स्टार्टर ऐप्लिकेशन में मौजूद समस्याओं का पता लगाना है.

  1. स्टार्टर कोड चलाएं और कुछ शब्दों के ज़रिए गेम खेलें. हर शब्द के बाद, छोड़ें या ठीक है पर टैप करें.
  2. अब गेम की स्क्रीन पर एक शब्द और मौजूदा स्कोर दिखता है. डिवाइस या एम्युलेटर को घुमाकर, स्क्रीन का ओरिएंटेशन बदलें. ध्यान दें कि मौजूदा स्कोर मिट जाएगा.
  3. गेम को कुछ और शब्दों में चलाएं. जब गेम की स्क्रीन पर कुछ स्कोर दिखे, तो ऐप्लिकेशन को बंद करें और फिर से खोलें. ध्यान दें कि गेम शुरू से शुरू होता है, क्योंकि ऐप्लिकेशन की स्थिति सेव नहीं की जाती है.
  4. कुछ शब्दों का इस्तेमाल करके गेम खेलें. इसके बाद, गेम खत्म करें बटन पर टैप करें. ध्यान दें कि कुछ नहीं होता है.

ऐप्लिकेशन में समस्याएं:

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

इस कोडलैब में बताए गए ऐप्लिकेशन आर्किटेक्चर कॉम्पोनेंट का इस्तेमाल करके, इन समस्याओं को हल किया जा सकता है.

ऐप्लिकेशन का आर्किटेक्चर

ऐप्लिकेशन आर्किटेक्चर, आपके ऐप्लिकेशन की क्लास और उनके बीच के संबंधों को इस तरह से डिज़ाइन करने का एक तरीका है कि कोड व्यवस्थित हो, खास स्थितियों में बेहतर परफ़ॉर्म करे, और उस पर काम करना आसान हो. चार कोडलैब के इस सेट में, GuessTheWord ऐप्लिकेशन में किए गए सुधार, Android ऐप्लिकेशन के आर्किटेक्चर से जुड़े दिशा-निर्देशों के मुताबिक होते हैं. साथ ही, Android Architecture Components का इस्तेमाल किया जाता है. Android ऐप्लिकेशन का आर्किटेक्चर, MVVM (मॉडल-व्यू-व्यूमॉडल) आर्किटेक्चरल पैटर्न जैसा होता है.

GuessTheWord ऐप्लिकेशन, ज़िम्मेदारियों को अलग-अलग करने के डिज़ाइन सिद्धांत का पालन करता है. इसे क्लास में बांटा गया है. हर क्लास एक अलग समस्या को हल करती है. इस सबक के पहले कोडलैब में, आपको यूज़र इंटरफ़ेस (यूआई) कंट्रोलर, ViewModel, और ViewModelFactory क्लास के साथ काम करना है.

यूज़र इंटरफ़ेस (यूआई) कंट्रोलर

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

GuessTheWord के स्टार्टर कोड में, यूज़र इंटरफ़ेस कंट्रोलर तीन फ़्रैगमेंट हैं: GameFragment, ScoreFragment,, और TitleFragment. "जिम्मेदारी अलग-अलग होना" डिज़ाइन सिद्धांत के मुताबिक, GameFragment सिर्फ़ गेम के एलिमेंट को स्क्रीन पर दिखाने और यह जानने के लिए ज़िम्मेदार है कि उपयोगकर्ता ने बटन कब टैप किए. इसके अलावा, इसकी कोई और ज़िम्मेदारी नहीं है. जब उपयोगकर्ता किसी बटन पर टैप करता है, तो यह जानकारी GameViewModel को पास कर दी जाती है.

ViewModel

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

GameViewModel में स्कोर वैल्यू, शब्दों की सूची, और मौजूदा शब्द जैसे डेटा को सेव किया जाता है, क्योंकि यह डेटा स्क्रीन पर दिखाया जाना है. GameViewModel में कारोबार से जुड़ा लॉजिक भी शामिल होता है. इससे डेटा की मौजूदा स्थिति का पता लगाने के लिए, सामान्य कैलकुलेशन की जाती हैं.

ViewModelFactory

ViewModelFactory, कंस्ट्रक्टर पैरामीटर के साथ या उनके बिना ViewModel ऑब्जेक्ट बनाता है.

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

ViewModel क्लास को यूज़र इंटरफ़ेस (यूआई) से जुड़े डेटा को सेव और मैनेज करने के लिए डिज़ाइन किया गया है. इस ऐप्लिकेशन में, हर ViewModel एक फ़्रैगमेंट से जुड़ा होता है.

इस टास्क में, आपको अपने ऐप्लिकेशन में पहला ViewModel जोड़ना है. यह GameFragment के लिए GameViewModel है. आपको यह भी पता चलता है कि ViewModel का लाइफ़साइकल से जुड़ा होना क्या मायने रखता है.

पहला चरण: GameViewModel क्लास जोड़ना

  1. build.gradle(module:app) फ़ाइल खोलें. dependencies ब्लॉक में, ViewModel के लिए Gradle डिपेंडेंसी जोड़ें.

    अगर लाइब्रेरी के सबसे नए वर्शन का इस्तेमाल किया जाता है, तो समाधान ऐप्लिकेशन को उम्मीद के मुताबिक कंपाइल करना चाहिए. अगर ऐसा नहीं होता है, तो समस्या हल करने की कोशिश करें या नीचे दिए गए वर्शन पर वापस जाएं.
//ViewModel
implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0'
  1. पैकेज screens/game/ फ़ोल्डर में, GameViewModel नाम की नई Kotlin क्लास बनाएं.
  2. GameViewModel क्लास को ऐब्स्ट्रैक्ट क्लास ViewModel से बढ़ाएं.
  3. ViewModel को लाइफ़साइकल के बारे में कैसे पता चलता है, इसे बेहतर तरीके से समझने के लिए log स्टेटमेंट के साथ init ब्लॉक जोड़ें.
class GameViewModel : ViewModel() {
   init {
       Log.i("GameViewModel", "GameViewModel created!")
   }
}

दूसरा चरण: onCleared() को बदलें और लॉगिंग जोड़ें

जब इससे जुड़ा फ़्रैगमेंट अलग हो जाता है या ऐक्टिविटी खत्म हो जाती है, तब ViewModel डिस्ट्रॉय हो जाता है. ViewModel को डिस्ट्रॉय करने से ठीक पहले, onCleared() कॉलबैक को कॉल किया जाता है, ताकि संसाधनों को क्लीन अप किया जा सके.

  1. GameViewModel क्लास में, onCleared() तरीके को बदलें.
  2. GameViewModel के लाइफ़साइकल को ट्रैक करने के लिए, onCleared() के अंदर एक लॉग स्टेटमेंट जोड़ें.
override fun onCleared() {
   super.onCleared()
   Log.i("GameViewModel", "GameViewModel destroyed!")
}

तीसरा चरण: GameViewModel को गेम फ़्रैगमेंट से जोड़ना

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

इस चरण में, आपको GameFragment के अंदर मौजूद GameViewModel का रेफ़रंस बनाना होता है.

  1. GameFragment क्लास में, टॉप लेवल पर GameViewModel टाइप का फ़ील्ड, क्लास वैरिएबल के तौर पर जोड़ें.
private lateinit var viewModel: GameViewModel

चौथा चरण: ViewModel को शुरू करना

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

ViewModelProvider कैसे काम करता है:

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

ViewModelProvider बनाने के लिए, ViewModelProviders.of() तरीके का इस्तेमाल करके ViewModel को शुरू करें:

  1. GameFragment क्लास में, viewModel वैरिएबल को शुरू करें. इस कोड को onCreateView() में, बाइंडिंग वैरिएबल की परिभाषा के बाद डालें. ViewModelProviders.of() तरीके का इस्तेमाल करें. साथ ही, इससे जुड़े GameFragment कॉन्टेक्स्ट और GameViewModel क्लास को पास करें.
  2. ViewModel ऑब्जेक्ट को शुरू करने से पहले, ViewModelProviders.of() तरीके के कॉल को लॉग करने के लिए, एक लॉग स्टेटमेंट जोड़ें.
Log.i("GameFragment", "Called ViewModelProviders.of")
viewModel = ViewModelProviders.of(this).get(GameViewModel::class.java)
  1. ऐप्लिकेशन चलाएं. Android Studio में, Logcat पैनल खोलें और Game पर फ़िल्टर करें. अपने डिवाइस या एम्युलेटर पर, Play बटन पर टैप करें. गेम की स्क्रीन खुलती है.

    Logcat में दिखाए गए तरीके के मुताबिक, GameFragment का onCreateView() तरीका, ViewModelProviders.of() तरीके को कॉल करके GameViewModel बनाता है. आपने GameFragment और GameViewModel में जो लॉगिंग स्टेटमेंट जोड़े हैं वे Logcat में दिखते हैं.

  1. अपने डिवाइस या एम्युलेटर पर, ऑटो-रोटेट की सेटिंग चालू करें. इसके बाद, स्क्रीन ओरिएंटेशन को कुछ बार बदलें. GameFragment को हर बार डिस्ट्रॉय और फिर से बनाया जाता है. इसलिए, ViewModelProviders.of() को हर बार कॉल किया जाता है. हालांकि, GameViewModel को सिर्फ़ एक बार बनाया जाता है. साथ ही, इसे हर कॉल के लिए फिर से नहीं बनाया जाता है और न ही मिटाया जाता है.
I/GameFragment: Called ViewModelProviders.of
I/GameViewModel: GameViewModel created!
I/GameFragment: Called ViewModelProviders.of
I/GameFragment: Called ViewModelProviders.of
I/GameFragment: Called ViewModelProviders.of
  1. गेम से बाहर निकलें या गेम के फ़्रैगमेंट से बाहर निकलें. GameFragment को खत्म कर दिया गया है. इससे जुड़ा GameViewModel भी डिस्ट्रॉय हो जाता है और कॉलबैक onCleared() को कॉल किया जाता है.
I/GameFragment: Called ViewModelProviders.of
I/GameViewModel: GameViewModel created!
I/GameFragment: Called ViewModelProviders.of
I/GameFragment: Called ViewModelProviders.of
I/GameFragment: Called ViewModelProviders.of
I/GameViewModel: GameViewModel destroyed!

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

  • स्क्रीन पर दिखने वाला डेटा और उस डेटा को प्रोसेस करने वाला कोड, ViewModel में डालें.
  • ViewModel में कभी भी फ़्रैगमेंट, ऐक्टिविटी या व्यू के रेफ़रंस नहीं होने चाहिए, क्योंकि कॉन्फ़िगरेशन में बदलाव होने पर ऐक्टिविटी, फ़्रैगमेंट, और व्यू नहीं रहते.

तुलना के लिए, यहां बताया गया है कि ViewModel जोड़ने से पहले और ViewModel जोड़ने के बाद, स्टार्टर ऐप्लिकेशन में GameFragment यूज़र इंटरफ़ेस (यूआई) डेटा को कैसे मैनेज किया जाता है:

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

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

पहला चरण: डेटा फ़ील्ड और डेटा प्रोसेसिंग को ViewModel में ले जाना

नीचे दिए गए डेटा फ़ील्ड और तरीकों को GameFragment से GameViewModel में ले जाएं:

  1. word, score, और wordList डेटा फ़ील्ड को दूसरी जगह ले जाएं. पक्का करें कि word और score, private न हों.

    बाइंडिंग वैरिएबल GameFragmentBinding को न हटाएं, क्योंकि इसमें व्यू के रेफ़रंस होते हैं. इस वैरिएबल का इस्तेमाल लेआउट को बड़ा करने, क्लिक लिसनर सेट अप करने, और स्क्रीन पर डेटा दिखाने के लिए किया जाता है. ये सभी काम फ़्रैगमेंट के होते हैं.
  2. resetList() और nextWord() तरीकों को मूव करें. इन तरीकों से यह तय किया जाता है कि स्क्रीन पर कौनसे शब्द दिखाए जाएं.
  3. onCreateView() तरीके के अंदर से, resetList() और nextWord() को कॉल करने के तरीकों को GameViewModel के init ब्लॉक में ले जाएं.

    ये तरीके init ब्लॉक में होने चाहिए, क्योंकि ViewModel के बनने पर आपको शब्दों की सूची रीसेट करनी चाहिए, न कि हर बार फ़्रैगमेंट बनने पर. GameFragment के init ब्लॉक में जाकर, लॉग स्टेटमेंट मिटाया जा सकता है.

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

फ़िलहाल, दोनों जगहों पर एक जैसे तरीके इस्तेमाल करें:

  1. GameFragment से onSkip() और onCorrect() तरीकों को GameViewModel में कॉपी करें.
  2. GameViewModel में, पक्का करें कि onSkip() और onCorrect() तरीके private न हों, क्योंकि आपको फ़्रैगमेंट से इन तरीकों का रेफ़रंस देना होगा.

रीफ़ैक्टर करने के बाद, GameViewModel क्लास का कोड यहां दिया गया है:

class GameViewModel : ViewModel() {
   // The current word
   var word = ""
   // The current score
   var score = 0
   // The list of words - the front of the list is the next word to guess
   private lateinit var wordList: MutableList<String>

   /**
    * Resets the list of words and randomizes the order
    */
   private fun resetList() {
       wordList = mutableListOf(
               "queen",
               "hospital",
               "basketball",
               "cat",
               "change",
               "snail",
               "soup",
               "calendar",
               "sad",
               "desk",
               "guitar",
               "home",
               "railway",
               "zebra",
               "jelly",
               "car",
               "crow",
               "trade",
               "bag",
               "roll",
               "bubble"
       )
       wordList.shuffle()
   }

   init {
       resetList()
       nextWord()
       Log.i("GameViewModel", "GameViewModel created!")
   }
   /**
    * Moves to the next word in the list
    */
   private fun nextWord() {
       if (!wordList.isEmpty()) {
           //Select and remove a word from the list
           word = wordList.removeAt(0)
       }
       updateWordText()
       updateScoreText()
   }
 /** Methods for buttons presses **/
   fun onSkip() {
       if (!wordList.isEmpty()) {
           score--
       }
       nextWord()
   }

   fun onCorrect() {
       if (!wordList.isEmpty()) {
           score++
       }
       nextWord()
   }

   override fun onCleared() {
       super.onCleared()
       Log.i("GameViewModel", "GameViewModel destroyed!")
   }
}

रीफ़ैक्टर करने के बाद, GameFragment क्लास का कोड यहां दिया गया है:

/**
* Fragment where the game is played
*/
class GameFragment : Fragment() {


   private lateinit var binding: GameFragmentBinding


   private lateinit var viewModel: GameViewModel


   override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                             savedInstanceState: Bundle?): View? {

       // Inflate view and obtain an instance of the binding class
       binding = DataBindingUtil.inflate(
               inflater,
               R.layout.game_fragment,
               container,
               false
       )

       Log.i("GameFragment", "Called ViewModelProviders.of")
       viewModel = ViewModelProviders.of(this).get(GameViewModel::class.java)

       binding.correctButton.setOnClickListener { onCorrect() }
       binding.skipButton.setOnClickListener { onSkip() }
       updateScoreText()
       updateWordText()
       return binding.root

   }


   /** Methods for button click handlers **/

   private fun onSkip() {
       if (!wordList.isEmpty()) {
           score--
       }
       nextWord()
   }

   private fun onCorrect() {
       if (!wordList.isEmpty()) {
           score++
       }
       nextWord()
   }


   /** Methods for updating the UI **/

   private fun updateWordText() {
       binding.wordText.text = word
   }

   private fun updateScoreText() {
       binding.scoreText.text = score.toString()
   }
}

दूसरा चरण: GameFragment में क्लिक हैंडलर और डेटा फ़ील्ड के रेफ़रंस अपडेट करना

  1. GameFragment में, onSkip() और onCorrect() तरीके अपडेट करें. स्कोर अपडेट करने के लिए कोड हटाएं. इसके बजाय, viewModel पर, इससे जुड़े onSkip() और onCorrect() तरीकों को कॉल करें.
  2. आपने nextWord() तरीके को ViewModel में ले जाया है. इसलिए, गेम फ़्रैगमेंट अब इसे ऐक्सेस नहीं कर सकता.

    GameFragment में, onSkip() और onCorrect() तरीकों में, nextWord() को कॉल करने की जगह updateScoreText() और updateWordText() को कॉल करें. इन तरीकों से, स्क्रीन पर डेटा दिखता है.
private fun onSkip() {
   viewModel.onSkip()
   updateWordText()
   updateScoreText()
}
private fun onCorrect() {
   viewModel.onCorrect()
   updateScoreText()
   updateWordText()
}
  1. GameFragment में, score और word वैरिएबल को अपडेट करके GameViewModel वैरिएबल का इस्तेमाल करें. ऐसा इसलिए, क्योंकि ये वैरिएबल अब GameViewModel में हैं.
private fun updateWordText() {
   binding.wordText.text = viewModel.word
}

private fun updateScoreText() {
   binding.scoreText.text = viewModel.score.toString()
}
  1. GameViewModel में, nextWord() तरीके के अंदर, updateWordText() और updateScoreText() तरीकों के कॉल हटाएं. अब इन तरीकों को GameFragment से कॉल किया जा रहा है.
  2. ऐप्लिकेशन बनाएं और पक्का करें कि उसमें कोई गड़बड़ी न हो. अगर आपको गड़बड़ियां दिखती हैं, तो प्रोजेक्ट को क्लीन करें और फिर से बनाएं.
  3. कुछ शब्दों का इस्तेमाल करके, ऐप्लिकेशन को चलाओ और गेम खेलो. गेम स्क्रीन पर रहते हुए, डिवाइस को घुमाएं. ध्यान दें कि ओरिएंटेशन बदलने के बाद भी, मौजूदा स्कोर और मौजूदा शब्द बने रहते हैं.

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

इस टास्क में, आपको End Game बटन के लिए क्लिक लिसनर लागू करना है.

  1. GameFragment में, onEndGame() नाम की एक नई विधि जोड़ें. जब उपयोगकर्ता गेम खत्म करें बटन पर टैप करेगा, तब onEndGame() तरीके को कॉल किया जाएगा.
private fun onEndGame() {
   }
  1. GameFragment में, onCreateView() तरीके के अंदर, वह कोड ढूंढें जो ठीक है और अभी नहीं बटन के लिए क्लिक लिसनर सेट करता है. इन दोनों लाइनों के ठीक नीचे, End Game बटन के लिए क्लिक लिसनर सेट करें. बाइंडिंग वैरिएबल, binding का इस्तेमाल करें. क्लिक लिसनर के अंदर, onEndGame() वाले तरीके को कॉल करें.
binding.endGameButton.setOnClickListener { onEndGame() }
  1. GameFragment में, ऐप्लिकेशन को स्कोर स्क्रीन पर ले जाने के लिए, gameFinished() नाम का एक तरीका जोड़ें. Safe Args का इस्तेमाल करके, स्कोर को आर्ग्युमेंट के तौर पर पास करें.
/**
* Called when the game is finished
*/
private fun gameFinished() {
   Toast.makeText(activity, "Game has just finished", Toast.LENGTH_SHORT).show()
   val action = GameFragmentDirections.actionGameToScore()
   action.score = viewModel.score
   NavHostFragment.findNavController(this).navigate(action)
}
  1. onEndGame() तरीके में, gameFinished() तरीके को कॉल करें.
private fun onEndGame() {
   gameFinished()
}
  1. ऐप्लिकेशन चलाएं, गेम खेलें, और कुछ शब्दों को दोहराएं. गेम खत्म करें बटन पर टैप करें. ध्यान दें कि ऐप्लिकेशन, स्कोर वाली स्क्रीन पर पहुंच जाता है, लेकिन फ़ाइनल स्कोर नहीं दिखता. अगले टास्क में इस समस्या को ठीक किया जा सकता है.

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

फ़ैक्ट्री मेथड पैटर्न, एक क्रिएशनल डिज़ाइन पैटर्न है. यह ऑब्जेक्ट बनाने के लिए फ़ैक्ट्री मेथड का इस्तेमाल करता है. फ़ैक्ट्री मेथड एक ऐसा मेथड होता है जो उसी क्लास का इंस्टेंस दिखाता है.

इस टास्क में, आपको स्कोर फ़्रैगमेंट के लिए पैरामीटर वाला कंस्ट्रक्टर और ViewModel को इंस्टैंशिएट करने के लिए फ़ैक्ट्री मेथड के साथ एक ViewModel बनाना होगा.

  1. score पैकेज में, ScoreViewModel नाम की नई Kotlin क्लास बनाएं. यह क्लास, स्कोर फ़्रैगमेंट के लिए ViewModel होगी.
  2. ScoreViewModel क्लास को ViewModel. से बढ़ाएं. फ़ाइनल स्कोर के लिए कंस्ट्रक्टर पैरामीटर जोड़ें. लॉग स्टेटमेंट के साथ init ब्लॉक जोड़ें.
  3. फ़ाइनल स्कोर को सेव करने के लिए, ScoreViewModel क्लास में score नाम का एक वैरिएबल जोड़ें.
class ScoreViewModel(finalScore: Int) : ViewModel() {
   // The final score
   var score = finalScore
   init {
       Log.i("ScoreViewModel", "Final score is $finalScore")
   }
}
  1. score पैकेज में, ScoreViewModelFactory नाम की एक और Kotlin क्लास बनाएं. यह क्लास, ScoreViewModel ऑब्जेक्ट को इंस्टैंशिएट करने के लिए ज़िम्मेदार होगी.
  2. ScoreViewModelFactory क्लास को ViewModelProvider.Factory से बढ़ाएं. फ़ाइनल स्कोर के लिए कंस्ट्रक्टर पैरामीटर जोड़ें.
class ScoreViewModelFactory(private val finalScore: Int) : ViewModelProvider.Factory {
}
  1. ScoreViewModelFactory में, Android Studio, लागू नहीं किए गए ऐब्स्ट्रैक्ट मेंबर के बारे में गड़बड़ी दिखाता है. गड़बड़ी को ठीक करने के लिए, create() तरीके को बदलें. create() तरीके में, नए बनाए गए ScoreViewModel ऑब्जेक्ट को दिखाएं.
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
   if (modelClass.isAssignableFrom(ScoreViewModel::class.java)) {
       return ScoreViewModel(finalScore) as T
   }
   throw IllegalArgumentException("Unknown ViewModel class")
}
  1. ScoreFragment में, ScoreViewModel और ScoreViewModelFactory के लिए क्लास वैरिएबल बनाएं.
private lateinit var viewModel: ScoreViewModel
private lateinit var viewModelFactory: ScoreViewModelFactory
  1. ScoreFragment में, onCreateView() के अंदर, binding वैरिएबल को शुरू करने के बाद, viewModelFactory को शुरू करें. ScoreViewModelFactory का इस्तेमाल करें. आर्ग्युमेंट बंडल से मिले फ़ाइनल स्कोर को, ScoreViewModelFactory() के कंस्ट्रक्टर पैरामीटर के तौर पर पास करें.
viewModelFactory = ScoreViewModelFactory(ScoreFragmentArgs.fromBundle(arguments!!).score)
  1. onCreateView( में, viewModelFactory को शुरू करने के बाद, viewModel ऑब्जेक्ट को शुरू करें. ViewModelProviders.of() वाले तरीके को कॉल करें. साथ ही, उससे जुड़े स्कोर फ़्रैगमेंट के कॉन्टेक्स्ट और viewModelFactory को पास करें. इससे viewModelFactory क्लास. में तय की गई फ़ैक्ट्री मेथड का इस्तेमाल करके, ScoreViewModel ऑब्जेक्ट बनाया जाएगा
viewModel = ViewModelProviders.of(this, viewModelFactory)
       .get(ScoreViewModel::class.java)
  1. onCreateView() तरीके में, viewModel को शुरू करने के बाद, scoreText व्यू के टेक्स्ट को ScoreViewModel में तय किए गए फ़ाइनल स्कोर पर सेट करें.
binding.scoreText.text = viewModel.score.toString()
  1. अपना ऐप्लिकेशन चलाएं और गेम खेलें. कुछ या सभी शब्दों को साइकल करें और गेम खत्म करें पर टैप करें. ध्यान दें कि स्कोर फ़्रैगमेंट में अब फ़ाइनल स्कोर दिख रहा है.

  1. ज़रूरी नहीं: Logcat में ScoreViewModel पर फ़िल्टर करके, ScoreViewModel लॉग देखें. स्कोर की वैल्यू दिखनी चाहिए.
2019-02-07 10:50:18.328 com.example.android.guesstheword I/ScoreViewModel: Final score is 15

इस टास्क में, आपने ViewModel का इस्तेमाल करने के लिए ScoreFragment को लागू किया. आपने यह भी सीखा कि ViewModelFactory इंटरफ़ेस का इस्तेमाल करके, ViewModel के लिए पैरामीटर वाला कंस्ट्रक्टर कैसे बनाया जाता है.

बधाई हो! आपने Android आर्किटेक्चर के कॉम्पोनेंट, ViewModel में से किसी एक का इस्तेमाल करने के लिए, अपने ऐप्लिकेशन के आर्किटेक्चर में बदलाव किया हो. आपने ऐप्लिकेशन के लाइफ़साइकल से जुड़ी समस्या हल कर दी है. अब गेम का डेटा, कॉन्फ़िगरेशन में हुए बदलावों के बाद भी सुरक्षित रहता है. आपने यह भी सीखा कि ViewModelFactory इंटरफ़ेस का इस्तेमाल करके, ViewModel बनाने के लिए पैरामीटर वाला कंस्ट्रक्टर कैसे बनाया जाता है.

Android Studio प्रोजेक्ट: GuessTheWord

  • Android के ऐप्लिकेशन आर्किटेक्चर के दिशा-निर्देशों में, अलग-अलग ज़िम्मेदारियों वाली क्लास को अलग करने का सुझाव दिया गया है.
  • यूज़र इंटरफ़ेस (यूआई) कंट्रोलर, यूज़र इंटरफ़ेस (यूआई) पर आधारित क्लास होती है. जैसे, Activity या Fragment. यूज़र इंटरफ़ेस (यूआई) कंट्रोलर में सिर्फ़ ऐसा लॉजिक होना चाहिए जो यूज़र इंटरफ़ेस (यूआई) और ऑपरेटिंग सिस्टम के इंटरैक्शन को मैनेज करता हो. इनमें यूज़र इंटरफ़ेस (यूआई) में दिखने वाला डेटा नहीं होना चाहिए. उस डेटा को ViewModel में डालें.
  • ViewModel क्लास, यूज़र इंटरफ़ेस (यूआई) से जुड़े डेटा को सेव और मैनेज करती है. ViewModel क्लास की मदद से, कॉन्फ़िगरेशन में हुए बदलावों के दौरान डेटा को सुरक्षित रखा जा सकता है. जैसे, स्क्रीन रोटेशन.
  • ViewModel, Android आर्किटेक्चर कॉम्पोनेंट में से एक है.
  • ViewModelProvider.Factory एक इंटरफ़ेस है. इसका इस्तेमाल करके, ViewModel ऑब्जेक्ट बनाया जा सकता है.

नीचे दी गई टेबल में, यूज़र इंटरफ़ेस (यूआई) कंट्रोलर की तुलना उन ViewModel इंस्टेंस से की गई है जिनमें उनके लिए डेटा होता है:

यूज़र इंटरफ़ेस (यूआई) कंट्रोलर

ViewModel

यूज़र इंटरफ़ेस (यूआई) कंट्रोलर का एक उदाहरण, ScoreFragment है जिसे आपने इस कोडलैब में बनाया है.

ViewModel का एक उदाहरण, वह ScoreViewModel है जिसे आपने इस कोडलैब में बनाया था.

इसमें यूज़र इंटरफ़ेस (यूआई) में दिखाने के लिए कोई डेटा नहीं होता.

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

इसमें डेटा दिखाने के लिए कोड होता है. साथ ही, इसमें उपयोगकर्ता के इवेंट का कोड होता है. जैसे, क्लिक लिसनर.

इसमें डेटा प्रोसेसिंग के लिए कोड होता है.

कॉन्फ़िगरेशन में हर बदलाव के दौरान, इसे डिस्ट्रॉय और फिर से बनाया जाता है.

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

इसमें व्यू शामिल हैं.

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

इसमें, उससे जुड़े ViewModel का रेफ़रंस होता है.

इसमें, यूज़र इंटरफ़ेस (यूआई) कंट्रोलर का कोई रेफ़रंस नहीं होता.

Udacity का कोर्स:

Android डेवलपर का दस्तावेज़:

अन्य:

इस सेक्शन में, उन छात्र-छात्राओं के लिए होमवर्क असाइनमेंट की सूची दी गई है जो किसी शिक्षक के कोर्स के हिस्से के तौर पर इस कोडलैब पर काम कर रहे हैं. शिक्षक के पास ये विकल्प होते हैं:

  • अगर ज़रूरी हो, तो होमवर्क असाइन करें.
  • छात्र-छात्राओं को बताएं कि होमवर्क असाइनमेंट कैसे सबमिट किए जाते हैं.
  • होमवर्क असाइनमेंट को ग्रेड दें.

शिक्षक इन सुझावों का इस्तेमाल अपनी ज़रूरत के हिसाब से कर सकते हैं. साथ ही, वे चाहें, तो कोई दूसरा होमवर्क भी दे सकते हैं.

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

इन सवालों के जवाब दें

पहला सवाल

डिवाइस के कॉन्फ़िगरेशन में बदलाव के दौरान डेटा को नुकसान से बचाने के लिए, आपको ऐप्लिकेशन का डेटा किस क्लास में सेव करना चाहिए?

  • ViewModel
  • LiveData
  • Fragment
  • Activity

दूसरा सवाल

ViewModel में कभी भी फ़्रैगमेंट, ऐक्टिविटी या व्यू के रेफ़रंस नहीं होने चाहिए. सही या गलत?

  • सही
  • गलत

तीसरा सवाल

ViewModel को कब मिटाया जाता है?

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

चौथा सवाल

ViewModelFactory इंटरफ़ेस का इस्तेमाल किस लिए किया जाता है?

  • ViewModel ऑब्जेक्ट को इंस्टैंशिएट करना.
  • ओरिएंटेशन में बदलाव होने पर डेटा को बनाए रखना.
  • स्क्रीन पर दिख रहे डेटा को रीफ़्रेश किया जा रहा है.
  • ऐप्लिकेशन का डेटा बदलने पर सूचनाएं पाना.

अगला सबक शुरू करें: 5.2: LiveData और LiveData ऑब्ज़र्वर

इस कोर्स में मौजूद अन्य कोडलैब के लिंक के लिए, Android Kotlin Fundamentals कोडलैब का लैंडिंग पेज देखें.