Chapters

Hide chapters

Jetpack Compose by Tutorials

First Edition · Android 11 · Kotlin 1.4 · Android Studio Canary

Before You Begin

Section 0: 3 chapters
Show chapters Hide chapters

13. Adding View Compatibility
Written by Denis Buketa

Congratulations on reaching the last chapter of this book!

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.

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 image above. 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.

Trending View
Trending View

You’ll also build a Trending Today component that will be the first item on the Home screen’s list. 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. :]

In this section, 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:

@ExperimentalMaterialApi
@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
    )
  }
}

@ExperimentalMaterialApi
@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. At the time of writing, Button() was an experimental API so you added ExperimentalMaterialApi.

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.ExperimentalMaterialApi
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:

@ExperimentalMaterialApi
override fun onCreate(savedInstanceState: Bundle?) {
  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) {
  val context = AmbientContext.current
  val trendingView = remember(trendingTopic) {
    TrendingTopicView(context)
  }

  AndroidView(viewBlock = { trendingView }) {
    it.text = trendingTopic.text
    it.image = trendingTopic.imageRes
  }
}

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

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

Next, you created TrendingTopicView(). You used AmbientContext.current to access the Context and used that information to create the view.

Note that you wrapped it with remember(). When you have variables in composables, it’s a good practice in Compose to remember them if they’re expensive to create. That’s because composables can recompose at any time given a system signal.

Notice that you also passed trendingTopic to remember(). If the topic changes over time, it will produce and remember a new TrendingTopicView.

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

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

Finally, the star of the show is AndroidView().

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

AndroidView() composes an Android View obtained from viewBlock(). You’ll call viewBlock 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 viewBlock, 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 updated the properties that might change depending on the state.

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
        )
        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.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 TrendingItemsPreview() {
  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 viewBlock(). viewBlock() 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 last chapter of this book!

On this journey, you’ve learned many new concepts about Jetpack Compose. You can now implement a new app from scratch using Compose and migrate an existing app to this awesome framework.

Don’t be afraid to dig more deeply into the subject. There’s a lot to discover about Jetpack Compose. Check out the Jetpack Compose course if you want to get a second example of building complex app using Compose, from the ground up.

Additionally, check out the Jetpack Compose Animations Tutorial: Getting Started article, that dives even deeper into animations and shows you how to build cool custom components!

Wishing you all the best in your continued Jetpack Compose adventures! :]

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.