Dependency Injection พร้อม Hilt

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

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

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

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

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

Groovy

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

Kotlin

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

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

Groovy

...
plugins {
  id 'com.google.devtools.ksp'
  id 'com.google.dagger.hilt.android'
}

android {
  ...
}

dependencies {
  implementation "com.google.dagger:hilt-android:2.56.2"
  ksp "com.google.dagger:hilt-compiler:2.56.2"
}

Kotlin

plugins {
  id("com.google.devtools.ksp")
  id("com.google.dagger.hilt.android")
}

android {
  ...
}

dependencies {
  implementation("com.google.dagger:hilt-android:2.56.2")
  ksp("com.google.dagger:hilt-android-compiler:2.56.2")
}

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 ที่ขึ้นอยู่กับคลาสดังกล่าวด้วย เช่น หากใส่คำอธิบายประกอบใน Fragment คุณต้องใส่คำอธิบายประกอบในกิจกรรมใดก็ตามที่ใช้ Fragment นั้นด้วย

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

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

Kotlin

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

Java

public class AnalyticsAdapter {

  private final AnalyticsService service;

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

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

โมดูล Hilt

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

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

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

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

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

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

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

  • ประเภทการคืนค่าของฟังก์ชันจะบอก Hilt ว่าฟังก์ชันมีอินเทอร์เฟซใด อินสแตนซ์
  • พารามิเตอร์ฟังก์ชันจะบอก Hilt ว่าจะใช้การติดตั้งใช้งานใด

Kotlin

interface AnalyticsService {
  fun analyticsMethods()
}

// Constructor-injected, because Hilt needs to know how to
// provide instances of AnalyticsServiceImpl, too.
class AnalyticsServiceImpl @Inject constructor(
  ...
) : AnalyticsService { ... }

@Module
@InstallIn(ActivityComponent::class)
abstract class AnalyticsModule {

  @Binds
  abstract fun bindAnalyticsService(
    analyticsServiceImpl: AnalyticsServiceImpl
  ): AnalyticsService
}

Java

public interface AnalyticsService {
  void analyticsMethods();
}

// Constructor-injected, because Hilt needs to know how to
// provide instances of AnalyticsServiceImpl, too.
public class AnalyticsServiceImpl implements AnalyticsService {
  ...
  @Inject
  AnalyticsServiceImpl(...) {
    ...
  }
}

@Module
@InstallIn(ActivityComponent.class)
public abstract class AnalyticsModule {

  @Binds
  public abstract AnalyticsService bindAnalyticsService(
    AnalyticsServiceImpl analyticsServiceImpl
  );
}

โมดูล Hilt AnalyticsModule มีคำอธิบายประกอบด้วย @InstallIn(ActivityComponent.class) เนื่องจากคุณต้องการให้ Hilt แทรกการอ้างอิง นั้นลงใน 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 หลายแบบ คุณต้องระบุ Binding หลายรายการให้ Hilt คุณสามารถ กำหนดการเชื่อมโยงหลายรายการสำหรับประเภทเดียวกันได้ด้วยตัวระบุ

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

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

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

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

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

คลาส Android คอมโพเนนต์ที่สร้างขึ้น ขอบเขต
Application SingletonComponent @Singleton
Activity ActivityRetainedComponent @ActivityRetainedScoped
ViewModel ViewModelComponent @ViewModelScoped
Activity ActivityComponent @ActivityScoped
Fragment FragmentComponent @FragmentScoped
View ViewComponent @ViewScoped
View ใส่คำอธิบายประกอบด้วย @WithFragmentBindings ViewWithFragmentComponent @ViewScoped
Service ServiceComponent @ServiceScoped

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

Kotlin

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

Java

@ActivityScoped
public class AnalyticsAdapter {

  private final AnalyticsService service;

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

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

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

Kotlin

// If AnalyticsService is an interface.
@Module
@InstallIn(SingletonComponent::class)
abstract class AnalyticsModule {

  @Singleton
  @Binds
  abstract fun bindAnalyticsService(
    analyticsServiceImpl: AnalyticsServiceImpl
  ): AnalyticsService
}

// If you don't own AnalyticsService.
@Module
@InstallIn(SingletonComponent::class)
object AnalyticsModule {

  @Singleton
  @Provides
  fun provideAnalyticsService(): AnalyticsService {
      return Retrofit.Builder()
               .baseUrl("https://example.com")
               .build()
               .create(AnalyticsService::class.java)
  }
}

Java

// If AnalyticsService is an interface.
@Module
@InstallIn(SingletonComponent.class)
public abstract class AnalyticsModule {

  @Singleton
  @Binds
  public abstract AnalyticsService bindAnalyticsService(
    AnalyticsServiceImpl analyticsServiceImpl
  );
}

// If you don't own AnalyticsService.
@Module
@InstallIn(SingletonComponent.class)
public class AnalyticsModule {

  @Singleton
  @Provides
  public static AnalyticsService provideAnalyticsService() {
      return new Retrofit.Builder()
               .baseUrl("https://example.com")
               .build()
               .create(AnalyticsService.class);
  }
}

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

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

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

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

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

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

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

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

Kotlin

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

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

Java

public class AnalyticsServiceImpl implements AnalyticsService {

  private final Context context;

  @Inject
  AnalyticsAdapter(@ApplicationContext Context context) {
    this.context = context;
  }
}

// The Application binding is available without qualifiers.
public class AnalyticsServiceImpl implements AnalyticsService {

  private final Application application;

  @Inject
  AnalyticsAdapter(Application application) {
    this.application = application;
  }
}

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

Kotlin

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

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

Java

public class AnalyticsAdapter {

  private final Context context;

  @Inject
  AnalyticsAdapter(@ActivityContext Context context) {
    this.context = context;
  }
}

// The Activity binding is available without qualifiers.
public class AnalyticsAdapter {

  private final FragmentActivity activity;

  @Inject
  AnalyticsAdapter(FragmentActivity activity) {
    this.activity = activity;
  }
}

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

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

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

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

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

ตัวอย่าง

Codelabs

บล็อก