``` Text("Hello World") .padding(...
# compose
f
Copy code
Text("Hello World")
         .padding()
Seems nicer than
Copy code
Padding {
            Text("Hello World")
         }
I have to be honest.
👌 2
🚫 7
☝️ 4
m
It doesn't. I don't know which widgets have
padding()
, why, and how should I support it in my custom widgets. External margins/paddings feel natural and simple.
💯 10
f
All widgets can have
padding()
, just like you can nest any widget in
Padding{}
. Padding is a modifier added to a widget, in which case a widget extension method is in order and not a "helper method" that receives a widget. This has all of the added benefits of using extension methods over "helper methods" - being more discover-able and being easier to apply on already typed objects. This also has the obvious upside of reducing nesting.
2
l
@Adam Powell Would composable extensions be possible for generic things like padding? I have to admit I'm a bit afraid of readability with nesting for simple things like that, and when I saw Compose example in I/O talk, I immediately wondered why so much nesting for just a padding. I'd like to also have things like
Text(someLongString).wrapInScrollContainer()
. In fact, that's how I'm currently doing it with `View`s, using
wrapInScrollView()
or
wrapInRecyclerView()
extension functions.
r
@Fudge it's also how our base view class became so big
9
l
@romainguy If Compose was based on returned values, extensions would work as they would return a padding widget wrapping its receiver, without having a 30K LOC base View class. I know it doesn't work that way in Compose, but would such a syntax be possible?
f
Having a big class as a result of this doesn't seem like a very big drawback. And also not a thing when you have extension methods, unless you want some kind of inheritence
m
but would such a syntax be possible?
Annotation-based Unit-returning bullshit, with implicit receiver, of course, should be reworked.
😂 1
🙂 1
f
You can always introduce more magic
😱 1
😂 1
Might not be worth the effort though
e
Developer ergonomics do require a lot of magic. There is simply no way around it. There are tradeoffs every framework makes with this. Look at Swift UI, for example. Widgets are structs which are essentially classes (so last-century), but when you construct them they "automagically" add themselves to container even though the return value of their "constructor" is simply ignored. Yet if you apply ".padding()" they magically modify themselves, so are they also mutable? Or do they magically add themselves to parent if you don't use the result type? (Edit: the latter in fact)
We know there are the ways to add a component to a parent's list of children: • Explict (flutter) -- inconvenient boilerplate • Implicit with a separate language (like React JSX) -- extremely intrusive, all magic • Implicit at creation time (compose) • Implicit at "result type drop" (Swift UI)
All convenient solutions are magical. Just pick which one looks more natural to you.
f
I pick the SwiftUI approach, but that doesn't mean I get to use it for Android development :(
e
In fact, it does not really matter. If you format them similarly they don't look different enough to matter:
Copy code
Text("...").padding()
vs
Copy code
Padding { Text("...") }
It is a matter of taste, style, and habit. I, for one, have been programming with Swing for too much, so the second one looks more natural for me.
👍 3
2
f
That's not a fair example
Padding is usually applied to 5-20 lines of code, in which case you might not want to put it all on one line
2
r
@Fudge But then in SwiftUI you’ll need a way to group those 5-20 lines of widgets just to call
.padding()
on them
7
e
I'm worried about Swift's
Text("...").color(...)
example. I'll have to dig how it really works, but it looks like it have to mutate 😱 the view, which makes the whole thing not-so-very-reactive-at-all
@Fudge If you repeat something over 5-20 lines of code that's where I'd typically suggest "extract function" refactoring to avoid this repetition. I personally start thinking about it at the second repeat.
f
Sorry, I don't see how that is related @elizarov (regarding the last comment)
r
I believe he mistook your "applied to 5-20 lines of code" to mean "repeated 5-20 times in code"
e
Maybe I did not understand what do you mean by "applied to 5-20 lines of code". I though you mean apply padding to 5-20 different components. If you mean make 5-20 different components and make padding around then it is exactly how Compose does it in the best way.
r
@elizarov I think he meant more like
Padding { <20 lines here> }
vs
<20 lines here>.padding()
👆 1
e
Got it
r
Which, admittedly, makes me lean more towards @romainguy's version, though it comes right back to your statement that it's a matter of taste/style/habit.
r
We want to emphasize composition
1
r
Oh, well, you should have just named it that way then 🧌 Sorry, couldn't help myself
😆 3
r
I guess ^^
e
Frankly, I also like immutability that compose adds to mix (no changing color after the fact), but making immutability ergonomic is quite challenging.
5
r
It is
e
To be fair here, we still have not really figured out ergonomic immutability for the Kotlin language itself.
🤔 2
f
(regarding a way to group widgets) @romainguy you mean with brackets? The usual case is that there is only one child so there's no real need for that. When you have multiple children you usually want to have a designated component for that (row, column, scrolling, etc). So you can just use
padding
at the end of the
Row
for example. It definitely may be that there is enough cases in which you have multiple children without a layout widget wrapping them, but I doubt that.
m
flutter way
Copy code
Padding(
  padding: EdgeInsets.all(8.0),
  child: const Card(child: Text('Hello World!')),
)
which uses padding as a widget.
e
The open design of swift language in its glory....
🧌 3
❤️ 1
📯 1
t
Yup 😒
k
I am not necessarily in favour of
Text("").padding()
but Kotlin capable of supporting both of this solution.
Copy code
Widget.padding() : Widget = Padding { this }
the unit return types just removes this great Kotlin language feature from the developers.
l
Wondering if making it an extension over
Unit
that is annotated with
@Composable
could work. Maybe it's as simple as that?
m
Such a question will appear every minute if the framework ignores language rules and invents its own.
☝️ 2
d
I would wonder about this from the perspective of "What do we want the syntax to look like, then what would Kotlin need to do to support our desired syntax?" It seems like improving Kotlin's DSL building capabilities wouldn't be unreasonable if there were some general extensions that could cover whatever was needed to make compose look good.
2
m
DSLs are workarounds for situations when you don't need to build an object graph but • wrap something imperative and mutable (Anko and Splitties) • write immediately to stream (kotlinx.html)
d
Your argument is that a DSL isn't appropriate for building GUIs?
e
DSLs are not workaround. DSLs are a very important languages feature in the modern world. Actually, Apple design document for Swift DSL has good write up showing “from first principles” why a language needs a special DSL support, because otherwise you’d have to write a lot of boilerplate. It is as good in this sense as the post that @Leland Richardson [G] wrote, showing what kind of boilerplate you’d have to write without a special
@Compose
compiler plugin.
m
Object graphs can be built without DSLs, in more explicit ways. By using constructors, possibly with vararg parameter.
e
Indeed. Both Apple’s design docs and Lelend’s post show how you can explicitly do it in “stock” language. Take a look. It is pretty self-explanatory.
They end up proposing different kinds of compiler magic to make it bearable for end users to write, (mostly owning to different prevailing traditions and idioms in Kotlin and Swift languages, even though it looks quite similar for end users), but nonetheless some kind of language magic is required. It will not be nearly as useful without it.
m
I'm not familiar with Apple docs. But (explicit) Flutter UI-framework looks OK. (despite their poor language!)
e
Flutter-like approach is Ok, but it can be made better. It is known to be error-prone and verbose, with non-trivial learning and gotchas for novices. It does not mean it is bad, though. It is just how progress works. People do something, play with it, use it, find issues, identify common error patterns, iterate, improve, build better solutions. It is never ending. The very moment better solutions come out of the door the cycle repeats. It will be always better in the next generation.
☝️ 1
A shorter version: The fact that something is Ok should not stop you from trying to improve it.
1
👏 1
For example, Rx is Ok. But we are working on Kotlin Flows.
☝️ 2
🙂 1
m
Compiler plug-ins are all about non-trivial gotchas! Can't agree that Rx is OK. Its quality is far lower beyond my vision of 'ok'.
t
Wow, 51 replies bikeshedding about if kotlin-ish dsl is better that java-style builder. And that’s all in the __kotlin__‘s slack channel
💯 4
🙃 3
e
Thanks god slack has threads!
g
What about setting up a direct parent as a argument. similar to styles?
Copy code
@ComposableStyle
fun CustomPaddingAndThings() {

} 

@Composable
fun SomeComponenet(directParent = CustomPaddingAndThings()) {

}
I'm unable to format it any better for some reason
f
try ```
👍 1
So
Copy code
@Composable
fun SomeComponent (directParent = CustomPaddingAndThings()) {

}
would be the same as
Copy code
So @Composable
fun SomeComponent = CustomPaddingAndThings() {

}
?
If so, it doesn't sound very useful
g
no
It was a quick draft
f
Please explain further then
g
I meant, creating a composable reference point that can be added
like creating an id for a style function
it should be more like
Copy code
@Composable
fun SomeComponent (directParent = SomeClass::CustomPaddingAndThings) {

}
a
To the original comparison, there are a few API differences in our approaches to components that I think justify the differences in syntax and why a trailing, builder-style
.padding()
is undesirable for compose. In compose, like in flutter, padding is a layout component of its own used as a wrapper, not a property of a base View class. There's nothing special about it in the compose framework, anyone could write it using public API. And perhaps more importantly, anyone else writing their own components doesn't have to think about how to correctly handle padding in their component implementation - the caller will add a
Padding {}
wrapper in the right place if it's desired.
In builder-style code there's an expectation that fluent property set methods are not order dependent, but padding's place in the resulting emitted UI is very significant
if padding is a wrapper rather than a property of every View, the semantics are quite different and worth highlighting with an API shape that emphasizes those differences
so then what's left is the unfortunate indentation march when you use several wrappers together, which is where some language thinking might be useful 🙂
for example, if you could do a form of partial application of several functions with trailing lambdas in a single go, things might get a little more pleasant:
Copy code
Card(...) {
  Padding(...) {
    TODO("content")
  }
}
vs.
Copy code
Compose(Card(...), Padding(...)) {
  TODO("content")
}
🤔 1
I'd rather not have additional parameters of composable functions written by users that get added by magic, nor do I want those composable function authors to have to think about the additional contingencies leading to some sort of standard boilerplate expected to be present in library-grade components - eliminating that kind of boilerplate is kind of the point 🙂
1
so extra parameters on every component to supply these sorts of things are out
f
Copy code
Compose(Card(...), Padding(...)) {
  TODO("content")
}
Is such a construct not available right now?
a
no, it's just something we've mused about in chat and whiteboard discussions
g
Your'e trying to direct people into writing custom components that contains things like padding right?
a
We're trying to direct people into writing components that accept
@Composable() () -> Unit
function references if they need to display arbitrary content, and in many cases the caller can add padding around their content they put there if they want it. Some designs might call for padding emitted by the container before calling the provided function, some might not.
g
That's one way to make it less verbose and "boilerplaty" but I have a feeling that it's going to be misused easily
a
But yes, writing a custom component for something you use more than a few times is meant to be easy here, e.g.
Copy code
@Composable fun PaddedCard(padding: ..., content: @Composable() () -> Unit) = Card {
  Padding(padding, content)
}
with the usual extract method refactorings making it smoother, etc.
g
Will the ide be able to show extraction suggestions when there is a code reuse?
in the same scope
a
Ideally yes 🙂
which is easy for me to say because I don't work on that part 😄
g
In our field we call it delegation
😁 3
a
what kind of misuse are you uneasy about?
g
Things like component that should be used in multiple classes or a more complicated element that needs to be manipulated based on multiple data sources
a
can you give an example?
g
Not something specific but the same issues that we're caused by a misused
Copy code
<include>
element
Or a complicated text field with some specific design that you will see a lot of copy and paste.
a
using composable lambdas for content largely breaks you out of the
<include>
tag problems since you can use the lexical scope of the caller to perform more logic/provide parameters for deeper content
🤔 1
this was by far my biggest, "aha!" moment about this whole model that occurs in pretty much all reactive systems
we might be thinking of different problems with include tags 🙂
g
I don't know why but I have a feeling like unless you have a good architecture this entire thing is going to end up like a god activitiy
a
Tell me more, because I feel the opposite 🙂
g
Because the entire thing is based on model "observing" for changes it's going to become a problem when you need to combine information from multiple sources. For example database and network that now are handled inside functions and just update the view. here people will just replace the entire root because they can't access the component outside of it's scope
As i said It's only a feeling. probably because there is not enough documentation out
n
To me, the main advantage of the composable structure is that it shows the hierarchy of the layout directly in the code. I think the main qualm with the padding example is considering "padding" to be an entire container, not just a property of another view, in which case it makes sense. @Adam Powell your example of single go passing is a great compromise IMO. It keeps the hierarchy clear while also bringing back a good option for succinctly repeating properties.