Colton Idle

    Colton Idle

    10 months ago
    I'm kind of going crazy on not being able to get this layout to look quite right in my app. Essentially, the top horizontal bar is a carousel made up of RedComposable's, and then there is a green composable list, which is a vertical scrolling column. The GreenComposable includes a RedComposable. The trouble I'm having is how to size the RedComposable. My design team said "The Red should be 40% of the green, and then that size of the red should be used in the horizontal scroller" How would I do that in compose?
    j

    josefdolezal

    10 months ago
    I would start with measuring column width (eq.
    onSizeChanged
    ), then I would use that size to compute size of GreenComposable (eg. subtract column paddings, ..), then I would derive RedComposable from that. If the performance is not good enough (there will be unnecessary layout passes due to laziness of this solution), I would try to create
    SubcomposeLayout
    to see if can spare some layout passes.
    Actually, you could probably just wrap your layout into
    BoxWithConstraints
    and compute sizes of column, red and green from that
    Chris Sinco [G]

    Chris Sinco [G]

    10 months ago
    Assuming you only care about screen width, you could query
    LocalConfiguration.current.screenWidthDp
    to get the device width, then do a calculation based on the horizontal padding you plan to have on the green Composables list. Save that as a variable hoisted to a place in your hierarchy that can control the top Red container and the green list, and go from there
    Albert Chang

    Albert Chang

    10 months ago
    You can just use
    Modifier.fillParentMaxWidth(fraction = 0.4f)
    in a
    LazyRow
    .
    Chris Sinco [G]

    Chris Sinco [G]

    10 months ago
    I think though that value needs to be then passed to size the red Composables in the top container
    Albert Chang

    Albert Chang

    10 months ago
    I mean use that in the top container:
    LazyRow(
        modifier = Modifier.fillMaxWidth(),
        contentPadding = PaddingValues(16.dp),
        horizontalArrangement = Arrangement.spacedBy(16.dp)
    ) {
        items(10) { i ->
            Text(
                text = (i + 1).toString(),
                modifier = Modifier
                    .fillParentMaxWidth(fraction = 0.4f)
                    .height(100.dp)
                    .background(color = Color.Red.copy(alpha = 0.2f))
                    .wrapContentSize()
            )
        }
    }
    Chris Sinco [G]

    Chris Sinco [G]

    10 months ago
    Oh yeah good call!
    Colton Idle

    Colton Idle

    10 months ago
    Hm. And then my GreenComposable would just fill the max width (with 16 horizontal padding), and the RedComposable would again be set to .4f width of the GreenComposable? So in that solution @Albert Chang I'm not actually "saving" a width value anywhere for green and reusing it... I "just so happen" to get the same width through a use of width modifiers?
    Albert Chang

    Albert Chang

    10 months ago
    Yes. Note that you need to adjust the paddings in order for them to be exactly the same width:
    LazyRow(
        modifier = Modifier.fillMaxWidth(),
        contentPadding = PaddingValues(horizontal = 16.dp * 0.8f, vertical = 16.dp),
        horizontalArrangement = Arrangement.spacedBy(16.dp * 0.6f)
    ) {
        items(10) { i ->
            Text(
                text = (i + 1).toString(),
                modifier = Modifier
                    .fillParentMaxWidth(fraction = 0.4f)
                    .height(100.dp)
                    .padding(horizontal = 16.dp * 0.2f)
                    .background(color = Color.Red.copy(alpha = 0.2f))
                    .wrapContentSize()
            )
        }
    }
    Colton Idle

    Colton Idle

    10 months ago
    Hm. You lost me at adjusting the paddings...
    16.dp * 0.2f
    and
    16.dp * 0.8f,
    ?
    Albert Chang

    Albert Chang

    10 months ago
    Modifier.fillParentMaxWidth(fraction = 0.4f)
    makes the width 40% of screen width, however you want it to be 40% of (screenWidth - 16dp), so you need to add a padding of (16dp * 20%) on both sides, and accordingly substract that from contentPadding and subtract 2 * that from spacing between items.
    Colton Idle

    Colton Idle

    10 months ago
    Hm. I don't think that extra calculation is needed?
    The top LazyRow is the first snippet of code, the second LazyRow is the second snippet (with the additional padding multipliers)
    Albert Chang

    Albert Chang

    10 months ago
    Oh yeah, seems that the parent size is already the size with contentPadding applied.
    Colton Idle

    Colton Idle

    10 months ago
    Awesome. Thought I was going crazy. That works sort of nicely then.
    The last thing is that the design team wants to name that RedComponent, and use it in both layouts, but I don't think that'll work because it needs different modifiers.
    So there's kind of an illusion going on that it's the same "exact" component being used... even though it's not?
    To my point. Here's my final code, but trying to extract it into a single composable doesn't give the result that my design team is thinking (i think)
    Column {
        LazyRow(
            modifier = Modifier.fillMaxWidth(),
            contentPadding = PaddingValues(16.dp),
            horizontalArrangement = Arrangement.spacedBy(16.dp)
        ) {
            items(10) { i ->
                Text(
                    text = (i + 1).toString(),
                    modifier = Modifier
                        .fillParentMaxWidth(fraction = 0.4f)
                        .height(100.dp)
                        .background(color = Color.Red.copy(alpha = 0.2f))
                        .wrapContentSize()
                )
            }
        }
        repeat(3) {
            Row(
                modifier = Modifier
                    .fillMaxWidth()
                    .padding(16.dp)
            ) {
                Text(
                    text = ("left 40%").toString(),
                    modifier = Modifier
                        .weight(0.4f)
                        .height(100.dp)
                        .background(color = Color.Red.copy(alpha = 0.2f))
                        .wrapContentSize()
                )
                Text(
                    text = ("right 60%").toString(),
                    modifier = Modifier
                        .weight(0.6f)
                        .height(100.dp)
                        .background(color = Color.Green.copy(alpha = 0.2f))
                        .wrapContentSize()
                )
            }
        }
    
    }
    Albert Chang

    Albert Chang

    10 months ago
    They can and should be the same component. You should always provide a Modifier parameter.
    Colton Idle

    Colton Idle

    10 months ago
    I read that article this past weekend. But yeah, I think maybe lemme give a whirl of how itd look like if it was shared.
    Okay. I created this
    @Composable
    fun RedComposable(text: String, modifier: Modifier) {
        Text(
            text = text,
            modifier = modifier
                .height(100.dp)
                .background(color = Color.Red.copy(alpha = 0.2f))
                .wrapContentSize()
        )
    }
    Which basically is as "skinny" as the text. Which is just a little contradictory to how I think my design team thinks about it. I.E: My design team has a component library and this RedComposable always has the same size in the library, but I think that's just a the price I gotta pay in order to make a composable reusable in multiple places.
    I'd also like to say that I still don't understand what the heck wrapContentSize does. lol
    Chris Sinco [G]

    Chris Sinco [G]

    10 months ago
    You can always set the default Modifier in your function parameter to
    Modifier.width(<usual size>)
    So if no Modifier is passed it looks like a consistent width
    Or you could also pass a default string to get to the size the design team expects most of the time
    Also since this component seems to be just Text, I don't think you need that wrapContentSize modifier because that is only really applied for width, since you have height always at 100.dp
    Colton Idle

    Colton Idle

    10 months ago
    @Chris Sinco [G] for "You can always set the default Modifier in your function parameter to 
    Modifier.width(<usual size>)
    " Where would I put that? In the RedComposable?
    Chris Sinco [G]

    Chris Sinco [G]

    10 months ago
    Yes the signature of the RedComposable function
    Then you can always override the width where you use the RedComposable based on the context / parent container
    Colton Idle

    Colton Idle

    10 months ago
    Oh. I think I know what you mean. So you mean like this
    @Composable
    fun RedComposable(text: String, modifier: Modifier.width(200.dp)) {
        Text(
            text = text,
            modifier = modifier
                .height(100.dp)
                .background(color = Color.Red.copy(alpha = 0.2f))
                .wrapContentSize()
        )
    }
    awesome. didn't even think of that. okay. cool. I have some avenues to pursue here. 🤞
    Albert Chang

    Albert Chang

    10 months ago
    wrapContentSize()
    (with the
    align
    parameter set to its default value
    Alignment.Center
    ) aligns the text at the center. Without that the text will be placed at the top start. You can also use
    Text(textAlign = TextAlign.Center)
    to align the text at the center horizontally but that doesn’t change vertical alignment.
    Colton Idle

    Colton Idle

    10 months ago
    ooh. interesting. thanks albert.
    After playing around with this for a few hours I do have to say that I really like the approach of using
    LocalConfiguration.current.screenWidthDp
    as I can calculate the value once and pass it in which makes the intent a bit easier to understand to me. Overall, I'm just happy that I didn't have to go with BoxWithConstraints 😄
    Chris Sinco [G]

    Chris Sinco [G]

    10 months ago
    Haha well you can still use the RedComposable as a component and pass whatever value you want in, even if that's screenWidthDp 😉
    Alex

    Alex

    10 months ago
    the screen width approach will completely nuke any drop in reusability of the component though, just something to keep in mind for future tech debt, when using this screen in a dialog / on the desktop or a tablet in a column etc
    In effect I want to say that the screen width approach is not a proper solution to the problem but more of a quick fix 😄 Not to say that it's not a valid quick fix considering the cirumstances maybe, but still kind of a quick workaround
    Colton Idle

    Colton Idle

    10 months ago
    What's your suggested solution to it?
    Zach Klippenstein (he/him) [MOD]

    Zach Klippenstein (he/him) [MOD]

    10 months ago
    Probably
    BoxWithConstraints
    Tash

    Tash

    3 months ago
    Running into a similar use case, came across this thread. @Colton Idle its been a while but do you recall what your solution was? (to make matters worse, for me everything is supposed to live in a
    LazyColumn
    ) 🙏🏼
    Colton Idle

    Colton Idle

    3 months ago
    IIRC and my biggest thing I took away from this was the fact that I stopped sizing my composables inside of the composable. Basically what @Chris Sinco [G] said
    Then you can always override the width where you use the RedComposable based on the context / parent container
    So a technique I was mostly taking was that "hey wouldn't it be convenient if this composable always just knew it should be fill max width" and after chris' comments and the rest of the comments above I basically conceded to the fact that it's kinda like not doing dependency injection. Sure. Having a class where I don't have to pass in a bunch of services to make it work correctly is convenient, but its extremely limiting. So yeah. I basically typically have composables take up the least amount of space possible, and then have a modifier so it can be adjusted based on its context.
    also @Tash i mightve misconstrued your question overall. but let me know if you have anything specific. in regards to the first example I drew out in the orinal post, I think I could recreate that pretty easily now in like 15 minutes.