Android KTX adalah kumpulan ekstensi untuk API framework Android yang umum digunakan, library Android Jetpack, dan lainnya. Kami membuat ekstensi ini agar panggilan dari kode Kotlin ke API berbasis bahasa pemrograman Java lebih ringkas dan idiomatis dengan memanfaatkan fitur bahasa seperti fungsi dan properti ekstensi, lambda, parameter bernama dan default, serta coroutine.
Apa yang dimaksud dengan library KTX?
KTX adalah singkatan dari ekstensi Kotlin, dan bukan teknologi khusus atau fitur bahasa dari bahasa Kotlin. Ini hanya nama yang kami gunakan untuk library Kotlin Google yang memperluas fungsi API yang awalnya dibuat dalam bahasa pemrograman Java.
Kelebihan ekstensi Kotlin adalah siapa saja bisa membangun library sendiri untuk API mereka sendiri, atau bahkan untuk library pihak ketiga yang Anda gunakan dalam project.
Codelab ini akan menjelaskan beberapa contoh penambahan ekstensi sederhana yang memanfaatkan fitur bahasa Kotlin. Kita juga akan melihat bagaimana cara mengonversi panggilan asinkron di API berbasis callback menjadi fungsi penangguhan dan Alur - aliran asinkron berbasis coroutine.
Yang akan Anda buat
Dalam codelab ini, Anda akan mengerjakan aplikasi sederhana yang memperoleh dan menampilkan lokasi pengguna saat ini. Aplikasi Anda akan:
|
Yang akan Anda pelajari
- Cara menambahkan ekstensi Kotlin untuk mendukung class yang ada
- Cara mengonversi panggilan asinkron yang menampilkan hasil tunggal menjadi fungsi penangguhan coroutine
- Cara menggunakan Alur untuk mendapatkan data dari sumber yang dapat memunculkan nilai beberapa kali
Yang Anda butuhkan
- Versi terbaru Android Studio (disarankan 3.6+)
- Android Emulator atau perangkat yang terhubung melalui USB
- Pengetahuan tingkat dasar tentang pengembangan Android dan bahasa Kotlin
- Pemahaman dasar tentang coroutine dan fungsi penangguhan
Download Kode
Klik link berikut untuk mendownload semua kode untuk codelab ini:
... atau clone repositori GitHub dari command line dengan menggunakan perintah berikut:
$ git clone https://github.com/googlecodelabs/kotlin-coroutines.git
Kode untuk codelab ini ada di direktori ktx-library-codelab
.
Dalam direktori project, Anda akan menemukan beberapa folder step-NN
yang berisi status akhir yang diinginkan dari setiap langkah codelab ini sebagai referensi.
Kita akan mengerjakan semua tugas coding di direktori work
.
Menjalankan aplikasi untuk pertama kali
Buka folder root (ktx-library-codelab
) di Android Studio, lalu pilih konfigurasi run work-app
di menu drop-down seperti yang ditunjukkan di bawah ini:
Tekan tombol Run untuk menguji aplikasi Anda:
Aplikasi ini belum melakukan sesuatu yang menarik. Beberapa bagian hilang sehingga tidak dapat menampilkan data. Kita akan menambahkan fungsi yang hilang tersebut pada langkah berikutnya.
Cara yang lebih mudah untuk memeriksa izin
Meskipun dapat berjalan, aplikasi hanya menampilkan error - aplikasi tidak dapat memperoleh lokasi saat ini.
Hal ini dikarenakan kode untuk meminta izin akses lokasi runtime dari pengguna tidak ada.
Buka MainActivity.kt
, dan cari kode yang diberi tanda komentar berikut:
// val permissionApproved = ActivityCompat.checkSelfPermission(
// this,
// Manifest.permission.ACCESS_FINE_LOCATION
// ) == PackageManager.PERMISSION_GRANTED
// if (!permissionApproved) {
// requestPermissions(arrayOf(Manifest.permission.ACCESS_FINE_LOCATION), 0)
// }
Jika Anda menghapus tanda komentar dari kode dan menjalankan aplikasi, aplikasi akan meminta izin dan melanjutkan untuk menampilkan lokasi. Namun, kode ini sulit dibaca karena beberapa alasan:
- Kode menggunakan metode statis
checkSelfPermission
dari class utilitasActivityCompat
, yang hanya ada untuk menahan metode untuk kompatibilitas mundur. - Metode ini selalu menggunakan instance
Activity
sebagai parameter pertama, karena tidak mungkin menambahkan metode ke class framework dalam bahasa pemrograman Java. - Kita selalu memeriksa apakah izin tersebut adalah
PERMISSION_GRANTED
, sehingga akan lebih baik untuk langsung mendapatkan nilai Booleantrue
jika izin diberikan dan salah (false) jika sebaliknya.
Kita ingin mengonversi kode panjang yang ditampilkan di atas menjadi kode yang lebih pendek seperti ini:
if (!hasPermission(Manifest.permission.ACCESS_FINE_LOCATION)) {
// request permission
}
Kita akan mempersingkat kode dengan bantuan fungsi ekstensi di Aktivitas. Dalam project ini, Anda akan menemukan modul lain bernama myktxlibrary
. Buka ActivityUtils.kt
dari modul tersebut, dan tambahkan fungsi berikut:
fun Activity.hasPermission(permission: String): Boolean {
return ActivityCompat.checkSelfPermission(
this,
permission
) == PackageManager.PERMISSION_GRANTED
}
Mari kita bedah apa yang terjadi di sini:
fun
dalam cakupan terluar (bukan di dalamclass
) berarti kita menentukan fungsi tingkat atas dalam file.Activity.hasPermission
menentukan fungsi ekstensi dengan namahasPermission
pada penerima jenisActivity
.- Fungsi ini mengambil izin sebagai argumen
String
dan menampilkanBoolean
yang menunjukkan apakah izin telah diberikan.
Jadi, apa sebenarnya yang dimaksud dengan "penerima jenis X"?
Anda akan sering melihatnya saat membaca dokumentasi untuk fungsi ekstensi Kotlin. Ini berarti fungsi ini akan selalu dipanggil pada instance Activity
(dalam kasus ini) atau subclass-nya, dan di dalam isi fungsi, kita dapat merujuk ke instance tersebut menggunakan kata kunci this
(yang juga dapat bersifat implisit, artinya kita dapat menghilangkannya sepenuhnya).
Ini merupakan inti dari fungsi ekstensi, yaitu menambahkan fungsi baru di atas class yang tidak dapat atau tidak ingin kita ubah.
Mari lihat bagaimana kita memanggilnya di MainActivity.kt
. Buka dan ubah kode izin menjadi:
if (!hasPermission(Manifest.permission.ACCESS_FINE_LOCATION)) {
requestPermissions(arrayOf(Manifest.permission.ACCESS_FINE_LOCATION), 0)
}
Jika menjalankan aplikasi sekarang, Anda dapat melihat lokasi yang ditampilkan di layar.
Bantuan untuk memformat teks Lokasi
Meski begitu, teks lokasi tidak tampak cukup bagus. Teks tersebut menggunakan metode Location.toString
default, yang tidak ditujukan untuk ditampilkan di UI.
Buka class LocationUtils.kt
di myktxlibrary
. File ini berisi ekstensi untuk class Location
. Lengkapi fungsi ekstensi Location.format
untuk menampilkan String
yang diformat, lalu modifikasi Activity.showLocation
di ActivityUtils.kt
untuk memanfaatkan ekstensi.
Anda dapat melihat kode di folder step-03
jika mengalami masalah. Hasil akhirnya akan terlihat seperti ini:
Penyedia Lokasi Gabungan dari Layanan Google Play
Project aplikasi yang kita kerjakan ini menggunakan Penyedia Lokasi Gabungan dari Layanan Google Play untuk mendapatkan data lokasi. API-nya sendiri cukup sederhana, tetapi karena mendapatkan lokasi pengguna bukanlah operasi yang dapat dijalankan seketika, semua panggilan ke library harus bersifat asinkron. Hal ini mempersulit kode karena adanya callback.
Ada dua langkah untuk mendapatkan lokasi pengguna. Pada langkah ini, kita akan berfokus untuk mendapatkan lokasi terakhir yang diketahui, jika tersedia. Pada langkah berikutnya, kita akan melihat pembaruan lokasi berkala saat aplikasi sedang berjalan.
Mendapatkan lokasi terakhir yang diketahui
Dalam Activity.onCreate
, kita melakukan inisialisasi FusedLocationProviderClient
yang akan menjadi titik masuk ke library.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
fusedLocationClient = LocationServices.getFusedLocationProviderClient(this)
}
Dalam Activity.onStart
, kita kemudian memanggil getLastKnownLocation()
yang saat ini terlihat seperti ini:
private fun getLastKnownLocation() {
fusedLocationClient.lastLocation.addOnSuccessListener { lastLocation ->
showLocation(R.id.textView, lastLocation)
}.addOnFailureListener { e ->
findAndSetText(R.id.textView, "Unable to get location.")
e.printStackTrace()
}
}
Seperti yang dapat Anda lihat, lastLocation
adalah panggilan asinkron yang dapat diselesaikan dengan berhasil atau gagal. Untuk setiap hasil ini, kita harus mendaftarkan fungsi callback yang akan menampilkan lokasi ke UI atau menampilkan pesan error.
Kode ini belum tampak terlalu rumit karena adanya callback saat ini. Namun, dalam project nyata, Anda mungkin ingin memproses lokasi, menyimpannya ke database, atau mengupload ke server. Banyak dari operasi ini juga asinkron, dan menambahkan callback di atas callback akan segera membuat kode kita tidak bisa dibaca dan dapat terlihat seperti ini:
private fun getLastKnownLocation() {
fusedLocationClient.lastLocation.addOnSuccessListener { lastLocation ->
getLastLocationFromDB().addOnSuccessListener {
if (it != location) {
saveLocationToDb(location).addOnSuccessListener {
showLocation(R.id.textView, lastLocation)
}
}
}.addOnFailureListener { e ->
findAndSetText(R.id.textView, "Unable to read location from DB.")
e.printStackTrace()
}
}.addOnFailureListener { e ->
findAndSetText(R.id.textView, "Unable to get location.")
e.printStackTrace()
}
}
Lebih buruk lagi, kode di atas memiliki masalah dengan kebocoran memori dan operasi, karena pemroses tidak akan dihapus ketika Activity
yang memuatnya selesai.
Kita akan mencari cara yang lebih baik untuk menyelesaikan masalah ini dengan coroutine, yang memungkinkan Anda menulis kode asinkron yang terlihat seperti blok kode imperatif biasa dari atas ke bawah, tanpa melakukan pemblokiran panggilan pada thread panggilan. Selain itu, coroutine juga dapat dibatalkan, sehingga kita dapat membersihkannya setiap kali berada di luar cakupan.
Pada langkah berikutnya, kita akan menambahkan fungsi ekstensi yang mengonversi callback API yang ada menjadi fungsi penangguhan yang dapat dipanggil dari cakupan coroutine yang terkait dengan UI Anda. Kita ingin hasil akhir terlihat seperti ini:
private fun getLastKnownLocation() {
try {
val lastLocation = fusedLocationClient.awaitLastLocation();
// process lastLocation here if needed
showLocation(R.id.textView, lastLocation)
} (e: Exception) {
// we can do regular exception handling here or let it throw outside the function
}
}
Membuat fungsi penangguhan menggunakan suspendCancellableCoroutine
Buka LocationUtils.kt
, dan tentukan fungsi ekstensi baru di FusedLocationProviderClient
:
suspend fun FusedLocationProviderClient.awaitLastLocation(): Location =
suspendCancellableCoroutine { continuation ->
TODO("Return results from the lastLocation call here")
}
Sebelum masuk ke bagian implementasi, mari kita bedah tanda tangan fungsi ini:
- Anda sudah mengetahui fungsi ekstensi dan jenis penerima dari bagian codelab sebelumnya:
fun FusedLocationProviderClient.awaitLastLocation
suspend
berarti fungsi tersebut akan menjadi fungsi penangguhan, yaitu jenis fungsi khusus yang hanya dapat dipanggil dalam coroutine atau dari fungsisuspend
lainnya- Jenis hasil pemanggilannya akan berupa
Location
, seolah-olah merupakan cara sinkron untuk mendapatkan hasil lokasi dari API.
Untuk membuat hasil, kita akan menggunakan suspendCancellableCoroutine
, blok pembuatan tingkat rendah untuk membuat fungsi penangguhan dari library coroutine.
suspendCancellableCoroutine
menjalankan blok kode yang diteruskan ke fungsi tersebut sebagai parameter, lalu menangguhkan eksekusi coroutine saat menunggu hasil.
Mari coba tambahkan callback yang berhasil dan gagal ke isi fungsi, seperti yang kita lihat pada panggilan lastLocation
sebelumnya. Sayangnya, seperti yang dapat Anda lihat pada komentar di bawah, hal yang jelas-jelas ingin kita lakukan, yaitu menampilkan hasil, tidak mungkin dilakukan di bagian isi callback:
suspend fun FusedLocationProviderClient.awaitLastLocation(): Location =
suspendCancellableCoroutine { continuation ->
lastLocation.addOnSuccessListener { location ->
// this is not allowed here:
// return location
}.addOnFailureListener { e ->
// this will not work as intended:
// throw e
}
}
Hal ini karena callback terjadi lama setelah fungsi di sekitarnya selesai, dan sekarang tidak ada tempat untuk menampilkan hasilnya. Di situlah suspendCancellableCoroutine
memainkan peran dengan continuation
yang disediakan untuk blok kode kita. Kita dapat menggunakannya untuk memberikan hasil kembali ke fungsi yang ditangguhkan di waktu mendatang, menggunakan continuation.resume
. Selesaikan kasus error menggunakan continuation.resumeWithException(e)
untuk menyebarkan pengecualian ke situs panggilan dengan benar.
Umumnya, Anda harus selalu memastikan bahwa pada masa mendatang, Anda akan menampilkan hasil atau pengecualian agar coroutine tidak ditangguhkan selamanya saat menunggu hasil.
suspend fun FusedLocationProviderClient.awaitLastLocation(): Location =
suspendCancellableCoroutine<Location> { continuation ->
lastLocation.addOnSuccessListener { location ->
continuation.resume(location)
}.addOnFailureListener { e ->
continuation.resumeWithException(e)
}
}
Selesai. Kita baru saja memperlihatkan versi ditangguhkan dari API lokasi terakhir yang diketahui, yang bisa dimanfaatkan dari coroutine di aplikasi.
Memanggil fungsi penangguhan
Mari kita modifikasi fungsi getLastKnownLocation
dalam MainActivity
untuk memanggil versi coroutine baru dari panggilan lokasi terakhir yang diketahui:
private suspend fun getLastKnownLocation() {
try {
val lastLocation = fusedLocationClient.awaitLastLocation()
showLocation(R.id.textView, lastLocation)
} catch (e: Exception) {
findAndSetText(R.id.textView, "Unable to get location.")
Log.d(TAG, "Unable to get location", e)
}
}
Seperti yang disebutkan sebelumnya, fungsi penangguhan harus selalu dipanggil dari fungsi penangguhan lain untuk memastikan fungsi tersebut berjalan dalam coroutine, yang berarti kita harus menambahkan pengubah penangguhan ke fungsi getLastKnownLocation
itu sendiri, atau kita akan mendapatkan error dari IDE.
Perhatikan bahwa kita dapat menggunakan blok try-catch reguler untuk penanganan pengecualian. Kita dapat memindahkan kode ini dari dalam callback yang gagal karena pengecualian yang berasal dari Location
API sekarang disebarkan dengan benar, seperti dalam program wajib yang reguler.
Untuk memulai coroutine, biasanya kita akan menggunakan CoroutineScope.launch
, yang memerlukan cakupan coroutine. Untungnya, library Android KTX dilengkapi dengan beberapa cakupan bawaan untuk objek siklus proses umum seperti Activity
, Fragment
, dan ViewModel
.
Tambahkan kode berikut ke Activity.onStart
:
override fun onStart() {
super.onStart()
if (!hasPermission(ACCESS_FINE_LOCATION)) {
requestPermissions(arrayOf(ACCESS_FINE_LOCATION), 0)
}
lifecycleScope.launch {
try {
getLastKnownLocation()
} catch (e: Exception) {
findAndSetText(R.id.textView, "Unable to get location.")
Log.d(TAG, "Unable to get location", e)
}
}
startUpdatingLocation()
}
Anda seharusnya dapat menjalankan aplikasi dan memastikan bahwa aplikasi berfungsi sebelum melanjutkan ke langkah berikutnya. Pada langkah tersebut, kita akan mengenalkan Flow
untuk fungsi yang memunculkan hasil lokasi beberapa kali.
Sekarang kita akan fokus pada fungsi startUpdatingLocation()
. Dalam kode saat ini, kita mendaftarkan pemroses dengan Penyedia Lokasi Gabungan untuk mendapatkan update lokasi secara berkala setiap kali perangkat pengguna bergerak di dunia nyata.
Untuk menampilkan apa yang ingin kita peroleh dengan API berbasis Flow
, pertama-tama mari lihat bagian MainActivity
yang akan dihapus di bagian ini, dan memindahkannya ke detail implementasi fungsi ekstensi baru.
Dalam kode saat ini, ada variabel untuk pelacakan jika kita mulai memroses pembaruan:
var listeningToUpdates = false
Ada juga subclass dari class callback dasar dan implementasi untuk fungsi callback yang diupdate lokasi:
private val locationCallback: LocationCallback = object : LocationCallback() {
override fun onLocationResult(locationResult: LocationResult?) {
if (locationResult != null) {
showLocation(R.id.textView, locationResult.lastLocation)
}
}
}
Kita juga memiliki pendaftaran awal pemroses (yang bisa gagal jika pengguna tidak memberikan izin yang diperlukan), beserta callback karena panggilan tersebut asinkron:
private fun startUpdatingLocation() {
fusedLocationClient.requestLocationUpdates(
createLocationRequest(),
locationCallback,
Looper.getMainLooper()
).addOnSuccessListener { listeningToUpdates = true }
.addOnFailureListener { e ->
findAndSetText(R.id.textView, "Unable to get location.")
e.printStackTrace()
}
}
Terakhir, saat layar tidak aktif lagi, kita menghapus:
override fun onStop() {
super.onStop()
if (listeningToUpdates) {
stopUpdatingLocation()
}
}
private fun stopUpdatingLocation() {
fusedLocationClient.removeLocationUpdates(locationCallback)
}
Anda dapat melanjutkan dan menghapus semua cuplikan kode tersebut dari MainActivity
, dan hanya menyisakan fungsi startUpdatingLocation()
kosong yang nantinya akan kita gunakan untuk mulai mengumpulkan Flow
.
callbackFlow: Builder Alur untuk API berbasis callback
Buka LocationUtils.kt
lagi, lalu tentukan fungsi ekstensi lainnya di FusedLocationProviderClient
:
fun FusedLocationProviderClient.locationFlow() = callbackFlow<Location> {
TODO("Register a location listener")
TODO("Emit updates on location changes")
TODO("Clean up listener when finished")
}
Ada beberapa hal yang perlu kita lakukan di sini untuk membuat replika fungsi yang baru saja kita hapus dari kode MainActivity
. Kita akan menggunakan callbackFlow()
, fungsi builder yang menampilkan Flow
, yang cocok untuk memunculkan data dari API berbasis callback.
Blok yang diteruskan ke callbackFlow()
ditentukan dengan ProducerScope
sebagai penerimanya.
noinline block: suspend ProducerScope<T>.() -> Unit
ProducerScope
mengenkapsulasi detail implementasi callbackFlow
, seperti fakta bahwa ada Channel
yang mencadangkan Flow
yang dibuat. Tanpa perlu membahas detailnya, Channels
digunakan secara internal oleh beberapa builder dan operator Flow
, dan kecuali Anda menulis builder/operator sendiri, Anda tidak perlu khawatir dengan detail tingkat rendah ini.
Kita hanya akan menggunakan beberapa fungsi yang ditampilkan ProducerScope
untuk menghasilkan data dan mengelola status Flow
.
Mari mulai dengan membuat pemroses untuk location API:
fun FusedLocationProviderClient.locationFlow() = callbackFlow<Location> {
val callback = object : LocationCallback() {
override fun onLocationResult(result: LocationResult?) {
result ?: return
for (location in result.locations) {
offer(location) // emit location into the Flow using ProducerScope.offer
}
}
}
TODO("Register a location listener")
TODO("Clean up listener when finished")
}
Kita akan menggunakan ProducerScope.offer
untuk mengirim data lokasi ke Flow
begitu data tersebut masuk.
Selanjutnya, daftarkan callback dengan FusedLocationProviderClient
, yang dengan cermat akan menangani error:
fun FusedLocationProviderClient.locationFlow() = callbackFlow<Location> {
val callback = object : LocationCallback() {
override fun onLocationResult(result: LocationResult?) {
result ?: return
for (location in result.locations) {
offer(location) // emit location into the Flow using ProducerScope.offer
}
}
}
requestLocationUpdates(
createLocationRequest(),
callback,
Looper.getMainLooper()
).addOnFailureListener { e ->
close(e) // in case of error, close the Flow
}
TODO("Clean up listener when finished")
}
FusedLocationProviderClient.requestLocationUpdates
adalah fungsi asinkron (seperti lastLocation
) yang menggunakan callback untuk memberi sinyal saat berhasil atau gagal menyelesaikan.
Di sini, kita dapat mengabaikan status berhasil, karena artinya di masa mendatang, onLocationResult
akan dipanggil, dan kita akan mulai menampilkan hasilnya ke Flow
.
Jika terjadi kegagalan, kita akan langsung menutup Flow
dengan Exception
.
Hal terakhir yang harus selalu Anda panggil di dalam blok yang diteruskan ke callbackFlow
adalah awaitClose
. Fungsi ini menyediakan tempat yang sesuai untuk meletakkan kode pembersihan apa pun untuk melepaskan resource jika terjadi penyelesaian atau pembatalan Flow
(terlepas dari apakah itu terjadi dengan Exception
atau tidak):
fun FusedLocationProviderClient.locationFlow() = callbackFlow<Location> {
val callback = object : LocationCallback() {
override fun onLocationResult(result: LocationResult?) {
result ?: return
for (location in result.locations) {
offer(location) // emit location into the Flow using ProducerScope.offer
}
}
}
requestLocationUpdates(
createLocationRequest(),
callback,
Looper.getMainLooper()
).addOnFailureListener { e ->
close(e) // in case of exception, close the Flow
}
awaitClose {
removeLocationUpdates(callback) // clean up when Flow collection ends
}
}
Setelah mengerjakan semua bagian (mendaftarkan pemroses, memroses pembaruan, dan membersihkan), mari kembali ke MainActivity
untuk memanfaatkan Flow
guna menampilkan lokasi.
Mengumpulkan Alur
Mari kita modifikasi fungsi startUpdatingLocation
di MainActivity
untuk memanggil builder Flow
dan mulai mengumpulkannya. Implementasi asli mungkin terlihat seperti ini:
private fun startUpdatingLocation() {
lifecycleScope.launch {
fusedLocationClient.locationFlow()
.conflate()
.catch { e ->
findAndSetText(R.id.textView, "Unable to get location.")
Log.d(TAG, "Unable to get location", e)
}
.collect { location ->
showLocation(R.id.textView, location)
Log.d(TAG, location.toString())
}
}
}
Flow.collect()
adalah operator terminal yang memulai operasi Flow
yang sebenarnya. Di dalamnya, kita akan menerima semua update lokasi yang ditransmisi dari builder callbackFlow
kami. Karena collect
adalah fungsi penangguhan, fungsi tersebut harus berjalan di dalam coroutine, yang kita luncurkan menggunakan lifecycleScope.
Anda juga dapat melihat operator perantara conflate
()
dan catch
()
yang dipanggil di Flow
. Ada banyak operator yang disertakan dengan library coroutine, yang memungkinkan Anda memfilter dan mentransformasikan alur secara deklaratif.
Menggabungkan alur berarti kita hanya ingin menerima update terbaru, setiap kali update dihasilkan lebih cepat daripada pemrosesan yang dilakukan kolektor. Ini sesuai dengan contoh, karena kita hanya ingin menampilkan lokasi terbaru di UI.
catch
, seperti namanya, akan memungkinkan Anda menangani pengecualian apa pun yang ditampilkan di upstream, dalam hal ini di builder locationFlow
. Anda dapat menganggap upstream sebagai operasi yang diterapkan sebelum operasi saat ini.
Jadi, apa masalahnya dengan cuplikan di atas? Meskipun tidak akan menyebabkan aplikasi error, dan akan dihapus dengan benar setelah aktivitas DIBERSIHKAN (berkat lifecycleScope
), cuplikan tersebut tidak memperhitungkan saat aktivitas dihentikan (misalnya saat tidak terlihat).
Artinya, kita tidak hanya akan memperbarui UI jika tidak diperlukan, tetapi Alur juga akan membuat langganan ke data lokasi tetap aktif serta menghabiskan baterai dan siklus CPU!
Salah satu cara untuk memperbaikinya adalah dengan mengonversi Alur ke LiveData menggunakan ekstensi Flow.asLiveData
dari library LiveData KTX. LiveData
mengetahui kapan harus mengamati dan kapan harus menjeda langganan, dan akan memulai ulang Alur dasar sesuai kebutuhan.
private fun startUpdatingLocation() {
fusedLocationClient.locationFlow()
.conflate()
.catch { e ->
findAndSetText(R.id.textView, "Unable to get location.")
Log.d(TAG, "Unable to get location", e)
}
.asLiveData()
.observe(this, Observer { location ->
showLocation(R.id.textView, location)
Log.d(TAG, location.toString())
})
}
lifecycleScope.launch
eksplisit tidak diperlukan lagi karena asLiveData
akan menyediakan cakupan yang diperlukan untuk menjalankan Alur masuk. Panggilan observe
sebenarnya berasal dari LiveData dan tidak berkaitan dengan coroutine atau Alur. Ini hanyalah cara standar untuk mengamati LiveData dengan LifecycleOwner. LiveData akan mengumpulkan Alur dasar dan metransmisikan lokasi ke pengamatnya.
Karena pembuatan ulang dan pengumpulan alur akan ditangani secara otomatis sekarang, kita harus memindahkan metode startUpdatingLocation()
dari Activity.onStart
(yang dapat dijalankan berulang kali) ke Activity.onCreate
:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
fusedLocationClient = LocationServices.getFusedLocationProviderClient(this)
startUpdatingLocation()
}
Sekarang Anda dapat menjalankan aplikasi dan memeriksa bagaimana reaksinya terhadap rotasi, dengan menekan tombol Beranda dan Kembali. Periksa logcat untuk melihat apakah lokasi baru ditampilkan saat aplikasi berada di latar belakang. Jika implementasinya benar, aplikasi harus menjeda dengan benar dan memulai ulang koleksi Alur saat Anda menekan Beranda lalu kembali ke aplikasi.
Anda berhasil membuat library KTX pertama Anda!
Selamat! Apa yang telah Anda capai dalam codelab ini sangat mirip dengan apa yang biasanya terjadi saat membuat library ekstensi Kotlin untuk API berbasis Java yang sudah ada.
Untuk meringkas hal-hal yang telah kita lakukan:
- Anda menambahkan fungsi praktis untuk memeriksa izin dari
Activity
. - Anda memberikan ekstensi format teks pada objek
Location
. - Anda mengekspos versi coroutine
Location
API untuk mendapatkan lokasi terakhir yang diketahui dan update lokasi berkala menggunakanFlow
. - Jika ingin, Anda dapat membersihkan kode lebih lanjut, menambahkan beberapa pengujian, dan mendistribusikan library
location-ktx
ke developer lain di tim Anda agar mereka dapat memanfaatkannya.
Untuk membuat file AAR untuk distribusi, jalankan tugas :myktxlibrary:bundleReleaseAar
.
Anda dapat mengikuti langkah serupa untuk API lainnya yang dapat memanfaatkan ekstensi Kotlin.
Menyempurnakan arsitektur aplikasi dengan Alur
Sebelumnya telah disebutkan bahwa meluncurkan operasi dari Activity
seperti yang kita lakukan dalam codelab ini tidak selalu menjadi pilihan terbaik. Anda dapat mengikuti codelab ini untuk mempelajari cara mengamati alur dari ViewModels
di UI, bagaimana alur dapat berinteroperasi dengan LiveData
, dan bagaimana Anda dapat mendesain aplikasi menggunakan aliran data.