Codelab ini adalah bagian dari kursus Dasar-Dasar Android Kotlin. Anda akan mendapatkan manfaat maksimal dari kursus ini jika menyelesaikan codelab secara berurutan. Semua codelab kursus tercantum di halaman landing codelab Dasar-Dasar Android Kotlin.
Pengantar
Sebagian besar aplikasi yang menggunakan daftar dan petak yang menampilkan item memungkinkan pengguna berinteraksi dengan item. Mengetuk item dari daftar dan melihat detail item adalah kasus penggunaan yang sangat umum untuk jenis interaksi ini. Untuk mencapainya, Anda dapat menambahkan pemroses klik yang merespons ketukan pengguna pada item dengan menampilkan tampilan detail.
Dalam codelab ini, Anda akan menambahkan interaksi ke RecyclerView
, dengan membangun versi yang diperluas dari aplikasi sleep-tracker dari rangkaian codelab sebelumnya.
Yang harus sudah Anda ketahui
- Membangun antarmuka pengguna dasar menggunakan aktivitas, fragmen, dan tampilan.
- Menavigasi antar-fragmen, dan menggunakan
safeArgs
untuk meneruskan data antar-fragmen. - Melihat model, melihat factory model, transformasi, dan
LiveData
serta pengamatnya. - Cara membuat database
Room
, membuat objek akses data (DAO), dan menentukan entity. - Cara menggunakan coroutine untuk database dan tugas berjalan lama lainnya.
- Cara menerapkan
RecyclerView
dasar denganAdapter
,ViewHolder
, dan tata letak item. - Cara menerapkan data binding untuk
RecyclerView
. - Cara membuat dan menggunakan adaptor binding untuk mengubah data.
- Cara menggunakan
GridLayoutManager
.
Yang akan Anda pelajari
- Cara membuat item di
RecyclerView
dapat diklik. Terapkan click listener untuk membuka tampilan detail saat item diklik.
Yang akan Anda lakukan
- Membangun versi yang diperluas dari aplikasi TrackMySleepQuality dari codelab sebelumnya dalam seri ini.
- Tambahkan pemroses klik ke daftar Anda dan mulai memproses interaksi pengguna. Saat item daftar diketuk, item tersebut akan memicu navigasi ke fragmen dengan detail item yang diklik. Kode awal menyediakan kode untuk fragmen detail, serta kode navigasi.
Aplikasi pelacak tidur awal memiliki dua layar, yang diwakili oleh fragmen, seperti yang ditunjukkan pada gambar di bawah.
Layar pertama, yang ditampilkan di sebelah kiri, memiliki tombol untuk memulai dan menghentikan pelacakan. Layar menampilkan beberapa data tidur pengguna. Tombol Hapus akan menghapus semua data yang telah dikumpulkan aplikasi untuk pengguna secara permanen. Layar kedua, yang ditampilkan di sebelah kanan, adalah untuk memilih rating kualitas tidur.
Aplikasi ini menggunakan arsitektur yang disederhanakan dengan pengontrol UI, model tampilan dan LiveData
, serta database Room
untuk mempertahankan data tidur.
Dalam codelab ini, Anda akan menambahkan kemampuan untuk merespons saat pengguna mengetuk item dalam petak, yang akan menampilkan layar detail seperti di bawah. Kode untuk layar ini (fragmen, model tampilan, dan navigasi) disediakan dengan aplikasi awal, dan Anda akan menerapkan mekanisme penanganan klik.
Langkah 1: Dapatkan aplikasi awal
- Download kode RecyclerViewClickHandler-Starter dari GitHub dan buka project di Android Studio.
- Bangun dan jalankan aplikasi pelacak tidur awal.
[Opsional] Update aplikasi Anda jika Anda ingin menggunakan aplikasi dari codelab sebelumnya
Jika Anda akan mengerjakan aplikasi awal yang disediakan di GitHub untuk codelab ini, lanjutkan ke langkah berikutnya.
Jika Anda ingin terus menggunakan aplikasi pelacak tidur yang Anda buat di codelab sebelumnya, ikuti petunjuk di bawah untuk mengupdate aplikasi yang ada agar memiliki kode untuk fragmen layar detail.
- Meskipun Anda melanjutkan dengan aplikasi yang ada, dapatkan kode RecyclerViewClickHandler-Starter dari GitHub agar Anda dapat menyalin file.
- Salin semua file dalam paket
sleepdetail
. - Di folder
layout
, salin filefragment_sleep_detail.xml
. - Salin konten
navigation.xml
yang telah diperbarui, yang menambahkan navigasi untuksleep_detail_fragment
. - Di paket
database
, diSleepDatabaseDao
, tambahkan metodegetNightWithId()
baru:
/**
* Selects and returns the night with given nightId.
*/
@Query("SELECT * from daily_sleep_quality_table WHERE nightId = :key")
fun getNightWithId(key: Long): LiveData<SleepNight>
- Di
res/values/strings
, tambahkan resource string berikut:
<string name="close">Close</string>
- Bersihkan dan bangun ulang aplikasi Anda untuk memperbarui data binding.
Langkah 2: Periksa kode untuk layar detail tidur
Dalam codelab ini, Anda akan menerapkan pengendali klik yang menavigasi ke fragmen yang menampilkan detail tentang malam tidur yang diklik. Kode awal Anda sudah berisi fragmen dan grafik navigasi untuk SleepDetailFragment
ini, karena kode tersebut cukup banyak, dan fragmen serta navigasi bukan bagian dari codelab ini. Pahami kode berikut:
- Di aplikasi Anda, temukan paket
sleepdetail
. Paket ini berisi fragmen, model tampilan, dan factory model tampilan untuk fragmen yang menampilkan detail tidur selama satu malam. - Di paket
sleepdetail
, buka dan periksa kode untukSleepDetailViewModel
. Model tampilan ini mengambil kunci untukSleepNight
dan DAO dalam konstruktor.
Isi class memiliki kode untuk mendapatkanSleepNight
untuk kunci tertentu, dan variabelnavigateToSleepTracker
untuk mengontrol navigasi kembali keSleepTrackerFragment
saat tombol Tutup ditekan.
FungsigetNightWithId()
menampilkanLiveData<SleepNight>
dan ditentukan dalamSleepDatabaseDao
(dalam paketdatabase
). - Di paket
sleepdetail
, buka dan periksa kode untukSleepDetailFragment
. Perhatikan penyiapan untuk binding data, model tampilan, dan pengamat untuk navigasi. - Di paket
sleepdetail
, buka dan periksa kode untukSleepDetailViewModelFactory
. - Di folder tata letak, periksa
fragment_sleep_detail.xml
. Perhatikan variabelsleepDetailViewModel
yang ditentukan dalam tag<data>
untuk mendapatkan data yang akan ditampilkan di setiap tampilan dari model tampilan.
Tata letak berisiConstraintLayout
yang berisiImageView
untuk kualitas tidur,TextView
untuk rating kualitas,TextView
untuk durasi tidur, danButton
untuk menutup fragmen detail. - Buka file
navigation.xml
. Untuksleep_tracker_fragment
, perhatikan tindakan baru untuksleep_detail_fragment
.
Tindakan baru,action_sleep_tracker_fragment_to_sleepDetailFragment
, adalah navigasi dari fragmen pelacak tidur ke layar detail.
Dalam tugas ini, Anda akan memperbarui RecyclerView
untuk merespons ketukan pengguna dengan menampilkan layar detail untuk item yang diketuk.
Menerima dan menangani klik adalah tugas dua bagian: Pertama, Anda perlu memproses dan menerima klik serta menentukan item mana yang telah diklik. Kemudian, Anda perlu merespons klik dengan tindakan.
Jadi, di mana tempat terbaik untuk menambahkan pemroses klik untuk aplikasi ini?
SleepTrackerFragment
menghosting banyak tampilan, sehingga memproses peristiwa klik di tingkat fragmen tidak akan memberi tahu Anda item mana yang diklik. Bahkan, Anda tidak akan tahu apakah yang diklik adalah item atau salah satu elemen UI lainnya.- Saat mendengarkan di tingkat
RecyclerView
, sulit untuk mengetahui item mana dalam daftar yang diklik pengguna. - Kecepatan terbaik untuk mendapatkan informasi tentang satu item yang diklik adalah di objek
ViewHolder
, karena objek tersebut merepresentasikan satu item daftar.
Meskipun ViewHolder
adalah tempat yang tepat untuk mendeteksi klik, biasanya bukan tempat yang tepat untuk menanganinya. Jadi, di mana tempat terbaik untuk menangani klik?
Adapter
menampilkan item data dalam tampilan, sehingga Anda dapat menangani klik di adaptor. Namun, tugas adaptor adalah mengadaptasi data untuk ditampilkan, bukan menangani logika aplikasi.- Anda biasanya harus menangani klik di
ViewModel
, karenaViewModel
memiliki akses ke data dan logika untuk menentukan apa yang perlu terjadi sebagai respons terhadap klik.
Langkah 1: Buat click listener dan picu dari tata letak item
- Di folder
sleeptracker
, buka SleepNightAdapter.kt. - Di akhir file, di tingkat teratas, buat class listener baru,
SleepNightListener
.
class SleepNightListener() {
}
- Di dalam class
SleepNightListener
, tambahkan fungsionClick()
. Saat tampilan yang menampilkan item daftar diklik, tampilan akan memanggil fungsionClick()
ini. (Anda akan menyetel propertiandroid:onClick
tampilan ke fungsi ini nanti.)
class SleepNightListener() {
fun onClick() =
}
- Tambahkan argumen fungsi
night
jenisSleepNight
keonClick()
. Tampilan mengetahui item yang sedang ditampilkan, dan informasi tersebut perlu diteruskan untuk menangani klik.
class SleepNightListener() {
fun onClick(night: SleepNight) =
}
- Untuk menentukan fungsi
onClick()
, berikan callbackclickListener
di konstruktorSleepNightListener
dan tetapkan keonClick()
.
Memberi nama lambda yang menangani klik,clickListener
, membantu melacaknya saat diteruskan antar-class. CallbackclickListener
hanya memerlukannight.nightId
untuk mengakses data dari database. ClassSleepNightListener
yang sudah selesai akan terlihat seperti kode di bawah.
class SleepNightListener(val clickListener: (sleepId: Long) -> Unit) {
fun onClick(night: SleepNight) = clickListener(night.nightId)
}
- Buka list_item_sleep_night.xml.
- Di dalam blok
data
, tambahkan variabel baru untuk membuat classSleepNightListener
tersedia melalui binding data. Beri<variable>
baruname
clickListener.
Tetapkantype
ke nama yang sepenuhnya memenuhi syarat untuk classcom.example.android.trackmysleepquality.sleeptracker.SleepNightListener
, seperti yang ditunjukkan di bawah. Anda kini dapat mengakses fungsionClick()
diSleepNightListener
dari tata letak ini.
<variable
name="clickListener"
type="com.example.android.trackmysleepquality.sleeptracker.SleepNightListener" />
- Untuk memproses klik di bagian mana pun dari item daftar ini, tambahkan atribut
android:onClick
keConstraintLayout
.
Tetapkan atribut keclickListener:onClick(sleep)
menggunakan lambda data binding, seperti yang ditunjukkan di bawah:
android:onClick="@{() -> clickListener.onClick(sleep)}"
Langkah 2: Teruskan pemroses klik ke holder tampilan dan objek binding
- Buka SleepNightAdapter.kt.
- Ubah konstruktor class
SleepNightAdapter
untuk menerimaval clickListener: SleepNightListener
. Saat mengikatViewHolder
, adaptor akan perlu memberikannya pemroses klik ini.
class SleepNightAdapter(val clickListener: SleepNightListener):
ListAdapter<SleepNight, SleepNightAdapter.ViewHolder>(SleepNightDiffCallback()) {
- Di
onBindViewHolder()
, perbarui panggilan keholder.bind()
agar juga meneruskan pemroses klik keViewHolder
. Anda akan mendapatkan error compiler karena menambahkan parameter ke panggilan fungsi.
holder.bind(getItem(position)!!, clickListener)
- Tambahkan parameter
clickListener
kebind()
. Untuk melakukannya, letakkan kursor pada error, lalu tekanAlt+Enter
(Windows) atauOption+Enter
(Mac) pada error untuk , seperti yang ditunjukkan pada screenshot di bawah.
- Di dalam class
ViewHolder
, di dalam fungsibind()
, tetapkan pemroses klik ke objekbinding
. Anda melihat error karena Anda perlu memperbarui objek pengikatan.
binding.clickListener = clickListener
- Untuk memperbarui binding data, Bersihkan dan Bangun ulang project Anda. (Anda mungkin juga perlu membatalkan validasi cache.) Jadi, Anda telah mengambil pemroses klik dari konstruktor adaptor, dan meneruskannya hingga ke penampung tampilan dan ke dalam objek binding.
Langkah 3: Menampilkan toast saat item diketuk
Sekarang Anda telah menempatkan kode untuk merekam klik, tetapi Anda belum menerapkan apa yang terjadi saat item daftar diketuk. Respons paling sederhana adalah menampilkan toast yang menunjukkan nightId
saat item diklik. Hal ini memastikan bahwa saat item daftar diklik, nightId
yang benar akan diambil dan diteruskan.
- Buka SleepTrackerFragment.kt.
- Di
onCreateView()
, temukan variabeladapter
. Perhatikan bahwa kode tersebut menampilkan error, karena sekarang mengharapkan parameter pemroses klik. - Tentukan pemroses klik dengan meneruskan lambda ke
SleepNightAdapter
. Lambda sederhana ini hanya menampilkan toast yang menunjukkannightId
, seperti yang ditunjukkan di bawah. Anda harus mengimporToast
. Berikut adalah definisi lengkap yang diperbarui.
val adapter = SleepNightAdapter(SleepNightListener { nightId ->
Toast.makeText(context, "${nightId}", Toast.LENGTH_LONG).show()
})
- Jalankan aplikasi, ketuk item, dan pastikan item menampilkan toast dengan
nightId
yang benar. Karena item memiliki nilainightId
yang meningkat, dan aplikasi menampilkan malam terbaru terlebih dahulu, item dengannightId
terendah berada di bagian bawah daftar.
Dalam tugas ini, Anda mengubah perilaku saat item di RecyclerView
diklik, sehingga alih-alih menampilkan toast, aplikasi akan membuka fragmen detail yang menampilkan informasi selengkapnya tentang malam yang diklik.
Langkah 1: Navigasi saat diklik
Pada langkah ini, alih-alih hanya menampilkan toast, Anda mengubah lambda pemroses klik di onCreateView()
dari SleepTrackerFragment
untuk meneruskan nightId
ke SleepTrackerViewModel
dan memicu navigasi ke SleepDetailFragment
.
Tentukan fungsi pengendali klik:
- Buka SleepTrackerViewModel.kt.
- Di dalam
SleepTrackerViewModel
, di bagian akhir, tentukan fungsi pengendali klikonSleepNightClicked()
.
fun onSleepNightClicked(id: Long) {
}
- Di dalam
onSleepNightClicked()
, picu navigasi dengan menyetel_navigateToSleepDetail
keid
yang diteruskan dari malam tidur yang diklik.
fun onSleepNightClicked(id: Long) {
_navigateToSleepDetail.value = id
}
- Menerapkan
_navigateToSleepDetail
. Seperti yang telah Anda lakukan sebelumnya, tentukanprivate MutableLiveData
untuk status navigasi. Danval
yang dapat diambil secara publik untuk melengkapinya.
private val _navigateToSleepDetail = MutableLiveData<Long>()
val navigateToSleepDetail
get() = _navigateToSleepDetail
- Tentukan metode yang akan dipanggil setelah aplikasi selesai menavigasi. Panggil
onSleepDetailNavigated()
dan tetapkan nilainya kenull
.
fun onSleepDetailNavigated() {
_navigateToSleepDetail.value = null
}
Tambahkan kode untuk memanggil pengendali klik:
- Buka SleepTrackerFragment.kt dan scroll ke bawah ke kode yang membuat adapter dan menentukan
SleepNightListener
untuk menampilkan toast.
val adapter = SleepNightAdapter(SleepNightListener { nightId ->
Toast.makeText(context, "${nightId}", Toast.LENGTH_LONG).show()
})
- Tambahkan kode berikut di bawah toast untuk memanggil pengendali klik,
onSleepNighClicked()
, disleepTrackerViewModel
saat item diketuk. TeruskannightId
, sehingga model tampilan mengetahui malam tidur mana yang harus diambil. Hal ini akan menimbulkan error karena Anda belum menentukanonSleepNightClicked()
. Anda dapat menyimpan, memberi komentar, atau menghapus toast, sesuai keinginan Anda.
sleepTrackerViewModel.onSleepNightClicked(nightId)
Tambahkan kode untuk mengamati klik:
- Buka SleepTrackerFragment.kt.
- Di
onCreateView()
, tepat di atas deklarasimanager
, tambahkan kode untuk mengamatiLiveData
navigateToSleepDetail
baru. SaatnavigateToSleepDetail
berubah, bukaSleepDetailFragment
, teruskannight
, lalu panggilonSleepDetailNavigated()
setelahnya. Karena Anda sudah pernah melakukannya di codelab sebelumnya, berikut kodenya:
sleepTrackerViewModel.navigateToSleepDetail.observe(this, Observer { night ->
night?.let {
this.findNavController().navigate(
SleepTrackerFragmentDirections
.actionSleepTrackerFragmentToSleepDetailFragment(night))
sleepTrackerViewModel.onSleepDetailNavigated()
}
})
- Jalankan kode Anda, klik item, dan ... aplikasi error.
Menangani nilai null di adaptor pengikatan:
- Jalankan kembali aplikasi dalam mode debug. Ketuk item, lalu filter log untuk menampilkan Error. Stack trace akan ditampilkan, termasuk sesuatu seperti di bawah ini.
Caused by: java.lang.IllegalArgumentException: Parameter specified as non-null is null: method kotlin.jvm.internal.Intrinsics.checkParameterIsNotNull, parameter item
Sayangnya, stack trace tidak menunjukkan dengan jelas di mana error ini dipicu. Salah satu kekurangan data binding adalah dapat mempersulit proses men-debug kode Anda. Aplikasi mengalami error saat Anda mengklik item, dan satu-satunya kode baru adalah untuk menangani klik.
Namun, ternyata dengan mekanisme penanganan klik baru ini, adaptor binding kini dapat dipanggil dengan nilai null
untuk item
. Secara khusus, saat aplikasi dimulai, LiveData
dimulai sebagai null
, jadi Anda perlu menambahkan pemeriksaan null ke setiap adaptor.
- Di
BindingUtils.kt
, untuk setiap adapter binding, ubah jenis argumenitem
menjadi nullable, dan bungkus isi denganitem?.let{...}
. Misalnya, adaptor untuksleepQualityString
akan terlihat seperti ini. Ubah adapter lainnya dengan cara yang sama.
@BindingAdapter("sleepQualityString")
fun TextView.setSleepQualityString(item: SleepNight?) {
item?.let {
text = convertNumericQualityToString(item.sleepQuality, context.resources)
}
}
- Jalankan aplikasi Anda. Ketuk item, dan tampilan detail akan terbuka.
Project Android Studio: RecyclerViewClickHandler.
Untuk membuat item di RecyclerView
merespons klik, lampirkan pemroses klik ke item daftar di ViewHolder
, dan tangani klik di ViewModel
.
Agar item di RecyclerView
merespons klik, Anda harus melakukan hal berikut:
- Buat class pemroses yang mengambil lambda dan menetapkannya ke fungsi
onClick()
.
class SleepNightListener(val clickListener: (sleepId: Long) -> Unit) {
fun onClick(night: SleepNight) = clickListener(night.nightId)
}
- Tetapkan pemroses klik pada tampilan.
android:onClick="@{() -> clickListener.onClick(sleep)}"
- Teruskan pemroses klik ke konstruktor adaptor, ke dalam pemegang tampilan, dan tambahkan ke objek binding.
class SleepNightAdapter(val clickListener: SleepNightListener):
ListAdapter<DataItem, RecyclerView.ViewHolder>(SleepNightDiffCallback()
holder.bind(getItem(position)!!, clickListener)
binding.clickListener = clickListener
- Di fragmen yang menampilkan tampilan recycler, tempat Anda membuat adaptor, tentukan pemroses klik dengan meneruskan lambda ke adaptor.
val adapter = SleepNightAdapter(SleepNightListener { nightId ->
sleepTrackerViewModel.onSleepNightClicked(nightId)
})
- Terapkan pengendali klik di model tampilan. Untuk klik pada item daftar, hal ini biasanya memicu navigasi ke fragmen detail.
Kursus Udacity:
Dokumentasi developer Android:
Bagian ini mencantumkan kemungkinan tugas pekerjaan rumah untuk siswa yang mengerjakan codelab ini sebagai bagian dari kursus yang dipimpin oleh instruktur. Instruktur menentukan hal berikut:
- Memberikan pekerjaan rumah jika diperlukan.
- Memberi tahu siswa cara mengirimkan tugas pekerjaan rumah.
- Memberi nilai tugas pekerjaan rumah.
Instruktur bisa menggunakan saran ini sesuai kebutuhan, dan bebas menugaskan pekerjaan rumah lain yang dirasa cocok.
Jika Anda menyelesaikan codelab ini sendiri, gunakan tugas pekerjaan rumah ini untuk menguji pengetahuan Anda.
Jawab pertanyaan-pertanyaan berikut
Pertanyaan 1
Asumsikan bahwa aplikasi Anda berisi RecyclerView
yang menampilkan item dalam daftar belanja. Aplikasi Anda juga menentukan class click-listener:
class ShoppingListItemListener(val clickListener: (itemId: Long) -> Unit) {
fun onClick(cartItem: CartItem) = clickListener(cartItem.itemId)
}
Bagaimana cara membuat ShoppingListItemListener
tersedia untuk pengikatan data? Pilih salah satu.
▢ Di file tata letak yang berisi RecyclerView
yang menampilkan daftar belanja, tambahkan variabel <data>
untuk ShoppingListItemListener
.
▢ Di file tata letak yang menentukan tata letak untuk satu baris dalam daftar belanja, tambahkan variabel <data>
untuk ShoppingListItemListener
.
▢ Di class ShoppingListItemListener
, tambahkan fungsi untuk mengaktifkan data binding:
fun onBinding (cartItem: CartItem) {dataBindingEnable(true)}
▢ Di class ShoppingListItemListener
, di dalam fungsi onClick()
, tambahkan panggilan untuk mengaktifkan data binding:
fun onClick(cartItem: CartItem) = {
clickListener(cartItem.itemId)
dataBindingEnable(true)
}
Pertanyaan 2
Di mana Anda menambahkan atribut android:onClick
agar item di RecyclerView
merespons klik? Pilih semua yang sesuai.
▢ Di file tata letak yang menampilkan RecyclerView
, tambahkan ke <androidx.recyclerview.widget.RecyclerView>
▢ Menambahkannya ke file tata letak untuk item di baris. Jika Anda ingin seluruh item dapat diklik, tambahkan ke tampilan induk yang berisi item di baris.
▢ Menambahkannya ke file tata letak untuk item di baris. Jika Anda ingin TextView
tunggal di item dapat diklik, tambahkan ke <TextView>
.
▢ Selalu tambahkan ke file tata letak untuk MainActivity
.
Mulai pelajaran berikutnya: