Dagger Android Injection - Part 1 - Inject Activity

Nutron
5 min readFeb 4, 2018

--

image power by www.flaticon.com

ในยุคหลังๆนี้หากเราสังเกตกันดีๆจะพบว่า code ที่ Blogger ส่วนใหญ่ใช้เพื่อประกอบความเข้าใจใน Blog มักจะใช้พวก Annotation (พวกที่มีเครื่องหมาย @ นำหน้า) ต่างๆเข้ามาช่วยเพื่อให้โค้ดอ่านง่ายขึ้น ซึ่งสิ่งที่เรามักจะเห็น Blogger เหล่านั้นใช้กันอยู่บ่อยๆก็คือ Dagger2 นั้นเอง ในบทความนี้เราจะมาพูดถึงการใช้ API ใหม่ ของ Dagger2 ที่สามารถทำงานร่วมกับ Android Environment ได้ดี ทำให้การ Inject Activity หรือ Fragment ง่ายขึ้น หัวข้อนี้จะแบ่งออกเป็น 3 ตอนด้วยกัน คือ

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 ว่ามันจะประกอบไปด้วยส่วนต่างๆดังนี้ครับ

  1. AndroidInjectionModule.class : อันนี้เป็น class ที่ Dagger provide มาให้ หน้าที่เราคือใส่มันเข้าไปเพื่อให้ Dagger สามารถ Inject Module ที่เกี่ยวข้องกับ Android ได้
  2. AppModule.class : อันนี้คือ Module ที่เราสร้างขึ้นสำหรับ AppComponent
  3. ActivityBinder.class : อันนี้เป็น class ที่เราสร้างขึ้นมา เพื่อบอกให้ Dagger รู้ว่า เราจะมีการ Inject ActivityComponent อะไรบ้าง
  4. @Component.Builder : เป็นตัวที่บอก Dagger ว่าจะสร้าง Component นี้ขึ้นมาต้องใช้อะไรบ้างในที่นี้เราต้องการให้ AppComponent ของเรามี object Application ด้วยเนื่องจากเราเรียกใช้งานมันใน AppModule
  5. ComponentBuilder : โดยปกติแล้ว Builder ที่เราสร้างขึ้นมาต้องมี method build() ที่ return Component นั้นออกไปเสมอ ในที่นี้เราจึงสร้าง ComponentBuilder ขึ้นมาเพื่อช่วยอำนวยความสะดวก
  6. @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 เป็นเรื่องที่ง่ายขึ้น โดยเราสามารถสรุปขั้นตอนได้คร่าวๆดังนี้

  1. สร้าง AppModule ซึ่งเป็น Module ของ AppComponent ขึ้นมา
  2. สร้าง AppComponent แล้วกำหนด AppModule ให้กับ AppComponent
  3. สร้าง Module ของ ActivityComponent
  4. สร้าง ActivityComponent แล้วกำหนด Module ที่สร้างในข้อสามให้กับ ActivityComponent
  5. กำหนด ActivityComponent ให้กับ AppModule โดยใช้คำสั่ง subcomponent
  6. สร้าง ActivityBinder เพื่อบอก Dagger ว่าจะมี Activity Component อะไรบ้าง
  7. กำหนด ActivityBinder ให้กับ AppComponent เนื่องจาก root component ของ ActivityComponent ต่างๆคือ AppComponent
  8. Implement interface HasActivityInjector ใน class Application เพื่อกำหนดตัว ActivityInjector ให้กับ Dagger
  9. สร้างตัวแปล DispatchingAndroidInjector<Activity> แล้ว return กลับไปใน method activityInjector ที่มาพร้อมกับ interface HasActivityInjector เพื่อใช้ dagger สามารถ Inject Activity ต่างๆของเราได้

ในบทความหน้า เราจะมาดูกันว่าเราจะสามารถลดโค้ดหรือตัดขั้นตอนไหนออกไปได้บ้าง และจะทำมันอย่างไร รอติดตามกันนะครับ

สุดท้ายนี้ก็เช่นเคยครับ หากผู้อ่านคิดว่าบทความนี้มีประโยชน์ ก็ฝากกด 👏 เป็นกำลังใจให้ด้วยนะครับ

--

--