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

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

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

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

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

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

מגניב

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

Kotlin

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

Kotlin

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

android {
  ...
}

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

‫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 היא הזרקת בנאי. משתמשים בהערה @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-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
  );
}

המודול AnalyticsModule של Hilt מסומן ב-@InstallIn(ActivityComponent.class) כי רוצים ש-Hilt יזריק את התלות הזו ל-ExampleActivity. ההערה הזו מציינת שכל התלויות ב-AnalyticsModule זמינות בכל הפעילויות של האפליקציה.

הזרקת מופעים באמצעות ‎ @Provides

ממשקים הם לא המקרה היחיד שבו אי אפשר להזריק סוג באמצעות 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 עם 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 מצמצם את הקוד הסטנדרטי שנדרש לשימוש ב-Dagger באפליקציית Android. ‫Hilt יוצר ומספק באופן אוטומטי את הפריטים הבאים:

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

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

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

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

טעימות

Codelabs

בלוגים