Hilt เป็นไลบรารีการแทรก Dependency สำหรับ Android ที่ช่วยลดโค้ดสำเร็จรูปในการแทรก Dependency ด้วยตนเองในโปรเจ็กต์ การทำการแทรก Dependency ด้วยตนเองกำหนดให้คุณต้องสร้างทุกคลาสและ 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 และให้ทรัพยากร Dependency แก่ออบเจ็กต์ดังกล่าว นอกจากนี้ ยังเป็นคอมโพเนนต์หลักของแอป ซึ่งหมายความว่าคอมโพเนนต์อื่นๆ จะเข้าถึงทรัพยากร Dependency ที่คอมโพเนนต์นี้มีให้ได้
แทรกทรัพยากร Dependency ลงในคลาส Android
เมื่อตั้งค่า Hilt ในคลาส Application และคอมโพเนนต์ระดับแอปพลิเคชันพร้อมใช้งานแล้ว Hilt จะสามารถจัดเตรียมทรัพยากร Dependency ให้กับคลาส Android อื่นๆ ที่มีคำอธิบายประกอบ @AndroidEntryPoint ได้
@AndroidEntryPoint class ExampleActivity : ComponentActivity() { ... }
ปัจจุบัน Hilt รองรับคลาส Android ต่อไปนี้
Application(โดยใช้@HiltAndroidApp)ViewModel(โดยใช้@HiltViewModel)ActivityServiceBroadcastReceiver
ใน Compose คุณไม่จำเป็นต้องใส่คำอธิบายประกอบคอมโพสิเบิลแต่ละรายการ แต่ให้ใส่คำอธิบายประกอบ ComponentActivity ระดับรูทด้วย @AndroidEntryPoint แทน ซึ่งทำหน้าที่เป็นจุดแรกเข้า DI จุดเดียวสำหรับลำดับชั้น UI ทั้งหมด คุณจึงเข้าถึง ViewModel ที่แทรก Hilt ได้โดยตรงภายในฟังก์ชันที่ประกอบกันได้
@AndroidEntryPoint จะสร้างคอมโพเนนต์ Hilt แต่ละรายการสำหรับคลาส Android แต่ละคลาสในโปรเจ็กต์ คอมโพเนนต์เหล่านี้สามารถรับทรัพยากร Dependency จากคลาสหลักที่เกี่ยวข้องตามที่อธิบายไว้ในลำดับชั้นของคอมโพเนนต์
หากต้องการรับทรัพยากร Dependency จากคอมโพเนนต์ ให้ใช้คำอธิบายประกอบ @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 เป็นทรัพยากร Dependency ดังนั้น 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 แทรก Dependency นั้นลงใน ExampleActivity หมายเหตุนี้หมายความว่าทรัพยากร Dependency ทั้งหมดใน AnalyticsModule พร้อมใช้งานในกิจกรรมทั้งหมดของแอป
แทรกอินสแตนซ์ด้วย @Provides
อินเทอร์เฟซไม่ใช่กรณีเดียวที่คุณไม่สามารถแทรกเครื่องมือสร้างประเภทได้
นอกจากนี้ คุณยังไม่สามารถใช้การแทรกเครื่องมือสร้างได้หากคุณไม่ได้เป็นเจ้าของคลาสเนื่องจากคลาสนั้นมาจากไลบรารีภายนอก (คลาสอย่าง Retrofit, OkHttpClient หรือฐานข้อมูล Room) หรือหากต้องสร้างอินสแตนซ์ด้วยรูปแบบเครื่องมือสร้าง
ลองพิจารณาตัวอย่างก่อนหน้านี้ หากคุณไม่ได้เป็นเจ้าของคลาส AnalyticsService โดยตรง คุณสามารถบอก Hilt ถึงวิธีจัดเตรียมอินสแตนซ์ของประเภทนี้ได้โดยสร้างฟังก์ชันภายในโมดูล Hilt และกำกับเนื้อหาฟังก์ชันนั้นด้วย @Provides
ฟังก์ชันที่มีคำอธิบายประกอบจะให้ข้อมูลต่อไปนี้แก่ Hilt
- ประเภทการแสดงผลของฟังก์ชันจะบอก Hilt ว่าฟังก์ชันจะแสดงอินสแตนซ์ประเภทใด
- พารามิเตอร์ของฟังก์ชันจะบอก Hilt ถึงทรัพยากร Dependency ของประเภทที่เกี่ยวข้อง
- เนื้อหาของฟังก์ชันจะบอก 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 คุณต้องระบุการเชื่อมโยงหลายรายการให้ Hilt คุณสามารถกำหนดการเชื่อมโยงหลายรายการสำหรับประเภทเดียวกันได้ด้วยตัวระบุ
ตัวระบุคุณสมบัติคือคำอธิบายประกอบที่คุณใช้เพื่อระบุการเชื่อมโยงที่เฉพาะเจาะจงสำหรับประเภทหนึ่งๆ เมื่อประเภทนั้นมีการเชื่อมโยงที่กำหนดไว้หลายรายการ
ลองดูตัวอย่างต่อไปนี้ หากต้องการสกัดกั้นการเรียกใช้ AnalyticsService คุณสามารถใช้ออบเจ็กต์ OkHttpClient กับ interceptor ได้ สำหรับบริการอื่นๆ คุณอาจต้องสกัดกั้นการโทรด้วยวิธีอื่น ในกรณีนี้ คุณต้องบอก 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 }
แนวทางปฏิบัติแนะนำคือ หากคุณเพิ่มตัวระบุคุณสมบัติลงในประเภท ให้เพิ่มตัวระบุคุณสมบัติลงในวิธีที่เป็นไปได้ทั้งหมดในการระบุทรัพยากร Dependency นั้น การปล่อยให้การติดตั้งใช้งานพื้นฐานหรือทั่วไปไม่มีตัวระบุอาจทำให้เกิดข้อผิดพลาดและอาจส่งผลให้ 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 | หัวฉีดสำหรับ |
|---|---|
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 เป็นการขึ้นต่อกันกับประเภทอื่นหรือผ่านการแทรกฟิลด์ (เช่นใน 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 ของการเชื่อมโยงอื่นๆ ในคอมโพเนนต์นั้นหรือในคอมโพเนนต์ย่อยใดๆ ที่อยู่ด้านล่างในลำดับชั้นของคอมโพเนนต์
การเชื่อมโยงเริ่มต้นของคอมโพเนนต์
คอมโพเนนต์ 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 ) { ... }
แทรกทรัพยากร Dependency ในคลาสที่ Hilt ไม่รองรับ
ใน Compose รูปแบบมาตรฐานคือการแทรกทรัพยากร Dependency ลงใน @HiltViewModel โดยใช้การแทรกเครื่องมือสร้าง จากนั้นใช้ hiltViewModel() ภายใน Composable เพื่อเข้าถึง ViewModel แม้ว่า Hilt จะรองรับคลาส Android ที่พบบ่อยที่สุด แต่คุณอาจยังคงพบคลาสที่ไม่รองรับซึ่งคุณต้องทำการแทรกฟิลด์
ในกรณีดังกล่าว คุณสามารถสร้างจุดแรกเข้าโดยใช้คำอธิบายประกอบ @EntryPoint จุดแรกเข้าคือขอบเขตระหว่างโค้ดที่จัดการโดย Hilt กับโค้ดที่ไม่ได้จัดการ ซึ่งเป็นจุดที่โค้ดเข้าสู่กราฟของออบเจ็กต์ที่ Hilt จัดการเป็นครั้งแรก จุดแรกเข้าช่วยให้ Hilt ใช้โค้ดที่ Hilt ไม่ได้จัดการเพื่อระบุทรัพยากร Dependency ภายในกราฟทรัพยากร Dependency ได้
เช่น Hilt ไม่รองรับ Content Provider โดยตรง หากต้องการให้ Content Provider ใช้ Hilt เพื่อรับทรัพยากร Dependency บางอย่าง คุณต้องกำหนดอินเทอร์เฟซที่มีคำอธิบายประกอบด้วย @EntryPoint สำหรับการเชื่อมโยงแต่ละประเภทที่ต้องการและรวมตัวระบุคุณสมบัติ จากนั้นเพิ่ม @InstallIn เพื่อระบุคอมโพเนนต์ที่จะติดตั้งจุดแรกเข้า ดังนี้
class ExampleContentProvider : ContentProvider() { @EntryPoint @InstallIn(SingletonComponent::class) interface ExampleContentProviderEntryPoint { fun analyticsService(): AnalyticsService } ... }
หากต้องการเข้าถึงจุดแรกเข้า ให้ใช้เมธอดแบบคงที่ที่เหมาะสมจาก EntryPointAccessors พารามิเตอร์ควรเป็นอินสแตนซ์คอมโพเนนต์หรือออบเจ็กต์ @AndroidEntryPoint ที่ทำหน้าที่เป็นตัวยึดคอมโพเนนต์ ตรวจสอบว่าคอมโพเนนต์ที่คุณส่งเป็นพารามิเตอร์และเมธอดแบบคงที่ EntryPointAccessors ตรงกับคลาส 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 และ Dagger
Hilt เป็นไลบรารีที่แนะนำอย่างเป็นทางการสำหรับการแทรก Dependency ใน Android ซึ่งเป็นวิธีที่ได้มาตรฐาน มีความเห็น และมีประสิทธิภาพในการใช้ Dependency Injection ในแอปพลิเคชัน โดยได้รับการเพิ่มประสิทธิภาพสำหรับ 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 ได้ที่แหล่งข้อมูลเพิ่มเติมต่อไปนี้
ตัวอย่าง
บล็อก
- Dependency Injection ใน Android ด้วย Hilt
- การกำหนดขอบเขตใน Android และ Hilt
- การเพิ่มคอมโพเนนต์ลงในลำดับชั้นของ Hilt
- การย้ายข้อมูลแอป Google I/O ไปยัง Hilt