I like to divide my screens up into smaller privat...
# compose
s
I like to divide my screens up into smaller private `Composable`s. I find it very cumbersome passing my dependencies around to all the different `Composable`s for a given screen. You have the state, and then any ViewModel’s that they might reference. I find myself 3 or 4 `Composable`s / functions deep, and I’m having to pass a ViewModel along every step of the way. It’s pretty annoying. Is there a cleaner way to do this that I’m just not aware of? Surely I’m not the only one to find this frustrating.
1
a
The same issue happens when you split a class by multiple smaller classes. You have to supply dependencies, this is how it works. But following the Interface Segregation Principle - no code should be forced to depend on methods it does not use - better to only pass fields and callbacks that are actually used by a composable function. The full state and the ViewModel could stay in the top-most function only.
In other words, the fact that particular dependencies are required clearly indicates that the entity can't work without them. Less responsibility - less dependencies.
c
By ViewModel if you mean Jetpack ViewModel then I don't propagate those down the tree at all. The smaller private Composables only receive data classes or individual parameters and lamdas. Only the top level "screen" Composable knows anything about ViewModel. To reduce the drudgery of "prop drilling" (i.e., passing parameters down the tree), consider using slots. https://chris.banes.dev/slotting-in-with-compose-ui/
1
s
I don’t propagate those down the tree at all
So I find I end up having to propagate ViewModels down the tree so that I can trigger things to happen through click events.
f
For the click events you should pass lambdas. That will make previewing your composables that much simpler and also reusable
s
I'm fairly against view re-use. Except in very certain exact circumstances.
😱 1
So, all the click events should bubble up to one place then?
👌 2
c
Yeah. State and lambdas gets passed down. Events bubble up. State down. events up.
I have a rule that the only composable that can take a ViewModel as an arg is a "Screen" level composable.
💯 1
f
I do the same, I usually have a top level
Screen
composable that accepts a viewmodel and simply extracts the state to pass to a 2nd composable, so it's just a wrapper to convert the viewmodel into state and lambdas
☝🏽 1
☝️ 2
s
@curioustechizen After reading your article and thinking through it, slotting makes sense in some circumstances. Especially things like the examples given, however, how does it fit into screen level components with UI that you won’t reuse. IE lets say I have a Screen with a header and content that is specific to it and isn’t re-used anywhere else. Are you saying I should use slots to surface more View creation to the Screen level composable? Bubbling all the Click Handlers to the Screen level component makes complete sense to me. For re-usable Composables, slots makes sense, especially for widget level Composables. Outside of that, I’m not sure I see the picture.
c
@spierce7 while slotting is most advantageous when used for the purpose of reuse, it is not the only application of slotting. Specifically, you can use slots when you want intermediate composables to treat composables further down the tree as opaque components. In your example, maybe you want your Screen to be responsible only for the layout and not know anything about the specific state parameters or event lambdas of Header and Content. Then you would use a slot each for Header and Content; then the caller of Screen would be responsible for supplying the state and events. The article by Chris Banes above links to another post (self-promotion alert: I wrote that post) that has another example of this. https://kiranrao.in/blog/2021/12/03/jetpack-compose-slots/ This approach makes more sense if you have a tree a few levels deep. If all you have is a Screen composable with a Header and Content then it might not make much of a difference