Jetpack Compose : State & Constraint Layout

Shubham Lambe
7 min readMay 10, 2021

This article is all about how we can create declarative native user interface in Android. It covers an important concept of Compose:State and explains how to render UI with the help of constraint layout using compose.

Introduction

Jetpack Compose is basically a modern UI toolkit that simplifies and accelerates UI development on Android and is based on Google’s Material Design UI. This toolkit actually eliminates the need to design the layout in a separate XML file. It has several benefits that plays a major role in rolling up the development. Android Developers

Compose is an absolute shift in Android UI development, but it also majorly simplifies many of the challenges that the legacy UI system already has. Many applications nowadays uses SDUI to render the UI as it provides more control over the displayed content in the applications.Compose provides a native UI platform in order to build apps through SDUI.

Benefits

  1. Follows Declarative pattern
  2. Concise in nature
  3. Accelerates Development
  4. Compatibility with existing views & widgets
  5. Unidirectional Data Flow
  6. Helps in implementing Server Driven UI (SDUI)

Prerequisite

Basic Knowledge of :

  • Android Development
  • Constraint Layout

Composable Functions

Composable functions must be annotated with @Composable annotation which informs the compiler that this function adds UI to the View Hierarchy. While Composable functions can call other standard functions from the compose framework. Composables themselves can only be called from other Composables.

In order to create our UI ,we’ve to write function with composable annotation as below:

There are number of widgets with a lot of parameters to customize UI as required.The properties of a parameter (such as modifier, textStyle, width, height,border etc) on a widget applies sequentially in a builder pattern.

Compose: State

As we know that every object maintains a state,that can change overtime.

So state is any value that is supposed to have change in it. For instance, LiveData is nothing but a state that the UI observes for any change.

Those values of state includes value of LiveData instance,property of a class,room database entity state, jSON update from API call etc. And as soon as the state changes, we need to update the UI to reflect those changes to user.

Get Started

Lets begin with a very basic example that has a composable function UpdateState().This function just updates a numeric text,every time user clicks on the text.

The above function has a mutable property counter ,whose state is supposed to be changed over time.Initially, by default the property holds a value i.e 1 an integer. As the user clicks on the Text field, the value of the counter gets incremented.And the function gets re-invoked to update the state,as the function gets executed again,it doesn’t reset the value back to 1 because of the remember {}expression.

The remember{ } lambda expression is again a composable function that remember the value produced by calculation. Calculation will only be evaluated during the composition. Recomposition will always return the value produced by composition. In a nutshell, it maintains the last updated value of the property.

Recomposition is the process of updating the UI as a result of a State or Data Change that a Composable is using to display. During recomposition, Compose understands which data each Composable uses and only updates the UI components that have changed. Whereas,the rest of the Composables are skipped.

In the above function,every time the user clicks on the text,the function gets re-invoked in order to update the UI state.

As we call the function inside the setContent of onCreate() function in our activity. We can see the state of the text is getting updated by incrementing the value.

Here we are now able to maintain and update the state of a mutable property.

On a broader picture,we can consider an API call which returns some response data that gets updated.We are storing the response in a mutable list that is enclosed within remember function.We just need to update the value of the mutable list, as we get the updated response from the server,it automatically re-invoke our composable function and further update the UI state.

Constraint Layout

As constraint layout is the default layout in android studio, it provides multiple ways of placing the UI widgets. We can constraint the views with respect to each other as well as the parent layout in different ways. In order to use constraint layout in compose we need to add the following dependency:

In this example, we will be creating a very basic login page UI. There are few basic steps involved in creating constraint layouts, which are as follows:

Step 1) Create a constraint set that includes references of all the UI widgets that are being rendered.

createRefFor(“YOUR_VIEW_ID”) is used to create a reference to denote the particular widget. createRefs() can also be used to create references in a single line of code.

Step 2) Give constraints to all the widgets with respect to each other/parent layout/guideline etc. By passing the widget reference to the constrain(“YOUR_VIEW_ID”) function and setting the properties as required, we can set the constraints.

Step 3) Finally we can add the constraints to the constraint layout, where we can specify the UI widgets, set there properties,modifiers, provide them there respective layout id’s.

Step 4) We can create guidelines to constrain the views as below:

Then use the guideLine1 object to constrain any of the view by specifying link to the guideline within the constrain scope.

Step 5) In order to arrange the views by chaining them together vertically or horizontally, we can use chains as below:

We can specify multiple views that we want to chain them together in the respective layout, along with the chain style.

@Composable
fun RenderLayout() {
val constraints = ConstraintSet {
//References of widgets in the layout
val title = createRefFor("tvTitle")
val logo = createRefFor("ivLogo")
val input1 = createRefFor("etInput1")
val input2 = createRefFor("etInput2")
val button = createRefFor("btnSubmit")

//guideline
val guideLine1 = createGuidelineFromTop(0.5f)

//set constraints to the widgets
constrain(title)
{
top.linkTo(logo.bottom)
start.linkTo(parent.start)
end.linkTo(parent.end)
width = Dimension.fillToConstraints
height = Dimension.wrapContent
}

constrain(logo)
{
top.linkTo(parent.top)
start.linkTo(parent.start)
end.linkTo(parent.end)
bottom.linkTo(title.top)
width = Dimension.value(150.dp)
height = Dimension.value(150.dp)
}

constrain(input1)
{
bottom.linkTo(guideLine1)
start.linkTo(parent.start)
end.linkTo(parent.end)
width = Dimension.fillToConstraints
height = Dimension.wrapContent
}

constrain(input2)
{
top.linkTo(guideLine1)
start.linkTo(parent.start)
end.linkTo(parent.end)
width = Dimension.fillToConstraints
height = Dimension.wrapContent
}

constrain(button)
{
start.linkTo(parent.start)
end.linkTo(parent.end)
bottom.linkTo(parent.bottom)
width = Dimension.fillToConstraints
height = Dimension.wrapContent
}

createVerticalChain(title,logo,chainStyle = ChainStyle.Spread)

}
//Adding constraints in the constraint layout
ConstraintLayout
(
constraints, modifier = Modifier
.fillMaxSize()
) {
Text(
text = "CONSTRAINTS",
textAlign = TextAlign.Center,
fontWeight = FontWeight.Bold,
style = TextStyle(
fontSize = 24.sp,
color = Color.DarkGray,
fontFamily = FontFamily(Font(R.font.cabin_semicondensed_bold))
),
modifier = Modifier
.padding(100.dp)
.layoutId("tvTitle")
)

Image(
painter = painterResource(id = R.drawable.ic_baseline_content_paste_24),
contentDescription = "Content",
modifier = Modifier
.padding(20.dp)
.layoutId("ivLogo")
)
TextField(
value = "",
onValueChange = {},
label = {
Text("Username")
},
modifier = Modifier
.layoutId("etInput1")
.padding(start = 10.dp, end = 10.dp)
)
TextField(
value = "",
onValueChange = {},
label = { Text("Password") },
modifier = Modifier
.layoutId("etInput2")
.padding(start = 10.dp, end = 10.dp)

)
Button(
onClick = { Toast.makeText(this, "Submit", Toast.LENGTH_SHORT).show() },
modifier = Modifier
.layoutId("btnSubmit")
)
{
Text("SUBMIT")
}
}
}

Final Output:

In this post we’ve understood the concept of state in compose as well as how to create constraint layout and give constraints to the view within our jetpack compose UI. Along with, how we can customize their look and feel with the use of the modifiers and other properties of compose library.

If there are any queries, questions please do reach out. I am still learning and exploring, so if you have any suggestions or opinions, please let me know so I can improve my work.

Thanks *

--

--