พื้นฐาน Dagger2 - 103 - Component Dependencies & Qualified types
ในบทความที่แล้วเราได้เรียนรู้การสร้าง Component, การใช้ Scope, @Component.Builder
และ @BindInstance
ไปแล้ว หากเห็นว่ามีประโยชน์ ฝากกด ❤️ กด Share ให้ด้วยนะจ๊ะ ในบทความนี้จะต่อยอดจากบทความที่แล้ว ซึ่งจะพูดถึงการทำงานรวมกันของหลายๆ Components โดยบทความนี้เป็นส่วนหนึ่งของชุดบทความที่มีเนื้อหาแบ่งออกเป็นหัวข้อดังนี้
- Dependency Injection Concept & Scope
- Component & Component Builder & @BindInstance
- Component dependencies & Qualified types
- Subcomponent & SubComponent Builder
- 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
จากโค้ดด้านบนคือโค้ดในส่วนของ AppComponent
สิ่งที่อย่างให้สังเกตคือใน AppComponent
เราประกาศ dependencies ต่างๆที่จะถูกใช้ใน Child Component ภายใต้ Interface ของ AppComponent
อีกสิ่งหนึ่งที่อยากให้สังเกตคือ ValueDataSource
ใน DataModule
สามารถอ้างถึง SharePreference
ใน AppModule
ได้ เนื่องจากทั้งคู่ถูกกำหนดให้เป็น Module ของ AppComponent
DomainComponent
จากโค้ดด้านบนให้เราสังเกตบรรทัดที่ 2 ของ DomainComponent
โดยเราประกาศให้ DomainComponent
มี Parent Component เป็น AppComponent
ด้วยคำสั่ง dependencies
ใน @Component
ซึ่งทำให้เราสามารถอ้างถึง Context
และ ValueDataSource
ที่เป็น Object Dependencies ที่อยู่ใน AppCompoment
ได้ จากนั้นในบรรทัดที่ 6 เราประกาศ GetNumberInteractor
ไว้เนื่องจากมันจะถูกใช้ ViewComponent
ViewComponent
จากโค้ดในบรรทัดที่ 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 มันควรจะถูกสร้างและเก็บอยู่ใน classAppliation
ซึ่งจะทำให้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 ให้กันด้วยนะจ๊ะ จะได้มีกำลังใจในการเขียนบทความต่อๆไป 😄 ขอบคุณครับ