Android KTX ส่วนหนึ่งของ Android Jetpack
Android KTX คือชุดส่วนขยาย Kotlin ที่รวมอยู่ใน Android Jetpack และไลบรารี Android อื่นๆ ส่วนขยาย KTX มี Kotlin ที่กระชับ และเป็นสำนวนสำหรับ Jetpack, แพลตฟอร์ม Android และ API อื่นๆ โดยส่วนขยายเหล่านี้ใช้ประโยชน์จากฟีเจอร์หลายอย่างของภาษา Kotlin ซึ่งรวมถึงฟีเจอร์ต่อไปนี้
- ฟังก์ชันของส่วนขยาย
- คุณสมบัติของส่วนขยาย
- Lambda
- พารามิเตอร์ที่มีชื่อ
- ค่าเริ่มต้นของพารามิเตอร์
- โครูทีน
ตัวอย่างเช่น เมื่อทำงานกับ
SharedPreferences
คุณต้อง
สร้างผู้แก้ไข
ก่อนจึงจะแก้ไขข้อมูลค่ากำหนดได้ นอกจากนี้ คุณยังต้องใช้
หรือส่งการเปลี่ยนแปลงเหล่านั้นเมื่อแก้ไขเสร็จแล้ว ดังที่แสดงใน
ตัวอย่างต่อไปนี้
sharedPreferences
.edit() // create an Editor
.putBoolean("key", value)
.apply() // write to disk asynchronously
Lambda ของ Kotlin เหมาะกับกรณีการใช้งานนี้เป็นอย่างยิ่ง ซึ่งจะช่วยให้คุณใช้แนวทางที่กระชับยิ่งขึ้นได้โดยการส่งบล็อกโค้ดเพื่อเรียกใช้หลังจากสร้างเอดิเตอร์แล้ว ให้โค้ดเรียกใช้ แล้วให้ SharedPreferences
API
ใช้การเปลี่ยนแปลงแบบอะตอม
ตัวอย่างฟังก์ชันหลักของ Android KTX
SharedPreferences.edit
ซึ่งเพิ่มฟังก์ชันแก้ไขลงใน SharedPreferences
ฟังก์ชันนี้ใช้อาร์กิวเมนต์แรกเป็นแฟล็ก boolean
ที่ไม่บังคับ ซึ่งระบุว่าจะคอมมิต
หรือใช้การเปลี่ยนแปลงหรือไม่ นอกจากนี้ยังได้รับการดำเนินการที่จะทำในSharedPreferences
เอดิเตอร์ในรูปแบบของ Lambda
// SharedPreferences.edit extension function signature from Android KTX - Core
// inline fun SharedPreferences.edit(
// commit: Boolean = false,
// action: SharedPreferences.Editor.() -> Unit)
// Commit a new value asynchronously
sharedPreferences.edit { putBoolean("key", value) }
// Commit a new value synchronously
sharedPreferences.edit(commit = true) { putBoolean("key", value) }
ผู้โทรเลือกได้ว่าจะยืนยันหรือใช้การเปลี่ยนแปลงหรือไม่ action
Lambda เป็นฟังก์ชันส่วนขยายที่ไม่ระบุชื่อใน SharedPreferences.Editor
ซึ่งจะแสดงผล Unit
ตามที่ระบุไว้ในลายเซ็น ด้วยเหตุนี้ คุณจึงทำงานในบล็อก
ได้โดยตรงใน SharedPreferences.Editor
สุดท้าย ลายเซ็น SharedPreferences.edit()
มีคีย์เวิร์ด inline
คีย์เวิร์ดนี้จะบอกคอมไพเลอร์ Kotlin ว่าควรคัดลอกและวาง (หรืออินไลน์) ไบต์โค้ดที่คอมไพล์แล้วสำหรับฟังก์ชันทุกครั้งที่มีการใช้ฟังก์ชัน
วิธีนี้จะช่วยหลีกเลี่ยงค่าใช้จ่ายในการสร้างอินสแตนซ์ของคลาสใหม่สำหรับทุกครั้งที่เรียกใช้ฟังก์ชันนี้action
รูปแบบการส่งโค้ดโดยใช้ Lambda การใช้ค่าเริ่มต้นที่เหมาะสมซึ่งสามารถ
ลบล้างได้ และการเพิ่มลักษณะการทำงานเหล่านี้ลงใน API ที่มีอยู่โดยใช้inline
ฟังก์ชันส่วนขยายเป็นลักษณะทั่วไปของการเพิ่มประสิทธิภาพที่ไลบรารี Android KTX
มีให้
ใช้ Android KTX ในโปรเจ็กต์
หากต้องการเริ่มใช้ Android KTX ให้เพิ่มการอ้างอิงต่อไปนี้ลงในไฟล์
build.gradle
ของโปรเจ็กต์
Groovy
repositories { google() }
Kotlin
repositories { google() }
โมดูล AndroidX
Android KTX จัดระเบียบเป็นโมดูล โดยแต่ละโมดูลจะมีแพ็กเกจอย่างน้อย 1 แพ็กเกจ
คุณต้องรวมการอ้างอิงสำหรับอาร์ติแฟกต์ของแต่ละโมดูลไว้ในไฟล์
build.gradle
ของแอป อย่าลืมต่อท้ายหมายเลขเวอร์ชันเข้ากับอาร์ติแฟกต์
คุณดูหมายเลขเวอร์ชันล่าสุดได้ในส่วนที่เกี่ยวข้องของแต่ละอาร์ติแฟกต์
ในหัวข้อนี้
Android KTX มีโมดูลหลักเดียวที่ให้ส่วนขยาย Kotlin สำหรับ API ของเฟรมเวิร์กทั่วไปและส่วนขยายเฉพาะโดเมนหลายรายการ
อาร์ติแฟกต์โมดูล KTX ทั้งหมดจะแทนที่การอ้างอิง Java พื้นฐานในไฟล์ build.gradle
ยกเว้นโมดูลหลัก เช่น คุณสามารถ
แทนที่การอ้างอิง androidx.fragment:fragment
ด้วย
androidx.fragment:fragment-ktx
ไวยากรณ์นี้ช่วยให้จัดการการควบคุมเวอร์ชันได้ดียิ่งขึ้นและไม่เพิ่มข้อกำหนดในการประกาศการขึ้นต่อกันเพิ่มเติม
Core KTX
โมดูล KTX หลักมีส่วนขยายสำหรับไลบรารีทั่วไปที่เป็นส่วนหนึ่งของเฟรมเวิร์ก Android
ไลบรารีเหล่านี้ไม่มีทรัพยากร Dependency ที่อิงตาม Java ซึ่งคุณต้องเพิ่มลงใน build.gradle
หากต้องการรวมโมดูลนี้ ให้เพิ่มค่าต่อไปนี้ลงในไฟล์ build.gradle
ของแอป
Groovy
dependencies { implementation "androidx.core:core-ktx:1.16.0" }
Kotlin
dependencies { implementation("androidx.core:core-ktx:1.16.0") }
รายการแพ็กเกจที่มีอยู่ในโมดูล KTX หลักมีดังนี้
- androidx.core.animation
- androidx.core.content
- androidx.core.content.res
- androidx.core.database
- androidx.core.database.sqlite
- androidx.core.graphics
- androidx.core.graphics.drawable
- androidx.core.location
- androidx.core.net
- androidx.core.os
- androidx.core.text
- androidx.core.transition
- androidx.core.util
- androidx.core.view
- androidx.core.widget
Collection KTX
ส่วนขยายคอลเล็กชันมีฟังก์ชันยูทิลิตีสำหรับการทำงานกับไลบรารีคอลเล็กชันที่มีประสิทธิภาพด้านหน่วยความจำของ Android ซึ่งรวมถึง ArrayMap
, LongSparseArray
, LruCache
และอื่นๆ
หากต้องการใช้โมดูลนี้ ให้เพิ่มค่าต่อไปนี้ลงในไฟล์ build.gradle
ของแอป
Groovy
dependencies { implementation "androidx.collection:collection-ktx:1.5.0" }
Kotlin
dependencies { implementation("androidx.collection:collection-ktx:1.5.0") }
ส่วนขยายคอลเล็กชันใช้ประโยชน์จากการโอเวอร์โหลดตัวดำเนินการของ Kotlin เพื่อ ลดความซับซ้อนของสิ่งต่างๆ เช่น การต่อกันของคอลเล็กชัน ดังตัวอย่างต่อไปนี้
// Combine 2 ArraySets into 1.
val combinedArraySet = arraySetOf(1, 2, 3) + arraySetOf(4, 5, 6)
// Combine with numbers to create a new sets.
val newArraySet = combinedArraySet + 7 + 8
Fragment KTX
โมดูล Fragment KTX มีส่วนขยายหลายรายการเพื่อลดความซับซ้อนของ Fragment API
หากต้องการรวมโมดูลนี้ ให้เพิ่มค่าต่อไปนี้ลงในไฟล์ build.gradle
ของแอป
ดึงดูด
dependencies { implementation "androidx.fragment:fragment-ktx:1.8.8" }
Kotlin
dependencies { implementation("androidx.fragment:fragment-ktx:1.8.8") }
โมดูล Fragment KTX ช่วยให้คุณลดความซับซ้อนของธุรกรรม Fragment ด้วย Lambda ได้ เช่น
fragmentManager().commit {
addToBackStack("...")
setCustomAnimations(
R.anim.enter_anim,
R.anim.exit_anim)
add(fragment, "...")
}
นอกจากนี้ คุณยังเชื่อมโยงกับ ViewModel
ในบรรทัดเดียวได้โดยใช้ตัวแทนพร็อพเพอร์ตี้ viewModels
และ
activityViewModels
ดังนี้
// Get a reference to the ViewModel scoped to this Fragment
val viewModel by viewModels<MyViewModel>()
// Get a reference to the ViewModel scoped to its Activity
val viewModel by activityViewModels<MyViewModel>()
Lifecycle KTX
Lifecycle KTX กำหนด LifecycleScope
สำหรับออบเจ็กต์ Lifecycle
แต่ละรายการ ระบบจะยกเลิกโครูทีนที่เปิดใช้ในขอบเขตนี้เมื่อทำลาย Lifecycle
คุณเข้าถึง CoroutineScope
ของ Lifecycle
ได้โดยใช้พร็อพเพอร์ตี้ lifecycle.coroutineScope
หรือ lifecycleOwner.lifecycleScope
หากต้องการรวมโมดูลนี้ ให้เพิ่มค่าต่อไปนี้ลงในไฟล์ build.gradle
ของแอป
Groovy
dependencies { implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.9.1" }
Kotlin
dependencies { implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.9.1") }
ตัวอย่างต่อไปนี้แสดงวิธีใช้ lifecycleOwner.lifecycleScope
เพื่อ
สร้างข้อความที่คำนวณล่วงหน้าแบบไม่พร้อมกัน
class MyFragment: Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewLifecycleOwner.lifecycleScope.launch {
val params = TextViewCompat.getTextMetricsParams(textView)
val precomputedText = withContext(Dispatchers.Default) {
PrecomputedTextCompat.create(longTextContent, params)
}
TextViewCompat.setPrecomputedText(textView, precomputedText)
}
}
}
LiveData KTX
เมื่อใช้ LiveData คุณอาจต้องคำนวณค่าแบบไม่พร้อมกัน เช่น คุณอาจต้องการดึงค่ากำหนดของผู้ใช้และแสดงใน UI ของคุณ ในกรณีเหล่านี้ LiveData KTX มีliveData
ฟังก์ชันบิลเดอร์ที่
เรียกใช้suspend
ฟังก์ชันและแสดงผลลัพธ์เป็นออบเจ็กต์ LiveData
หากต้องการรวมโมดูลนี้ ให้เพิ่มค่าต่อไปนี้ลงในไฟล์ build.gradle
ของแอป
Groovy
dependencies { implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.9.1" }
Kotlin
dependencies { implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.9.1") }
ในตัวอย่างต่อไปนี้ loadUser()
คือฟังก์ชันระงับที่ประกาศไว้ที่อื่น
คุณสามารถใช้ฟังก์ชันตัวสร้าง liveData
เพื่อเรียก loadUser()
แบบไม่พร้อมกัน
แล้วใช้ emit()
เพื่อส่งผลลัพธ์ได้
val user: LiveData<User> = liveData {
val data = database.loadUser() // loadUser is a suspend function.
emit(data)
}
ดูข้อมูลเพิ่มเติมเกี่ยวกับการใช้โครูทีนกับ LiveData
ได้ที่
ใช้โครูทีน Kotlin กับคอมโพเนนต์สถาปัตยกรรม
Navigation KTX
คอมโพเนนต์แต่ละรายการของไลบรารีการนำทางมี KTX เวอร์ชันของตัวเองซึ่งปรับ API ให้กระชับและเป็นไปตามรูปแบบของ Kotlin มากขึ้น
หากต้องการรวมโมดูลเหล่านี้ ให้เพิ่มค่าต่อไปนี้ลงในไฟล์ build.gradle
ของแอป
Groovy
dependencies { implementation "androidx.navigation:navigation-runtime-ktx:2.9.1" implementation "androidx.navigation:navigation-fragment-ktx:2.9.1" implementation "androidx.navigation:navigation-ui-ktx:2.9.1" }
Kotlin
dependencies { implementation("androidx.navigation:navigation-runtime-ktx:2.9.1") implementation("androidx.navigation:navigation-fragment-ktx:2.9.1") implementation("androidx.navigation:navigation-ui-ktx:2.9.1") }
ใช้ฟังก์ชันส่วนขยายและการมอบสิทธิ์พร็อพเพอร์ตี้เพื่อเข้าถึงอาร์กิวเมนต์ปลายทาง และไปยังปลายทาง ดังที่แสดงในตัวอย่างต่อไปนี้
class MyDestination : Fragment() {
// Type-safe arguments are accessed from the bundle.
val args by navArgs<MyDestinationArgs>()
...
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
view.findViewById<Button>(R.id.next)
.setOnClickListener {
// Fragment extension added to retrieve a NavController from
// any destination.
findNavController().navigate(R.id.action_to_next_destination)
}
}
...
}
Palette KTX
โมดูล Palette KTX มีฟีเจอร์รองรับ Kotlin ที่เป็นสำนวนสำหรับการทำงานกับชุดสี
หากต้องการใช้โมดูลนี้ ให้เพิ่มค่าต่อไปนี้ลงในไฟล์ build.gradle
ของแอป
ดึงดูด
dependencies { implementation "androidx.palette:palette-ktx:1.0.0" }
Kotlin
dependencies { implementation("androidx.palette:palette-ktx:1.0.0") }
ตัวอย่างเช่น เมื่อทำงานกับอินสแตนซ์ Palette
คุณสามารถเรียกข้อมูลตัวอย่าง selected
สำหรับ target
ที่กำหนดได้โดยใช้โอเปอเรเตอร์ get ([ ]
) ดังนี้
val palette = Palette.from(bitmap).generate()
val swatch = palette[target]
Reactive Streams KTX
โมดูล Reactive Streams KTX ช่วยให้คุณสร้างสตรีมที่สังเกตได้LiveData
จาก
ReactiveStreams
ผู้เผยแพร่โฆษณา
หากต้องการรวมโมดูลนี้ ให้เพิ่มค่าต่อไปนี้ลงในไฟล์ build.gradle
ของแอป
Groovy
dependencies { implementation "androidx.lifecycle:lifecycle-reactivestreams-ktx:2.9.1" }
Kotlin
dependencies { implementation("androidx.lifecycle:lifecycle-reactivestreams-ktx:2.9.1") }
ตัวอย่างเช่น สมมติว่ามีฐานข้อมูลที่มีรายชื่อผู้ใช้จำนวนเล็กน้อย ในแอป คุณ
โหลดฐานข้อมูลลงในหน่วยความจำ แล้วแสดงข้อมูลผู้ใช้ใน UI คุณอาจใช้ RxJava เพื่อให้บรรลุเป้าหมายนี้
คอมโพเนนต์ Room
Jetpack สามารถดึงข้อมูล
รายชื่อผู้ใช้เป็น Flowable
ได้ ในสถานการณ์นี้ คุณต้องจัดการการสมัครใช้บริการ Rx
publisher ตลอดอายุของ Fragment หรือกิจกรรมด้วย
แต่เมื่อใช้ LiveDataReactiveStreams
คุณจะได้รับประโยชน์จาก RxJava และชุดโอเปอเรเตอร์ที่หลากหลาย รวมถึงความสามารถในการกำหนดเวลางาน ขณะเดียวกันก็ยังคงใช้ LiveData
ที่ใช้งานง่ายได้ด้วย ดังตัวอย่างต่อไปนี้
val fun getUsersLiveData() : LiveData<List<User>> {
val users: Flowable<List<User>> = dao.findUsers()
return LiveDataReactiveStreams.fromPublisher(users)
}
Room KTX
ส่วนขยาย Room เพิ่มการรองรับโครูทีนสำหรับธุรกรรมในฐานข้อมูล
หากต้องการใช้โมดูลนี้ ให้เพิ่มค่าต่อไปนี้ลงในไฟล์ build.gradle
ของแอป
Groovy
dependencies { implementation "androidx.room:room-ktx:2.7.2" }
Kotlin
dependencies { implementation("androidx.room:room-ktx:2.7.2") }
ต่อไปนี้เป็นตัวอย่าง 2 รายการที่ Room ใช้โครูทีน ตัวอย่างแรก
ใช้ฟังก์ชัน suspend
เพื่อแสดงผลรายการออบเจ็กต์ User
ส่วนตัวอย่างที่สอง
ใช้ Flow
ของ Kotlin
เพื่อแสดงผลรายการ User
แบบไม่พร้อมกัน โปรดทราบว่าเมื่อใช้ Flow
คุณจะได้รับแจ้ง
เกี่ยวกับการเปลี่ยนแปลงในตารางที่คุณกําลังค้นหาด้วย
@Query("SELECT * FROM Users")
suspend fun getUsers(): List<User>
@Query("SELECT * FROM Users")
fun getUsers(): Flow<List<User>>
SQLite KTX
ส่วนขยาย SQLite จะรวมโค้ดที่เกี่ยวข้องกับ SQL ไว้ในธุรกรรม ซึ่งช่วยลดโค้ด บอยเลอร์เพลตได้เป็นจำนวนมาก
หากต้องการใช้โมดูลนี้ ให้เพิ่มค่าต่อไปนี้ลงในไฟล์ build.gradle
ของแอป
ดึงดูด
dependencies { implementation "androidx.sqlite:sqlite-ktx:2.5.2" }
Kotlin
dependencies { implementation("androidx.sqlite:sqlite-ktx:2.5.2") }
ตัวอย่างการใช้ส่วนขยาย transaction
เพื่อทำธุรกรรมในฐานข้อมูลมีดังนี้
db.transaction {
// insert data
}
ViewModel KTX
ไลบรารี ViewModel KTX มีviewModelScope()
ฟังก์ชันที่ช่วยให้เปิดใช้โครูทีนจาก ViewModel
ได้ง่ายขึ้น CoroutineScope
เชื่อมโยงกับ Dispatchers.Main
และจะถูกยกเลิกโดยอัตโนมัติ
เมื่อล้าง ViewModel
แล้ว คุณใช้ viewModelScope()
แทนการสร้างขอบเขตใหม่สำหรับแต่ละ ViewModel
ได้
หากต้องการรวมโมดูลนี้ ให้เพิ่มค่าต่อไปนี้ลงในไฟล์ build.gradle
ของแอป
Groovy
dependencies { implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.9.1" }
Kotlin
dependencies { implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.9.1") }
ตัวอย่างเช่น ฟังก์ชัน viewModelScope()
ต่อไปนี้จะเปิดใช้โครูทีน
ที่ส่งคำขอเครือข่ายในเธรดเบื้องหลัง ไลบรารีจะจัดการการตั้งค่าทั้งหมด
และการล้างขอบเขตที่เกี่ยวข้อง
class MainViewModel : ViewModel() {
// Make a network request without blocking the UI thread
private fun makeNetworkRequest() {
// launch a coroutine in viewModelScope
viewModelScope.launch {
remoteApi.slowFetch()
...
}
}
// No need to override onCleared()
}
WorkManager KTX
WorkManager KTX รองรับโครูทีนอย่างเต็มที่
หากต้องการรวมโมดูลนี้ ให้เพิ่มค่าต่อไปนี้ลงในไฟล์ build.gradle
ของแอป
ดึงดูด
dependencies { implementation "androidx.work:work-runtime-ktx:2.10.2" }
Kotlin
dependencies { implementation("androidx.work:work-runtime-ktx:2.10.2") }
ตอนนี้คุณสามารถขยาย CoroutineWorker
แทนการขยาย Worker
ได้แล้ว
ซึ่งมี API ที่แตกต่างกันเล็กน้อย เช่น หากต้องการสร้างCoroutineWorker
อย่างง่ายเพื่อดำเนินการบางอย่างในเครือข่าย คุณสามารถทำดังนี้
class CoroutineDownloadWorker(context: Context, params: WorkerParameters)
: CoroutineWorker(context, params) {
override suspend fun doWork(): Result = coroutineScope {
val jobs = (0 until 100).map {
async {
downloadSynchronously("https://www.google.com")
}
}
// awaitAll will throw an exception if a download fails, which
// CoroutineWorker will treat as a failure
jobs.awaitAll()
Result.success()
}
}
ดูข้อมูลเพิ่มเติมเกี่ยวกับการใช้ CoroutineWorker
ได้ที่
การใช้ Thread ใน CoroutineWorker
นอกจากนี้ WorkManager KTX ยังเพิ่มฟังก์ชันส่วนขยายไปยัง Operations
และ
ListenableFutures
เพื่อระงับโครูทีนปัจจุบัน
ตัวอย่างต่อไปนี้จะระงับ Operation
ที่ enqueue()
แสดงผล
// Inside of a coroutine...
// Run async operation and suspend until completed.
WorkManager.getInstance()
.beginWith(longWorkRequest)
.enqueue().await()
// Resume after work completes...
โมดูล KTX อื่นๆ
นอกจากนี้ คุณยังรวมโมดูล KTX เพิ่มเติมที่อยู่นอก AndroidX ได้ด้วย
Firebase KTX
SDK บางรายการของ Firebase สำหรับ Android มีไลบรารีส่วนขยาย Kotlin ที่ช่วยให้คุณเขียนโค้ด Kotlin ที่เป็นสำนวนเมื่อใช้ Firebase ในแอปได้ ดูข้อมูลเพิ่มเติมได้ในหัวข้อต่อไปนี้
Google Maps Platform KTX
มีส่วนขยาย KTX สำหรับ Google Maps Platform Android SDK ซึ่งช่วยให้คุณใช้ประโยชน์จากฟีเจอร์ภาษา Kotlin หลายอย่างได้ เช่น ฟังก์ชันส่วนขยาย พารามิเตอร์ที่มีชื่อและอาร์กิวเมนต์เริ่มต้น การประกาศการแยกโครงสร้าง และโครูทีน ดูข้อมูลเพิ่มเติมได้ที่หัวข้อต่อไปนี้
Play Core KTX
Play Core KTX เพิ่มการรองรับโครูทีน Kotlin สำหรับคำขอแบบครั้งเดียวและ Flow
สำหรับการตรวจสอบการอัปเดตสถานะโดยการเพิ่มฟังก์ชันส่วนขยายไปยัง
SplitInstallManager
และ AppUpdateManager
ในไลบรารี Play Core
หากต้องการรวมโมดูลนี้ ให้เพิ่มค่าต่อไปนี้ลงในไฟล์ build.gradle
ของแอป
Groovy
dependencies { implementation "com.google.android.play:core-ktx:1.8.1" }
Kotlin
dependencies { implementation("com.google.android.play:core-ktx:1.8.1") }
ตัวอย่าง Flow
สำหรับการตรวจสอบสถานะมีดังนี้
// Inside of a coroutine...
// Request in-app update status updates.
manager.requestUpdateFlow().collect { updateResult ->
when (updateResult) {
is AppUpdateResult.Available -> TODO()
is AppUpdateResult.InProgress -> TODO()
is AppUpdateResult.Downloaded -> TODO()
AppUpdateResult.NotAvailable -> TODO()
}
}
ข้อมูลเพิ่มเติม
ดูข้อมูลเพิ่มเติมเกี่ยวกับ Android KTX ได้ในวิดีโอ DevBytes
หากต้องการรายงานปัญหาหรือแนะนำฟีเจอร์ ให้ใช้เครื่องมือติดตามปัญหาของ Android KTX