Topik ini berfokus pada beberapa aspek bahasa Kotlin yang paling berguna saat melakukan pengembangan untuk Android.
Menangani fragmen
Bagian berikut menggunakan contoh Fragment
untuk menyoroti beberapa fitur terbaik Kotlin.
Pewarisan
Anda dapat mendeklarasikan class di Kotlin dengan kata kunci class
. Pada contoh berikut, LoginFragment
adalah subclass dari Fragment
. Anda dapat menunjukkan pewarisan menggunakan operator :
antara subclass dan induknya:
class LoginFragment : Fragment()
Pada deklarasi class ini, LoginFragment
bertanggung jawab memanggil konstruktor superclass-nya, Fragment
.
Dalam LoginFragment
, Anda dapat mengganti sejumlah callback siklus proses untuk menanggapi perubahan status pada Fragment
Anda. Untuk mengganti fungsi, gunakan kata kunci override
, seperti yang ditunjukkan pada contoh berikut:
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.login_fragment, container, false)
}
Untuk mereferensikan fungsi di class induk, gunakan kata kunci super
, seperti yang ditunjukkan pada contoh berikut:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
}
Nullability dan inisialisasi
Pada contoh sebelumnya, beberapa parameter dalam metode yang diganti memiliki jenis yang diberi akhiran dengan tanda tanya ?
. Hal tersebut menunjukkan bahwa argumen yang diteruskan untuk parameter ini bisa berupa null. Pastikan untuk menangani nullability dengan aman.
Di Kotlin, Anda harus menginisialisasi properti objek saat mendeklarasikan objek.
Ini berarti bahwa jika Anda memperoleh instance dari suatu class, Anda bisa langsung mereferensikan salah satu properti yang dapat diaksesnya. Namun, objek View
di dalam Fragment
belum siap untuk di-inflate hingga memanggil Fragment#onCreateView
, sehingga Anda perlu sebuah cara untuk mengalihkan inisialisasi properti untuk View
.
lateinit
memungkinkan Anda mengalihkan inisialisasi properti. Saat menggunakan lateinit
, Anda harus melakukan inisialisasi properti secepatnya.
Contoh berikut menunjukkan penggunaan lateinit
untuk menetapkan objek View
dalam onViewCreated
:
class LoginFragment : Fragment() {
private lateinit var usernameEditText: EditText
private lateinit var passwordEditText: EditText
private lateinit var loginButton: Button
private lateinit var statusTextView: TextView
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
usernameEditText = view.findViewById(R.id.username_edit_text)
passwordEditText = view.findViewById(R.id.password_edit_text)
loginButton = view.findViewById(R.id.login_button)
statusTextView = view.findViewById(R.id.status_text_view)
}
...
}
Konversi SAM
Anda dapat memproses peristiwa klik di Android dengan mengimplementasikan antarmuka OnClickListener
. Objek Button
berisi fungsi setOnClickListener()
yang menerima implementasi dari OnClickListener
.
OnClickListener
memiliki metode abstrak tunggal, onClick()
, yang harus Anda implementasikan. Karena setOnClickListener()
selalu mengambil OnClickListener
sebagai argumen, dan OnClickListener
selalu memiliki metode abstrak tunggal yang sama, implementasi ini dapat direpresentasikan menggunakan fungsi anonim di Kotlin. Proses ini dikenal sebagai konversi Metode Abstrak Tunggal, atau konversi SAM.
Konversi SAM dapat membuat kode Anda jauh lebih rapi. Contoh berikut menunjukkan cara menggunakan konversi SAM guna mengimplementasikan OnClickListener
untuk Button
:
loginButton.setOnClickListener {
val authSuccessful: Boolean = viewModel.authenticate(
usernameEditText.text.toString(),
passwordEditText.text.toString()
)
if (authSuccessful) {
// Navigate to next screen
} else {
statusTextView.text = requireContext().getString(R.string.auth_failed)
}
}
Kode dalam fungsi anonim yang diteruskan ke setOnClickListener()
akan dijalankan ketika pengguna mengklik loginButton
.
Objek pendamping
Objek pendamping
menyediakan mekanisme untuk menentukan variabel atau fungsi yang ditautkan
secara konseptual ke suatu jenis, tetapi tidak terikat ke objek tertentu. Objek
pendamping mirip dengan menggunakan kata kunci static
Java untuk variabel dan metode.
Pada contoh berikut, TAG
adalah konstanta String
. Anda tidak perlu instance unik dari String
untuk setiap instance LoginFragment
, sehingga Anda harus menentukannya pada objek pendamping:
class LoginFragment : Fragment() {
...
companion object {
private const val TAG = "LoginFragment"
}
}
Anda dapat menentukan TAG
di tingkat atas file, tetapi file tersebut juga mungkin memiliki sejumlah besar variabel, fungsi, dan class yang juga ditentukan di tingkat atas. Objek pendamping membantu menghubungkan variabel, fungsi, dan definisi class tanpa merujuk pada instance tertentu dari class tersebut.
Delegasi properti
Saat menginisialisasi properti, Anda mungkin mengulangi beberapa pola Android yang lebih umum, seperti mengakses ViewModel
dalam Fragment
. Guna menghindari kode duplikat berlebih, Anda bisa menggunakan sintaksis delegasi properti Kotlin.
private val viewModel: LoginViewModel by viewModels()
Delegasi properti menyediakan implementasi umum yang dapat Anda gunakan kembali di seluruh aplikasi. Android KTX menyediakan beberapa delegasi properti untuk Anda.
Misalnya, viewModels
mengambil ViewModel
yang dicakupkan ke Fragment
saat ini.
Delegasi properti menggunakan refleksi, yang menambahkan beberapa overhead performa. Hasilnya adalah sintaks ringkas yang menghemat waktu pengembangan.
Nullability
Kotlin memberikan aturan nullability yang ketat yang menjaga keamanan jenis di seluruh aplikasi Anda. Di Kotlin, referensi ke objek tidak boleh berisi nilai null secara default. Untuk menetapkan nilai null ke variabel, Anda harus mendeklarasikan jenis variabel nullable dengan menambahkan ?
ke bagian akhir jenis dasar.
Sebagai contoh, ekspresi berikut ini ilegal di Kotlin. name
adalah jenis String
dan tidak nullable:
val name: String = null
Guna mengizinkan nilai null, Anda harus menggunakan jenis String
nullable, String?
, seperti yang ditunjukkan pada contoh berikut:
val name: String? = null
Interoperabilitas
Aturan ketat Kotlin membuat kode Anda lebih aman dan ringkas. Aturan ini menurunkan kemungkinan memiliki NullPointerException
yang akan menyebabkan aplikasi Anda tidak bekerja. Selain itu, aturan tersebut mengurangi jumlah pemeriksaan null yang perlu Anda buat dalam kode.
Biasanya, Anda juga harus memanggil kode non-Kotlin saat menulis aplikasi Android, karena sebagian besar Android API ditulis dalam bahasa pemrograman Java.
Nullability adalah area utama tempat Java dan Kotlin memiliki perilaku berbeda. Java sedikit kurang ketat dengan sintaksis nullability.
Sebagai contoh, class Account
memiliki beberapa properti, termasuk properti String
yang bernama name
. Java tidak memiliki aturan Kotlin seputar nullability, melainkan bergantung pada anotasi nullability opsional untuk mendeklarasikan secara eksplisit apakah Anda dapat menetapkan nilai null atau tidak.
Karena framework Android pada dasarnya ditulis dalam Java, Anda mungkin mengalami skenario ini saat memanggil ke API tanpa anotasi nullability.
Jenis platform
Jika Anda menggunakan Kotlin untuk mereferensikan anggota name
yang tidak dianotasi yang ditentukan dalam class Account
Java, compiler tidak akan mengetahui apakah String
dipetakan ke String
atau String?
di dalam Kotlin. Ambiguitas ini direpresentasikan melalui jenis platform, String!
.
String!
tidak memiliki arti khusus untuk compiler Kotlin. String!
dapat merepresentasikan String
atau String?
, dan compiler ini memungkinkan Anda menetapkan nilai dari salah satu jenis tersebut. Ingat bahwa Anda berisiko memunculkan NullPointerException
jika merepresentasikan jenis sebagai String
dan menetapkan nilai null.
Untuk mengatasi masalah ini, sebaiknya gunakan anotasi nullability setiap kali Anda menulis kode di Java. Anotasi ini membantu developer Java dan Kotlin.
Misalnya, berikut adalah class Account
seperti yang ditentukan di Java:
public class Account implements Parcelable {
public final String name;
public final String type;
private final @Nullable String accessId;
...
}
Salah satu variabel anggota, accessId
, dianotasi dengan @Nullable
, yang menunjukkan bahwa variabel tersebut dapat menyimpan nilai null. Selanjutnya, Kotlin akan memperlakukan accessId
sebagai String?
.
Untuk menunjukkan bahwa variabel tidak boleh null, gunakan anotasi @NonNull
:
public class Account implements Parcelable {
public final @NonNull String name;
...
}
Dalam skenario ini, name
dianggap String
non-nullable di Kotlin.
Anotasi nullability disertakan di semua Android API baru dan di banyak Android API yang sudah ada. Banyak library Java telah menambahkan anotasi nullability untuk mendukung developer Kotlin dan Java dengan lebih baik.
Menangani nullability
Jika tidak yakin dengan jenis Java, Anda harus menganggapnya sebagai nullable.
Misalnya, anggota name
dari class Account
tidak dianotasi, jadi Anda harus menganggapnya sebagai String
nullable.
Jika ingin memotong name
agar nilainya tidak mencantumkan spasi kosong di awal atau akhir, Anda dapat menggunakan fungsi trim
Kotlin. Anda dapat memotong String?
secara aman dengan beberapa cara yang berbeda. Salah satu caranya adalah menggunakan operator pernyataan bukan null, !!
, seperti yang ditunjukkan pada contoh berikut:
val account = Account("name", "type")
val accountName = account.name!!.trim()
Operator !!
memperlakukan semua yang ada di sisi kiri sebagai non-null, sehingga dalam hal ini, Anda akan memperlakukan name
sebagai String
non-null. Jika hasil ekspresi di sebelah kirinya null, aplikasi akan memunculkan NullPointerException
.
Operator ini cepat dan mudah, tetapi harus digunakan seperlunya, karena operator tersebut dapat memperkenalkan ulang instance NullPointerException
ke dalam kode Anda.
Pilihan yang lebih aman adalah menggunakan operator panggilan aman, ?.
, seperti yang ditunjukkan pada contoh berikut:
val account = Account("name", "type")
val accountName = account.name?.trim()
Dengan menggunakan operator panggilan aman, jika name
non-null, maka hasil dari name?.trim()
adalah nilai nama tanpa spasi kosong di awal atau akhir. Jika name
adalah null, maka hasil dari name?.trim()
adalah null
. Artinya, aplikasi Anda tidak dapat memunculkan NullPointerException
saat menjalankan pernyataan ini.
Meskipun operator panggilan aman menyelamatkan Anda dari potensi NullPointerException
, operator akan meneruskan nilai null ke pernyataan berikutnya. Sebagai gantinya, Anda dapat menangani kasus null langsung menggunakan operator Elvis (?:
), seperti yang ditunjukkan pada contoh berikut:
val account = Account("name", "type")
val accountName = account.name?.trim() ?: "Default name"
Jika hasil ekspresi di sisi kiri operator Elvis null, maka nilai di sisi kanan ditetapkan ke accountName
. Teknik ini berguna untuk memberikan nilai default yang tidak akan bernilai null.
Anda juga dapat menggunakan operator Elvis untuk kembali dari fungsi awal, seperti yang ditunjukkan pada contoh berikut:
fun validateAccount(account: Account?) {
val accountName = account?.name?.trim() ?: "Default name"
// account cannot be null beyond this point
account ?: return
...
}
Perubahan Android API
Android API menjadi semakin cocok untuk Kotlin. Banyak Android API yang paling umum, termasuk AppCompatActivity
dan Fragment
, berisi anotasi nullability, dan panggilan tertentu seperti Fragment#getContext
memiliki lebih banyak alternatif yang cocok untuk Kotlin.
Misalnya, mengakses Context
dari Fragment
hampir selalu non-null, karena sebagian besar panggilan yang dibuat di Fragment
terjadi saat Fragment
dilampirkan ke Activity
(subclass Context
). Oleh karena itu, Fragment#getContext
tidak selalu menampilkan nilai non-null, karena terdapat skenario di mana Fragment
tidak dilampirkan ke Activity
. Dengan demikian, jenis nilai yang ditampilkan Fragment#getContext
nullable.
Karena Context
yang ditampilkan dari Fragment#getContext
adalah nullable (dan dianotasikan sebagai @Nullable), Anda harus memperlakukannya sebagai Context?
dalam kode Kotlin Anda.
Ini berarti menerapkan salah satu operator yang disebutkan sebelumnya untuk mengatasi nullability sebelum mengakses properti dan fungsinya. Untuk beberapa skenario tersebut, Android berisi API alternatif yang memberikan kemudahan ini.
Misalnya, Fragment#requireContext
menampilkan Context
non-null dan memunculkan IllegalStateException
jika dipanggil ketika Context
akan menjadi null. Dengan cara ini, Anda dapat memperlakukan Context
yang dihasilkan sebagai non-null tanpa perlu operator panggilan aman atau solusi.
Inisialisasi properti
Properti di Kotlin tidak diinisialisasi secara default. Properti tersebut harus diinisialisasi saat class yang menyertakannya diinisialisasi.
Anda dapat menginisialisasi properti dengan beragam cara. Contoh berikut menunjukkan cara menginisialisasi variabel index
dengan menetapkannya nilai pada deklarasi class:
class LoginFragment : Fragment() {
val index: Int = 12
}
Inisialisasi ini juga dapat ditentukan dalam blok penginisialisasi:
class LoginFragment : Fragment() {
val index: Int
init {
index = 12
}
}
Pada contoh di atas, index
diinisialisasi saat LoginFragment
dibuat.
Namun, Anda mungkin memiliki beberapa properti yang tidak dapat diinisialisasi selama pembuatan objek. Misalnya, Anda mungkin ingin mereferensikan View
dari dalam Fragment
, yang berarti tata letaknya harus di-inflate terlebih dahulu. Inflasi tidak terjadi jika Fragment
dibuat. Sebaliknya, hal tersebut di-inflate saat memanggil Fragment#onCreateView
.
Salah satu cara untuk mengatasi skenario ini adalah dengan mendeklarasikan tampilan sebagai nullable dan menginisialisasinya secepatnya, seperti yang ditunjukkan pada contoh berikut:
class LoginFragment : Fragment() {
private var statusTextView: TextView? = null
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
statusTextView = view.findViewById(R.id.status_text_view)
statusTextView?.setText(R.string.auth_failed)
}
}
Meskipun berfungsi seperti yang diharapkan, Anda sekarang harus mengelola nullability dari View
setiap kali Anda mereferensikannya. Solusi yang lebih baik adalah menggunakan lateinit
untuk inisialisasi View
, seperti yang ditunjukkan pada contoh berikut:
class LoginFragment : Fragment() {
private lateinit var statusTextView: TextView
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
statusTextView = view.findViewById(R.id.status_text_view)
statusTextView.setText(R.string.auth_failed)
}
}
Kata kunci lateinit
memungkinkan Anda menghindari inisialisasi properti ketika objek dibuat. Jika properti Anda direferensikan sebelum diinisialisasi, Kotlin akan memunculkan UninitializedPropertyAccessException
, jadi pastikan Anda menginisialisasi properti secepatnya.