'How to implement Builder pattern in Kotlin?
Hi I am a newbie in the Kotlin world. I like what I see so far and started to think to convert some of our libraries we use in our application from Java to Kotlin.
These libraries are full of Pojos with setters, getters and Builder classes. Now I have googled to find what is the best way to implement Builders in Kotlin but no success.
2nd Update: The question is how to write a Builder design-pattern for a simple pojo with some parameters in Kotlin? The code below is my attempt by writing java code and then using the eclipse-kotlin-plugin to convert to Kotlin.
class Car private constructor(builder:Car.Builder) {
var model:String? = null
var year:Int = 0
init {
this.model = builder.model
this.year = builder.year
}
companion object Builder {
var model:String? = null
private set
var year:Int = 0
private set
fun model(model:String):Builder {
this.model = model
return this
}
fun year(year:Int):Builder {
this.year = year
return this
}
fun build():Car {
val car = Car(this)
return car
}
}
}
Solution 1:[1]
One approach is to do something like the following:
class Car(
val model: String?,
val color: String?,
val type: String?) {
data class Builder(
var model: String? = null,
var color: String? = null,
var type: String? = null) {
fun model(model: String) = apply { this.model = model }
fun color(color: String) = apply { this.color = color }
fun type(type: String) = apply { this.type = type }
fun build() = Car(model, color, type)
}
}
Usage sample:
val car = Car.Builder()
.model("Ford Focus")
.color("Black")
.type("Type")
.build()
Solution 2:[2]
I personally have never seen a builder in Kotlin, but maybe it is just me.
All validation one needs happens in the init block:
class Car(val model: String,
val year: Int = 2000) {
init {
if(year < 1900) throw Exception("...")
}
}
Here I took a liberty to guess that you don't really wanted model and year to be changeable. Also those default values seems to have no sense, (especially null for name) but I left one for demonstration purposes.
An Opinion: The builder pattern used in Java as a mean to live without named parameters. In languages with named parameters (like Kotlin or Python) it is a good practice to have constructors with long lists of (maybe optional) parameters.
Solution 3:[3]
Because I'm using Jackson library for parsing objects from JSON, I need to have an empty constructor and I can't have optional fields. Also all fields have to be mutable. Then I can use this nice syntax which does the same thing as Builder pattern:
val car = Car().apply{ model = "Ford"; year = 2000 }
Solution 4:[4]
I have seen many examples that declare extra funs as builders. I personally like this approach. Save effort to write builders.
package android.zeroarst.lab.koltinlab
import kotlin.properties.Delegates
class Lab {
companion object {
@JvmStatic fun main(args: Array<String>) {
val roy = Person {
name = "Roy"
age = 33
height = 173
single = true
car {
brand = "Tesla"
model = "Model X"
year = 2017
}
car {
brand = "Tesla"
model = "Model S"
year = 2018
}
}
println(roy)
}
class Person() {
constructor(init: Person.() -> Unit) : this() {
this.init()
}
var name: String by Delegates.notNull()
var age: Int by Delegates.notNull()
var height: Int by Delegates.notNull()
var single: Boolean by Delegates.notNull()
val cars: MutableList<Car> by lazy { arrayListOf<Car>() }
override fun toString(): String {
return "name=$name, age=$age, " +
"height=$height, " +
"single=${when (single) {
true -> "looking for a girl friend T___T"
false -> "Happy!!"
}}\nCars: $cars"
}
}
class Car() {
var brand: String by Delegates.notNull()
var model: String by Delegates.notNull()
var year: Int by Delegates.notNull()
override fun toString(): String {
return "(brand=$brand, model=$model, year=$year)"
}
}
fun Person.car(init: Car.() -> Unit): Unit {
cars.add(Car().apply(init))
}
}
}
I have not yet found a way that can force some fields to be initialized in DSL like showing errors instead of throwing exceptions. Let me know if anyone knows.
Solution 5:[5]
For a simple class you don't need a separate builder. You can make use of optional constructor arguments as Kirill Rakhman described.
If you have more complex class then Kotlin provides a way to create Groovy style Builders/DSL:
Here is an example:
Solution 6:[6]
People nowdays should check Kotlin's Type-Safe Builders.
Using said way of object creation will look something like this:
html {
head {
title {+"XML encoding with Kotlin"}
}
// ...
}
A nice 'in-action' usage example is the vaadin-on-kotlin framework, which utilizes typesafe builders to assemble views and components.
Solution 7:[7]
I would say the pattern and implementation stays pretty much the same in Kotlin. You can sometimes skip it thanks to default values, but for more complicated object creation, builders are still a useful tool that can't be omitted.
Solution 8:[8]
I was working on a Kotlin project that exposed an API consumed by Java clients (which can't take advantage of the Kotlin language constructs). We had to add builders to make them usable in Java, so I created an @Builder annotation: https://github.com/ThinkingLogic/kotlin-builder-annotation - it's basically a replacement for the Lombok @Builder annotation for Kotlin.
Solution 9:[9]
I am late to the party. I also encountered the same dilemma if I had to use Builder pattern in the project. Later, after research I have realized it is absolutely unnecessary since Kotlin already provides the named arguments and default arguments.
If you really need to implement, Kirill Rakhman's answer is solid answer on how to implement in most effective way. Another thing you may find it useful is https://www.baeldung.com/kotlin-builder-pattern you can compare and contrast with Java and Kotlin on their implementation
Solution 10:[10]
A little changed and improved version of answers above
class MyDialog {
private var title: String? = null
private var content: String? = null
private var confirmButtonTitle: String? = null
private var rejectButtonTitle: String? = null
@DrawableRes
private var icon: Int? = null
fun show() {
// set dialog content here and show at the end
}
class Builder {
private var dialog: MyDialog = MyDialog()
fun title(title: String) = apply { dialog.title = title }
fun icon(@DrawableRes icon: Int) = apply { dialog.icon = icon }
fun content(content: String) = apply { dialog.content = content }
fun confirmTitle(confirmTitle: String) = apply { dialog.confirmButtonTitle = confirmTitle }
fun rejectButtonTitle(rejectButtonTitle: String) = apply { dialog.rejectButtonTitle = rejectButtonTitle }
fun build() = MyDialog()
}
}
And usage
MyDialog.Builder()
.title("My Title")
.content("My content here")
.icon(R.drawable.bg_edittext)
.confirmTitle("Accept")
.rejectButtonTitle("Cancel")
.build()
.show()
Solution 11:[11]
class Foo private constructor(@DrawableRes requiredImageRes: Int, optionalTitle: String?) {
@DrawableRes
@get:DrawableRes
val requiredImageRes: Int
val optionalTitle: String?
init {
this.requiredImageRes = requiredImageRes
this.requiredImageRes = optionalTitle
}
class Builder {
@DrawableRes
private var requiredImageRes: Int = -1
private var optionalTitle: String? = null
fun requiredImageRes(@DrawableRes imageRes: Int): Builder {
this.intent = intent
return this
}
fun optionalTitle(title: String): Builder {
this.optionalTitle = title
return this
}
fun build(): Foo {
if(requiredImageRes == -1) {
throw IllegalStateException("No image res provided")
}
return Foo(this.requiredImageRes, this.optionalTitle)
}
}
}
Solution 12:[12]
I implemented a basic Builder pattern in Kotlin with the follow code:
data class DialogMessage(
var title: String = "",
var message: String = ""
) {
class Builder( context: Context){
private var context: Context = context
private var title: String = ""
private var message: String = ""
fun title( title : String) = apply { this.title = title }
fun message( message : String ) = apply { this.message = message }
fun build() = KeyoDialogMessage(
title,
message
)
}
private lateinit var dialog : Dialog
fun show(){
this.dialog= Dialog(context)
.
.
.
dialog.show()
}
fun hide(){
if( this.dialog != null){
this.dialog.dismiss()
}
}
}
And finally
Java:
new DialogMessage.Builder( context )
.title("Title")
.message("Message")
.build()
.show();
Kotlin:
DialogMessage.Builder( context )
.title("Title")
.message("")
.build()
.show()
Solution 13:[13]
class Person(
val name:String,
val family:String,
val age:Int,
val nationalCode: String?,
val email: String?,
val phoneNumber: String?
) {
// Private constructor
private constructor(builder: Builder) : this (
builder.name,
builder.family,
builder.age,
builder.nationalCode,
builder.email,
builder.phoneNumber
)
// Builder class
// 1 Necessary parameters in Builder class : name , family
class Builder(val name :String,val family :String) {
// 2 Optional parameters in Builder class :
var age: Int = 0
private set
var nationalCode: String? = null
private set
var email: String? = null
private set
var phoneNumber: String? = null
private set
fun age(age: Int) = apply { this.age = age }
fun nationalCode(nationalCode: String) =
apply { this.nationalCode = nationalCode }
fun email(email: String) = apply { this.email = email }
fun phoneNumber(phoneNumber: String) =
apply { this.phoneNumber = phoneNumber }
// 3 Create
fun create() = Person(this)
}
}
for accessing :
val firstPerson = Person.Builder(
name = "Adnan",
family = "Abdollah Zaki")
.age(32)
.email("[email protected]")
.phoneNumber("+989333030XXX")
.nationalCode("04400XXXXX")
.create()
val secondPerson = Person.Builder(
name = "Foroogh",
family = "Varmazyar")
.create()
Solution 14:[14]
I just found a fun way to create builder in kotlin:
As you can see, moduleBuilder can be reuse for other grafana build.
Here is the code:
class Grafana(
private val module: String,
private val scene: String,
private val action: String,
private val metric: String
) {
companion object {
fun build(module: String, scene: String, action: String, metric: String) =
Grafana(module, scene, action, metric)
val builder = ::build.curriedBuilder()
private fun <P1, P2, P3, P4, R> Function4<P1, P2, P3, P4, R>.curriedBuilder() =
fun(p1: P1) = fun(p2: P2) = fun(p3: P3) = fun(p4: P4) = this(p1, p2, p3, p4)
}
fun report() = Unit
}
val moduleBuilder = Grafana.builder("module")
val scene = moduleBuilder("scene")
val gfA = scene("action")("metric")
gfA.report()
val sceneB = moduleBuilder("sceneB")
val gfB = sceneB("action")("metric")
gfB.report()
val gfC = Grafana.builder("xx")("xxx")("xxxx")("xxxx")
gfC.report()
Solution 15:[15]
you can use optional parameter in kotlin example:
fun myFunc(p1: String, p2: Int = -1, p3: Long = -1, p4: String = "default") {
System.out.printf("parameter %s %d %d %s\n", p1, p2, p3, p4)
}
then
myFunc("a")
myFunc("a", 1)
myFunc("a", 1, 2)
myFunc("a", 1, 2, "b")
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow

