การแทรกทรัพยากร Dependency ด้วย Hilt (มุมมอง)

แนวคิดและการใช้งาน Jetpack Compose

Hilt เป็นไลบรารีการแทรก Dependency สำหรับ Android ที่ช่วยลดโค้ดสำเร็จรูปในการแทรก Dependency ด้วยตนเองในโปรเจ็กต์ การทำการแทรก Dependency ด้วยตนเองกำหนดให้คุณต้องสร้างทุกคลาสและ Dependency ของคลาสนั้นด้วยตนเอง และใช้คอนเทนเนอร์เพื่อนำ Dependency กลับมาใช้ใหม่และจัดการ Dependency

Hilt มีวิธีมาตรฐานในการใช้ DI ในแอปพลิเคชันโดยการจัดเตรียมคอนเทนเนอร์สำหรับคลาส Android ทุกคลาสในโปรเจ็กต์และจัดการวงจรของคลาสเหล่านั้นโดยอัตโนมัติ Hilt สร้างขึ้นจากไลบรารี DI ยอดนิยมอย่าง Dagger เพื่อใช้ประโยชน์จากความถูกต้องของเวลาคอมไพล์ ประสิทธิภาพรันไทม์ ความสามารถในการปรับขนาด และการรองรับ Android Studio ที่ Dagger มีให้ ดูข้อมูลเพิ่มเติมได้ที่ Hilt และ Dagger

คู่มือนี้จะอธิบายแนวคิดพื้นฐานของ Hilt และคอนเทนเนอร์ที่สร้างขึ้น นอกจากนี้ยังมีการสาธิตวิธีบูตสแตรปแอปที่มีอยู่เพื่อใช้ Hilt ด้วย

คลาสแอปพลิเคชัน Hilt

แอปทั้งหมดที่ใช้ Hilt ต้องมีคลาส Application ที่กำกับด้วยคำอธิบายประกอบ @HiltAndroidApp

@HiltAndroidApp จะทริกเกอร์การสร้างโค้ดของ Hilt ซึ่งรวมถึงคลาสพื้นฐานสำหรับแอปพลิเคชันของคุณที่ทำหน้าที่เป็นคอนเทนเนอร์การแทรกทรัพยากร Dependency ระดับแอปพลิเคชัน

Kotlin

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

Java

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

คอมโพเนนต์ Hilt ที่สร้างขึ้นนี้จะแนบอยู่กับวงจรของออบเจ็กต์ Application และให้ทรัพยากร Dependency แก่ออบเจ็กต์ดังกล่าว นอกจากนี้ ยังเป็นคอมโพเนนต์หลักของแอป ซึ่งหมายความว่าคอมโพเนนต์อื่นๆ จะเข้าถึงทรัพยากร Dependency ที่คอมโพเนนต์นี้มีให้ได้

แทรกทรัพยากร Dependency ลงในคลาส Android

เมื่อตั้งค่า Hilt ในคลาส Application และคอมโพเนนต์ระดับแอปพลิเคชันพร้อมใช้งานแล้ว Hilt จะสามารถจัดเตรียมทรัพยากร Dependency ให้กับคลาส 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 แต่ละคลาสในโปรเจ็กต์ คอมโพเนนต์เหล่านี้สามารถรับทรัพยากร Dependency จากคลาสหลักที่เกี่ยวข้องตามที่อธิบายไว้ในลำดับชั้นของคอมโพเนนต์

หากต้องการรับทรัพยากร Dependency จากคอมโพเนนต์ ให้ใช้คำอธิบายประกอบ @Inject เพื่อทำการแทรกฟิลด์ ดังนี้

Kotlin

@AndroidEntryPoint
class ExampleActivity : AppCompatActivity() {

  @Inject lateinit var analytics: AnalyticsAdapter
  ...
}

Java

@AndroidEntryPoint
public class ExampleActivity extends AppCompatActivity {

  @Inject
  AnalyticsAdapter analytics;
  ...
}

คลาสที่ Hilt แทรกอาจมีคลาสพื้นฐานอื่นๆ ที่ใช้การแทรกด้วย คลาสเหล่านั้นไม่จำเป็นต้องมีคำอธิบายประกอบ @AndroidEntryPoint หากเป็นคลาสแอบสแตรกต์

ดูข้อมูลเพิ่มเติมเกี่ยวกับ Lifecycle Callback ที่คลาส Android ได้รับการแทรกได้ที่อายุการใช้งานของคอมโพเนนต์

กำหนดการเชื่อมโยง Hilt

ในการแทรกฟิลด์ Hilt จำเป็นต้องทราบวิธีระบุอินสแตนซ์ของทรัพยากร Dependency ที่จำเป็นจากคอมโพเนนต์ที่เกี่ยวข้อง การเชื่อมโยงมีข้อมูลที่จำเป็นในการระบุอินสแตนซ์ของประเภทเป็นการขึ้นต่อกัน

วิธีหนึ่งในการให้ข้อมูลการเชื่อมโยงแก่ 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;
  }
  ...
}

พารามิเตอร์ของคอนสตรัคเตอร์ที่มีคำอธิบายประกอบของคลาสคือทรัพยากร Dependency ของคลาสนั้น ในตัวอย่างนี้ AnalyticsAdapter มี AnalyticsService เป็นทรัพยากร Dependency ดังนั้น Hilt จึงต้องรู้วิธีระบุอินสแตนซ์ของ AnalyticsService ด้วย

โมดูล Hilt

บางครั้งระบบไม่สามารถแทรกเครื่องมือสร้างประเภทได้ ซึ่งอาจเกิดขึ้นจากหลายสาเหตุ เช่น คุณไม่สามารถแทรกเครื่องมือสร้างอินเทอร์เฟซได้ นอกจากนี้ คุณยังไม่สามารถแทรกเครื่องมือสร้างประเภทที่คุณไม่ได้เป็นเจ้าของ เช่น คลาสจากไลบรารีภายนอก ในกรณีเหล่านี้ คุณสามารถให้ข้อมูลการเชื่อมโยงแก่ Hilt ได้โดยใช้โมดูล Hilt

โมดูล Hilt เป็นคลาสที่มีคำอธิบายประกอบด้วย @Module เช่นเดียวกับโมดูล Dagger ซึ่งจะแจ้งให้ Hilt ทราบวิธีระบุอินสแตนซ์ของประเภทหนึ่งๆ คุณต้องใส่คำอธิบายประกอบโมดูล Hilt ด้วย @InstallIn เพื่อบอก Hilt ว่าจะใช้หรือติดตั้งแต่ละโมดูลในคลาส Android ใด ซึ่งต่างจากโมดูล Dagger

ทรัพยากร Dependency ที่คุณระบุในโมดูล Hilt จะพร้อมใช้งานในคอมโพเนนต์ที่สร้างขึ้นทั้งหมดซึ่งเชื่อมโยงกับคลาส Android ที่คุณติดตั้งโมดูล Hilt

แทรกอินสแตนซ์อินเทอร์เฟซด้วย @Binds

ลองดูตัวอย่าง AnalyticsService หาก AnalyticsService เป็นอินเทอร์เฟซ คุณจะแทรกเครื่องมือสร้างไม่ได้ แต่ให้ระบุข้อมูลการเชื่อมโยงแก่ 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 แทรก Dependency นั้นลงใน ExampleActivity หมายเหตุนี้หมายความว่าทรัพยากร Dependency ทั้งหมดใน AnalyticsModule พร้อมใช้งานในกิจกรรมทั้งหมดของแอป

แทรกอินสแตนซ์ด้วย @Provides

อินเทอร์เฟซไม่ใช่กรณีเดียวที่คุณไม่สามารถแทรกเครื่องมือสร้างประเภทได้ นอกจากนี้ คุณยังไม่สามารถใช้การแทรกเครื่องมือสร้างได้หากคุณไม่ได้เป็นเจ้าของคลาสเนื่องจากคลาสนั้นมาจากไลบรารีภายนอก (คลาสอย่าง Retrofit, OkHttpClient หรือฐานข้อมูล Room) หรือหากต้องสร้างอินสแตนซ์ด้วยรูปแบบเครื่องมือสร้าง

ลองพิจารณาตัวอย่างก่อนหน้านี้ หากคุณไม่ได้เป็นเจ้าของคลาส AnalyticsService โดยตรง คุณสามารถบอก Hilt ถึงวิธีจัดเตรียมอินสแตนซ์ของประเภทนี้ได้โดยสร้างฟังก์ชันภายในโมดูล Hilt และกำกับเนื้อหาฟังก์ชันนั้นด้วย @Provides

ฟังก์ชันที่มีคำอธิบายประกอบจะให้ข้อมูลต่อไปนี้แก่ Hilt

  • ประเภทการแสดงผลของฟังก์ชันจะบอก Hilt ว่าฟังก์ชันจะแสดงอินสแตนซ์ประเภทใด
  • พารามิเตอร์ของฟังก์ชันจะบอก Hilt ถึงทรัพยากร Dependency ของประเภทที่เกี่ยวข้อง
  • เนื้อหาของฟังก์ชันจะบอก 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 มีการใช้งานประเภทเดียวกันที่แตกต่างกันเป็นทรัพยากร Dependency คุณต้องระบุการเชื่อมโยงหลายรายการให้ Hilt คุณสามารถกำหนดการเชื่อมโยงหลายรายการสำหรับประเภทเดียวกันได้ด้วยตัวระบุ

ตัวระบุคุณสมบัติคือคำอธิบายประกอบที่คุณใช้เพื่อระบุการเชื่อมโยงที่เฉพาะเจาะจงสำหรับประเภทหนึ่งๆ เมื่อประเภทนั้นมีการเชื่อมโยงที่กำหนดไว้หลายรายการ

ลองดูตัวอย่างต่อไปนี้ หากต้องการสกัดกั้นการเรียกใช้ AnalyticsService คุณสามารถใช้ออบเจ็กต์ OkHttpClient กับ interceptor ได้ สำหรับบริการอื่นๆ คุณอาจต้องสกัดกั้นการโทรด้วยวิธีอื่น ในกรณีนี้ คุณต้องบอก Hilt ว่าจะจัดเตรียมการติดตั้งใช้งาน OkHttpClient 2 แบบที่แตกต่างกันอย่างไร

ก่อนอื่น ให้กำหนดตัวระบุคุณสมบัติที่คุณจะใช้เพื่อกำกับเนื้อหาเมธอด @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 ทั้ง 2 เมธอดมีประเภทการแสดงผลเหมือนกัน แต่ตัวระบุจะติดป้ายกำกับเป็น 2 การเชื่อมโยงที่แตกต่างกัน ดังนี้

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;
  ...
}

แนวทางปฏิบัติแนะนำคือ หากคุณเพิ่มตัวระบุคุณสมบัติลงในประเภท ให้เพิ่มตัวระบุคุณสมบัติลงในวิธีที่เป็นไปได้ทั้งหมดในการระบุทรัพยากร Dependency นั้น การปล่อยให้การติดตั้งใช้งานพื้นฐานหรือทั่วไปไม่มีตัวระบุอาจทำให้เกิดข้อผิดพลาดและอาจส่งผลให้ Hilt แทรก Dependency ที่ไม่ถูกต้อง

ตัวระบุที่กำหนดไว้ล่วงหน้าใน 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

ลำดับชั้นของคอมโพเนนต์

การติดตั้งโมดูลลงในคอมโพเนนต์ช่วยให้เข้าถึงการเชื่อมโยงของโมดูลได้ในฐานะทรัพยากร Dependency ของการเชื่อมโยงอื่นๆ ในคอมโพเนนต์นั้นหรือในคอมโพเนนต์ย่อยที่อยู่ด้านล่างในลำดับชั้นของคอมโพเนนต์

ViewWithFragmentComponent อยู่ภายใต้ FragmentComponent FragmentComponent และ ViewComponent อยู่ภายใต้ ActivityComponent ActivityComponent อยู่ภายใต้ ActivityRetainedComponent ViewModelComponent อยู่ภายใต้ ActivityRetainedComponent ActivityRetainedComponent และ ServiceComponent อยู่ภายใต้ SingletonComponent
รูปที่ 1 ลำดับชั้นของคอมโพเนนต์ที่ Hilt สร้างขึ้น

การเชื่อมโยงเริ่มต้นของคอมโพเนนต์

คอมโพเนนต์ Hilt แต่ละรายการมาพร้อมกับชุดการเชื่อมโยงเริ่มต้นที่ Hilt สามารถแทรกเป็นการขึ้นต่อกันลงในการเชื่อมโยงที่กำหนดเองของคุณได้ โปรดทราบว่าการเชื่อมโยงเหล่านี้สอดคล้องกับกิจกรรมทั่วไปและประเภท Fragment ไม่ใช่คลาสย่อยที่เฉพาะเจาะจง เนื่องจาก 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;
  }
}

แทรกทรัพยากร Dependency ในคลาสที่ Hilt ไม่รองรับ

Hilt มาพร้อมกับการรองรับคลาส Android ที่พบบ่อยที่สุด อย่างไรก็ตาม คุณอาจต้องทำการแทรกฟิลด์ในคลาสที่ Hilt ไม่รองรับ

ในกรณีดังกล่าว คุณสามารถสร้างจุดแรกเข้าโดยใช้คำอธิบายประกอบ @EntryPoint จุดแรกเข้าคือขอบเขตระหว่างโค้ดที่จัดการโดย Hilt กับโค้ดที่ไม่ได้จัดการ ซึ่งเป็นจุดที่โค้ดเข้าสู่กราฟของออบเจ็กต์ที่ Hilt จัดการเป็นครั้งแรก จุดแรกเข้าช่วยให้ Hilt ใช้โค้ดที่ Hilt ไม่ได้จัดการเพื่อระบุทรัพยากร Dependency ภายในกราฟทรัพยากร Dependency ได้

เช่น Hilt ไม่รองรับ Content Provider โดยตรง หากต้องการให้ Content Provider ใช้ Hilt เพื่อรับทรัพยากร Dependency บางอย่าง คุณต้องกำหนดอินเทอร์เฟซที่มีคำอธิบายประกอบด้วย @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 สร้างขึ้นบนไลบรารีการแทรก Dependency ของ Dagger ซึ่งเป็นวิธีมาตรฐานในการรวม Dagger เข้ากับแอปพลิเคชัน Android

เมื่อพิจารณาจาก Dagger แล้ว เป้าหมายของ Hilt มีดังนี้

  • เพื่อลดความซับซ้อนของโครงสร้างพื้นฐานที่เกี่ยวข้องกับ Dagger สำหรับแอป Android
  • เพื่อสร้างชุดคอมโพเนนต์และขอบเขตมาตรฐานเพื่อลดความซับซ้อนในการตั้งค่า ความสามารถในการอ่าน และการแชร์โค้ดระหว่างแอป
  • เพื่อมอบวิธีที่ง่ายในการจัดสรรการเชื่อมโยงต่างๆ ให้กับบิลด์ประเภทต่างๆ เช่น การทดสอบ การแก้ไขข้อบกพร่อง หรือการเผยแพร่

เนื่องจากระบบปฏิบัติการ Android สร้างอินสแตนซ์คลาสเฟรมเวิร์กของตัวเองจำนวนมาก การใช้ Dagger ในแอป Android จึงกำหนดให้คุณต้องเขียนต้นแบบจำนวนมาก Hilt ช่วยลดโค้ด Boilerplate ที่เกี่ยวข้องกับการใช้ Dagger ในแอปพลิเคชัน Android Hilt จะสร้างและจัดเตรียมสิ่งต่อไปนี้ให้โดยอัตโนมัติ

  • คอมโพเนนต์สำหรับการผสานรวมคลาสเฟรมเวิร์ก Android กับ Dagger ที่คุณจะต้องสร้างด้วยตนเอง
  • คำอธิบายประกอบขอบเขตเพื่อใช้กับคอมโพเนนต์ที่ Hilt สร้างขึ้นโดยอัตโนมัติ
  • การเชื่อมโยงที่กำหนดไว้ล่วงหน้าเพื่อแสดงคลาส Android เช่น Application หรือ Activity
  • ตัวระบุคุณสมบัติที่กำหนดไว้ล่วงหน้าเพื่อแสดง @ApplicationContext และ @ActivityContext

โค้ด Dagger และ Hilt สามารถอยู่ร่วมกันในฐานของโค้ดเดียวกันได้ อย่างไรก็ตาม ในกรณีส่วนใหญ่ คุณควรใช้ Hilt เพื่อจัดการการใช้งาน Dagger ทั้งหมดใน Android หากต้องการย้ายข้อมูลโปรเจ็กต์ที่ใช้ Dagger ไปยัง Hilt โปรดดูคำแนะนำในการย้ายข้อมูล