Thread
#compose
    f

    frankelot

    1 year ago
    This message got lost in the noise, can anyone experienced with compose can give me a hand? Not sure if this is a but I should report or something I’m doing wrong
    Timo Drick

    Timo Drick

    1 year ago
    It does not look like a change of anchor points. It looks like it centers vertically and hrizontally.
    So maybe you should try to set contentAlignment = ... to the Box which contains the image
    Adam Powell

    Adam Powell

    1 year ago
    This has to do with how Compose UI's layout system works. When a compose UI element is measured, the parent gives the child a set of constraints - min and max width and height. If the child returns a size that is not compatible with the constraints, the parent sees a size for the child clamped to be compatible with the constraints anyway.
    When the parent places the misbehaving child, the child's actual position is centered with respect to the clamped size rect seen by the parent
    This helps write and arrange UI elements that have hard requirements while attributing where the breakdown happened; it also helps for simple cases of placing absolute-sized elements within larger minimum-sized areas
    Modifier.size
    - as opposed to
    Modifier.preferredSize
    - tells an element to disregard the constraints it is measured with and be the given size anyway.
    So if you use
    Modifier.size(100.dp)
    inside a container that is only 50dp large, it will do what you ask for, but the parent will position that element as if it were only 50dp
    f

    frankelot

    1 year ago
    Thank you both! The default
    contentAligment
    for
    Box
    is already
    contentAlignment: Alignment = Alignment.TopStart,
    Adam Powell

    Adam Powell

    1 year ago
    That's what you're seeing here - the duck is bigger than the container, so it's getting centered by this default behavior. First along the horizontal axis, since it becomes larger than the available width first, and then along the vertical axis once it becomes too big for the available height too.
    f

    frankelot

    1 year ago
    @Adam Powell thanks, that makes sense, so this is intentional, would like to learn more about it, do you have a link to the docs where this is explained?
    Adam Powell

    Adam Powell

    1 year ago
    nope, no docs links to this so far, just the primary source in this thread 🙂
    f

    frankelot

    1 year ago
    but the parent will position that element as if it were only 50dp
    got it, any ways to avoid this then 🤔
    Adam Powell

    Adam Powell

    1 year ago
    Modifiers have the ability to alter this behavior by implementing a resolution strategy for when the content becomes too big (or too small) for the constraints.
    Modifier.wrapContentSize
    is an example of this; it relaxes a minimum size constraint and lets you supply options for how the element should be positioned within the minimum size
    Note that
    wrapContentSize
    has an
    unbounded
    parameter that permits the element to be larger than the available size, which seems particularly relevant for your case 🙂
    f

    frankelot

    1 year ago
    Yup, a quick try using:
    .wrapContentSize(Alignment.TopStart, true)
    seems to fix the issue 🎉
    Adam Powell

    Adam Powell

    1 year ago
    so in the chain of modifiers, you want to relax the constraints and supply an alignment policy, then apply the unconstrained size:
    Modifier.wrapContentSize(desiredAlignment, unbounded = true)
      .size(animatedUnconstrainedSize)
    f

    frankelot

    1 year ago
    Oh wait, so I should be applying this to the
    Image
    composable, and not to the parent container
    Box
    ?
    That seems to work as well, I think I need to spend some time wrapping my head around how this works 😅
    Timo Drick

    Timo Drick

    1 year ago
    It doesn't matter because when you use it on the Box than the content size of the Box is infinite i would assume.
    This makes some effects much easier to archive. 6 month ago i implemented a height animation. So when the content of a Composable changes the height will adapt to the new content height smoothly. For this i implemented a custom Layout() but now it should be easier with just this modifier.
    f

    frankelot

    1 year ago
    I see, I’m wondering if it should be the default behaviour though. I’d rather this to be opt in
    Adam Powell

    Adam Powell

    1 year ago
    @Timo Drick if you're looking to write a custom layout for working with a single child at a time, you generally want to write a modifier instead.
    Modifier.layout {}
    uses the same DSL as the
    Layout
    composable
    @frankelot which behavior, the centering as the default strategy for a child element that disobeyed its constraints?
    f

    frankelot

    1 year ago
    Yes 🙂
    Adam Powell

    Adam Powell

    1 year ago
    we had a lot of conversations on this topic on the team and I insisted on this behavior as the default. 🙂 The reason centering is the default is that no other option is universally applicable. Left vs. right in layout is often subject to bidi/rtl layout concerns in different locales, so something that looks right to a developer in one locale might have very unexpected behavior in another.
    Similarly, different containers might stack elements vertically from the bottom, so top-anchoring isn't universal either. Plus there's no good reason for a default to have different rules for width vs. height - it would just be more complicated to explain and keep in your head
    Centering will always look the same at any level of tree recursion regardless of locale or container.
    f

    frankelot

    1 year ago
    I have no idea what I’m talking about here but, why does it have to be a behaviour in the first place? Why not just clipping the view and position it according to its
    offset
    modifier parameters
    Adam Powell

    Adam Powell

    1 year ago
    There's no guarantee or requirement that an
    offset
    modifier is present. Clipping to parents is opt-in in compose and implies additional drawing behavior that is normally decoupled from layout - clipping as part of this default layout conflict resolution would introduce a dependency that doesn't need to exist
    fundamentally, this behavior only comes into play when you've declared an unstoppable force against an immovable object: the child is unable to satisfy the constraints given to it by the parent.
    The first decision there is whether to permit it at all, or just crash. Crashing is pretty counterproductive here for a number of practical reasons; we can't give you a retroactive stack trace that is meaningful about where the problematic state was constructed, since the measurement constraints are dependent on the overall host environment (plus measurement happens long after composition of the elements has completed). They can change in ways that are unexpected by the original component developers, so robustness with predictable behavior is a better way to go here.
    The coordinate space may be (0, 0) at top left, but as mentioned above, defaulting to top left has locale and container-specific implications that can make the behavior harder to predict and reason about.
    Timo Drick

    Timo Drick

    1 year ago
    Maybe there should be a warning in the size modifier documentation about this. So i assume that you should use preferredSize and only if you know what you are doing you can use size
    Ok it is already documented 😄
    Adam Powell

    Adam Powell

    1 year ago
    that's ok, we know no one reads the docs 😄
    f

    frankelot

    1 year ago
    Alright! that makes a lot of sense, I’m lacking context to give constructive feedback on this, I’m just getting started with compose (and really liking it so far!! despite this minor bump that had me scratching my head for a few days, part of the learning process I guess 😅)
    Adam Powell

    Adam Powell

    1 year ago
    yeah I'd like to see us illustrate this really explicitly in a codelab or something. It's one of those things that is unexpected the first time you see it, but it's a simple rule that once you know it, you can debug layouts effectively with barely a glance at the output
    we made a lot of these kinds of API decisions expecting that for any given piece of compose UI code, there are three separate developers (or teams) working together, potentially without talking to one another: the developer who wrote the container that the composable is being placed in, the developer who wrote the elements being placed in a composable, and the developer writing the composable that arranges those elements.
    If someone tries to place a full date picker with calendar and all into a 50dp square, it's important that someone can determine very quickly which of those three teams to send a bug to
    If the date picker was forced to try to lay out its own contents in a space that is unreasonably small, someone would file that bug to the date picker's author, wait for it to get triaged, hear back, etc.
    and if the parent was forced to accommodate an unexpectedly large child, the developer who wrote the whole screen would get the bug, because the whole screen's layout would get disrupted
    with this default behavior, the surrounding layout is not disrupted, the date picker will be fully present (though probably too big for/overlapping/incidentally clipping in some way due to other surrounding clip behavior) and it is clear that whoever put the date picker in that particular spot probably made a mistake
    the exact place where the bug is is the part of the screen that looks wrong
    f

    frankelot

    1 year ago
    A codelab for this would be amazing 🙂 ! So, just to test my understanding. Using
    .wrapContentSize(Alignment, true)
    on a child basically tells its parent “I will ignore your constraints and position myself with this
    aligment
    )“, am I getting it right?… failing to use .`wrapContentSize`` on the child will just center (whenever it violates the parent constrains)
    Adam Powell

    Adam Powell

    1 year ago
    Yep, which is consistent with .wrapContentSize's default alignment parameter being center
    f

    frankelot

    1 year ago
    and the
    unbounded
    parameter determines if this applies for the
    max
    constrains or not
    Adam Powell

    Adam Powell

    1 year ago
    yep
    Timo Drick

    Timo Drick

    1 year ago
    I love compose 🙂 Now this effect is just 23 lines of code:
    @Composable
    fun <T> CrossHeightAnimation(current: T, children: @Composable() (T) -> Unit) {
        var lastHeight by rememberState<Float?> { null }
        val height = animatedFloat(initVal = 0f)
    
        Box(modifier = Modifier.fillMaxWidth()
                .height(if (height.isRunning) height.value.dp else lastHeight?.dp ?: 0.dp)
                .wrapContentHeight(align = Alignment.Bottom, unbounded = true)
                .onSizeChanged { size ->
            val newHeight = with(AmbientDensity.current) { size.height.toDp().value }
            lastHeight.let { lh ->
                if (lh == null) {
                    height.snapTo(newHeight)
                } else if (lh != newHeight) {
                    height.snapTo(lh)
                    height.animateTo(newHeight, anim = tween(500))
                }
            }
            if (lastHeight != newHeight)
                lastHeight = newHeight
        }) {
            children(current)
        }
    }
    f

    frankelot

    1 year ago
    Amazing 🙂 ! thank you so much
    pretty cool effect @Timo Drick I’m using Unsplash on my pet project as well
    Adam Powell

    Adam Powell

    1 year ago
    @Timo Drick try to avoid those kinds of feedback loops like the one formed by the
    onSizeChanged
    ->
    lastHeight
    ->
    .height()
    modifier; those are the kinds of things that form layouts that don't complete in a single pass, or that relayout until they hit some sort of coalescence
    some of it also might already be written for you by
    Modifier.animateContentSize
    as well
    Timo Drick

    Timo Drick

    1 year ago
    Yes indeed i do have problems there. Because the size is on frame behind the current visible frame.
    Adam Powell

    Adam Powell

    1 year ago
    Yep. That's generally a sign that you want to write a custom layout modifier so that you can make all of those decisions at once in a single pass rather than by using feedback loops in a chain of other layout modifiers
    Timo Drick

    Timo Drick

    1 year ago
    @Adam Powell ok i see. Compose changed and improve so fast that it is hard to be at state of the art 😄 Especcially when you do it just in your spare time.
    Adam Powell

    Adam Powell

    1 year ago
    remember too that the layout composable/modifier DSL performs its own snapshot state observation; if you read
    mutableStateOf
    state inside it, the system knows when to relayout automatically
    and even better, it doesn't have to recompose to do it; it only needs to do the layout/draw passes and can skip composition entirely
    Timo Drick

    Timo Drick

    1 year ago
    Ok than i will try to improve my Layout implementation. Because currently i do it wrong.
    f

    frankelot

    1 year ago
    @Adam Powell with your permission I’m going to use your responses to this thread to answer my own stackoverflow question in case it’s useful for someone else
    Adam Powell

    Adam Powell

    1 year ago
    Go for it 🙂
    f

    frankelot

    1 year ago
    Done! let me know if you want me to change anything
    Timo Drick

    Timo Drick

    1 year ago
    @Adam Powell the animateContentSize modifier does exactly what i need. Do not need to do any custom code.