Converting XML UI to Jetpack Compose

Jyoti Sheoran
4 min readFeb 6, 2023

--

Before discussing screen migration, let’s compare the components used in the XML-based views and Jetpack Compose.

In XML-based views, group views or layouts such as FrameLayout, LinearLayout, RelativeLayout, and ConstraintLayout were used as containers for other views.

Jetpack Compose supports several layouts out of the box, including:

  • Box: A simple layout that stacks its children one on top of the other.
  • Column: A layout that arranges its children in a vertical column.
  • Row: A layout that arranges its children in a horizontal row.
  • Stack: A layout that arranges its children on top of each other with the last child on top.
  • Flow: A layout that arranges its children in a flow and can wrap to a new line as needed.
  • ConstraintLayout: A layout that uses constraints to position its children relative to each other and the parent.
  • ScrollableColumn: A vertical scrolling column that allows its children to scroll if the content exceeds the screen height.
  • ScrollableRow: A horizontal scrolling row that allows its children to scroll if the content exceeds the screen width.
  • LazyColumn: A layout that arranges its children in a vertical column and only lays out the visible children for improved performance.

These layouts are sufficient for most use cases, but Jetpack Compose also supports custom layouts by combining and extending these basic layouts.

Here’s an example that demonstrates how these basic composables can be used in Jetpack Compose.

Consider the following UI design where we have a header, a body with four columns, and a footer:

+------------------------------------------------+
| Header |
+------------------------------------------------+
| Column 1 Column 2 |
+------------------------------------------------+
| Column 3 Column 4 |
+------------------------------------------------+
| Footer |
+------------------------------------------------+

Here’s how you could implement this UI design in XML:

<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">

<!-- Header -->
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Header"
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
/>

<!-- Body -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:orientation="vertical">

<!-- Row 1 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:orientation="horizontal">

<!-- Column 1 -->
<TextView
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:text="Column 1"
android:textAppearance="@style/TextAppearance.AppCompat.Body1" />

<!-- Column 2 -->
<TextView
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:text="Column 2"
android:textAppearance="@style/TextAppearance.AppCompat.Body1" />
</LinearLayout>

<!-- Row 2 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:orientation="horizontal">

<!-- Column 3 -->
<TextView
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:text="Column 3"
android:textAppearance="@style/TextAppearance.AppCompat.Body1" />

<!-- Column 4 -->
<TextView
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:text="Column 4"
android:textAppearance="@style/TextAppearance.AppCompat.Body1" />
</LinearLayout>
</LinearLayout>

<!-- Footer -->
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Footer"
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
/>
</LinearLayout>

Here’s how you could implement this UI design using Jetpack Compose:

@Composable
fun MyComplexUI() {
Column(modifier = Modifier.fillMaxSize()) {
// Header
Text("Header", style = TextStyle(fontSize = 20.sp))

// Body
Column(modifier = Modifier.weight(1f)) {
// Row 1
Row(modifier = Modifier.weight(1f)) {
// Column 1
Text("Column 1", style = TextStyle(fontSize = 14.sp), modifier = Modifier.weight(1f))

// Column 2
Text("Column 2", style = TextStyle(fontSize = 14.sp), modifier = Modifier.weight(1f))
}

// Row 2
Row(modifier = Modifier.weight(1f)) {
// Column 3
Text("Column 3", style = TextStyle(fontSize = 14.sp), modifier = Modifier.weight(1f))

// Column 4
Text("Column 4", style = TextStyle(fontSize = 14.sp), modifier = Modifier.weight(1f))
}
}

// Footer
Text("Footer", style = TextStyle(fontSize = 20.sp))
}
}

In this example, we use a Column composable for the main container. Inside the Column, we have the header, body, and footer sections, each of which is implemented using a combination of Text, Row, Column composables. The Text composable is used to display the header, body, and footer text.

Note that in Jetpack Compose, you don’t need to worry about layout weights and attributes like android:layout_width and android:layout_height as they're automatically handled by the framework. Also, the Text composable takes care of styling and text appearance, so there's no need to use TextAppearance resources like in XML.

This is just a simple example, but it demonstrates how you can use the basic Jetpack Compose composables to build complex UIs.

Here’s another example of how you could convert a ConstraintLayout to Jetpack Compose:

<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">

<TextView
android:id="@+id/text_view_1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Text View 1"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"/>

<TextView
android:id="@+id/text_view_2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Text View 2"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"/>

<TextView
android:id="@+id/text_view_3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Text View 3"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/>

<TextView
android:id="@+id/text_view_4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Text View 4"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/>

</androidx.constraintlayout.widget.ConstraintLayout>

And here’s the equivalent code in Jetpack Compose:

@Composable
fun MyConstraintLayout() {
ConstraintLayout(modifier = Modifier.fillMaxSize()) {
val (textView1, textView2, textView3, textView4) = createRefs()

Text("Text View 1", style = TextStyle(fontSize = 14.sp),
modifier = Modifier
.constrainAs(textView1) {
start.linkTo(parent.start)
top.linkTo(parent.top)
}
)

Text("Text View 2", style = TextStyle(fontSize = 14.sp),
modifier = Modifier
.constrainAs(textView2) {
end.linkTo(parent.end)
top.linkTo(parent.top)
}
)

Text("Text View 3", style = TextStyle(fontSize = 14.sp),
modifier = Modifier
.constrainAs(textView3) {
start.linkTo(parent.start)
bottom.linkTo(parent.bottom)
}
)

Text("Text View 4", style = TextStyle(fontSize = 14.sp),
modifier = Modifier
.constrainAs(textView4) {
end.linkTo(parent.end)
bottom.linkTo(parent.bottom)
}
)
}
}

The code is creating a Jetpack Compose layout using the ConstraintLayout composable. The Modifier.fillMaxSize() is applied to the ConstraintLayout to make it fill the entire screen.

Then, the code uses the createRefs() function to create references to four different Text composables. These references are used to apply constraints to the Text composables so they can be positioned relative to each other and the parent ConstraintLayout.

For each Text composable, the modifier property is used to apply constraints using the constrainAs function. The constraints are defined using a lambda expression passed to the constrainAs function. In this example, the constraints are linking each Text composable to either the start or end of the parent ConstraintLayout or to the top or bottom of the parent ConstraintLayout.

This concludes this part of the series. In the following parts, we will demonstrate how to migrate other complex views, such as RecyclerView, ImageView, RadioButtons, etc.

Until then, happy reading!

--

--

Jyoti Sheoran
Jyoti Sheoran

No responses yet