Chapters

Hide chapters

Jetpack Compose by Tutorials

First Edition · Android 11 · Kotlin 1.4 · Android Studio Canary

Before You Begin

Section 0: 3 chapters
Show chapters Hide chapters

9. Using ConstraintSets in Composables
Written by Tino Balint

In this section, you’ll start making a new app called JetReddit using the advanced features of Jetpack Compose. JetReddit is a composable version of the Reddit app in raywenderlich.com style. :]

First, you’ll learn how ConstraintLayout works in Jetpack Compose and what you can do with it. Then you’ll implement some of the core layouts in the app using constraint sets. Let’s get on it!

Understanding ConstraintLayout

To follow this chapter, you need to know how ConstraintLayout works.

ConstraintLayout is, as its name says, a layout. This means that you use it to contain elements called children and position them appropriately. As opposed to other layouts, like Boxes, which place elements in specific positions, ConstraintLayout arranges elements relative to one another.

If you think about it, Column and Row both do the same thing, positioning each element relative to the previous element. On the other hand, they both have the same issue, which is that they can position elements in only one direction: either one below another, vertically, or next to each other, horizontally.

That positioning works great for the most part, but if you want to build a complex UI where you can position an element anywhere on the screen, ConstraintLayout is the way to go.

ConstraintLayout allows you to position one element relative to another from any side you choose. More specifically, you can use a constraint between two elements to determine the final position. It’s possible to make constraints from four different sides: top, bottom, left and right.

Note: It’s better to use start and end instead of left and right. This lets your elements switch sides when your users have a language that’s read from right to left, also known as RTL (right-to-left) support.

ConstraintLayout Example

To make constraints easier to understand, look at the image below:

Constraint Layout Example
Constraint Layout Example

On the left side of the image, you see a basic login form with two inputs and a button. Inside the password input, you see a small eye icon that toggles whether or not the password displays in plain text, or if it’s hidden.

On the right side of the image, you see the zoomed-in buttons corresponding to the login form. Around the eye icon, arrows show the constraint directions. When you position your element on the screen, you have to think from the perspective of that element relative to the other elements.

In simple words, you can say that the eye icon is in the vertical center of the password element. It’s also constrained to the end of the password element, with a small space between the icon and the end of the box. When you set constraints, you follow this approach to model the positional information of the different elements.

Now, to implement that positioning. First, you make the constraint between the top of the icon and the top of the password element. Next, you make a constraint between the bottom of the icon and the bottom of the password element. Since one constraint pulls the icon to the top and the other one pulls to the bottom, the icon ends up in the vertical center.

Finally, you add a constraint between the end of the eye icon and the end of the password element. This positions the eye icon at the far-right side of the password element. To get the desired result, all you need to do is add a margin on the right side.

Now that you understand the essentials of working with ConstraintLayout, you’re ready to learn about its composable version.

ConstraintLayout in Jetpack Compose

In Jetpack Compose, there’s a composable with the same name called ConstraintLayout. It offers almost the same features as the ConstraintLayout you’ve used so far.

@Composable
fun ConstraintLayout(
   modifier: Modifier = Modifier,
   children: @Composable ConstraintLayoutScope.() -> Unit
)

This composable takes only two parameters:

  • The modifier to expose styling options, which is pretty standard.
  • children, which represents any number of composables that’ll be its children.

Now, imagine that you have a scenario like in the image above, where you want to position the eye icon inside the password input element. Here’s how you’d do that in Compose:

val (passwordInput, eyeIcon) = createRefs()

Icon(
   imageVector = vectorResource(id = R.drawable.ic_eye)

   modifier = Modifier.constrainAs(eyeIcon) {
     top.linkTo(passwordInput.top)
     bottom.linkTo(passwordInput.bottom)
     end.linkTo(passwordInput.end)
   }.padding(end = 16.dp)
)

First, you create references for the elements inside the ConstraintLayout, which serve as an ID for each of your elements. Calling createRefs() creates those references for you. Next, you set the vector resource and call constrainAs(). constrainAs() sets the reference for the current element, then sets the constraints between it and other elements within a lambda function.

For this situation, you set the eyeIcon reference and make three constraints, as shown in the image. You make each of those constraints by calling linkTo(). You link the top of the eye icon to the top of the password input, the bottom of the eye icon to the bottom of the password input and the end of the eye icon to the end of the password input.

Finally, you set the padding at the end of the eye icon to get that small space shown in the image.

Keep in mind that this example works on the assumption that you’ve already constrained other elements, like the password input.

Now that you have some essential knowledge about working with the ConstraintLayout(), you’re ready to start building your new app.

Implementing the app drawer layout

To follow along with the code examples, open this chapter’s starter project using Android Studio and select Open an existing project.

Next, navigate to 09-using-constraint-layout-in-composables/projects and select the starter folder as the project root. Once the project opens, let it build and sync and you’ll be ready to go!

Project Hierarchy
Project Hierarchy

There are several packages and classes already prepared for you, so you don’t have to worry about handling navigation, dependency injection and theme switching. If this looks a bit overwhelming, don’t worry, you only need to make changes to the appdrawer and screens packages.

Once you’re familiar with the file organization, build and run the app. You’ll see this screen:

Starting Screen
Starting Screen

Here, you see a top bar and a bottom bar with three different icons. Clicking on any of them will display an empty screen and the title in the top bar will change. Clicking on the Account icon in the top bar displays an empty app drawer.

Your first step in creating JetReddit is to build the app drawer. Your goal is to make a screen similar to the one in the official Reddit app, which looks like this:

Reddit App Drawer
Reddit App Drawer

The screen is split into three different sections: header, body and footer. You’ll implement each of them individually in the coming sections.

Before starting, check the root layout implementation for the app drawer by opening appdrawer/AppDrawer.kt and taking a look at AppDrawer():

@Composable
fun AppDrawer(
  modifier: Modifier = Modifier,
  closeDrawerAction: () -> Unit
) {
  Column(
    modifier = modifier
      .fillMaxSize()
      .background(color = MaterialTheme.colors.surface)
  ) {
    AppDrawerHeader()

    AppDrawerBody(closeDrawerAction)

    AppDrawerFooter(modifier)
  }
}

The composable has a Column as the root element with three custom composables as children that correspond to the header, the body and the footer in the previous screenshot. Your first step is to implement the header.

Creating the app drawer header

Examining the header section of the Reddit screenshot shows that you can break it down into smaller parts. First, you’ll need to add a profile icon with the user name below it. Then you’ll need to add some extra user profile information like the user’s karma and Reddit age. Finally, there’s a divider that separates the header from the body.

Implementing the user icon and name

You’ll implement the user icon and user name first. You’ll add them in a Column, because they need to be ordered vertically. Add the Column and the Image first:

@Composable
private fun AppDrawerHeader() {
  Column(
     modifier = Modifier.fillMaxWidth(),
     horizontalAlignment = Alignment.CenterHorizontally
  ) {
    Image(
       imageVector = Icons.Filled.AccountCircle,
       colorFilter = ColorFilter.tint(Color.LightGray),
       modifier = Modifier
           .padding(16.dp)
           .size(50.dp),
       contentScale = ContentScale.Fit,
       alignment = Alignment.Center
    )
  }
}

In this code, you added a Column that centers everything horizontally. You put the provided account image at the top below which you’ll add a Text with the default user name and a Divider to separate the header from the body. Do that next:

@Composable
private fun AppDrawerHeader() {
  Column(
     modifier = Modifier.fillMaxWidth(),
     horizontalAlignment = Alignment.CenterHorizontally
  ) {
	...
    Text(
      text = stringResource(R.string.default_username),
      color = MaterialTheme.colors.primaryVariant
    )
  }
  
  Divider(
    color = MaterialTheme.colors.onSurface.copy(alpha = .2f),
    modifier = Modifier.padding(
      start = 16.dp,
      end = 16.dp,
      top = 16.dp
    )
  )
}

The Text is using the default_username resource and you added an onSurface color to the Divider, with a custom alpha value. The divider also has some extra padding to make it look nicer while it separates the header from the body of the drawer.

Build and run, then open the drawer.

App Drawer Header Without Profile Info
App Drawer Header Without Profile Info

At this point you can see the whole header section, except for the profile info, which you’ll implement next.

Adding the profile info

To get a better understanding of what you need to implement, look at the following image:

Profile Info
Profile Info

As you see, this image includes a lot of repeated elements. This is good because it means you can extract components and reuse them multiple times.

That’s exactly what you’ll do with the Icon and the two Text elements. You’ll extract those components into a composable called ProfileInfoItem.

Extracting reusable components

Because these components require relative constraints, you’ll use a ConstraingLayout. Add the following code to ProfileInfoItem():

@Composable
private fun ProfileInfoItem(
...
) {
  val colors = MaterialTheme.colors

  ConstraintLayout(modifier = modifier) {
    val (iconRef, amountRef, titleRef) = createRefs() // references
    val itemModifier = Modifier

    Icon(
      imageVector = imageVector,
      tint = Color.Blue,
      modifier = itemModifier
        .constrainAs(iconRef) {
          centerVerticallyTo(parent)
          start.linkTo(parent.start)
        }.padding(start = 16.dp)
    )
  }
}

To begin building the reusable item, you need to create a ConstraintLayout, children references and add an Icon as its child. Again, using createRefs() you can create up to 16 component references and destructure them accordingly.

You then prepared the itemModifier, as it’s good practice to differentiate between parent and item modifiers.

Finally, using constrainAs(iconRef), centerVertically(parent) and linkTo(parent.start), you tell the Icon where you want to position it. Specifically, you want it to be centered vertically within the parent and at the very start of the parent, with a small amount of padding.

Now, below the Icon, add the Text that’ll represent the amount of karma points or the Reddit age:

@Composable
private fun ProfileInfoItem(
...
) {
  val colors = MaterialTheme.colors

  ConstraintLayout(modifier = modifier) {
	...
    Text(
      text = stringResource(amountResourceId),
      color = colors.primaryVariant,
      fontSize = 10.sp,
      modifier = itemModifier
        .padding(start = 8.dp)
        .constrainAs(amountRef) {
          top.linkTo(iconRef.top)
          start.linkTo(iconRef.end)
          bottom.linkTo(titleRef.top)
        }
    )
  }
}

This should be familiar now, as you want this element to be relative to the iconRef and the titleRef. You constrain it as amountRef, linking it to the top and the end of the iconRef. You also link the bottom of the amount Text to the top of the title Text, which you’ll add next.

@Composable
private fun ProfileInfoItem(
...
) {
  val colors = MaterialTheme.colors

  ConstraintLayout(modifier = modifier) {
	...
    Text(
      text = stringResource(textResourceId),
      color = Color.Gray,
      fontSize = 10.sp,
      modifier = itemModifier
        .padding(start = 8.dp)
        .constrainAs(titleRef) {
          top.linkTo(amountRef.bottom)
          start.linkTo(iconRef.end)
          bottom.linkTo(iconRef.bottom)
        }
    )
  }
}

Pretty straightforward. You added another element, again being constrained to the iconRef, but this time at the bottom instead of the top. You also linked the top of the titleRef to the bottom of the amountRef.

It’s important to know that you’ll use ProfileInfoItem() inside ProfileInfo(), which has its own ConstraintLayout as a root. To avoid constraint conflicts between the parent and child composables, you have to pass the modifier from the parent as a parameter and set it to the child’s ConstraintLayout.

Also, creating new references within this ConstraintLayout lets you avoid the previously mentioned conflicts when using multiple ConstraintLayout instances.

Build the app and take a look at the preview section to see the result.

Profile Info Item Preview
Profile Info Item Preview

Don’t worry if the colors are a bit off, they’ll change to match the theme once you run the app.

Completing ProfileInfo

Now, you’ll use your freshly made composable to complete ProfileInfo(). Replace the code of ProfileInfo() with the following implementation:

@Composable
fun ProfileInfo(modifier: Modifier = Modifier) {
  ConstraintLayout(
      modifier = modifier
          .fillMaxWidth()
          .padding(top = 16.dp)
  ) {
    val (karmaItem, divider, ageItem) = createRefs()
    val colors = MaterialTheme.colors

    ProfileInfoItem(
        Icons.Filled.Star,
        R.string.default_karma_amount,
        R.string.karma,
        modifier = modifier.constrainAs(karmaItem) {
          centerVerticallyTo(parent)
          start.linkTo(parent.start)
        }
    )

    Divider(
        modifier = modifier
            .width(1.dp)
            .constrainAs(divider) {
              centerVerticallyTo(karmaItem)
              centerHorizontallyTo(parent)
              height = Dimension.fillToConstraints
            },
        color = colors.onSurface.copy(alpha = .2f)
    )

    ProfileInfoItem(
        Icons.Filled.ShoppingCart,
        R.string.default_reddit_age_amount,
        R.string.reddit_age,
        modifier = modifier.constrainAs(ageItem) {
          start.linkTo(divider.end)
          centerVerticallyTo(parent)
        }
    )
  }
}

Here, you added a ConstraintLayout as the root and created references for the three elements that it contains: karma, divider and age. You constrained the karma item at the start of the parent, centered vertically.

Next, you added a divider in the vertical center of the parent and the horizontal center of the first ProfileInfoItem.

Finally, you added another ProfileInfoItem containing the age information to the right of the divider and centered it vertically. This should be more and more familiar to you because constraints work just like they did in the XML version of the ConstraintLayout. The main difference is that you have to write them in code, but with the amazing Compose syntax, that’s a piece of cake! :]

Now, add ProfileInfo() to AppDrawerHeader():

@Composable
private fun AppDrawerHeader() {
  Column(
     modifier = Modifier.fillMaxWidth(),
     horizontalAlignment = Alignment.CenterHorizontally
  ) {
    Image(
       ...
    )

    Text(
       ...
    )
    ProfileInfo() // Add this
  }
 
  Divider(
      ...
  )
}

This shows the missing profile information in the header.

Build and run the app to see the result.

App Drawer Header With Profile Info
App Drawer Header With Profile Info

You can now see the profile info section, split into two parts by a divider.

With this, you’ve completed the header section and had the chance to familiarize yourself with working with ConstraintLayout.

All right, it’s time to move on to the body!

Implementing the app drawer’s body

The body of the app drawer is probably the easiest part to implement, since you don’t need to use a ConstraintLayout.

The body section in the Reddit screenshot is your reference, but you’ll implement a very simplified version of it. You only need to add two buttons here, one to open the profile and the other to view the saved screens.

The button composable, ScreenNavigationButton, has already been prepared for you in the starter code. This composable has an icon, a label and an onClickAction.

Next, add the following code to AppDrawerBody():

@Composable
private fun AppDrawerBody(closeDrawerAction: () -> Unit) {
  Column {
    ScreenNavigationButton(
        icon = Icons.Filled.AccountBox,
        label = stringResource(R.string.my_profile),
        onClickAction = {
          closeDrawerAction()
        }
    )

    ScreenNavigationButton(
        icon = Icons.Filled.Home,
        label = stringResource(R.string.saved),
        onClickAction = {
          closeDrawerAction()
        }
    )
  }
}

Here, you added two ScreenNavigationButtons for the drawer’s body. Currently, the action you pass to the buttons only closes the drawer because you haven’t implemented those screens yet. You’ll take care of that in the coming chapters.

Note that both buttons are set inside the Column. You did this so you can preview this composable, but it’s not necessary since the root composable AppDrawer already uses a Column.

Remember that you passed closeDrawerAction as an argument in AppDrawer():

fun AppDrawer(closeDrawerAction: () -> Unit, modifier: Modifier = Modifier) {
   ...
   AppDrawerBody(closeDrawerAction)
   ...
}

Build and run the app, then open the app drawer once again.

App Drawer Body
App Drawer Body

You can now see the two buttons below the header section. When you click on either of them, the drawer closes.

Implementing the app drawer footer

Once again, check the Reddit screenshot, but this time, pay closer attention to the bottom of the screen. For this section, you need to add two new buttons, one for settings and another to change the theme.

Start by adding the initial ConstraingLayout setup to the AppDrawerFooter():

@Composable
private fun AppDrawerFooter(modifier: Modifier = Modifier) {
  ConstraintLayout(
    modifier = modifier
      .fillMaxSize()
      .padding(
        start = 16.dp,
        bottom = 16.dp,
        end = 16.dp
      )
  ) {

    val colors = MaterialTheme.colors
    val (settingsImage, settingsText, darkModeButton) = createRefs()
  }
}

Here you added the ConstraintLayout, styled it with modifiers and prepared all the references you’ll need to position its children. Now add the settings Icon and its label:

@Composable
private fun AppDrawerFooter(modifier: Modifier = Modifier) {
  ConstraintLayout(
	...
  ) {
	...
    Icon(
      modifier = modifier.constrainAs(settingsImage) {
        start.linkTo(parent.start)
        bottom.linkTo(parent.bottom)
      },
      imageVector = Icons.Default.Settings,
      tint = colors.primaryVariant
    )

    Text(
      fontSize = 10.sp,
      text = stringResource(R.string.settings),
      style = MaterialTheme.typography.body2,
      color = colors.primaryVariant,
      modifier = modifier
        .padding(start = 16.dp)
        .constrainAs(settingsText) {
          start.linkTo(settingsImage.end)
          centerVerticallyTo(settingsImage)
        }
    )
  }
}

These two elements are positioned at the bottom of the parent. The Icon sits at the start of the parent, while the label is linked to the end of the settingsImage. The rest of the code should be self-explanatory, as its mostly styling the Text and the Icon.

Now add the last element to the footer—the theme Icon:

@Composable
private fun AppDrawerFooter(modifier: Modifier = Modifier) {
  ConstraintLayout(
	...
  ) {
	...
    Icon(
      imageVector = vectorResource(id = R.drawable.ic_moon),
      modifier = modifier
        .clickable(onClick = { changeTheme() })
        .constrainAs(darkModeButton) {
          end.linkTo(parent.end)
          bottom.linkTo(settingsImage.bottom)
        },
      tint = colors.primaryVariant
    )
  }
}

The theme Icon follows the same principles, except that it’s constrained to the bottom and end of the parent. Also, for the theme icon, you added an onClick action to change the theme by calling changeTheme(). This function is pre-built for you in the starter project.

Build and run the app, then open the drawer.

App Drawer Footer
App Drawer Footer

There’s now a footer inside the drawer with the settings and the theme icons. If you click on the theme icon, the app will change to the dark theme, which has all the colors already defined.

Advanced features of ConstraintLayout

ConstraintLayout makes building UI much easier than before. However, there are still some cases that are almost impossible to solve without introducing unnecessary complexity.

For example, consider the case from earlier in the chapter, when you made the profile info composable in the drawer. That setup had some elements at the left side of the screen and others close to the vertical line in the center.

Now, imagine that there was no vertical line. How would you position your elements to start from the center of the screen? One idea is to place an element in the center that’s invisible and position your other elements relative to that object. The solution is a lot like that, only more optimized.

Guidelines

A guideline is an invisible object you use as a helper tool when you work with ConstraintLayout. You can create a guideline from any side of the screen and use one of two different ways to give it an offset:

  • You can specify the fixed amount of dp you want the offset to be.
  • You can give the screen percentage if you want the guideline to display in the same place, regardless of the screen size.

You use different functions to create guidelines, depending on where you want to place them. In your previous example, where you needed a guideline in the vertical center, you could use either of the following options:

createGuidelineFromStart(0.5f)
createGuidelineFromEnd(0.5f)

This creates a vertical anchor that’s half a screen away from the start or half a screen away from the end. The anchor is a virtual helper that isn’t displayed on the screen, but which allows you to make constraints to it. Here’s an example of how to use one:

val verticalGuideline = createGuidelineFromStart(0.5f)
Icon(
  imageVector = iconAsset,
  modifier = Modifier
    .constrainAs(iconReference) {
       start.linkTo(verticalGuideline)
       top.linkTo(parent.top)
       bottom.linkTo(parent.bottom)
    }
)

Here, you create a vertical guideline at the center of the screen. Next, you add an icon that’s centered vertically and starts from your guideline’s position, the vertical center.

You can also use any of the following functions to create an anchor, depending on your need. For vertical anchors you can choose from:

  • createGuidelineFromStart()
  • createGuidelineFromAbsoluteLeft()
  • createGuidelineFromEnd()
  • createGuidelineFromAbsoluteRight()

For horizontal anchors, you have the following functions:

  • createGuidelineFromTop()
  • createGuidelineFromBottom()

You then use the vertical anchor to make vertical constraints and the horizontal anchor to make horizontal constraints.

Vertical constraints are:

  • Start
  • AbsoluteLeft
  • End
  • AbsoluteRight

Horizontal constraints are:

  • Top
  • Bottom

Note that all constraints and anchors with the absolute prefix represent the absolute left or right of the screen, regardless of the different layout directions, such as right-to-left.

If you want to learn more about guidelines, check out the official documentation: https://developer.android.com/reference/kotlin/androidx/compose/foundation/layout/ConstraintLayoutBaseScope.

Barriers

Now that you know how to position objects at specific places on the screen, it’s time to think about some other problems you can solve.

Take a look at the image below to better understand the next problem:

Barrier
Barrier

On the left side of the image, you see a button and two text fields: first and last name. In this scenario, you want to place the button to the left of the two text fields. In the first example, these texts have almost the same width, so the button will always be on the left, no matter which text you constrain to it.

In the second example, you have a long first name and a short last name. To have the button on the left side of both texts, you need to constrain it to the start of the first name, because that’s the longer one.

In the last example, you have the opposite situation. To have the button on the left side of the two texts, you need to constrain it to the start of the last name.

From these examples, you can see that for this to work, the button should sometimes be constrained to the first name and sometimes to the last name, depending on which one is bigger.

To solve this problem, you add a barrier within the ConstraintLayout. A barrier is an element that can contain multiple constraint references.

Here’s how you can use barriers to solve the problem from the previous example:

ConstraintLayout(modifier = Modifier.fillMaxSize()) {
  val (button, firstName, lastName) = createRefs()
  val startBarrier = createStartBarrier(firstName, lastName)

  Text(
    text = "long first name",
    modifier = Modifier.constrainAs(firstName) {
      end.linkTo(parent.end)
      top.linkTo(parent.top)
    }
  )

  Text(
    text = "last name",
    modifier = Modifier.constrainAs(lastName) {
      end.linkTo(parent.end)
      top.linkTo(firstName.bottom)
    }
  )
  
  Button(
    content = {},
    onClick = {},
    modifier = Modifier.constrainAs(button) {
      end.linkTo(startBarrier)
    }
  )
}

First, you create constraint references for all three elements. Next, you create a start barrier by passing the references for the first and last name. You use the start barrier when you want to set a constraint to multiple elements from their left side.

Finally, you make a constraint from the end of the button to the start barrier. This will ensure that the button is always constrained to the element with the larger width, resolving the problem.

As with guidelines, you can create a barrier from any side by calling one of the following functions:

  • createStartBarrier()
  • createAbsoluteLeftBarrier()
  • createEndBarrier()
  • createAbsoluteRightBarrier()
  • createTopBarrier()
  • createBottomBarrier()

Chains

The final problem that you might face when using ConstraintLayout is when you have multiple elements that are constrained to each other. Here are the possible scenarios:

Chains
Chains

You can see three different screens, each containing three elements. In the first case, the elements are placed together in the middle, one next to the other. In the second case, they’re spaced evenly from each other and the screen edges. In the last case, the elements are still evenly spaced, but they start at the edge of the screen.

With your current knowledge of constraints, if you had these three elements, you could constrain the elements to each other from both sides and to the parent at the edges. By doing this, you could achieve the first result, but not the other two.

Cases like these are solved with chains. A chain allows you to reference multiple elements that are constrained to each other, forming a chain as in the image above. Once you have a chain, you can specify the ChainStyle you want. There are three types of ChainStyles, which correspond to the scenarios described earlier:

  • Packed: All the elements are packed in a group, as in the first example.
  • Spread: All the elements are spread evenly from each other and the edges, as in the second example.
  • SpreadInside: All the elements are spread evenly from each other but start at the edges, as in the third example.

Here is an example that uses ChainStyle.SpreadInside:

val (firstElement, secondElement, thirdElement) = createRefs()

Button(
  modifier = Modifier
  .constrainAs(firstElement) {
    start.linkTo(parent.start)
    end.linkTo(secondElement.start)
    top.linkTo(parent.top)
    bottom.linkTo(parent.bottom)
  }
)

Button(
  modifier = Modifier
  .constrainAs(secondElement) {
    start.linkTo(firstElement.end)
    end.linkTo(thirdElement.start)
    top.linkTo(parent.top)
    bottom.linkTo(parent.bottom)
  }
)

Button(
  modifier = Modifier
  .constrainAs(thirdElement) {
    start.linkTo(secondElement.end)
    end.linkTo(parent.end)
    top.linkTo(parent.top)
    bottom.linkTo(parent.bottom)
  }
)

createHorizontalChain(
    firstElement,
    secondElement,
    thirdElement, 
    chainStyle = ChainStyle.SpreadInside
)

Note that applying a specific ChainStyle will only work if you create a chain between the elements. In the previous code, you do this by setting the constraint references between the three buttons. Also, keep in mind that you can create both vertical and horizontal chains.

Check out the official ChainStyle documentation for more information: https://developer.android.com/reference/kotlin/androidx/compose/foundation/layout/ChainStyle.

That was an overview of the most complex layout you can currently use in Android. By now, you should be able to make a screen of any complexity using what you’ve learned.

Keep this in mind because, in the next chapter, that’s exactly what you’ll be doing: making a complex UI to further implement the features in your JetReddit app.

You’ll combine everything you learned so far and you’ll use the component-based approach reuse as much code as possible. See you in the next chapter!

Key points

  • ConstraintLayout positions its children relative to each other.
  • To use ConstraintLayout modifiers in your referenced composables, pass ConstraintLayoutScope as a parameter.
  • It’s better to use start and end constraints, rather than left and right.
  • Use createRefs() to create constraint references for your composables.
  • Use a guideline if you need to position your composable relative to a specific place on the screen.
  • Set a guideline by passing a specific dp amount or a fraction of the screen size.
  • Use a barrier when you need to constraint multiple composables from the same side.
  • Use a chain when you need multiple elements constrained to each other.
  • Use ChainStyle to specify the kind of chain to use.
Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2026 Kodeco Inc.