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

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

Hilt, आपके ऐप्लिकेशन में डीआई का इस्तेमाल करने का एक स्टैंडर्ड तरीका उपलब्ध कराता है. इसके लिए, यह आपके प्रोजेक्ट में मौजूद हर Android क्लास के लिए कंटेनर उपलब्ध कराता है. साथ ही, उनकी लाइफ़साइकल को अपने-आप मैनेज करता है. Hilt को लोकप्रिय DI लाइब्रेरी Dagger के ऊपर बनाया गया है, ताकि Dagger से मिलने वाले फ़ायदों का इस्तेमाल किया जा सके. जैसे, कंपाइल-टाइम की सही जानकारी, रनटाइम परफ़ॉर्मेंस, स्केलेबिलिटी, और Android Studio का सपोर्ट. ज़्यादा जानकारी के लिए, 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 के लिए कॉन्फ़िगर किया गया है, जो Jetpack Compose और Hilt के वर्शन के लिए ज़रूरी है, app/build.gradle फ़ाइल में यह कोड जोड़ें:

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-इंजेक्ट किए गए ViewModels को ऐक्सेस किया जा सकता है.

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

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

@AndroidEntryPoint
class ExampleActivity : ComponentActivity() {

  @Inject lateinit var analytics: AnalyticsAdapter
  ...
}

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

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

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

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

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

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

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

Hilt मॉड्यूल

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

Hilt मॉड्यूल, @Module के साथ एनोटेट की गई क्लास होती है. यह Hilt को ऐसे टाइप के इंस्टेंस बनाने के निर्देश देता है जिन्हें कंस्ट्रक्टर इंजेक्शन के ज़रिए नहीं दिया जा सकता. जैसे, इंटरफ़ेस या तीसरे पक्ष की क्लास. आपको हर मॉड्यूल को @InstallIn से एनोटेट करना होगा, ताकि Hilt को यह पता चल सके कि हर मॉड्यूल का इस्तेमाल किस Android क्लास में किया जाएगा या उसे किस 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 में सभी बाइंडिंग unscoped होती हैं. इसका मतलब है कि जब भी आपका ऐप्लिकेशन बाइंडिंग का अनुरोध करता है, तब Hilt ज़रूरी टाइप का नया इंस्टेंस बनाता है.

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

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

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

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

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

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

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

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

// 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 की ज़्यादातर क्लास के साथ काम करता है. हालांकि, आपको ऐसी क्लास भी मिल सकती हैं जिनके साथ Hilt काम नहीं करता. ऐसे में, आपको फ़ील्ड इंजेक्शन का इस्तेमाल करना पड़ सकता है.

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

उदाहरण के लिए, Hilt सीधे तौर पर content providers के साथ काम नहीं करता. अगर आपको किसी कॉन्टेंट प्रोवाइडर को कुछ डिपेंडेंसी पाने के लिए 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 के बारे में ज़्यादा जानने के लिए, यहां दिए गए अन्य संसाधन देखें.

सैंपल

ब्लॉग

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