Dependency Injection พร้อม Hilt

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

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

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

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

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

ดึงดูดKotlin
plugins {
  ...
  id 'com.google.dagger.hilt.android' version '2.51.1' apply false
}
plugins {
  ...
  id("com.google.dagger.hilt.android") version "2.51.1" apply false
}

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

ดึงดูด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-compiler:2.51.1"
}

// Allow references to generated code
kapt {
  correctErrorTypes true
}
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

GroovyKotlin
android {
  ...
  compileOptions {
    sourceCompatibility JavaVersion.VERSION_1_8
    targetCompatibility JavaVersion.VERSION_1_8
  }
}
android {
  ...
  compileOptions {
    sourceCompatibility = JavaVersion.VERSION_1_8
    targetCompatibility = JavaVersion.VERSION_1_8
  }
}

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

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

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

KotlinJava
@HiltAndroidApp
class ExampleApplication : Application() { ... }
@HiltAndroidApp
public class ExampleApplication extends Application { ... }

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

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

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

KotlinJava
@AndroidEntryPoint
class ExampleActivity : AppCompatActivity() { ... }
@AndroidEntryPoint
public class ExampleActivity extends AppCompatActivity { ... }

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

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

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

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

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

KotlinJava
@AndroidEntryPoint
class ExampleActivity : AppCompatActivity() {

  @Inject lateinit var analytics: AnalyticsAdapter
  ...
}
@AndroidEntryPoint
public class ExampleActivity extends AppCompatActivity {

  @Inject
  AnalyticsAdapter analytics;
  ...
}

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

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

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

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

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

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

  private final AnalyticsService service;

  @Inject
  AnalyticsAdapter(AnalyticsService service) {
    this.service = service;
  }
  ...
}

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

โมดูล Hilt

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

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

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

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

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

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

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

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

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

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

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

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

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

ก่อนอื่น ให้กำหนดคำขยายที่คุณจะใช้ในการใส่คำอธิบายประกอบ @Binds หรือ @Provides วิธี:

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

@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class OtherInterceptorOkHttpClient
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
private @interface AuthInterceptorOkHttpClient {}

@Qualifier
@Retention(RetentionPolicy.RUNTIME)
private @interface OtherInterceptorOkHttpClient {}

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

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

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

KotlinJava
// 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
}
// 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 แทรกพึ่งพาที่ไม่ถูกต้อง

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

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

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

KotlinJava
class AnalyticsAdapter @Inject constructor(
    @ActivityContext private val context: Context,
    private val service: AnalyticsService
) { ... }
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 เป็นทรัพยากร Dependency ประเภทอื่นหรือผ่านการแทรกช่อง (เช่น 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 อินสแตนซ์เดียวกัน ตลอดระยะเวลาของกิจกรรมที่เกี่ยวข้อง:

KotlinJava
@ActivityScoped
class AnalyticsAdapter @Inject constructor(
  private val service: AnalyticsService
) { ... }
@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

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

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

การติดตั้งโมดูลลงในคอมโพเนนต์อนุญาตให้เข้าถึงการเชื่อมโยงของโมดูลดังกล่าวเป็น การพึ่งพาการเชื่อมโยงอื่นๆ ในคอมโพเนนต์นั้นหรือในคอมโพเนนต์ย่อยใดๆ ด้านล่าง ในลำดับชั้นขององค์ประกอบ

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

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

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

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

// The Application binding is available without qualifiers.
class AnalyticsServiceImpl @Inject constructor(
  application: Application
) : AnalyticsService { ... }
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 ด้วย เช่น

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

// The Activity binding is available without qualifiers.
class AnalyticsAdapter @Inject constructor(
  activity: FragmentActivity
) { ... }
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 เพื่อระบุคอมโพเนนต์ที่จะ ติดตั้งจุดแรกเข้าดังนี้

KotlinJava
class ExampleContentProvider : ContentProvider() {

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

  ...
}
public class ExampleContentProvider extends ContentProvider {

  @EntryPoint
  @InstallIn(SingletonComponent.class)
  interface ExampleContentProviderEntryPoint {
    public AnalyticsService analyticsService();
  }
  ...
}

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

KotlinJava
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()
    ...
  }
}
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 ไลบรารี Dependency Injection ที่มีวิธีมาตรฐานในการรวม Dagger ในแอปพลิเคชัน Android

สำหรับ Dagger เป้าหมายของ Hilt มีดังนี้:

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

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

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

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

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

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

ตัวอย่าง

ไม่พบผลลัพธ์

Codelabs

บล็อก