Modernized Android architecture - Hilt
Tags: android, kotlin, hilt
Published: 2021-01-02 14:52:16

_Cover Photo by Roman Koval from_ [Pexels](https://www.pexels.com/photo/glowing-lightsaber-placed-on-white-table-3717321/)

แรกเริ่มเดิมทีว่าจะลองเขียน Blog เกี่ยวกับ JetPack Compose อย่างเดียว แต่ทำไปทำมาตอนลองทำ Sample Project แล้วมันดันมีจุดที่น่าสนใจหลายอย่างเลยคิดว่าถ้าเราเขียนเรื่อง JetPack Compose อย่างเดียวคงไม่จบ เลยอยากจะเขียนบทความนี้แยกเป็นหลายๆส่วน โดย Follow ตามตัวอย่าง code แบ่งตามเรื่องที่น่าสนใจดังนี้
- Hilt
- JetPack Compose (UI Component) & Unidirectional Data Flow
- จัดการ ใน JetPack Compose ด้วย LiveData และ State
- จัดการ ใน JetPack Compose ด้วย State/Share Flow

## Hilt
[Hilt](https://dagger.dev/hilt/) คือ Library ที่ Google เพิ่งจะออกมาสดๆร้อนๆ โดยถูกสร้าง on-top ด้วย Dagger2 อีกที ใช้สำหรับการทำ [Dependency Injection](https://en.wikipedia.org/wiki/Dependency_injection) ใน Android ให้ง่ายขึ้น เรียกว่าเป็น Simplified version ของ Dagger Android ก็ว่าได้ 

### ทำไมต้องทำ Dependency Injection
เพื่อให้เข้าใจภาพแบบง่ายขึ้น ยกตัวอย่าง สมมติเรามี class ที่ต้องทำหน้าที่ส่งข้อความโดยแรกเริ่มเดิมที่ ข้อความที่ส่งไปมันก็เป็น email ธรรมดา โดย function จะ return True หรือ False มาหากส่งข้อความสำเร็จหรือไม่สำเร็จ
```kotlin
class EmailSender {

fun sendEmail(message: String): Boolean {
 // TO-DO email sending implementation is processed here
   }
}
```
โดยเจ้า Class นี้ก็จะถูกเรียกใช้งานในอีก Layer นึง
```kotlin
class MessageProcessor {

private val emailSender = EmailSender()

fun sendProcessedMessage() {
 val message = // doing many things to get message
 emailSender.sendEmail(message)
}
}
```
```kotlin
class Main {

fun main() {
val processor = MessageProcessor()
processor.sendProcessedMessage()
}
}
```
ถ้าดูจากการใช้งานแล้ว ในเคสนี้ก็ไม่น่ามีอะไร เจ้า `MessageProcessor` ก็ไป process ข้อความมาเพื่อจะส่ง message จากนั้นก็ส่งผ่าน `EmailSender` ก็จบ ดูแล้วก็ไม่น่าจะมีปัญหาตรงไหน :thinking:

:warning: ประเด็นอยู่ตรงที่ว่า ถ้าเราสังเกตุดีๆ เราจะเห็นว่า `MessageProcessor` นั้นมัน `depends` กับตัว `EmailSender` อยู่ ถ้าเราสร้าง class `MessageProcessor` มาเมื่อไหร่ ตัว `EmailSender` ก็จะถูกสร้างมาด้วยอัตโนมัติ ให้เราลองนึกถึงการเขียน Test เจ้าตัว `MessageProcessor` คงเป็นเรื่องยากพอสมควร
สมมติว่าเราสามารถเขียน Test มันได้ แล้ววันนึง Requirement มันเปลี่ยนแทนที่จะต้องส่ง email กลายเป็นว่าต้องส่ง sms แทนล่ะ เท่ากับว่า Test ที่เราเขียนไปอาจจะต้องแก้พอสมควรเลย แบบนี้ไม่ดีแน่ สิ่งที่เราควรทำคือ ลด dependency ระหว่าง class แต่ละ class ลงซะ เลยเป็นที่มาของการทำ Dependency Injection 
ในเคสนี้จะยกตัวอย่างการทำ _Constructor Injection_ เนื่องจากว่าเป็นวิธีที่ง่ายต่อความเข้าใจรวมถึงง่ายต่อการเทสกว่า มากๆ

สร้าง interface MessageSender
```kotlin
interface MessageSender {
 fun send(message: String): Boolean
}
```
ให้ class `EmailSender` extends `MessageSender`
```kotlin
class EmailSender: MessageSender {
 override fun send(message: String): Boolean {
// TO-DO email sending implementation is processed here
 }
}
```
ให้ class `SmsSender` extends `MessageSender`
```kotlin
class SmsSender: MessageSender {
 override fun send(message: String): Boolean {
// TO-DO sms sending implementation is processed here
 }
}
```
inject MessageSender ไปที่ constructor ของ MessageProcessor
```kotlin
class MessageProcessor internal constructor(
private val msgSender: MessageSender
) {

fun sendProcessedMessage() {
 val message = // doing many things to get message
 msgSender.send(message)
}
}
```
```kotlin
class Main {

fun main() {
val processor = MessageProcessor(SmsSender())
processor.sendProcessedMessage()
 }
}
```
Unit Test ของ `MessageProcessor`
```kotlin
class MessageProcessorTest {

private val msgSender: MessageSender = mock()
private val processor = MessageProcessor(msgSender)

@Test
fun `test sendProcessedMessage(), msgSender send() is called`() {
  processor.sendProcessedMessage()
  Mockito.verify(msgSender.send(anyString()))
}
}
```
จริงๆ หลักการของ Dependency Injection มีง่ายๆแค่นี้ Dagger2, DaggerAndroid และ Hilt แค่ช่วยให้เราสามารถทำงานได้ง่ายขึ้นบน Android เท่านั้นเอง

### Setup Hilt
จริงๆ วิธีการ Setup นั้นไม่ยากเลย โดย Google ได้อธิบายเป็น Step เอาไว้ให้เราแล้ว สามารถเข้าไปทำตามได้ที่[นี่](https://developer.android.com/training/dependency-injection/hilt-android) ทั้งนี้ทั้งนั้นเผื่อคนที่ไม่อยาก Switch ไปอีกหน้า ผมจะเขียนวิธีการตาม Google เอาไว้ในนี้เลยละกัน

- ไปที่ file `build.gradle` ที่ Root ของ Project เราแล้วใส่
```gradle
buildscript {
    ...
    dependencies {
        ...
        classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version"
    }
}
```
- ไปที่ file `build.gradle` ของ module ที่ต้องการใช้ Hilt ในเคสนี้คือ `app/build.gradle`
```gradle
...
apply plugin: 'kotlin-kapt'
apply plugin: 'dagger.hilt.android.plugin'

android {
    ...
}

dependencies {
    implementation "com.google.dagger:hilt-android:$hilt_version"
    kapt "com.google.dagger:hilt-android-compiler:$hilt_version"
}
```
Hilt ใช้ feature ของ Java8 จึงต้องประกาศด้วย
```gradle
android {
  ...
  compileOptions {
    sourceCompatibility JavaVersion.VERSION_1_8
    targetCompatibility JavaVersion.VERSION_1_8
  }
}
```
Sync Project ให้เรียบร้อย หลังจากนั้นทำการสร้าง Application class ของเราขึ้นมาแล้วใส่ `@HiltAndroidApp` ให้เรียบร้อย
```kotlin
import android.app.Application
import dagger.hilt.android.HiltAndroidApp

@HiltAndroidApp
class ComposePlaygroundApplication: Application()
```
ใน Activity ของเราใส่ `@AndroidEntryPoint`
```kotlin
@AndroidEntryPoint
class MainActivity : AppCompatActivity()
```
เท่านี้เราก็พร้อมใช้งาน Hilt แล้ว 
##### เทียบกับ Dagger
ใน Hilt เราเพียงใส่ `@HiltAndroidApp` ที่ Application Class ในขณะที่
Dagger2 เราอาจจะต้องสร้าง `Component` class ที่มี `fun inject(activity: MainActivity)` ข้างใน หรือถ้าใครใช้ Dagger-Android วิธีการอาจจะซับซ้อนกว่านี้พอควร ในขณะที่ Hilt เพียงแค่เราประกาศ annotation ก็เพียงพอแล้ว

### Field Injection 
ใน Hilt นั้น การ inject นั้นง่ายมากๆ ยกตัวอย่างให้เห็นภาพ เรามี Activity class แล้วต้องการจะ inject class นึงเข้าไปใน Activity สมมติง่ายๆว่าเป็น `EmailSender` class ละกัน
Activity ของเรา
```kotlin
@AndroidEntryPoint
class MainActivity : AppCompatActivity() { 
}
```
class สำหรับส่ง email
```kotlin
class EmailSender {
  fun send(msg: String) {
   println("$msg is sent")
  }
}
```
ถ้าเราต้องการ inject `EmailSender` เข้าไปใน Activity เราสามารถประกาศ `@Inject` ที่ Constructor ได้เลยตามนี้
```kotlin
class EmailSender 
+ @Inject constructor() {
  fun send(msg: String) {
   println("$msg is sent")
  }
}
```
ภายใน Activity เราสามารถประกาศ
```kotlin
@AndroidEntryPoint
class MainActivity : AppCompatActivity() { 
+ @Inject
+ laterinit var emailSender: EmailSender
}
```
เพียงเท่านี้ `EmailSender` ก็พร้อมใช้งานใน `MainActivity` ของเรา 

### Constructor Injection
จากตัวอย่างข้างบน สมมติเราต้องการจะ format message ก่อนจะส่งโดยเรียกใช้class MessageFormatter โดยเราต้องการ Inject  `MessageFormatter` เข้าไปใน `EmailSender` ผ่านทาง Constructor เราก็สามารถทำได้ยกตัวอย่าง

class สำหรับ format ข้อความ
```kotlin
class MessageFormatter {
 fun formatMessages(msg: String): String {
  return "formatted $msg"
 }
}
```
เพิ่ม `@Inject constructor()` ไปที่ `MessageFormatter`
```kotlin
class MessageFormatter 
+ @Inject constructor() {
 fun format(msg: String): String {
  return "formatted $msg"
 }
}
```
```kotlin
class EmailSender 
@Inject constructor(
+ private val formatter: MessageFormatter
) {
  fun send(msg: String) {
   println("${formatter.format(msg)}is sent")
  }
}
```
#### ปัญหา Injection กับ Interface
จากตัวอย่างข้างบนเราทำการ inject class ตรงๆ เข้าไปใน constructor ซึ่งสามารถทำได้ แต่ในหลายๆครั้ง เราต้องการให้ code ของเรามีความ abstract มากขึ้น เพื่อง่ายต่อการ Test วิธีการส่วนมากที่เราทำกันนั้นคือการสร้าง interface มาครอบ class ของเราอีกที ซึ่งใน Hilt ไม่สามารถทำได้ตรงๆตามวิธีการข้างบน 
ยกตัวอย่าง:
เรามี Class ที่ชื่อ EmailSender สำหรับส่ง email อยู่แล้วโดยเราต้องการที่จะ Format ข้อความเราก่อนจะส่ง โดยในที่นี้เราจะสร้าง Interface มาเพื่อทำการ format ข้อความชื่อ `MessageFormatter` 

สร้าง Interface ชื่อ MessageFormatter
```kotlin
interface MessageFormatter {
fun format(msg: String): String
}
```
สร้าง class ชื่อ MessageFormatterV1Impl ที่สืบทอดมาจาก MessageFormatter
```kotlin
class MessageFormatterV1Impl 
@Inject constructor(): MessageFormatter {
 override fun format(msg: String): String {
  return "formatted $msg"
 }
}
```
สร้าง class ชื่อ MessageFormatterV2Impl ที่สืบทอดมาจาก MessageFormatter
```kotlin
class MessageFormatterV2Impl 
@Inject constructor(): MessageFormatter {
 override fun format(msg: String): String {
  return "$msg is formatted"
 }
}
```
เรามี 2 classes ที่ extend มาจาก `MessageFormatter` ชื่อ `MessageFormatterV1Impl` และ `MessageFormatterV2Impl` โดยทั้งสอง class ทำหน้าที่ format ข้อความเหมือนกัน แต่วิธีการ format ข้อความต่างกัน

inject `MessageFormatter` ที่เป็น Inteface เข้าไปใน constructor ของ `EmailSender`
```kotlin
class EmailSender 
@Inject constructor(
+ private val formatter: MessageFormatter
) {
  fun send(msg: String) {
   println("${formatter.format(msg)}is sent")
  }
}
```
Error หลังจาก Build:
```
error: [Dagger/MissingBinding] MessageFormatter cannot be provided without an @Provides-annotated method.
  public abstract static class ApplicationC implements ComposePlaygroundApplication_GeneratedInjector,
                         ^
```
จริงๆ Error นั้นเรียกว่า make sense มากๆ ตรงที่ code ของเราจะรู้ได้ยังไงว่าจะเอา instant ไหน inject เข้าไปใน Constructor กันล่ะในเมื่อมี 2 classes ที่ implement interface เดียวกัน ซึ่งวิธีการแก้ปัญหานั้นอาจจะต้องใช้ตัวช่วยสักหน่อย นั่นก็คือการสร้าง `@Module` 

**หมายเหตุ** :bulb: *ข้อดีของการทำ Constructor injection คือเราไม่จำเป็นต้องทำอะไรเพิ่มเติมเลยหากเราต้องการจะเขียน UnitTest ไม่ใช่แค่สำหรับ Hilt แต่ Dagger ด้วยก็เหมือนกัน โดยเฉพาะอย่างยิ่งการทำ Constructor injection ด้วย Interface นั้นทำให้การ Mock เป็นเรื่องง่ายมากๆครับ*

### Component
ก่อนจะเริ่มสร้าง Module นั้น สิ่งที่เราต้องทำความเข้าใจก่อนคือ [Component](https://developer.android.com/training/dependency-injection/hilt-android#component-lifetimes) และ [Component Scope](https://developer.android.com/training/dependency-injection/hilt-android#component-scopes) ตัว Hilt เองนั้นจะมีการ pre-create Hilt Components มาให้เราใช้งานแล้วโดยที่เราไม่จำเป็นต้องไปสร้าง Component เองเหมือนที่เราทำใน Dagger โดยสามารถแบ่งตามนี้

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/9if0kr625x3avis7qpsk.png)
ถึงตรงนี้หลายคนอาจจะงงว่า Component นั้นคืออะไร ถ้าพูดให้เข้าใจง่ายคือ เปรียบเสมือน Layer ที่เราอยากจะให้ instance ที่เราสร้างเข้าไปอยู่ ยกตัวอย่าง เราอยากจะให้ `EmailSender` class ของเราใช้ได้ Activity เราก็เพียงแค่ต้องมั่นใจว่า มันถูก "ใส่" เข้าไปใน (`@InstallIn`) `ActivityComponent`

แล้วเมื่อไหร่ที่ instance เราถูกสร้างและถูกทำลายล่ะ?
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/7o26pgockf3b16t6yezb.jpg)
instance ที่ถูก bound เข้าไปจะถูกสร้างและทำลายตามแต่ละ Component ที่ตัวมันเองโดน installed ลงไป

### Scope
โดยปกติแล้ว instance ที่ถูกสร้างขึ้นมาจะไม่มีการกำหนด scope ของการใช้งาน (unscoped) นั่นหมายความว่า ทุกๆครั้งที่มีการเรียกใช้ class นั้น instance นั้นจะถูกสร้างขึ้นมาใหม่ ยกตัวอย่าง:
เรามี class EmailSender
```kotlin
class EmailSender @Inject constructor() {
    fun send(msg: String){
        println(msg)
    }
}
```
ทำการ inject EmailSender ในอีก class นึงผ่านทาง Constructor ชื่อว่า MessageHandler แล้วลอง Print ค่าออกมาดู

```kotlin
class MessageHandler @Inject constructor(
    private val sender: EmailSender
) {
    init {
        println("emailSender from ${this::class.java} $sender" )
    }
}
```
ทำการ inject ทั้ง EmailSender และ MessageHandler ใน Activity 
```kotlin
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {

    @Inject
    lateinit var emailSender: EmailSender

    @Inject
    lateinit var messageHandler: MessageHandler

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        println("emailSender from ${this::class.java} $emailSender" )
    }
}
```
- เรา Inject `MessageHandler` เพื่อให้ Hilt สร้าง instance ของ MessageHandler เพื่อดูค่า `EmailSender` ที่เรา print ใน init ของ MessageHandler
- เรา Inject `EmailSender` เพื่อให้ Hilt สร้าง instance ของ `EmailSender` เพื่อดูค่า `EmailSender` ที่เรา print ใน onCreate ของ Activity

ค่าที่ได้:
```
2021-01-04 16:13:03.060 7374-7374/? I/System.out: emailSender from class MessageHandler EmailSender@cacc883
2021-01-04 16:13:03.102 7374-7374/? I/System.out: emailSender from class MainActivity EmailSender@72ec68a
```
จะเห็นว่า Instance นั้นเป็นคนละตัวกัน

ลองกำหนด Scope ลงไปใน EmailSender ในเคสนี้จะลองกำหนด `@ActivityScoped`
```kotlin
+ @ActivityScoped
class EmailSender @Inject constructor() {
    fun send(msg: String){
        println(msg)
    }
}
```
ค่าที่ได้:
```
2021-01-04 16:16:06.611 7714-7714/? I/System.out: emailSender from MessageHandler EmailSender@b6f3903
2021-01-04 16:16:06.634 7714-7714/? I/System.out: emailSender from class MainActivity EmailSender@b6f3903
```
Scope เปรียบเสมือนขอบเขตของการใช้งานว่าเราอยากจะให้ Instance นั้นถูกใช้ได้ในขอบเขตไหนลงไปเป็นลำดับขั้น โดย Hilt เอง ก็ได้ทำการ Predefined เอาไว้แล้วตามนี้
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/xs5oa0zwik8dlb8uo04a.png)

ยกตัวอย่าง:
**ตัวอย่างที่ 1** ต้องการให้ `class EmailSender` ใช้งานได้ใน `Activity` และให้ `class EmailSender` และมีขอบเขตการใช้งานภายใน Activity ลงไป
เทียบตาราง:
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/d8kybhhtdhxwcytdt7o3.png)
- ต้องให้ `class EmailSender` ถูก Inject ใน `Activity`
- ต้องให้ `class EmailSender` มี Scope เป็น `ActivityScope`

ตัวอย่าง Code:
_ประกาศ @ActivityScoped_
```kotlin
@ActivityScoped
class EmailSender @Inject constructor() {
    fun send(msg: String) {
        println("msg is sent")
    }
}
```
_ใช้งาน EmailSender ใน Activity_
```kotlin
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {

    @Inject
    lateinit var emailSender: EmailSender
}
```

**ตัวอย่างที่ 2** ต้องการให้ `class EmailSender` ใช้งานได้ใน `Fragment` และให้ `class EmailSender` และมีขอบเขตการใช้งานภายใน `Fragment` ลงไป
เทียบตาราง:
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/048zacod6e1j2jt4ddmu.png)
- ต้องให้ `class EmailSender` ถูก Inject ใน `Fragment`
- ต้องให้ `class EmailSender` มี Scope เป็น `FragmentScope`

ตัวอย่าง Code:
_ประกาศ @FragmentScoped_
```kotlin
@FragmentScoped
class EmailSender @Inject constructor() {
    fun send(msg: String) {
        println("msg is sent")
    }
}
```
_ใช้งาน EmailSender ใน Fragment_
```kotlin
@AndroidEntryPoint
class MainFragment : Fragment() {

    @Inject
    lateinit var emailSender: EmailSender
}
```

**ตัวอย่างที่ 3** ต้องการให้ `class EmailSender` ใช้งานได้ใน `Fragment` **แต่**ยังให้ `class EmailSender` และมีขอบเขตการใช้งานภายใน Activity ลงไป
เทียบตาราง:
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/48zd70dsrbq5x3z1vraz.png)
- ต้องให้ `class EmailSender` ถูก Inject ใน `Fragment`
- ต้องให้ `class EmailSender` มี Scope เป็น `ActivityScope`

ตัวอย่าง Code:
_ประกาศ @ActivityScoped_
```kotlin
@ActivityScoped
class EmailSender @Inject constructor() {
    fun send(msg: String) {
        println("msg is sent")
    }
}
```
_ใช้งาน EmailSender ใน Fragment_
```kotlin
@AndroidEntryPoint
class MyFragment : Fragment() {

    @Inject
    lateinit var emailSender: EmailSender
}
```
**เนื่องจากว่า ขอบเขตการใช้งานของ EmailSender อยู่ใน Activity แต่การใช้งานเรานำไปใช้ใน Fragment ซึ่งอยู่ภายใต้ Activity อีกที ในกรณีนี้นั้นสามารถทำได้** 
 
**ข้อควรระวัง** Scope กับ Component นั้นมีความสัมพันธ์กันเป็นลำดับชั้น ถ้าหากเราประกาศ Scope กับ Component ไม่ถูกต้องเราอาจจะเจอ Error ได้ยกตัวอย่าง:
- ให้ `class EmailSender` ถูกใช้งานใน Activity
- แต่ให้ `class EmailSender` มี Scope เป็น `FragmentScope` เพราะต้องการให้ `Class EmailSender` มีขอบเขตการใช้งานแค่ภายใน `Fragment` 

Compile Error ที่เกิดขึ้น:
```
error: [Dagger/IncompatiblyScopedBindings] ComposePlaygroundApplication_HiltComponents.ActivityC scoped with @dagger.hilt.android.scopes.ActivityScoped may not reference bindings with different scopes:
  public abstract static class ApplicationC implements ...,
``` 
#### แก้ไขปัญหา Constructor Injection กับ Interface
จากที่บอกไปด้านบนคือ วิธีการแก้ไขนั้นทำได้ไม่ยากโดยการใช้ `Module` ซึ่งวิธีการใช้งาน Module ของ Hilt นั้นมี 2 วิธีหลักๆ คือการ `@Binds` และ `@Provides`
### Binds module
วิธีการ bind instance ด้วยการใช้ `@Binds` ค่อนข้างเรียบง่ายและเหมาะกับการแก้ปัญหา constructor injection ด้วย interface ในกรณีที่เรามี class ที่เราต้องการ bind instance เข้าไป ยกตัวอย่าง:

เรามี MessageFormatter ที่เป็น interface และมี class MessageFormatterImpl ที่เป็น class
```kotlin
interface MessageFormatter {
fun format(msg: String): String
}

class MessageFormatterImpl: MessageFormatter {
fun format(msg: String): String {
 return "Formatted $msg"
}
}
```
ต้องการ inject เข้าไปที่ constructor ของ EmailSender class
```kotlin
class EmailSender
@Inject constructor(
private val formatter: MessageFormatter
)
```
เราต้องการ Inject `EmailSender` เข้าไปที่ Activity โดยที่ Compile ผ่าน
```kotlin
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {

    @Inject
    lateinit var emailSender: EmailSender
}
```
เริ่มทำการสร้าง Module ในที่นี้จะให้ชื่อว่า `MessageSendingModule`
```kotlin
@Module
abstract class MessageSendingModule {

}
```
ทำการประกาศว่าจะทำ Module ไปใช้ที่ไหน ในที่นี้ เราต้องการใช้ `EmailSender` ที่ Activity เราจึงต้องประกาศ `@InstallIn(ActivityComponent::class)`
```kotlin
+ @InstallIn(ActivityComponent::class)
@Module
abstract class MessageSendingModule {

}
```
ทำการ Bind Instance ของ `MessageFormatter`
```kotlin
@InstallIn(ActivityComponent::class)
@Module
abstract class MessageSendingModule {

+ @Binds
+ fun bindMessageFormatter(impl: MessageFormatterImpl): MessageFormatter
}
```
**จุดสังเกตุ** :warning::warning: เราใส่ `MessageFormatterImpl` ที่เป็น `class` ลงไปเป็น parameter ของ `bindMessageFormatter` และให้ function return `MessageFormatter` ที่เป็น `Interface`
ทำการประกาศ Scope ของ `MessageFormatter` เคสนี้ เราต้องการให้มันมีขอบเขตการใช้งานภายใน Activity ลงไป ดังนั้นต้องใช้ `@ActivityScope`
```kotlin
@InstallIn(ActivityComponent::class)
@Module
abstract class MessageSendingModule {

@Binds
+ @ActivityScope
fun bindMessageFormatter(impl: MessageFormatterImpl): MessageFormatter
}
```
เพียงเท่านี้เราก็สามารถ compile ได้ผ่านฉลุย :rocket::rocket::rocket:
### Provide module
Provide module มักใช้ในกรณีที่เราต้องการสร้าง Instance ของ Class ด้วยเราเอง ยกตัวอย่าง หากเราต้องการ Inject `Gson` instance การใช้ @Binds ตามตัวอย่างข้างบนไม่สามารถทำได้ วิธีการใช้ Provide module จึงเหมาะสมมากกว่า
ตัวอย่าง: 
- ต้องการให้ `Gson` เข้าถูกใช้งานที่ใดก็ได้ใน **Application**
- ต้องการให้ Gson  มีขอบเขตการใช้งานภายใน **Application**

สร้าง module ในที่นี้จะให้ชื่อว่า `GsonModule`
```kotlin
@Module
class GsonModule
```
**จุดสังเกตุ** :warning: class GsonModule ไม่ใช่ abstract class เหมือนในกรณี `@Binds`

ประกาศ `@InstallIn(ApplicationComponent::class)` เนื่องจากว่าเราต้องการให้ Gson ถูกใช้งานที่ Application level ในกรณีนี้ instance ของ Gson จะถูกนำไปใช้งานได้ทุกที่ของ App (หาก inject Gson ด้วย Hilt) 
```kotlin
+ @InstallIn(ApplicationComponent::class)
@Module
class GsonModule
``` 
สร้าง Function ที่ทำหน้าที่ Provide Gson 
```kotlin
@InstallIn(ApplicationComponent::class)
@Module
class GsonModule {
+ fun provideGson(): Gson = GsonBuilder().create()
}
```
ประกาศ `@Singleton` เพื่อให้ Gson มี มีขอบเขตการใช้งานภายที่ **Application** ลงไป 

จากนั้นประกาศ @Provides
```kotlin
@InstallIn(ApplicationComponent::class)
@Module
class GsonModule {
+ @Singleton
+ @Provides
fun provideGson(): Gson = GsonBuilder().create()
}
```
เพียงเท่านี้เราก็สามารถ Inject Gson ไปที่ไหนก็ได้ใน App เพราะ Gson จะมีขอบเขตการใช้งานที่ Application layer ลงไป

**หมายเหตุ** :bulb: วิธีการสร้าง Module จำเป็นต้องให้ component ที่เราจะระบุใน `@InstallIn` ตรงกันกับ `Scope` รวมถึงการนำไปใช้งาน (inject) ด้วย ตามตาราง:
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/eagoa82u2shx563iuqp1.jpg)
เช่น:
ต้องการ EmailSender class ใช้งานใน Fragment ลงไป (Fragment, View, Service) เราสามารถระบุ
- `@InstallIn` `ApplicationComonent` หรือ `AcivityComponent` หรือ `FragmentComponent` ได้
- ระบุ Scope เป็น `@Singleton` หรือ `@ActivityScope` หรือ `@FragmentScope` โดยต้องตรงกับ Component ที่ InstallIn
- สามารถ inject EmailSender ได้ใน Fragment, View และ Service

### Monolithic components
อีกเรื่องที่สำคัญสำหรับ Hilt คือ การที่ Hilt นั้นมีโครงสร้าง แบบ [Monolithic components](https://dagger.dev/hilt/monolithic) นั่นหมายความว่า ของที่อยู่ใน Components จะ**ถูกใช้ได้ในทุกที่ใน Layer เดียวกัน** 
ยกตัวอย่าง:
- Application เรามี 3 Activities ชื่อ `MainActivity`, `SecondActivity`, `ThirdActivity` โดยให้ทั้งสาม Activities สามารถใช้งาน Hilt ได้โดยการระบุ `@AndroidEntryPoint` บน Activities ทั้ง 3 ตัว
```kotlin
@AndroidEntryPoint
class ThirdActivity  : AppCompatActivity()

@AndroidEntryPoint
class SecondActivity  : AppCompatActivity()

@AndroidEntryPoint
class MainActivity : AppCompatActivity() 
```
-  ให้ `EmailSender` มี Scope เป็น `ActivityScoped`
```kotlin
@ActivityScoped
class EmailSender @Inject constructor() {
    fun send(msg: String){
        println(msg)
    }
}
```

inject `EmailSender` ในทั้ง 3 Activities
```kotlin
@AndroidEntryPoint
class ThirdActivity  : AppCompatActivity() {
    @Inject
    lateinit var emailSender: EmailSender
}

@AndroidEntryPoint
class SecondActivity  : AppCompatActivity() {
    @Inject
    lateinit var emailSender: EmailSender
}

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {

    @Inject
    lateinit var emailSender: EmailSender
}
```
ทั้งนี้ทั้งนั้น ถึงแม้ว่าเราจะ inject มันได้ ทั้ง 3 ที่ แต่ instance ก็ยังคงเป็นคนละตัวกัน 

ทดสอบด้วยการ Print ออกมาใน onCreate ของแต่ละ Activity:
```kotlin
println("emailSender from ${this::class.java} $emailSender" )
```
ผลที่ได้
```
2021-01-04 14:51:01.868 5198-5198/? I/System.out: emailSender from class MainActivity EmailSender@245bf98
2021-01-04 14:51:02.346 5198-5198/? I/System.out: emailSender from class SecondActivity EmailSender@50720c3
2021-01-04 14:51:02.675 5198-5198/? I/System.out: emailSender from class ThirdActivity EmailSender@2026848
```
ลองเปลี่ยนให้ Scope เป็น `@Singleton` แทน
```kotlin
- @ActivityScoped
+ @Singleton
class EmailSender @Inject constructor() {
    fun send(msg: String){
        println(msg)
    }
}
```
ผลที่ได้
```
2021-01-04 14:51:01.868 5198-5198/? I/System.out: emailSender from class MainActivity EmailSender@245bf98
2021-01-04 14:51:02.346 5198-5198/? I/System.out: emailSender from class SecondActivity EmailSender@245bf98
2021-01-04 14:51:02.675 5198-5198/? I/System.out: emailSender from class ThirdActivity EmailSender@245bf98
```


ทีนี้เรามาลองอะไรเล่นๆดูบ้าง จากตัวอย่าง code ด้านบน ให้ scope เป็น `@ActivityRetainedScoped` ดู
```kotlin
@ActivityRetainedScoped
class EmailSender @Inject constructor() {
    fun send(msg: String){
        println(msg)
    }
}
```
ผลที่ได้
```
2021-01-04 14:51:01.868 5198-5198/? I/System.out: emailSender from class MainActivity EmailSender@3ade714
2021-01-04 14:51:02.346 5198-5198/? I/System.out: emailSender from class SecondActivity EmailSender@5776b47
2021-01-04 14:51:02.675 5198-5198/? I/System.out: emailSender from class ThirdActivity EmailSender@8b35fe6
```
จะเห็นว่า Instance นั้นยังตงเป็นคนละตัวกัน เนื่องจากว่า เรามี `@AndroidEntryPoint` อยู่สามที่นั่นทำให้ Hilt ทำการสร้าง Instance ของ emailSender แยกกัน

ลองใช้ `@ActivityRetainedScoped` เหมือนเดิม แต่เปลี่ยนให้มี `@AndroidEntryPoint` เพียงที่เดียวบ้าง
```kotlin
@ActivityRetainedScoped
class EmailSender @Inject constructor() {
    fun send(msg: String){
        println(msg)
    }
}
```
สร้าง ClassA และ ClassB โดย inject `emailSender` ไปที่ constructor
```kotlin
class ClassA @Inject constructor(
    private val emailSender: EmailSender
) {
    init {
        println("From $this EmailSender is $emailSender")
    }
}

class ClassB @Inject constructor(
    private val emailSender: EmailSender
) {
    init {
        println("From $this EmailSender is $emailSender")
    }
}
```
Print ค่าใน Activity:
```kotlin
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {

    @Inject
    lateinit var emailSender: EmailSender

    @Inject
    lateinit var classA: ClassA

    @Inject
    lateinit var classB: ClassB

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.main_activity)
        println("From $this EmailSender is $emailSender")
    }
}
```
ผลที่ได้

```
2021-01-05 10:33:21.296 13089-13089 From ClassA@5d9e5b2 EmailSender is EmailSender@b6f3903
2021-01-05 10:33:21.296 13089-13089 From ClassB@d0c6080 EmailSender is EmailSender@b6f3903
2021-01-05 10:33:21.362 13089-13089 From MainActivity@fa313f1  EmailSender is EmailSender@b6f3903
```

จบไปคร่าวๆ สำหรับเรื่อง Hiltเบื้องต้น หวังว่าจะนำไปใช้งานกันได้ไม่ยาก ส่วน Part หน้าจะมาพูดถึง JetPack Compose บ้าง ว่ามัน Revolutionize วิธีการเขียน UI ของ Android App เราอย่างไร 

### References:
- [Hilt documentation](https://dagger.dev/hilt/)
- [Dependency injection with Hilt](https://developer.android.com/training/dependency-injection/hilt-android)
- [Coding with Mitch - Hilt Dependency Injection](https://codingwithmitch.com/courses/hilt-dependency-injection/)