janvladimirmostert
01/30/2022, 9:28 PM@JsExport
@Serializable
@ExperimentalJsExport
@Suppress("ArrayInDataClass")
data class Field(
@JsName("name")
val type: String? = type,
@JsName("name")
val name: String? = type,
@JsName("children")
var children: Array<Field> = arrayOf(), // <<<--- this var is bugging me
@Transient
val handler: (Field.() -> Unit)? = null,
) {
init {
handler?.invoke(this@Field)
}
fun addChild(field: Field): Field {
return this.copy(
children = arrayOf(*this.children, field)
)
}
fun field(
name: String? = null,
handler: (Field.() -> Unit)? = null,
): Field {
return Field(
name = name ?: "${this@Field.name}.$type",
value = value,
type = type,
).let {
this.children += it // <<<--- this is the reason for the var children instead of val children
handler?.invoke(it)
it
}
}
}
The DSL when used is something like this (I've simplified it removing unimportant fields):
val field = Field(
name = "main",
) {
field(
name = "main",
) {
field(type = "column") {
field(type = "blah1")
}
field(type = "column") {
field(type = "group") {
field(type = "blah2.1")
field(type = "blah2.2")
}
field(type = "group") {
field(type = "blah2.3")
field(type = "blah2.4")
}
}
}
},
This field is to be used inside a MutableStateFlow, so whenever you add a child, it triggers the MutableStateFlow to emit to all Observers
// adding a child programatically:
mutableStateFlow.getAndUpdate {
it.copy(
field = field.addChild(someOtherField)
)
}
Only problem is, it's also possible to modify the original children in the Field class and if I make it immutable (val), then the DSL use-case seems tricky -
it'll need an internal private list, but I can't override getters inside a data class to include the internal children and if I change the data class to a regular class, I lose the ability to copy.
What is the clean way of getting this right assuming DSL and Immutability is doable?ephemient
01/30/2022, 9:32 PMdata class Field(
val name: String,
val children: List<Field>,
)
class FieldBuilder(val name: String) {
val children: MutableList<Field> = mutableListOf()
}
inline fun Field(name: string, builderAction: FieldBuilder.() -> Unit): Field {
val builder = FieldBuilder(name)
builder.builderAction()
return Field(builder.name, builder.children.toList())
}
inline fun FieldBuilder.field(name: String, builderAction: FieldBuilder.() -> Unit) {
children.add(Field(name, builderAction))
}
janvladimirmostert
01/30/2022, 9:43 PMephemient
01/30/2022, 9:49 PMField(...) { ... }
which works with an intermediary FieldBuilder
? this is is fairly common in Kotlin, for example
List(5) { it * it } // builder block runs with a MutableList receiver
Json { encodeDefaults = true } // builder block runs with a JsonBuilder receiver
etc.janvladimirmostert
01/30/2022, 9:54 PMephemient
01/30/2022, 10:00 PM