Hilt เป็นไลบรารี Dependency Injection สําหรับ Android ที่ช่วยลดการเขียนโค้ดซ้ำๆ ในการทำ Dependency Injection ด้วยตนเองในโปรเจ็กต์ การทำการแทรก Dependency ด้วยตนเองจะทำให้คุณต้องสร้างคลาสและ Dependency ของคลาสแต่ละรายการด้วยตนเอง รวมถึงใช้คอนเทนเนอร์เพื่อนำ Dependency กลับมาใช้ซ้ำและจัดการ Dependency
Hilt มีวิธีมาตรฐานในการใช้ DI ในแอปพลิเคชันโดยจัดหาคอนเทนเนอร์สำหรับคลาส Android ทุกคลาสในโปรเจ็กต์และจัดการวงจรชีวิตของคลาสเหล่านั้นโดยอัตโนมัติ Hilt สร้างขึ้นจากไลบรารี DI ที่ได้รับความนิยมอย่าง Dagger เพื่อให้ได้รับประโยชน์จากความถูกต้องของเวลาคอมไพล์ ประสิทธิภาพรันไทม์ ความสามารถในการปรับขนาด และการรองรับ Android Studio ที่ Dagger มีให้ ดูข้อมูลเพิ่มเติมได้ที่ด้ามจับและกริช
คู่มือนี้จะอธิบายแนวคิดพื้นฐานของ Hilt และคอนเทนเนอร์ที่สร้างขึ้น รวมถึงมีการแสดงวิธีเริ่มต้นแอปที่มีอยู่เพื่อใช้ Hilt ด้วย
การเพิ่มทรัพยากร Dependency
ก่อนอื่น ให้เพิ่มปลั๊กอิน hilt-android-gradle-plugin
ลงในไฟล์ build.gradle
รูทของโปรเจ็กต์ โดยทำดังนี้
Groovy
plugins { ... id 'com.google.dagger.hilt.android' version '2.51.1' apply false }
Kotlin
plugins { ... id("com.google.dagger.hilt.android") version "2.51.1" apply false }
จากนั้นใช้ปลั๊กอิน Gradle และเพิ่ม Dependency ต่อไปนี้ในไฟล์ app/build.gradle
Groovy
... plugins { id 'kotlin-kapt' id 'com.google.dagger.hilt.android' } android { ... } dependencies { implementation "com.google.dagger:hilt-android:2.51.1" kapt "com.google.dagger:hilt-compiler:2.51.1" } // Allow references to generated code kapt { correctErrorTypes true }
Kotlin
plugins { id("kotlin-kapt") id("com.google.dagger.hilt.android") } android { ... } dependencies { implementation("com.google.dagger:hilt-android:2.51.1") kapt("com.google.dagger:hilt-android-compiler:2.51.1") } // Allow references to generated code kapt { correctErrorTypes = true }
Hilt ใช้ฟีเจอร์ Java 8 หากต้องการเปิดใช้ Java 8 ในโปรเจ็กต์ ให้เพิ่มข้อมูลต่อไปนี้ลงในไฟล์ app/build.gradle
Groovy
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
และระบุข้อมูลที่ต้องพึ่งพา นอกจากนี้ ยังเป็นคอมโพเนนต์หลักของแอป ซึ่งหมายความว่าคอมโพเนนต์อื่นๆ สามารถเข้าถึงข้อกําหนดซึ่งคอมโพเนนต์หลักมีให้
แทรก Dependency ลงในคลาส 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
หากเป็นคลาสนามธรรม
ดูข้อมูลเพิ่มเติมเกี่ยวกับรอบการเรียกกลับของวงจรที่คลาส 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
บางครั้งระบบจะฉีดประเภทไม่ได้ ปัญหานี้อาจเกิดขึ้นได้จากหลายสาเหตุ เช่น คุณจะฉีดอินเทอร์เฟซโดยใช้คอนสตรัคเตอร์ไม่ได้ นอกจากนี้ คุณยังไม่สามารถแทรกประเภทที่คุณไม่ได้เป็นเจ้าของลงในตัวสร้างคอนสตรัคเตอร์ เช่น คลาสจากไลบรารีภายนอก ในกรณีเหล่านี้ คุณสามารถระบุข้อมูลการเชื่อมโยงให้กับ Hilt ได้โดยใช้โมดูล Hilt
โมดูล Hilt คือคลาสที่มีคำอธิบายประกอบ @Module
เช่นเดียวกับโมดูล Dagger ข้อมูลนี้จะบอก Hilt ว่าจะจัดหาอินสแตนซ์ของบางประเภทอย่างไร คุณต้องกำกับเนื้อหาโมดูล Hilt ด้วย @InstallIn
เพื่อบอก Hilt ว่าจะใช้หรือติดตั้งโมดูลแต่ละรายการในคลาส Android ใด ซึ่งแตกต่างจากโมดูล Dagger
ไลบรารีที่คุณระบุไว้ในโมดูล 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 แทรกข้อกำหนดดังกล่าวลงใน ExampleActivity
คําอธิบายประกอบนี้หมายความว่าข้อกําหนดทั้งหมดใน AnalyticsModule
พร้อมใช้งานในกิจกรรมทั้งหมดของแอป
แทรกอินสแตนซ์ด้วย @Provides
อินเทอร์เฟซไม่ใช่กรณีเดียวที่คุณไม่สามารถแทรกประเภทลงในตัวสร้าง
นอกจากนี้ คุณยังใช้การฉีดคอนสตรัคเตอร์ไม่ได้หากไม่ได้เป็นเจ้าของคลาสเนื่องจากคลาสนั้นมาจากไลบรารีภายนอก (คลาสอย่าง Retrofit, OkHttpClient
หรือฐานข้อมูล Room) หรือหากต้องสร้างอินสแตนซ์ด้วยรูปแบบตัวสร้าง
ลองดูตัวอย่างก่อนหน้า หากคุณไม่ได้เป็นเจ้าของ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 ให้บริการใช้งานประเภทเดียวกันกับ Dependency ที่แตกต่างกัน คุณต้องให้การเชื่อมโยงกับ Hilt หลายรายการ คุณกำหนดการเชื่อมโยงหลายรายการสำหรับประเภทเดียวกันได้โดยใช้ตัวระบุ
ตัวระบุคือคำอธิบายประกอบที่คุณใช้เพื่อระบุการเชื่อมโยงที่เฉพาะเจาะจงสำหรับประเภทหนึ่งๆ เมื่อประเภทนั้นมีการเชื่อมโยงหลายรายการที่กําหนดไว้
พิจารณาตัวอย่างต่อไปนี้ หากต้องการขัดขวางการเรียก AnalyticsService
คุณอาจใช้ออบเจ็กต์ OkHttpClient
ที่มีอินเตอร์เซปเตอร์ สำหรับบริการอื่นๆ คุณอาจต้องขัดจังหวะการโทรด้วยวิธีอื่น ในกรณีนี้ คุณต้องบอก 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; ... }
แนวทางปฏิบัติแนะนำคือ หากคุณเพิ่มตัวระบุลงในประเภทหนึ่ง ให้เพิ่มตัวระบุในทุกวิธีที่เป็นไปได้เพื่อระบุข้อมูลดังกล่าว การไม่ใส่ตัวระบุให้กับการใช้งานพื้นฐานหรือทั่วไปมีแนวโน้มที่จะเกิดข้อผิดพลาดและอาจส่งผลให้ 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 | Injector สำหรับ |
---|---|
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
เป็น Dependency ให้กับประเภทอื่นหรือผ่าน Field Injection (เช่น ใน ExampleActivity
) ทุกครั้ง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.mod ขอบเขตของการเชื่อมโยงต้องตรงกับขอบเขตของคอมโพเนนต์ที่ติดตั้ง ดังนั้นในตัวอย่างนี้ คุณต้องติดตั้ง 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
ลําดับชั้นของคอมโพเนนต์
การติดตั้งโมดูลลงในคอมโพเนนต์ช่วยให้เข้าถึงการเชื่อมโยงได้ในฐานะที่เป็นการพึ่งพาการเชื่อมโยงอื่นๆ ในคอมโพเนนต์นั้นหรือในคอมโพเนนต์ย่อยที่อยู่ด้านล่างในลําดับชั้นของคอมโพเนนต์
การเชื่อมโยงเริ่มต้นของคอมโพเนนต์
คอมโพเนนต์ 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; } }
แทรกข้อมูล Dependency ในคลาสที่ Hilt ไม่รองรับ
Hilt รองรับคลาส Android ที่พบบ่อยที่สุด อย่างไรก็ตาม คุณอาจต้องทำการฉีดฟิลด์ในคลาสที่ Hilt ไม่รองรับ
ในกรณีดังกล่าว คุณสามารถสร้างจุดเข้าโดยใช้@EntryPoint
คำอธิบายประกอบ จุดแรกเข้าคือขอบเขตระหว่างโค้ดที่ Hilt จัดการกับโค้ดที่ไม่ได้จัดการ ซึ่งเป็นจุดที่โค้ดเข้าสู่กราฟของออบเจ็กต์ที่ Hilt จัดการเป็นครั้งแรก จุดแรกเข้าช่วยให้ Hilt ใช้โค้ดที่ Hilt ไม่สามารถจัดหา Dependency ภายในกราฟ Dependency ได้
เช่น 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 ซึ่งให้วิธีมาตรฐานในการรวม Dagger เข้ากับแอปพลิเคชัน Android
เป้าหมายของ Hilt ที่เกี่ยวข้องกับ Dagger มีดังนี้
- เพื่อลดความซับซ้อนของโครงสร้างพื้นฐานที่เกี่ยวข้องกับ Dagger สําหรับแอป Android
- สร้างชุดคอมโพเนนต์และขอบเขตมาตรฐานเพื่อลดความซับซ้อนของการตั้งค่า ความสามารถในการอ่าน และการแชร์โค้ดระหว่างแอป
- เพื่อมอบวิธีง่ายๆ ในการจัดสรรการเชื่อมโยงต่างๆ ให้กับประเภทบิลด์ต่างๆ เช่น การทดสอบ การแก้ไขข้อบกพร่อง หรือรุ่น
เนื่องจากระบบปฏิบัติการ Android จะสร้างอินสแตนซ์ของคลาสเฟรมเวิร์กของตัวเองจำนวนมาก คุณจึงต้องใช้โค้ดที่ซ้ำกันจำนวนมากเมื่อใช้ Dagger ในแอป Android Hilt จะลดโค้ดเทมเพลตที่เกี่ยวข้องกับการใช้ Dagger ในแอปพลิเคชัน Android Hilt จะสร้างและให้บริการต่อไปนี้โดยอัตโนมัติ
- คอมโพเนนต์สำหรับการผสานรวมคลาสเฟรมเวิร์ก Android กับ Dagger ซึ่งคุณจะต้องสร้างด้วยตนเอง
- คำอธิบายประกอบขอบเขตเพื่อใช้กับคอมโพเนนต์ที่ Hilt สร้างขึ้นโดยอัตโนมัติ
- การเชื่อมโยงที่กําหนดไว้ล่วงหน้าเพื่อแสดงคลาส Android เช่น
Application
หรือActivity
- ตัวคําจํากัดที่กําหนดไว้ล่วงหน้าเพื่อแสดง
@ApplicationContext
และ@ActivityContext
โค้ด Dagger และ Hilt อยู่ร่วมกันได้ในฐานโค้ดเดียวกัน อย่างไรก็ตาม ในกรณีส่วนใหญ่ เราขอแนะนำให้ใช้ Hilt เพื่อจัดการการใช้งาน Dagger ทั้งหมดใน Android หากต้องการย้ายข้อมูลโปรเจ็กต์ที่ใช้ Dagger ไปยัง Hilt โปรดดูคำแนะนำในการย้ายข้อมูลและการย้ายข้อมูลแอป Dagger ไปยัง Hilt ใน Codelab
แหล่งข้อมูลเพิ่มเติม
ดูข้อมูลเพิ่มเติมเกี่ยวกับ Hilt ได้ที่แหล่งข้อมูลเพิ่มเติมต่อไปนี้
ตัวอย่าง
Codelabs
บล็อก
- Dependency Injection ใน Android ด้วย Hilt
- การกําหนดขอบเขตใน Android และ Hilt
- การเพิ่มคอมโพเนนต์ลงในลําดับชั้น Hilt
- การย้ายข้อมูลแอป Google I/O ไปยัง Hilt