Hilt के साथ डिपेंडेंसी इंजेक्शन

Hilt, Android के लिए डिपेंडेंसी इंजेक्शन लाइब्रेरी है. यह आपके प्रोजेक्ट में मैन्युअल तरीके से डिपेंडेंसी इंजेक्शन करने के बॉयलरप्लेट को कम करती है. मैन्युअल डिपेंडेंसी इंजेक्शन करने के लिए, आपको हर क्लास और उसकी डिपेंडेंसी को मैन्युअल तरीके से बनाना होगा. साथ ही, डिपेंडेंसी को फिर से इस्तेमाल करने और मैनेज करने के लिए, कंटेनर का इस्तेमाल करना होगा.

Hilt, आपके ऐप्लिकेशन में डीआई का इस्तेमाल करने का एक स्टैंडर्ड तरीका उपलब्ध कराता है. इसके लिए, यह आपके प्रोजेक्ट में मौजूद हर Android क्लास के लिए कंटेनर उपलब्ध कराता है और उनकी लाइफ़साइकल को अपने-आप मैनेज करता है. Hilt को लोकप्रिय डीआई लाइब्रेरी Dagger के ऊपर बनाया गया है, ताकि कंपाइल के समय सही होने, रनटाइम की परफ़ॉर्मेंस, बढ़ाए जा सकने की योग्यता, और Android Studio की सहायता का फ़ायदा मिल सके. ये सुविधाएं Dagger उपलब्ध कराता है. ज़्यादा जानकारी के लिए, Hilt और Dagger देखें.

इस गाइड में, Hilt और इसके जनरेट किए गए कंटेनर के बुनियादी सिद्धांतों के बारे में बताया गया है. इसमें यह भी दिखाया गया है कि Hilt का इस्तेमाल करने के लिए, किसी मौजूदा ऐप्लिकेशन को बूटस्ट्रैप कैसे किया जाता है.

डिपेंडेंसी जोड़ना

सबसे पहले, अपने प्रोजेक्ट की रूट build.gradle फ़ाइल में hilt-android-gradle-plugin प्लगिन जोड़ें:

Kotlin

plugins {
  ...
  id("com.google.dagger.hilt.android") version "2.57.1" apply false
}

Groovy

plugins {
  ...
  id 'com.google.dagger.hilt.android' version '2.57.1' apply false
}

इसके बाद, Gradle प्लगिन लागू करें और अपनी app/build.gradle फ़ाइल में ये डिपेंडेंसी जोड़ें:

Kotlin

plugins {
  id("com.google.devtools.ksp")
  id("com.google.dagger.hilt.android")
}

android {
  ...
}

dependencies {
  implementation("com.google.dagger:hilt-android:2.57.1")
  ksp("com.google.dagger:hilt-android-compiler:2.57.1")
}

Groovy

...
plugins {
  id 'com.google.devtools.ksp'
  id 'com.google.dagger.hilt.android'
}

android {
  ...
}

dependencies {
  implementation "com.google.dagger:hilt-android:2.57.1"
  ksp "com.google.dagger:hilt-compiler:2.57.1"
}

यह पक्का करने के लिए कि आपका प्रोजेक्ट Java 17 के लिए कॉन्फ़िगर किया गया है, app/build.gradle फ़ाइल में यह कोड जोड़ें. Jetpack Compose और Hilt के वर्शन के लिए, Java 17 ज़रूरी है:

Kotlin

android {
  ...
  compileOptions {
    sourceCompatibility = JavaVersion.VERSION_17
    targetCompatibility = JavaVersion.VERSION_17
  }
}

Groovy

android {
  ...
  compileOptions {
    sourceCompatibility JavaVersion.VERSION_17
    targetCompatibility JavaVersion.VERSION_17
  }
}

Hilt ऐप्लिकेशन क्लास

Hilt का इस्तेमाल करने वाले सभी ऐप्लिकेशन में, Application क्लास होनी चाहिए. साथ ही, इसमें @HiltAndroidApp एनोटेशन होना चाहिए.

@HiltAndroidApp, Hilt के कोड जनरेशन को ट्रिगर करता है. इसमें आपके ऐप्लिकेशन के लिए एक बेस क्लास शामिल होती है, जो ऐप्लिकेशन-लेवल डिपेंडेंसी कंटेनर के तौर पर काम करती है.

@HiltAndroidApp
class ExampleApplication : Application() { ... }

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

Android क्लास में डिपेंडेंसी इंजेक्ट करना

Application क्लास में Hilt सेट अप करने और ऐप्लिकेशन-लेवल कॉम्पोनेंट उपलब्ध होने के बाद, Hilt उन अन्य Android क्लास को डिपेंडेंसी उपलब्ध करा सकता है जिनमें @AndroidEntryPoint एनोटेशन मौजूद है:

@AndroidEntryPoint
class ExampleActivity : ComponentActivity() { ... }

फ़िलहाल, Hilt इन Android क्लास के साथ काम करता है:

  • Application (@HiltAndroidApp का इस्तेमाल करके)
  • ViewModel (@HiltViewModel का इस्तेमाल करके)
  • Activity
  • Service
  • BroadcastReceiver

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

@AndroidEntryPoint आपके प्रोजेक्ट में मौजूद हर Android क्लास के लिए, एक अलग Hilt कॉम्पोनेंट जनरेट करता है. इन कॉम्पोनेंट को अपनी पैरंट क्लास से डिपेंडेंसी मिल सकती हैं. इसके बारे में कॉम्पोनेंट की हैरारकी में बताया गया है.

किसी कॉम्पोनेंट से डिपेंडेंसी पाने के लिए, फ़ील्ड इंजेक्शन की मदद से @Inject एनोटेशन का इस्तेमाल करें:

@AndroidEntryPoint
class ExampleActivity : ComponentActivity() {

  @Inject lateinit var analytics: AnalyticsAdapter
  ...
}

Hilt जिन क्लास को इंजेक्ट करता है उनमें अन्य बेस क्लास भी हो सकती हैं. ये क्लास भी इंजेक्शन का इस्तेमाल करती हैं. अगर वे क्लास ऐब्स्ट्रैक्ट हैं, तो उन्हें @AndroidEntryPoint एनोटेशन की ज़रूरत नहीं होती.

Android क्लास में किस लाइफ़साइकल कॉलबैक को इंजेक्ट किया जाता है, इस बारे में ज़्यादा जानने के लिए, कॉम्पोनेंट लाइफ़टाइम देखें.

Hilt बाइंडिंग तय करना

फ़ील्ड इंजेक्शन की प्रोसेस पूरी करने के लिए, Hilt को यह जानना ज़रूरी है कि कॉम्पोनेंट से ज़रूरी डिपेंडेंसी के इंस्टेंस कैसे उपलब्ध कराए जाएं. बाइंडिंग में, डिपेंडेंसी के तौर पर किसी टाइप के इंस्टेंस देने के लिए ज़रूरी जानकारी होती है.

Hilt को बाइंडिंग की जानकारी देने का एक तरीका कंस्ट्रक्टर इंजेक्शन है. किसी क्लास के कंस्ट्रक्टर पर @Inject एनोटेशन का इस्तेमाल करके, Hilt को बताएं कि उस क्लास के इंस्टेंस कैसे उपलब्ध कराए जाएं:

class AnalyticsAdapter @Inject constructor(
  private val service: AnalyticsService
) { ... }

किसी क्लास के एनोटेट किए गए कंस्ट्रक्टर के पैरामीटर, उस क्लास की डिपेंडेंसी होते हैं. उदाहरण के लिए, AnalyticsAdapter डाइमेंशन, AnalyticsService पर निर्भर करता है. इसलिए, Hilt को यह भी पता होना चाहिए कि AnalyticsService के इंस्टेंस कैसे उपलब्ध कराए जाएं.

Hilt मॉड्यूल

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

Hilt मॉड्यूल एक ऐसी क्लास है जिसमें @Module एनोटेशन जोड़ा गया है. यह Hilt को ऐसे टाइप के इंस्टेंस बनाने के निर्देश देता है जिन्हें कंस्ट्रक्टर इंजेक्शन के ज़रिए उपलब्ध नहीं कराया जा सकता. जैसे, इंटरफ़ेस या तीसरे पक्ष की क्लास. आपको हर मॉड्यूल को @InstallIn के साथ एनोटेट करना होगा, ताकि Hilt को पता चल सके कि हर मॉड्यूल का इस्तेमाल किस Android क्लास में किया जाएगा या उसे किस क्लास में इंस्टॉल किया जाएगा.

Hilt मॉड्यूल में उपलब्ध कराई गई डिपेंडेंसी, जनरेट किए गए उन सभी कॉम्पोनेंट में उपलब्ध होती हैं जो उस Android क्लास से जुड़े होते हैं जिसमें आपने Hilt मॉड्यूल इंस्टॉल किया है.

@Binds का इस्तेमाल करके, इंटरफ़ेस के इंस्टेंस इंजेक्ट करना

AnalyticsService का उदाहरण देखें. अगर AnalyticsService एक इंटरफ़ेस है, तो इसे कंस्ट्रक्टर-इंजेक्ट नहीं किया जा सकता. इसके बजाय, Hilt मॉड्यूल में @Binds के साथ एनोटेट किया गया ऐब्स्ट्रैक्ट फ़ंक्शन बनाकर, Hilt को बाइंडिंग की जानकारी दें.

@Binds एनोटेशन, Hilt को बताता है कि इंटरफ़ेस का इंस्टेंस उपलब्ध कराने के लिए, किस इंप्लिमेंटेशन का इस्तेमाल करना है.

एनोटेट किया गया फ़ंक्शन, Hilt को यह जानकारी देता है:

  • फ़ंक्शन का रिटर्न टाइप, Hilt को बताता है कि फ़ंक्शन किस इंटरफ़ेस के इंस्टेंस उपलब्ध कराता है.
  • फ़ंक्शन पैरामीटर, Hilt को बताता है कि कौनसी डिपेंडेंसी इंजेक्ट करनी है.
interface AnalyticsService {
  fun analyticsMethods()
}

// Constructor-injected, because Hilt needs to know how to
// provide instances of AnalyticsServiceImpl, too.
class AnalyticsServiceImpl @Inject constructor(
  ...
) : AnalyticsService { ... }

@Module
@InstallIn(ActivityComponent::class)
abstract class AnalyticsModule {

  @Binds
  abstract fun bindAnalyticsService(
    analyticsServiceImpl: AnalyticsServiceImpl
  ): AnalyticsService
}

Hilt मॉड्यूल AnalyticsModule को @InstallIn(ActivityComponent.class) के साथ एनोटेट किया गया है, क्योंकि आपको Hilt से उस डिपेंडेंसी को ExampleActivity में इंजेक्ट कराना है. इस एनोटेशन का मतलब है कि AnalyticsModule में मौजूद सभी डिपेंडेंसी, ऐप्लिकेशन की सभी गतिविधियों में उपलब्ध हैं.

@Provides के साथ इंस्टेंस इंजेक्ट करना

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

पिछले उदाहरण को देखें. अगर आपके पास AnalyticsService क्लास का मालिकाना हक नहीं है, तो Hilt को इस टाइप के इंस्टेंस उपलब्ध कराने का तरीका बताया जा सकता है. इसके लिए, Hilt मॉड्यूल में एक फ़ंक्शन बनाएं और उस फ़ंक्शन को @Provides के साथ एनोटेट करें.

एनोटेट किया गया फ़ंक्शन, Hilt को यह जानकारी देता है:

  • फ़ंक्शन का रिटर्न टाइप, Hilt को बताता है कि फ़ंक्शन किस टाइप के इंस्टेंस उपलब्ध कराता है.
  • फ़ंक्शन पैरामीटर, Hilt को उससे जुड़े टाइप की डिपेंडेंसी के बारे में बताते हैं.
  • फ़ंक्शन बॉडी, Hilt को बताती है कि उससे जुड़े टाइप का इंस्टेंस कैसे उपलब्ध कराया जाए. Hilt, फ़ंक्शन बॉडी को हर बार तब एक्ज़ीक्यूट करता है, जब उसे उस टाइप का कोई इंस्टेंस उपलब्ध कराना होता है.
@Module
@InstallIn(ActivityComponent::class)
object AnalyticsModule {

  @Provides
  fun provideAnalyticsService(
    // Potential dependencies of this type
  ): AnalyticsService {
      return Retrofit.Builder()
               .baseUrl("https://example.com")
               .build()
               .create(AnalyticsService::class.java)
  }
}

एक ही टाइप के लिए कई बाइंडिंग उपलब्ध कराना

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

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

इस उदाहरण को देखें. अगर आपको AnalyticsService पर किए जाने वाले कॉल को इंटरसेप्ट करना है, तो इंटरसेप्टर के साथ OkHttpClient ऑब्जेक्ट का इस्तेमाल किया जा सकता है. अन्य सेवाओं के लिए, आपको कॉल को किसी दूसरे तरीके से इंटरसेप्ट करना पड़ सकता है. ऐसे में, आपको Hilt को यह बताना होगा कि OkHttpClient के दो अलग-अलग इंप्लिमेंटेशन कैसे उपलब्ध कराए जाएं.

सबसे पहले, उन क्वालिफ़ायर को तय करें जिनका इस्तेमाल @Binds या @Provides तरीकों के बारे में जानकारी देने के लिए किया जाएगा:

@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class AuthInterceptorOkHttpClient

@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class OtherInterceptorOkHttpClient

इसके बाद, Hilt को यह जानना होगा कि हर क्वालिफ़ायर से जुड़े टाइप का इंस्टेंस कैसे उपलब्ध कराया जाए. इस मामले में, @Provides के साथ Hilt मॉड्यूल का इस्तेमाल किया जा सकता है. दोनों फ़ंक्शन का रिटर्न टाइप एक ही है, लेकिन क्वालिफ़ायर उन्हें दो अलग-अलग बाइंडिंग के तौर पर लेबल करते हैं:

@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {

  @AuthInterceptorOkHttpClient
  @Provides
  fun provideAuthInterceptorOkHttpClient(
    authInterceptor: AuthInterceptor
  ): OkHttpClient {
      return OkHttpClient.Builder()
               .addInterceptor(authInterceptor)
               .build()
  }

  @OtherInterceptorOkHttpClient
  @Provides
  fun provideOtherInterceptorOkHttpClient(
    otherInterceptor: OtherInterceptor
  ): OkHttpClient {
      return OkHttpClient.Builder()
               .addInterceptor(otherInterceptor)
               .build()
  }
}

फ़ील्ड या पैरामीटर को उससे जुड़े क्वालीफ़ायर के साथ एनोटेट करके, अपनी ज़रूरत के हिसाब से खास टाइप इंजेक्ट किया जा सकता है:

// As a dependency of another class.
@Module
@InstallIn(ActivityComponent::class)
object AnalyticsModule {

  @Provides
  fun provideAnalyticsService(
    @AuthInterceptorOkHttpClient okHttpClient: OkHttpClient
  ): AnalyticsService {
      return Retrofit.Builder()
               .baseUrl("https://example.com")
               .client(okHttpClient)
               .build()
               .create(AnalyticsService::class.java)
  }
}

// As a dependency of a constructor-injected class.
class ExampleServiceImpl @Inject constructor(
  @AuthInterceptorOkHttpClient private val okHttpClient: OkHttpClient
) : ...

// At field injection.
@AndroidEntryPoint
class ExampleActivity: ComponentActivity() {

  @AuthInterceptorOkHttpClient
  @Inject lateinit var okHttpClient: OkHttpClient
}

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

Hilt में पहले से तय किए गए क्वालिफ़ायर

Hilt, पहले से तय किए गए कुछ क्वालिफ़ायर उपलब्ध कराता है. उदाहरण के लिए, आपको ऐप्लिकेशन या गतिविधि से Context क्लास की ज़रूरत पड़ सकती है. इसलिए, Hilt आपको @ApplicationContext और @ActivityContext क्वालीफ़ायर उपलब्ध कराता है.

मान लें कि उदाहरण में दी गई AnalyticsAdapter क्लास को गतिविधि के कॉन्टेक्स्ट की ज़रूरत है. यहां दिए गए कोड से पता चलता है कि AnalyticsAdapter को गतिविधि का कॉन्टेक्स्ट कैसे दिया जाता है:

class AnalyticsAdapter @Inject constructor(
    @ActivityContext private val context: Context,
    private val service: AnalyticsService
) { ... }

Hilt में उपलब्ध अन्य पहले से तय बाइंडिंग के लिए, कॉम्पोनेंट की डिफ़ॉल्ट बाइंडिंग देखें.

Android क्लास के लिए जनरेट किए गए कॉम्पोनेंट

हर उस Android क्लास के लिए जिसमें फ़ील्ड इंजेक्शन किया जा सकता है, उससे जुड़ा एक Hilt कॉम्पोनेंट होता है. इसे @InstallIn एनोटेशन में रेफ़र किया जा सकता है. हर Hilt कॉम्पोनेंट, अपनी बाइंडिंग को उससे जुड़ी Android क्लास में इंजेक्ट करने के लिए ज़िम्मेदार होता है.

पिछले उदाहरणों में, Hilt मॉड्यूल में ActivityComponent का इस्तेमाल दिखाया गया है.

Hilt में ये कॉम्पोनेंट होते हैं:

Hilt कॉम्पोनेंट इस इवेंट को यह इंजेक्ट करता है:
SingletonComponent Application
ActivityRetainedComponent लागू नहीं
ViewModelComponent ViewModel
ActivityComponent Activity
ServiceComponent Service

कॉम्पोनेंट की लाइफ़टाइम

Hilt, जनरेट की गई कॉम्पोनेंट क्लास के इंस्टेंस अपने-आप बनाता है और उन्हें खत्म करता है. इसके लिए, वह Android क्लास की लाइफ़साइकल को फ़ॉलो करता है.

जनरेट किया गया कॉम्पोनेंट मैसेज किस समय लिखा गया खत्म होने का समय
SingletonComponent Application#onCreate() Application को नष्ट किया गया
ActivityRetainedComponent Activity#onCreate() Activity#onDestroy()
ViewModelComponent ViewModel बनाया गया ViewModel को नष्ट किया गया
ActivityComponent Activity#onCreate() Activity#onDestroy()
ServiceComponent Service#onCreate() Service#onDestroy()

कॉम्पोनेंट के स्कोप

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

उदाहरण के लिए, जब भी Hilt किसी अन्य टाइप को डिपेंडेंसी के तौर पर AnalyticsAdapter उपलब्ध कराता है या फ़ील्ड इंजेक्शन (ExampleActivity की तरह) के ज़रिए उपलब्ध कराता है, तो Hilt, AnalyticsAdapter का एक नया इंस्टेंस उपलब्ध कराता है.

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

नीचे दी गई टेबल में, जनरेट किए गए हर कॉम्पोनेंट के लिए स्कोप एनोटेशन की सूची दी गई है:

Android क्लास जनरेट किया गया कॉम्पोनेंट दायरा
Application SingletonComponent @Singleton
Activity ActivityRetainedComponent @ActivityRetainedScoped
ViewModel ViewModelComponent @ViewModelScoped
Activity ActivityComponent @ActivityScoped
Service ServiceComponent @ServiceScoped

उदाहरण के लिए, अगर @ActivityScoped का इस्तेमाल करके AnalyticsAdapter को ActivityComponent तक सीमित किया जाता है, तो Hilt, संबंधित गतिविधि के दौरान AnalyticsAdapter का एक ही इंस्टेंस उपलब्ध कराता है:

@ActivityScoped
class AnalyticsAdapter @Inject constructor(
  private val service: AnalyticsService
) { ... }

मान लें कि AnalyticsService में एक इंटरनल स्टेट है, जिसके लिए हर बार एक ही इंस्टेंस का इस्तेमाल करना ज़रूरी है. यह इंस्टेंस, न सिर्फ़ ExampleActivity में, बल्कि ऐप्लिकेशन में कहीं भी इस्तेमाल किया जा सकता है. इस मामले में, AnalyticsService को SingletonComponent के दायरे में रखना सही है. इसका नतीजा यह होता है कि जब भी कॉम्पोनेंट को AnalyticsService का कोई इंस्टेंस देना होता है, तो वह हर बार एक ही इंस्टेंस देता है.

यहां दिए गए उदाहरण में, Hilt मॉड्यूल में किसी कॉम्पोनेंट के लिए बाइंडिंग को स्कोप करने का तरीका बताया गया है. बाइंडिंग का स्कोप, उस कॉम्पोनेंट के स्कोप से मेल खाना चाहिए जिसमें उसे इंस्टॉल किया गया है. इसलिए, इस उदाहरण में आपको ActivityComponent के बजाय SingletonComponent में AnalyticsService इंस्टॉल करना होगा:

// If AnalyticsService is an interface.
@Module
@InstallIn(SingletonComponent::class)
abstract class AnalyticsModule {

  @Singleton
  @Binds
  abstract fun bindAnalyticsService(
    analyticsServiceImpl: AnalyticsServiceImpl
  ): AnalyticsService
}

// If you don't own AnalyticsService.
@Module
@InstallIn(SingletonComponent::class)
object AnalyticsModule {

  @Singleton
  @Provides
  fun provideAnalyticsService(): AnalyticsService {
      return Retrofit.Builder()
               .baseUrl("https://example.com")
               .build()
               .create(AnalyticsService::class.java)
  }
}

Hilt कॉम्पोनेंट के स्कोप के बारे में ज़्यादा जानने के लिए, Android और Hilt में स्कोप तय करना लेख पढ़ें.

कॉम्पोनेंट की हैरारकी

किसी कॉम्पोनेंट में मॉड्यूल इंस्टॉल करने से, उसकी बाइंडिंग को उस कॉम्पोनेंट में मौजूद अन्य बाइंडिंग की डिपेंडेंसी के तौर पर ऐक्सेस किया जा सकता है. इसके अलावा, कॉम्पोनेंट की हैरारकी में उसके नीचे मौजूद किसी भी चाइल्ड कॉम्पोनेंट में भी बाइंडिंग को ऐक्सेस किया जा सकता है.

ActivityComponent,
    ActivityRetainedComponent के अंदर होता है. ViewModelComponent, ActivityRetainedComponent के अंदर होता है. ActivityRetainedComponent और ServiceComponent, SingletonComponent के अंदर होते हैं.
पहली इमेज. Hilt से जनरेट होने वाले कॉम्पोनेंट की हैरारकी.

कॉम्पोनेंट की डिफ़ॉल्ट बाइंडिंग

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

Android कॉम्पोनेंट डिफ़ॉल्ट बाइंडिंग
SingletonComponent Application
ActivityRetainedComponent Application
ViewModelComponent SavedStateHandle
ActivityComponent Application, Activity
ServiceComponent Application, Service

ऐप्लिकेशन कॉन्टेक्स्ट बाइंडिंग, @ApplicationContext का इस्तेमाल करके भी उपलब्ध है. उदाहरण के लिए:

class AnalyticsServiceImpl @Inject constructor(
  @ApplicationContext context: Context
) : AnalyticsService { ... }

// The Application binding is available without qualifiers.
class AnalyticsServiceImpl @Inject constructor(
  application: Application
) : AnalyticsService { ... }

@ActivityContext का इस्तेमाल करके, गतिविधि के कॉन्टेक्स्ट को भी बाइंड किया जा सकता है. उदाहरण के लिए:

class AnalyticsAdapter @Inject constructor(
  @ActivityContext context: Context
) { ... }

// The Activity binding is available without qualifiers.
class AnalyticsAdapter @Inject constructor(
  activity: ComponentActivity
) { ... }

उन क्लास में डिपेंडेंसी इंजेक्ट करना जो Hilt के साथ काम नहीं करतीं

Compose में, स्टैंडर्ड पैटर्न के तहत कंस्ट्रक्टर इंजेक्शन का इस्तेमाल करके, @HiltViewModel में डिपेंडेंसी इंजेक्ट की जाती हैं. इसके बाद, ViewModel को ऐक्सेस करने के लिए, कंपोज़ेबल में hiltViewModel() का इस्तेमाल किया जाता है. Hilt, Android की ज़्यादातर सामान्य क्लास के साथ काम करता है. हालांकि, आपको अब भी ऐसी क्लास मिल सकती हैं जो काम नहीं करतीं. इनमें आपको फ़ील्ड इंजेक्शन का इस्तेमाल करना होगा.

ऐसे मामलों में, @EntryPoint एनोटेशन का इस्तेमाल करके एंट्री पॉइंट बनाया जा सकता है. एंट्री पॉइंट, Hilt से मैनेज किए जाने वाले कोड और मैनेज न किए जाने वाले कोड के बीच की सीमा होती है. यह वह पॉइंट है जहां कोड, ऑब्जेक्ट के उस ग्राफ़ में पहली बार शामिल होता है जिसे Hilt मैनेज करता है. एंट्री पॉइंट की मदद से, Hilt उस कोड का इस्तेमाल कर सकता है जिसे वह डिपेंडेंसी ग्राफ़ में डिपेंडेंसी उपलब्ध कराने के लिए मैनेज नहीं करता.

उदाहरण के लिए, Hilt सीधे तौर पर कॉन्टेंट देने वाले को सपोर्ट नहीं करता. अगर आपको कॉन्टेंट देने वाले किसी व्यक्ति या कंपनी को कुछ डिपेंडेंसी पाने के लिए Hilt का इस्तेमाल करना है, तो आपको एक इंटरफ़ेस तय करना होगा. इसमें हर बाइंडिंग टाइप के लिए, @EntryPoint के साथ एनोटेट किया गया हो और क्वालिफ़ायर शामिल हों. इसके बाद, @InstallIn जोड़ें, ताकि यह तय किया जा सके कि एंट्री पॉइंट को किस कॉम्पोनेंट में इंस्टॉल करना है. इसके लिए, यह तरीका अपनाएं:

class ExampleContentProvider : ContentProvider() {

  @EntryPoint
  @InstallIn(SingletonComponent::class)
  interface ExampleContentProviderEntryPoint {
    fun analyticsService(): AnalyticsService
  }

  ...
}

एंट्री पॉइंट को ऐक्सेस करने के लिए, EntryPointAccessors से सही स्टैटिक तरीके का इस्तेमाल करें. पैरामीटर, कॉम्पोनेंट इंस्टेंस या कॉम्पोनेंट होल्डर के तौर पर काम करने वाला @AndroidEntryPoint ऑब्जेक्ट होना चाहिए. पक्का करें कि पैरामीटर के तौर पर पास किया गया कॉम्पोनेंट और EntryPointAccessors स्टैटिक मेथड, दोनों ही @EntryPoint इंटरफ़ेस पर @InstallIn एनोटेशन में मौजूद Android क्लास से मैच करते हों:

class ExampleContentProvider: ContentProvider() {
    ...

  override fun query(...): Cursor {
    val appContext = context?.applicationContext ?: throw IllegalStateException()
    val hiltEntryPoint =
      EntryPointAccessors.fromApplication(appContext, ExampleContentProviderEntryPoint::class.java)

    val analyticsService = hiltEntryPoint.analyticsService()
    ...
  }
}

इस उदाहरण में, एंट्री पॉइंट को वापस पाने के लिए आपको ApplicationContext का इस्तेमाल करना होगा, क्योंकि एंट्री पॉइंट SingletonComponent में इंस्टॉल किया गया है. अगर आपको ActivityComponent में मौजूद बाइंडिंग को वापस पाना है, तो आपको ActivityContext का इस्तेमाल करना होगा.

Hilt और Dagger

Android में डिपेंडेंसी इंजेक्शन के लिए, Hilt को आधिकारिक तौर पर सुझाई गई लाइब्रेरी माना जाता है. यह आपके ऐप्लिकेशन में डिपेंडेंसी इंजेक्शन को लागू करने का एक स्टैंडर्ड, आसान, और असरदार तरीका उपलब्ध कराता है. इसे खास तौर पर, Jetpack Compose और सिंगल-ऐक्टिविटी आर्किटेक्चर के लिए ऑप्टिमाइज़ किया गया है.

Hilt के लक्ष्य ये हैं:

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

Android ऑपरेटिंग सिस्टम, अपने कई फ़्रेमवर्क क्लास को इंस्टैंशिएट करता है. इसलिए, Android ऐप्लिकेशन में Dagger का इस्तेमाल करने के लिए, आपको काफ़ी मात्रा में बॉयलरप्लेट कोड लिखना पड़ता है. Hilt, Android ऐप्लिकेशन में Dagger का इस्तेमाल करने के लिए, छोटे-मोटे बदलाव वाले कोड को कम करता है. Hilt, अपने-आप ये कॉम्पोनेंट जनरेट करता है और उपलब्ध कराता है:

  • Android फ़्रेमवर्क क्लास को Dagger के साथ इंटिग्रेट करने के लिए कॉम्पोनेंट. अगर ये कॉम्पोनेंट नहीं होते, तो आपको इन्हें मैन्युअल तरीके से बनाना पड़ता.
  • स्कोप एनोटेशन, ताकि उन्हें उन कॉम्पोनेंट के साथ इस्तेमाल किया जा सके जिन्हें Hilt अपने-आप जनरेट करता है.
  • पहले से तय बाइंडिंग, ताकि Android क्लास को दिखाया जा सके. जैसे, Application या Activity.
  • पहले से तय किए गए क्वालिफ़ायर, जो @ApplicationContext और @ActivityContext को दिखाते हैं.

Dagger और Hilt कोड, एक ही कोड बेस में मौजूद हो सकते हैं. हालांकि, ज़्यादातर मामलों में, Android पर Dagger के इस्तेमाल को मैनेज करने के लिए, Hilt का इस्तेमाल करना सबसे सही होता है. Dagger का इस्तेमाल करने वाले किसी प्रोजेक्ट को Hilt पर माइग्रेट करने के लिए, माइग्रेट करने से जुड़ी गाइड देखें.

अन्य संसाधन

Hilt के बारे में ज़्यादा जानने के लिए, यहां दिए गए अन्य संसाधन देखें.

सैंपल

ब्लॉग

कॉन्टेंट देखना