إدخال المهام التابعة باستخدام Hilt

‫Hilt هي مكتبة لتوفير التبعيات في Android، وهي تقلّل من الرمز النموذجي اللازم لتوفير التبعيات يدويًا في مشروعك. يتطلّب تنفيذ إدخال التبعيات يدويًا إنشاء كل فئة وتبعياتها يدويًا، واستخدام الحاويات لإعادة استخدام التبعيات وإدارتها.

توفّر Hilt طريقة عادية لاستخدام ميزة "توفير التبعية" في تطبيقك من خلال توفير حاويات لكل فئة Android في مشروعك وإدارة دورات حياتها تلقائيًا. تم إنشاء Hilt استنادًا إلى مكتبة DI الشائعة Dagger للاستفادة من الصحة في وقت الترجمة والأداء في وقت التشغيل وقابلية التوسّع وتوافقها مع Android Studio التي توفّرها Dagger. لمزيد من المعلومات، يُرجى الاطّلاع على Hilt وDagger.

يوضّح هذا الدليل المفاهيم الأساسية لـ Hilt والحاويات التي يتم إنشاؤها. ويتضمّن أيضًا عرضًا توضيحيًا لكيفية إعداد تطبيق حالي لاستخدام Hilt.

إضافة التبعيات

أولاً، أضِف المكوّن الإضافي hilt-android-gradle-plugin إلى ملف build.gradle في جذر مشروعك:

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

بعد إعداد Hilt في فئة Application وتوفُّر مكوّن على مستوى التطبيق، يمكن أن يوفّر 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 أحد مكونات Hilt لكل فئة Android في مشروعك. يمكن أن تتلقّى هذه المكوّنات عناصر تابعة من فئاتها الرئيسية، كما هو موضّح في التسلسل الهرمي للمكوّنات.

للحصول على التبعيات من أحد المكوّنات، استخدِم التعليق التوضيحي @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، يجب إضافة التعليق التوضيحي @InstallIn إلى وحدات Hilt لإخبار 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
  );
}

يتم وضع التعليق التوضيحي AnalyticsModule على وحدة Hilt لأنّك تريد أن يدرج Hilt هذه التبعية في ExampleActivity.@InstallIn(ActivityComponent.class) تعني هذه التعليقات التوضيحية أنّ جميع التبعيات في 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 إلى معرفة كيفية توفير مثيل للنوع الذي يتوافق مع كل مؤهّل. في هذه الحالة، يمكنك استخدام وحدة Hilt مع @Provides. لكلتا الطريقتين نوع الإرجاع نفسه، ولكن المؤهلات تصنّفهما على أنّهما رابطتان مختلفتان:

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 المناسبة.

توضّح الأمثلة السابقة استخدام ActivityComponent في وحدات Hilt.

توفّر مكتبة Hilt المكوّنات التالية:

مكوِّن Hilt أداة إدخال لـ
SingletonComponent Application
ActivityRetainedComponent لا ينطبق
ViewModelComponent ViewModel
ActivityComponent Activity
FragmentComponent Fragment
ViewComponent View
ViewWithFragmentComponent View أضاف تعليقًا توضيحيًا باستخدام @WithFragmentBindings
ServiceComponent Service

مدد صلاحية المكوّنات

ينشئ Hilt تلقائيًا مثيلات لفئات المكوّنات التي تم إنشاؤها ويزيلها باتّباع مراحل نشاط فئات Android المقابلة.

المكوّن الذي تم إنشاؤه وقت الإنشاء: تاريخ ووقت الإتلاف
SingletonComponent Application#onCreate() Application destroyed
ActivityRetainedComponent Activity#onCreate() Activity#onDestroy()
ViewModelComponent تم إنشاء ViewModel. ViewModel destroyed
ActivityComponent Activity#onCreate() Activity#onDestroy()
FragmentComponent Fragment#onAttach() Fragment#onDestroy()
ViewComponent View#super() View destroyed
ViewWithFragmentComponent View#super() View destroyed
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
Fragment FragmentComponent @FragmentScoped
View ViewComponent @ViewScoped
View أضاف تعليقًا توضيحيًا باستخدام @WithFragmentBindings ViewWithFragmentComponent @ViewScoped
Service ServiceComponent @ServiceScoped

في المثال، إذا حدّدت نطاق AnalyticsAdapter إلى ActivityComponent باستخدام @ActivityScoped، يوفّر Hilt مثيلاً واحدًا من 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 يتضمّن حالة داخلية تتطلّب استخدام مثيل واحد في كل مرة، ليس فقط في ExampleActivity، بل في أي مكان في التطبيق. في هذه الحالة، من المناسب تحديد نطاق AnalyticsService إلى SingletonComponent. والنتيجة هي أنّه كلما احتاج المكوّن إلى توفير مثيل من AnalyticsService، سيوفّر المثيل نفسه في كل مرة.

يوضّح المثال التالي كيفية تحديد نطاق ربط بمكوّن في وحدة Hilt. يجب أن يتطابق نطاق الربط مع نطاق المكوّن الذي تم تثبيته فيه، لذا في هذا المثال، يجب تثبيت AnalyticsService في SingletonComponent بدلاً من ActivityComponent:

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.
الشكل 1. التسلسل الهرمي للمكوّنات التي ينشئها 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 استخدام موفّري المحتوى مباشرةً. إذا أردت أن يستخدم موفّر المحتوى 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 يتطابقان مع فئة Android في التعليق التوضيحي @InstallIn على واجهة @EntryPoint:

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 and Dagger

تم إنشاء Hilt استنادًا إلى مكتبة Dagger لإدراج التبعيات، وهي توفّر طريقة عادية لدمج Dagger في تطبيق Android.

في ما يتعلق بـ Dagger، تكون أهداف Hilt على النحو التالي:

  • لتبسيط البنية الأساسية المتعلّقة بإطار عمل Dagger لتطبيقات Android
  • لإنشاء مجموعة عادية من المكوّنات والنطاقات لتسهيل عملية الإعداد والقراءة ومشاركة الرموز البرمجية بين التطبيقات
  • لتوفير طريقة سهلة لتقديم عمليات ربط مختلفة لأنواع إصدارات متنوعة، مثل الإصدارات التجريبية أو إصدارات تصحيح الأخطاء أو الإصدارات العلنية

بما أنّ نظام التشغيل Android ينشئ العديد من فئات إطاره، يتطلّب استخدام Dagger في تطبيق Android كتابة قدر كبير من الرموز النموذجية. يقلّل Hilt من الرموز النموذجية المتضمّنة في استخدام Dagger في تطبيق Android. تنشئ Hilt تلقائيًا وتوفّر ما يلي:

  • مكوّنات لدمج فئات إطار عمل Android مع Dagger، والتي كان عليك إنشاؤها يدويًا.
  • تعليقات توضيحية للنطاق لاستخدامها مع المكوّنات التي ينشئها Hilt تلقائيًا
  • عمليات الربط المحدّدة مسبقًا لتمثيل فئات Android، مثل Application أو Activity
  • محدّدات مُعرَّفة مسبقًا لتمثيل @ApplicationContext و@ActivityContext

يمكن أن يتواجد رمز Dagger وHilt في قاعدة الرموز البرمجية نفسها. ومع ذلك، في معظم الحالات، من الأفضل استخدام Hilt لإدارة جميع استخدامات Dagger على Android. لنقل مشروع يستخدم Dagger إلى Hilt، يُرجى الاطّلاع على دليل نقل البيانات والتدريب العملي حول نقل تطبيق Dagger إلى Hilt.

مراجع إضافية

لمزيد من المعلومات حول Hilt، اطّلِع على المراجع الإضافية التالية.

نماذج

الدروس التطبيقية حول الترميز

المدوّنات