here is my solution SInce PaddingValues is an inte...
# compose
z
here is my solution SInce PaddingValues is an interface, I created this
Copy code
/**
 * Represents an high performance [PaddingValues]. In Compose, `padding` values are represented by a `data class`,
 * but it is used frequently in animations and composables. Since it represents padding in dp instead of
 * pixels, making this a value class that stores 4 properties as half-precision shorts (`float16`)
 * within a Long is more efficient.
 *
 * The maximum value of a `float16` is `65505`, which is more than enough for everyday use cases.
 * Since padding can never be negative in Compose, we might increase this value further.
 *
 * This class also allows users to perform addition operations on padding values internally,
 * saving boilerplate code.
 */
@Immutable
@JvmInline
value class PaddingValues2(private val packed: Long) : PaddingValues {
    // Constructor to pack four Dp values into a single Long
    constructor(start: Dp = 0.dp, top: Dp = 0.dp, end: Dp = 0.dp, bottom: Dp = 0.dp) :
            this(
                (floatToHalf(start.value).toLong() shl 48) or
                        (floatToHalf(top.value).toLong() shl 32) or
                        (floatToHalf(end.value).toLong() shl 16) or
                        floatToHalf(bottom.value).toLong()
            )

    // Unpacking each 16-bit Dp value from the Long
    internal val start: Dp get() = Dp(halfToFloat((packed shr 48).toShort()))
    internal val top: Dp get() = Dp(halfToFloat((packed shr 32 and 0xFFFF).toShort()))
    internal val end: Dp get() = Dp(halfToFloat((packed shr 16 and 0xFFFF).toShort()))
    internal val bottom: Dp get() = Dp(halfToFloat((packed and 0xFFFF).toShort()))

    override fun calculateLeftPadding(layoutDirection: LayoutDirection) =
        if (layoutDirection == LayoutDirection.Ltr) start else end

    override fun calculateTopPadding() = top
    override fun calculateRightPadding(layoutDirection: LayoutDirection) =
        if (layoutDirection == LayoutDirection.Ltr) end else start

    override fun calculateBottomPadding() = bottom

    // Override toString for better readability
    override fun toString() = "Padding(start=$start, top=$top, end=$end, bottom=$bottom)"

    /**
     * Adds another `PaddingValues2` instance to this instance.
     *
     * @param other The other `PaddingValues2` instance.
     * @return A new `PaddingValues2` instance representing the sum of the two instances.
     */
    operator fun plus(other: PaddingValues2): PaddingValues2 {
        return PaddingValues2(
            start + other.start,
            top + other.top,
            end + other.end,
            bottom + other.bottom
        )
    }
}

I've come up with this solution, but I need to confirm if it's correct. I am new to Slack, so please be patient with me.
Thanks.
s
Hey welcome to Slack 👋 Take a look at the pinned comment of this channel https://kotlinlang.slack.com/archives/CJLTWPH7S/p1616265877303000?thread_ts=1616265877.303000&cid=CJLTWPH7S A good suggestion in general when you have big code snippets is to write your question in one message, possibly with a couple of lines of code if that gives good information, and then paste the bigger code snippets inside a thread which you can make by replying to yourself. It helps keep the channel readable for all people.
z
Thanks for the info. I am currently not on pc so I will fix this tomorrow, if that is not going to be the problem
d
This is a micro-optimization that is not necessary. The bit manipulation and additional conversions are likely to counteract and benefit you might get from saving a few bytes.
s
Fyi this value class will be boxed as soon as the function requires an instance of
PaddingValues
, and in that case using 4 ints is generally cheaper
z
@shikasd I was wondering will this be beneficial or not. If not then why Like in case of IntOffset, Offset, the devs have considered the same solution, I was wondering,will this be beneficial or not in the long run up This might seem a silly question, please bear with me on this one. I am trying to better understand bit manipulations and their benefits.
@Daniel Pitts why it is not necessary?
d
Why would it be necessary? How many instances of this class will be in memory at once? How many of them are being copied from one memory location to another? These are padding values, they are on the same order as the number of composables in your view.
z
@Daniel Pitts , thank you again for your response! I’d like to share my perspective on why having
PaddingValues
as a value class might be beneficial in certain cases. While it’s true that we typically use far fewer padding values in Compose, the fact that Compose automatically invalidates layouts makes it critical to have performant code wherever possible. Even seemingly small optimizations can add up in complex UI scenarios. For example, consider the collapsible top bar in Compose Material3. This component relies on animating padding values during its collapse/expand transitions. In such cases, having a more performant representation for
PaddingValues
could provide tangible benefits, especially during animations where every frame matters. Animating
PaddingValues
directly is another scenario where performance can be a concern. Additionally, I believe the rationale for making
PaddingValues
a value class is similar to why
IntOffset
and other similar structures in Compose are value classes. These are lightweight objects designed to avoid unnecessary heap allocations, which can improve performance and memory efficiency. That said, I think the primary reason
PaddingValues
hasn’t been implemented as a value class is because it’s an interface. Since interfaces in Kotlin lead to autoboxing when used with value classes, the benefits of using a value class would be nullified in many cases. The reason
PaddingValues
is an interface in the first place is to accommodate multiple implementations, such as
AbsolutePaddingValues
and
RelativePaddingValues
, which depend on the layout direction. This design ensures flexibility, but it does come at the cost of potential boxing and the inability to use value class optimizations.
🤔 1
d
That makes sense, but for this situation, I would only worry about it when there is a performance issue that can be profiled to find the cause. Often times, our assumptions about what does or doesn't affect performance are wrong, and the only real way to improve things is to measure.
z
@Daniel Pitts I agree, we should only be concerned when there is an actual performance issue.
s
IntOffset and other value classes are used directly in the corresponding code, that's why the compiler doesn't box them
👍 1
As soon as you pass it as Any or any other super type, you will get a boxed value, and value class is no longer beneficial