تزریق وابستگی با هیلت

Hilt یک کتابخانه تزریق وابستگی برای اندروید است که حجم کارهای تکراری و خسته‌کننده‌ی تزریق وابستگی دستی در پروژه را کاهش می‌دهد. انجام تزریق وابستگی دستی مستلزم آن است که شما هر کلاس و وابستگی‌های آن را به صورت دستی بسازید و از کانتینرها برای استفاده مجدد و مدیریت وابستگی‌ها استفاده کنید.

Hilt با ارائه کانتینرهایی برای هر کلاس اندروید در پروژه شما و مدیریت خودکار چرخه حیات آنها، روشی استاندارد برای استفاده از DI در برنامه شما ارائه می‌دهد. Hilt بر اساس کتابخانه DI محبوب Dagger ساخته شده است تا از صحت زمان کامپایل، عملکرد زمان اجرا، مقیاس‌پذیری و پشتیبانی Android Studio که Dagger ارائه می‌دهد، بهره‌مند شود. برای اطلاعات بیشتر، به Hilt و Dagger مراجعه کنید.

این راهنما مفاهیم اولیه Hilt و کانتینرهای تولید شده توسط آن را توضیح می‌دهد. همچنین شامل نمایشی از نحوه بوت‌استرپ کردن یک برنامه موجود برای استفاده از Hilt است.

افزودن وابستگی‌ها

ابتدا، افزونه hilt-android-gradle-plugin را به فایل build.gradle ریشه پروژه خود اضافه کنید:

گرووی

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

کاتلین

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

سپس، افزونه Gradle را اعمال کنید و این وابستگی‌ها را در فایل app/build.gradle خود اضافه کنید:

گرووی

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

android {
  ...
}

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

کاتلین

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

android {
  ...
}

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

Hilt از ویژگی‌های جاوا ۸ استفاده می‌کند. برای فعال کردن جاوا ۸ در پروژه خود، موارد زیر را به فایل app/build.gradle اضافه کنید:

گرووی

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

کاتلین

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

کلاس کاربرد هیلت

تمام برنامه‌هایی که از Hilt استفاده می‌کنند باید شامل یک کلاس Application باشند که با @HiltAndroidApp حاشیه‌نویسی شده باشد.

@HiltAndroidApp تولید کد Hilt را آغاز می‌کند، که شامل یک کلاس پایه برای برنامه شما است که به عنوان ظرف وابستگی سطح برنامه عمل می‌کند.

کاتلین

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

جاوا

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

این کامپوننت Hilt تولید شده به چرخه حیات شیء Application متصل شده و وابستگی‌هایی را برای آن فراهم می‌کند. علاوه بر این، کامپوننت والد برنامه است، به این معنی که سایر کامپوننت‌ها می‌توانند به وابستگی‌هایی که ارائه می‌دهد دسترسی داشته باشند.

تزریق وابستگی‌ها به کلاس‌های اندروید

زمانی که Hilt در کلاس Application شما تنظیم شد و یک کامپوننت سطح Application در دسترس قرار گرفت، Hilt می‌تواند وابستگی‌هایی را برای سایر کلاس‌های اندروید که حاشیه‌نویسی @AndroidEntryPoint را دارند، فراهم کند:

کاتلین

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

جاوا

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

Hilt در حال حاضر از کلاس‌های اندروید زیر پشتیبانی می‌کند:

  • Application (با استفاده از @HiltAndroidApp )
  • ViewModel (با استفاده از @HiltViewModel )
  • Activity
  • Fragment
  • View
  • Service
  • BroadcastReceiver

اگر یک کلاس اندروید را با @AndroidEntryPoint حاشیه‌نویسی می‌کنید، باید کلاس‌های اندروید وابسته به آن را نیز حاشیه‌نویسی کنید. برای مثال، اگر یک فرگمنت را حاشیه‌نویسی می‌کنید، باید هر فعالیتی را که در آن از آن فرگمنت استفاده می‌کنید نیز حاشیه‌نویسی کنید.

@AndroidEntryPoint برای هر کلاس اندروید در پروژه شما یک کامپوننت Hilt جداگانه تولید می‌کند. این کامپوننت‌ها می‌توانند از کلاس‌های والد مربوطه خود، همانطور که در سلسله مراتب کامپوننت توضیح داده شده است، وابستگی دریافت کنند.

برای دریافت وابستگی‌ها از یک کامپوننت، از حاشیه‌نویسی @Inject برای انجام تزریق فیلد استفاده کنید:

کاتلین

@AndroidEntryPoint
class ExampleActivity : AppCompatActivity() {

  @Inject lateinit var analytics: AnalyticsAdapter
  ...
}

جاوا

@AndroidEntryPoint
public class ExampleActivity extends AppCompatActivity {

  @Inject
  AnalyticsAdapter analytics;
  ...
}

کلاس‌هایی که Hilt تزریق می‌کند می‌توانند کلاس‌های پایه دیگری داشته باشند که آنها هم از تزریق استفاده می‌کنند. اگر این کلاس‌ها انتزاعی باشند، نیازی به حاشیه‌نویسی @AndroidEntryPoint ندارند.

برای کسب اطلاعات بیشتر در مورد اینکه یک کلاس اندروید در کدام چرخه عمر فراخوانی تزریق می‌شود، به بخش طول عمر کامپوننت‌ها مراجعه کنید.

تعریف اتصالات Hilt

برای انجام تزریق فیلد، هیلت باید بداند که چگونه نمونه‌هایی از وابستگی‌های لازم را از مؤلفه مربوطه ارائه دهد. یک اتصال شامل اطلاعات لازم برای ارائه نمونه‌هایی از یک نوع به عنوان وابستگی است.

یکی از راه‌های ارائه اطلاعات اتصال به Hilt، تزریق سازنده است. از حاشیه‌نویسی @Inject روی سازنده یک کلاس استفاده کنید تا به Hilt بگویید چگونه نمونه‌هایی از آن کلاس را ارائه دهد:

کاتلین

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

جاوا

public class AnalyticsAdapter {

  private final AnalyticsService service;

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

پارامترهای یک سازنده‌ی حاشیه‌نویسی‌شده‌ی یک کلاس، وابستگی‌های آن کلاس هستند. در این مثال، AnalyticsAdapter دارای AnalyticsService به عنوان یک وابستگی است. بنابراین، Hilt باید بداند که چگونه نمونه‌هایی از AnalyticsService را نیز ارائه دهد.

ماژول‌های دسته

گاهی اوقات نمی‌توان یک نوع را با تزریق سازنده (Constructor-injected) اجرا کرد. این اتفاق می‌تواند به دلایل مختلفی رخ دهد. برای مثال، شما نمی‌توانید یک رابط را با تزریق سازنده (Constructor-injected) اجرا کنید. همچنین نمی‌توانید نوعی را که متعلق به شما نیست، مانند کلاسی از یک کتابخانه خارجی، با تزریق سازنده (Constructor-injected) اجرا کنید. در این موارد، می‌توانید با استفاده از ماژول‌های Hilt، اطلاعات اتصال را در اختیار Hilt قرار دهید.

یک ماژول Hilt کلاسی است که با @Module حاشیه‌نویسی شده است. مانند یک ماژول Dagger ، این ماژول به Hilt اطلاع می‌دهد که چگونه نمونه‌هایی از انواع خاص را ارائه دهد. برخلاف ماژول‌های Dagger، شما باید ماژول‌های Hilt را با @InstallIn حاشیه‌نویسی کنید تا به Hilt بگویید که هر ماژول در کدام کلاس اندروید استفاده یا نصب خواهد شد.

وابستگی‌هایی که در ماژول‌های Hilt ارائه می‌دهید، در تمام کامپوننت‌های تولید شده‌ای که با کلاس اندروید مرتبط هستند و ماژول Hilt را در آن نصب می‌کنید، در دسترس هستند.

تزریق نمونه‌های رابط با @Binds

مثال AnalyticsService را در نظر بگیرید. اگر AnalyticsService یک رابط باشد، نمی‌توانید آن را با استفاده از constructor-inject تزریق کنید. در عوض، با ایجاد یک تابع انتزاعی که با @Binds درون یک ماژول Hilt حاشیه‌نویسی شده است، اطلاعات اتصال را در اختیار Hilt قرار دهید.

حاشیه‌نویسی @Binds به Hilt می‌گوید که در صورت نیاز به ارائه نمونه‌ای از یک رابط، از کدام پیاده‌سازی استفاده کند.

تابع حاشیه‌نویسی‌شده اطلاعات زیر را در اختیار Hilt قرار می‌دهد:

  • نوع بازگشتی تابع به Hilt می‌گوید که تابع نمونه‌هایی از چه رابطی را ارائه می‌دهد.
  • پارامتر تابع به Hilt می‌گوید که کدام پیاده‌سازی را ارائه دهد.

کاتلین

interface AnalyticsService {
  fun analyticsMethods()
}

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

@Module
@InstallIn(ActivityComponent::class)
abstract class AnalyticsModule {

  @Binds
  abstract fun bindAnalyticsService(
    analyticsServiceImpl: AnalyticsServiceImpl
  ): AnalyticsService
}

جاوا

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 و حاشیه‌نویسی آن تابع با @Provides ، به Hilt بگویید که چگونه نمونه‌هایی از این نوع را ارائه دهد.

تابع حاشیه‌نویسی‌شده اطلاعات زیر را در اختیار هیلت قرار می‌دهد:

  • نوع بازگشتی تابع به Hilt می‌گوید که تابع نمونه‌هایی از چه نوعی را ارائه می‌دهد.
  • پارامترهای تابع، وابستگی‌های نوع مربوطه را به Hilt اطلاع می‌دهند.
  • بدنه تابع به Hilt می‌گوید که چگونه یک نمونه از نوع مربوطه را ارائه دهد. Hilt هر بار که نیاز به ارائه نمونه‌ای از آن نوع داشته باشد، بدنه تابع را اجرا می‌کند.

کاتلین

@Module
@InstallIn(ActivityComponent::class)
object AnalyticsModule {

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

جاوا

@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 ارائه دهید. می‌توانید چندین اتصال را برای یک نوع با استفاده از qualifierها تعریف کنید.

یک توصیف‌کننده (qualifier) ​​حاشیه‌نویسی‌ای است که برای شناسایی یک متغیر خاص برای یک نوع، زمانی که آن نوع چندین متغیر تعریف شده دارد، استفاده می‌کنید.

به این مثال توجه کنید. اگر نیاز به رهگیری فراخوانی‌ها به AnalyticsService دارید، می‌توانید از یک شیء OkHttpClient به همراه یک interceptor استفاده کنید. برای سایر سرویس‌ها، ممکن است نیاز به رهگیری فراخوانی‌ها به روش دیگری داشته باشید. در این صورت، باید به Hilt بگویید که چگونه دو پیاده‌سازی مختلف از OkHttpClient را ارائه دهد.

ابتدا، توصیف‌گرهایی را که برای حاشیه‌نویسی متدهای @Binds یا @Provides استفاده خواهید کرد، تعریف کنید:

کاتلین

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

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

جاوا

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

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

سپس، Hilt باید بداند که چگونه یک نمونه از نوعی که با هر توصیف‌کننده مطابقت دارد، ارائه دهد. در این حالت، می‌توانید از یک ماژول Hilt با @Provides استفاده کنید. هر دو متد نوع بازگشتی یکسانی دارند، اما توصیف‌کننده‌ها آنها را به عنوان دو متغیر متفاوت برچسب‌گذاری می‌کنند:

کاتلین

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

جاوا

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

شما می‌توانید نوع خاصی را که نیاز دارید با حاشیه‌نویسی فیلد یا پارامتر با توصیف‌کننده‌ی مربوطه تزریق کنید:

کاتلین

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

جاوا

// 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 از مثال به context مربوط به activity نیاز دارد. کد زیر نحوه ارائه activity context به AnalyticsAdapter را نشان می‌دهد:

کاتلین

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

جاوا

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، به اتصالات پیش‌فرض کامپوننت مراجعه کنید.

کامپوننت‌های تولید شده برای کلاس‌های اندروید

برای هر کلاس اندروید که می‌توانید در آن تزریق فیلد انجام دهید، یک کامپوننت Hilt مرتبط وجود دارد که می‌توانید در حاشیه‌نویسی @InstallIn به آن اشاره کنید. هر کامپوننت Hilt مسئول تزریق متغیرهای خود به کلاس اندروید مربوطه است.

مثال‌های قبلی استفاده از ActivityComponent را در ماژول‌های Hilt نشان دادند.

هیلت اجزای زیر را ارائه می‌دهد:

جزء دسته انژکتور برای
SingletonComponent Application
ActivityRetainedComponent ناموجود
ViewModelComponent ViewModel
ActivityComponent Activity
FragmentComponent Fragment
ViewComponent View
ViewWithFragmentComponent View حاشیه‌نویسی شده با @WithFragmentBindings
ServiceComponent Service

طول عمر قطعات

هیلت به طور خودکار نمونه‌هایی از کلاس‌های کامپوننت تولید شده را با پیروی از چرخه حیات کلاس‌های اندروید مربوطه ایجاد و از بین می‌برد.

مولفه تولید شده ایجاد شده در تخریب شده در
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()

دامنه‌های کامپوننت

به طور پیش‌فرض، تمام bindingها در Hilt بدون محدوده هستند. این بدان معناست که هر بار که برنامه شما binding را درخواست می‌کند، Hilt یک نمونه جدید از نوع مورد نیاز ایجاد می‌کند.

در این مثال، هر بار که Hilt، AnalyticsAdapter به عنوان یک وابستگی به نوع دیگر یا از طریق تزریق فیلد (مانند ExampleActivity ) ارائه می‌دهد، Hilt یک نمونه جدید از AnalyticsAdapter ارائه می‌دهد.

با این حال، هیلت همچنین اجازه می‌دهد که یک اتصال به یک کامپوننت خاص محدود شود. هیلت فقط یک بار به ازای هر نمونه از کامپوننتی که اتصال به آن محدود شده است، یک اتصال محدود ایجاد می‌کند و همه درخواست‌ها برای آن اتصال، همان نمونه را به اشتراک می‌گذارند.

جدول زیر حاشیه‌نویسی‌های دامنه (scope annotations) را برای هر کامپوننت تولید شده فهرست می‌کند:

کلاس اندروید مولفه تولید شده دامنه
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 با استفاده از @ActivityScoped به ActivityComponent محدود کنید، Hilt نمونه‌ی یکسانی از AnalyticsAdapter در طول حیات activity مربوطه ارائه می‌دهد:

کاتلین

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

جاوا

@ActivityScoped
public class AnalyticsAdapter {

  private final AnalyticsService service;

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

فرض کنید AnalyticsService یک وضعیت داخلی دارد که نیاز دارد هر بار از یک نمونه استفاده شود - نه تنها در ExampleActivity ، بلکه در هر کجای برنامه. در این حالت، مناسب است که AnalyticsService به SingletonComponent محدود کنیم. نتیجه این است که هر زمان که کامپوننت نیاز به ارائه نمونه‌ای از AnalyticsService داشته باشد، هر بار نمونه مشابهی را ارائه می‌دهد.

مثال زیر نحوه‌ی تعیین محدوده‌ی یک اتصال به یک کامپوننت در ماژول Hilt را نشان می‌دهد. محدوده‌ی یک اتصال باید با محدوده‌ی کامپوننتی که در آن نصب شده است، مطابقت داشته باشد، بنابراین در این مثال باید AnalyticsService به جای ActivityComponent در SingletonComponent نصب کنید:

کاتلین

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

  @Singleton
  @Binds
  abstract fun bindAnalyticsService(
    analyticsServiceImpl: AnalyticsServiceImpl
  ): AnalyticsService
}

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

  @Singleton
  @Provides
  fun provideAnalyticsService(): AnalyticsService {
      return Retrofit.Builder()
               .baseUrl("https://example.com")
               .build()
               .create(AnalyticsService::class.java)
  }
}

جاوا

// 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، به Scoping در اندروید و Hilt مراجعه کنید.

سلسله مراتب اجزا

نصب یک ماژول در یک کامپوننت، امکان دسترسی به متغیرهای آن را به عنوان وابستگی به سایر متغیرهای موجود در آن کامپوننت یا هر کامپوننت فرزند زیر آن در سلسله مراتب کامپوننت فراهم می‌کند:

ViewWithFragmentComponent زیر مجموعه FragmentComponent است. FragmentComponent و ViewComponent زیر مجموعه ActivityComponent هستند. ActivityComponent زیر مجموعه ActivityRetainedComponent است. ViewModelComponent زیر مجموعه ActivityRetainedComponent است. ActivityRetainedComponent و ServiceComponent زیر مجموعه SingletonComponent هستند.
شکل ۱. سلسله مراتب اجزایی که هیلت تولید می‌کند.

اتصالات پیش‌فرض کامپوننت

هر کامپوننت Hilt با مجموعه‌ای از اتصالات پیش‌فرض ارائه می‌شود که Hilt می‌تواند آن‌ها را به عنوان وابستگی به اتصالات سفارشی شما تزریق کند. توجه داشته باشید که این اتصالات مربوط به انواع عمومی activity و fragment هستند و نه هیچ زیرکلاس خاصی. دلیل این امر این است که Hilt از یک تعریف کامپوننت activity واحد برای تزریق همه activityها استفاده می‌کند. هر activity نمونه متفاوتی از این کامپوننت دارد.

کامپوننت اندروید اتصالات پیش‌فرض
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 قابل دسترسی است. برای مثال:

کاتلین

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

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

جاوا

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 قابل دسترسی است. برای مثال:

کاتلین

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

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

جاوا

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 از رایج‌ترین کلاس‌های اندروید پشتیبانی می‌کند. با این حال، ممکن است لازم باشد تزریق فیلد را در کلاس‌هایی که Hilt از آنها پشتیبانی نمی‌کند، انجام دهید.

در این موارد، می‌توانید با استفاده از حاشیه‌نویسی @EntryPoint یک نقطه ورود ایجاد کنید. نقطه ورود مرز بین کدی است که توسط Hilt مدیریت می‌شود و کدی که توسط Hilt مدیریت نمی‌شود. این نقطه‌ای است که کد برای اولین بار وارد نمودار اشیاء تحت مدیریت Hilt می‌شود. نقاط ورود به Hilt اجازه می‌دهند از کدی استفاده کند که Hilt مدیریت نمی‌کند تا وابستگی‌هایی را در نمودار وابستگی ارائه دهد.

برای مثال، Hilt مستقیماً از ارائه‌دهندگان محتوا پشتیبانی نمی‌کند. اگر می‌خواهید یک ارائه‌دهنده محتوا از Hilt برای دریافت برخی وابستگی‌ها استفاده کند، باید رابطی تعریف کنید که برای هر نوع اتصال مورد نظر شما با @EntryPoint حاشیه‌نویسی شده باشد و شامل توصیف‌کننده‌ها باشد. سپس @InstallIn برای مشخص کردن مؤلفه‌ای که نقطه ورود در آن نصب می‌شود، به صورت زیر اضافه کنید:

کاتلین

class ExampleContentProvider : ContentProvider() {

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

  ...
}

جاوا

public class ExampleContentProvider extends ContentProvider {

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

برای دسترسی به یک نقطه ورودی، از متد استاتیک مناسب از EntryPointAccessors استفاده کنید. پارامتر باید یا نمونه کامپوننت یا شیء @AndroidEntryPoint باشد که به عنوان نگهدارنده کامپوننت عمل می‌کند. مطمئن شوید که کامپوننتی که به عنوان پارامتر ارسال می‌کنید و متد استاتیک EntryPointAccessors هر دو با کلاس اندروید در حاشیه‌نویسی @InstallIn در رابط @EntryPoint مطابقت دارند:

کاتلین

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

جاوا

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 در یک برنامه اندروید ارائه می‌دهد.

در رابطه با Dagger، اهداف Hilt به شرح زیر است:

  • برای ساده‌سازی زیرساخت‌های مربوط به Dagger برای برنامه‌های اندروید.
  • برای ایجاد مجموعه‌ای استاندارد از کامپوننت‌ها و حوزه‌ها به منظور سهولت در راه‌اندازی، خوانایی و اشتراک‌گذاری کد بین برنامه‌ها.
  • برای ارائه روشی آسان برای تهیه‌ی اتصالات مختلف به انواع مختلف ساخت، مانند آزمایش، اشکال‌زدایی یا انتشار.

از آنجا که سیستم عامل اندروید بسیاری از کلاس‌های چارچوب خود را نمونه‌سازی می‌کند، استفاده از Dagger در یک برنامه اندروید مستلزم نوشتن مقدار قابل توجهی کد تکراری است. Hilt کد تکراری مورد نیاز برای استفاده از Dagger در یک برنامه اندروید را کاهش می‌دهد. Hilt به طور خودکار موارد زیر را تولید و ارائه می‌دهد:

  • کامپوننت‌هایی برای ادغام کلاس‌های فریم‌ورک اندروید با Dagger که در غیر این صورت باید به صورت دستی ایجاد می‌شدند.
  • حاشیه‌نویسی‌های محدوده برای استفاده با کامپوننت‌هایی که Hilt به طور خودکار تولید می‌کند.
  • اتصالات از پیش تعریف شده برای نمایش کلاس‌های اندروید مانند Application یا Activity .
  • توصیف‌کننده‌های از پیش تعریف‌شده برای نمایش @ApplicationContext و @ActivityContext .

کد Dagger و Hilt می‌توانند در یک کدبیس مشترک وجود داشته باشند. با این حال، در بیشتر موارد بهتر است از Hilt برای مدیریت تمام استفاده‌های خود از Dagger در اندروید استفاده کنید. برای انتقال پروژه‌ای که از Dagger به Hilt استفاده می‌کند، به راهنمای انتقال و Migrating your Dagger app to Hilt codelab مراجعه کنید.

منابع اضافی

برای کسب اطلاعات بیشتر در مورد Hilt، به منابع اضافی زیر مراجعه کنید.

نمونه‌ها

کدلبز

وبلاگ‌ها