พื้นฐาน Dagger2 - 103 - Component Dependencies & Qualified types

Nutron
3 min readSep 23, 2020

ในบทความที่แล้วเราได้เรียนรู้การสร้าง Component, การใช้ Scope, @Component.Builder และ @BindInstance ไปแล้ว หากเห็นว่ามีประโยชน์ ฝากกด ❤️ กด Share ให้ด้วยนะจ๊ะ ในบทความนี้จะต่อยอดจากบทความที่แล้ว ซึ่งจะพูดถึงการทำงานรวมกันของหลายๆ Components โดยบทความนี้เป็นส่วนหนึ่งของชุดบทความที่มีเนื้อหาแบ่งออกเป็นหัวข้อดังนี้

  1. Dependency Injection Concept & Scope
  2. Component & Component Builder & @BindInstance
  3. Component dependencies & Qualified types
  4. Subcomponent & SubComponent Builder
  5. Dagger Android Injection

แน่นอนว่าในหนึ่ง Application เราคงไม่ได้มีแค่ Component เดียว ซึ่งอาจจะทำให้ Component นั้นใหญ่เกินไป การแยก Component ออกตามการใช้งานดูจะเป็นเรื่องที่เข้าท่ากว่า แต่จะทำอย่างไรหาก Dependencies ที่เราต้องการอยู่อีก Component หนึ่ง ซึ่งครั้นจะให้สร้างใหม่ใน Component เองก็ดูซ้ำซ้อน ดังนั้นการทำ Component Dependencies จึงเป็นวิธีที่มาช่วยแก้ปัญหานี้

Component dependencies

จากรูปตัวอย่างด้านบนคือตัวอย่างของ Component Dependencies ซึ่งประกอบไปด้วย 3 Components ได้แก่ AppComponent, DomainComponent และ ViewComponent เราจะเห็นว่า DomainComponent มี Dependency ไปยัง AppComponent ส่วน ViewComponent มี Dependency ไปยัง DomainComponent โดยที่ Interactor ใน DomainComponent ต้องการ Context และ DataSource จาก AppComponent ส่วน ViewModel ใน ViewComponent ต้องจาก Interactor จาก DomainComponent

Component Dependencies คือการเชื่อม component ต่างๆเข้าด้วยกันโดย Component (Child Component) ที่ไป Depend กับอีก Component หนึ่ง (Parent Component) สามารถอ้างถึง Objects ที่อยู่ใน Parent Component ได้ ซึ่งการทำ Component Dependencies ต้องอยู่บนเงื่อนไขที่ว่า

  • Parent Component และ Child Component ห้ามใช้ Scope เดียวกัน
  • Parent Component ต้อง declare objects ทุกตัวที่จะถูกใช้โดย Child Component
  • Component สามารถมีได้หลาย Parent Components

จาก Diagram ด้านบน เราสามารถสร้าง Component ออกมาได้ดังนี้

AppComponent

AppModule.kt
DataModule.kt
AppComponent.kt

จากโค้ดด้านบนคือโค้ดในส่วนของ AppComponent สิ่งที่อย่างให้สังเกตคือใน AppComponent เราประกาศ dependencies ต่างๆที่จะถูกใช้ใน Child Component ภายใต้ Interface ของ AppComponent อีกสิ่งหนึ่งที่อยากให้สังเกตคือ ValueDataSource ใน DataModule สามารถอ้างถึง SharePreference ใน AppModule ได้ เนื่องจากทั้งคู่ถูกกำหนดให้เป็น Module ของ AppComponent

DomainComponent

DomainModule.kt

จากโค้ดด้านบนให้เราสังเกตบรรทัดที่ 2 ของ DomainComponent โดยเราประกาศให้ DomainComponent มี Parent Component เป็น AppComponent ด้วยคำสั่ง dependencies ใน @Component ซึ่งทำให้เราสามารถอ้างถึง Context และ ValueDataSource ที่เป็น Object Dependencies ที่อยู่ใน AppCompoment ได้ จากนั้นในบรรทัดที่ 6 เราประกาศ GetNumberInteractor ไว้เนื่องจากมันจะถูกใช้ ViewComponent

ViewComponent

ViewModelModule.kt
ViewComponent.kt

จากโค้ดในบรรทัดที่ 2 ของ ViewComponent เรากำหนดให้ DomainComponent เป็น parent ของ ViewComponent ทำให้ใน ViewModelModule เราสามารถอ้างถึง GetNumberInteractor ที่เป็น object Dependencies ของ DomainComponent ได้

และสุดท้ายเมื่อเราต้องการ Inject Dependencies ให้กับ Classes ที่เรากำหนดใน ViewComponent ซึ่งในที่นี้คือ RamdomFragment และ EmptyFragment เราสามารถทำได้ดังนี้

จากโค้ดด้านบนจะเห็นว่า ViewCompoment (บรรทัดที่ 18) ต้องระบุ Parent Component ซึ่งในที่นี้คือ DomainComponent ในบรรทัดที่ 19 โดยเป็นฟังก์ชันที่ Dagger สร้างขึ้นตามชื่อ Compoment ที่ระบุใน dependencies ของ @Component ถัดมาจะเห็นว่า DomainComponent ก็ต้องระบุ Parent Component เป็น AppConponent ในบรรทัดที่ 14 จึงทำให้เราต้องสร้าง Parent ของแต่ละ Component ขึ้นมาก่อนเพื่อ Provide Component เหล่านั้นไปให้กับ Child Component

Note: อันที่จริงการสร้างเราไม่ควรสร้าง AppComponent ใน Fragment มันควรจะถูกสร้างและเก็บอยู่ใน class Appliation ซึ่งจะทำให้ AppComponent คงอยู่ตลอดใน Application Lifecycle แต่อย่างไรก็ตามเพื่อให้เห็นภาพการเรียกใช้งาน Component ชัดขึ้น เราจึงสร้าง AppComponent ขึ้นมาเป็นตัวอย่าง

เพียงเท่านี้เราก็สามารถใช้งาน Dagger Component Dependencies ได้แล้ว! 😄

เสริมความรู้ (1): Using Component Builder

ในบทความที่แล้ว เราได้เรียนรู้เกี่ยวกับการใช้ Component Builder กับไปแล้ว แต่ในบทความนี้เราไม่ได้ใช้ Component Builder เพื่อต้องการหลีกเลี่ยงความซับซ้อนที่จะเกิดขึ้น แต่จริงๆแล้ว แนะนำให้ใช้ Component Builder มากกว่า ถึงแม้จะดูซับซ้อนแต่มันชัดเจนกว่าว่า Compoment ต้องการอะไร มี Dependencies ไปถึงใครบ้าง อีกทั้งยังสามารถ Customise ได้และยังสามารถใช้ annotation พิเศษอย่าง @BindInstance ได้อีกด้วย

ทีนี้เรามาลองใช้ @Component.Builder ให้ดูเป็นตัวอย่างกันสักหน่อยโดยเราจะใช้กับ DomainComponent ซึ่งจะได้หน้าตาประมาณนี้

จุดที่อยากให้สังเกตคือใน Builder นอกจากฟังก์ชัน build() และ Module ที่เราจะต้องประกาศแล้ว เรายังต้องประกาศฟังก์ชันที่ระบุถึง Parent Component ของ Component ด้วย โดยในที่นี้คือ appComponent()

เสริมความรู้ (2): Skip level dependencies

จากตัวอย่างที่เราได้ทำมา หลายคนอาจจะตั้งคำถามว่า ถ้าหาก ViewComponent ต้องการ Object Dependencies ที่อยู่ใน AppComponent หล่ะ จะทำอย่างไร? ในกรณีนี้ ทำได้หลักๆสองวิธีคือ

วิธีแรก เราสามารถกำหนดให้ AppComponent เป็น dependencies ของ ViewCompoment ด้วยได้ เพราะ Compoment สามารถมี Parent Component ได้มากกว่าหนึ่ง Component แต่วิธีนี้ไม่ค่อยอยากแนะนำเท่าไรเพราะจะทำให้ความสัมพันธ์มันซับซ้อนและเสี่ยงต่อการเกิด Circle Dependeny เมื่อโปรแกรมของเราใหญ่ขึ้น

วิธีที่สอง คือการส่งต่อ Object Dependencies เหล่านั้นมาเป็นทอดๆ โดยส่งผ่านทาง DomainComponent และ DomainComponent ส่งต่อให้กับ ViewComponent วิธีนี้จะทำให้เราเห็นภาพชัดเจนขึ้นว่า Dependencies Graph มีความสัมพันธ์กันอย่างไร ตัวอย่างเช่น จาก Dependencies Graph ข้างต้น หาก ViewModel ต้องการ DataSource เราก็ประกาศฟังก์ชั่นที่ provide DataSource ไว้ใน AppComponent เพื่อให้ DomainComponent รู้จัก แล้วเราก็ประกาศแบบเดียวกันใน DomainComponent เพื่อให้ ViewModelComponent รู้จัก DataSource

เสริมความรู้ (3): Qualified types: @Named

อย่างที่ได้เคยกล่าวไว้ในบทความที่แล้วว่า Dagger จะยึด Return Type เป็นสำคัญในการสร้าง Dependencies Graph แต่หากเราต้องการ provide object dependencies ที่ return เป็น Type เดียวกัน ก็สามารถทำได้ด้วยการใช้ annotation ที่ชื่อว่า @Named() ลองดูตัวอย่างด้านล่าง

จากโค้ดด้านบนจะเห็นว่าทั้ง LocalDataSource และ RemoteDataSource มี Type แบบเดียวกับนั่นคือ ValueDataSource เราสามารถใช้ @Named() เพื่อระบุความแตกต่างให้ Dagger รู้ได้ แต่ทั้งนี้ทั้งนั้น Caller ที่เรียกใช้ก็ต้องระบุ @Named() ด้วยเหมือนกันว่าจะใช้ตัวไหน ลองดูตัวอย่างข้างล่าง

Conclusion

ในบทความนี้ เราได้เรียนรู้และเข้าใจหลักการทำงานของ Conponent Dependenies อีกทั้งได้เรียนรู้ Qualified types ใหม่อย่าง @Named จุดที่อยากจะเน้นย้ำของการทำ Conponent dependencies ก็คือเงื่อนไขสามข้อ

  • Parent Component และ Child Component ห้ามใช้ Scope เดียวกัน
  • Parent Component ต้อง declare objects ทุกตัวที่จะถูกใช้โดย Child Component
  • Component สามารถมีได้หลาย Parent Components

และแนะนำให้ใช้ @Component.Builder แทนที่จะให้ Dagger generate code ให้ เพราะนอกจากจะทำให้เห็น dependencies ของ Component ได้อย่างชัดเจนแล้วยังเบื้องลึกเบื้องหลังยังช่วยลด Build time ลงได้อีกด้วย ซึ่งเหมาะกับการใช้ในโปรเจคที่มีขนาดใหญ่

ในบทความหน้าเราจะมาเรียนรู้อีกหนึ่งวิธี ที่ช่วยในการจัดการ Dependencies ของหลายๆ Component รอติดตามกันได้นะจ๊ะ

สุดท้าย ถ้าเห็นว่า Blog นี้มีประโยชน์ ช่วยสนับสนุนด้วยการฝากกด ❤️ กด Share ให้กันด้วยนะจ๊ะ จะได้มีกำลังใจในการเขียนบทความต่อๆไป 😄 ขอบคุณครับ

--

--