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

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

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

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

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

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

Groovy

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

Kotlin

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

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

Groovy

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

android {
  ...
}

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

Kotlin

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

android {
  ...
}

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

Hilt, Java 8 की सुविधाओं का इस्तेमाल करता है. अपने प्रोजेक्ट में Java 8 चालू करने के लिए, app/build.gradle फ़ाइल में यह कोड जोड़ें:

Groovy

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

Kotlin

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

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

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

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

Kotlin

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

Java

@HiltAndroidApp
public class ExampleApplication extends Application { ... }

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

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

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

Kotlin

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

Java

@AndroidEntryPoint
public class ExampleActivity extends AppCompatActivity { ... }

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

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

अगर आपने किसी Android क्लास को @AndroidEntryPoint से एनोटेट किया है, तो आपको उस पर निर्भर Android क्लास को भी एनोटेट करना होगा. उदाहरण के लिए, अगर आपने किसी फ़्रैगमेंट को एनोटेट किया है, तो आपको उन सभी गतिविधियों को भी एनोटेट करना होगा जिनमें उस फ़्रैगमेंट का इस्तेमाल किया जाता है.

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

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

Kotlin

@AndroidEntryPoint
class ExampleActivity : AppCompatActivity() {

  @Inject lateinit var analytics: AnalyticsAdapter
  ...
}

Java

@AndroidEntryPoint
public class ExampleActivity extends AppCompatActivity {

  @Inject
  AnalyticsAdapter analytics;
  ...
}

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

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

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

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

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

Kotlin

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

Java

public class AnalyticsAdapter {

  private final AnalyticsService service;

  @Inject
  AnalyticsAdapter(AnalyticsService service) {
    this.service = service;
  }
  ...
}

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

Hilt मॉड्यूल

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

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

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

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

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

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

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

  • फ़ंक्शन के रिटर्न टाइप से Hilt को पता चलता है कि फ़ंक्शन किस इंटरफ़ेस के इंस्टेंस उपलब्ध कराता है.
  • फ़ंक्शन पैरामीटर, Hilt को बताता है कि कौनसे इंप्लीमेंटेशन को उपलब्ध कराना है.

Kotlin

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
}

Java

public interface AnalyticsService {
  void analyticsMethods();
}

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

@Module
@InstallIn(ActivityComponent.class)
public abstract class AnalyticsModule {

  @Binds
  public abstract AnalyticsService bindAnalyticsService(
    AnalyticsServiceImpl analyticsServiceImpl
  );
}

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

@Provides का इस्तेमाल करके इंस्टेंस इंजेक्ट करना

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

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

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

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

Kotlin

@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)
  }
}

Java

@Module
@InstallIn(ActivityComponent.class)
public class AnalyticsModule {

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

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

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

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

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

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

Kotlin

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

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

Java

@Qualifier
@Retention(RetentionPolicy.RUNTIME)
private @interface AuthInterceptorOkHttpClient {}

@Qualifier
@Retention(RetentionPolicy.RUNTIME)
private @interface OtherInterceptorOkHttpClient {}

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

Kotlin

@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()
  }
}

Java

@Module
@InstallIn(ActivityComponent.class)
public class NetworkModule {

  @AuthInterceptorOkHttpClient
  @Provides
  public static OkHttpClient provideAuthInterceptorOkHttpClient(
    AuthInterceptor authInterceptor
  ) {
      return new OkHttpClient.Builder()
                   .addInterceptor(authInterceptor)
                   .build();
  }

  @OtherInterceptorOkHttpClient
  @Provides
  public static OkHttpClient provideOtherInterceptorOkHttpClient(
    OtherInterceptor otherInterceptor
  ) {
      return new OkHttpClient.Builder()
                   .addInterceptor(otherInterceptor)
                   .build();
  }
}

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

Kotlin

// 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: AppCompatActivity() {

  @AuthInterceptorOkHttpClient
  @Inject lateinit var okHttpClient: OkHttpClient
}

Java

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

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

// As a dependency of a constructor-injected class.
public class ExampleServiceImpl ... {

  private final OkHttpClient okHttpClient;

  @Inject
  ExampleServiceImpl(@AuthInterceptorOkHttpClient OkHttpClient okHttpClient) {
    this.okHttpClient = okHttpClient;
  }
}

// At field injection.
@AndroidEntryPoint
public class ExampleActivity extends AppCompatActivity {

  @AuthInterceptorOkHttpClient
  @Inject
  OkHttpClient okHttpClient;
  ...
}

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

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

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

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

Kotlin

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

Java

public class AnalyticsAdapter {

  private final Context context;
  private final AnalyticsService service;

  @Inject
  AnalyticsAdapter(
    @ActivityContext Context context,
    AnalyticsService service
  ) {
    this.context = context;
    this.service = service;
  }
}

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

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

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

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

Hilt ये कॉम्पोनेंट उपलब्ध कराता है:

Hilt कॉम्पोनेंट इसके लिए इंजेक्टर
SingletonComponent Application
ActivityRetainedComponent लागू नहीं
ViewModelComponent ViewModel
ActivityComponent Activity
FragmentComponent Fragment
ViewComponent View
ViewWithFragmentComponent View को @WithFragmentBindings के साथ एनोटेट किया गया है
ServiceComponent Service

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

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

जनरेट किया गया कॉम्पोनेंट मैसेज किस समय लिखा गया इस तारीख को नष्ट किया गया
SingletonComponent Application#onCreate() Application नष्ट कर दिया गया
ActivityRetainedComponent Activity#onCreate() Activity#onDestroy()
ViewModelComponent ViewModel बनाया गया ViewModel नष्ट कर दिया गया
ActivityComponent Activity#onCreate() Activity#onDestroy()
FragmentComponent Fragment#onAttach() Fragment#onDestroy()
ViewComponent View#super() View नष्ट कर दिया गया
ViewWithFragmentComponent View#super() View नष्ट कर दिया गया
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
Fragment FragmentComponent @FragmentScoped
View ViewComponent @ViewScoped
View को @WithFragmentBindings के साथ एनोटेट किया गया है ViewWithFragmentComponent @ViewScoped
Service ServiceComponent @ServiceScoped

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

Kotlin

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

Java

@ActivityScoped
public class AnalyticsAdapter {

  private final AnalyticsService service;

  @Inject
  AnalyticsAdapter(AnalyticsService service) {
    this.service = service;
  }
  ...
}

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

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

Kotlin

// 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)
  }
}

Java

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

  @Singleton
  @Binds
  public abstract AnalyticsService bindAnalyticsService(
    AnalyticsServiceImpl analyticsServiceImpl
  );
}

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

  @Singleton
  @Provides
  public static AnalyticsService provideAnalyticsService() {
      return new Retrofit.Builder()
               .baseUrl("https://example.com")
               .build()
               .create(AnalyticsService.class);
  }
}

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

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

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

ViewWithFragmentComponent, FragmentComponent के तहत आता है. FragmentComponent
    और ViewComponent, ActivityComponent के तहत आते हैं. ActivityComponent, ActivityRetainedComponent के तहत आता है. ViewModelComponent, ActivityRetainedComponent के दायरे में आता है. ActivityRetainedComponent और ServiceComponent, SingletonComponent के तहत आते हैं.
पहली इमेज. Hilt जनरेट किए गए कॉम्पोनेंट का क्रम.

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

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

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

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

Kotlin

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

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

Java

public class AnalyticsServiceImpl implements AnalyticsService {

  private final Context context;

  @Inject
  AnalyticsAdapter(@ApplicationContext Context context) {
    this.context = context;
  }
}

// The Application binding is available without qualifiers.
public class AnalyticsServiceImpl implements AnalyticsService {

  private final Application application;

  @Inject
  AnalyticsAdapter(Application application) {
    this.application = application;
  }
}

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

Kotlin

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

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

Java

public class AnalyticsAdapter {

  private final Context context;

  @Inject
  AnalyticsAdapter(@ActivityContext Context context) {
    this.context = context;
  }
}

// The Activity binding is available without qualifiers.
public class AnalyticsAdapter {

  private final FragmentActivity activity;

  @Inject
  AnalyticsAdapter(FragmentActivity activity) {
    this.activity = activity;
  }
}

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

Hilt, Android की सबसे आम क्लास के साथ काम करता है. हालांकि, आपको उन क्लास में फ़ील्ड इंजेक्शन लागू करना पड़ सकता है जिनमें Hilt काम नहीं करता.

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

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

Kotlin

class ExampleContentProvider : ContentProvider() {

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

  ...
}

Java

public class ExampleContentProvider extends ContentProvider {

  @EntryPoint
  @InstallIn(SingletonComponent.class)
  interface ExampleContentProviderEntryPoint {
    public AnalyticsService analyticsService();
  }
  ...
}

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

Kotlin

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()
    ...
  }
}

Java

public class ExampleContentProvider extends ContentProvider {

  @Override
  public Cursor query(...) {
    Context appContext = getContext().getApplicationContext();
    ExampleContentProviderEntryPoint hiltEntryPoint =
      EntryPointAccessors.fromApplication(appContext, ExampleContentProviderEntryPoint.class);
    AnalyticsService analyticsService = hiltEntryPoint.analyticsService();
  }
}

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

हिल्ट ऐंड डैगर

Hilt को Dagger डिपेंडेंसी इंजेक्शन लाइब्रेरी के आधार पर बनाया गया है. यह Android ऐप्लिकेशन में Dagger को शामिल करने का स्टैंडर्ड तरीका उपलब्ध कराता है.

Dagger के मुकाबले Hilt के ये लक्ष्य हैं:

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

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

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

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

अन्य संसाधन

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

सैंपल

कोडलैब

ब्लॉग