วันนี้ขอแนะนำให้รู้จักกับเรื่องราวของ Dependency ที่นักพัฒนาแอนดรอยด์หลายๆคนน่าจะคุ้นเคยกันดีอยู่แล้วบน Android Studio

รู้จักกับ Module กันก่อนดีกว่า

เพราะการเขียนโค้ดในสมัยนี้ไม่ได้มีแค่โค้ดระดับหลักพันบรรทัด แต่อาจจะมากถึงหมื่นไปจนถึงล้านบรรทัดก็เป็นได้ เพื่อให้โปรเจคนั้นสามารถตอบโจทย์ในการทำงานได้จริง จึงทำให้โปรเจคใน Android Studio หรือ IntelliJ IDEA รองรับการแยกโค้ดแต่ละชุดให้เป็นอิสระออกจากกันในรูปแบบของ Module โดยในแต่ละโปรเจคจะต้องมีอย่างน้อย 1 Module เสมอ

และเมื่อสร้างโปรเจคสำหรับแอนดรอยด์ขึ้นมา ก็จะมี Module หลักที่ใช้ชื่อว่า app เพื่อทำหน้าที่เป็น Module หลักของโปรเจค

ในกรณีที่มีโค้ดอื่นๆที่ไม่ได้เกี่ยวข้องกับ Module หลักโดยตรง ก็จะนิยมแยกออกมาเป็น Module ย่อยๆ เพื่อแยกโค้ดให้เป็นอิสระต่อจากกัน ไม่ผูกกันจนเกินไป ซึ่งจะเรียกกันว่า Library Module

และนั่นก็คือที่มาของ Dependency

ด้วยแนวคิดดังกล่าวจึงทำให้ Android Studio แยกชุดโค้ดเหล่านั้นให้อยู่ในรูปแบบต่างๆที่เรียกว่า Dependency โดยที่ Library Module ก็เป็นหนึ่งใน Dependency เช่นกัน

ดังนั้น Dependency จึงเป็นการจัดเก็บชุดโค้ดที่แยกย่อยออกมาเพื่อทำงานตามหน้าที่ของตัวเองที่ทุกจำกัดขอบเขตไว้อย่างชัดเจน เพื่อช่วยแยกโค้ดให้เป็นสัดส่วน และนำไปใช้งานซ้ำๆได้ง่าย

โดย Dependency จะมีทั้งหมด 3 รูปแบบด้วยกัน

  • Local Library Module Dependency
  • Local Binary Dependency
  • Remote Binary Dependency

ความแตกต่างของ Dependency จะอยู่ที่ Source ในการเก็บข้อมูลของ Dependency และรูปแบบของข้อมูลที่เก็บชุดโค้ด

Local Library Module Dependency

Library Module ที่สร้างขึ้นมาแล้วนำไปเรียกใช้งานใน App Module จะมีชื่อเรียกแบบเต็มๆว่า Local Library Module Dependency ซึ่งเป็น Dependency ที่ชุดคำสั่งจะอยู่ในรูปของ Source Code ที่นักพัฒนาสามารถเปิดดูและแก้ไขจากในโปรเจคได้ทันที

Dependency แบบนี้มักจะใช้กับโค้ดมีต้องการแก้ไขอยู่ตลอดเวลา อาจจะเกิดขึ้นมาจากการแยกโค้ดใน App Module ออกเป็นหลายๆส่วน เพื่อให้ง่ายต่อการดูแลและแก้ไข หรือเอา Source Code จากที่อื่นมาใส่ในโปรเจคในรูปของ Library Module เพื่อแก้ไขโค้ดบางอย่างให้ตรงกับที่ต้องการนำไปใช้งาน

การเพิ่ม Local Library Module Dependency เข้าไปในโปรเจค จะต้องเพิ่มใน build.gradle โดยกำหนดเป็น project พร้อมกับชื่อ Module

// build.gradle
dependencies {
    /* ... */
    implementation project(':material-ui')
}

โดยจะต้องใส่ : นำหน้าชื่อ Module ด้วยทุกครั้ง

Local Binary Dependency

สำหรับโค้ดที่ผ่านการ Compile ให้อยู่ในรูปของไฟล์ .jar ก็สามารถนำมาใช้งานในโปรเจคได้เช่นกัน โดยจะถือว่าเป็น Local Binary Dependency ซึ่งจะต่างจากแบบก่อนหน้าตรงที่ไม่ได้เป็น Source Code จึงแก้ไขโค้ดที่อยู่ข้างในไม่ได้

การเพิ่ม Local Binary Dependency เข้าไปในโปรเจค จะต้องเพิ่มใน build.gradle โดยกำหนดเป็น files พร้อมกับที่อยู่ของไฟล์นั้นๆ

// build.gradle
dependencies {
    /* ... */
    implementation files('libs/foo.jar', 'libs/bar.jar')
}

ถ้ามี .jar หลายๆไฟล์ สามารถรวมไว้ใน Directory เดียวกัน แล้วกำหนดให้ Gradle เรียกไฟล์ทั้งหมดที่อยู่ในนั้นก็ได้เช่นกัน

// build.gradle
dependencies {
    /* ... */
    implementation fileTree(dir: 'libs', include: ['*.jar'])
}

จากตัวอย่างโค้ดข้างบนจะกำหนดให้ Gradle ไปเรียก .jar ที่อยู่ใน libs ทั้งหมด

ในกรณีที่เป็นไฟล์ .aar จะต้องสร้างเป็น Local Library Module Dependency แทน โดยสามารถดูวิธีสร้างได้ที่ วิธีใช้งาน AAR Library ในโปรเจคแอนดรอยด์ของเรา
วิธีใช้งาน AAR Library ในโปรเจคแอนดรอยด์ของเรา
บ่อยครั้งที่การพัฒนาแอปแอนดรอยด์ก็ต้องพึ่งพาไลบรารีต่างๆมากมาย แต่บางครั้งก็อาจจะต้องใช้ไลบรารีที่เป็นไฟล์ AAR มาใช้ในโปรเจค

Remote Binary Dependency

ในโลก Open-source ที่เปิดกว้างให้นักพัฒนาทั่วโลกสามารถแชร์โค้ดกันได้ ใครที่เขียนโค้ดสำหรับการทำงานบางอย่างก็สามารถสร้างเป็น Open-source Library เพื่อให้นักพัฒนาคนอื่นๆนำไปใช้งานได้

และก็จะมี Server ที่ทำหน้าที่เป็น Repository สำหรับเก็บ Library เหล่านี้ไว้ เพื่อให้นักพัฒนาทั่วโลกสามารถเรียกใช้งาน Library ที่ต้องการได้ทุกที่ทุกเวลาผ่านอินเตอร์เน็ต ซึ่งแต่ละ Server ก็จะมีชื่อเรียกแตกต่างกันออกไป โดย Public Repository สำหรับนักพัฒนาแอนดรอยด์ที่นิยมใช้งานกันก็จะมี JCenter, Maven Central, JitPack และ Google Repository

เมื่อสร้างโปรเจคแอนดรอยด์ขึ้นมาใหม่ในทุกๆครั้งจะกำหนด Repository เป็น JCenter กับ Google Repository ให้โดย Default

ซึ่งข้อดีของ Dependency แบบนี้ก็คือนักพัฒนาไม่ต้องดาวน์โหลดไฟล์เอง แค่กำหนด Artifact ของ Dependency ตัวนั้นๆให้ถูกต้องก็พอ

โดยในการกำหนด Artifact สำหรับ Gradle จะประกอบไปด้วย 3 ส่วนด้วยกันคือ <groupId>:<artifactId>:<version> ยกตัวอย่างเช่น

// build.gradle
dependencies {
    /* ... */
    implementation 'androidx.appcompat:appcompat:1.2.0'
}
  • groupId : androidx.appcompat
  • artifactId : appcompat
  • version : 1.2.0

โดยข้อมูลใน Artifact ของ Dependency แต่ละตัว คนสร้างจะเป็นคนกำหนดเอง หน้าที่ของนักพัฒนาก็คือกำหนดให้ถูกเพื่อให้ Gradle สามารถดาวน์โหลดมาใช้งานได้นั่นเอง

อันไหนเป็น App Module และอันไหนเป็น Library Module ?

วิธีดูว่า Module แต่ละตัวว่าเป็น Module แบบไหนใน Android Studio สามารถดูได้ง่ายๆจาก Plugin ที่กำหนดไว้บรรทัดแรกสุดของ build.gradle ใน Module นั้นๆ

  • apply plugin: 'com.android.application' เป็น App Module
  • apply plugin: 'com.android.library' เป็น Library Module

โดยปกติแล้วในแต่ละโปรเจคจะมี App Module แค่หนึ่งตัวเท่านั้น แต่นักพัฒนาก็สามารถกำหนดให้มีมากกว่าหนึ่งตัวได้เช่นกัน

เช่น ต้องการแยก Build Target ระหว่าง Phone & Table กับ Android TV แต่ตอนสั่ง Build Gradle จะเลือก App Module ได้แค่ทีละตัวเท่านั้น

กำหนด Dependency ผ่านหน้าต่าง Project Structure

นักพัฒนาส่วนใหญ่จะกำหนด Dependency ใน build.gradle โดยตรง แต่สำหรับนักพัฒนาบางคนที่ไม่อยากแก้ไขด้วยโค้ด Groovy โดยตรง ก็สามารถเปิดหน้าต่าง Project Structure ขึ้นมาแทนก็ได้

จะมีเมนูที่ชื่อว่า Dependencies เพื่อให้นักพัฒนาสามารถกำหนด Dependency ที่ต้องการได้ตามใจชอบ

ชื่อเรียกของ Dependency แต่ละประเภทจะแตกต่างกับที่เจ้าของบล็อกพูดถึงนิดหน่อย

  • Module Dependency → Local Library Module Dependency
  • Jar Dependency → Local Binary Dependency
  • Library Dependency → Remote Binary Dependency

Dependency แต่ละตัวก็สามารถมี Dependency เป็นของตัวเองได้

ไม่ใช่แค่ App Module เท่านั้นที่จะมี Dependency เป็นของตัวเอง แต่ว่า Dependency แต่ละตัวก็สามารถมี Dependency ข้างในได้เช่นกัน ดังนั้นจึงเป็นเรื่องปกติที่จะมี Nested Dependency มากมายในโปรเจคแต่ละตัว

เส้นโยงความสัมพันธ์ของ Dependency ทั้งหมดจะถูกเรียกว่า Dependency Graph

บ่อยครั้งที่จะเจอ Dependency ที่ถูกเรียกจาก Dependency เป็นจำนวนหลายตัว แต่สิ่งที่นักพัฒนาต้องพึงระวังคือ

  • Dependency Conflict ที่เกิดมาจาก Dependency ตัวเดียวกันแต่มีเวอร์ชันที่ต่างกันจนเกินไปและมีโค้ดข้างในขัดแย้งกันเอง
  • Dependency Loop ที่เกิดมาจาก Dependency ถูกเรียกใช้วนกันไปมา

สรุป

ในปัจจุบันการเรียกใช้งาน Dependency หรือ Library ถือว่าเป็นสิ่งที่ขาดไปไม่ได้สำหรับการพัฒนาแอปแอนดรอยด์ในยุคนี้แล้วก็ว่าได้ เพราะว่าระยะเวลาในการพัฒนานั้นเป็นหนึ่งในปัจจัยสำคัญในการแข่งขันกับตลาด การเขียนโค้ดเองทั้งหมดอาจจะดีตรงที่นักพัฒนาสามารถแก้ไขและควบคุมปัญหาที่อาจจะเกิดจากโค้ดได้ แต่ทว่าถ้าต้องแข่งขันกับเวลาแล้ว การนำ Open-source Library ที่มีอยู่มาแล้วมาใช้งานในโปรเจคก็อาจจะเป็นเรื่องที่ดีกว่า