Dasar-Dasar Android Kotlin 07.5: Header di RecyclerView

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

Dalam codelab ini, Anda akan mempelajari cara menambahkan header yang mencakup lebar daftar yang ditampilkan di RecyclerView. Anda akan membangun aplikasi pelacak tidur dari codelab sebelumnya.

Yang harus sudah Anda ketahui

  • Cara membuat antarmuka pengguna dasar menggunakan aktivitas, fragmen, dan tampilan.
  • Cara beralih antar-fragmen, dan cara menggunakan safeArgs untuk meneruskan data antar-fragmen.
  • Melihat model, melihat factory model, transformasi, dan LiveData serta pengamatnya.
  • Cara membuat database Room, membuat DAO, dan menentukan entity.
  • Cara menggunakan coroutine untuk interaksi database dan tugas berjalan lama lainnya.
  • Cara menerapkan RecyclerView dasar dengan Adapter, ViewHolder, dan tata letak item.
  • Cara menerapkan data binding untuk RecyclerView.
  • Cara membuat dan menggunakan adaptor binding untuk mengubah data.
  • Cara menggunakan GridLayoutManager.
  • Cara merekam dan menangani klik pada item di RecyclerView.

Yang akan Anda pelajari

  • Cara menggunakan lebih dari satu ViewHolder dengan RecyclerView untuk menambahkan item dengan tata letak yang berbeda. Khususnya, cara menggunakan ViewHolder kedua untuk menambahkan header di atas item yang ditampilkan di RecyclerView.

Yang akan Anda lakukan

  • Bangun aplikasi TrackMySleepQuality dari codelab sebelumnya dalam seri ini.
  • Tambahkan header yang mencakup lebar layar di atas malam tidur yang ditampilkan di RecyclerView.

Aplikasi pelacak tidur yang Anda mulai memiliki tiga 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 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 akan 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 berbeda dalam RecyclerView. Salah satu contoh umumnya adalah memiliki header dalam daftar atau petak Anda. Daftar dapat memiliki satu header untuk mendeskripsikan 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 setiap item. LayoutManager mengatur item di layar, tetapi adaptor menyesuaikan data yang akan ditampilkan dan meneruskan penampung tampilan ke RecyclerView. Jadi, Anda akan menambahkan kode untuk membuat header di adaptor.

Dua cara menambahkan header

Di RecyclerView, setiap item dalam daftar sesuai dengan nomor indeks yang dimulai dari 0. Contoh:

[Data Aktual] -> [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 untuk 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 harus menampilkan ViewHolder yang berbeda untuk header saat menyusun item berindeks nol. Kemudian, semua item lainnya akan dipetakan dengan offset header, seperti yang ditunjukkan di bawah.

[Data Aktual] -> [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 pendukung untuk petak data Anda. Karena semua data yang perlu ditampilkan disimpan dalam daftar, Anda dapat mengubah daftar untuk menyertakan item yang mewakili header. Hal ini sedikit lebih mudah dipahami, tetapi mengharuskan Anda memikirkan cara mendesain objek, sehingga Anda dapat menggabungkan berbagai jenis item ke dalam satu daftar. Dengan cara ini, adaptor akan menampilkan item yang diteruskan kepadanya. Jadi, item di posisi 0 adalah header, dan item di posisi 1 adalah SleepNight, yang dipetakan langsung ke apa yang ada di layar.

[Data Aktual] -> [Tampilan Adaptor]

[0: Header] -> [0: Header]

[1: SleepNight] -> [1: SleepNight]

[2: SleepNight] -> [2: SleepNight]

[3: SleepNight] -> [3: SleepNight]

Setiap metodologi memiliki kelebihan dan kekurangan. Mengubah set data tidak banyak mengubah kode adaptor lainnya, dan Anda dapat menambahkan logika header dengan memanipulasi daftar data. Di sisi lain, penggunaan ViewHolder yang berbeda dengan memeriksa indeks untuk header memberikan lebih banyak kebebasan pada tata letak header. Hal ini juga memungkinkan adaptor menangani cara data diadaptasi ke 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 dan item data. Aplikasi akan memeriksa indeks daftar untuk menentukan ViewHolder mana yang akan digunakan.

Langkah 1: Buat class DataItem

Untuk mengabstraksi jenis item dan membiarkan adaptor hanya menangani "item", Anda dapat membuat class penampung data yang merepresentasikan SleepNight atau Header. Set data Anda kemudian akan menjadi daftar item penampung data.

Anda dapat mendapatkan aplikasi awal dari GitHub, atau melanjutkan penggunaan aplikasi SleepTracker yang Anda buat di codelab sebelumnya.

  1. 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.
  2. Buka SleepNightAdapter.kt.
  3. Di bawah class SleepNightListener, di tingkat teratas, tentukan class sealed yang disebut DataItem yang merepresentasikan item data.

    Class sealed menentukan jenis tertutup, yang berarti semua subclass DataItem harus ditentukan dalam file ini. Akibatnya, jumlah subkelas diketahui oleh compiler. Bagian lain dari kode Anda tidak dapat menentukan jenis DataItem baru yang dapat merusak adaptor Anda.
sealed class DataItem {

 }
  1. Di dalam isi class DataItem, tentukan dua class yang merepresentasikan berbagai jenis item data. Yang pertama adalah SleepNightItem, yang merupakan wrapper di sekitar SleepNight, sehingga mengambil satu nilai yang disebut sleepNight. Untuk menjadikannya bagian dari class tertutup, buat class tersebut memperluas DataItem.
data class SleepNightItem(val sleepNight: SleepNight): DataItem()
  1. Class kedua adalah Header, untuk merepresentasikan header. Karena header tidak memiliki data aktual, Anda dapat mendeklarasikannya sebagai object. Artinya, hanya akan ada satu instance Header. Sekali lagi, buat class ini memperluas DataItem.
object Header: DataItem()
  1. Di dalam DataItem, di tingkat class, tentukan properti abstract Long bernama id. Saat adaptor menggunakan DiffUtil untuk menentukan apakah dan bagaimana item telah berubah, DiffItemCallback perlu mengetahui ID setiap item. Anda akan melihat error, karena SleepNightItem dan Header perlu mengganti properti abstrak id.
abstract val id: Long
  1. Di SleepNightItem, ganti id untuk menampilkan nightId.
override val id = sleepNight.nightId
  1. Di Header, ganti id untuk menampilkan Long.MIN_VALUE, yang merupakan angka yang sangat, sangat kecil (secara harfiah, -2 pangkat 63). Jadi, ID ini tidak akan pernah bertentangan dengan nightId yang ada.
override val id = Long.MIN_VALUE
  1. Kode yang sudah selesai akan terlihat seperti ini, dan aplikasi Anda akan dibangun 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

  1. Buat tata letak untuk header dalam file resource tata letak baru bernama header.xml yang menampilkan TextView. Tidak ada yang menarik tentang 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" />
  1. Ekstrak "Sleep Results" ke dalam resource string dan panggil header_text.
<string name="header_text">Sleep Results</string>
  1. Di SleepNightAdapter.kt, di dalam SleepNightAdapter, di atas class ViewHolder, buat class TextViewHolder baru. Class ini meng-inflate tata letak textview.xml, dan menampilkan instance TextViewHolder. Karena Anda pernah melakukannya sebelumnya, berikut adalah kodenya, dan Anda harus mengimpor View dan R:
    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. Daripada hanya mendukung satu jenis ViewHolder, class ini harus dapat menggunakan jenis penampung tampilan apa pun.

Menentukan jenis item

  1. Di SleepNightAdapter.kt, di tingkat teratas, di bawah pernyataan import dan di atas SleepNightAdapter, tentukan dua konstanta untuk jenis tampilan.

    RecyclerView harus membedakan jenis tampilan setiap item, sehingga dapat menetapkan holder tampilan dengan benar.
    private val ITEM_VIEW_TYPE_HEADER = 0
    private val ITEM_VIEW_TYPE_ITEM = 1
  1. Di dalam SleepNightAdapter, buat fungsi untuk mengganti getItemViewType() guna 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
        }
    }

Perbarui definisi SleepNightAdapter

  1. Dalam definisi SleepNightAdapter, perbarui argumen pertama untuk ListAdapter dari SleepNight menjadi DataItem.
  2. Dalam definisi SleepNightAdapter, ubah argumen generik kedua untuk ListAdapter dari SleepNightAdapter.ViewHolder menjadi RecyclerView.ViewHolder. Anda akan melihat beberapa error untuk pembaruan 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()

  1. Ubah tanda tangan onCreateViewHolder() untuk menampilkan RecyclerView.ViewHolder.
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder
  1. Perluas penerapan metode onCreateViewHolder() untuk menguji dan menampilkan holder tampilan yang sesuai untuk setiap jenis item. Metode yang diperbarui akan terlihat seperti kode di bawah.
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()

  1. Ubah jenis parameter onBindViewHolder() dari ViewHolder menjadi RecyclerView.ViewHolder.
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int)
  1. Tambahkan kondisi untuk hanya menetapkan data ke penampung tampilan jika penampung adalah ViewHolder.
        when (holder) {
            is ViewHolder -> {...}
  1. Transmisikan jenis objek yang ditampilkan oleh getItem() ke DataItem.SleepNightItem. Fungsi onBindViewHolder() 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)
            }
        }
    }

Memperbarui callback diffUtil

  1. Ubah metode di SleepNightDiffCallback untuk menggunakan class DataItem baru, bukan SleepNight. Tekan peringatan lint seperti yang ditunjukkan dalam kode di bawah.
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

  1. Di dalam SleepNightAdapter, di bawah onCreateViewHolder(), tentukan fungsi addHeaderAndSubmitList() seperti yang ditunjukkan di bawah. Fungsi ini menggunakan daftar SleepNight. Daripada menggunakan submitList(), yang disediakan oleh ListAdapter, untuk mengirimkan daftar, Anda akan menggunakan fungsi ini untuk menambahkan header, lalu mengirimkan daftar.
fun addHeaderAndSubmitList(list: List<SleepNight>?) {}
  1. Di dalam addHeaderAndSubmitList(), jika daftar yang diteruskan adalah null, hanya tampilkan header. Jika tidak, lampirkan header ke bagian atas daftar, lalu kirimkan daftar.
val items = when (list) {
                null -> listOf(DataItem.Header)
                else -> listOf(DataItem.Header) + list.map { DataItem.SleepNightItem(it) }
            }
submitList(items)
  1. Buka SleepTrackerFragment.kt dan ubah panggilan ke submitList() menjadi addHeaderAndSubmitList().
  1. Jalankan aplikasi Anda dan amati cara header Anda ditampilkan sebagai item pertama dalam daftar item tidur.

Ada dua hal yang perlu diperbaiki untuk aplikasi ini. Satu terlihat, dan satu lagi tidak.

  • Header muncul di sudut kiri atas, dan tidak mudah dibedakan.
  • Hal ini tidak terlalu penting untuk daftar pendek dengan satu header, tetapi Anda tidak boleh melakukan manipulasi daftar di addHeaderAndSubmitList() pada thread UI. Bayangkan daftar dengan ratusan item, beberapa header, dan logika untuk memutuskan tempat item perlu disisipkan. Tugas ini termasuk dalam coroutine.

Ubah addHeaderAndSubmitList() untuk menggunakan coroutine:

  1. Di level teratas dalam class SleepNightAdapter, tentukan CoroutineScope dengan Dispatchers.Default.
private val adapterScope = CoroutineScope(Dispatchers.Default)
  1. Di addHeaderAndSubmitList(), luncurkan coroutine di adapterScope untuk memanipulasi daftar. Kemudian, beralihlah ke konteks Dispatchers.Main untuk mengirimkan daftar, seperti yang ditunjukkan dalam kode di bawah.
 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)
            }
        }
    }
  1. Kode Anda akan dibangun dan dijalankan, dan Anda tidak akan melihat perbedaan apa pun.

Saat ini, header memiliki lebar yang sama dengan item lain di petak, yang menempati satu rentang secara horizontal dan vertikal. Seluruh petak memuat tiga item dengan lebar rentang satu secara horizontal, sehingga header harus menggunakan tiga rentang secara horizontal.

Untuk memperbaiki lebar header, Anda perlu memberi tahu GridLayoutManager kapan data harus mencakup semua kolom. Anda dapat melakukannya dengan mengonfigurasi SpanSizeLookup pada GridLayoutManager. Ini adalah objek konfigurasi yang digunakan GridLayoutManager untuk menentukan jumlah rentang yang akan digunakan untuk setiap item dalam daftar.

  1. Buka SleepTrackerFragment.kt.
  2. Temukan kode tempat Anda menentukan manager, di bagian akhir onCreateView().
val manager = GridLayoutManager(activity, 3)
  1. Di bawah manager, tentukan manager.spanSizeLookup, seperti yang ditunjukkan. Anda perlu membuat object karena setSpanSizeLookup tidak menerima lambda. Untuk membuat object di Kotlin, ketik object : classname, dalam hal ini GridLayoutManager.SpanSizeLookup.
manager.spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
}
  1. Anda mungkin mendapatkan error compiler untuk memanggil konstruktor. Jika Anda melakukannya, buka menu niat dengan Option+Enter (Mac) atau Alt+Enter (Windows) untuk menerapkan panggilan konstruktor.
  1. Kemudian, Anda akan mendapatkan error di object yang menyatakan bahwa Anda perlu mengganti metode. Letakkan kursor pada object, tekan Option+Enter (Mac) atau Alt+Enter (Windows) untuk membuka menu maksud, lalu ganti metode getSpanSize().
  1. Di 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:
    manager.spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
            override fun getSpanSize(position: Int) =  when (position) {
                0 -> 3
                else -> 1
            }
        }
  1. Untuk meningkatkan tampilan header, 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"
  1. Jalankan aplikasi Anda. Tampilannya akan terlihat seperti screenshot di bawah.

Selamat! Anda sudah selesai.

Project Android Studio: RecyclerViewHeaders

  • Header umumnya adalah item yang mencakup lebar daftar dan berfungsi sebagai judul atau pemisah. Daftar dapat memiliki satu header untuk mendeskripsikan konten item, atau beberapa header untuk mengelompokkan item dan memisahkan item satu sama lain.
  • RecyclerView dapat menggunakan beberapa penampung tampilan untuk mengakomodasi kumpulan item heterogen; misalnya, header dan item daftar.
  • Salah satu cara untuk menambahkan header adalah dengan mengubah adaptor Anda agar menggunakan ViewHolder yang berbeda dengan memeriksa indeks tempat header Anda perlu ditampilkan. Adapter bertanggung jawab untuk melacak header.
  • Cara lain untuk menambahkan header adalah dengan mengubah set data pendukung (daftar) untuk petak data Anda, seperti yang Anda lakukan dalam codelab ini.

Berikut adalah langkah-langkah utama untuk menambahkan header:

  • Abstrakkan data dalam daftar Anda dengan membuat DataItem yang dapat menyimpan header atau data.
  • Buat penampung tampilan dengan tata letak untuk header di adaptor.
  • Perbarui adaptor dan metodenya untuk menggunakan jenis RecyclerView.ViewHolder apa pun.
  • Di onCreateViewHolder(), tampilkan jenis penampung tampilan yang benar untuk item data.
  • Perbarui SleepNightDiffCallback agar berfungsi dengan class DataItem.
  • Buat fungsi addHeaderAndSubmitList() yang menggunakan coroutine untuk menambahkan header ke dataset, lalu panggil submitList().
  • Terapkan GridLayoutManager.SpanSizeLookup() agar hanya header yang memiliki lebar tiga rentang.

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

Manakah dari pernyataan berikut yang benar tentang ViewHolder?

▢ Adaptor dapat menggunakan beberapa class ViewHolder untuk menampung header dan berbagai jenis data.

▢ Anda dapat memiliki tepat satu holder tampilan untuk data, dan satu holder tampilan 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 sebaiknya Anda menggunakan coroutine dengan RecyclerView? Pilih semua pernyataan yang benar.

▢ Tidak pernah. RecyclerView adalah elemen UI dan tidak boleh menggunakan coroutine.

▢ Gunakan coroutine untuk tugas yang berjalan lama yang dapat memperlambat UI.

▢ Manipulasi daftar dapat memakan waktu lama, dan Anda harus selalu melakukannya menggunakan coroutine.

▢ Gunakan coroutine dengan fungsi penangguhan untuk menghindari pemblokiran thread utama.

Pertanyaan 3

Manakah dari berikut ini yang TIDAK harus Anda lakukan saat menggunakan lebih dari satu ViewHolder?

▢ Di ViewHolder, berikan beberapa file tata letak untuk di-inflate sesuai kebutuhan.

▢ Di onCreateViewHolder(), tampilkan jenis penampung tampilan yang benar untuk item data.

▢ Di onBindViewHolder(), hanya ikat data jika holder tampilan adalah jenis holder tampilan yang benar untuk item data.

▢ Menggeneralisasi tanda tangan class adaptor untuk menerima RecyclerView.ViewHolder apa pun.

Mulai pelajaran berikutnya: 8.1 Mendapatkan data dari internet

Untuk link ke codelab lain dalam kursus ini, lihat halaman landing codelab Dasar-Dasar Android Kotlin.