Kotlin data class builders

Kotlin data class builders

Data classes are great in Kotlin. I consider it one of the killer features of the language.

They are great to group a few values together, quickly and safely. But what if you want to group a lot of values? With the data class constructor you can only create (immutable) instances when you provide all the values of the data class.

The builder pattern provides a solution for this problem. But what is the most concise and easy way to write this in Kotlin?

Builder inner class

    class CombinedData(
        val v1: String,
        val v2: Int
    ) {

        private constructor(builder: Builder) : 
          this(builder.v1!!, builder.v2!!)

        class Builder {
            var v1: String? = null
                private set
            var v2: Int? = null
                private set

            fun v1(v1: String) = apply { this.v1 = v1 }
            fun v2(v2: Int) = apply { this.v2 = v2 }

            fun build() = CombinedData(this)
        }
    }
Definition

The immutable class is the outer class. The builder is the inner class that is mutable and can be passed around while building the object. This is a very Java-like approach and quite verbose, but still works.

Note that we are using a private constructor so the only way to instantiate a CombinedData object is using the builder.

val cdBuilder = CombinedData.Builder()
[...]
cd.v1("string")
[...]
cd.v2(1)
[...]
val cd = cdBuilder.build()
Usage

Using data classes

data class CombinedData(
        val v1: String,
        val v2: Int
    ) {

        data class Builder(
            private var v1: String? = null,
            private var v2: Int? = null
        ) {

            fun v1(v1: String) = apply { this.v1 = v1 }
            fun v2(v2: Int) = apply { this.v2 = v2 }
            fun build() = CombinedData(v1!!, v2!!)
        }
    }
Definition

In this approach we are using data classes for the builder and the outer (immutable) class. We get all the benefits of the Kotlin data classes (such as automatic equals() and hashCode()).

Someone might be able to instantiate a CombinedData object without using the builder, but if this is not a big deal in your use-case, then this approach is more concise.

val cdBuilder = CombinedData.Builder()
[...]
cd.v1("string")
[...]
cd.v2(1)
[...]
val cd = cdBuilder.build()
Usage

Use copy()

Not exactly a builder pattern, but the most quick and dirty way to accomplish a builder-like behavior for a data class is use copy(). This will require having nullable fields, setting the required fields in each step and resetting the var holding the immutable value. Nevertheless, you won't have to write any additional line of code.

data class CombinedData(val v1: String? = null, val v2: Int? = null)

var cd = CombinedData()
[...]
cd = cd.copy(v1 = "string")
[...]
cd = cd.copy(v2 = 1)
Definition + Usage

Happy building!

Show Comments