Kotlin for Android
Build modern Android apps with Kotlin's powerful features
š± What is Kotlin for Android?
Kotlin is Google's preferred language for Android development. It offers modern syntax, null safety, and seamless Java interoperability, making Android app development faster, safer, and more enjoyable than ever before.
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val button = findViewById<Button>(R.id.button)
button.setOnClickListener {
Toast.makeText(this, "Hello Kotlin!", Toast.LENGTH_SHORT).show()
}
}
}
Android Development Features
Null Safety
Eliminate null pointer exceptions
val name: String? = null
name?.let { println(it) }
View Binding
Type-safe access to views
binding.textView.text = "Hello"
binding.button.setOnClickListener { }
Coroutines
Asynchronous programming for UI
lifecycleScope.launch {
val data = fetchData()
updateUI(data)
}
Data Classes
Perfect for model objects
data class User(
val id: Int,
val name: String
)
š¹ Basic Activity with Kotlin
Create a simple Android activity using Kotlin:
import android.os.Bundle
import android.widget.Button
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
class MainActivity : AppCompatActivity() {
private lateinit var welcomeText: TextView
private lateinit var clickButton: Button
private var clickCount = 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Initialize views
welcomeText = findViewById(R.id.welcomeText)
clickButton = findViewById(R.id.clickButton)
// Set initial text
welcomeText.text = "Welcome to Kotlin Android!"
// Set click listener
clickButton.setOnClickListener {
handleButtonClick()
}
}
private fun handleButtonClick() {
clickCount++
val message = when {
clickCount == 1 -> "First click!"
clickCount < 5 -> "Click #$clickCount"
clickCount == 5 -> "You've clicked 5 times!"
else -> "Click count: $clickCount"
}
welcomeText.text = message
Toast.makeText(this, "Button clicked $clickCount times", Toast.LENGTH_SHORT).show()
}
}
Features Demonstrated:
⢠lateinit for delayed initialization
⢠String templates with $variable
⢠when expression for conditional logic
⢠Lambda expressions for click listeners
⢠Null-safe findViewById calls
š¹ Data Classes for Models
Use data classes to represent your app's data:
// User model
data class User(
val id: Int,
val name: String,
val email: String,
val isActive: Boolean = true
) {
fun getDisplayName(): String = name.uppercase()
}
// Product model with validation
data class Product(
val id: Int,
val name: String,
val price: Double,
val category: String
) {
val isExpensive: Boolean
get() = price > 100.0
fun getFormattedPrice(): String = "$%.2f".format(price)
}
// Usage in Activity or Fragment
class ProductActivity : AppCompatActivity() {
private val products = listOf(
Product(1, "Laptop", 999.99, "Electronics"),
Product(2, "Coffee Mug", 12.50, "Kitchen"),
Product(3, "Smartphone", 699.99, "Electronics")
)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_product)
displayProducts()
}
private fun displayProducts() {
val expensiveProducts = products.filter { it.isExpensive }
val electronicProducts = products.filter { it.category == "Electronics" }
println("Expensive products:")
expensiveProducts.forEach { product ->
println("${product.name}: ${product.getFormattedPrice()}")
}
println("Electronic products: ${electronicProducts.size}")
}
}
Output:
Expensive products:
Laptop: $999.99
Smartphone: $699.99
Electronic products: 2
š¹ View Binding Example
Use View Binding for type-safe view access:
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.example.myapp.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Initialize view binding
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
setupViews()
}
private fun setupViews() {
// Type-safe access to views
binding.titleText.text = "Welcome to Kotlin!"
binding.subtitleText.text = "Building amazing Android apps"
// Set click listeners
binding.primaryButton.setOnClickListener {
handlePrimaryAction()
}
binding.secondaryButton.setOnClickListener {
handleSecondaryAction()
}
// Update UI based on data
val user = User(1, "John Doe", "[email protected]")
updateUserInfo(user)
}
private fun handlePrimaryAction() {
binding.statusText.text = "Primary action clicked!"
binding.primaryButton.isEnabled = false
// Re-enable after delay (in real app, use coroutines)
binding.primaryButton.postDelayed({
binding.primaryButton.isEnabled = true
binding.statusText.text = "Ready for action"
}, 2000)
}
private fun handleSecondaryAction() {
val currentText = binding.titleText.text.toString()
binding.titleText.text = if (currentText.contains("!")) {
currentText.replace("!", "")
} else {
"$currentText!"
}
}
private fun updateUserInfo(user: User) {
binding.userNameText.text = "Hello, ${user.name}"
binding.userEmailText.text = user.email
binding.userStatusIcon.setImageResource(
if (user.isActive) R.drawable.ic_active else R.drawable.ic_inactive
)
}
}
View Binding Benefits:
- Type Safety: No more ClassCastException
- Null Safety: Views are guaranteed to exist
- Performance: No findViewById() calls needed
- Code Completion: IDE can suggest view names
š¹ Coroutines in Android
Handle asynchronous operations with coroutines:
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.*
class UserProfileActivity : AppCompatActivity() {
private lateinit var binding: ActivityUserProfileBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityUserProfileBinding.inflate(layoutInflater)
setContentView(binding.root)
loadUserProfile()
}
private fun loadUserProfile() {
// Show loading state
binding.progressBar.visibility = View.VISIBLE
binding.contentLayout.visibility = View.GONE
// Launch coroutine in lifecycle scope
lifecycleScope.launch {
try {
// Simulate network calls
val user = fetchUserData()
val posts = fetchUserPosts(user.id)
val followers = fetchFollowers(user.id)
// Update UI on main thread
displayUserProfile(user, posts, followers)
} catch (e: Exception) {
showError("Failed to load profile: ${e.message}")
} finally {
binding.progressBar.visibility = View.GONE
}
}
}
private suspend fun fetchUserData(): User = withContext(Dispatchers.IO) {
delay(1000) // Simulate network delay
User(1, "Alice Johnson", "[email protected]", true)
}
private suspend fun fetchUserPosts(userId: Int): List = withContext(Dispatchers.IO) {
delay(800)
listOf("My first post!", "Learning Kotlin", "Android development is fun")
}
private suspend fun fetchFollowers(userId: Int): Int = withContext(Dispatchers.IO) {
delay(600)
1250 // Follower count
}
private fun displayUserProfile(user: User, posts: List, followerCount: Int) {
binding.contentLayout.visibility = View.VISIBLE
binding.userName.text = user.name
binding.userEmail.text = user.email
binding.followerCount.text = "$followerCount followers"
binding.postCount.text = "${posts.size} posts"
// Display recent posts
val recentPosts = posts.take(3).joinToString("\n⢠", "⢠")
binding.recentPosts.text = recentPosts
}
private fun showError(message: String) {
binding.contentLayout.visibility = View.GONE
binding.errorText.text = message
binding.errorText.visibility = View.VISIBLE
}
}
Coroutines Benefits:
⢠Non-blocking UI operations
⢠Automatic lifecycle management
⢠Easy error handling with try-catch
⢠Parallel execution with async/await
⢠Context switching (Main, IO, Default)
š¹ Extension Functions for Android
Create useful extensions for Android development:
import android.content.Context
import android.view.View
import android.widget.Toast
import androidx.fragment.app.Fragment
// Context extensions
fun Context.showToast(message: String, duration: Int = Toast.LENGTH_SHORT) {
Toast.makeText(this, message, duration).show()
}
fun Context.showLongToast(message: String) {
showToast(message, Toast.LENGTH_LONG)
}
// View extensions
fun View.show() {
visibility = View.VISIBLE
}
fun View.hide() {
visibility = View.GONE
}
fun View.invisible() {
visibility = View.INVISIBLE
}
// String extensions for validation
fun String.isValidEmail(): Boolean {
return android.util.Patterns.EMAIL_ADDRESS.matcher(this).matches()
}
fun String.isValidPhone(): Boolean {
return android.util.Patterns.PHONE.matcher(this).matches()
}
// Usage in Activity
class LoginActivity : AppCompatActivity() {
private lateinit var binding: ActivityLoginBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityLoginBinding.inflate(layoutInflater)
setContentView(binding.root)
setupLoginForm()
}
private fun setupLoginForm() {
binding.loginButton.setOnClickListener {
val email = binding.emailInput.text.toString()
val password = binding.passwordInput.text.toString()
if (validateInput(email, password)) {
performLogin(email, password)
}
}
}
private fun validateInput(email: String, password: String): Boolean {
return when {
email.isEmpty() -> {
showToast("Please enter email")
false
}
!email.isValidEmail() -> {
showToast("Please enter valid email")
false
}
password.length < 6 -> {
showLongToast("Password must be at least 6 characters")
false
}
else -> true
}
}
private fun performLogin(email: String, password: String) {
// Show loading
binding.progressBar.show()
binding.loginButton.hide()
lifecycleScope.launch {
try {
delay(2000) // Simulate login
showToast("Login successful!")
// Navigate to main activity
} catch (e: Exception) {
showToast("Login failed: ${e.message}")
} finally {
binding.progressBar.hide()
binding.loginButton.show()
}
}
}
}
Extension Benefits:
⢠Cleaner, more readable code
⢠Reusable utility functions
⢠Better code organization
⢠Enhanced existing Android classes
⢠Improved developer productivity