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

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

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

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

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

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

رائع

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

Kotlin

plugins {
  ...
  id("com.google.dagger.hilt.android") version "2.51.1" 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.51.1"
  kapt "com.google.dagger:hilt-compiler:2.51.1"
}

// 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.51.1")
  kapt("com.google.dagger:hilt-android-compiler:2.51.1")
}

// 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 هي حقن مُنشئ العنصر. استخدِم annotation @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 كيفية تقديم مثيل من النوع الذي يتوافق مع كلّ مؤهّل. في هذه الحالة، يمكنك استخدام وحدة 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 للحصول على بعض التبعيات، عليك تعريف واجهة مُشارَك فيها تعليقًا توضيحيًا باستخدام @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 static تطابقان مع فئة 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 استنادًا إلى مكتبة 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 في codelab.

مصادر إضافية

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

نماذج

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

المدوّنات