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

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

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

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

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

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

ต่อไปนี้คือตัวอย่างฟังก์ชันหลักของ Android KTX รายการหนึ่ง ซึ่งก็คือ SharedPreferences.edit ซึ่งจะเพิ่มฟังก์ชันแก้ไขลงใน SharedPreferences ฟังก์ชันนี้ใช้ Flag 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ฟังก์ชันส่วนขยายเป็นลักษณะการเพิ่มประสิทธิภาพที่ไลบรารี KTX ของ Android มีให้

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

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

Groovy

repositories {
    google()
}

Kotlin

repositories {
    google()
}

โมดูล AndroidX

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

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

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

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

Core KTX

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

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

Groovy

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

Kotlin

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

ต่อไปนี้คือรายการแพ็กเกจที่อยู่ในโมดูล KTX หลัก

Collection KTX

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

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

Groovy

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

Kotlin

dependencies {
    implementation("androidx.collection:collection-ktx:1.4.5")
}

ส่วนขยายคอลเล็กชันใช้ประโยชน์จากการโอเวอร์โหลดโอเปอเรเตอร์ของ 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

KTX ของ Fragment

โมดูล KTX ของ FRGMENT มีส่วนขยายหลายรายการเพื่อลดความซับซ้อนของ FRGMENT API

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

ดึงดูด

dependencies {
    implementation "androidx.fragment:fragment-ktx:1.8.3"
}

Kotlin

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

โมดูล KTX ของส่วนช่วยให้คุณทําธุรกรรมของส่วนย่อยได้ง่ายขึ้นด้วยแลมบดา เช่น

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

KTX วงจรชีวิตของแอปจะกำหนด LifecycleScope สำหรับออบเจ็กต์ Lifecycle แต่ละรายการ โคโริวทีนที่เริ่มทำงานในสโกปนี้จะยกเลิกเมื่อ Lifecycle ถูกทำลาย คุณสามารถเข้าถึง CoroutineScope ของ Lifecycle โดยใช้พร็อพเพอร์ตี้ lifecycle.coroutineScope หรือ lifecycleOwner.lifecycleScope

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

ดึงดูด

dependencies {
    implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.8.7"
}

Kotlin

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

ตัวอย่างต่อไปนี้แสดงวิธีใช้ 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 ของแอป

ดึงดูด

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

Kotlin

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

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

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

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

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

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

Groovy

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

Kotlin

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

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

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 ของแอป

ดึงดูด

dependencies {
    implementation "androidx.lifecycle:lifecycle-reactivestreams-ktx:2.8.7"
}

Kotlin

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

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

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

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

Room KTX

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

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

ดึงดูด

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

Kotlin

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

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

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

ดึงดูด

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

Kotlin

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

ตัวอย่างการใช้ส่วนขยาย transaction เพื่อดําเนินธุรกรรมฐานข้อมูลมีดังนี้

db.transaction {
    // insert data
}

ViewModel KTX

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

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

ดึงดูด

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

Kotlin

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

ตัวอย่างเช่น ฟังก์ชัน viewModelScope() ต่อไปนี้จะเปิดใช้ coroutine ที่ส่งคำขอเครือข่ายในเธรดแบ็กกราวด์ ไลบรารีจะจัดการการตั้งค่าทั้งหมดและการล้างขอบเขตที่เกี่ยวข้อง

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

Kotlin

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

ตอนนี้คุณขยาย 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

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

KTX ของ Google Maps Platform

เรามีส่วนขยาย KTX สำหรับ SDK ของ Google Maps Platform สำหรับ Android ซึ่งจะช่วยให้คุณใช้ประโยชน์จากฟีเจอร์ต่างๆ ของภาษา 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