Dasar-Dasar Android Kotlin 07.5: Header di RecyclerView

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 dengan Adapter, 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 dengan RecyclerView untuk menambahkan item dengan tata letak yang berbeda. Secara khusus, cara menggunakan ViewHolder kedua untuk menambahkan header di atas item yang ditampilkan di RecyclerView.

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.

  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 atas, tentukan class sealed yang disebut DataItem yang mewakili item data.

    Class sealed menentukan jenis tertutup, yang berarti bahwa semua subclass DataItem harus ditentukan dalam file ini. Hasilnya, jumlah subclass 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 menggunakan satu nilai yang disebut sleepNight. Untuk membuatnya menjadi bagian dari class tertutup, mintalah memperluas DataItem.
data class SleepNightItem(val sleepNight: SleepNight): DataItem()
  1. Class kedua adalah Header, untuk mewakili header. Karena header tidak memiliki data aktual, Anda dapat mendeklarasikannya sebagai object. Artinya, hanya akan ada satu instance Header. Sekali lagi, perluas ekstensi DataItem.
object Header: DataItem()
  1. Di dalam DataItem, di tingkat class, tentukan properti abstract Long bernama id. Bila adaptor menggunakan DiffUtil untuk menentukan apakah dan bagaimana sebuah item berubah, DiffItemCallback perlu mengetahui ID setiap item. Anda akan melihat error, karena SleepNightItem dan Header harus mengganti properti abstrak id.
abstract val id: Long
  1. Di SleepNightItem, ganti id untuk menampilkan nightId.
override val id = sleepNight.nightId
  1. Dalam Header, ganti id untuk menampilkan Long.MIN_VALUE, yang merupakan angka yang sangat kecil (secara harfiah, -2 dengan pangkat 63). Jadi, hal 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 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

  1. 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" />
  1. Ekstrak "Sleep Results" ke resource string dan beri nama 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 sudah 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. Class ini harus dapat menggunakan jenis holder tampilan apa pun, bukan hanya mendukung satu jenis ViewHolder.

Menentukan jenis item

  1. Di SleepNightAdapter.kt, di tingkat atas, di bawah pernyataan import dan di atas SleepNightAdapter, 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
  1. Di dalam SleepNightAdapter, buat fungsi untuk mengganti getItemViewType() 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

  1. Dalam definisi SleepNightAdapter, perbarui argumen pertama untuk ListAdapter dari SleepNight menjadi DataItem.
  2. Dalam definisi SleepNightAdapter, ubah argumen umum kedua untuk ListAdapter dari SleepNightAdapter.ViewHolder menjadi RecyclerView.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()

  1. Ubah tanda tangan onCreateViewHolder() untuk menampilkan RecyclerView.ViewHolder.
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder
  1. 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()

  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 holder tampilan jika pemegangnya 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)
            }
        }
    }

Mengupdate callback diffUtil

  1. Ubah metode di SleepNightDiffCallback untuk menggunakan class DataItem baru, bukan SleepNight. 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

  1. Di dalam SleepNightAdapter, di bawah onCreateViewHolder(), tentukan fungsi addHeaderAndSubmitList() seperti yang ditunjukkan di bawah. Fungsi ini mengambil daftar SleepNight. Alih-alih menggunakan submitList(), yang disediakan oleh ListAdapter, untuk mengirimkan daftar, Anda akan menggunakan fungsi ini untuk menambahkan header, lalu mengirimkan daftar tersebut.
fun addHeaderAndSubmitList(list: List<SleepNight>?) {}
  1. Di dalam addHeaderAndSubmitList(), jika yang dimasukkan dalam daftar adalah null, 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)
  1. Buka SleepTrackerFragment.kt dan ubah panggilan ke submitList() menjadi addHeaderAndSubmitList().
  1. 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:

  1. Di tingkat 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. Lalu beralih ke konteks Dispatchers.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)
            }
        }
    }
  1. 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.

  1. Buka SleepTrackerFragment.kt.
  2. Temukan kode tempat Anda menentukan manager, di akhir onCreateView().
val manager = GridLayoutManager(activity, 3)
  1. Di bawah manager, tentukan manager.spanSizeLookup, seperti yang ditunjukkan. Anda perlu membuat object karena setSpanSizeLookup tidak menggunakan 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 intent 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 di object, tekan Option+Enter (Mac) atau Alt+Enter (Windows) untuk membuka menu intent, lalu ganti metode getSpanSize().
  1. 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
            }
        }
  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"
  1. 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 class DataItem.
  • Buat fungsi addHeaderAndSubmitList() yang menggunakan coroutine untuk menambahkan header ke set data, lalu memanggil submitList().
  • 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: 8.1 Mendapatkan data dari internet

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