https://kotlinlang.org logo
#compose
Title
# compose
b

Ben Woodworth

11/10/2020, 2:29 AM
Is there a reason that modifiers use a fluent interface instead of a DSL? It seems non-idiomatic for Kotlin, so I'm curious what the design decisions were, or what advantages this approach has 🙂
g

gildor

11/10/2020, 2:31 AM
I don't see how this is not idiomatic Every modifier essentially wraps previous modifier, they are not properties of some object, so it's not clear how dsl block would help in this case, you definitely don't want to have a bunch of nesting for modifiers
r

romainguy

11/10/2020, 2:37 AM
And modifiers exist partly to avoid increased nesting that came from initially modeling everything as composables
b

Ben Woodworth

11/10/2020, 3:57 AM
I think it seems non-idiomatic because it's my first time seeing fluent interfaces in a Kotlin-specific library in like ~5 years of using the language. That makes sense though, with each call on modifier wrapping the receiver. I just looked at the source for
fun Modifier.then(Modifier)
and that gave me the answer. Each call on Modifier is wrapping the receiver with a
CompinedModifier
And I'm definitely a fan of the change to the fluent interface from the old nesting-heavy approach, and from the
Modifier.a() + Modifier.b()
approach. Makes sense now why something like this wouldn't work:
Copy code
// Modifier.preferredWidth(100.dp).padding(10.dp)
modifier = Modifier {
    preferredWidth(100.dp)
    padding(10.dp)
}
g

gildor

11/10/2020, 4:02 AM
Makes sense now why something like this wouldn’t work
Because it requies different API and order of modifiers matters, essentially to implement something like this you need:
Copy code
Modifier { // this is some ModifierScope
   add(preferredWidth(100.dp)) // add to list of modififers
} // Returns modififer, by converting List<Modifier> to a single nesting modifier
it doesn’t look some how better for me, chain modifier makes a lot of sense here, it more natural and less error prone (like forgot add modifier to chain) Also I completely disagree about “my first time seeing fluent interfaces in a Kotlin-specific library in like ~5 years of using the language” Kotlin has a lot chain operators, like map/onEach/filter/flatMap etc for Collection/sequences, whole Flow API and even block operators like let/run/apply/also which allow to keep chain of calls, and something like usage of Flow/Collection operators is a lot closer to case of modifiers (because each next item of chain wraps all previous ones)
👍 1
j

Javier

11/10/2020, 8:36 AM
I like the DSL, I have even thought to use infix functions but the DSL approach should be great. I think there is an open issue for a feature request in YouTrack where you can limit the scope so adding your DSL could be possible in the future
Copy code
modifier = Modifier {
    preferredWidth(100.dp)
    padding(10.dp)
}
g

gildor

11/10/2020, 11:50 AM
But how would it work?
j

Javier

11/10/2020, 1:39 PM
If all modifier functions where
infix
and
infix
would allow multiline.
Copy code
modifier = Modifier
    preferredWidth(100.dp)
    padding(10.dp)
If we can create a
Modifier
which
block
only can use this
infix
functions, we should get the the same behavior to the builder pattern, but I don't know what issue was in YouTrack, and if it lets us to solve our problem 100%. But know we have two problems so I think it is infeasible
g

gildor

11/10/2020, 1:51 PM
I highly doubt that it would be possible at all, parsing and resolution such ambiguous code would be nightmare and probably backward incompatible in many cases
And the main point how would it be better? Just to drop dot?
j

Javier

11/10/2020, 2:05 PM
Yes, and add curly braces, personally I like it more and I think Coil removed its DSL and is using builder pattern for the same problem we would have with Modifier, but there are no more changes
g

gildor

11/10/2020, 2:15 PM
I don't think that in case of Coil it's the same reason, there dsl would work, it doesn't wrap every item like modifier do
b

Ben Woodworth

11/10/2020, 8:19 PM
I'm not a fan of the infix approach either, since the syntax seems pretty foreign for Kotlin. I came up with a way to implement the DSL like I originally suggested, though:
j

Javier

11/10/2020, 8:24 PM
can you share the usage?
b

Ben Woodworth

11/10/2020, 8:27 PM
Sure!
Copy code
// Modifier.preferredWidth(100.dp).padding(10.dp)
modifier = Modifier {
    preferredWidth(100.dp)
    padding(10.dp)
}
Though something to note, operating on the return values won't affect the result (since the underlying
then
call is what's used by the builder, not the return value), so this is equivalent, and might be misleading:
Copy code
modifier = Modifier {
    preferredWidth(100.dp).preferredHeight(40.dp)
    padding(10.dp)
}
j

Javier

11/10/2020, 9:04 PM
Thank you 👍
2 Views