Kotlin Android Extensions
Kotlin Android Extensions is a powerful plugin that enhances Android development with Kotlin. It provides a set of features designed to reduce boilerplate code and make Android development more concise and enjoyable. In this guide, we'll explore what Kotlin Android Extensions are, how to use them, and best practices for implementation in your projects.
Introduction to Kotlin Android Extensions
Kotlin Android Extensions was introduced as a plugin to simplify the way developers interact with UI elements in Android apps. Traditionally, to access a view in your layout, you would need to call findViewById()
which could become tedious and error-prone when dealing with multiple views.
However, it's important to note that the original Kotlin Android Extensions with synthetic properties have been deprecated in favor of Jetpack's ViewBinding and other solutions. We'll cover both the legacy approach and modern alternatives in this guide.
Setting Up Kotlin Android Extensions
For Legacy Synthetic Properties (Deprecated)
In your module's build.gradle
file, you needed to add:
plugins {
id 'kotlin-android-extensions'
}
For Modern Projects
For modern projects, it's recommended to use ViewBinding instead:
android {
buildFeatures {
viewBinding true
}
}
View Binding with Synthetic Properties (Legacy)
The most popular feature of Kotlin Android Extensions was synthetic properties, which allowed direct access to views without using findViewById()
.
How It Worked
After importing the layout resources, you could directly access any view by its ID:
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Use the button directly by ID without findViewById
submitButton.setOnClickListener {
val username = usernameEditText.text.toString()
welcomeTextView.text = "Welcome, $username!"
}
}
}
Here, submitButton
, usernameEditText
, and welcomeTextView
are view IDs from your activity_main.xml
layout.
Example Layout (activity_main.xml)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:id="@+id/welcomeTextView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Welcome!"
android:textSize="20sp" />
<EditText
android:id="@+id/usernameEditText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Enter username"
android:layout_marginTop="16dp" />
<Button
android:id="@+id/submitButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Submit"
android:layout_marginTop="16dp" />
</LinearLayout>
Modern Alternative: ViewBinding
Since synthetic properties are deprecated, use ViewBinding for a type-safe way to access views:
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.submitButton.setOnClickListener {
val username = binding.usernameEditText.text.toString()
binding.welcomeTextView.text = "Welcome, $username!"
}
}
}
@Parcelize Extension
Another valuable feature of Kotlin Android Extensions is the @Parcelize
annotation, which simplifies creating Parcelable
implementations in Android.
Setting Up Parcelize
Add to your module's build.gradle
:
plugins {
id 'kotlin-parcelize'
}
Using @Parcelize
Without Parcelize, implementing Parcelable requires lots of boilerplate code:
// Traditional implementation - lots of code
class User(val name: String, val age: Int) : Parcelable {
constructor(parcel: Parcel) : this(
parcel.readString() ?: "",
parcel.readInt()
)
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeString(name)
parcel.writeInt(age)
}
override fun describeContents(): Int {
return 0
}
companion object CREATOR : Parcelable.Creator<User> {
override fun createFromParcel(parcel: Parcel): User {
return User(parcel)
}
override fun newArray(size: Int): Array<User?> {
return arrayOfNulls(size)
}
}
}
With Parcelize, it becomes much simpler:
// With @Parcelize - minimal code
@Parcelize
data class User(val name: String, val age: Int) : Parcelable
Using in Activities
// Sending Parcelable data
val user = User("John", 25)
val intent = Intent(this, DetailActivity::class.java).apply {
putExtra("USER_DATA", user)
}
startActivity(intent)
// Receiving Parcelable data
val user = intent.getParcelableExtra<User>("USER_DATA")
Experimental Extensions
Kotlin Android Extensions also provided other experimental features that might be useful in specific cases.
LayoutContainer Extension
This extension allowed you to define a custom container for synthetic properties:
class UserViewHolder(override val containerView: View) : RecyclerView.ViewHolder(containerView),
LayoutContainer {
fun bind(user: User) {
// Direct access to views in the container
userNameTextView.text = user.name
userAgeTextView.text = "${user.age} years old"
}
}
Best Practices
-
Migrate to ViewBinding: Since synthetic properties are deprecated, transition to ViewBinding for new and existing projects.
-
Continue Using @Parcelize: The
@Parcelize
annotation is still recommended and has been moved to a separate plugin. -
Be Aware of Cache Issues: When using the legacy extensions, caching can lead to memory leaks if not handled properly.
-
Import Specific Layouts: If using legacy extensions, import specific layouts rather than wildcard imports to avoid ambiguous references.
Real-World Application Example
Let's build a simple contact list app with Kotlin Android Extensions:
Modern Approach with ViewBinding
class ContactListActivity : AppCompatActivity() {
private lateinit var binding: ActivityContactListBinding
private val contactsList = mutableListOf<Contact>()
private lateinit var adapter: ContactAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityContactListBinding.inflate(layoutInflater)
setContentView(binding.root)
setupRecyclerView()
setupAddButton()
}
private fun setupRecyclerView() {
adapter = ContactAdapter(contactsList)
binding.contactsRecyclerView.apply {
layoutManager = LinearLayoutManager(this@ContactListActivity)
adapter = this@ContactListActivity.adapter
}
}
private fun setupAddButton() {
binding.addContactButton.setOnClickListener {
val name = binding.nameEditText.text.toString()
val phone = binding.phoneEditText.text.toString()
if (name.isNotEmpty() && phone.isNotEmpty()) {
val newContact = Contact(name, phone)
contactsList.add(newContact)
adapter.notifyItemInserted(contactsList.size - 1)
// Clear input fields
binding.nameEditText.text.clear()
binding.phoneEditText.text.clear()
} else {
Toast.makeText(this, "Please fill all fields", Toast.LENGTH_SHORT).show()
}
}
}
}
// Parcelable Contact class
@Parcelize
data class Contact(val name: String, val phoneNumber: String) : Parcelable
Contact Adapter Using ViewBinding
class ContactAdapter(private val contacts: List<Contact>) :
RecyclerView.Adapter<ContactAdapter.ContactViewHolder>() {
class ContactViewHolder(val binding: ItemContactBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(contact: Contact) {
binding.contactNameTextView.text = contact.name
binding.contactPhoneTextView.text = contact.phoneNumber
binding.root.setOnClickListener {
val context = binding.root.context
val intent = Intent(context, ContactDetailActivity::class.java).apply {
putExtra("CONTACT_DATA", contact)
}
context.startActivity(intent)
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ContactViewHolder {
val binding = ItemContactBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
return ContactViewHolder(binding)
}
override fun onBindViewHolder(holder: ContactViewHolder, position: Int) {
holder.bind(contacts[position])
}
override fun getItemCount() = contacts.size
}
Summary
Kotlin Android Extensions significantly simplified Android development by reducing boilerplate code. While the synthetic properties for view access are now deprecated in favor of ViewBinding, other extensions like @Parcelize
remain valuable tools in modern Android development.
Key takeaways:
- For view binding, use the official ViewBinding library instead of synthetic properties
- The
@Parcelize
annotation remains useful for creating Parcelable implementations easily - Kotlin Android Extensions made Android development more concise and type-safe
Additional Resources
- Official Kotlin Documentation on Android Extensions
- ViewBinding Documentation
- Parcelize Documentation
Exercises
- Convert an existing activity that uses
findViewById()
to use ViewBinding instead. - Create a data class that implements Parcelable using
@Parcelize
and pass it between two activities. - Build a simple to-do list app that utilizes ViewBinding and Parcelable objects.
- Implement a RecyclerView adapter using ViewBinding to display a list of custom objects.
By mastering these extensions and their modern alternatives, you'll be able to write cleaner, more concise, and type-safe Android applications with Kotlin!
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)