ในยุคหลังๆนี้หากเราสังเกตกันดีๆจะพบว่า code ที่ Blogger ส่วนใหญ่ใช้เพื่อประกอบความเข้าใจใน Blog มักจะใช้พวก Annotation (พวกที่มีเครื่องหมาย @ นำหน้า) ต่างๆเข้ามาช่วยเพื่อให้โค้ดอ่านง่ายขึ้น ซึ่งสิ่งที่เรามักจะเห็น Blogger เหล่านั้นใช้กันอยู่บ่อยๆก็คือ Dagger2 นั้นเอง ในบทความนี้เราจะมาพูดถึงการใช้ API ใหม่ ของ Dagger2 ที่สามารถทำงานร่วมกับ Android Environment ได้ดี ทำให้การ Inject Activity หรือ Fragment ง่ายขึ้น หัวข้อนี้จะแบ่งออกเป็น 3 ตอนด้วยกัน คือ
- Part1 - Inject Activity with Dagger Android Injection
- Part2 - Eliminate Boilerplate code in Dagger Android Injection
- Part3 - Inject Fragment with Dagger Android Injection
Dagger & Dependency Injection (DI)
หลายคนอาจเข้าใจว่า Dagger เป็น library ที่ช่วยทำให้โค้ดของเราสั้น กระชับ และอ่านเข้าใจง่าย แต่จริงๆแล้วประโยชน์ที่แท้จริงของ Dagger คือการทำ Dependency Injection (DI) โดยมี concept ที่ว่า caller ไม่ควรที่จะต้องรู้ว่า Object เหล่านั้นถูกสร้างขึ้นมาอย่างไร ตัวอย่างเช่น หาก Class A ต้องการใช้ Class B แทนที่จะสร้าง B ขึ้นมาใน A เรากลับสร้าง B จากที่อื่นแล้วโยน B เข้ามาให้ A แทน จะเห็นได้ว่า A ไม่จำเป็นต้องรู้ว่า B ถูกสร้างขึ้นมาอย่างไร ซึ่งประโยชน์ของการทำ DI ที่เห็นได้ชัดคือ ช่วยทำให้การทำ Unit Test นั้นง่ายขึ้นและลดการผูกมัดกันระหว่าง Class แต่อย่างไรก็ตามบทความนี้เราไม่ได้มาสาธยายความดีงามของการทำ DI ถ้าใครอยากรู้เพิ่มเติมก็สามารถไปหาอ่านกันได้ไม่ยาก มีบทความที่พูดถึงเรื่องนี้กันอยู่มากมายหรือจะอ่านจากบทความเก่าของผมตาม link นี้ก็ยินดีครับ และเช่นกันเราก็ไม่ได้มาสอนพื้นฐานการใช้งาน Dagger2 แบบ 101 ว่าจะเริ่มใช้ Dagger2 ยังไง เพราะในบทความนี้เราจะเน้นพูดถึงการใช้งาน API ใหม่ของ Dagger2 ที่มาใน version 2.11 แทน ซึ่งไม่ต้องกลัวว่าจะตามไม่ทัน เพราะจะพยายามเขียนให้ละเอียดที่สุด เพื่อประโยชน์สุขแห่งชาวประชาชี แต่ถ้าจะให้ดีก็อยากให้ลองศึกษา Dagger2 ขั้นพื้นฐานมาก่อนก็จะ very good มากจ้าา
Old Version Issues
หากใครเคยใช้ Dagger มาก่อน (ตั้งแต่ version 2.11 ลงไป) จะพบว่าการ Inject Activity หรือ Fragment ค่อนข้างเป็นเรื่องที่ซับซ้อน เนื่องจาก Dagger2 เองก็ยังไม่ได้ Support การ Inject Activity หรือ Fragment อย่างเป็นทางการ เลยทำให้ไม่มีรูปแบบการทำที่ชัดเจน พูดง่ายๆว่าใครสบายใจแบบไหนก็ทำแบบนั้น โดยใช้ความสามารถของ Dagger เท่าที่มีอยู่ตอนนั้น จนกระทั้ง Dagger2 version 2.11 ได้อุบัติขึ้น ทำให้ชีวิตของชาวแด๊กทั้งหลายเปลี่ยนไป
หากย้อนกลับไปก่อน version 2.11 เวลาเราจะ Inject ของเข้า Activity หรือ Fragment สิ่งที่เกิดขึ้นคือเราต้องสร้าง Component ขึ้นมาใน Activity หรือ Fragment แบบนี้
ถ้าหากอ่านโค้ดไม่เข้าใจไม่ต้องกังวลให้เราข้ามมันไปก่อน แต่ประเด็นที่อยากจะกล่าวถึงคือ ทำไม Activity หรือ Fragment เหล่านี้ที่ต้องการ inject presenter
เข้ามา ต้องรู้ว่าการจะได้ object presenter
มา ต้องสร้าง ViewComponent
แล้วค่อยสั่ง Inject
หนำซ้ำยังต้องรู้ด้วยว่าจะต้องใช้ Module อะไรบ้างในการสร้าง ViewComponent
ซึ่งแน่นอนว่าโค้ดเหล่านี้จะปรากฏอยู่ใน Activity หรือ Fragment นั้นๆ ทำให้ Activity หรือ Fragment นั้นเกิด Dependency กับ class ViewModule
และDomainModule
อย่างหลีกเลี่ยงไม่ได้ ซึ่งผิด Concept ของ DI ที่ว่า Class ไม่ควรจะรู้ว่า object เหล่านั้นถูกสร้างขึ้นมาอย่างไร หรือดีขึ้นมาหน่อยบางคนก็อาจจะใช้ Extension Function (ใน Kotlin) เข้ามาช่วย ซึ่งก็เป็นอีกวิธีหนึ่งที่ช่วยแยกโค้ดส่วนที่ซับซ้อนออกไป แต่เราจะไม่พูดถึงประเด็นนี้กันในบทความนี้
Dagger Android Injection
และด้วยปัญหาที่กล่าวไปข้างต้น Dagger2 จึงออก version 2.11 ซึ่งเพิ่มความสามารถให้ทำงานคู่กับ Environment ของ Android ได้ดีขึ้น โดยสามารถ Inject
Activity หรือ Fragment ผ่านทาง API ใหม่ที่ Dagger เตรียมไว้ให้ แต่อย่างไรก็ตาม ของทุกอย่างก็ต้องมีพิธีรีตอง จะได้อะไรมาก็ต้องออกแรงนิดนึง ซึ่งเป็นหน้าที่ของ Dev อย่างเราๆที่จะต้องช่วยบอก Dagger ว่าจะมารู้จักกับ Activity หรือ Fragment ของเราได้อย่างไร
It’s time to implement!
บทความนี้จะขออธิบายถึงวิธีการทำ Activity Injection ก่อน ส่วน Fragment Injection จะขออธิบายในบทความต่อไป โดยผลลัพธ์สุดท้ายของบทความนี้จะได้โครงสร้างหน้าตาความสัมพันธ์ของ Component ประมาณรูปด้านล่างนี้ครับ
แต่เพื่อให้ง่ายต่อความเข้าใจในบทความนี้เราจะมาลองทำแค่ Component เดียวกันก่อน โดยให้ชื่อว่า MainActivityComponent
ซึ่งแบ่งเป็นขั้นตอนการทำได้ดังนี้
Step 0). Add dependencies ใน build.gradle
ให้เราเพิ่มโค้ดด้านล่างลงใน build.gradleโดยในที่นี้เราจะใช้ dagger version 2.13 กัน
implementation "com.google.dagger:dagger:$daggerVersion"
implementation "com.google.dagger:dagger-android-support:$daggerVersion"
kapt "com.google.dagger:dagger-compiler:$daggerVersion"
Step 1). สร้าง AppModule
ให้เราสร้าง class AppModule
ที่เป็น Module ของ AppComponent
ขึ้นมาก่อน โดย Module นี้จะทำหน้าที่บอก Dagger ว่าการสร้าง Instance ของ Feature ต่างๆขึ้นมานั้น จะมีวิธีการสร้างอย่างไร โดย Feature ต่างๆที่สร้างอยู่ใน AppModule
นี้ จะมี Life-Time อยู่ในระดับ Application Scope นั้นคือ จะมีชีวิตอยู่จนกว่า Application ของเราจะดับสูญไป เหมาะกับ Feature mudule ที่ต้องใช้งานมันบ่อยๆและต้องใช้พลังงานสูงในการสร้างมันขึ้นมา (expensive to initialize) ตัวอย่างของ Feature เหล่านี้ได้แก่ ApiService, Database หรือ SharedPrefs. โดยในที่นี้เราจะจำลองการสร้าง ApiService
ขึ้นมาใน AppModule
Note*: [สำหรับผู้ที่มีประสบการณ์ Dagger มาก่อน] เราสามารถ provide
Application
instance ให้กับAppModule
ได้โดยผ่านทางconstructor
ของAppModule
แต่ในที่นี้เราจะใช้วิธีการ provide ผ่านทาง@BindsInstance
ในAppComponent
แทน (รอสังเกตในขั้นตอนการสร้างAppComponent
)
Step 2). สร้าง MainComponent
ในขั้นตอนนี้เราจะยังไม่สร้าง AppComponent
ขึ้นมาก่อน ถึงแม้จะสร้าง AppModule
ขึ้นมารอแล้วก็ตาม โดยเราจะไปสร้าง ActivityCompoment ขึ้นมาแทน ซึ่งในที่นี้คือ MainActivityComponent
เนื่องจากเราจำเป็นที่จะต้องใช้ MainActivityComponent
นี้ใน AppComponent
นั้นเอง
Step 2.1). สร้าง Module สำหรับ MainActivityComponent
ขั้นตอนนี้จะเป็นการบอก Dagger ว่าการสร้าง Instance ของ Object ต่างๆที่ต้องใช้ใน MainActivity
ต้องทำอย่างไร โดยในที่นี้จะบอกถึงวิธีการสร้าง Presenter
และ MainView
ซึ่งหากใครเคยมีประสบการณ์กับ Dagger มาก่อนในขั้นตอนนี้บางคนก็เลือกที่จะส่ง MainActivity
เข้ามาทาง constructor
ของ Module เนื่องจาก method provideMainView()
ต้องการ MainActivity
เป็น parameter แต่ใน version ใหม่นี้ เราไม่จำเป็นต้องส่ง MainActivity
เข้ามา เพราะ Dagger จะจัดการให้เราเอง โดย Dagger จะรู้เองว่าจะไปหา MainActivity
ได้จากที่ไหน
Step 2.2). สร้าง MainActivityComponent
ในขั้นตอนนี้จะเป็นการสร้าง Component ของ MainActivity
ขึ้นมา โดยจะเป็น Pattern บังคับของทาง Dagger ให้เราทำตามนี้
เสริมความรู้: @Subcomponent.Builder
จะเป็นตัวบอก Dagger ว่าจะสร้าง Component เหล่านี้ขึ้นมาต้องใช้อะไรบ้าง สาเหตุที่ต้องใช้ @SupComponent
เพราะว่า Component ที่เรากำลังสร้างนี้ เป็น sub-component ของ AppComponent
อีกทีหนึ่ง
ข้อสังเกตอีกอย่างหนึ่งก็คือ Builder ที่เราสร้างขึ้นมาจะเป็น abtract
class
(ไม่ใช่ Interface class) เนื่องจากเราจะ extend classAndroidInjector.Builder
ทำให้เราไม่ต้องมี method build()
(ที่ปกติบังคับให้ต้องมี) เนื่องจาก class AndroidInjector.Builder
ได้จัดการให้เราแล้ว
Step 2.3). ทำให้ MainActivityComponent รู้จักกับ AppModule
หลังจาก สร้าง Component เสร็จแล้ว หาก Component เหล่านั้นใช้ Feature อื่นๆที่มีอยู่ใน AppMudule
ให้เรานำ Component เหล่านั้นไปเพิ่มไว้ในคำสั่ง subcomponents
ที่อยู่ใน AppModule
ด้วย โดยในที่นี้เรารู้ว่า MainActivity
ของเราใช้ ApiService
ที่อยู่ใน AppModule
ดังนั้นเราจึงจัดการเพิ่ม MainActivityComponent
ของเราลงใน class AppModule
ดังนี้
@Module(subcomponents = [MainActivityComponent::class])
step 3). สร้าง Binder Class ขึ้นมา
ต่อไปเราจะสร้าง class ที่ไว้สำหรับบอก Dagger ว่าเราจะมี ActivityComponent อะไรบ้าง โดยในที่นี้เราจะสร้าง class ที่ชื่อว่า ActivityBinder
ที่ไว้สำหรับรวบรวม ActivityComponent ทั้งหมด โดยหน้าตาของ class จะเป็นดังนี้
อาจจะรู้สึกว่ามี Anotation เยอะเยะเต็มไปหมด แต่จำไปก่อนครับเพราะมันเป็น Pattern ที่ต้องจำ เดี๋ยวท้ายๆจะมีวิธีทำให้มันสั้นลงกว่านี้ครับ
step 4). สร้าง AppComponent
และแล้วก็ถึงขั้นตอนการสร้าง AppComponent
สักที โดย AppComponent
นี้จะเป็น Root Component ของทุกๆ Component ซึ่ง AppComponent
จะมีหน้าตาประมาณนี้
อยากให้ผู้อ่านลองสังเกตุ AppComponent
ว่ามันจะประกอบไปด้วยส่วนต่างๆดังนี้ครับ
- AndroidInjectionModule.class : อันนี้เป็น class ที่ Dagger provide มาให้ หน้าที่เราคือใส่มันเข้าไปเพื่อให้ Dagger สามารถ Inject Module ที่เกี่ยวข้องกับ Android ได้
- AppModule.class : อันนี้คือ Module ที่เราสร้างขึ้นสำหรับ
AppComponent
- ActivityBinder.class : อันนี้เป็น class ที่เราสร้างขึ้นมา เพื่อบอกให้ Dagger รู้ว่า เราจะมีการ Inject ActivityComponent อะไรบ้าง
- @Component.Builder : เป็นตัวที่บอก Dagger ว่าจะสร้าง Component นี้ขึ้นมาต้องใช้อะไรบ้างในที่นี้เราต้องการให้
AppComponent
ของเรามี object Application ด้วยเนื่องจากเราเรียกใช้งานมันในAppModule
- ComponentBuilder : โดยปกติแล้ว Builder ที่เราสร้างขึ้นมาต้องมี method
build()
ที่ return Component นั้นออกไปเสมอ ในที่นี้เราจึงสร้างComponentBuilder
ขึ้นมาเพื่อช่วยอำนวยความสะดวก - @BindsInstand : คือ Annotation ที่จะอนุญาติให้ เรา Inject Instance ของเราเข้าไปใน Component นั้นๆ ซึ่งในที่นี่เรา Inject
Application
instance เข้าไป
step 5). Implement HasActivityInjector
เนื่องจากว่า class Application นั้นเป็น Root ของทุกๆ Activity ดังนั้นเราจึงต้องให้ class Application นี้ implement HasActivityInjector
เพื่อบอกให้ Dagger รู้ว่าเรามี Activity ที่ต้องการ Inject โดยหน้าตาของ MainApplication
ของเราจะเป็นดังนี้
จะเห็นว่า เมื่อเรา implement HasActivityInjector
แล้ว จะมี method ที่เราต้อง implement เพิ่ม นั่นคือ activityInjector()
ซึ่งเราจะต้องสร้าง instance ของ DispatchingAndroidInjector<Activity>
ขึ้นมา แล้วคืนค่ามันกลับไปใน method นี้ จากนั้นจึงสั่ง inject
ผ่านทาง appComponent
ใน onCreate
เพื่อให้ Dagger inject object DispatchingAndroidInjector<Activity>
ให้กับ MainApplication
class ของเรา
ถึงจุดนี้เราก็พร้อมใช้งาน Dagger ที่สามารถ Inject ของต่างๆให้กับ Activity ของเราได้แล้ว โดยที่เราไม่ต้องสร้าง Component เหล่านั้นขึ้นมาใน Activity ของเรา โดยวิธีใช้มันง่ายมากจอร์จเพียงแค่คุณทำตามนี้
@Inject lateinit var presenter: MainContract.UserActionListener...override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main) AndroidInjection.inject(this)
}
เพียงเท่านี้ presenter
ก็จะถูก Inject
เข้าไปใน Activity
ของเรา พร้อมใช้งานทันที
แต่อย่างไรก็ตาม ทั้งหมดทั้งมวนที่เราทำมาดูมันจะมีโค้ดเยะเยอะเต็มไปหมด ในบทความหน้าเราจะมาดูกันว่าขั้นตอนไหนบ้างที่เราจะตัดมันออกไปได้บ้าง รอติดตามกันนะครับ
ใครสนใจดูโค้ดทั้งหมดสามารถไป Checkout ออกมาดูได้ตาม link ด้านล่างนี้ได้ครับ
Conclusion
ใน บทความนี้ เราได้พูดถึงการใช้งาน API ใหม่ของ Dagger ที่สามารถทำงานร่วมกับ Android environment ได้ดี ทำให้การ Inject Activity เป็นเรื่องที่ง่ายขึ้น โดยเราสามารถสรุปขั้นตอนได้คร่าวๆดังนี้
- สร้าง
AppModule
ซึ่งเป็น Module ของAppComponent
ขึ้นมา - สร้าง
AppComponent
แล้วกำหนดAppModule
ให้กับAppComponent
- สร้าง Module ของ
ActivityComponent
- สร้าง
ActivityComponent
แล้วกำหนด Module ที่สร้างในข้อสามให้กับActivityComponent
- กำหนด
ActivityComponent
ให้กับAppModule
โดยใช้คำสั่งsubcomponent
- สร้าง
ActivityBinder
เพื่อบอก Dagger ว่าจะมี Activity Component อะไรบ้าง - กำหนด
ActivityBinder
ให้กับAppComponent
เนื่องจาก root component ของ ActivityComponent ต่างๆคือAppComponent
Implement
interface
HasActivityInjector
ใน classApplication
เพื่อกำหนดตัวActivityInjector
ให้กับ Dagger- สร้างตัวแปล
DispatchingAndroidInjector<Activity>
แล้วreturn
กลับไปใน methodactivityInjector
ที่มาพร้อมกับinterface
HasActivityInjector
เพื่อใช้ dagger สามารถ InjectActivity
ต่างๆของเราได้
ในบทความหน้า เราจะมาดูกันว่าเราจะสามารถลดโค้ดหรือตัดขั้นตอนไหนออกไปได้บ้าง และจะทำมันอย่างไร รอติดตามกันนะครับ
สุดท้ายนี้ก็เช่นเคยครับ หากผู้อ่านคิดว่าบทความนี้มีประโยชน์ ก็ฝากกด 👏 เป็นกำลังใจให้ด้วยนะครับ