החדרת תלות באמצעות Hilt (תצוגות)

מושגים והטמעה של Jetpack Compose

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

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

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

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

‫@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 היא החדרה ל-constructor. משתמשים בהערה @Inject ב-constructor של מחלקה כדי להורות ל-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;
  }
  ...
}

הפרמטרים של ה-constructor של המחלקה הם יחסי התלות של המחלקה הזו. בדוגמה, 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
  );
}

המודול AnalyticsModule של Hilt מסומן באמצעות ההערה @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.

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

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

ביחס ל-Dagger, המטרות של Hilt הן:

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

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

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

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