:thinking_face: Subjective question: What is the s...
# compose
f
🤔 Subjective question: What is the size (LOC) of
@Composable
function when you ask yourself "should I divide this into separate composables/files"? 1️⃣ 50 lines 2️⃣ 100 lines 3️⃣ 200 lines 4️⃣ 300 lines 5️⃣ It's not dependant on number of lines but rather number and cohesion of parameters 6️⃣ It's fine as long it does "one thing". 7️⃣ Other 👉 comment
2️⃣ 3
5️⃣ 2
6️⃣ 14
For those who are voting 6️⃣, is there some some other secondary limit? Honestly I can't imagine 300+ LOC function doing one thing.
3
g
It can be one thing with 300+ loc if it has custom rendering/layout, but in this case I would still suggest to split it up
👍 2
z
Also I don’t think this is any different for composable functions than other functions.
5
f
Personally it is different for me. The difference being that I pass a lot of lambda functions around and it gets quickly messy. Thinking about (re-)composition and reusability in bigger scope is also a big difference for me.
z
Right but non-composable functions can also have lots of lambdas, e.g. for DSLs. And recomposition shouldn’t affect this answer in most cases – if the correctness of your code depends on very specific recomposition boundaries between components, something else is probably wrong.
f
Yes, you are right. Now that I thought about what I wrote, I am not that sure anymore if it makes difference for function size 😅 But composable/non-composable function size preference is still different for me. I think I can handle bigger UI-emitting composables than normal funcitons.
z
Maybe the math works out if you remove lines that just contain
}
?
f
Maybe. I also use way more empty lines with Composable functions. And maybe it is just because I am not used to writing really long non-composable functions so it does not feel as "okay" 🤔
a
for me it's down to how many places I need to jump around and look at to understand what something is doing
7
d
Compose functions usually suffer from Long Parameter List code smells. Here is some advice on reducing this problem: https://refactoring.guru/smells/long-parameter-list Don’t forget to be pragmatic though.
It’s hard to offer much advice with out understanding your code more intimately though.
a
the best tool you have to reduce parameter count is the lambda. Black box with highly limited dependencies.
take a look at the list of imports that a composable needs; the more distinct packages you see in that list, the higher the chances are that it knows too much about too many things
3
d
This might be hiding the problem a bit but I tend to make use of “State” classes. Instead of passing in a ton of parameters I will pass in a
data class
that contains the state.
a
the advice at that link has drawbacks that show up frequently with compose. Passing whole objects where you only need a small subset of data from them means your composable has broader dependencies and access to those dependencies than it needs. Introducing parameter object types means you're likely to be spending a lot of code mass and/or runtime wrapping and unwrapping parameters
d
☝️ this is true!
a
parameter object types make sense when it's common to pass a grouping around as a unit to/from more than just one function
we absolutely agonized over this stuff when it comes to APIs like
Text
d
It’s not an easy problem. But your comment on passing in lambdas makes a ton of sense.
a
view it sort of like a database normalization/denormalization problem
d
Using the Slot pattern?
a
yeah
this is also part of why we recommend avoiding ever using
[Mutable]State<T>
in a parameter list
getFoo: () -> Foo
is always going to be a more flexible parameter than
foo: State<Foo>
1
when you're working with databases, total normalization isn't desirable for a whole ton of practical reasons
the same applies here
t
getFoo: () -> Foo
is always going to be a more flexible parameter than
foo: State<Foo>
oh! Adam, could you elaborate on this a bit more? I feel like I kinda understand why, but a little unsure. Is it because
[Mutable]State<T>
as a param to a composable is akin to having a
var list = mutableListOf()
i.e. two degrees of mutability?
a
consider:
Copy code
@Composable
fun SomeComposable(state: State<MyType>)
Happy path:
Copy code
val myState = remember { mutableStateOf(initial) }
SomeComposable(myState)
Broken property delegation; wrapper needed:
Copy code
var myState by remember { mutableStateOf(initial) }
SomeComposable(
  object : State<MyType> {
    override val value: MyType get() = myState
  }
}
Broken aggregation into a single source of truth:
Copy code
class SomeParentState {
  var thing by mutableStateOf(...)
    private set
}

@Composable
fun Caller(parentState: SomeParentState) {
  SomeComposable(
    object : State<MyType> {
      override val value: MyType get() = parentState.thing
    }
  )
}
🙏🏼 1
🙈 1
contrast:
Copy code
@Composable
fun SomeComposable(getState: () -> MyType)
No property delegation:
Copy code
val myState = remember { mutableStateOf(initial) }
SomeComposable { myState.value }
With property delegation:
Copy code
var myState by remember { mutableStateOf(initial) }
SomeComposable { myState }
Aggregation into a single source of truth:
Copy code
class SomeParentState {
  var thing by mutableStateOf(...)
    private set
}

@Composable
fun Caller(parentState: SomeParentState) {
  SomeComposable { parentState.thing }
}
🙏🏼 1
t
Broken aggregation into a single source of truth:
ack! 😖
so, many footguns and ways to introduce incorrectness with that
a
yes. So we recommend not using
[Mutable]State<T>
as parameters or as public properties of a type; both lead to these kinds of pathologies
👍🏼 1
The lambda is a wrapper too, it's just one that kotlin has first-class support for so we might as well use it instead of creating our own 🙂
t
Yeah, that makes sense. A
getFoo: () -> Unit
is more one the nose for what you might be trying to do anyway
a
right, you want a deferred read somewhere
f
It’s hard to offer much advice with out understanding your code more intimately though.
Thank you but I wasn't really looking for advice 😅 I was just interested in other people opinion on this and to be honest I didn't expect so many people are fine with having big composable function as long as they do "one thing" (whatever that means). Whenever I have 150+ LOC long composable I start actively looking how to divide it because it feels really weird and cumbersome. Even if it does one thing, e.g. Screen component that just puts together other parts. Thanks everyone who voted and/or explained their opinion in comments ♥️
3
d
XML got just as big and nobody said boo about it. Eventually you have to realize that code is not perfect, It's designed by imperfect humans. Ideals are nice but being dogmatic about them is rarely practical.
1
f
Fair enough 😄 Thank you, that's interesting point of view.
a
I mean, a lot of people said boo about android layout xml, that's how we got databinding and viewbinding and compose 😁
😅 2
but the rest, yeah. If it communicates intent, it's easy to test, it's easy to maintain, and it performs well, it's good code
3