Chapters

Hide chapters

Jetpack Compose by Tutorials

Second Edition · Android 13 · Kotlin 1.7 · Android Studio Dolphin

Section VI: Appendices

Section 6: 1 chapter
Show chapters Hide chapters

13. Adding View Compatibility
Written by Denis Buketa

Congratulations on reaching the last chapter of this section!

So far, you’ve learned a lot about Jetpack Compose. In the book’s first section, you learned about basic composables. In the second, you saw how to use Compose when building a real app. In the third section, you learned how to build a more complex UI and how to make simple but beautiful animations.

In this chapter, you’ll finish your journey by learning the basic principles of combining Jetpack Compose and the old View framework, which can coexist in the same codebase. That knowledge will make it easier for you to gradually migrate your apps to Jetpack Compose.

Introducing the Chat Screen and the Trending View

To follow along with the code examples, open this chapter’s starter project in Android Studio and select Open an existing project.

Then, navigate to 13-adding-view-compatibility/projects and select the starter folder as the project root. Once the project opens, let it build and sync and you’re ready to go! You can see the completed project by skipping ahead to the final project.

Also make sure to clean the app’s storage, before running the project.

For this chapter, we’ve added a few things to the starter project.

Home Screen Chat Button
Home Screen Chat Button

These additions include a new Chat screen, which uses the old View framework. Access it by clicking the new Chat icon in the top bar of the Home screen, as you can see in the previous image.

If you tap that button, you’ll open the following screen:

Chat Screen
Chat Screen

In this chapter, you’ll replace the Start Chatting button with a button made up of composable functions. To see the final implementation, check out screens/ChatActivity.kt and res/layout/activity_chat.xml. You’ll also build a Trending Today component that will be the first item on the Home screen’s list.

Trending View
Trending View

You’ll build the entire component using composables, except for one piece of functionality: Trending topic. This will use the View framework in views/TrendingTopicView.kt and res/layout/view_trending_topic.xml.

Next, you’ll see how Jetpack Compose and the View framework work together.

Using Composables With the View Framework

Learning how to use composables with the old View framework will make it easier to migrate existing screens to Jetpack Compose. You’ll start with small components and gradually migrate the whole screen.

Furthermore, some components are easier to make using Jetpack Compose. There’s no reason not to use those components when the framework allows it. :]

You’ll start by implementing the Start Chatting button using Jetpack Compose.

Implementing the Start Chatting Button

Open ChatActivity.kt and add the following code below ChatActivity:

@Composable
private fun ComposeButton(onButtonClick: () -> Unit) {
  val buttonColors = buttonColors(
    backgroundColor = Color(0xFF006837),
    contentColor = Color.White
  )

  Button(
    onClick = onButtonClick,
    elevation = null,
    shape = RoundedCornerShape(corner = CornerSize(24.dp)),
    contentPadding = PaddingValues(
      start = 32.dp,
      end = 32.dp
    ),
    colors = buttonColors,
    modifier = Modifier.height(48.dp)
  ) {
    Text(
      text = "Start chatting".toUpperCase(Locale.US),
      fontSize = 16.sp,
      fontWeight = FontWeight.Medium
    )
  }
}

@Preview
@Composable
private fun ComposeButtonPreview() {
  ComposeButton { }
}

To break down the code, you added a root composable, ComposeButton(), for the button. You then exposed onButtonClick so it can react to clicks.

To emit the button UI, you used Button() from the material composables. You specified the background and content color with buttonColors() and passed in the backgroundColor and the contentColor. You also set the shape and styled the text to match the current implementation.

You also added @Preview to visualize how your button looks in Android Studio.

For this to work, you have to add the following imports as well:

import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.shape.CornerSize
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Button
import androidx.compose.material.ButtonDefaults.buttonColors
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import java.util.*

Build the project and check the preview panel. You’ll see something like this:

ComposeButton Preview
ComposeButton Preview

Adding ComposeButton to ChatActivity

Next, you have to replace the old implementation with the composable button. Open activity_chat.xml in the layout resource folder and replace the old AppCompatButton with the following:

<androidx.compose.ui.platform.ComposeView
    android:id="@+id/composeButton"
    android:layout_width="wrap_content"
    android:layout_height="48dp"
    android:layout_marginTop="16dp"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toBottomOf="@+id/subtitle" />

You also have to update ChatActivity. In ChatActivity.kt, replace onCreate() with the following code:

override fun onCreate(savedInstanceState: Bundle?) {
  // Switch to AppTheme for displaying the activity
  setTheme(R.style.Theme_JetReddit)  

  super.onCreate(savedInstanceState)
  binding = ActivityChatBinding.inflate(layoutInflater)
  val view = binding.root
  setContentView(view)

  binding.backButton.setOnClickListener {
    finish()
  }

  binding.composeButton.setContent {
    MaterialTheme {
      ComposeButton { showToast() }
    }
  }
}

Don’t forget to add the following import for MaterialTheme:

import androidx.compose.material.MaterialTheme

The old button’s implementation used AppCompatButton in activity_chat.xml. Here, you replaced that with ComposeView, which is a View that can host Jetpack Compose UI content. Compose needs a host Activity or Fragment to render UI.

setContent() supplies the content composable function for the view in ChatActivity’s onCreate().

ComposeView requires that the window it’s attached to contains a ViewTreeLifecycleOwner. This LifecycleOwner disposes the underlying composition when the host lifecycle is destroyed. That allows you to attach and detach the view repeatedly while preserving the composition.

Build and run the app. Open the Chat screen and check out your new button. The button looks and acts the same as the old one did.

ComposeButton in Chat Screen
ComposeButton in Chat Screen

Great job! That was easy, wasn’t it? With ComposeView, you can gradually migrate any screen that uses the old View framework. Once you migrate the layout, you can even remove the hosting Activity or Fragment and implement the whole screen using just Jetpack Compose.

But the great thing is that you can mix and match the two frameworks however you like it.

Using View With Jetpack Compose

Now, reverse the situation. Imagine that you decided to implement a screen or a component using Jetpack Compose, but for some reason — time restrictions, framework support, etc. — it would be easier to reuse a custom View you already implemented in that new screen. Well, Jetpack Compose allows you to do that! :]

In this section, you’ll implement the component for Trending Topics.

Trending Topics
Trending Topics

For this component, TrendingTopicView has been prepared for you. It represents one item in the scrollable list of topics. To see the implementation, check TrendingTopicView.kt and view_trending_topic.xml.

Before you implement that component, you need to make a few modifications to HomeScreen.kt.

Preparing the Home Screen

Before you can add Trending Topics as part of the scrollable list in the Home screen, you need to prepare the code to support different types of items in the list.

Open HomeScreen.kt and add the following code at the bottom:

private data class HomeScreenItem(
  val type: HomeScreenItemType,
  val post: PostModel? = null
)

private enum class HomeScreenItemType {
  TRENDING,
  POST
}

private data class TrendingTopicModel(
  val text: String,
  @DrawableRes val imageRes: Int = 0
)

You added HomeScreenItem, which represents one item in the list, then defined its type with HomeScreenItemType. If the item’s type is POST, the post parameter will contain data for the post. Otherwise, it will be null.

You also added TrendingTopicModel, which contains the data for one topic item that will be visible in the Trending Topics component.

To finish, add one additional import:

import androidx.annotation.DrawableRes

Adding TrendingTopic

Next, you’ll create a composable to represent one topic item. Add the following code below HomeScreen():

@Composable
private fun TrendingTopic(trendingTopic: TrendingTopicModel) {
  AndroidView({ context ->
    TrendingTopicView(context).apply {
      text = trendingTopic.text
      image = trendingTopic.imageRes
    }
  })
}

@Preview
@Composable
private fun TrendingTopicPreview() {
  TrendingTopic(trendingTopic = TrendingTopicModel(
    "Compose Animations",
    R.drawable.jetpack_compose_animations)
  )
}

To make Android Studio happy, add the following imports as well:

import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.viewinterop.AndroidView
import com.yourcompany.android.jetreddit.R
import com.yourcompany.android.jetreddit.views.TrendingTopicView

Here, you added a root composable called TrendingTopic for one topic item. It takes TrendingTopicModel as an argument.

The star of the show is AndroidView().

@Composable fun <T : View> AndroidView(
    factory: (Context) -> T,
    modifier: Modifier = Modifier,
    update: (T) -> Unit = NoOpUpdate
): Unit

AndroidView() composes an Android View obtained from factory(). The factory block will be called exactly once to obtain the View you need to compose. It’s also guaranteed to be invoked on the UI thread. Therefore, in addition to creating the factory, the block can also perform one-off initializations and set View’s properties.

The app might run update() multiple times on the UI thread, as well, due to recomposition. It’s the right place to set View properties that depend on the state. When the state changes, the block will re-execute to set the new properties. The block will also run once, right after viewBlock() completes.

In your code, you passed trendingView as viewBlock(). In update(), you left it as a NoOpUpdate, which is an empty lambda function, that doesn’t update the IO.

You also added the preview composable so you can preview TrendingTopic() in the preview panel.

Build the project and check the preview panel and you’ll see:

TrendingTopic Preview
TrendingTopic Preview

Building a List of Trending Topics

Now that you have a composable that represents one trending topic, you’ll work on a composable to represent the whole component with multiple trending topics.

Add the following code below HomeScreen():

@Composable
private fun TrendingTopics(
  trendingTopics: List<TrendingTopicModel>,
  modifier: Modifier = Modifier
) {
  Card(
    shape = MaterialTheme.shapes.large,
    modifier = modifier
  ) {
    Column(modifier = Modifier.padding(vertical = 8.dp)) {
      // "Trending Today" heading
      Row(
        modifier = Modifier.padding(horizontal = 16.dp),
        verticalAlignment = Alignment.CenterVertically
      ) {
        Icon(
          modifier = Modifier.size(18.dp),
          imageVector = Icons.Filled.Star,
          tint = Color.Blue,
          contentDescription = "Star Icon"
        )
        Spacer(modifier = Modifier.width(4.dp))
        Text(
          text = "Trending Today",
          fontWeight = FontWeight.Bold,
          color = Color.Black
        )
      }

      Spacer(modifier = Modifier.height(8.dp))
    }
  }
}

This is a larger piece of code, but the structure of the components is very simple. You add a card that will hold the entire trending topic section. You add Column() as the root of Card(), as you’ll have two elements ordered vertically. The first is Row() that holds the title and the star icon. Then second will be all the trending topic items.

Now add the last piece of code right after the last Spacer(), that represents the trending topic items:

LazyRow(
  contentPadding = PaddingValues(
    start = 16.dp,
    top = 8.dp,
    end = 16.dp
  ),
  content = {
    itemsIndexed(
      items = trendingTopics,
      itemContent = { index, trendingModel ->
        TrendingTopic(trendingModel)
        if (index != trendingTopics.lastIndex) {
          Spacer(modifier = Modifier.width(8.dp))
        }
      }
    )
  }
)

This code is pretty straightforward. For the trending topics content, you added LazyRow(). Within it, you built TrendingTopic() for each item in the list. You also added some padding to each item using contentPadding.

For this to work, you also have to add the following imports:

import androidx.compose.material.Card
import androidx.compose.material.Icon
import androidx.compose.material.Text
import androidx.compose.material.icons.Icons
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.material.icons.filled.Star
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight

Before adding the preview composable, first add the dummy data you’ll use as an argument. Add the following above HomeScreen():

private val trendingItems = listOf(
  TrendingTopicModel(
    "Compose Tutorial",
    R.drawable.jetpack_composer
  ),
  TrendingTopicModel(
    "Compose Animations",
    R.drawable.jetpack_compose_animations
  ),
  TrendingTopicModel(
    "Compose Migration",
    R.drawable.compose_migration_crop
  ),
  TrendingTopicModel(
    "DataStore Tutorial",
    R.drawable.data_storage
  ),
  TrendingTopicModel(
    "Android Animations",
    R.drawable.android_animations
  ),
  TrendingTopicModel(
    "Deep Links in Android",
    R.drawable.deeplinking
  )
)

This is just dummy data that represents fake trending topics. The images you used here were already prepared for you.

Now that you have the dummy data, add the preview composable above TrendingTopicPreview():

@Preview
@Composable
private fun TrendingTopicsPreview() {
  TrendingTopics(trendingTopics = trendingItems)
}

Build the project and check the preview panel. You’ll see this:

TrendingTopics Preview
TrendingTopics Preview

Adding TrendingTopics to the Home Screen

TrendingTopics() is now ready to use in the Home screen. Before integrating it into HomeScreen(), however, you have to add logic to map the trending items to HomeScreenItems.

In HomeScreen.kt, add the following code below HomeScreen():

private fun mapHomeScreenItems(
    posts: List<PostModel>
): List<HomeScreenItem> {
  val homeScreenItems = mutableListOf<HomeScreenItem>()

  // Add Trending item
  homeScreenItems.add(
      HomeScreenItem(HomeScreenItemType.TRENDING)
  )

  // Add Post items
  posts.forEach { post ->
    homeScreenItems.add(
        HomeScreenItem(HomeScreenItemType.POST, post)
    )
  }

  return homeScreenItems
}

This function takes a list of PostModels and returns a list of HomeScreenItems, where the first item is of type HomeScreenItemType.TRENDING.

Now, add the code to invoke this method just above the Box() that defines HomeScreen()’s content:

fun HomeScreen(viewModel: MainViewModel) {
    ...

    // Add this line
    val homeScreenItems = mapHomeScreenItems(posts)

    Box(modifier = Modifier.fillMaxSize()) {
    	LazyColumn(...)
        ...
    }
}

With this, you mapped the list of PostModels to a list of HomeScreenItems.

Finally, update the LazyColumn() in HomeScreen(), like this:

LazyColumn(
  modifier = Modifier
    .background(color = MaterialTheme.colors.secondary),
  content = {
    items(
      items = homeScreenItems,
      itemContent = { item ->
        if (item.type == HomeScreenItemType.TRENDING) {
          TrendingTopics(
            trendingTopics = trendingItems,
            modifier = Modifier.padding(
              top = 16.dp,
              bottom = 6.dp
            )
          )
        } else if (item.post != null) {
          val post = item.post
          if (post.type == PostType.TEXT) {
            TextPost(
              post = post,
              onJoinButtonClick = onJoinClickAction
            )
          } else {
            ImagePost(
              post = post,
              onJoinButtonClick = onJoinClickAction
            )
          }
          Spacer(modifier = Modifier.height(6.dp))
        }
      })
  }
)

Here, you added the logic that emits either TrendingTopics(), TextPost() or ImagePost(), depending on the item.type and item.post content.

Good job! :]

Build and run the app and check out your fancy trending topics component at the top of the Home screen.

Trending Topics on the Home Screen
Trending Topics on the Home Screen

Excellent work! You just learned the basic principles of combining Jetpack Compose and the old View framework. This will allow you to migrate any app to Jetpack Compose with no trouble! :]

Key Points

  • Use ComposeView when you want to use a composable within the View framework. ComposeView is a View that can host Jetpack Compose UI content.
  • Use setContent() to supply the content composable function for the view.
  • AndroidView() lets you create a composable from the Android View.
  • AndroidView() composes an Android View obtained from factory(). factory() will be called exactly once to obtain the View to compose. It’s also guaranteed to be invoked on the UI thread.
  • The update() block of the AndroidView can be run multiple times (on the UI thread) due to recomposition. It’s the right place to set View properties that depend on state.

Where to Go From Here?

Congratulations, you just completed the Adding View Compatibility chapter!

You are definitely ready to adopt Jetpack Compose even if you already have an app built with the old View framework. Just start bit by bit and in no time you’ll only write Compose! :]

In the next section, you will learn about how to write UI tests for your Compose code and how to handle accessibility. See you there!

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2026 Kodeco Inc.