I'm trying to create a non-lazy carousel. i.e. Scr...
# compose
c
I'm trying to create a non-lazy carousel. i.e. Scrollable Row with each item being about 90% of the full screen width. I have a small sample showing that I'm failing. Anyone that can spot my issue?
Copy code
Box(modifier = Modifier.fillMaxWidth().border(1.dp, Color.Red)) {
  Row(
      modifier =
          Modifier.height(IntrinsicSize.Min)
              .width(IntrinsicSize.Max)
              .horizontalScroll(rememberScrollState())) {
    Button(
        onClick = { /*TODO*/},
        modifier = Modifier.fillMaxHeight().fillMaxWidth(.9f).border(1.dp, Color.Green)) {
      Text(text = "One")
    }
    Button(
        onClick = { /*TODO*/},
        modifier = Modifier.fillMaxHeight().fillMaxWidth(.9f).border(1.dp, Color.Green)) {
      Text(text = "Two\nTwo\nTwo")
    }
    Button(
        onClick = { /*TODO*/},
        modifier = Modifier.fillMaxHeight().fillMaxWidth(.9f).border(1.dp, Color.Green)) {
      Text(text = "Three")
    }
  }
}
No matter what I do, I can't get each item to take up the full width.
z
i don’t think this makes sense:
Copy code
.width(IntrinsicSize.Max)
.horizontalScroll(rememberScrollState())
horizontalScroll
will measure its child with infinite max width, and the parent box is already
fillMaxWidth
, so i’m not sure what you’re trying to achieve with this combination of modifiers.
Also side note: any time you’re wrapping a single composable with a
Box
just to apply modifiers, you can just move the outer modifiers to the start of the inner component’s modifier chain
I’m not sure using
height(IntrinsicSize.Max)
makes sense either when all the children are
fillMaxHeight()
Never mind i just forgot the point of intrinsics 🤦🏻‍♂️
The reason the
fillMaxWidth
isn’t working on the children is because
horizontalScroll
causes its layout node to measure with infinite width, and 90% of infinity is still infinity.
If you use a lazy row, you could use
fillParentMaxWidth
which you can do manually here with a custom layout or
BoxWithConstraints
, but i also don’t see a reason not to just use a lazy row
c
SO the reason why I can't use a lazy row is because I need all items in the carousel to be the height of the tallest item in the carousel. I do have a guarantee that the carousel wont be more than like 5 items. This is my attempt at refactoring my lazyRow into just a regular row unfortunately.
Unless I can somehow get LazyRow + all items are the height of the tallest item.
WHich would be asweome, because then I could use chris banes' snapper lib.
z
ah, right that makes sense. That seems fundamentally impossible – can’t know the height of all items without composing them, which is the definitely of “not lazy”
c
Well glad you could sanity check that thinking. 😄 So now as long as I can get these items 90% of the screen width, and have height of all the same size. then im golden. With your help, my items are now 90%. still working on the height part.
Current status:
Copy code
BoxWithConstraints(modifier = Modifier.fillMaxWidth()) {
  val boxWithConstraintsScope = this
  Row(
      modifier =
          Modifier.fillMaxWidth()
              .border(1.dp, Color.Red)
              .horizontalScroll(rememberScrollState())) {
    Button(
        onClick = { /*TODO*/},
        modifier =
            Modifier
                .width(boxWithConstraintsScope.maxWidth.times(.9f))
                .border(1.dp, Color.Green)) { Text(text = "One") }
    Button(
        onClick = { /*TODO*/},
        modifier =
            Modifier
                .width(boxWithConstraintsScope.maxWidth.times(.9f))
                .border(1.dp, Color.Green)) { Text(text = "Two\nTwo\nTwo") }
    Button(
        onClick = { /*TODO*/},
        modifier =
            Modifier
                .width(boxWithConstraintsScope.maxWidth.times(.9f))
                .border(1.dp, Color.Green)) { Text(text = "Three") }
  }
}
z
any reason not to use
maxWidth * 0.9f
?
c
I guess not? I just started chaning method calls because I didn't know if it was .dp for whatever reason and i know its easy to use method calls when operating on dps.
Updated to * instead of
times
. Is there a reason not to use times? Is it more overhead because its a method call or something?
z
no it compiles to the exact same bytecode
but it’s more idiomatic to just use the
*
operator if you’re multiplying
it’s totally just a style nit 😛
c
your nit fix will live on forever in my codebase. 😄
So this is almost perfect, but the height of every item is like 2.5x of what it should be for some reason. Anything in here look terribly off at this point?
Copy code
BoxWithConstraints(modifier = Modifier.fillMaxWidth()) {
  val boxWithConstraintsScope = this
  Row(
      modifier =
          Modifier.fillMaxWidth()
              .border(1.dp, Color.Red)
              .height(IntrinsicSize.Min)
              .horizontalScroll(rememberScrollState())) {
    Button(
        onClick = { /*TODO*/},
        modifier =
            Modifier.fillMaxHeight()
                .width(boxWithConstraintsScope.maxWidth * (.9f))
                .border(1.dp, Color.Green)) { Text(text = "One") }
    Button(
        onClick = { /*TODO*/},
        modifier =
            Modifier.fillMaxHeight()
                .width(boxWithConstraintsScope.maxWidth * (.9f))
                .border(1.dp, Color.Green)) { Text(text = "Two\nTwo\nTwo") }
    Button(
        onClick = { /*TODO*/},
        modifier =
            Modifier.fillMaxHeight()
                .width(boxWithConstraintsScope.maxWidth * (.9f))
                .border(1.dp, Color.Green)) { Text(text = "Three") }
  }
}
above snippet running on a device.
z
I’m not sure, but maybe it has something to do with
Button
not propagating intrinsics correctly? What happens if you replace the buttons with just their text (and move the button modifiers to
Text
)
c
if I take away the fillMaxHeights, and the height(Intrinsic.Min) then I get this though
Oh. good point about button. Let me try your sugg
z
so i was wrong earlier, parent using intrinsic min and child using fill max height absolutely makes sense, because the parent will communicate the sibling’s heights through the max constraint, i believe. And fillMaxHeight shouldn’t affect the intrinsic height a component reports
intrinsics always turn me around until i think through them really hard 😅
c
Removed the button, and just used a box instead. Not perfect. but much closer to what it should be.
Intrinsics make my head hurt. I was like "No problem. I'll convert this lazy row into a regular row carousel." Now it's been 3 hours.
This is the code that repros the above. Not sure why there is space after the Two Two Two, but getting closer to my desired result
Copy code
BoxWithConstraints(modifier = Modifier.fillMaxWidth()) {
  val boxWithConstraintsScope = this
  Row(
      modifier =
          Modifier.fillMaxWidth()
                  .height(IntrinsicSize.Min)
              .border(1.dp, Color.Red)
              .horizontalScroll(rememberScrollState())) {
    Box(
        modifier =
            Modifier.fillMaxHeight()
                .width(boxWithConstraintsScope.maxWidth * (.9f))
                .border(1.dp, Color.Green)) { Text(text = "One") }
    Box(
        modifier =
            Modifier.fillMaxHeight()
                .width(boxWithConstraintsScope.maxWidth * (.9f))
                .border(1.dp, Color.Green)) { Text(text = "Two\nTwo\nTwo") }
    Box(
        modifier =
            Modifier.fillMaxHeight()
                .width(boxWithConstraintsScope.maxWidth * (.9f))
                .border(1.dp, Color.Green)) { Text(text = "Three") }
  }
}
z
So
Button
is
Box { Row {} }
internally. I would think both of those would just forward intrinsic height, but maybe not?
c
ok. officially out of ideas. This is my result:
Copy code
BoxWithConstraints(modifier = Modifier.fillMaxWidth()) {
  val boxWithConstraintsScope = this
  Row(
      modifier =
          Modifier.fillMaxWidth()
              .height(IntrinsicSize.Min)
              .border(1.dp, Color.Red)
              .horizontalScroll(rememberScrollState())) {
    Text(
        text = "One",
        modifier =
            Modifier.fillMaxHeight()
                .width(boxWithConstraintsScope.maxWidth * (.9f))
                .border(1.dp, Color.Green))
    Text(
        text = "Two\nTwo\nTwo",
        modifier =
            Modifier.fillMaxHeight()
                .width(boxWithConstraintsScope.maxWidth * (.9f))
                .border(1.dp, Color.Green))
    Text(
        text = "Three",
        modifier =
            Modifier.fillMaxHeight()
                .width(boxWithConstraintsScope.maxWidth * (.9f))
                .border(1.dp, Color.Green))
  }
}
I'm going to downgrade compose from 1.1RC and see if I get a diff result
z
wow it’s complicated
if you replace the child
Box
components in your second-last code snippet with `Row`s, does it break again?
c
If I replace the Boxes with Rows I get the same result as the last screenshot I sent.
z
which is still wrong, correct?
c
Yeah. I would expect the red line to match up with the bottom Two
Maybe the issue is with the new line...
z
There’s no other child that is that full height?
and how much height space is available?
c
Nope. End result should be this
Copy code
One     Two     Three
        Two
        Two
Looks like this though
Copy code
One     Two     Three
        Two
        Two
I'm somehow convinced its the newline in this example though...
nope. same thing with this code where there are no newlines
Copy code
BoxWithConstraints(modifier = Modifier.fillMaxWidth()) {
  val boxWithConstraintsScope = this
  Row(
      modifier =
          Modifier.fillMaxWidth()
              .height(IntrinsicSize.Min)
              .border(1.dp, Color.Red)
              .horizontalScroll(rememberScrollState())) {
    Column(
        modifier =
            Modifier.fillMaxHeight()
                .width(boxWithConstraintsScope.maxWidth * (.9f))
                .border(1.dp, Color.Green)) { Text(text = "One") }
    Column(
        modifier =
            Modifier.fillMaxHeight()
                .width(boxWithConstraintsScope.maxWidth * (.9f))
                .border(1.dp, Color.Green)) {
      Text(text = "Two")
      Text(text = "Two")
      Text(text = "Two")
    }
    Column(
        modifier =
            Modifier.fillMaxHeight()
                .width(boxWithConstraintsScope.maxWidth * (.9f))
                .border(1.dp, Color.Green)) { Text(text = "Three") }
  }
}
added more borders to debug. something is def off...
Copy code
BoxWithConstraints(modifier = Modifier.fillMaxWidth()) {
  val boxWithConstraintsScope = this
  Row(
      modifier =
          Modifier.fillMaxWidth()
              .height(IntrinsicSize.Min)
              .border(1.dp, Color.Red)
              .horizontalScroll(rememberScrollState())) {
    Column(
        modifier =
            Modifier.fillMaxHeight()
                .width(boxWithConstraintsScope.maxWidth * (.9f))
                    .padding(4.dp)
                .border(1.dp, Color.Green)) { Text(text = "One") }
    Column(
        modifier =
            Modifier.fillMaxHeight()
                .width(boxWithConstraintsScope.maxWidth * (.9f))
                .border(1.dp, Color.Green)) {
      Text(text = "Two", Modifier.border(1.dp, Color.Cyan))
      Text(text = "Two", Modifier.border(1.dp, Color.Cyan))
      Text(text = "Two", Modifier.border(1.dp, Color.Cyan))
    }
    Column(
        modifier =
            Modifier.fillMaxHeight()
                .width(boxWithConstraintsScope.maxWidth * (.9f))
                .border(1.dp, Color.Green)) { Text(text = "Three") }
  }
}
Weird that the first item in the row seems to have a proper height, but the second and third items are funky
z
huh so i got it working, but for some reason i needed a
wrapContentHeight
before the
height(IntrinsicSize.Min)
, or else the row and all the buttons fill the entire height.
Wrong:
Copy code
Row(
        Modifier
//            .wrapContentHeight()
            .height(IntrinsicSize.Min)
    ) {
        Button(
            onClick = {},
            Modifier.fillMaxHeight()
        ) {
            Text("one")
        }
        Button(
            onClick = {},
            Modifier.fillMaxHeight()
        ) {
            Text("two\ntwo\ntwo")
        }
        Button(
            onClick = {},
            Modifier.fillMaxHeight()
        ) {
            Text("three\nthree")
        }
    }
Right:
Copy code
Row(
        Modifier
            .wrapContentHeight()
            .height(IntrinsicSize.Min)
    ) {
        Button(
            onClick = {},
            Modifier.fillMaxHeight()
        ) {
            Text("one")
        }
        Button(
            onClick = {},
            Modifier.fillMaxHeight()
        ) {
            Text("two\ntwo\ntwo")
        }
        Button(
            onClick = {},
            Modifier.fillMaxHeight()
        ) {
            Text("three\nthree")
        }
    }
i left out the width stuff but i don’t think that should matter
c
woah. wrapContentHeight.
hm. so is this a compose bug or just how it works?
z
I'm not sure actually
🙃 1
c
Something weird too that might point to this being a bug is that this code
Copy code
Row(
    modifier =
        Modifier.wrapContentHeight()
            .height(IntrinsicSize.Min)
            .border(1.dp, Color.Red)
            .horizontalScroll(rememberScrollState())) {
  Column(modifier = Modifier.fillMaxHeight().padding(4.dp).border(1.dp, Color.Green)) {
    Text(text = "One")
  }
  Column(modifier = Modifier.fillMaxHeight().border(1.dp, Color.Green)) {
    Text(text = "Two", Modifier.border(1.dp, Color.Cyan))
    Text(text = "Two", Modifier.border(1.dp, Color.Cyan))
    Text(text = "Two", Modifier.border(1.dp, Color.Cyan))
  }
  Column(modifier = Modifier.fillMaxHeight().border(1.dp, Color.Green)) { Text(text = "Three") }
}
Leads to this. What's weird is that the heights are not the same of Two and Three. They start at the top, so the baseline of the top three text labels aren't in a single line.
Ah crap. I had an extra padding on the first column. LMAO. okay. probably a false alarm there. whooops
Crap. now if I wrap it with a simple boxWithConstraints then it breaks again.
Copy code
BoxWithConstraints(modifier = Modifier.fillMaxWidth()) {
  val boxWithConstraintsScope = this
  Row(
      Modifier.wrapContentHeight()
          .height(IntrinsicSize.Min)
          .horizontalScroll(rememberScrollState())) {
    Button(onClick = {}, Modifier.fillMaxHeight().width(boxWithConstraintsScope.maxWidth * .9f)) {
      Text("one")
    }
    Button(onClick = {}, Modifier.fillMaxHeight().width(boxWithConstraintsScope.maxWidth * .9f)) {
      Text("two\ntwo\ntwo")
    }
    Button(onClick = {}, Modifier.fillMaxHeight().width(boxWithConstraintsScope.maxWidth * .9f)) {
      Text("three\nthree")
    }
  }
}
I added boxWithConstraints, and a width to each item, and a horizontal scroll, and its back to the previous behavior seemingly.
Going to try mashing on this for a little bit more and ill see what happens.
alright. so... cue spongebob 5 hours later cut scene.
😂 1
nothing makes sense anymore. This code... produces this
Copy code
var blah by remember { mutableStateOf(0.dp) }
BoxWithConstraints(modifier = Modifier.fillMaxWidth()) {
    val boxWithConstraintsScope = this
    blah = boxWithConstraintsScope.maxWidth
}

Row(
        Modifier.wrapContentHeight()
                .height(IntrinsicSize.Min)
                .horizontalScroll(rememberScrollState()),
) {
    Button(onClick = {}, Modifier.fillMaxHeight()) {
        Text("one")
    }
    Button(onClick = {}, Modifier.fillMaxHeight()) {
        Text("two\ntwo\ntwo")
    }
    Button(onClick = {}, Modifier.fillMaxHeight()) {
        Text("three\nthree")
    }
}
note in the above code, I dont wrap the row with the BoxWithConstraints.
once I change the width of the buttons. the height also changes
Copy code
var blah by remember { mutableStateOf(0.dp) }
BoxWithConstraints(modifier = Modifier.fillMaxWidth()) {
    val boxWithConstraintsScope = this
    blah = boxWithConstraintsScope.maxWidth
}

Row(
        Modifier.wrapContentHeight()
                .height(IntrinsicSize.Min)
                .horizontalScroll(rememberScrollState()),
) {
    Button(onClick = {}, Modifier.fillMaxHeight().width(blah * .9f)) {
        Text("one")
    }
    Button(onClick = {}, Modifier.fillMaxHeight().width(blah * .9f)) {
        Text("two\ntwo\ntwo")
    }
    Button(onClick = {}, Modifier.fillMaxHeight().width(blah * .9f)) {
        Text("three\nthree")
    }
}
I quit (for the night). Opened a bug with a crazy simple repro case, as well as some screenshots. Hopefully someone can chime in. https://issuetracker.google.com/issues/213924413 Tomorrow... I will look for a workaround. I gotta ship this asap and the ios team is already done. 😭
m
Hi 🙂 what you want is to put the
horizontalScroll
before
height
on
Row
. So just:
Copy code
Row(Modifier.horizontalScroll(rememberScrollState()).height(IntrinsicSize.Min))
🙏🏻 1
Having a width constraint (the screen width) when calculating the min intrinsic height messes with the calculation in this scenario. Did not look to understand exactly why, but it might be WAI; will look closer at the bug you filed soon hopefully.
Also, looking at your code, I would suggest moving the
Row
inside the
BoxWithConstraints
. This is the intended usage and will not introduce a 1 frame delay between the first measurement and the drawing of the buttons
c
@Mihai Popa it worked! ALL IS RIGHT WITH THE UNIVERSE. For real tho. Thank you and @Zach Klippenstein (he/him) [MOD] so much. This was driving me crazy. This is what I ended up with:
Copy code
BoxWithConstraints(modifier = Modifier.fillMaxWidth()) {
  val boxWithConstraintsScope = this
  val blah = boxWithConstraintsScope.maxWidth * .9f

  Row(
      Modifier.horizontalScroll(rememberScrollState()).height(IntrinsicSize.Min),
  ) {
    Box(Modifier.width(blah).fillMaxHeight().border(1.dp, Color.Green)) { Text("one") }
    Box(Modifier.width(blah).fillMaxHeight().border(1.dp, Color.Green)) {
      Text("two\ntwo\ntwo")
    }
    Box(Modifier.width(blah).fillMaxHeight().border(1.dp, Color.Green)) { Text("three\nthree") }
  }
}
As a final question. Is there really no other way to get the 90% width besides using BoxWithConstraints. I've heard BWC isn't the best thing to use if its not necessary, so I'm trying to make sure there isn't something else I can do.
z
There definitely is, although it's a little more boilerplate for a one-off. You could provide a custom layout Modifier for your children that adjusts the constraints based on the parent size, and get the parent size via
onSizeChanged
or another custom layout around the parent.
c
Oof. Custom layout. Not today... gotta get this PR in.
^ That's def not the right attitude as I know custom layouts are way easier in compose and not as scary as view-land, but I can make the release train with the advice from Mihai! lol
d
sigh. I thought this thread would save me, but it doesnt. I've done everything like Colton demonstrated in the last snippet and in my case all row items are still cut-off at bottom and don't fill in.
c
Show code?
d
a bit later, was coding in anger 🙂 It seems my problems stem from the fact that I have long texts which are wrapped and compose continues to calculate intrinsic height as though the texts are not wrapped -> ends up with smaller height. I.e. in your example if you hadn't `\n`'s but instead had a really long text which would result in wrapped 2-3 lines. I'm planning to try to come up with minimal example a bit later. Thanks for the thread!
Done some digging, posted sample into a new thread: https://kotlinlang.slack.com/archives/CJLTWPH7S/p1643674208275499