Converting XML UI to Jetpack Compose
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!
Other stories related to Jetpack compose
https://medium.com/@jyotic26/jetpack-compose-migration-part1-dacf95225cc3