ในบทความที่แล้ว (part 1) เราได้พูดถึงการนำแนวคิดของการทำ Dependency Injection เข้ามาช่วยในการทำ mock data ทำให้เราสามารถพัฒนาแอปฯในระหว่างที่ API ทางฝั่ง backend ยังไม่พร้อมใช้งาน แต่อย่างไรก็ตามโค้ดที่ได้ยังไม่สวยงาม และเสี่ยงต่อการเกิด human error ขึ้นได้ในอนาคต รวมถึงยังมีการปะปนกันระหว่างโค้ดที่ใช้งานจริงและโค้ดที่ใช้เพื่อการทดสอบ
ในบทความนี้ เราจะมาดูกันว่า Product Flavors จะเข้ามาช่วยแก้ปัญหาเหล่านี้ได้อย่างไร โค้ดที่ได้จะแบ่งเป็นสัดส่วนที่ชัดเจนได้มากน้อยแค่ไหน และจะช่วยลดการเกิด human error ได้อย่างไร ไปลุยกันเลยครับ!
Product Flavors (PF)
Product Flavors คือ ความสามารถหนึ่งใน Android ที่ทำงานร่วมกับ Gradle ทำให้เราสามารถแยกประเภทของแอปพลิเคชั่นให้ออกมาเป็นหลายๆเวอร์ชั่นได้ ซึ่งเวอร์ชั่นในทีนี้ไม่ได้หมายถึงเลขเวอร์ชั่นของแอปฯ แต่หมายถึงลักษณะของแอปฯที่มีความแตกต่างกันตามแต่ละจุดประสงค์ของการนำไปใช้งาน เช่น debug version, free version หรือ production version เป็นต้น ซึ่งต่อไปจะขอเรียกเวอร์ชั่นเหล่านี้ว่า Variant โดยเราสามารถกำหนด variant ได้ผ่านไฟล์ build.gradle ที่อยู่ภายใน module app ครับ
ตัวอย่างการกำหนด variant ให้กับโปรเจคด้วย Product Flavors
// add flavors.
productFlavors {
free {
...
}trial {
...
}prod {
...
}
}
ซึ่งเมื่อเราลองกำหนด variant ตามด้านบน Android Studio ก็จะแสดง variant ต่างๆตามที่เรากำหนด ผ่านทางหน้าต่างด้านที่ชื่อว่า build variants ดังรูปด้านล่างครับ
ประโยชน์ของการแยก variant ยังมีอีกมาก หากสนใจก็ลองดูรายละเอียดเพิ่มเติมได้จาก Android Document ครับ ในบทความนี้เราจะนำความสามารถของ Product Flavors มาช่วยในการทำ Mock data และช่วยให้การเข้าถึงแหล่งข้อมูลมีความยืดหยุ่นมากขึ้น ดูเป็นสัดส่วนขึ้นครับ
หลังจากที่เราทำความรู้จักกับ Product Flavors กันไปแล้ว ต่อไปเราจะมาดูกันว่า Product Flavors จะมาช่วยปรับปรุงโค้ดในโปรเจคที่แล้วของเราให้ดีขึ้นได้อย่างไร ไปลุยกันเลยครับ
1. เพิ่ม Product Flavors ใน build.gradle
ในขั้นตอนนี้เราจะแยกโปรเจคของเราเป็น 2 variants โดยจะให้ชื่อว่า mock
และ prod
ให้เราไปที่ไฟล์ build.gradle ในโมดูลแอปฯ จากนั้นให้เพิ่มโค้ดตามโค้ดด้านล่างนี้
...buildTypes {
...
}productFlavors {
mock {
applicationIdSuffix = ".mock"
}
prod {
}
}
...
จากนั้นให้เรากด sync gradle แล้วดูผมลัพธ์ที่หน้าต่าง Build variants ซึ่งควรจะได้ผลลัพธ์ดังนี้ครับ
2.สร้าง Folder Structure ให้กับแต่ละ Variant
หลังจากที่เรากำหนด variant ให้กับโปรเจคของเราเสร็จแล้ว ต่อไปเราจะสร้าง folder structure ให้กับแต่ละ variant ถึงแม้ว่าโค้ดหลักๆที่ใช้ จะอยู่ภายใต้ folder main
แต่เนื่องจากแต่ละ variant มีโค้ด (รวมถึง resourse) บางส่วนที่แตกต่างกัน จึงจำเป็นต้องมีที่อยู่สำหรับโค้ดเหล่านั้นโดยเฉพาะ
ให้เราสร้างโฟลเดอร์ที่ชื่อว่า mock
และ prod
ภายใต้โฟลเดอร์ src
หลังจากนั้น ให้สร้างโฟลเดอร์ java
ภายใต้โฟลเดอร์เหล่านั้นอีกที ซึ่งจะได้หน้าตาโครงสร้างของโฟลเดอร์ประมาณนี้ครับ
3. Restructure files
หลังจากที่เราสร้างโฟลเดอร์สำหรับแต่ละ variant เสร็จแล้วต่อไปเราจะย้ายไฟล์ MockServiceApiImpl.java
ไปไว้ใน variant mock
โดยให้ไฟล์อยู่ภายใต้ package ที่ชื่อเดียวกันกับใน main
ซึ่งในที่นี้คือ com.sample.di.data
สุดท้ายจะได้หน้าตาโครงสร้างโฟลเดอร์ดังนี้
ในขั้นตอนนี้จะเห็นว่าโค้ดได้ถูกแยกเป็นสัดส่วนที่ชัดเจนระหว่างโค้ดที่ใช้เพื่อการทดสอบระบบและโค้ดที่ใช้งานจริง โดยใช้การทำ varaint เป็นตัวช่วย
4. Refactor class Repositories
ต่อไปเราจะปรับปรุง class Repositories
ให้รองรับการแยก variant โดยให้เราแทนที่ class Repositories
เดิมด้วยโค้ดต่อไปนี้
จากโค้ดด้านบนจะเห็นว่า จากเดิมที่ class Repositories
เป็นผู้สร้าง object ServiceApi
ขึ้นมาเอง เราเปลี่ยนให้ Repositories
รับ object ของ ServiceApi
เข้ามาแทน ทำให้การผูกมัดกันระหว่างโค้ดของ Repositories
และ ServiceApi
น้อยลง และยังทำให้โค้ดดูเป็นระเบียบมากขึ้น
5. สร้าง Injection class ในแต่ละ variant
จากเดิมที่การเข้าถึงแหล่งข้อมูลถูกกำหนดโดย class Repositories
ในขั้นตอนนี้เราจะสร้าง class ที่ทำหน้าที่ในการกำหนดการเข้าถึงแหล่งข้อมูลแทน class Repositories
โดยเราจะสร้าง class ที่ชื่อว่า Injection
ไว้ในแต่ละ variant ดังนี้
ให้เราสร้าง class Injection
ใน mock
variant ดังนี้
และสร้าง Injection ใน prod
variant ดังนี้
ซึ่งสุดท้ายหน้าตาโครงสร้างไฟล์เป็นเป็นดังนี้
จากโค้ดด้านบนจะเห็นว่า class Injection
ของทั้งสอง variant ทำหน้าคืนค่า object Repository เหมือนกัน แต่จะแตกต่างกันตรงที่ object Repository ของแต่ละ variant ใช้ Service API ที่แตกต่างกัน โดย Repository ของ mock
จะเป็น Repository ที่รับ MockServiceApi
เข้าไป ส่วน Repository ของ prod
จะเป็น Repository ที่รับ ServiceApi
ซึ่งจะเห็นว่า Class Injection นี้เองที่เป็นกุญแจสำคัญที่ทำหน้าที่เลือกการเข้าถึงแหล่งข้อมูลของแอปฯให้เราตาม variant ที่เรากำหนด
การเรียกใช้งาน
หลังจากที่เราเตรียม class ทุกอย่างไว้แล้ว ต่อไปก็ได้เวลานำไปใช้งานครับ โดยจะขออธิบายจากการทำงานระหว่าง class GroceryActivity
และ GroceryPresenter
อีกเช่นเคย ดังนี้ครับ
ให้เราเปลี่ยนโค้ดตอนสร้าง GroceryPresenter
ที่อยู่ใน class GroceryActivity
เป็นดังนี้
จากโค้ดบรรทัดที่ 12 จะเห็นว่า จากเดิมที่เราสร้าง object ของ repository ด้วยการเรียก class Repositories
เราเปลี่ยนมาเป็นเรียกผ่าน class Injection
แทน โดย object Repository ที่ได้ จะมาจาก class Injection
ที่อยู่ในแต่ละ variant ที่เราเลือก กล่าวคือ หากเราเลือก build ด้วย variant mock
ตัว repository ที่ได้ก็จะมาจาก Injection
ใน variant mock
ในทางกลับกัน หากเรา Build ด้วย variant prod
ตัว repository ที่ได้ก็จะมาจาก Injection
ใน variant prod
นั้นเอง ซึ่งจะเห็นว่าขั้นตอนนี้คือการรวมคุณสมบัติของ Dependency Injection และ ProductFlavors เข้าด้วยกัน ทำให้การสับเปลี่ยนเพื่อเข้าถึงแหล่งข้อมูลสามารถกำหนดได้ด้วยการเลือก variant
สรุป
การนำคุณสมบัติของการทำ Dependency Injection และ ProductFlavors มาใช้ ช่วยให้การทำ mock data ของเราเป็นระบบมากขึ้น เข้าถึงแหล่งข้อมูลที่เรากำหนดได้ง่ายขึ้น และทำให้เราสามารถพัฒนาแอปฯในระหว่างที่ backend ยังไม่พร้อมใช้งานได้สะดวกขึ้น นอกจากนี้ยังมีข้อดีอีกหลายอย่างที่เกิดจากการใช้ DI คู่กับ PF โดยจะขอสรุปออกมาเป็นข้อๆดังนี้
- แยกส่วนของโค้ดที่ใช้ทดสอบระบบและโค้ดที่ใช้งานจริงได้อย่างชัดเจน
- โค้ดมีความยืดหยุ่นมากขึ้น ง่ายต่อการเปลี่ยน tools หรือ library ในอนาคต
- ลดการเกิดการผูกมัดกันระหว่างโค้ด
- ลดการเกิด Human Error ที่เกิดจากการเลือกใช้แหล่งข้อมูลที่ผิดอย่างไม่ได้ตั้งใจ เนื่องจากการเข้าถึงแหล่งข้อมูลถูกกำหนดโดย variant แทนการกำหนดด้วยการแก้ไขโค้ด
- ง่ายต่อการทำการทดสอบระบบ (unit test)
สุดท้ายนี้ หากใครสนใจเอาโค้ดไปลองเล่น ก็สามารถ check out โค้ดจาก Github ได้เลยครับ และเช่นเคย หากบทความนี้มีประโยชน์ ฝากกด ❤ ด้วยนะครับ