Dependency Injection พร้อม Hilt

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 หัวฉีดสำหรับ
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

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

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

ViewWithFragmentComponent อยู่ภายใต้ FragmentComponent FragmentComponent และ ViewComponent อยู่ภายใต้ ActivityComponent ActivityComponent อยู่ภายใต้
    ActivityRetainedComponent ViewModelComponent อยู่ภายใต้ ActivityRetainedComponent ActivityRetainedComponent และ ServiceComponent อยู่ภายใต้ SingletonComponent
รูปที่ 1 ลําดับชั้นของคอมโพเนนต์ที่ 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

บล็อก