Is there a better way to handle `@Composable` func...
# compose
n
Is there a better way to handle
@Composable
functions with many lambda parameter callbacks? would creating a state holder be a better approach? šŸ¤”
Copy code
@Composable
fun StatusDetailContent(
  status: StatusUiData,
  descendants: ImmutableList<StatusUiData>,
  loading: Boolean,
  modifier: Modifier = Modifier,
  favouriteStatus: (String) -> Unit,
  unfavouriteStatus: (String) -> Unit,
  navigateToDetail: (Status) -> Unit,
  navigateToProfile: (Account) -> Unit,
  navigateToMedia: (List<Attachment>, Int) -> Unit,
) {
  LazyColumn(modifier = modifier) {
    item {
      StatusDetailCard(
        status = status,
        favouriteStatus = { favouriteStatus(status.actionableId) },
        unfavouriteStatus = { unfavouriteStatus(status.actionableId) },
        navigateToDetail = { navigateToDetail(status.actionable) },
        navigateToMedia = navigateToMedia,
        navigateToProfile = navigateToProfile,
        contentTextStyle = TextStyle(
          fontSize = 16.sp,
          color = AppTheme.colors.primaryContent
        ),
      )
    }
}
a
I think thats to many params to pass, the reasonable amount param to pass js arround 7, according to detekt rules, so to handle such a param, maybe you can save it in data class and pass it as a param in composable
c
One way to move the complexity outside the Composable is to convert N callbacks into N children of a sealed class/interface. For example:
Copy code
sealed interface StatusDetailAction {
    data class OnFavoriteStatus(val actionableId: String): StatusDetailAction
    data class OnUnfavoriteStatus(val actionableId: String): StatusDetailAction
    data class OnNavigateToProfile(val account: Account): StatusDetailAction
    // You get the idea
}
And then
Copy code
@Composable
fun StatusDetailContent(
    status: StatusUiData,
    actionHandler: (StatusDetailAction) -> Unit,
) {
    LazyColumn {
        item {
            StatusDetailCard(
                status = status,
                onFavoriteStatus = { actionHandler(StatusDetailAction.OnFavoriteStatus(status.actionableId)) },
                // and so on
            )
        }
    }
}
✨ 1
āž• 1
Side-note: Please paste code snippets here instead of screenshots of code. It is way easier for us to copy-paste your code snippets to help you.
ā™„ļø 2
n
thanks a lot !
s
Honestly this doesn't look that bad imo šŸ˜… This way you save on having to create a new sealed class too, with the appropriate name, being careful not to reuse it somewhere else, having it pop up in autocomplete in unrelated places etc šŸ¤·ā€ā™‚ļø
c
For me the big advantage of using a sealed interface was with previews. I was previously using lambdas for every action and I agree with @Stylianos Gakis that it was not bad at all. However, every time a requirement changed and I had to add/delete/modify an action, it meant also updating the preview. With the sealed interface, your previews are untouched when you modify the actions. It is this
Copy code
@Preview
@Composable
fun StatusDetailPreview() {
    StatusDetailContent(status = StatusUiData(), actionHandler = {})
}
versus this
Copy code
@Preview
@Composable
fun StatusDetailPreview() {
    StatusDetailContent(
        status = StatusUiData(),
        favouriteStatus = {},
        unfavouriteStatus = {},
        navigateToDetail = {},
        navigateToProfile = {},
        navigateToMedia = {},
    )
}
✨ 2
Also, note that sealed interfaces need to have their children defined in the same package (I think with sealed classes it is the same module). So the auto-complete in unrelated places problem is not as bad as you might think.
s
Yeah, I should’ve said ā€œUnrelated places within your current moduleā€ šŸ˜… It’s always a balance, and yes having to add
, {}
or with the name as you showed above is definitely not the most fun experience in the world. But it’d be an even worse experience if I had to come up with a name and create a new sealed class for every single composable that takes in 3+ lambdas in the entire codebase. So yeah, strike the balance you feel like is best for you šŸ˜Ž
c
Oh wait - I think I did not make my point clear either. I don't think this makes sense for all composables. I would only recommend this for top-level composables. And my ViewModels/Presenters/Whatever are already generic
<S, A>
where S is a FooUiState data class and A is a sealed interface for actions - so this makes sense in that situation. For other composables that are not at the top-level, it is indeed a balance that you need to strike.
šŸ’Æ 3