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

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

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

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

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

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

מגניב

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

Kotlin

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

Kotlin

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 משתמש בתכונות של Java 8. כדי להפעיל את Java 8 בפרויקט, מוסיפים את השורות הבאות לקובץ app/build.gradle:

מגניב

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

Kotlin

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

מחלקת אפליקציה של Hilt

כל האפליקציות שמשתמשות ב-Hilt חייבות להכיל מחלקה Application עם ההערה @HiltAndroidApp.

@HiltAndroidApp מפעיל את יצירת הקוד של Hilt, כולל מחלקת בסיס לאפליקציה שמשמשת כמאגר תלות ברמת האפליקציה.

Kotlin

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

Java

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

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

הזרקת יחסי תלות למחלקות ב-Android

אחרי שמגדירים את Hilt בכיתה Application ורכיב ברמת האפליקציה זמין, Hilt יכול לספק תלויות לכיתות אחרות ב-Android עם ההערה @AndroidEntryPoint:

Kotlin

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

Java

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

נכון לעכשיו, Hilt תומך במחלקות הבאות של Android:

  • Application (באמצעות @HiltAndroidApp)
  • ViewModel (באמצעות @HiltViewModel)
  • Activity
  • Fragment
  • View
  • Service
  • BroadcastReceiver

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

@AndroidEntryPoint יוצר רכיב Hilt נפרד לכל מחלקה של Android בפרויקט. הרכיבים האלה יכולים לקבל תלויות ממחלקות ההורה שלהם, כפי שמתואר בהיררכיית הרכיבים.

כדי לקבל תלויות מרכיב, משתמשים בהערה @Inject כדי לבצע הזרקת שדה:

Kotlin

@AndroidEntryPoint
class ExampleActivity : AppCompatActivity() {

  @Inject lateinit var analytics: AnalyticsAdapter
  ...
}

Java

@AndroidEntryPoint
public class ExampleActivity extends AppCompatActivity {

  @Inject
  AnalyticsAdapter analytics;
  ...
}

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

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

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

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

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

Kotlin

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

Java

public class AnalyticsAdapter {

  private final AnalyticsService service;

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

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

מודולים של Hilt

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

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

במאמר Component default bindings (התאמות ברירת מחדל של רכיבים) מפורטות התאמות מוגדרות מראש אחרות שזמינות ב-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 destroyed
ActivityRetainedComponent Activity#onCreate() Activity#onDestroy()
ViewModelComponent נוצרו ViewModel ViewModel destroyed
ActivityComponent Activity#onCreate() Activity#onDestroy()
FragmentComponent Fragment#onAttach() Fragment#onDestroy()
ViewComponent View#super() View destroyed
ViewWithFragmentComponent View#super() View destroyed
ServiceComponent Service#onCreate() Service#onDestroy()

היקפי הרכיבים

כברירת מחדל, כל הקישורים ב-Hilt הם unscoped. כלומר, בכל פעם שהאפליקציה מבקשת את הקישור, 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 זמין במאמר Scoping in Android and 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 כדי לבצע את הקישור של הקשר האפליקציה. לדוגמה:

Kotlin

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

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

Java

public class AnalyticsServiceImpl implements AnalyticsService {

  private final Context context;

  @Inject
  AnalyticsAdapter(@ApplicationContext Context context) {
    this.context = context;
  }
}

// The Application binding is available without qualifiers.
public class AnalyticsServiceImpl implements AnalyticsService {

  private final Application application;

  @Inject
  AnalyticsAdapter(Application application) {
    this.application = application;
  }
}

אפשר להשתמש ב-@ActivityContext גם כדי לקשר את ההקשר של הפעילות. לדוגמה:

Kotlin

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

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

Java

public class AnalyticsAdapter {

  private final Context context;

  @Inject
  AnalyticsAdapter(@ActivityContext Context context) {
    this.context = context;
  }
}

// The Activity binding is available without qualifiers.
public class AnalyticsAdapter {

  private final FragmentActivity activity;

  @Inject
  AnalyticsAdapter(FragmentActivity activity) {
    this.activity = activity;
  }
}

הזרקת תלויות בכיתות שלא נתמכות על ידי Hilt

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

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

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

Kotlin

class ExampleContentProvider : ContentProvider() {

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

  ...
}

Java

public class ExampleContentProvider extends ContentProvider {

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

כדי לגשת לנקודת כניסה, משתמשים בשיטה הסטטית המתאימה מתוך EntryPointAccessors. הפרמטר צריך להיות מופע הרכיב או אובייקט @AndroidEntryPoint שמשמש כמאגר הרכיבים. מוודאים שהרכיב שמעבירים כפרמטר והשיטה EntryPointAccessors 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 and Dagger

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

המטרות של Hilt בהשוואה ל-Dagger הן:

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

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

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

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

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

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

דוגמאות

Codelabs

בלוגים