הזרקת תלות באמצעות Hilt

Hilt היא ספרייה להזרקת תלויות ל-Android שמפחיתה את הקוד הסטנדרטי הנדרש להזרקת תלויות באופן ידני בפרויקט. כדי לבצע הזרקה ידנית של יחסי תלות, צריך ליצור כל כיתה ואת יחסי התלות שלה באופן ידני, ולהשתמש בקונטיינרים כדי לעשות שימוש חוזר ביחסי התלות ולנהל אותם.

Hilt מספק דרך רגילה להשתמש ב-DI באפליקציה, על ידי מתן קונטיינרים לכל כיתה של Android בפרויקט וניהול מחזור החיים שלהם באופן אוטומטי. ‏Hilt מבוסס על ספריית ה-DI הפופולרית Dagger, כדי ליהנות מהנכסים הבאים של Dagger: תקינות בזמן הידור, ביצועים בסביבת זמן ריצה, יכולת התאמה לעומס ותמיכה ב-Android Studio. מידע נוסף זמין במאמר Hilt and Dagger.

במדריך הזה מוסבר על המושגים הבסיסיים של Hilt ועל הקונטיינרים שנוצרים על ידו. בנוסף, תוכלו לראות הדגמה של איך להפעיל אפליקציה קיימת כדי להשתמש ב-Hilt.

הוספת יחסי תלות

קודם כול, מוסיפים את הפלאגין hilt-android-gradle-plugin לקובץ build.gradle ברמה הבסיסית של הפרויקט:

Groovy

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:

Groovy

...
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:

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.

למידע נוסף על קריאה חוזרת (callback) של מחזור חיים שדרכה מוזרק שיעור של Android, תוכלו לקרוא את המאמר משכי החיים של רכיבים.

הגדרת קישורים של Hilt

כדי לבצע הזרקה של שדות, Hilt צריך לדעת איך לספק מופעים של יחסי התלות הנדרשים מהרכיב המתאים. קישור מכיל את המידע הנדרש כדי לספק מכונות של סוג מסוים כיחס תלות.

אחת הדרכים לספק מידע על קישור ל-Hilt היא הזרקה לברירת המחדל (constructor injection). כדי להורות ל-Hilt איך לספק מכונות של הכיתה הזו, משתמשים בהערה @Inject ב-constructor של הכיתה:

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

לפעמים אי אפשר להחדיר סוג באמצעות ה-constructor. יכולות להיות לכך כמה סיבות. לדוגמה, אי אפשר להחדיר ממשק באמצעות ה-constructor. בנוסף, אי אפשר להחדיר סוג שאינו בבעלותכם ליצירת ה-constructor, למשל כיתה מספרייה חיצונית. במקרים כאלה, אפשר לספק ל-Hilt את פרטי הקישור באמצעות מודולים של Hilt.

מודול Hilt הוא מחלקה עם הערה @Module. בדומה למודול של Dagger, הוא מעדכן את Hilt איך לספק מופעים של סוגים מסוימים. בניגוד למודולים של Dagger, צריך להוסיף הערה למודולים של Hilt באמצעות @InstallIn כדי להודיע ל-Hilt באיזו כיתה של Android ישתמשו בכל מודול או באיזו כיתה יתקינו אותו.

יחסי התלות שאתם מספקים במודולים של Hilt זמינים בכל הרכיבים שנוצרים ומשויכים לכיתה של Android שבה מתקינים את מודול Hilt.

הזרקה של מופע ממשק באמצעות @Binds

נבחן את הדוגמה AnalyticsService. אם AnalyticsService הוא ממשק, אי אפשר להחדיר אותו באמצעות ה-constructor. במקום זאת, צריך לספק ל-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

ממשקים הם לא המקרה היחיד שבו אי אפשר להחדיר סוג באמצעות ה-constructor. אי אפשר גם להזריק ל-constructor אם אין לכם בעלות על הכיתה כי היא מגיעה מספרייה חיצונית (כיתות כמו Retrofit,‏ OkHttpClient או מסדי נתונים של Room), או אם צריך ליצור את המופעים באמצעות תבנית ה-builder.

נבחן את הדוגמה הקודמת. אם אתם לא הבעלים הישירים של הכיתה 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. רכיב ViewModel נמצא ב-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 כדי לקשר את ה-context של האפליקציה. לדוגמה:

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 יש תמיכה בסוגי ה-class הנפוצים ביותר ב-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 תואמים לכיתה של 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.
  • כדי ליצור קבוצה רגילה של רכיבים והיקפים, וכך להקל על ההגדרה, הקריאה ושיתוף הקוד בין אפליקציות.
  • כדי לספק דרך קלה להקצות קישורים שונים לסוגים שונים של גרסאות build, כמו בדיקה, ניפוי באגים או גרסה זמינה.

מאחר שמערכת ההפעלה של Android יוצרת עותקים של הרבה מהכיתות של המסגרת שלה, כדי להשתמש ב-Dagger באפליקציית Android צריך לכתוב כמות גדולה של קוד סטנדרטי. Hilt מפחית את כמות הקוד הסטנדרטי שנדרש לשימוש ב-Dagger באפליקציית Android. Hilt יוצר באופן אוטומטי את הפריטים הבאים ומספק אותם:

  • רכיבים לשילוב של כיתות מסגרת של Android עם Dagger, שאחרת צריך ליצור אותם באופן ידני.
  • הערות היקף לשימוש עם הרכיבים ש-Hilt יוצר באופן אוטומטי.
  • קישורים מוגדרים מראש שמייצגים כיתות של Android, כמו Application או Activity.
  • מסננים מוגדרים מראש שמייצגים את @ApplicationContext ואת @ActivityContext.

אפשר לשלב קוד של Dagger ו-Hilt באותו קוד בסיס. עם זאת, ברוב המקרים עדיף להשתמש ב-Hilt כדי לנהל את כל השימוש ב-Dagger ב-Android. כדי להעביר פרויקט שמשתמש ב-Dagger ל-Hilt, אפשר לעיין במדריך להעברה ובcodelab להעברת אפליקציות Dagger ל-Hilt.

מקורות מידע נוספים

מידע נוסף על Hilt זמין במקורות המידע הבאים.

דוגמיות

Codelabs

בלוגים