Koin คือเฟรมเวิร์ก DI สำหรับ Kotlin ที่ทำงานโดยไม่ต้องสร้างโค้ด ใช้รีเฟลกชันหรือคำอธิบายประกอบ ไลบรารีใช้ DSL เพื่ออธิบายโมดูลและฉีด dependencies ผ่านคอนเทนเนอร์น้ำหนักเบาที่รองรับ Android, Ktor และ Multiplatform ตาม เอกสารทางการของ Koin เฟรมเวิร์กมีโมดูล สโคป และการสนับสนุนในตัวสำหรับ Jetpack Compose ด้วย boilerplate น้อยที่สุด
ประเด็นสำคัญ
Koin คือเฟรมเวิร์ก DI สำหรับ Kotlin ที่เขียนด้วยภาษาบริสุทธิ์โดยไม่ต้องใช้รีเฟลกชัน คำอธิบายประกอบหรือการสร้างโค้ด แตกต่างจาก Dagger Hilt ที่ต้องใช้ตัวประมวลผลคำอธิบายประกอบและการสร้างโค้ดในขณะคอมไพล์ Koin ทำงานเฉพาะในรันไทม์โดยใช้ DSL น้ำหนักเบาเพื่ออธิบายโมดูล
แนวคิดหลักของ Koin คือการให้ API ที่เรียบง่ายสำหรับการลงทะเบียนและแก้ไข dependencies โดยไม่ต้องเรียนรู้แนวคิดที่ซับซ้อนเกี่ยวกับกราฟ dependencies และแผนภูมิคอมโพเนนต์ นักพัฒนาอธิบายว่าคลาสใดบ้างที่พร้อมใช้งานสำหรับคอนเทนเนอร์ และ Koin จะฉีด dependencies เหล่านั้นโดยอัตโนมัติผ่าน คอนสตรักเตอร์ หรือตัวแทนแบบขี้เกียจ by inject เฟรมเวิร์กเข้ากันได้อย่างสมบูรณ์กับ Kotlin Multiplatform ทำให้สามารถใช้วิธีการ DI แบบเดียวกันบน Android, iOS และฝั่งเซิร์ฟเวอร์
ตามการสำรวจของ Kotlin Developers Community (2025) Koin ถูกใช้ใน 31% ของโปรเจกต์ Android เชิงพาณิชย์ รองจาก Hilt (47%) เท่านั้น เหตุผลหลักในการเลือกคือการตั้งค่าง่ายและไม่จำเป็นต้องสร้างโค้ด ซึ่งช่วยเร่งการคอมไพล์โปรเจกต์
เลือก Koin สำหรับโปรเจกต์ขนาดกลางและใหญ่ที่ต้องการการเริ่มต้นพัฒนาที่รวดเร็ว หรือสำหรับโซลูชัน Kotlin Multiplatform ที่ Hilt ไม่พร้อมใช้งานด้วยเหตุผลทางสถาปัตยกรรม
Koin ไม่ใช้รีเฟลกชันหรือการสร้างโค้ด — การลงทะเบียนทั้งหมดสร้างขึ้นบนฟังก์ชัน inline พร้อมชนิด reified ที่แทนที่ชนิดที่เป็นรูปธรรมลงในเนื้อหาของฟังก์ชันในขณะคอมไพล์ สิ่งนี้ทำให้ Koin เป็นหนึ่งในเฟรมเวิร์ก DI ที่เบาที่สุดในแง่ของขนาด APK สุดท้าย: การเพิ่ม Koin ทำให้ขนาดแอปพลิเคชันเพิ่มขึ้นเพียง 100-150 KB ในขณะที่ Dagger Hilt เพิ่มประมาณ 500 KB เนื่องจากโค้ดที่สร้างขึ้น
คอนเทนเนอร์ Koin เริ่มต้นผ่านฟังก์ชัน startKoin ซึ่งรับแลมบ์ดาพร้อมการกำหนดค่า ภายในแลมบ์ดานี้ โมดูลที่มีการลงทะเบียน — ส่วนประกอบหลักของตรรกะ DI — จะถูกอธิบาย
ฟังก์ชัน startKoin สร้างคอนเทนเนอร์ส่วนกลางที่สามารถเข้าถึงได้จากทุกที่ในแอปพลิเคชันผ่าน GlobalContext อย่างไรก็ตามในโปรเจกต์แบบหลายโมดูล แนะนำให้ใช้ KoinApplication เพื่อสร้างคอนเทนเนอร์ที่แยกออกจากกัน ใน Android ใช้ AndroidContext สำหรับการเริ่มต้น ซึ่งผูกกับวงจรชีวิตของ Application โดยอัตโนมัติ โมดูลถูกลงทะเบียนผ่านพารามิเตอร์ modules ซึ่งรับรายการอินสแตนซ์ของ Module
val networkModule = module {
single {
OkHttpClient()
}
single {
Retrofit.Builder()
.baseUrl("https://api.example.com")
.build()
}
}
startKoin {
modules(networkModule)
}
แต่ละโมดูลมีคำจำกัดความผ่าน single (ซิงเกิลตัน) หรือ factory (อินสแตนซ์ใหม่) คำจำกัดความสามารถอ้างอิงถึง dependencies ที่ลงทะเบียนอื่นผ่าน get() ทำให้เกิดกราฟการฉีดโดยไม่ต้องระบุชนิดอย่างชัดเจนและไม่มีโค้ด boilerplate
Koin ใช้ฟังก์ชัน inline พร้อมพารามิเตอร์ reified อย่างแข็งขันสำหรับการอนุมานชนิดจากบริบท ซึ่งช่วยให้เขียนการลงทะเบียนโดยไม่ต้องระบุคลาสอย่างชัดเจน: single { MyService() } จะกำหนดชนิดโดยอัตโนมัติจากค่าที่ส่งคืนของแลมบ์ดา
แตกต่างจาก Dagger ตรงที่ Koin ไม่ตรวจสอบกราฟ dependencies ในขณะคอมไพล์ — ข้อผิดพลาดทั้งหมดจะถูกตรวจพบในรันไทม์เมื่อเข้าถึง dependency ที่ยังไม่ได้รับการแก้ไขครั้งแรก นี่คือการแลกเปลี่ยนที่ทำให้โค้ดง่ายขึ้นอย่างมากและเร่งการคอมไพล์ แต่ต้องมีการครอบคลุมการทดสอบของการกำหนดค่า DI หลายทีมเลือก Koin เนื่องจากความเร็วในการพัฒนาและความเรียบง่าย แม้จะไม่มีการตรวจสอบในขณะคอมไพล์
ใน Koin เวอร์ชัน 3.5 มีการตรวจสอบกราฟในขณะคอมไพล์แบบทดลองผ่านปลั๊กอิน Koin Annotations นักพัฒนาเพิ่มคำอธิบายประกอบ @Module และ @KoinComponent และปลั๊กอินสร้างโค้ดการตรวจสอบที่ทำงานระหว่างการคอมไพล์ อย่างไรก็ตาม ข้อได้เปรียบหลักของ Koin — ไม่มีการสร้างโค้ด — จะหายไปในโหมดนี้ ดังนั้นทีมส่วนใหญ่ยังคงใช้วิธีการ DSL แบบคลาสสิกพร้อมการตรวจสอบรันไทม์ผ่านการทดสอบ
Koin มีหลายวิธีในการฉีด dependencies: by inject(), get() และการส่งผ่านคอนสตรักเตอร์โดยตรง การเลือกขึ้นอยู่กับบริบทการใช้งาน
ตัวแทน by inject เป็นวิธีการฉีดที่พบมากที่สุดใน ViewModel และแฟรกเมนต์ Android dependency จะเริ่มต้นแบบขี้เกียจ — เฉพาะเมื่อมีการเข้าถึงคุณสมบัติครั้งแรกเท่านั้น ซึ่งมีประสิทธิภาพสำหรับบริการที่ใช้ทรัพยากรมากซึ่งอาจไม่จำเป็นทันที
class MainViewModel : ViewModel() {
private val repository: UserRepository by inject()
fun loadUsers() {
repository.fetchAll()
}
}
ฟังก์ชัน get ส่งคืนอินสแตนซ์ dependency ทันที ใช้ภายในแลมบ์ดา factory ระหว่างการลงทะเบียนหรือเมื่อต้องการ dependency ในบริบทแบบซิงโครนัสโดยไม่ต้องเริ่มต้นแบบขี้เกียจ แตกต่างจาก by inject() ตรงที่ get() ไม่รองรับการโหลดแบบขี้เกียจและต้องการให้คอนเทนเนอร์เริ่มต้นแล้วในขณะที่เรียก
สโคป ใน Koin คือกลไกในการผูกอายุของ dependencies เข้ากับคอมโพเนนต์เฉพาะ เช่น Activity, Fragment หรือเซสชันที่กำหนดเอง นี่คือฟังก์ชันหลักสำหรับการจัดการหน่วยความจำในแอปพลิเคชัน Android
ฟังก์ชัน scope ภายในโมดูลจะสร้างสโคปที่มีชีวิตตราบเท่าที่คอมโพเนนต์ที่ผูกไว้ยังมีชีวิตอยู่ dependencies ทั้งหมดที่ลงทะเบียนในสโคปจะถูกทำลายเมื่อปิด ซึ่งป้องกันการรั่วไหลของหน่วยความจำ
val userScope = module {
scope<UserSession> {
scoped {
UserRepository(get())
}
scoped {
SessionManager(get())
}
}
}
ฟังก์ชัน scoped ลงทะเบียน dependency ที่จะมีอยู่ภายในสโคปเท่านั้น เมื่อปิดสโคป ออบเจกต์ scoped ทั้งหมดจะพร้อมใช้งานสำหรับตัวเก็บขยะ
single ลงทะเบียนอินสแตนซ์เดียวสำหรับทั้งแอปพลิเคชันด้วยการเริ่มต้นแบบขี้เกียจ ใช้สำหรับบริการไร้สถานะ: ไคลเอนต์เครือข่าย, แคช, ตัวบันทึก
factory สร้างอินสแตนซ์ใหม่ทุกครั้งที่เรียก get() ใช้กับ ViewModel ที่เก็บข้อมูล และออบเจกต์ที่มีสถานะซึ่งต้องการอินสแตนซ์ใหม่ทุกครั้งที่เข้าถึง
การรวม Koin เข้ากับโปรเจกต์ Android นั้นน้อยมาก: เพียงเพิ่ม dependency ใน build.gradle และเรียก startKoin ใน Application.onCreate Koin มีโมดูลสำหรับการรวมกับ Jetpack Compose, Navigation และ WorkManager ทำให้เป็นทางเลือกที่สมบูรณ์สำหรับ Hilt
ไลบรารีพิเศษ koin-android-compose ช่วยให้สามารถฉีด dependencies โดยตรงในฟังก์ชัน Composable ผ่าน koinViewModel() และ koinInject() สิ่งนี้ช่วยลดความจำเป็นในการส่งคอนเทนเนอร์ผ่านพารามิเตอร์ของแต่ละหน้าจอและทำให้โค้ด ViewModel สะอาดขึ้นผ่านการผูกวงจรชีวิตอัตโนมัติ
ตาม Google I/O 2024 Jetpack Compose กลายเป็นเฟรมเวิร์กหลักสำหรับโปรเจกต์ Android ใหม่ Koin รองรับ Compose โดยกำเนิดโดยไม่ต้องกำหนดค่าเพิ่มเติม ผูกสโคปกับวงจรชีวิต ViewModel โดยอัตโนมัติผ่าน koinViewModel() โดยคำนึงถึง บริบทของ coroutine
สำหรับการทดสอบ Koin มีฟังก์ชัน koinTest และ koinTestRule ซึ่งสร้างคอนเทนเนอร์ทดสอบที่แยกออกมาพร้อมโมดูลทดสอบและปิดโดยอัตโนมัติหลังจากเสร็จสิ้นการทดสอบ สิ่งนี้ช่วยให้การทดสอบแยกออกจากกันและป้องกันการรั่วไหลของสถานะระหว่างกรณีทดสอบ
การรวม Koin กับ Jetpack Navigation ถูกนำไปใช้ผ่านโมดูล koin-androidx-navigation ViewModel ของแต่ละหน้าจอจะได้รับ dependencies โดยอัตโนมัติผ่าน by viewModel() พร้อมการส่ง SavedStateHandle เพื่อรักษาสถานะเมื่อหมุนหน้าจอและกู้คืนหลังจากแอปพลิเคชันถูกระงับ
สำหรับการทดสอบหน่วยของ ViewModel ด้วย Koin ใช้ koinTestRule จากไลบรารี koin-test-junit5 หรือ koin-test-junit4 กฎจะสร้างคอนเทนเนอร์ที่แยกออกมาพร้อมโมดูลทดสอบก่อนการทดสอบแต่ละครั้งและปิดโดยอัตโนมัติหลังจากเสร็จสิ้น ป้องกันการรั่วไหลของสถานะระหว่างกรณีทดสอบ dependencies จริงจะถูกแทนที่ด้วยม็อกผ่าน MockK: โมดูลที่มีการลงทะเบียน single
หนึ่งในคุณสมบัติหลักของ Koin 3.x คือการรองรับ Ktor สำหรับการสร้างแอปพลิเคชันเซิร์ฟเวอร์บน Kotlin และ Compose Multiplatform สำหรับแอปพลิเคชันเดสก์ท็อป สิ่งนี้ทำให้ Koin เป็นเฟรมเวิร์ก DI เพียงตัวเดียวที่ครอบคลุมทั้งสามแพลตฟอร์ม Kotlin โดยไม่ต้องเปลี่ยนกระบวนทัศน์การฉีด โมดูล koin-ktor ช่วยให้สามารถลงทะเบียน dependencies ผ่าน install(Koin) ในบล็อก Application และฉีดบริการเข้าไปใน routes ผ่าน by inject() เช่นเดียวกับใน Android สิ่งนี้ทำให้ Koin เป็นโซลูชัน DI สากลสำหรับโปรเจกต์ Kotlin ทุกสถาปัตยกรรม — จากไคลเอนต์มือถือไปจนถึงแบ็คเอนด์เซิร์ฟเวอร์
การรวม Koin กับ Jetpack Navigation ผ่านโมดูล koin-androidx-navigation ช่วยลดความจำเป็นในการสร้าง ViewModelProvider.Factory ด้วยตนเองสำหรับแต่ละหน้าจอ สำหรับโปรเจกต์แบบหลายโมดูล Koin รองรับการโหลดโมดูลแบบขี้เกียจผ่าน loadKoinModules ซึ่งช่วยให้แต่ละ โมดูลคุณลักษณะ เชื่อมต่อการกำหนดค่า DI ของตัวเองได้อย่างอิสระ
คำถามที่พบบ่อย
Koin ทำงานในรันไทม์โดยไม่ต้องสร้างโค้ดหรือคำอธิบายประกอบ ซึ่งช่วยเร่งการคอมไพล์แต่ไม่ตรวจสอบกราฟ dependencies ในขณะคอมไพล์ Hilt สร้างโค้ดในขณะคอมไพล์และจับข้อผิดพลาด DI ได้เร็วกว่า แต่ต้องมีการตั้งค่าที่ซับซ้อนและทำให้การคอมไพล์ช้าลง
ใช่ Koin รองรับ Kotlin Multiplatform อย่างสมบูรณ์ ไลบรารี koin-core ทำงานบนทุกแพลตฟอร์ม Kotlin ในขณะที่ koin-android และ koin-compose เพิ่มความสามารถเฉพาะแพลตฟอร์มสำหรับ Android และ iOS ตามลำดับ
dependencies แบบ วนซ้ำ ทำให้เกิด StackOverflowError ในรันไทม์ Koin ไม่ตรวจพบโดยอัตโนมัติ วิธีแก้ไขคือการปรับโครงสร้างสถาปัตยกรรม: แยกอินเทอร์เฟซร่วม ใช้รูปแบบ Listener/Observer หรือทำลายวงจรผ่าน factory ด้วยการเริ่มต้นแบบเลื่อนเวลา
ใน Android สโคป สามารถผูกกับวงจรชีวิตของ Activity หรือ Fragment ผ่าน AndroidScope เมื่อคอมโพเนนต์ถูกทำลาย Koin จะปิดสโคปที่เกี่ยวข้องโดยอัตโนมัติ ในสโคปที่กำหนดเอง (เซสชันผู้ใช้) การปิดจะทำด้วยตนเองโดยเรียก scope.close
ใช้ฟังก์ชัน koinTest จากโมดูล koin-test มันสร้างคอนเทนเนอร์ที่แยกออกมาพร้อมโมดูลทดสอบซึ่งจะปิดโดยอัตโนมัติหลังการทดสอบ dependencies จริงจะถูกแทนที่ด้วยม็อกผ่านโมดูลที่ใช้ Mockito หรือ MockK
สรุป
เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร
IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ
อ่านเพิ่มเติม