DEV Community

Ted Hagos
Ted Hagos

Posted on

Recipe: Immutable Data Updates with Kotlin

Got a data class? Great! They give us a magical copy() method for free. Combine that with Kotlin's awesome scope functions like apply or also, and you've got a super-clean way to modify objects without actually modifying them. This means fewer bugs and safer concurrency!

Let's say we have a User with a profile image.

data class User(
    val id: String,
    val name: String,
    val email: String,
    val profileImageUrl: String? = null, // Can be null
    val isActive: Boolean = true
)
Enter fullscreen mode Exit fullscreen mode

Now, imagine we need to update a user's profile image and perhaps deactivate them. Instead of directly changing properties (which is fine for var but less safe), we can create a new User instance with just the changes.

fun main() {
    val initialUser = User(
        id = "user123",
        name = "Alice Wonderland",
        email = "alice@example.com",
        profileImageUrl = "http://old-image.com/alice.jpg"
    )

    println("Initial User: $initialUser")

    // --- The Recipe ---

    // 1. Update profile image and deactivate using copy() + apply
    val updatedUser = initialUser.copy(
        profileImageUrl = "http://new-image.com/alice_v2.jpg"
    ).apply {
        // 'this' refers to the NEWLY CREATED user object
        isActive = false // Note: 'isActive' would need to be 'var' for this direct assignment
    }

    // Better for val properties: use another copy() or constructor if needed
    // For val properties, if you truly want to make multiple changes to the *new* object
    // within a single flow, you might do something like this (more lines but fully immutable):
    val fullyImmutableUpdatedUser = initialUser.copy(
        profileImageUrl = "http://new-image.com/alice_v2.jpg"
    ).copy(isActive = false) // Chaining copy() calls for multiple immutable updates


    println("Updated User: $fullyImmutableUpdatedUser")
    println("Did initialUser change? ${initialUser.isActive} (No, it's still true!)")

    // Let's activate someone else with a simpler update
    val inactiveUser = User(id = "bob456", name = "Bob Builder", email = "bob@example.com", isActive = false)
    val activatedUser = inactiveUser.copy(isActive = true)
    println("Activated User: $activatedUser")
}
Enter fullscreen mode Exit fullscreen mode

What happened here?

  • initialUser remains unchanged thanks to data class and copy().
  • copy() creates a brand-new User object, letting us specify only the properties we want to change.
  • The original initialUser is still isActive = true, proving the immutability!

This pattern is fantastic for maintaining state in complex apps, especially when dealing with databases, network responses, or multi-threaded environments. Embrace immutability for cleaner, safer code!

Top comments (0)