Advanced Jetpack Compose Modifiers

Jetpack compose, a modern UI toolkit for building Android UI using Kotlin programming language as a declarative programming model. Jetpack Compose Modifiers is an object that you will use most of the time while creating UI in Android using jetpack compose. It does all the tasks like giving padding, margin, shaping an element, giving color to a UI component or to the background and border etc. You can check out our last post which talks about the basics of modifiers.

A modifier makes elements of UI alive by making them clickable, scrollable, draggable, or zoomable. In this post, we will discuss advanced uses of jetpack compose’s modifier object.

Order of modifiers matters (Modifiers chaining)

1. What is Modifier Chaining

Calling of a bunch of modifiers together on the same modifier object. Example:

Text(
            text = "Hello Jetpack Compose",
            modifier = Modifier
                .padding(bottom = 16.dp)
                .clip(RectangleShape)
                .background(color = Color.Green)
                .padding(8.dp)
        )

In the above code block, we are calling padding(), clip(), background() and padding() functions again on the same modifier object. This is called modifier chaining.

2. Ordering of Jetpack Compose Modifiers

The order of the modifiers function is very important because each function makes changes to the Modifier returned by the previous function, the sequence affects the final result. Ordering matters because modifier elements that are added first will be applied first. Let’s take an example:

Image(
            painter = painterResource(id = R.drawable.cat),
            contentDescription = null,
            modifier = Modifier
                .clickable { }
                .padding(16.dp)
                .clip(RoundedCornerShape(16.dp))
        )
image showing whole area clickable including padding which is given after clickable function on jetpack compose modifiers object

In the above code, the whole area is clickable, including the padding, because the padding modifier has been applied after the clickable modifier, that’s why it made the UI component clickable first and then added padding. Now, if we reverse the order of clickable and padding modifier, the space added by the padding modifier does not react to user input. Let’s do this:

Image(
            painter = painterResource(id = R.drawable.cat),
            contentDescription = null,
            modifier = Modifier
                .padding(16.dp)
                .clickable { }
                .clip(RoundedCornerShape(16.dp))
        )
image showing rounded corner clickable on jetpack compose modifiers

Now the padding area is not clickable but the area of the image which is modded by the clip modifier is clickable due to the same reason for calling the clickable modifier earlier than the clip. Let’s modify this:

Image(
            painter = painterResource(id = R.drawable.cat),
            contentDescription = null,
            modifier = Modifier
                .padding(16.dp)
                .clip(RoundedCornerShape(16.dp))
                .clickable { }
        )
image showing only image is clickable in jetpack compose modifiers

paddingFromBaseline Modifier

In the last post of modifiers, we talked about padding modifier which gives padding all around the UI element. Use the paddingFromBaseline modifier to achieve a specific distance from the top of the layout to the baseline. It will add padding above a text baseline.

Column{
        Text(
            text = "Padding From Baseline",
            modifier = Modifier.paddingFromBaseline(top = 50.dp)
        )
        Text(text = "Second text")
    }
image showing padding from baseline modifier

In the above code, padding is given from the baseline of the “Padding from Baseline” text element.

Clickable Modifier

As the name suggests, this modifier makes the element clickable or makes them able to respond on click. In the below code block, I suggest you ignore myColor variable initialization if you are a beginner. Let’s understand the code, firstly we created a variable named myColor which holds the initial colour for the border then in the clickable lambda function we assigned a different colour to myColor variable which causes the border to change colour on clicking the text element.

var myColor by remember {
    mutableStateOf(Color.Magenta)
}
Column {
    Text(
        text = "Hello Jetpack Compose",
        modifier = Modifier
            .fillMaxWidth()
            .background(Color.LightGray)
            .padding(16.dp)
            .border(4.dp, myColor, RoundedCornerShape(4.dp))
            .padding(8.dp)
            .clickable {
                myColor = Color.Green
            }
    )

}
a gif showing clickable jetpack compose modifiers

Offset Modifier

It allows an element to position itself relative to its original position. Offsets can be positive as well as non-positive and it takes two parameters, one for the x-axis and other for the y-axis.

Column{
        Text(
            text = "Offset Modifier",
            modifier = Modifier.paddingFromBaseline(top = 50.dp)
        )
        Text(
            text = "Second text",
            modifier = Modifier.offset(x = 16.dp)
        )
    }
image showing offset jetpack compose modifiers

In the above image, the second text element shifts to the right by 16 dp because of the offset modifier. Now, you will say what is the difference between offset and padding modifiers? The difference between padding and offset is that adding an offset to a composable does not change its measurements.

The offset modifier is applied horizontally according to the layout direction. In a left-to-right context, a positive offset shifts the element to the right, while in a right-to-left context, it shifts the element to the left. In the case of a negative value, the direction will be exactly opposite to the original. Let’s take the example of a negative offset and consider both parameters x and y.

Column {
        Text(
            text = "Offset Modifier",
            modifier = Modifier.
            paddingFromBaseline(top = 50.dp)
        )
        Text(
            text = "Second text",
            modifier = Modifier.offset(
                x = 16.dp,
                y = 6.dp
            )
        )
    }
Column {
        Text(
            text = "Offset Modifier",
            modifier = Modifier.
            paddingFromBaseline(top = 50.dp)
        )
        Text(
            text = "Second text",
            modifier = Modifier.offset(
                x = -(16).dp,
                y = -(8).dp
            )
        )
    }
image showing offset modifier with both parameter x and y
Positive Offset
image showing negative offset modifier
Negative Offset

In the above code, the offset modifier takes two parameters x and y. The x parameter defines offset horizontally and the y parameter defines offset vertically. In the left image, the 16.dp value of the x parameter shifts the text element rightward and 6.dp value of the y parameter shifts the text element downward. And in the right image, the negative value of the x parameter shifts the text element leftward and the negative value of the y parameter shifts the text element upward.

matchParentSize Modifier

This modifier is only available to the child of the Box layout of the jetpack compose. Therefore, it can only be used on a child within a Box parent. If you want to make a child as big as the parent Box without affecting the Box size, use the matchParentSize modifier. Let’s take an example:

Box {
        Spacer(
            modifier = Modifier
                .matchParentSize()
                .background(Color.Gray)
        )
        WebsiteNameShower()
    }
image showing match parent size jetpack compose modifiers

In the above code, the child Spacer takes its size from its parent Box, which in turn takes its size from the biggest children, WebsiteNameShower in this case. The complete code including the WebsiteNameShower function.

If fillMaxSize were used instead of matchParentSize, the Spacer would take all the available space allowed to the parent in turn causing the parent to expand and fill all the available space. Let’s take an example:

Box {
        Spacer(
            modifier = Modifier
                .fillMaxSize()
                .background(Color.Gray)
        )
        WebsiteNameShower()
    }
image showing fill max size modifier

Reusing Jetpack Compose Modifiers ( Extracted Modifiers )

Sometimes it can be beneficial to reuse the same modifier chain instances in different composable, by extracting them into variables and hoisting them into higher scopes. Benefits of doing this:

  • It can improve the readability of code.
  • It can improve app performance because reallocation of the modifiers won’t be repeated when recomposition occurs for composable that use them.
  • Modifier chains could be very long and complex, so reusing them can alleviate the workload Compose runtime.
  • It also promotes code cleanliness, consistency and maintainability across the codebase.

Let’s understand this with an example:

Column {
        Text(
            text = "Hello Jetpack Compose",
            modifier = Modifier
                .fillMaxWidth()
                .background(Color.Yellow)
                .padding(16.dp)
                .border(4.dp, Color.Magenta, RoundedCornerShape(4.dp))
                .padding(8.dp)
        )
        Text(
            text = "Hello Jetpack Compose World",
            modifier = Modifier
                .fillMaxWidth()
                .background(Color.Yellow)
                .padding(16.dp)
                .border(4.dp, Color.Magenta, RoundedCornerShape(4.dp))
                .padding(8.dp)
        )
    }
image showing drawbacks of not reusing modifiers

In the above code, we can see that both text elements are taking the same modifier object and when recomposition will occur it will take extra time to load. Now we can solve this problem by creating a variable of modifier and then using the same variable in both text elements. Let’s see:

val reusableModifier = Modifier
        .fillMaxWidth()
        .background(Color.Yellow)
        .padding(16.dp)
        .border(4.dp, Color.Magenta, RoundedCornerShape(4.dp))
        .padding(8.dp)
    
    Column {
        Text(
            text = "Hello Jetpack Compose",
            modifier = reusableModifier
        )
        Text(
            text = "Hello Jetpack Compose World",
            modifier = reusableModifier
        )
    }

And the output will be the same for this code block as well.

image showing reusability of modifier

Chaining of Extracted Jetpack Modifiers

Let’s assume, that I want to give different background colors to the text elements in the above image and the remaining styling will be the same. In this case, we will use .then() function to further append or chain extracted modifiers. Let’s take an example:

val reusableModifier = Modifier
        .fillMaxWidth()
        .padding(16.dp)
        .border(4.dp, Color.Magenta, RoundedCornerShape(4.dp))
        .padding(8.dp)

    Column {
        Text(
            text = "Hello Jetpack Compose",
            modifier = Modifier
                .background(Color.Yellow).then(reusableModifier)
        )
        Text(
            text = "Hello Jetpack Compose World",
            modifier = Modifier
                .background(Color.Green).then(reusableModifier)
        )
    }
image showing then function for further chaining of modifier in case of extracted modifier.

Note: Always remember the order of modifiers matters.

Well, that’s all for advanced uses of jetpack compose modifiers. Jetpack Compose Modifier is a powerful tool for building impressive UI with ease. I know, it could be cumbersome to understand modifiers but I can assure you, without having a good knowledge of the modifiers, no one can go far. So, take a break if needed and come back and start where you left off.

Happy Coding 🙂

Leave a Reply

Your email address will not be published. Required fields are marked *