Android KTX   เป็นส่วนหนึ่งของ Android Jetpack

Android KTX คือชุดส่วนขยาย Kotlin ที่รวมอยู่ใน Android Jetpack และไลบรารี Android อื่นๆ ส่วนขยาย KTX มี Kotlin ที่กระชับและเป็นสำนวนสำหรับ Jetpack, แพลตฟอร์ม Android และ API อื่นๆ โดยส่วนขยายเหล่านี้จะใช้ประโยชน์จากฟีเจอร์ต่างๆ ของภาษา Kotlin ซึ่งรวมถึงฟีเจอร์ต่อไปนี้

  • ฟังก์ชันส่วนขยาย
  • คุณสมบัติของส่วนขยาย
  • แลมบ์ดา
  • พารามิเตอร์ที่มีชื่อ
  • ค่าเริ่มต้นของพารามิเตอร์
  • โครูทีน

ตัวอย่างเช่น เมื่อทำงานกับ SharedPreferences คุณต้องสร้างเครื่องมือแก้ไขก่อนจึงจะแก้ไขข้อมูลค่ากำหนดได้ นอกจากนี้ คุณต้องใช้หรือคอมมิตการเปลี่ยนแปลงเหล่านั้นเมื่อแก้ไขเสร็จแล้ว ดังที่แสดงในตัวอย่างต่อไปนี้

sharedPreferences
        .edit()  // create an Editor
        .putBoolean("key", value)
        .apply() // write to disk asynchronously

Kotlin Lambda เหมาะอย่างยิ่งสำหรับกรณีการใช้งานนี้ ซึ่งช่วยให้คุณใช้วิธีที่กระชับมากขึ้นได้โดยการส่งบล็อกโค้ดเพื่อเรียกใช้หลังจากสร้างเครื่องมือแก้ไข ปล่อยให้โค้ดทำงาน แล้วปล่อยให้ SharedPreferences API ใช้การเปลี่ยนแปลงแบบแยกส่วน

นี่คือตัวอย่างของฟังก์ชัน Android KTX Core SharedPreferences.edit ซึ่งเพิ่มฟังก์ชันแก้ไขลงใน SharedPreferences ฟังก์ชันนี้ใช้แฟล็ก boolean ที่ไม่บังคับเป็นอาร์กิวเมนต์แรกซึ่งระบุว่าจะคอมมิตหรือใช้การเปลี่ยนแปลง นอกจากนี้ยังได้รับการดำเนินการเพื่อดำเนินการกับเครื่องมือแก้ไข SharedPreferences ในรูปแบบของแลมบ์ดาด้วย

// 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 ว่าควรคัดลอกและวาง (หรือ inline) ไบต์โค้ดที่คอมไพล์แล้วสำหรับฟังก์ชันทุกครั้งที่มีการใช้ฟังก์ชัน วิธีนี้จะช่วยหลีกเลี่ยงค่าใช้จ่ายในการสร้างอินสแตนซ์คลาสใหม่สำหรับ action ทุกครั้งที่มีการเรียกใช้ฟังก์ชันนี้

รูปแบบการส่งโค้ดโดยใช้ Lambda, การใช้ค่าเริ่มต้นที่เหมาะสมซึ่งสามารถลบล้างได้ และการเพิ่มลักษณะการทำงานเหล่านี้ลงใน API ที่มีอยู่โดยใช้ฟังก์ชันส่วนขยาย inline เป็นลักษณะทั่วไปของการเพิ่มประสิทธิภาพที่ไลบรารี Android KTX มีให้

ใช้ Android KTX ในโปรเจ็กต์

หากต้องการเริ่มใช้ Android KTX ให้เพิ่มทรัพยากร Dependency ต่อไปนี้ลงในไฟล์ build.gradle ของโปรเจ็กต์

ดึงดูด

repositories {
    google()
}

Kotlin

repositories {
    google()
}

โมดูล AndroidX

Android KTX จัดระเบียบเป็นโมดูล โดยแต่ละโมดูลจะมีแพ็กเกจอย่างน้อย 1 รายการ

คุณต้องรวมทรัพยากร Dependency สำหรับอาร์ติแฟกต์ของแต่ละโมดูลไว้ในไฟล์ build.gradle ของแอป อย่าลืมใส่หมายเลขเวอร์ชันต่อท้ายอาร์ติแฟกต์ คุณดูหมายเลขเวอร์ชันล่าสุดได้ในส่วนที่เกี่ยวข้องของอาร์ติแฟกต์แต่ละรายการในหัวข้อนี้

Android KTX มีโมดูลหลักเดียวที่ให้ส่วนขยาย Kotlin สำหรับ API เฟรมเวิร์กทั่วไปและส่วนขยายเฉพาะโดเมนหลายรายการ

อาร์ติแฟกต์โมดูล KTX ทั้งหมดจะแทนที่ทรัพยากร Dependency ของ Java ที่สำคัญในไฟล์ build.gradle ยกเว้นโมดูลหลัก เช่น คุณสามารถแทนที่ทรัพยากร Dependency ของ androidx.fragment:fragment ด้วย androidx.fragment:fragment-ktx ไวยากรณ์นี้ช่วยให้จัดการการกำหนดเวอร์ชันได้ดียิ่งขึ้น และไม่เพิ่มข้อกำหนดในการประกาศทรัพยากร Dependency เพิ่มเติม

Core KTX

โมดูล Core KTX มีส่วนขยายสำหรับไลบรารีทั่วไปที่เป็นส่วนหนึ่งของเฟรมเวิร์ก Android ไลบรารีเหล่านี้ไม่มีทรัพยากร Dependency ที่ใช้ Java ซึ่งคุณต้องเพิ่มลงใน build.gradle

หากต้องการรวมโมดูลนี้ ให้เพิ่มค่าต่อไปนี้ลงในไฟล์ build.gradle ของแอป

Groovy

dependencies {
    implementation "androidx.core:core-ktx:1.19.1"
}

Kotlin

dependencies {
    implementation("androidx.core:core-ktx:1.19.1")
}

รายการแพ็กเกจที่อยู่ในโมดูล Core KTX มีดังนี้

Collection KTX

ส่วนขยายคอลเล็กชันมีฟังก์ชันยูทิลิตีสำหรับการทำงานกับไลบรารีคอลเล็กชันที่ใช้หน่วยความจำอย่างมีประสิทธิภาพของ Android ซึ่งรวมถึง ArrayMap, LongSparseArray, LruCache และอื่นๆ

หากต้องการใช้โมดูลนี้ ให้เพิ่มโค้ดต่อไปนี้ลงในไฟล์ build.gradle ของแอป

Groovy

dependencies {
    implementation "androidx.collection:collection-ktx:1.6.0"
}

Kotlin

dependencies {
    implementation("androidx.collection:collection-ktx:1.6.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.9.1"
}

Kotlin

dependencies {
    implementation("androidx.fragment:fragment-ktx:1.9.1")
}

โมดูล 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.11.0"
}

Kotlin

dependencies {
    implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.11.0")
}

ตัวอย่างต่อไปนี้แสดงวิธีใช้ 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 จะมีฟังก์ชัน Builder liveData ที่เรียกใช้ฟังก์ชัน suspend และแสดงผลลัพธ์เป็นออบเจ็กต์ LiveData

หากต้องการรวมโมดูลนี้ ให้เพิ่มค่าต่อไปนี้ลงในไฟล์ build.gradle ของแอป

Groovy

dependencies {
    implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.11.0"
}

Kotlin

dependencies {
    implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.11.0")
}

ในตัวอย่างต่อไปนี้ loadUser() คือฟังก์ชันระงับที่ประกาศไว้ที่อื่น คุณสามารถใช้ฟังก์ชัน liveData builder เพื่อเรียก loadUser() แบบอะซิงโครนัส แล้วใช้ emit() เพื่อแสดงผลลัพธ์ได้ ดังนี้

val user: LiveData<User> = liveData {
    val data = database.loadUser() // loadUser is a suspend function.
    emit(data)
}

ดูข้อมูลเพิ่มเติมเกี่ยวกับการใช้โครูทีนกับ LiveData ได้ที่ใช้โครูทีน Kotlin กับคอมโพเนนต์สถาปัตยกรรม

คอมโพเนนต์แต่ละรายการของไลบรารีการนำทางมีเวอร์ชัน KTX ของตัวเองซึ่งปรับ API ให้กระชับและเป็นสำนวน Kotlin มากขึ้น

หากต้องการรวมโมดูลเหล่านี้ ให้เพิ่มค่าต่อไปนี้ลงในไฟล์ build.gradle ของแอป

ดึงดูด

dependencies {
    implementation "androidx.navigation:navigation-runtime-ktx:2.10.2"
    implementation "androidx.navigation:navigation-fragment-ktx:2.10.2"
    implementation "androidx.navigation:navigation-ui-ktx:2.10.2"
}

Kotlin

dependencies {
    implementation("androidx.navigation:navigation-runtime-ktx:2.10.2")
    implementation("androidx.navigation:navigation-fragment-ktx:2.10.2")
    implementation("androidx.navigation:navigation-ui-ktx:2.10.2")
}

ใช้ฟังก์ชันส่วนขยายและการมอบสิทธิ์พร็อพเพอร์ตี้เพื่อเข้าถึงอาร์กิวเมนต์ปลายทางและไปยังปลายทาง ดังที่แสดงในตัวอย่างต่อไปนี้

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.1.0"
}

Kotlin

dependencies {
    implementation("androidx.palette:palette-ktx:1.1.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.11.0"
}

Kotlin

dependencies {
    implementation("androidx.lifecycle:lifecycle-reactivestreams-ktx:2.11.0")
}

ตัวอย่างเช่น สมมติว่ามีฐานข้อมูลที่มีรายชื่อผู้ใช้จำนวนน้อย ในแอป คุณจะโหลดฐานข้อมูลลงในหน่วยความจำ แล้วแสดงข้อมูลผู้ใช้ใน UI คุณอาจใช้ RxJava เพื่อให้บรรลุเป้าหมายนี้ คอมโพเนนต์ Jetpack Room สามารถดึงข้อมูลรายชื่อผู้ใช้เป็น Flowable ได้ ในสถานการณ์นี้ คุณต้องจัดการการสมัครใช้บริการของผู้เผยแพร่ Rx ตลอดอายุของ Fragment หรือกิจกรรมด้วย

อย่างไรก็ตาม LiveDataReactiveStreams ช่วยให้คุณได้รับประโยชน์จาก RxJava และชุดโอเปอเรเตอร์ที่หลากหลาย รวมถึงความสามารถในการจัดกำหนดการงาน ขณะเดียวกันก็ทำงานด้วยความเรียบง่ายของ LiveData ดังที่แสดงในตัวอย่างต่อไปนี้

val fun getUsersLiveData() : LiveData<List<User>> {
    val users: Flowable<List<User>> = dao.findUsers()
    return LiveDataReactiveStreams.fromPublisher(users)
}

Room KTX

ส่วนขยาย Room เพิ่มการรองรับ Coroutine สำหรับธุรกรรมฐานข้อมูล

หากต้องการใช้โมดูลนี้ ให้เพิ่มโค้ดต่อไปนี้ลงในไฟล์ build.gradle ของแอป

Groovy

dependencies {
    implementation "androidx.room:room-ktx:2.8.5"
}

Kotlin

dependencies {
    implementation("androidx.room:room-ktx:2.8.5")
}

ต่อไปนี้เป็นตัวอย่างบางส่วนที่ Room ใช้โครูทีนในปัจจุบัน ตัวอย่างแรกใช้ฟังก์ชัน suspend เพื่อแสดงรายการออบเจ็กต์ User ส่วนตัวอย่างที่ 2 ใช้ 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 ไว้ในธุรกรรม ซึ่งช่วยลดโค้ด Boilerplate ได้มาก

หากต้องการใช้โมดูลนี้ ให้เพิ่มโค้ดต่อไปนี้ลงในไฟล์ build.gradle ของแอป

Groovy

dependencies {
    implementation "androidx.sqlite:sqlite-ktx:2.7.1"
}

Kotlin

dependencies {
    implementation("androidx.sqlite:sqlite-ktx:2.7.1")
}

ต่อไปนี้คือตัวอย่างการใช้ส่วนขยาย transaction เพื่อทำธุรกรรมฐานข้อมูล

db.transaction {
    // insert data
}

ViewModel KTX

ไลบรารี ViewModel KTX มีฟังก์ชัน viewModelScope() ที่ช่วยให้เปิดใช้ Coroutine จาก ViewModel ได้ง่ายขึ้น CoroutineScope จะเชื่อมโยงกับ Dispatchers.Main และจะถูกยกเลิกโดยอัตโนมัติเมื่อล้าง ViewModel คุณสามารถใช้ viewModelScope() แทนการสร้างขอบเขตใหม่สำหรับ ViewModel แต่ละรายการได้

หากต้องการรวมโมดูลนี้ ให้เพิ่มค่าต่อไปนี้ลงในไฟล์ build.gradle ของแอป

Groovy

dependencies {
    implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2"
}

Kotlin

dependencies {
    implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2")
}

ตัวอย่างเช่น ฟังก์ชัน 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.12.0"
}

Kotlin

dependencies {
    implementation("androidx.work:work-runtime-ktx:2.12.0")
}

ตอนนี้คุณสามารถขยาย 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 ได้ที่การทำ Threading ใน 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

Firebase SDK บางรายการสำหรับ Android มีไลบรารีส่วนขยาย Kotlin ที่ช่วยให้คุณเขียนโค้ด Kotlin ที่เป็นสำนวนเมื่อใช้ Firebase ในแอปได้ ดูข้อมูลเพิ่มเติมได้ที่หัวข้อต่อไปนี้

KTX ของ Google Maps Platform

มีส่วนขยาย KTX สำหรับ Android SDK ใน Google Maps Platform ซึ่งช่วยให้คุณใช้ประโยชน์จากฟีเจอร์ต่างๆ ของภาษา Kotlin ได้ เช่น ฟังก์ชันส่วนขยาย, พารามิเตอร์ที่มีชื่อและอาร์กิวเมนต์เริ่มต้น, การประกาศการแยกโครงสร้าง และโครูทีน ดูข้อมูลเพิ่มเติมได้ที่หัวข้อต่อไปนี้

Play Core KTX

Play Core KTX เพิ่มการรองรับโครูทีน Kotlin สำหรับคำขอแบบครั้งเดียวและ Flow สำหรับการตรวจสอบการอัปเดตสถานะโดยเพิ่มฟังก์ชันส่วนขยายลงใน SplitInstallManager และ AppUpdateManager ในไลบรารี Play Core

หากต้องการรวมโมดูลนี้ ให้เพิ่มค่าต่อไปนี้ลงในไฟล์ build.gradle ของแอป

ดึงดูด

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