บทความที่แล้วเราพูดถึง Component Dependencies รวมถึงการใช้ @Component.Builder
และ @Named
กันไปแล้ว ในบนความนี้เราจะมาลองใช้อีกหนึ่งวิธีที่ช่วยจัดการกับการแชร์ Object Dependencies ระหว่าง Components โดยบทความนี้เป็นส่วนหนึ่งของชุดบทความที่มีเนื้อหาแบ่งออกเป็นหัวข้อดังนี้
- Dependency Injection Concept & Scope
- Component & Component Builder & @BindInstance
- Component dependencies & Qualified types
- Subcomponent & SubComponent Builder
- Dagger Android Injection
Subcomponent
Subcomponent คืออีกรูปแบบหนึ่งของการแชร์ Object Dependencies ระหว่าง Components เช่นเดียวเดียวกับ Component with Dependencies ทั้ง Subcomponent และ Component with Dependencies เองมี Lifecycle เป็นของตัวเอง นั่นคือ หากไม่มี Component หรือ Object ไหนอ้างถึง (reference) มันก็จะตายไปทำให้ Object Dependencies ทีมันถืออยู่ก็หายไปด้วย เราสามารถใช้ Scope กับ SubComponent เพื่อช่วยในการทำ local Singleton ได้เช่นกัน การใช้ Subcomponent มีข้อแตกต่างจากการใช้ Component with Dependencies ดังนี้
- เราไม่จำเป็นต้องประกาศ Object dependencies ที่จะถูกใช้ใน Subcomponent ใน Parent Component เหมือนกับ Component Dependencies แต่ Parent component ต้องประกาศว่ามี Subcomponent อะไรบ้าง
- Subcomponent สามารถเข้าถึงได้ทุก Object dependencies ใน Parent Component ซึ่งรวมถึง Parent ของ Parent Component ด้วย
- Subcomponent มี Parent Component ได้แค่ตัวเดียว
จาก Diagram ด้านบนจะเห็นว่า ViewModel
ที่อยู่ใน ViewComponent
สามารถอ้างถึง NetworkUtils
ที่อยู่ใน AppComponent
ได้ โดยที่ ViewComponent
เป็น Subcomponent ของ DomainComponent
จาก Diagram ด้านบน เราสามารถสร้าง Component ออกมาได้ดังนี้
AppComponent
จากโค้ดด้านบนเราประกาศฟังก์ชั่นที่จะคืนค่ากลับไปเป็น DomainComponent
ไว้ใน Interface
ของ AppComponent
เพื่อบอกให้ Dagger รู้ว่า DomainComponent
จะเป็น SubComponent ของ AppComponent
ส่วนสำคัญที่อยากให้สังเกตคือ Return Type จะต้องเป็น Component และ Parameter ที่รับเป็น Module ของ SubComponent นั้น
DomainComponent
จากโค้ดด้านบน เราประกาศให้ DomainComponent
เป็น Subcomponent โดยใช้คำสั่ง @Subcomponent
จะสังเกตเห็นว่าเราไม่ต้องระบุ Parent Component เหมือนการทำ Component with Dependencies จากนั้นเราประกาศฟังก์ชั่นที่จะคืนค่ากลับไปเป็น ViewComponent
ไว้ใน Interface
ของ DomainComponent
เพื่อบอกให้ Dagger รู้ว่า ViewComponent
จะเป็น Subcomponent ของ DomainComponent
ViewComponent
จากโค้ดด้านบน เราประกาศให้ ViewComponent
เป็น Subcomponent ด้วย @Subcomponent
และใน ViewModelModule
มีการอ้างถึง NetworlUtils
ซึ่งเป็น Object Dependencies ของ AppComponent
ทั้งๆที่ ViewComponent
ไม่ได้เป็น Subcomponent โดยตรงของ AppComponent
แต่เป็น Subcomponent ของ DomainComponent
เมื่อเราเตรียม Component ทั้งหมดแล้ว ก็ได้เวลาเรียกใช้งาน ซึ่งการเรียกใช้งานเป็นไปตามโค้ดด้านล่างดังนี้
จากโค้ดด้านบน ViewComponent
ถูกสร้างโดย DomainComponent
และ DomainComponent
ถูกสร้างโดย AppComponent
โดยเรียกฟังก์ชั่นที่ประกาศไว้ในแต่ละ Interface ของ Component
Note(1): ตัวอย่างด้านบนเป็นเพียงการสาธิตการเรียกใช้งาน Subcomponent ซึ่งจริงๆแล้ว instance ของ AppComponent
ไม่ควรถูกสร้างทุกครั้งที่เรียกใช้งาน แต่ควรถูกสร้างแค่ครั้งเดียวใน Application เพื่อให้มันคงอยู่ตลอดในระดับ Application Scope (หรือ Global Scope)
Note(2): ทุกครั้งที่เรียก addDomainComponent()
หรือ addViewComponent()
นั้น คือการสร้าง instance ของ Component เหล่านั้นขึ้นมาใหม่ ดังนั้นควรระมัดระวังในการใช้
Note(3): อย่างที่กล่าวไว้ตอนต้น ทั้ง Component with Dependencies และ Subcomponent มี Lifecycle เป็นของตัวเอง ดังนั้นหากเราไม่ต้องการใช้มันแล้วหรือ ต้องการทำลายมันเพื่อจัดการกับ memmory เราสามารถทำได้ด้วยการกำหนดค่า null
ให้กับ component เหล่านั้น ตัวอย่างเช่น viewComponent = null
— — — — — — — — — — — — — — — — — — — — — — — — — — — — — — —
@Subcomponent.Builder
บทความที่แล้วเราได้เรียนรู้การใช้งาน @Component.Builder
คู่กับ Component กันไปแล้ว แน่นอนว่า Subcomponent เองก็มี @Subcomponent.Builder
ให้ใช้เหมือนกัน แล้วการใช้ Subcomponent Builder มันดีอย่างไร?
จากตัวอย่างการสร้าง Subcomponent ด้านบนจะเห็นว่า Parent Component ต้องประกาศ Subcomponent ทุกตัวใน Interface ทำให้ Parent Component ผูกมัด (coupled) กับ Subcomponent โดยตรง การใช้ Subcomponent ฺBuilder จึงเป็นวิธีที่มาช่วยลดการผูกมัด (de-coupled) ระหว่าง Parent Component และ Subcomponent โดยไม่จำเป็นต้องประกาศ Subcomponents ใน Parent Component อีกต่อไป
ทีนี้เรามาลองเปลี่ยนตัวอย่างด้านบนให้มาใช้ @Subcomponent.Builder
กัน เช่นเดียวกับ @Component.Builder
ตัว Builder ต้องมีฟังก์ชันที่คืนค่าเป็น Component นั้นเสมอ ซึ่งในที่นี่คือ build()
ดังนั้นเพื่อความสะดวก เราจะสร้าง Base Interface ที่จัดเตรียมฟังก์ชั่น build()
ไว้ให้แล้วดังนี้
interface SubComponentBuilder<out V> {
fun build(): V
}
ต่อไปเราจะแก้ไข DomainComponent
ให้มาใช้ @Subcomponent.Builder
ดังนี้
จากโค้ด เราสร้าง Interface ที่ชื่อว่า Builder
โดยมี @Subcomponent.Builder
เป็นตัวกำหนด ซึ่ง Builder
จะ implement SubComponentBuilder
ที่เป็น base interface ที่เราสร้างขึ้น
ถัดไปเราจะสร้าง Binder ขึ้นมา ซึ่งเจ้า Binder นี้จะถูกกำหนดให้กับ Parent Component เพื่อใช้ในการสร้าง Dependencies graph ในฝั่งของ Parent Component โดยหน้าตาของ Binder จะเป็นดังนี้
จากโค้ดด้านบนใน บรรทัดที่ 9–12 เป็นการสร้าง Annotation ใหม่ขึ้นมา โดยให้ชื่อว่า SubConponentKey
โดยจะรับ parameter เป็น Class Type ซึ่งใช้ในการ Map ระหว่างชื่อ Builder กับ Builder Object
ถัดมาเราจะสร้างคลาส ApplicationBinderModule
ซึ่งเป็น abstract class โดยมี @Module
ที่รับ parameter เป็น subcomponents
กำกับอยู่ ซึ่งในที่นี้เนื่องมีแค่ DomainComponent
ที่เราต้องการให้เป็น SubComponent ของ AppComponent ดังนั้นเราจึงใส่ DomainComponent
ให้กับ subcomponents
ใน @Module
สุดท้ายเราใช้คำสั่ง @Binds
ในบรรทัดที่ 17 เพื่อให้ Parent Component ซึ่งในที่นี้คือ AppComponent
รู้จักกับ Builder ของ SubComponent ที่เราจะผูกด้วย จากนั้นเราบอกให้ ใช้ @IntoMap
และ @SubComponentKey
เพื่อระบุให้ Dagger สร้าง Map ระหว่างชื่อ Builder class และ Object ของ Builder โดยในที่นี่เราจะระบุเป็น DomainComponent.Builder
ซึ่งหากเรามีมากกว่าหนึ่ง SubComponent ก็ทำแบบเดียวกัน
หลังจากที่เราสร้าง Binder เสร็จแล้ว ขั้นตอนต่อไปคือการกำหนด Binder ให้กับ AppComponent โดยจะได้หน้าตาประมาณนี้
จากโค้ดด้านบน เราจะเพิ่ม ApplicationBinderModule
ใน modules
ของ @Component
ที่บรรทัดที่ 2 จากนั้นเราจะสร้างฟังก์ชันที่คืนค่าเป็น Map ที่ภายใน Map จะเก็บ Object ของทุก Builder ของ Subcomponent ที่ประกาศใน Binder ไว้ และจุดสำคัญที่อยากให้สังเกตคือ เราไม่ต้องประกาศ Subcomponent ไว้ภายใน Interface
ของ Parent Component อีกแล้ว
ขั้นตอนสุดท้ายคือการเรียกใช้ Subcomponent เพื่อ Inject Dependencies ก็จะได้หน้าตาประมาณนี้
ในที่นี้เราทำ DomainComponent ให้ดูเป็นตัวอย่าง โดย ViewComponent ก็จะทำแบบเดียวกัน แค่สร้าง Binder และระบุให้กับ DomainComponent ที่เป็น Parent Component ของ ViewComponent
Conclusion
บทความนี้ เราได้เรียนรู้การทำ Dependencies ระหว่าง Component ด้วยการใช้ Subcomponent รวมถึงเรียนรู้การใช้ SubComponent Builder เพื่อช่วยในการสร้าง Subcomponent และทำให้ Component และ Subcomponent ไม่ผูกมัดกันมากเกินไป
ส่วนสำคัญที่อยากจะเน้นย้ำของการใช้ subcomponent คือ
- เราไม่จำเป็นต้องประกาศ Object dependencies ที่จะถูกใช้ใน Subcomponent ใน Parent Component เหมือนกับ Component Dependencies แต่ Parent component ต้องประกาศว่ามี Subcomponent อะไรบ้าง
- Subcomponent สามารถเข้าถึงได้ทุก Object dependencies ใน Parent Component ซึ่งรวมถึง Parent ของ Parent Component ด้วย
- Subcomponent มี Parent Component ได้แค่ตัวเดียว
แต่อย่างไรก็ตาม สำหรับ Application ใหญ่ๆ ขอแนะนำให้ใช้ Component with Dependencies มากกว่าการใช้ SubComponent เพราะเบื้องหลังแล้ว Dagger อาจสร้างโค้ดที่ไม่จำเป็นต่อเรา ดังจะเห็นได้จากตัวอย่างที่ Subcomponent สามารถเข้าถึงได้ทุก Object dependencies ใน Parent Component ซึ่งรวมถึง Parent ของ Parent Component ด้วย ซึ่ง Object dependencies บางตัวเราไม่ได้ต้องการใช้ใน SubComponent การใช้ SubComponent จึงเป็นการหว่านแห และอาจทำให้เกิด Build time ที่เพิ่มขึ้นโดยไม่จำเป็น
ในบทความหน้าเราจะมาพูดถึงวิธีการพิเศษที่ Dagger สร้างขึ้นมา เพื่อรองรับการใช้งาน Dagger ในการพัฒนา Application บน Android โดยเฉพาะ เราจะมาดูกันว่ามันมันจะช่วยให้นักพัฒนา พัฒนา Application ได้ง่ายขึ้นมากน้อยเพียงใด คอยติดตามกันนะครับ
สุดท้ายนี้ถ้าเห็นว่า Blog นี้มีประโยชน์ ช่วยสนับสนุนด้วยการฝากกด ❤️ กด Share ให้กันด้วยนะจ๊ะ จะได้มีกำลังใจในการเขียนบทความต่อๆไป 😄 ขอบคุณครับ