I have some components like this: ```@Composable ...
# compose
h
I have some components like this:
Copy code
@Composable
fun Layout(
    label: String,
    component: @Composable (label: String, enabled: Boolean) -> Unit,
    modifier: Modifier = Modifier,
) { ... }

@Composable
fun Layout(
    label: String,
    optionalIcon: ImageVector,
    component: @Composable (label: String, enabled: Boolean, icon: ImageVector?) -> Unit,
    modifier: Modifier = Modifier,
) { ... }

@Composable
fun Layout(
    label: String,
    optionalLeadingIcon: ImageVector,
    optionalTrailingIcon: ImageVector,
    component: @Composable (label: String, enabled: Boolean, leadingIcon: ImageVector?, trailingIcon: ImageVector?) -> Unit,
    modifier: Modifier = Modifier,
) { ... }
The idea is that
component
will be based on other arguments. Currently this working find for me, but as I add more and more argument, I have to create more and more composable function for it. Is there a better way to write this, I'm looking at extensible data arguments but seem likes it not suitable.
l
Why not creating default values as arguments? And the lambda will contain all of them. When receiving it, you can use
_
to not need to use the variable
h
The reason for that is if I do that it will make the usage of it harder to reason about. For examples, this is how I actually use it:
Copy code
Layout(
    label = "Chip",
    optionalIcon = Icons.Filled.Check,
    component = { label, enabled, optionalIcon ->
        // chip implementation with label and icon
    }       
)
and this is how it will look like if I use default values arguments:
Copy code
Layout(
    label = "Chip"
    component = { label, enabled, _, optionalIcon, _, _, _, _, _, _, _ ->
        // chip implementation with label and icon
    }       
)
l
yeah that is unfortunate
Maybe expose a data class instead of number of properties
d
are you building a kind of design system? check this video from IO, maybe it'll give you some ideas

https://www.youtube.com/watch?v=JvbyGcqdWBA

m
How about using a receiver?
Copy code
component: @Composable ComponentContext.() -> Unit
Then have an appropriate interface...
Copy code
interface ComponentContext {
   val label: String
   val enabled: Boolean
   val leadingIcon: ImageVector?
   val trailingIcon: ImageVector?
}
Alternative, you could just pass it to the lambda:
Copy code
component: @Composable (ComponentContext) -> Unit
Using a receiver, the syntax of the lambda is the same (except you don't need to give the argument names):
Copy code
component = { 
    Text(label)
}
...but you do have to worry about namespace conflicts (if you later add a
val splunge: String
but already have code using
splunge
for something else...) Passing it as an argument, it's a little more verbose, but you avoid namespace conflicts:
Copy code
component = { 
    Text(it.label)
}
Of course, there's an object allocation this way (vs. just passing everything as individual lambda arguments). In most cases, unless you're doing some really heavy recomposition, I don't think that will be an issue. If it does become an issue, you can always reuse the
ComponentContext
instance... (with appropriate cautions about the lambda not keeping a reference...) If you take this approach, another thing to be careful about is how you handle
State
. If you do it wrong, you can end up recomposing unnecessarily or recomposing too often. For example, if you naively make
ComponentContext
as a data class and reconstruct it every time, then you'll be referencing the state during the construction, which is probably not what you want. Something like...
Copy code
object : ComponentContext {
   override val label: String get() = labelState.value
}
...or something like that is probably best. It can be a lot of extra work to set it up, but if you're building a common component that's going to re-used in a lot of places, it might be worth it.
h
thanks @Matthew Feinberg currently I created something similar to your idea, using receiver, but only for the private component that will be used by other public component.
Copy code
@Composable
fun Layout(
    label: String,
    component: @Composable (label: String, enabled: Boolean) -> Unit,
    modifier: Modifier = Modifier,
) {
    Content(
        component = { state -> 
            component(state.label, state.enabled)
        } 
    )
}

@Composable
fun Layout(
    label: String,
    optionalIcon: ImageVector,
    component: @Composable (label: String, enabled: Boolean, icon: ImageVector?) -> Unit,
    modifier: Modifier = Modifier,
) {
    Content(
        component = { state -> 
            component(state.label, state.enabled, state.icon)
        } 
    )
}

@Composable
private fun Content(
    component: @Composable (state: ComponentState) -> Unit,
) { ... }
so that I can use the public component like this:
Copy code
Layout(
    label = "Chip",
    optionalIcon = Icons.Filled.Check,
    component = { label, enabled, optionalIcon ->
        // chip implementation with label and icon
    }       
)
currently, I believe this solution works best for me because it addresses my two main problems: • It reduces the amount of duplicate code by using common
Content
. • The public component lambda only exposes what is necessary for its usage, eliminating the need to use
_
for unused variables.