Kotlin Basics

Introduction
Kotlin is the modern, expressive language recommended by Google for Android development. Since its introduction in 2011 by JetBrains and Google's official adoption in 2019, Kotlin has revolutionized Android development with its concise syntax, null safety, and interoperability with Java.
In this comprehensive guide, we'll explore Kotlin fundamentals that every Android developer needs to master. Whether you're transitioning from Java or starting fresh, this post will give you a solid foundation for building robust Android applications.
Why Kotlin for Android?
Before diving into syntax, let's understand why Kotlin is the preferred choice for Android development:
- Null Safety: Built-in null safety prevents the infamous NullPointerException
- Concise Syntax: 40% less code compared to Java for the same functionality
- Interoperability: Seamless integration with existing Java code and libraries
- Modern Features: Coroutines, extension functions, and smart casts
- Google Support: Official language for Android development
Variables and Data Types
Kotlin provides two types of variables with different mutability characteristics:
Immutable Variables (val)
// Immutable - cannot be changed after initialization
val name = "Android Developer"
val age = 25
val isActive = true
// Type annotation is optional but can be explicit
val score: Double = 95.5
val items: List = listOf("item1", "item2")
Mutable Variables (var)
// Mutable - can be changed
var counter = 0
var message = "Hello"
var temperature = 22.5
// Changing values
counter = 10
message = "Hello, Kotlin!"
temperature = 25.0
Basic Data Types
// Numbers
val intNumber: Int = 42
val longNumber: Long = 42L
val floatNumber: Float = 42.0f
val doubleNumber: Double = 42.0
// Characters and Strings
val char: Char = 'A'
val string: String = "Hello Kotlin"
// Booleans
val isTrue: Boolean = true
val isFalse: Boolean = false
// Arrays
val numbers: Array = arrayOf(1, 2, 3, 4, 5)
val names: Array = arrayOf("Alice", "Bob", "Charlie")
Null Safety
One of Kotlin's most powerful features is its null safety system, which prevents null pointer exceptions at compile time.
Nullable vs Non-Nullable Types
// Non-nullable (default)
val name: String = "John"
// name = null // This would cause a compile error
// Nullable (use ?)
val nullableName: String? = "John"
val nullName: String? = null
// Safe call operator
val length = nullableName?.length // Returns null if nullableName is null
// Elvis operator (?:)
val safeLength = nullableName?.length ?: 0 // Returns 0 if nullableName is null
// Not-null assertion (!!) - use carefully
val forcedLength = nullableName!!.length // Throws exception if null
Practical Example
fun processUser(user: User?) {
// Safe call
val name = user?.name ?: "Unknown"
// Safe call with let
user?.let {
println("Processing user: ${it.name}")
// Process user safely
}
// Early return pattern
if (user == null) return
// Now user is guaranteed to be non-null
println("User age: ${user.age}")
}
Functions
Kotlin functions are powerful and flexible, supporting various parameter types and return values.
Basic Function Declaration
// Simple function
fun greet(name: String): String {
return "Hello, $name!"
}
// Single expression function
fun add(a: Int, b: Int) = a + b
// Function with default parameters
fun createUser(name: String, age: Int = 18, email: String = "") {
println("User: $name, Age: $age, Email: $email")
}
// Function with named parameters
fun main() {
createUser("John", email = "john@example.com")
createUser(name = "Alice", age = 25)
}
Extension Functions
// Extending String class
fun String.addExclamation() = "$this!"
fun String.isPalindrome(): Boolean {
return this == this.reversed()
}
// Usage
val message = "Hello"
println(message.addExclamation()) // "Hello!"
println("racecar".isPalindrome()) // true
Higher-Order Functions
// Function that takes another function as parameter
fun processNumbers(numbers: List, processor: (Int) -> Int): List {
return numbers.map(processor)
}
// Usage
val numbers = listOf(1, 2, 3, 4, 5)
val doubled = processNumbers(numbers) { it * 2 }
val squared = processNumbers(numbers) { it * it }
println(doubled) // [2, 4, 6, 8, 10]
println(squared) // [1, 4, 9, 16, 25]
Control Flow
Kotlin provides powerful and expressive control flow constructs.
If Expression
// If as expression
val max = if (a > b) a else b
// Multi-line if expression
val grade = if (score >= 90) {
"A"
} else if (score >= 80) {
"B"
} else if (score >= 70) {
"C"
} else {
"F"
}
When Expression (Enhanced Switch)
// When with multiple conditions
val dayOfWeek = 3
val dayName = when (dayOfWeek) {
1 -> "Monday"
2 -> "Tuesday"
3 -> "Wednesday"
4 -> "Thursday"
5 -> "Friday"
6, 7 -> "Weekend"
else -> "Invalid day"
}
// When with ranges
val age = 25
val category = when (age) {
in 0..12 -> "Child"
in 13..19 -> "Teenager"
in 20..64 -> "Adult"
else -> "Senior"
}
// When without argument (like if-else chain)
val x = 5
when {
x < 0 -> println("Negative")
x == 0 -> println("Zero")
else -> println("Positive")
}
Loops
// For loop with range
for (i in 1..5) {
println(i) // 1, 2, 3, 4, 5
}
// For loop with step
for (i in 0..10 step 2) {
println(i) // 0, 2, 4, 6, 8, 10
}
// For loop with downTo
for (i in 5 downTo 1) {
println(i) // 5, 4, 3, 2, 1
}
// For loop with collections
val names = listOf("Alice", "Bob", "Charlie")
for (name in names) {
println("Hello, $name!")
}
// For loop with indices
for (index in names.indices) {
println("$index: ${names[index]}")
}
// While loop
var counter = 0
while (counter < 5) {
println(counter)
counter++
}
Collections
Kotlin provides rich collection APIs with both mutable and immutable variants.
Lists
// Immutable list
val immutableList = listOf("a", "b", "c")
// Mutable list
val mutableList = mutableListOf("a", "b", "c")
mutableList.add("d")
// List operations
val numbers = listOf(1, 2, 3, 4, 5)
val doubled = numbers.map { it * 2 }
val evenNumbers = numbers.filter { it % 2 == 0 }
val sum = numbers.reduce { acc, num -> acc + num }
println(doubled) // [2, 4, 6, 8, 10]
println(evenNumbers) // [2, 4]
println(sum) // 15
Sets
// Immutable set
val immutableSet = setOf(1, 2, 3, 3) // Duplicates are ignored
// Mutable set
val mutableSet = mutableSetOf(1, 2, 3)
mutableSet.add(4)
// Set operations
val set1 = setOf(1, 2, 3)
val set2 = setOf(3, 4, 5)
val union = set1.union(set2)
val intersection = set1.intersect(set2)
println(union) // [1, 2, 3, 4, 5]
println(intersection) // [3]
Maps
// Immutable map
val immutableMap = mapOf("a" to 1, "b" to 2, "c" to 3)
// Mutable map
val mutableMap = mutableMapOf("a" to 1, "b" to 2)
mutableMap["c"] = 3
// Map operations
val userAges = mapOf("Alice" to 25, "Bob" to 30, "Charlie" to 35)
val adults = userAges.filter { it.value >= 18 }
val names = userAges.keys
val ages = userAges.values
println(adults) // {Alice=25, Bob=30, Charlie=35}
println(names) // [Alice, Bob, Charlie]
println(ages) // [25, 30, 35]
Classes and Objects
Kotlin classes are more concise than Java classes and provide many useful features.
Basic Class
// Simple class
class User(val name: String, var age: Int) {
fun introduce() {
println("Hi, I'm $name and I'm $age years old")
}
}
// Usage
val user = User("John", 25)
user.introduce()
user.age = 26 // Can change age because it's var
Data Classes
// Data class automatically provides toString, equals, hashCode, copy
data class Person(
val name: String,
val age: Int,
val email: String = ""
) {
fun isAdult() = age >= 18
}
// Usage
val person1 = Person("Alice", 25, "alice@example.com")
val person2 = person1.copy(age = 26) // Creates new instance with different age
println(person1) // Person(name=Alice, age=25, email=alice@example.com)
println(person1 == person2) // false
println(person1.isAdult()) // true
Object Declaration (Singleton)
// Singleton object
object DatabaseManager {
private val users = mutableListOf()
fun addUser(user: User) {
users.add(user)
}
fun getAllUsers(): List = users.toList()
}
// Usage
val user = User("John", 25)
DatabaseManager.addUser(user)
val allUsers = DatabaseManager.getAllUsers()
Companion Objects
class MyClass {
companion object {
const val MAX_SIZE = 100
fun createDefault(): MyClass {
return MyClass()
}
}
}
// Usage
val maxSize = MyClass.MAX_SIZE
val instance = MyClass.createDefault()
Practical Android Example
Let's see how these Kotlin concepts apply to Android development:
// Data class for API response
data class UserResponse(
val id: Int,
val name: String,
val email: String,
val avatar: String?
)
// Extension function for String
fun String.isValidEmail(): Boolean {
return android.util.Patterns.EMAIL_ADDRESS.matcher(this).matches()
}
// Higher-order function for API calls
fun fetchUser(
userId: Int,
onSuccess: (UserResponse) -> Unit,
onError: (String) -> Unit
) {
// Simulate API call
if (userId > 0) {
val user = UserResponse(
id = userId,
name = "John Doe",
email = "john@example.com",
avatar = "https://example.com/avatar.jpg"
)
onSuccess(user)
} else {
onError("Invalid user ID")
}
}
// Usage in Android Activity
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Using the fetchUser function
fetchUser(
userId = 1,
onSuccess = { user ->
// Handle successful response
updateUI(user)
},
onError = { error ->
// Handle error
showError(error)
}
)
}
private fun updateUI(user: UserResponse) {
// Update UI with user data
findViewById(R.id.nameText).text = user.name
findViewById(R.id.emailText).text = user.email
// Safe call for nullable avatar
user.avatar?.let { avatarUrl ->
// Load avatar image
loadImage(avatarUrl)
}
}
private fun showError(message: String) {
Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
}
}
Best Practices
- Prefer val over var: Use immutable variables when possible
- Use data classes: For simple data containers
- Leverage null safety: Use safe calls and Elvis operator
- Extension functions: Add functionality to existing classes
- Use when expressions: Instead of if-else chains
- Functional programming: Use map, filter, reduce for collections
Common Pitfalls to Avoid
- Force unwrapping: Avoid using !! unless absolutely necessary
- Mutable collections: Use immutable collections when possible
- Complex expressions: Break down complex expressions for readability
- Ignoring null safety: Always handle nullable types properly
Practice Exercises
Try these exercises to reinforce your Kotlin knowledge:
Exercise 1: String Processing
// Create a function that takes a string and returns:
// - The number of vowels
// - The number of consonants
// - Whether it's a palindrome
fun analyzeString(input: String): Map {
// Your code here
}
Exercise 2: List Operations
// Given a list of numbers, create functions to:
// - Find the second largest number
// - Group numbers by even/odd
// - Calculate the sum of squares
val numbers = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
Exercise 3: Data Class with Validation
// Create a data class for a User with:
// - Name (non-empty)
// - Age (18-120)
// - Email (valid format)
// Add validation methods
Next Steps
Now that you have a solid foundation in Kotlin basics, explore these advanced topics:
- Coroutines: Asynchronous programming
- Flow: Reactive streams
- Delegated Properties: Property delegation patterns
- Sealed Classes: Type-safe hierarchies
- Infix Functions: DSL-like syntax
Resources
Summary
Kotlin is a powerful, modern language that makes Android development more enjoyable and productive. Its null safety, concise syntax, and rich standard library help you write safer, more readable code. Mastering these fundamentals will set you up for success in Android development and prepare you for more advanced Kotlin features.
Practice regularly, experiment with the code examples, and don't hesitate to explore the official documentation. The more you use Kotlin, the more you'll appreciate its elegance and power.