Kotlin Getter and Setter

Understanding Kotlin Properties and Default Accessors

In Kotlin programming, properties are fundamental building blocks that simplify data encapsulation compared to traditional Java getters and setters. When you declare a Kotlin property, the compiler automatically generates default getter and setter methods behind the scenes.

Basic Property Declaration

Kotlin properties can be declared using two keywords:

  • var - Creates a mutable property with both getter and setter
  • val - Creates an immutable property with only a getter
class User {  
    var name: String = "Default Name"    // Mutable property with getter and setter  
    val userId: Int = 12345             // Immutable property with only getter  
}  

Here’s how Kotlin automatic getter setter generation works:

class Person {  
    var age: Int = 25  
        get() = field        // Default getter returns the backing field  
        set(value) {         // Default setter assigns value to backing field  
            field = value  
        }  
}  

Accessing Properties Through Getters and Setters

Kotlin property access is seamless and doesn’t require explicit method calls:

fun main() {  
    val person = Person()  
      
    // Using implicit setter  
    person.age = 30  
      
    // Using implicit getter  
    println("Person's age: ${person.age}")  
}  

Custom Getters in Kotlin

Custom getters in Kotlin allow you to add logic when retrieving property values. This is particularly useful for computed properties and derived values.

Creating Custom Getters

A custom getter is defined using the get() function after the property declaration:

class Rectangle {  
    var width: Double = 0.0  
    var height: Double = 0.0  
      
    // Custom getter for computed property  
    val area: Double  
        get() = width * height  
      
    // Custom getter with validation logic  
    val isSquare: Boolean  
        get() = width == height  
}  

Custom Getter with Transformation

Kotlin getter transformation allows you to modify returned values:

class UserProfile {  
    var email: String = ""  
      
    // Custom getter that always returns lowercase email  
    val normalizedEmail: String  
        get() = email.lowercase().trim()  
      
    // Custom getter with conditional logic  
    val displayName: String  
        get() = if (email.isNotEmpty()) email.substringBefore("@") else "Anonymous"  
}  

Custom Setters in Kotlin

Custom setters in Kotlin enable you to add validation, transformation, or side effects when assigning values to properties.

Basic Custom Setter

A custom setter is defined using the set(value) function:

class BankAccount {  
    var balance: Double = 0.0  
        set(value) {  
            if (value >= 0) {  
                field = value  
            } else {  
                println("Balance cannot be negative!")  
            }  
        }  
}  

Custom Setter with Validation

Kotlin property validation through custom setters ensures data integrity:

class UserAccount {  
    var password: String = ""  
        set(value) {  
            require(value.length >= 8) { "Password must be at least 8 characters" }  
            require(value.any { it.isDigit() }) { "Password must contain a digit" }  
            field = value  
        }  
      
    var age: Int = 0  
        set(value) {  
            require(value in 0..150) { "Age must be between 0 and 150" }  
            field = value  
        }  
}  

Custom Setter with Side Effects

Kotlin setter side effects can trigger additional operations:

class TemperatureMonitor {  
    var temperature: Double = 20.0  
        set(value) {  
            field = value  
            notifyObservers(value)  
            logTemperatureChange(value)  
        }  
      
    private fun notifyObservers(temp: Double) {  
        println("Temperature changed to: $temp°C")  
    }  
      
    private fun logTemperatureChange(temp: Double) {  
        // Log to file or database  
        println("Logging temperature: $temp")  
    }  
}  

Understanding Backing Fields in Kotlin

Kotlin backing fields are essential for property storage and preventing infinite recursion in custom accessors.

The Field Identifier

The field identifier in Kotlin backing field refers to the actual storage location of the property:

class Counter {  
    var count: Int = 0  
        get() = field           // Returns the backing field value  
        set(value) {  
            if (value >= 0) {  
                field = value   // Assigns to the backing field  
            }  
        }  
}  

When Backing Fields Are Generated

Kotlin backing field generation follows specific rules:

  1. Property uses default implementation of at least one accessor
  2. Custom accessor references the field identifier
class Example {  
    // Backing field generated (uses default accessors)  
    var property1: String = "value"  
      
    // Backing field generated (custom accessor uses 'field')  
    var property2: String = "value"  
        set(value) {  
            field = value.uppercase()  
        }  
      
    // No backing field generated (no default accessors, no 'field' reference)  
    val property3: String  
        get() = "computed value"  
}  

Avoiding Infinite Recursion

Kotlin recursive setter prevention is crucial for avoiding stack overflow errors:

class SafeProperty {  
    var value: String = ""  
        get() = field          // Correct: uses backing field  
        set(newValue) {  
            field = newValue   // Correct: assigns to backing field  
        }  
}  
  
class UnsafeProperty {  
    var value: String = ""  
        get() = value          // Wrong: causes infinite recursion  
        set(newValue) {  
            value = newValue   // Wrong: causes infinite recursion  
        }  
}  

Private Setters and Public Getters

Kotlin private setter allows you to create read-only properties from outside the class while maintaining write access internally.

Implementing Private Setters

class GameScore {  
    var score: Int = 0  
        private set    // Public getter, private setter  
      
    var playerName: String = ""  
        private set(value) {   // Custom private setter  
            field = value.trim().takeIf { it.isNotEmpty() } ?: "Anonymous"  
        }  
      
    fun increaseScore(points: Int) {  
        score += points  
    }  
      
    fun setPlayerName(name: String) {  
        playerName = name  
    }  
}  

Encapsulation with Private Setters

Kotlin encapsulation through private setters provides controlled access:

class DatabaseConnection {  
    var isConnected: Boolean = false  
        private set  
      
    var connectionUrl: String = ""  
        private set  
      
    fun connect(url: String): Boolean {  
        connectionUrl = url  
        isConnected = establishConnection(url)  
        return isConnected  
    }  
      
    fun disconnect() {  
        isConnected = false  
        connectionUrl = ""  
    }  
      
    private fun establishConnection(url: String): Boolean {  
        // Connection logic here  
        return true  
    }  
}  

Advanced Kotlin Property Patterns

Lazy Properties with Custom Getters

Kotlin lazy properties combined with custom getters provide efficient initialization:

class DataProcessor {  
    private var _data: String? = null  
      
    val processedData: String  
        get() {  
            if (_data == null) {  
                _data = loadAndProcessData()  
            }  
            return _data!!  
        }  
      
    private fun loadAndProcessData(): String {  
        // Expensive data processing  
        return "Processed data"  
    }  
}  

Property Delegation

Kotlin property delegation offers advanced property management:

import kotlin.properties.Delegates  
  
class ObservableProperties {  
    var observedProperty: String by Delegates.observable("initial") {   
        property, oldValue, newValue ->  
        println("Property ${property.name} changed from $oldValue to $newValue")  
    }  
      
    var vetoableProperty: Int by Delegates.vetoable(0) {   
        property, oldValue, newValue ->  
        newValue >= 0  // Only allow non-negative values  
    }  
}  

Backing Properties

Kotlin backing properties provide complete control over property access:

class SecureData {  
    private var _sensitiveData: String? = null  
      
    val sensitiveData: String?  
        get() = _sensitiveData?.let { decrypt(it) }  
      
    fun setSensitiveData(data: String) {  
        _sensitiveData = encrypt(data)  
    }  
      
    private fun encrypt(data: String): String = "encrypted_$data"  
    private fun decrypt(data: String): String = data.removePrefix("encrypted_")  
}  

Practical Android Development Examples

ViewModel with Custom Properties

import androidx.lifecycle.ViewModel  
import androidx.lifecycle.MutableLiveData  
import androidx.lifecycle.LiveData  
  
class UserProfileViewModel : ViewModel() {  
    private val _username = MutableLiveData<String>()  
    val username: LiveData<String> = _username  
      
    var userInput: String = ""  
        set(value) {  
            field = value.trim()  
            _username.value = field  
            validateInput(field)  
        }  
      
    var isValid: Boolean = false  
        private set  
      
    private fun validateInput(input: String) {  
        isValid = input.length >= 3 && input.isNotBlank()  
    }  
}  

Data Class with Validation

data class MobileUser(  
    private var _phoneNumber: String,  
    private var _email: String  
) {  
    var phoneNumber: String  
        get() = _phoneNumber  
        set(value) {  
            require(isValidPhoneNumber(value)) { "Invalid phone number format" }  
            _phoneNumber = value  
        }  
      
    var email: String  
        get() = _email.lowercase()  
        set(value) {  
            require(isValidEmail(value)) { "Invalid email format" }  
            _email = value  
        }  
      
    private fun isValidPhoneNumber(phone: String): Boolean {  
        return phone.matches(Regex("^\\+?[1-9]\\d{1,14}\$"))  
    }  
      
    private fun isValidEmail(email: String): Boolean {  
        return email.contains("@") && email.contains(".")  
    }  
      
    init {  
        phoneNumber = _phoneNumber  
        email = _email  
    }  
}  

Complete Working Example

Here’s a comprehensive example demonstrating Kotlin getter setter concepts in a real-world scenario:

import kotlin.math.max  
import kotlin.math.min  
  
/**  
 * Mobile App User Profile with comprehensive property management  
 * Demonstrates Kotlin getters, setters, backing fields, and validation  
 */  
class MobileAppUser(initialEmail: String) {  
      
    // Property with custom setter validation  
    var email: String = ""  
        set(value) {  
            require(value.isNotEmpty()) { "Email cannot be empty" }  
            require(value.contains("@")) { "Email must contain @ symbol" }  
            field = value.lowercase().trim()  
            println("Email updated to: $field")  
        }  
        get() = field  
      
    // Property with private setter and validation  
    var profileScore: Int = 0  
        private set(value) {  
            field = max(0, min(100, value))  // Clamp between 0 and 100  
        }  
      
    // Computed property with custom getter  
    val emailDomain: String  
        get() = email.substringAfter("@")  
      
    // Property with custom getter and backing field  
    var _displayName: String = ""  
    var displayName: String  
        get() = if (_displayName.isNotEmpty()) _displayName else email.substringBefore("@")  
        set(value) {  
            _displayName = value.trim().take(50)  // Limit to 50 characters  
        }  
      
    // Property with side effects in setter  
    var isActive: Boolean = true  
        set(value) {  
            if (field != value) {  // Only act on actual changes  
                field = value  
                onActiveStatusChanged(value)  
            }  
        }  
      
    // Read-only computed property  
    val userSummary: String  
        get() = "User: $displayName ($email) - Score: $profileScore - Active: $isActive"  
      
    init {  
        email = initialEmail  
        updateProfileScore(25)  // Initial score  
    }  
      
    // Public method to update private setter property  
    fun updateProfileScore(newScore: Int) {  
        profileScore = newScore  
    }  
      
    fun increaseScore(points: Int) {  
        profileScore += points  
    }  
      
    fun deactivateUser() {  
        isActive = false  
    }  
      
    private fun onActiveStatusChanged(active: Boolean) {  
        println("User ${displayName} is now ${if (active) "active" else "inactive"}")  
        // Could trigger analytics, notifications, etc.  
    }  
}  
  
// Usage example  
fun main() {  
    // Create user with validation  
    val user = MobileAppUser("john.doe@example.com")  
      
    // Property assignments trigger custom setters  
    user.displayName = "John Doe"  
    user.increaseScore(25)  
      
    // Access computed properties  
    println("Email domain: ${user.emailDomain}")  
    println("User summary: ${user.userSummary}")  
      
    // Trigger side effects  
    user.deactivateUser()  
      
    // Attempt invalid email (will throw exception)  
    try {  
        user.email = "invalid-email"  
    } catch (e: IllegalArgumentException) {  
        println("Validation error: ${e.message}")  
    }  
}  

Output:

Email updated to: john.doe@example.com  
Email domain: example.com  
User summary: User: John Doe (john.doe@example.com) - Score: 50 - Active: true  
User John Doe is now inactive  
Validation error: Email must contain @ symbol  

This example showcases all major Kotlin property concepts including custom getters, custom setters, backing fields, property validation, private setters, computed properties, and side effects in a practical Android development context.