Deep Dive into Integrating Jetpack Compose into an Existing Codebase

Ankit Angra
4 min readJun 11, 2024

Welcome to our deep dive into integrating Jetpack Compose into an existing codebase! As the Android development landscape evolves, Jetpack Compose is emerging as a game-changer, offering a modern, declarative UI toolkit designed to simplify and accelerate UI development. However, adopting it in projects with substantial legacy code presents unique challenges and opportunities.

In this blog post, we will explore the nuts and bolts of seamlessly integrating Jetpack Compose into your current Android projects. We’ll start by understanding the various dependencies required to introduce Jetpack Compose into your existing codebase. From there, we’ll walk through various integration points, identifying opportunities to bridge the gap between the traditional UI components and the new Compose system.

Source code for the project can be found at: https://github.com/angra007/Jetpack-Compose-Integration

Adding Jetpack Compose

Step 1: Configuring Build Scripts for Android Projects with Gradle and Kotlin

This code is part of a build configuration for an Android project using Gradle. It specifies the repositories and dependencies necessary for the project’s build system. The repositories block includes Google’s Maven repository and Maven Central, ensuring access to a wide range of Android and Java libraries including Jetpack Compose.

buildscript {
repositories {
google()
mavenCentral()
}
dependencies {
classpath("com.android.tools.build:gradle:7.0.4")
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.24")
}
}

Step 2: Setting Up a Kotlin Android Project with Jetpack Compose

Steps 2 is to provide a configuration setup for an Android project using Kotlin and Jetpack Compose. It begins by applying necessary plugins for Android application development and Kotlin support. The android block configures specific Kotlin options and enables Jetpack Compose features, including setting the JVM target and activating Compose build features. Additionally, specify the version of Kotlin compiler extensions used by Compose. In the dependencies section, it includes various Compose libraries essential for UI, runtime, foundation, animations, and material design components, ensuring that the project is equipped with the latest Compose capabilities for building modern, reactive UIs in Android applications.

plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
kotlin("kapt")
}


android {
.......

kotlinOptions {
jvmTarget = "1.8"
}

buildFeatures {
compose = true
}

composeOptions {
kotlinCompilerExtensionVersion = "1.5.14"
}
}

dependencies {
....

implementation ("androidx.compose.ui:ui:1.6.7")
implementation ("androidx.compose.runtime:runtime:1.6.7")
implementation ("androidx.compose.foundation:foundation:1.6.7")
implementation ("androidx.compose.animation:animation:1.6.7")
implementation ("androidx.compose.material:material:1.6.7")
implementation ("androidx.compose.material3:material3:1.2.1")
implementation("androidx.activity:activity-compose:1.9.0")
}

Step 3: Finally we can replace add an integration point using following code

Once the sync is successful we are all set to start adding Jetpack Compose code in our project

class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
HelloWorld()
}
}
}


@Composable
fun HelloWorld() {
Text("Hello World!!")
}

Integration Points

Integration Point refers to a specific location within an existing Android project where Jetpack Compose can be introduced or connected with the traditional Android UI toolkit.

1. ComponentActivity.setContent { }

This is the simplest form of integrating Jetpack Compose into an existing codebase. Under the hood, this creates a ComposeView at the root and adds it to the view tree, allowing us to bridge both worlds together.

class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
HelloWorld()
}
}
}


@Composable
fun HelloWorld() {
Text("Hello World!!")
}

2. ComposeView()

If we want to use ComposeView as the root view of a fragment, instead of inflating a fragment view, we can directly return a ComposeView. The setViewCompositionStrategy() method determines when the data will be removed from memory. setViewCompositionStrategy() can take one of four possible values:

  • DisposeOnDetachedFromWindowOrReleasedFromPool
  • DisposeOnDetachedFromWindow
  • DisposeOnLifecycleDestroyed
  • DisposeOnViewTreeLifecycleDestroyed
class NewFragment : Fragment() {


override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}

override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return ComposeView(requireContext()).apply {

setViewCompositionStrategy(
ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed
)

setContent {
HelloFromFragment()
}
}
}

companion object {
@JvmStatic
fun newInstance() = NewFragment()
}
}

@Composable
fun HelloFromFragment() {
Text(text = "Hello From Fragment")
}

3. <androidx.compose.ui.platform.ComposeView> inside a layout

We can include <androidx.compose.ui.platform.ComposeView> within the layout file and apply setContent to that instance. This approach is highly flexible, enabling us to migrate to Jetpack Compose incrementally. One of the best use cases for this is within a RecyclerView. Many popular apps heavily utilize RecyclerView, and to gradually transition to Jetpack Compose, we can incorporate ComposeView into the layout file’s ViewHolder of the RecyclerView.

<androidx.compose.ui.platform.ComposeView
android:id="@+id/compose_view"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
val composeView = view?.findViewById<ComposeView>(R.id.compose_view)
composeView?.apply {
setContent {
HelloFromFragment()
}
}

4. Using AbstractComposeView

AbstractComposeView is a key component in Jetpack Compose, designed to facilitate the integration of composables into the traditional Android View system. It allows developers to seamlessly embed Compose UI elements within existing XML layouts or add them programmatically, making it ideal for gradually transitioning to Jetpack Compose.

class MyComposeView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0,
) : AbstractComposeView(context, attrs, defStyleAttr) {

@Composable
override fun Content() {
HelloFromCustomView()
}
}

@Composable
fun HelloFromCustomView() {
Text(text = "Hello From Custom View")
}
<com.codefylabs.www.jetpackcompose.MyComposeView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />

In conclusion, integrating Jetpack Compose into an existing Android codebase presents a valuable opportunity to modernize and enhance your application’s UI with the latest declarative UI framework. We’ve explored various strategies, from using ComposeView for seamless embedding to leveraging AbstractComposeView for more complex integrations. The flexibility to incrementally introduce Compose ensures that developers can make this transition at their own pace, without disrupting existing functionalities. By taking advantage of Compose’s powerful features and ensuring smooth interoperability with existing Views, developers can significantly improve both development speed and application performance. As we continue to embrace Jetpack Compose, it’s clear that the future of Android UI development is heading towards more intuitive and efficient design practices. Whether you are looking to overhaul your UI completely or simply inject modern touches to specific components, Jetpack Compose offers the tools and scalability to meet these needs effectively.

--

--