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)
}
}
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()
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!!)
}
}
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()
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)
Happy building!