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

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

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

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

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

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

رائع

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

Kotlin

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

بعد ذلك، طبِّق مكوّن Gradle الإضافي وأضِف هذه التبعيات في ملف app/build.gradle:

رائع

...
plugins {
  id 'kotlin-kapt'
  id 'com.google.dagger.hilt.android'
}

android {
  ...
}

dependencies {
  implementation "com.google.dagger:hilt-android:2.44"
  kapt "com.google.dagger:hilt-compiler:2.44"
}

// Allow references to generated code
kapt {
  correctErrorTypes true
}

Kotlin

plugins {
  id("kotlin-kapt")
  id("com.google.dagger.hilt.android")
}

android {
  ...
}

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

// Allow references to generated code
kapt {
  correctErrorTypes = true
}

يستخدم Hilt ميزات Java 8. لتفعيل Java 8 في مشروعك، أضِف ما يلي إلى ملف app/build.gradle:

رائع

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 هي فئة تمت إضافة تعليقات توضيحية إليها باستخدام @Module. وكما هي الحال في وحدة Dagger، تحدّد الأداة Hilt كيفية توفير مثيلات من أنواع معيّنة. على عكس وحدات Dagger، يجب إضافة تعليقات توضيحية إلى وحدات Hilt باستخدام @InstallIn لإعلام Hilt بفئة Android التي سيتم استخدام كل وحدة أو تثبيتها فيها.

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

إدخال حالات الواجهة باستخدام @Binds

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

يوضِّح التعليق التوضيحي @Binds لخوارزمية Hilt طريقة التنفيذ التي يجب استخدامها عندما يحتاج إلى توفير نسخة افتراضية من الواجهة.

توفّر الدالة التي تحتوي على تعليقات توضيحية المعلومات التالية لجهة Hilt:

  • تخبر الدالة return type 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

لا تكون الواجهات هي الحالة الوحيدة التي لا يمكنك فيها إدخال نوع من الدالة الإنشائية. لا يمكن أيضًا إدخال الدالة الإنشائية إذا كنت لا تملك الفئة لأنها تأتي من مكتبة خارجية (فئات مثل إعادة التحديث أو OkHttpClient أو قواعد بيانات الغرف)، أو إذا كان يجب إنشاء مثيلات باستخدام نمط أداة الإنشاء.

بالنظر إلى المثال السابق. إذا كنت لا تملك الفئة AnalyticsService بشكل مباشر، يمكنك إخبار Hilt بكيفية توفير مثيلات من هذا النوع من خلال إنشاء دالة داخل وحدة Hilt وإضافة تعليقات توضيحية إلى هذه الدالة باستخدام @Provides.

توفّر الدالة المُشار إليها المعلومات التالية إلى Hilt:

  • تخبر الدالة return type 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. كلتا الطريقتين لهما نفس نوع الإرجاع، لكن المؤهلات تصنفها على أنها عملية ربط مختلفة:

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.
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 غير محدَّدة تلقائيًا. هذا يعني أنّه في كل مرة يطلب فيها تطبيقك الربط، ينشئ 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 يقعان ضمن المكوّنَين "سينغلتون".
الشكل 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 وخنجر

تم تصميم Hilt في أعلى مكتبة الحقن التابعة لـ Dagger في توفير طريقة قياسية لدمج تطبيق Dagger في أحد تطبيقات Android.

في ما يتعلق بأداة Dagger، تتمثل أهداف Hilt في ما يلي:

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

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

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

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

مصادر إضافية

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

العيّنات

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

المدوّنات