Very often it happens that an Android application needs to be refactored on Compose. But the project is very big and it is impossible to do it in one go. In this case, Compose has classes for almost seamless implementation of Compose into XML and vice versa.
The full documentation is available here. In this article I won't dwell on the theoretical part, I want to share my practice and tips about refactoring.
Let's imagine that we have a project with LegacyActivity. The LegacyActivity has 4 nested rectangles (FrameLayout) in red, purple, green, and orange. This view is implemented using XML. The full code is here:
At some point, we are faced with the task of refactoring this view on Compose. My example above is trivial enough. But let's imagine that our visual elements are so huge that we can't refactor all the visual representations in a reasonable amount of time.
At the same time, we can't completely abandon refactoring either. We decide to refactor only part of this view in the first iteration. We can go two ways:
Google documentation says that there is not much difference in these approaches. For my part, I want to note that most often your project will tell you which way to go. In complex projects with thousands of files, very often one of these approaches will simply not be applicable.
I also want to note that refactoring by itself does not degrade performance compared to the original XML approach with a careful approach (see the article).
We need the red and purple rectangles to stay on the XML, and the green and orange to be on Compose.
The full result of the refactoring is here:
In order to achieve this, it is necessary:
@Composable
private fun InternalPart() {
Box(
Modifier
.fillMaxSize()
.background(color = Color(0xFF4CAF50))
.padding(32.dp)
) {
Box(
Modifier
.fillMaxSize()
.background(color = Color(0xFFFF9800))
.padding(32.dp)
)
}
}
<androidx.compose.ui.platform.ComposeView
android:id="@+id/composeViewInternalPart"
android:layout_width="match_parent"
android:layout_height="match_parent" />
In LegacyActivity, find the necessary ComposeView. Call the setContent{} method and pass InternalPart() to it.
findViewById<ComposeView>(R.id.composeViewInternalPart).setContent {
InternalPart()
}
As you can see, the very fact of connecting compose to the XML is not a labor-intensive operation. Much more effort is required to refactor the existing view to compose.
Let's try to solve the inverse problem: that the red and purple rectangles become on Compose, while the green and orange remain on XML.
The full code is here:
In order to achieve our goal, we need:
In the XML, remove the outer part so that only the green and orange rectangles remain. In my case these are:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#4CAF50"
android:padding="32dp">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#FF9800"
android:padding="32dp" />
</FrameLayout>
Refactor the outer part of Compose. In my case these are the red and purple rectangles:
@Composable
private fun ExternalPart() {
Box(
Modifier
.fillMaxSize()
.background(color = Color(0xFFF44336))
.padding(32.dp)
) {
Box(
Modifier
.fillMaxSize()
.background(color = Color(0xFF9C27B0))
.padding(32.dp)
) {
//...
}
}
}
AndroidView(
modifier = Modifier.fillMaxSize(),
factory = { context ->
View.inflate(context, R.layout.internal_part, null)
}
)
As you can see, in this case, XML and compose also work almost seamlessly.
After all the manipulation, I would recommend doing a design review - take the original and the resulting screenshots and compare the results, overlaying them on top of each other. There are many tools that allow you to do this. I mostly use Figma for such tasks.