הזרקת תלות באמצעות 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:

מגניב

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. כדי להורות ל-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. רכיב ViewModelComponent נמצא ב-ActivityRetainedComponent. ActivityRetainedComponent ו-ServiceComponent נמצאים ב-SingletonComponent.
איור 1. היררכיית הרכיבים ש-Hilt יוצרת.

קישורי ברירת מחדל של רכיבים

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

רכיב Android קישורי ברירת מחדל
SingletonComponent Application
ActivityRetainedComponent Application
ViewModelComponent SavedStateHandle
ActivityComponent Application, Activity
FragmentComponent Application, Activity Fragment
ViewComponent Application, Activity View
ViewWithFragmentComponent Application, Activity, Fragment, View
ServiceComponent Application, Service

אפשר גם להשתמש ב-@ApplicationContext כדי לקשר את ה-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. עם זאת, יכול להיות שתצטרכו לבצע החדרת שדה למחלקות שלא נתמכות בהילט.

במקרים כאלה, אפשר ליצור נקודת כניסה באמצעות ההערה @EntryPoint. נקודת הכניסה היא הגבול בין קוד שמנוהל על ידי Hilt לבין קוד שלא מנוהל על ידו. זו הנקודה שבה הקוד נכנס לראשונה לתרשים האובייקטים שמנוהל על ידי Hilt. נקודות הכניסה מאפשרות ל-Hhilt להשתמש בקוד שה-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

בלוגים