Android Kotlin Fundamentals 08.2: इंटरनेट से इमेज लोड करना और उन्हें दिखाना

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

शुरुआती जानकारी

पिछले कोडलैब में, आपने वेब सेवा से डेटा पाने और जवाब को डेटा ऑब्जेक्ट में पार्स करने का तरीका सीखा था. इस कोडलैब में, आपको उस जानकारी का इस्तेमाल करके, वेब यूआरएल से फ़ोटो लोड करने और उन्हें दिखाने के बारे में बताया गया है. आपको यह भी बताया जाएगा कि RecyclerView कैसे बनाया जाता है और इसका इस्तेमाल, खास जानकारी वाले पेज पर इमेज की ग्रिड दिखाने के लिए कैसे किया जाता है.

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

  • फ़्रैगमेंट बनाने और इस्तेमाल करने का तरीका.
  • व्यू मॉडल, व्यू मॉडल फ़ैक्ट्री, ट्रांसफ़ॉर्मेशन, और LiveData जैसे आर्किटेक्चर कॉम्पोनेंट का इस्तेमाल कैसे करें.
  • REST वेब सेवा से JSON को कैसे वापस पाएं और Retrofit और Moshi लाइब्रेरी का इस्तेमाल करके, उस डेटा को Kotlin ऑब्जेक्ट में कैसे पार्स करें.
  • RecyclerView की मदद से ग्रिड लेआउट बनाने का तरीका.
  • Adapter, ViewHolder, और DiffUtil के काम करने का तरीका.

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

  • वेब यूआरएल से इमेज लोड करने और उसे दिखाने के लिए, Glide लाइब्रेरी का इस्तेमाल कैसे करें.
  • इमेज की ग्रिड दिखाने के लिए, RecyclerView और ग्रिड अडैप्टर का इस्तेमाल करने का तरीका.
  • इमेज डाउनलोड और डिसप्ले होने के दौरान, संभावित गड़बड़ियों को कैसे ठीक करें.

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

  • MarsRealEstate ऐप्लिकेशन में बदलाव करें, ताकि मंगल ग्रह की प्रॉपर्टी के डेटा से इमेज का यूआरएल मिल सके. साथ ही, Glide का इस्तेमाल करके उस इमेज को लोड और दिखाया जा सके.
  • ऐप्लिकेशन में लोडिंग ऐनिमेशन और गड़बड़ी का आइकॉन जोड़ें.
  • मंगल ग्रह की प्रॉपर्टी की इमेज का ग्रिड दिखाने के लिए, RecyclerView का इस्तेमाल करें.
  • RecyclerView में स्थिति और गड़बड़ी को मैनेज करने की सुविधा जोड़ें.

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

इस कोडलैब में बनाया गया ऐप्लिकेशन, खास जानकारी वाले पेज पर दिखता है. इस पेज पर इमेज की ग्रिड दिखती है. ये इमेज, प्रॉपर्टी के उस डेटा का हिस्सा हैं जो आपका ऐप्लिकेशन, Mars real estate की वेब सेवा से पाता है. आपका ऐप्लिकेशन, Glide लाइब्रेरी का इस्तेमाल करके इमेज लोड करेगा और उन्हें दिखाएगा. साथ ही, इमेज के लिए ग्रिड लेआउट बनाने के लिए RecyclerView का इस्तेमाल करेगा. आपका ऐप्लिकेशन, नेटवर्क से जुड़ी गड़बड़ियों को भी आसानी से ठीक कर पाएगा.

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

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

Glide को बुनियादी तौर पर दो चीज़ों की ज़रूरत होती है:

  • उस इमेज का यूआरएल जिसे लोड और दिखाना है.
  • उस इमेज को दिखाने के लिए, ImageView ऑब्जेक्ट.

इस टास्क में, आपको Glide का इस्तेमाल करके, रियल एस्टेट की वेब सेवा से एक इमेज दिखाने का तरीका बताया गया है. आपको उस इमेज को दिखाना होगा जो वेब सेवा से मिली प्रॉपर्टी की सूची में, मंगल ग्रह की पहली प्रॉपर्टी को दिखाती है. यहां पहले और बाद के स्क्रीनशॉट दिए गए हैं:

पहला चरण: Glide डिपेंडेंसी जोड़ना

  1. पिछले कोडलैब से MarsRealEstate ऐप्लिकेशन खोलें. (अगर आपके पास यह ऐप्लिकेशन नहीं है, तो MarsRealEstateNetwork को यहां से डाउनलोड किया जा सकता है.)
  2. ऐप्लिकेशन को चलाकर देखें कि यह क्या काम करता है. (इसमें मंगल ग्रह पर मौजूद किसी प्रॉपर्टी की जानकारी दिखाई गई है.)
  3. build.gradle (Module: app) खोलें.
  4. dependencies सेक्शन में, Glide लाइब्रेरी के लिए यह लाइन जोड़ें:
implementation "com.github.bumptech.glide:glide:$version_glide"


ध्यान दें कि वर्शन नंबर, प्रोजेक्ट की Gradle फ़ाइल में पहले से ही अलग से तय किया गया है.

  1. नई डिपेंडेंसी के साथ प्रोजेक्ट को फिर से बनाने के लिए, अभी सिंक करें पर क्लिक करें.

दूसरा चरण: व्यू मॉडल अपडेट करना

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

  1. overview/OverviewViewModel.kt खोलें. _response के ठीक नीचे, एक ही MarsProperty ऑब्जेक्ट के लिए, इंटरनल (बदला जा सकने वाला) और एक्सटर्नल (बदला नहीं जा सकने वाला) लाइव डेटा, दोनों जोड़ें.LiveData

    जब अनुरोध किया जाए, तब MarsProperty क्लास (com.example.android.marsrealestate.network.MarsProperty) इंपोर्ट करें.
private val _property = MutableLiveData<MarsProperty>()

val property: LiveData<MarsProperty>
   get() = _property
  1. getMarsRealEstateProperties() तरीके में, try/catch {} ब्लॉक के अंदर मौजूद वह लाइन ढूंढें जो _response.value को प्रॉपर्टी की संख्या पर सेट करती है. नीचे दिखाया गया टेस्ट जोड़ें. अगर MarsProperty ऑब्जेक्ट उपलब्ध हैं, तो यह टेस्ट _property LiveData की वैल्यू को listResult में मौजूद पहली प्रॉपर्टी पर सेट करता है.
if (listResult.size > 0) {   
    _property.value = listResult[0]
}

पूरा try/catch {} ब्लॉक अब ऐसा दिखता है:

try {
   var listResult = getPropertiesDeferred.await()
   _response.value = "Success: ${listResult.size} Mars properties retrieved"
   if (listResult.size > 0) {      
       _property.value = listResult[0]
   }
 } catch (e: Exception) {
    _response.value = "Failure: ${e.message}"
 }
  1. res/layout/fragment_overview.xml फ़ाइल खोलें. <TextView> एलिमेंट में, android:text को बदलकर property LiveData के imgSrcUrl कॉम्पोनेंट से बाइंड करें:
android:text="@{viewModel.property.imgSrcUrl}"
  1. ऐप्लिकेशन चलाएं. TextView, Mars की पहली प्रॉपर्टी में मौजूद इमेज का यूआरएल दिखाता है. अब तक आपने सिर्फ़ व्यू मॉडल और उस यूआरएल के लिए लाइव डेटा सेट अप किया है.

तीसरा चरण: बाइंडिंग अडैप्टर बनाना और Glide को कॉल करना

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

  1. BindingAdapters.kt खोलें. इस फ़ाइल में, ऐप्लिकेशन में इस्तेमाल किए जाने वाले बाइंडिंग अडैप्टर मौजूद होंगे.
  2. एक bindImage() फ़ंक्शन बनाएं, जो ImageView और String को पैरामीटर के तौर पर इस्तेमाल करे. फ़ंक्शन को @BindingAdapter से एनोटेट करें. @BindingAdapter एनोटेशन, डेटा बाइंडिंग को बताता है कि जब किसी एक्सएमएल आइटम में imageUrl एट्रिब्यूट मौजूद हो, तब आपको इस बाइंडिंग अडैप्टर को एक्ज़ीक्यूट करना है.

    अनुरोध किए जाने पर, androidx.databinding.BindingAdapter और android.widget.ImageView इंपोर्ट करें.
@BindingAdapter("imageUrl")
fun bindImage(imgView: ImageView, imgUrl: String?) {

}
  1. bindImage() फ़ंक्शन में, imgUrl आर्ग्युमेंट के लिए let {} ब्लॉक जोड़ें:
imgUrl?.let { 
}
  1. let {} ब्लॉक में, नीचे दी गई लाइन जोड़ें. इससे यूआरएल स्ट्रिंग (एक्सएमएल से) को Uri ऑब्जेक्ट में बदला जा सकेगा. अनुरोध किए जाने पर androidx.core.net.toUri इंपोर्ट करें.

    आपको फ़ाइनल Uri ऑब्जेक्ट के लिए एचटीटीपीएस स्कीम का इस्तेमाल करना है, क्योंकि जिस सर्वर से इमेज ली जाती हैं उसके लिए इस स्कीम की ज़रूरत होती है. एचटीटीपीएस स्कीम का इस्तेमाल करने के लिए, toUri बिल्डर में buildUpon.scheme("https") जोड़ें. toUri() तरीका, Android KTX की कोर लाइब्रेरी का Kotlin एक्सटेंशन फ़ंक्शन है. इसलिए, यह सिर्फ़ String क्लास का हिस्सा दिखता है.
val imgUri = imgUrl.toUri().buildUpon().scheme("https").build()
  1. अब भी let {} के अंदर, Glide.with() को कॉल करें, ताकि Uri ऑब्जेक्ट से इमेज को ImageView में लोड किया जा सके. अनुरोध किए जाने पर, com.bumptech.glide.Glide इंपोर्ट करें.
Glide.with(imgView.context)
       .load(imgUri)
       .into(imgView)

चौथा चरण: लेआउट और फ़्रैगमेंट अपडेट करना

Glide ने इमेज लोड कर दी है, लेकिन अभी कुछ भी नहीं दिख रहा है. इसके बाद, इमेज दिखाने के लिए लेआउट और फ़्रैगमेंट को ImageView से अपडेट करें.

  1. res/layout/gridview_item.xml खोलें. यह लेआउट रिसॉर्स फ़ाइल है. इसका इस्तेमाल, कोडलैब में बाद में RecyclerView के हर आइटम के लिए किया जाएगा. इसका इस्तेमाल यहां सिर्फ़ एक इमेज दिखाने के लिए किया जाता है.
  2. डेटा बाइंडिंग के लिए, <ImageView> एलिमेंट के ऊपर <data> एलिमेंट जोड़ें. इसके बाद, इसे OverviewViewModel क्लास से बाइंड करें:
<data>
   <variable
       name="viewModel"
       type="com.example.android.marsrealestate.overview.OverviewViewModel" />
</data>
  1. नई इमेज लोडिंग बाइंडिंग अडैप्टर का इस्तेमाल करने के लिए, ImageView एलिमेंट में app:imageUrl एट्रिब्यूट जोड़ें:
app:imageUrl="@{viewModel.property.imgSrcUrl}"
  1. overview/OverviewFragment.kt खोलें. onCreateView() तरीके में, उस लाइन पर टिप्पणी करें जो FragmentOverviewBinding क्लास को बढ़ाती है और उसे बाइंडिंग वैरिएबल को असाइन करती है. यह सिर्फ़ कुछ समय के लिए है. बाद में आपको यह सुविधा फिर से मिल जाएगी.
//val binding = FragmentOverviewBinding.inflate(inflater)
  1. इसके बजाय, GridViewItemBinding क्लास को बड़ा करने के लिए एक लाइन जोड़ें. अनुरोध किए जाने पर, com.example.android.marsrealestate. databinding.GridViewItemBinding इंपोर्ट करें.
val binding = GridViewItemBinding.inflate(inflater)
  1. ऐप्लिकेशन चलाएं. अब आपको नतीजों की सूची में, पहले MarsProperty की इमेज की फ़ोटो दिखेगी.

पांचवां चरण: सामान्य लोडिंग और गड़बड़ी की इमेज जोड़ना

Glide, इमेज लोड होने के दौरान प्लेसहोल्डर इमेज दिखा सकता है. साथ ही, इमेज लोड न होने पर गड़बड़ी वाली इमेज दिखा सकता है. इससे उपयोगकर्ता अनुभव को बेहतर बनाया जा सकता है. उदाहरण के लिए, अगर इमेज मौजूद नहीं है या खराब हो गई है, तो ऐसा हो सकता है. इस चरण में, बाइंडिंग अडैप्टर और लेआउट में यह सुविधा जोड़ी जाती है.

  1. res/drawable/ic_broken_image.xml खोलें और दाईं ओर मौजूद, झलक देखें टैब पर क्लिक करें. गड़बड़ी वाली इमेज के लिए, आपने टूटी हुई इमेज वाले आइकॉन का इस्तेमाल किया है. यह आइकॉन, पहले से मौजूद आइकॉन लाइब्रेरी में उपलब्ध है. इस वेक्टर ड्रॉएबल में, android:tint एट्रिब्यूट का इस्तेमाल करके आइकॉन को ग्रे रंग में दिखाया गया है.

  1. res/drawable/loading_animation.xml खोलें. यह ड्रॉ करने लायक एक ऐनिमेशन है, जिसे <animate-rotate> टैग के साथ तय किया गया है. यह ऐनिमेशन, इमेज ड्रॉएबल loading_img.xml को बीच के पॉइंट के चारों ओर घुमाता है. (आपको झलक में ऐनिमेशन नहीं दिखता.)

  1. BindingAdapters.kt फ़ाइल पर वापस जाएं. bindImage() तरीके में, Glide.with() को कॉल करने के लिए अपडेट करें, ताकि load() और into() के बीच apply() फ़ंक्शन को कॉल किया जा सके. अनुरोध किए जाने पर com.bumptech.glide.request.RequestOptions इंपोर्ट करें.

    यह कोड, लोड होने के दौरान इस्तेमाल की जाने वाली प्लेसहोल्डर इमेज सेट करता है (loading_animation ड्रॉएबल). अगर इमेज लोड नहीं होती है, तो कोड एक इमेज सेट करता है. इस इमेज का इस्तेमाल किया जाता है (broken_image ड्रॉएबल). bindImage() तरीका अब ऐसा दिखता है:
@BindingAdapter("imageUrl")
fun bindImage(imgView: ImageView, imgUrl: String?) {
    imgUrl?.let {
        val imgUri = 
           imgUrl.toUri().buildUpon().scheme("https").build()
        Glide.with(imgView.context)
                .load(imgUri)
                .apply(RequestOptions()
                        .placeholder(R.drawable.loading_animation)
                        .error(R.drawable.ic_broken_image))
                .into(imgView)
    }
}
  1. ऐप्लिकेशन चलाएं. आपके नेटवर्क कनेक्शन की स्पीड के हिसाब से, आपको कुछ समय के लिए लोडिंग इमेज दिख सकती है. ऐसा तब होता है, जब Glide प्रॉपर्टी की इमेज डाउनलोड करके दिखाता है. हालांकि, नेटवर्क बंद करने पर भी आपको अब तक टूटी हुई इमेज का आइकॉन नहीं दिखेगा. इसे ठीक करने का तरीका, कोडलैब के आखिरी हिस्से में बताया गया है.

अब आपका ऐप्लिकेशन, इंटरनेट से प्रॉपर्टी की जानकारी लोड करता है. आपने पहले MarsProperty सूची आइटम से मिले डेटा का इस्तेमाल करके, व्यू मॉडल में LiveData प्रॉपर्टी बनाई है. साथ ही, आपने उस प्रॉपर्टी के डेटा से मिले इमेज यूआरएल का इस्तेमाल करके, ImageView को भरा है. हालांकि, आपका लक्ष्य यह है कि आपका ऐप्लिकेशन इमेज का ग्रिड दिखाए. इसलिए, आपको GridLayoutManager के साथ RecyclerView का इस्तेमाल करना होगा.

पहला चरण: व्यू मॉडल अपडेट करना

फ़िलहाल, व्यू मॉडल में एक _property LiveData है, जिसमें एक MarsProperty ऑब्जेक्ट है. यह वेब सेवा से मिले रिस्पॉन्स की सूची में पहला ऑब्जेक्ट है. इस चरण में, LiveData को बदलकर MarsProperty ऑब्जेक्ट की पूरी सूची को शामिल किया जाता है.

  1. overview/OverviewViewModel.kt खोलें.
  2. निजी _property वैरिएबल को _properties में बदलें. टाइप को MarsProperty ऑब्जेक्ट की सूची में बदलें.
private val _properties = MutableLiveData<List<MarsProperty>>()
  1. बाहरी property लाइव डेटा को properties से बदलें. इस सूची को यहां भी LiveData टाइप में जोड़ें:
 val properties: LiveData<List<MarsProperty>>
        get() = _properties
  1. नीचे की ओर स्क्रोल करके, getMarsRealEstateProperties() तरीके पर जाएं. try {} ब्लॉक में, पिछले टास्क में जोड़े गए पूरे टेस्ट को नीचे दी गई लाइन से बदलें. listResult वैरिएबल में MarsProperty ऑब्जेक्ट की सूची होती है. इसलिए, आपको _properties.value को सिर्फ़ इसे असाइन करना होगा. इसके लिए, आपको यह जांच करने की ज़रूरत नहीं है कि रिस्पॉन्स सही है या नहीं.
_properties.value = listResult

पूरा try/catch ब्लॉक अब ऐसा दिखता है:

try {
   var listResult = getPropertiesDeferred.await()
   _response.value = "Success: ${listResult.size} Mars properties retrieved"
   _properties.value = listResult
} catch (e: Exception) {
   _response.value = "Failure: ${e.message}"
}

दूसरा चरण: लेआउट और फ़्रैगमेंट अपडेट करना

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

  1. res/layout/gridview_item.xml खोलें. डेटा बाइंडिंग को OverviewViewModel से बदलकर MarsProperty करें. साथ ही, वैरिएबल का नाम बदलकर "property" करें.
<variable
   name="property"
   type="com.example.android.marsrealestate.network.MarsProperty" />
  1. <ImageView> में, app:imageUrl एट्रिब्यूट को बदलकर, MarsProperty ऑब्जेक्ट में मौजूद इमेज के यूआरएल का रेफ़रंस दें:
app:imageUrl="@{property.imgSrcUrl}"
  1. overview/OverviewFragment.kt खोलें. onCreateview() में, FragmentOverviewBinding को बढ़ाने वाली लाइन से टिप्पणी हटाएं. GridViewBinding को बढ़ाने वाली लाइन को मिटाएं या उस पर टिप्पणी करें. इन बदलावों से, पिछले टास्क में किए गए अस्थायी बदलावों को पहले जैसा कर दिया जाता है.
val binding = FragmentOverviewBinding.inflate(inflater)
 // val binding = GridViewItemBinding.inflate(inflater)
  1. res/layout/fragment_overview.xml खोलें. पूरे <TextView> एलिमेंट को मिटाएं.
  2. इसके बजाय, यह <RecyclerView> एलिमेंट जोड़ें. यह एक आइटम के लिए, GridLayoutManager और grid_view_item लेआउट का इस्तेमाल करता है:
<androidx.recyclerview.widget.RecyclerView
            android:id="@+id/photos_grid"
            android:layout_width="0dp"
            android:layout_height="0dp"
            android:padding="6dp"
            android:clipToPadding="false"
            app:layoutManager=
               "androidx.recyclerview.widget.GridLayoutManager"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:spanCount="2"
            tools:itemCount="16"
            tools:listitem="@layout/grid_view_item" />

तीसरा चरण: फ़ोटो ग्रिड अडैप्टर जोड़ना

अब fragment_overview लेआउट में एक RecyclerView है, जबकि grid_view_item लेआउट में एक ImageView है. इस चरण में, RecyclerView अडैप्टर के ज़रिए डेटा को RecyclerView से बाइंड किया जाता है.

  1. overview/PhotoGridAdapter.kt खोलें.
  2. नीचे दिए गए कंस्ट्रक्टर पैरामीटर के साथ PhotoGridAdapter क्लास बनाएं. PhotoGridAdapter क्लास, ListAdapter को बढ़ाता है. इसके कंस्ट्रक्टर को लिस्ट आइटम टाइप, व्यू होल्डर, और DiffUtil.ItemCallback लागू करने की ज़रूरत होती है.

    अनुरोध किए जाने पर, androidx.recyclerview.widget.ListAdapter और com.example.android.marsrealestate.network.MarsProperty क्लास इंपोर्ट करें. यहां दिए गए तरीके में, इस कंस्ट्रक्टर के अन्य छूटे हुए हिस्सों को लागू किया जाता है. इससे गड़बड़ियां ठीक हो जाती हैं.
class PhotoGridAdapter : ListAdapter<MarsProperty,
        PhotoGridAdapter.MarsPropertyViewHolder>(DiffCallback) {

}
  1. PhotoGridAdapter क्लास में कहीं भी क्लिक करें और Control+i दबाकर ListAdapter तरीकों को लागू करें. ये तरीके onCreateViewHolder() और onBindViewHolder() हैं.
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PhotoGridAdapter.MarsPropertyViewHolder {
   TODO("not implemented") 
}

override fun onBindViewHolder(holder: PhotoGridAdapter.MarsPropertyViewHolder, position: Int) {
   TODO("not implemented") 
}
  1. PhotoGridAdapter क्लास की परिभाषा के आखिर में, अभी जोड़े गए तरीकों के बाद, DiffCallback के लिए कंपैनियन ऑब्जेक्ट की परिभाषा जोड़ें. इसे नीचे दिखाया गया है.

    अनुरोध किए जाने पर androidx.recyclerview.widget.DiffUtil इंपोर्ट करें.

    DiffCallback ऑब्जेक्ट, DiffUtil.ItemCallback को उस ऑब्जेक्ट के टाइप के साथ बढ़ाता है जिससे आपको तुलना करनी है—MarsProperty.
companion object DiffCallback : DiffUtil.ItemCallback<MarsProperty>() {
}
  1. इस ऑब्जेक्ट के लिए, तुलना करने के तरीके लागू करने के लिए Control+i दबाएं. ये तरीके areItemsTheSame() और areContentsTheSame() हैं.
override fun areItemsTheSame(oldItem: MarsProperty, newItem: MarsProperty): Boolean {
   TODO("not implemented") 
}

override fun areContentsTheSame(oldItem: MarsProperty, newItem: MarsProperty): Boolean {
   TODO("not implemented") }
  1. areItemsTheSame() तरीके के लिए, TODO हटाएं. Kotlin के रेफ़रेंशियल इक्वैलिटी ऑपरेटर (===) का इस्तेमाल करें. यह ऑपरेटर, oldItem और newItem के लिए ऑब्जेक्ट रेफ़रंस एक ही होने पर true दिखाता है.
override fun areItemsTheSame(oldItem: MarsProperty, 
                  newItem: MarsProperty): Boolean {
   return oldItem === newItem
}
  1. areContentsTheSame() के लिए, oldItem और newItem के सिर्फ़ आईडी पर स्टैंडर्ड इक्वैलिटी ऑपरेटर का इस्तेमाल करें.
override fun areContentsTheSame(oldItem: MarsProperty, 
                  newItem: MarsProperty): Boolean {
   return oldItem.id == newItem.id
}
  1. अब भी PhotoGridAdapter क्लास में, कंपैनियन ऑब्जेक्ट के नीचे, MarsPropertyViewHolder के लिए इनर क्लास की परिभाषा जोड़ें. यह RecyclerView.ViewHolder से एक्सटेंड होती है.

    जब अनुरोध किया जाए, तब androidx.recyclerview.widget.RecyclerView और com.example.android.marsrealestate.databinding.GridViewItemBinding इंपोर्ट करें.

    आपको लेआउट में MarsProperty को बाइंड करने के लिए, GridViewItemBinding वैरिएबल की ज़रूरत होगी. इसलिए, वैरिएबल को MarsPropertyViewHolder में पास करें. बेस ViewHolder क्लास को अपने कंस्ट्रक्टर में व्यू की ज़रूरत होती है. इसलिए, इसे बाइंडिंग रूट व्यू पास किया जाता है.
class MarsPropertyViewHolder(private var binding: 
                   GridViewItemBinding):
       RecyclerView.ViewHolder(binding.root) {

}
  1. MarsPropertyViewHolder में, एक bind() तरीका बनाएं. यह MarsProperty ऑब्जेक्ट को आर्ग्युमेंट के तौर पर लेता है और binding.property को उस ऑब्जेक्ट पर सेट करता है. प्रॉपर्टी सेट करने के बाद, executePendingBindings() को कॉल करें. इससे अपडेट तुरंत लागू हो जाएगा.
fun bind(marsProperty: MarsProperty) {
   binding.property = marsProperty
   binding.executePendingBindings()
}
  1. onCreateViewHolder() में, TODO को हटाएं और यहां दिखाई गई लाइन जोड़ें. अनुरोध किए जाने पर, android.view.LayoutInflater इंपोर्ट करें.

    onCreateViewHolder() तरीके को एक नया MarsPropertyViewHolder वापस करना होगा. इसे GridViewItemBinding को बड़ा करके और पैरंट ViewGroup कॉन्टेक्स्ट से LayoutInflater का इस्तेमाल करके बनाया जाता है.
   return MarsPropertyViewHolder(GridViewItemBinding.inflate(
      LayoutInflater.from(parent.context)))
  1. onBindViewHolder() तरीके में, TODO को हटाएं और नीचे दी गई लाइनें जोड़ें. यहां getItem() को कॉल करके, मौजूदा RecyclerView पोज़िशन से जुड़ा MarsProperty ऑब्जेक्ट मिलता है. इसके बाद, उस प्रॉपर्टी को MarsPropertyViewHolder में मौजूद bind() तरीके से पास किया जाता है.
val marsProperty = getItem(position)
holder.bind(marsProperty)

चौथा चरण: बाइंडिंग अडैप्टर जोड़ना और हिस्सों को कनेक्ट करना

आखिर में, MarsProperty ऑब्जेक्ट की सूची के साथ PhotoGridAdapter को शुरू करने के लिए, BindingAdapter का इस्तेमाल करें. RecyclerView डेटा सेट करने के लिए BindingAdapter का इस्तेमाल करने से, डेटा बाइंडिंग अपने-आप MarsProperty ऑब्जेक्ट की सूची के लिए LiveData को मॉनिटर करती है. इसके बाद, MarsProperty सूची में बदलाव होने पर, बाइंडिंग अडैप्टर अपने-आप कॉल हो जाता है.

  1. BindingAdapters.kt खोलें.
  2. फ़ाइल के आखिर में, एक bindRecyclerView() तरीका जोड़ें. यह RecyclerView और MarsProperty ऑब्जेक्ट की सूची को आर्ग्युमेंट के तौर पर लेता है. उस तरीके को @BindingAdapter के साथ एनोटेट करें.

    अनुरोध किए जाने पर, androidx.recyclerview.widget.RecyclerView और com.example.android.marsrealestate.network.MarsProperty इंपोर्ट करें.
@BindingAdapter("listData")
fun bindRecyclerView(recyclerView: RecyclerView, 
    data: List<MarsProperty>?) {
}
  1. bindRecyclerView() फ़ंक्शन में, recyclerView.adapter को PhotoGridAdapter में बदलें. इसके बाद, डेटा के साथ adapter.submitList() को कॉल करें. इससे RecyclerView को पता चलता है कि नई सूची कब उपलब्ध है.

अनुरोध किए जाने पर, com.example.android.marsrealestate.overview.PhotoGridAdapter इंपोर्ट करें.

val adapter = recyclerView.adapter as PhotoGridAdapter
adapter.submitList(data)
  1. res/layout/fragment_overview.xml खोलें. RecyclerView एलिमेंट में app:listData एट्रिब्यूट जोड़ें और डेटा बाइंडिंग का इस्तेमाल करके, इसे viewmodel.properties पर सेट करें.
app:listData="@{viewModel.properties}"
  1. overview/OverviewFragment.kt खोलें. onCreateView() में, setHasOptionsMenu() को कॉल करने से ठीक पहले, binding.photosGrid में RecyclerView अडैप्टर को नए PhotoGridAdapter ऑब्जेक्ट के तौर पर शुरू करें.
binding.photosGrid.adapter = PhotoGridAdapter()
  1. ऐप्लिकेशन चलाएं. आपको MarsProperty इमेज का ग्रिड दिखेगा. नई इमेज देखने के लिए स्क्रोल करते समय, ऐप्लिकेशन इमेज दिखाने से पहले लोडिंग-प्रोग्रेस आइकॉन दिखाता है. एयरप्लेन मोड चालू करने पर, जो इमेज अभी तक लोड नहीं हुई हैं वे टूटी हुई इमेज के आइकॉन के तौर पर दिखती हैं.

MarsRealEstate ऐप्लिकेशन, इमेज फ़ेच न हो पाने पर टूटी हुई इमेज का आइकॉन दिखाता है. हालांकि, नेटवर्क न होने पर ऐप्लिकेशन में खाली स्क्रीन दिखती है.

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

पहला चरण: व्यू मॉडल में स्टेटस जोड़ना

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

  1. overview/OverviewViewModel.kt खोलें. फ़ाइल में सबसे ऊपर (इंपोर्ट के बाद, क्लास डेफ़िनिशन से पहले), सभी उपलब्ध स्टेटस दिखाने के लिए enum जोड़ें:
enum class MarsApiStatus { LOADING, ERROR, DONE }
  1. पूरी OverviewViewModel क्लास में, इंटरनल और एक्सटर्नल, दोनों तरह के _response लाइव डेटा डेफ़िनिशन का नाम बदलकर _status करें. आपने इस कोडलैब में पहले ही _properties LiveData के लिए सहायता जोड़ी थी. इसलिए, वेब सेवा के पूरे जवाब का इस्तेमाल नहीं किया गया है. मौजूदा स्थिति को ट्रैक करने के लिए, आपको यहां LiveData की ज़रूरत होगी. इसलिए, मौजूदा वैरिएबल का नाम बदला जा सकता है.

साथ ही, टाइप को String से बदलकर MarsApiStatus. करें

private val _status = MutableLiveData<MarsApiStatus>()

val status: LiveData<MarsApiStatus>
   get() = _status
  1. नीचे की ओर स्क्रोल करके getMarsRealEstateProperties() तरीके पर जाएं और यहां भी _response को _status पर अपडेट करें. "Success" स्ट्रिंग को MarsApiStatus.DONE स्थिति में और "Failure" स्ट्रिंग को MarsApiStatus.ERROR स्थिति में बदलें.
  2. await() को कॉल करने से पहले, try {} ब्लॉक के सबसे ऊपर MarsApiStatus.LOADING स्टेटस जोड़ें. यह शुरुआती स्टेटस है. यह तब दिखता है, जब कोरूटीन चल रहा हो और डेटा का इंतज़ार किया जा रहा हो. पूरा try/catch {} ब्लॉक अब ऐसा दिखता है:
try {
    _status.value = MarsApiStatus.LOADING
   var listResult = getPropertiesDeferred.await()
   _status.value = MarsApiStatus.DONE
   _properties.value = listResult
} catch (e: Exception) {
   _status.value = MarsApiStatus.ERROR
}
  1. catch {} ब्लॉक में गड़बड़ी की स्थिति के बाद, _properties LiveData को खाली सूची पर सेट करें. इससे RecyclerView मिट जाता है.
} catch (e: Exception) {
   _status.value = MarsApiStatus.ERROR
   _properties.value = ArrayList()
}

दूसरा चरण: स्टेटस ImageView के लिए बाइंडिंग अडैप्टर जोड़ना

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

  1. BindingAdapters.kt खोलें. bindStatus() नाम का एक नया बाइंडिंग अडैप्टर जोड़ें. यह ImageView और MarsApiStatus वैल्यू को आर्ग्युमेंट के तौर पर लेता है. अनुरोध किए जाने पर, com.example.android.marsrealestate.overview.MarsApiStatus इंपोर्ट करें.
@BindingAdapter("marsApiStatus")
fun bindStatus(statusImageView: ImageView, 
          status: MarsApiStatus?) {
}
  1. अलग-अलग स्टेटस के बीच स्विच करने के लिए, bindStatus() तरीके में when {} जोड़ें.
when (status) {

}
  1. when {} के अंदर, लोडिंग की स्थिति (MarsApiStatus.LOADING) के लिए एक केस जोड़ें. इस स्थिति के लिए, ImageView को 'दिख रहा है' पर सेट करें और इसे लोडिंग ऐनिमेशन असाइन करें. यह वही ऐनिमेशन ड्रॉएबल है जिसका इस्तेमाल आपने पिछले टास्क में Glide के लिए किया था. अनुरोध किए जाने पर, android.view.View इंपोर्ट करें.
when (status) {
   MarsApiStatus.LOADING -> {
      statusImageView.visibility = View.VISIBLE
      statusImageView.setImageResource(R.drawable.loading_animation)
   }
}
  1. गड़बड़ी की स्थिति के लिए एक केस जोड़ें, जो कि MarsApiStatus.ERROR है. LOADING राज्य के लिए किए गए काम की तरह ही, स्टेटस ImageView को 'दिखे' पर सेट करें और कनेक्शन की गड़बड़ी वाले ड्रॉएबल का फिर से इस्तेमाल करें.
MarsApiStatus.ERROR -> {
   statusImageView.visibility = View.VISIBLE
   statusImageView.setImageResource(R.drawable.ic_connection_error)
}
  1. 'हो गया' स्थिति के लिए एक केस जोड़ें, जो कि MarsApiStatus.DONE है. यहां आपको सही जवाब मिला है. इसलिए, स्टेटस ImageView की दृश्यता बंद करके इसे छिपाएं.
MarsApiStatus.DONE -> {
   statusImageView.visibility = View.GONE
}

तीसरा चरण: लेआउट में status ImageView जोड़ना

  1. res/layout/fragment_overview.xml खोलें. RecyclerView एलिमेंट के नीचे, ConstraintLayout के अंदर, यहां दिखाया गया ImageView जोड़ें.

    इस ImageView पर वही पाबंदियां लागू होती हैं जो RecyclerView पर लागू होती हैं. हालांकि, चौड़ाई और ऊंचाई के लिए wrap_content का इस्तेमाल किया जाता है, ताकि इमेज को बीच में रखा जा सके. इमेज को स्ट्रेच करके व्यू को भरने के लिए इसका इस्तेमाल नहीं किया जाता. app:marsApiStatus एट्रिब्यूट पर भी ध्यान दें. व्यू मॉडल में स्टेटस प्रॉपर्टी बदलने पर, यह आपके BindingAdapter को व्यू कॉल करता है.
<ImageView
   android:id="@+id/status_image"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toRightOf="parent"
    app:layout_constraintTop_toTopOf="parent"
    app:marsApiStatus="@{viewModel.status}" />
  1. नेटवर्क कनेक्शन न होने की स्थिति को सिम्युलेट करने के लिए, अपने एम्युलेटर या डिवाइस में हवाई जहाज़ मोड चालू करें. ऐप्लिकेशन को कंपाइल और रन करें. देखें कि गड़बड़ी वाली इमेज दिख रही है:

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

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

  • इमेज को मैनेज करने की प्रोसेस को आसान बनाने के लिए, Glide लाइब्रेरी का इस्तेमाल करें. इससे आपके ऐप्लिकेशन में इमेज डाउनलोड, बफ़र, डिकोड, और कैश मेमोरी में सेव की जा सकती हैं.
  • इंटरनेट से इमेज लोड करने के लिए, Glide को दो चीज़ों की ज़रूरत होती है: इमेज का यूआरएल और इमेज को रखने के लिए ImageView ऑब्जेक्ट. इन विकल्पों को तय करने के लिए, Glide के साथ load() और into() तरीकों का इस्तेमाल करें.
  • बाइंडिंग अडैप्टर, एक्सटेंशन के ऐसे तरीके होते हैं जो व्यू और उस व्यू के बाउंड डेटा के बीच में काम करते हैं. बाइंडिंग अडैप्टर, डेटा में बदलाव होने पर कस्टम व्यवहार करते हैं. उदाहरण के लिए, किसी यूआरएल से इमेज को ImageView में लोड करने के लिए Glide को कॉल करना.
  • बाइंडिंग अडैप्टर, एक्सटेंशन के ऐसे तरीके होते हैं जिन्हें @BindingAdapter एनोटेशन के साथ एनोटेट किया जाता है.
  • Glide अनुरोध में विकल्प जोड़ने के लिए, apply() तरीके का इस्तेमाल करें. उदाहरण के लिए, लोडिंग ड्रॉएबल के बारे में बताने के लिए, placeholder() के साथ apply() का इस्तेमाल करें. साथ ही, गड़बड़ी वाले ड्रॉएबल के बारे में बताने के लिए, error() के साथ apply() का इस्तेमाल करें.
  • इमेज की ग्रिड बनाने के लिए, GridLayoutManager के साथ RecyclerView का इस्तेमाल करें.
  • प्रॉपर्टी की सूची में बदलाव होने पर उसे अपडेट करने के लिए, RecyclerView और लेआउट के बीच बाइंडिंग अडैप्टर का इस्तेमाल करें.

Udacity का कोर्स:

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

अन्य:

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

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

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

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

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

पहला सवाल

लोड की गई इमेज को शामिल करने वाले ImageView को दिखाने के लिए, Glide के किस तरीके का इस्तेमाल किया जाता है?

into()

with()

imageview()

apply()

दूसरा सवाल

Glide के लोड होने के दौरान दिखने वाली प्लेसहोल्डर इमेज कैसे तय की जाती है?

▢ ड्रॉ किए जा सकने वाले ऑब्जेक्ट के साथ into() तरीके का इस्तेमाल करें.

RequestOptions() का इस्तेमाल करें और placeholder() तरीके को ड्रॉएबल के साथ कॉल करें.

Glide.placeholder प्रॉपर्टी को किसी ड्रॉएबल को असाइन करें.

RequestOptions() का इस्तेमाल करें और loadingImage() तरीके को ड्रॉएबल के साथ कॉल करें.

तीसरा सवाल

किसी तरीके को बाइंडिंग अडैप्टर के तौर पर कैसे दिखाया जाता है?

LiveData पर setBindingAdapter() वाले तरीके को कॉल करें.

▢ इस तरीके को BindingAdapters.kt नाम की Kotlin फ़ाइल में डालें.

▢ एक्सएमएल लेआउट में android:adapter एट्रिब्यूट का इस्तेमाल करें.

@BindingAdapter का इस्तेमाल करके, तरीके के बारे में एनोटेशन जोड़ें.

अगला सबक शुरू करें: 8.3 इंटरनेट डेटा की मदद से फ़िल्टर करना और ज़्यादा जानकारी वाले व्यू देखना

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