Codelab ini adalah bagian dari kursus Dasar-Dasar Kotlin Android. Anda akan mendapatkan manfaat maksimal dari kursus ini jika Anda mengerjakan codelab secara berurutan. Semua codelab kursus tercantum di halaman landing codelab Dasar-Dasar Android Kotlin.
Pengantar
Dalam codelab ini, Anda akan mempelajari cara menambahkan header yang mencakup lebar daftar yang ditampilkan di RecyclerView
. Anda membuat aplikasi pelacak tidur dari codelab sebelumnya.
Yang harus sudah Anda ketahui
- Cara membuat antarmuka pengguna dasar menggunakan aktivitas, fragmen, dan tampilan.
- Cara menavigasi antar-fragmen, dan cara menggunakan
safeArgs
untuk meneruskan data antar-fragmen. - Lihat model, lihat pabrik model, transformasi, dan
LiveData
serta pengamatnya. - Cara membuat database
Room
, membuat DAO, dan menentukan entity. - Cara menggunakan coroutine untuk interaksi database dan tugas jangka panjang lainnya.
- Cara menerapkan
RecyclerView
dasar denganAdapter
,ViewHolder
, dan tata letak item. - Cara menerapkan data binding untuk
RecyclerView
. - Cara membuat dan menggunakan adaptor binding untuk mentransformasi data.
- Cara menggunakan
GridLayoutManager
. - Cara memperoleh dan menangani klik pada item di
RecyclerView.
Yang akan Anda pelajari
- Cara menggunakan lebih dari satu
ViewHolder
denganRecyclerView
untuk menambahkan item dengan tata letak yang berbeda. Secara khusus, cara menggunakanViewHolder
kedua untuk menambahkan header di atas item yang ditampilkan diRecyclerView
.
Yang akan Anda lakukan
- Buat aplikasi TrackMySleepQuality dari codelab sebelumnya dalam seri ini.
- Tambahkan header yang mencakup lebar layar di atas malam mode tidur yang ditampilkan di
RecyclerView
.
Aplikasi pelacak tidur yang Anda mulai memiliki tiga layar, diwakili oleh fragmen, seperti yang ditunjukkan pada gambar di bawah ini.
Layar pertama, yang ditampilkan di sebelah kiri, memiliki tombol untuk memulai dan menghentikan pelacakan. Layar menampilkan beberapa data tidur pengguna. Tombol Hapus secara permanen menghapus semua data yang telah dikumpulkan oleh aplikasi untuk pengguna. Layar kedua, yang ditampilkan di bagian tengah, adalah untuk memilih rating kualitas tidur. Layar ketiga adalah tampilan detail yang terbuka saat pengguna mengetuk item dalam petak.
Aplikasi ini menggunakan arsitektur yang disederhanakan dengan pengontrol UI, model tampilan dan LiveData
, serta database Room
untuk mempertahankan data tidur.
Dalam codelab ini, Anda menambahkan header ke petak item yang ditampilkan. Layar utama akhir Anda akan terlihat seperti ini:
Codelab ini mengajarkan prinsip umum menyertakan item yang menggunakan tata letak yang berbeda dalam RecyclerView
. Salah satu contoh umumnya adalah memiliki header dalam daftar atau petak. Daftar dapat memiliki satu header untuk menjelaskan konten item. Daftar juga dapat memiliki beberapa header untuk mengelompokkan dan memisahkan item dalam satu daftar.
RecyclerView
tidak mengetahui apa pun tentang data Anda atau jenis tata letak yang dimiliki setiap item. LayoutManager
menyusun item di layar, tetapi adaptor menyesuaikan data yang akan ditampilkan dan meneruskan holder tampilan ke RecyclerView
. Jadi, Anda akan menambahkan kode untuk membuat header di adaptor.
Dua cara menambahkan header
Pada RecyclerView
, setiap item dalam daftar sesuai dengan nomor indeks yang dimulai dari 0. Misalnya:
[Data Sebenarnya] -> [Tampilan Adaptor]
[0: SleepNight] -> [0: SleepNight]
[1: SleepNight] -> [1: SleepNight]
[2: SleepNight] -> [2: SleepNight]
Salah satu cara untuk menambahkan header ke daftar adalah dengan mengubah adaptor agar menggunakan ViewHolder
yang berbeda dengan memeriksa indeks tempat header perlu ditampilkan. Adapter
akan bertanggung jawab untuk melacak header. Misalnya, untuk menampilkan header di bagian atas tabel, Anda perlu menampilkan ViewHolder
yang berbeda untuk header saat menata letak item dengan indeks nol. Lalu semua item lain akan dipetakan dengan offset header, seperti yang ditunjukkan di bawah ini.
[Data Sebenarnya] -> [Tampilan Adaptor]
[0: Header]
[0: SleepNight] -> [1: SleepNight]
[1: SleepNight] -> [2: SleepNight]
[2: SleepNight] -> [3: SleepNight.
Cara lain untuk menambahkan header adalah dengan mengubah set data cadangan untuk petak data Anda. Karena semua data yang perlu ditampilkan disimpan dalam daftar, Anda dapat mengubah daftar agar menyertakan item untuk mewakili header. Ini sedikit lebih mudah dipahami, tetapi Anda harus memikirkan cara mendesain objek, sehingga Anda dapat menggabungkan berbagai jenis item menjadi satu daftar. Dengan diimplementasikan dengan cara ini, adaptor akan menampilkan item yang diteruskan ke sana. Jadi, item di posisi 0 adalah header dan item di posisi 1 adalah SleepNight
yang dipetakan langsung ke item di layar.
[Data Sebenarnya] -> [Tampilan Adaptor]
[0: Header] -> [0: Header]
[1: SleepNight] -> [1: SleepNight]
[2: SleepNight] -> [2: SleepNight]
[3: SleepNight] -> [3: SleepNight]
Tiap metodologi memiliki kelebihan dan kekurangan. Mengubah set data tidak menyebabkan banyak perubahan pada kode adaptor lainnya, dan Anda dapat menambahkan logika header dengan memanipulasi daftar data. Di sisi lain, menggunakan ViewHolder
yang berbeda dengan memeriksa indeks untuk header memberikan lebih banyak kebebasan pada tata letak header. Ini juga memungkinkan adaptor menangani cara data disesuaikan dengan tampilan tanpa mengubah data pendukung.
Dalam codelab ini, Anda akan memperbarui RecyclerView
untuk menampilkan header di awal daftar. Dalam hal ini, aplikasi Anda akan menggunakan ViewHolder
yang berbeda untuk header dibandingkan untuk item data. Aplikasi akan memeriksa indeks daftar untuk menentukan ViewHolder
yang akan digunakan.
Langkah 1: Buat class DataItem
Untuk memisahkan jenis item dan memungkinkan adaptor menangani "item", Anda dapat membuat class holder data yang mewakili SleepNight
atau Header
. Set data Anda kemudian akan menjadi daftar item pemegang data.
Anda bisa mendapatkan aplikasi awal dari GitHub, atau terus menggunakan aplikasi SleepTracker yang Anda buat di codelab sebelumnya.
- Download kode RecyclerViewHeaders-Starter dari GitHub. Direktori RecyclerViewHeaders-Starter berisi versi awal aplikasi SleepTracker yang diperlukan untuk codelab ini. Anda juga dapat melanjutkan dengan aplikasi yang telah selesai dari codelab sebelumnya jika mau.
- Buka SleepNightAdapter.kt.
- Di bawah class
SleepNightListener
, di tingkat atas, tentukan classsealed
yang disebutDataItem
yang mewakili item data.
Classsealed
menentukan jenis tertutup, yang berarti bahwa semua subclassDataItem
harus ditentukan dalam file ini. Hasilnya, jumlah subclass diketahui oleh compiler. Bagian lain dari kode Anda tidak dapat menentukan jenisDataItem
baru yang dapat merusak adaptor Anda.
sealed class DataItem {
}
- Di dalam isi class
DataItem
, tentukan dua class yang merepresentasikan berbagai jenis item data. Yang pertama adalahSleepNightItem
, yang merupakan wrapper di sekitarSleepNight
, sehingga menggunakan satu nilai yang disebutsleepNight
. Untuk membuatnya menjadi bagian dari class tertutup, mintalah memperluasDataItem
.
data class SleepNightItem(val sleepNight: SleepNight): DataItem()
- Class kedua adalah
Header
, untuk mewakili header. Karena header tidak memiliki data aktual, Anda dapat mendeklarasikannya sebagaiobject
. Artinya, hanya akan ada satu instanceHeader
. Sekali lagi, perluas ekstensiDataItem
.
object Header: DataItem()
- Di dalam
DataItem
, di tingkat class, tentukan propertiabstract
Long
bernamaid
. Bila adaptor menggunakanDiffUtil
untuk menentukan apakah dan bagaimana sebuah item berubah,DiffItemCallback
perlu mengetahui ID setiap item. Anda akan melihat error, karenaSleepNightItem
danHeader
harus mengganti properti abstrakid
.
abstract val id: Long
- Di
SleepNightItem
, gantiid
untuk menampilkannightId
.
override val id = sleepNight.nightId
- Dalam
Header
, gantiid
untuk menampilkanLong.MIN_VALUE
, yang merupakan angka yang sangat kecil (secara harfiah, -2 dengan pangkat 63). Jadi, hal ini tidak akan pernah bertentangan dengannightId
yang ada.
override val id = Long.MIN_VALUE
- Kode yang sudah selesai akan terlihat seperti ini, dan aplikasi Anda akan dibuat tanpa error.
sealed class DataItem {
abstract val id: Long
data class SleepNightItem(val sleepNight: SleepNight): DataItem() {
override val id = sleepNight.nightId
}
object Header: DataItem() {
override val id = Long.MIN_VALUE
}
}
Langkah 2: Buat ViewHolder untuk Header
- Buat tata letak untuk header dalam file resource tata letak baru yang disebut header.xml yang menampilkan
TextView
. Tidak ada yang menarik dari hal ini, jadi berikut kodenya.
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceLarge"
android:text="Sleep Results"
android:padding="8dp" />
- Ekstrak
"Sleep Results"
ke resource string dan beri namaheader_text
.
<string name="header_text">Sleep Results</string>
- Di SleepNightAdapter.kt, di dalam
SleepNightAdapter
, di atas classViewHolder
, buat classTextViewHolder
baru. Class ini meng-inflate tata letak textview.xml, dan menampilkan instanceTextViewHolder
. Karena Anda sudah melakukannya sebelumnya, berikut adalah kodenya, dan Anda harus mengimporView
danR
:
class TextViewHolder(view: View): RecyclerView.ViewHolder(view) {
companion object {
fun from(parent: ViewGroup): TextViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val view = layoutInflater.inflate(R.layout.header, parent, false)
return TextViewHolder(view)
}
}
}
Langkah 3: Perbarui SleepNightAdapter
Selanjutnya, Anda perlu memperbarui deklarasi SleepNightAdapter
. Class ini harus dapat menggunakan jenis holder tampilan apa pun, bukan hanya mendukung satu jenis ViewHolder
.
Menentukan jenis item
- Di
SleepNightAdapter.kt
, di tingkat atas, di bawah pernyataanimport
dan di atasSleepNightAdapter
, tentukan dua konstanta untuk jenis tampilan.RecyclerView
harus membedakan setiap jenis tampilan item, sehingga dapat menetapkan pemegang tampilan ke dalamnya dengan benar.
private val ITEM_VIEW_TYPE_HEADER = 0
private val ITEM_VIEW_TYPE_ITEM = 1
- Di dalam
SleepNightAdapter
, buat fungsi untuk menggantigetItemViewType()
agar menampilkan header atau konstanta item yang tepat, bergantung pada jenis item saat ini.
override fun getItemViewType(position: Int): Int {
return when (getItem(position)) {
is DataItem.Header -> ITEM_VIEW_TYPE_HEADER
is DataItem.SleepNightItem -> ITEM_VIEW_TYPE_ITEM
}
}
Mengupdate definisi SleepNightAdapter
- Dalam definisi
SleepNightAdapter
, perbarui argumen pertama untukListAdapter
dariSleepNight
menjadiDataItem
. - Dalam definisi
SleepNightAdapter
, ubah argumen umum kedua untukListAdapter
dariSleepNightAdapter.ViewHolder
menjadiRecyclerView.ViewHolder
. Anda akan melihat beberapa error untuk update yang diperlukan, dan header class Anda akan terlihat seperti yang ditunjukkan di bawah.
class SleepNightAdapter(val clickListener: SleepNightListener):
ListAdapter<DataItem, RecyclerView.ViewHolder>(SleepNightDiffCallback()) {
Mengupdate onCreateViewHolder()
- Ubah tanda tangan
onCreateViewHolder()
untuk menampilkanRecyclerView.ViewHolder
.
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder
- Perluas implementasi metode
onCreateViewHolder()
untuk menguji dan menampilkan holder tampilan yang sesuai untuk setiap jenis item. Metode yang diperbarui akan terlihat seperti kode di bawah ini.
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return when (viewType) {
ITEM_VIEW_TYPE_HEADER -> TextViewHolder.from(parent)
ITEM_VIEW_TYPE_ITEM -> ViewHolder.from(parent)
else -> throw ClassCastException("Unknown viewType ${viewType}")
}
}
Mengupdate onBindViewHolder()
- Ubah jenis parameter
onBindViewHolder()
dariViewHolder
menjadiRecyclerView.ViewHolder
.
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int)
- Tambahkan kondisi untuk hanya menetapkan data ke holder tampilan jika pemegangnya adalah
ViewHolder
.
when (holder) {
is ViewHolder -> {...}
- Transmisikan jenis objek yang ditampilkan oleh
getItem()
keDataItem.SleepNightItem
. FungsionBindViewHolder()
yang sudah selesai akan terlihat seperti ini.
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (holder) {
is ViewHolder -> {
val nightItem = getItem(position) as DataItem.SleepNightItem
holder.bind(nightItem.sleepNight, clickListener)
}
}
}
Mengupdate callback diffUtil
- Ubah metode di
SleepNightDiffCallback
untuk menggunakan classDataItem
baru, bukanSleepNight
. Sembunyikan peringatan lint seperti yang ditunjukkan pada kode di bawah ini.
class SleepNightDiffCallback : DiffUtil.ItemCallback<DataItem>() {
override fun areItemsTheSame(oldItem: DataItem, newItem: DataItem): Boolean {
return oldItem.id == newItem.id
}
@SuppressLint("DiffUtilEquals")
override fun areContentsTheSame(oldItem: DataItem, newItem: DataItem): Boolean {
return oldItem == newItem
}
}
Menambahkan dan mengirimkan header
- Di dalam
SleepNightAdapter
, di bawahonCreateViewHolder()
, tentukan fungsiaddHeaderAndSubmitList()
seperti yang ditunjukkan di bawah. Fungsi ini mengambil daftarSleepNight
. Alih-alih menggunakansubmitList()
, yang disediakan olehListAdapter
, untuk mengirimkan daftar, Anda akan menggunakan fungsi ini untuk menambahkan header, lalu mengirimkan daftar tersebut.
fun addHeaderAndSubmitList(list: List<SleepNight>?) {}
- Di dalam
addHeaderAndSubmitList()
, jika yang dimasukkan dalam daftar adalahnull
, hanya tampilkan header, jika tidak, lampirkan header ke header daftar, lalu kirim daftar.
val items = when (list) {
null -> listOf(DataItem.Header)
else -> listOf(DataItem.Header) + list.map { DataItem.SleepNightItem(it) }
}
submitList(items)
- Buka SleepTrackerFragment.kt dan ubah panggilan ke
submitList()
menjadiaddHeaderAndSubmitList()
.
- Jalankan aplikasi dan amati bagaimana header ditampilkan sebagai item pertama dalam daftar item tidur.
Ada dua hal yang perlu diperbaiki untuk aplikasi ini. Satu terlihat dan satunya tidak.
- Header ditampilkan di pojok kiri atas, dan tidak dapat dibedakan dengan mudah.
- Tidak terlalu penting untuk daftar singkat dengan satu header, tetapi Anda tidak boleh melakukan manipulasi daftar di
addHeaderAndSubmitList()
pada UI thread. Bayangkan sebuah daftar dengan ratusan item, sejumlah header, dan logika untuk menentukan tempat item harus disisipkan. Pekerjaan ini termasuk dalam coroutine.
Ubah addHeaderAndSubmitList()
untuk menggunakan coroutine:
- Di tingkat teratas dalam class
SleepNightAdapter
, tentukanCoroutineScope
denganDispatchers.Default
.
private val adapterScope = CoroutineScope(Dispatchers.Default)
- Di
addHeaderAndSubmitList()
, luncurkan coroutine diadapterScope
untuk memanipulasi daftar. Lalu beralih ke konteksDispatchers.Main
untuk mengirimkan daftar, seperti yang ditunjukkan pada kode di bawah ini.
fun addHeaderAndSubmitList(list: List<SleepNight>?) {
adapterScope.launch {
val items = when (list) {
null -> listOf(DataItem.Header)
else -> listOf(DataItem.Header) + list.map { DataItem.SleepNightItem(it) }
}
withContext(Dispatchers.Main) {
submitList(items)
}
}
}
- Kode Anda seharusnya dibuat dan dijalankan, dan Anda tidak akan melihat perbedaan apa pun.
Saat ini, header memiliki lebar yang sama seperti item lain pada petak, yang mengambil satu span secara horizontal dan vertikal. Seluruh petak sesuai dengan tiga item dengan satu lebar span secara horizontal, sehingga header harus menggunakan tiga span secara horizontal.
Untuk memperbaiki lebar header, Anda perlu memberi tahu GridLayoutManager
kapan harus memperluas data di semua kolom. Anda dapat melakukannya dengan mengonfigurasi SpanSizeLookup
di GridLayoutManager
. Ini adalah objek konfigurasi yang digunakan GridLayoutManager
untuk menentukan jumlah span yang akan digunakan untuk setiap item dalam daftar.
- Buka SleepTrackerFragment.kt.
- Temukan kode tempat Anda menentukan
manager
, di akhironCreateView()
.
val manager = GridLayoutManager(activity, 3)
- Di bawah
manager
, tentukanmanager.spanSizeLookup
, seperti yang ditunjukkan. Anda perlu membuatobject
karenasetSpanSizeLookup
tidak menggunakan lambda. Untuk membuatobject
di Kotlin, ketikobject : classname
, dalam hal iniGridLayoutManager.SpanSizeLookup
.
manager.spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
}
- Anda mungkin mendapatkan error compiler untuk memanggil konstruktor. Jika Anda melakukannya, buka menu intent dengan
Option+Enter
(Mac) atauAlt+Enter
(Windows) untuk menerapkan panggilan konstruktor.
- Kemudian Anda akan mendapatkan error di
object
yang menyatakan bahwa Anda perlu mengganti metode. Letakkan kursor diobject
, tekanOption+Enter
(Mac) atauAlt+Enter
(Windows) untuk membuka menu intent, lalu ganti metodegetSpanSize()
.
- Dalam isi
getSpanSize()
, tampilkan ukuran rentang yang tepat untuk setiap posisi. Posisi 0 memiliki ukuran rentang 3, dan posisi lainnya memiliki ukuran rentang 1. Kode yang sudah selesai akan terlihat seperti kode di bawah ini:
manager.spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
override fun getSpanSize(position: Int) = when (position) {
0 -> 3
else -> 1
}
}
- Untuk meningkatkan tampilan header Anda, buka header.xml dan tambahkan kode ini ke file tata letak header.xml.
android:textColor="@color/white_text_color"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:background="@color/colorAccent"
- Jalankan aplikasi Anda. Aplikasi akan terlihat seperti screenshot di bawah.
Selamat! Anda sudah selesai.
Project Android Studio: RecyclerViewHeaders
- Header umumnya adalah item yang membentang sesuai lebar daftar dan berfungsi sebagai judul atau pemisah. Daftar dapat memiliki satu header untuk menjelaskan konten item, atau beberapa header untuk mengelompokkan item dan memisahkan item satu sama lain.
RecyclerView
dapat menggunakan beberapa holder tampilan untuk mengakomodasi kumpulan item yang beragam; misalnya, header dan item daftar.- Salah satu cara untuk menambahkan header adalah dengan mengubah adaptor agar menggunakan
ViewHolder
yang berbeda dengan memeriksa indeks tempat header perlu ditampilkan.Adapter
bertanggung jawab untuk melacak header. - Cara lain untuk menambahkan header adalah dengan mengubah set data cadangan (daftar) untuk petak data, yang Anda lakukan di codelab ini.
Berikut adalah langkah-langkah utama untuk menambahkan header:
- Buat abstrak data di daftar dengan membuat
DataItem
yang dapat menyimpan header atau data. - Buat holder tampilan dengan tata letak untuk header di adaptor.
- Update adaptor dan metodenya untuk menggunakan jenis
RecyclerView.ViewHolder
apa pun. - Dalam
onCreateViewHolder()
, tampilkan jenis holder tampilan yang benar untuk item data. - Mengupdate
SleepNightDiffCallback
agar berfungsi dengan classDataItem
. - Buat fungsi
addHeaderAndSubmitList()
yang menggunakan coroutine untuk menambahkan header ke set data, lalu memanggilsubmitList()
. - Terapkan
GridLayoutManager.SpanSizeLookup()
untuk hanya membuat header tiga span lebar.
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. Terserah instruktur untuk melakukan hal berikut:
- Tugaskan pekerjaan rumah jika diperlukan.
- Berkomunikasi dengan siswa cara mengirimkan tugas pekerjaan rumah.
- Beri nilai tugas pekerjaan rumah.
Instruktur dapat menggunakan saran ini sesedikit atau sebanyak yang mereka inginkan, dan harus bebas memberikan pekerjaan rumah lain yang dirasa sesuai.
Jika Anda mengerjakan codelab ini sendiri, silakan gunakan tugas pekerjaan rumah ini untuk menguji pengetahuan Anda.
Jawab pertanyaan berikut
Pertanyaan 1
Manakah dari pernyataan berikut yang benar tentang ViewHolder
?
▢ Adaptor dapat menggunakan beberapa class ViewHolder
untuk menampung header dan berbagai jenis data.
▢ Anda dapat memiliki satu holder tampilan untuk data, dan satu view holder untuk header.
▢ RecyclerView
mendukung beberapa jenis header, tetapi datanya harus seragam.
▢ Saat menambahkan header, Anda membuat subclass RecyclerView
untuk menyisipkan header ke posisi yang tepat.
Pertanyaan 2
Kapan Anda harus menggunakan coroutine dengan RecyclerView
? Pilih semua pernyataan yang benar.
▢ Tidak pernah. RecyclerView
adalah elemen UI dan tidak boleh menggunakan coroutine.
▢ Menggunakan coroutine untuk tugas berdurasi panjang yang dapat memperlambat UI.
▢ Manipulasi daftar dapat memerlukan waktu yang lama, dan Anda harus selalu melakukannya menggunakan coroutine.
▢ Gunakan coroutine dengan fungsi penangguhan untuk menghindari pemblokiran thread utama.
Pertanyaan 3
Manakah di antara berikut ini yang TIDAK harus Anda lakukan saat menggunakan lebih dari satu ViewHolder
?
▢ Di ViewHolder
, sediakan beberapa file tata letak untuk di-inflate sesuai kebutuhan.
▢ Di onCreateViewHolder()
, tampilkan jenis holder tampilan yang benar untuk item data.
▢ Di onBindViewHolder()
, hanya ikat data jika holder tampilan adalah jenis holder tampilan yang benar untuk item data.
▢ Umumkan tanda tangan class adaptor untuk menerima RecyclerView.ViewHolder
.
Mulai pelajaran berikutnya:
Untuk link ke codelab lainnya dalam kursus ini, lihat halaman landing codelab Dasar-Dasar Kotlin Android.