Dependency Injection พร้อม Hilt

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

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

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

การเพิ่มทรัพยากร Dependency

ก่อนอื่น ให้เพิ่มปลั๊กอิน hilt-android-gradle-plugin ลงในรูทของไฟล์ build.gradle ของโปรเจ็กต์

Kotlin

plugins {
  ...
  id("com.google.dagger.hilt.android") version "2.57.1" apply false
}

ดึงดูด

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

จากนั้นใช้ปลั๊กอิน Gradle และเพิ่มทรัพยากร Dependency เหล่านี้ในไฟล์ app/build.gradle

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

ดึงดูด

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

หากต้องการตรวจสอบว่าโปรเจ็กต์ได้รับการกำหนดค่าสำหรับ Java 17 ซึ่งจำเป็นสำหรับ Jetpack Compose และ Hilt เวอร์ชันต่างๆ ให้เพิ่มข้อมูลต่อไปนี้ลงในไฟล์ app/build.gradle

Kotlin

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

ดึงดูด

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

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

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

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

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

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

แทรกการอ้างอิงในคลาส Android

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

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

ปัจจุบัน Hilt รองรับคลาส Android ต่อไปนี้

  • Application (โดยใช้ @HiltAndroidApp)
  • ViewModel (โดยใช้ @HiltViewModel)
  • Activity
  • Service
  • BroadcastReceiver

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

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

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

@AndroidEntryPoint
class ExampleActivity : ComponentActivity() {

  @Inject lateinit var analytics: AnalyticsAdapter
  ...
}

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

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

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

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

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

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

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

โมดูล Hilt

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

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

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

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

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

คำอธิบายประกอบ @Binds จะบอก Hilt ว่าควรใช้การติดตั้งใช้งานใดเมื่อต้องการ ระบุอินสแตนซ์ของอินเทอร์เฟซ

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

  • ประเภทการแสดงผลของฟังก์ชันจะบอก Hilt ว่าฟังก์ชันมีอินเทอร์เฟซใด อินสแตนซ์
  • พารามิเตอร์ฟังก์ชันจะบอก Hilt ว่าควรใช้การติดตั้งใช้งานใด
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
}

โมดูล Hilt AnalyticsModule มีคำอธิบายประกอบด้วย @InstallIn(ActivityComponent.class) เนื่องจากคุณต้องการให้ Hilt แทรกการอ้างอิงนั้นลงใน ExampleActivity คำอธิบายประกอบนี้หมายความว่า การอ้างอิงทั้งหมดใน AnalyticsModule จะพร้อมใช้งานในกิจกรรมทั้งหมดของแอป

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

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

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

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

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

ระบุการเชื่อมโยงหลายรายการสำหรับประเภทเดียวกัน

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

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

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

ก่อนอื่น ให้กำหนดตัวระบุที่คุณจะใช้เพื่อประกอบคำอธิบายประกอบของเมธอด @Binds หรือ @Provides ดังนี้

@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class AuthInterceptorOkHttpClient

@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class OtherInterceptorOkHttpClient

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

@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()
  }
}

คุณสามารถแทรกประเภทที่ต้องการได้โดยการใส่คำอธิบายประกอบฟิลด์หรือ พารามิเตอร์ด้วยตัวระบุที่เกี่ยวข้อง

// 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: ComponentActivity() {

  @AuthInterceptorOkHttpClient
  @Inject lateinit var okHttpClient: OkHttpClient
}

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

ตัวระบุที่กำหนดไว้ล่วงหน้าใน Hilt

Hilt มีตัวคัดกรองที่กำหนดไว้ล่วงหน้าบางรายการ ตัวอย่างเช่น เนื่องจากคุณอาจต้องใช้คลาส Contextจากแอปพลิเคชันหรือกิจกรรม Hilt จึงมีตัวระบุ @ApplicationContextและ @ActivityContext

สมมติว่าคลาส AnalyticsAdapter จากตัวอย่างต้องการบริบทของกิจกรรม โค้ดต่อไปนี้แสดงวิธีระบุบริบทกิจกรรม ให้กับ AnalyticsAdapter

class AnalyticsAdapter @Inject constructor(
    @ActivityContext private val context: Context,
    private val service: AnalyticsService
) { ... }

ดูการเชื่อมโยงที่กำหนดไว้ล่วงหน้าอื่นๆ ที่มีใน Hilt ได้ที่การเชื่อมโยงเริ่มต้นของคอมโพเนนต์

คอมโพเนนต์ที่สร้างขึ้นสำหรับคลาส Android

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

ตัวอย่างก่อนหน้านี้แสดงให้เห็นการใช้ ActivityComponent ในโมดูล Hilt

Hilt มีคอมโพเนนต์ต่อไปนี้

คอมโพเนนต์ Hilt Injector สำหรับ
SingletonComponent Application
ActivityRetainedComponent ไม่มี
ViewModelComponent ViewModel
ActivityComponent Activity
ServiceComponent Service

อายุการใช้งานของคอมโพเนนต์

Hilt จะสร้างและทำลายอินสแตนซ์ของคลาสคอมโพเนนต์ที่สร้างขึ้นโดยอัตโนมัติ ตามวงจรของคลาส Android ที่เกี่ยวข้อง

คอมโพเนนต์ที่สร้างขึ้น สร้างเมื่อ: ทำลายแล้วที่
SingletonComponent Application#onCreate() Application ถูกทำลาย
ActivityRetainedComponent Activity#onCreate() Activity#onDestroy()
ViewModelComponent สร้างViewModelแล้ว ViewModel ทำลาย
ActivityComponent Activity#onCreate() Activity#onDestroy()
ServiceComponent Service#onCreate() Service#onDestroy()

ขอบเขตของคอมโพเนนต์

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

ในตัวอย่างนี้ ทุกครั้งที่ Hilt ระบุ AnalyticsAdapter เป็นทรัพยากร Dependency ถึง ประเภทอื่นหรือผ่านการแทรกฟิลด์ (ดังใน ExampleActivity) Hilt จะระบุ อินสแตนซ์ใหม่ของ AnalyticsAdapter

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

ตารางด้านล่างแสดงคำอธิบายประกอบขอบเขตสำหรับคอมโพเนนต์ที่สร้างขึ้นแต่ละรายการ

ชั้นเรียน Android คอมโพเนนต์ที่สร้างขึ้น ขอบเขต
Application SingletonComponent @Singleton
Activity ActivityRetainedComponent @ActivityRetainedScoped
ViewModel ViewModelComponent @ViewModelScoped
Activity ActivityComponent @ActivityScoped
Service ServiceComponent @ServiceScoped

ในตัวอย่าง หากคุณกำหนดขอบเขต AnalyticsAdapter ให้กับ ActivityComponent โดยใช้ @ActivityScoped Hilt จะจัดเตรียมอินสแตนซ์เดียวกันของ AnalyticsAdapter ตลอดอายุการใช้งานของกิจกรรมที่เกี่ยวข้อง

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

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

ตัวอย่างต่อไปนี้แสดงวิธีกำหนดขอบเขตการเชื่อมโยงกับคอมโพเนนต์ในโมดูล Hilt ขอบเขตของการเชื่อมโยงต้องตรงกับขอบเขตของคอมโพเนนต์ที่ติดตั้ง ดังนั้นในตัวอย่างนี้ คุณต้องติดตั้ง AnalyticsService ใน SingletonComponent แทน ActivityComponent

// 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)
  }
}

ดูข้อมูลเพิ่มเติมเกี่ยวกับขอบเขตของคอมโพเนนต์ Hilt ได้ที่การกำหนดขอบเขตใน Android และ Hilt

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

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

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

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

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

คอมโพเนนต์ Android การเชื่อมโยงเริ่มต้น
SingletonComponent Application
ActivityRetainedComponent Application
ViewModelComponent SavedStateHandle
ActivityComponent Application, Activity
ServiceComponent Application, Service

การเชื่อมโยงบริบทของแอปพลิเคชันยังใช้ได้โดยใช้ @ApplicationContext ด้วย เช่น

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

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

การเชื่อมโยงบริบทของกิจกรรมยังใช้ได้โดยใช้ @ActivityContext เช่น

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

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

แทรกการอ้างอิงในคลาสที่ Hilt ไม่รองรับ

ใน Compose รูปแบบมาตรฐานคือการแทรกทรัพยากร Dependency ลงใน @HiltViewModel โดยใช้การแทรกผ่านตัวสร้าง จากนั้นใช้ hiltViewModel() ภายใน Composable เพื่อเข้าถึง ViewModel แม้ว่า Hilt จะรองรับคลาส Android ที่พบบ่อยที่สุด แต่คุณก็อาจยังพบคลาสที่ไม่รองรับซึ่งคุณต้องทำการแทรกฟิลด์

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

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

class ExampleContentProvider : ContentProvider() {

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

  ...
}

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

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()
    ...
  }
}

ในตัวอย่างนี้ คุณต้องใช้ ApplicationContext เพื่อดึงข้อมูลจุดแรกเข้า เนื่องจากมีการติดตั้งจุดแรกเข้าใน SingletonComponent หากต้องการดึงข้อมูลการเชื่อมโยงที่อยู่ใน ActivityComponent คุณจะต้องใช้ ActivityContext แทน

Hilt and Dagger

Hilt เป็นคลังที่แนะนำอย่างเป็นทางการสำหรับการจัดการทรัพยากร Dependency ใน Android โดยมีวิธีที่ได้มาตรฐาน มีความเห็น และมีประสิทธิภาพในการติดตั้งใช้งาน การแทรกทรัพยากร Dependency ในแอปพลิเคชัน ซึ่งได้รับการเพิ่มประสิทธิภาพสำหรับ Jetpack Compose และสถาปัตยกรรมแบบกิจกรรมเดียวโดยเฉพาะ

เป้าหมายของ Hilt มีดังนี้

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

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

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

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

แหล่งข้อมูลเพิ่มเติม

ดูข้อมูลเพิ่มเติมเกี่ยวกับ Hilt ได้ที่แหล่งข้อมูลเพิ่มเติมต่อไปนี้

ตัวอย่าง

บล็อก

ดูเนื้อหา