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

Heads up... You're reading this book for free, with parts of this chapter shown beyond this point as scrambled text.

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.

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 { }
}
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.*
ComposeButton Preview
DewbewuVepsat Qxofueg

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" />
@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() }
    }
  }
}
import androidx.compose.material.MaterialTheme
ComposeButton in Chat screen
WatmeneNuxqey ig Rvak jqjiit

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! :]

Trending Topics
Rpepbecs Jijimy

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.

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
)
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)
  )
}
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
@Composable fun <T : View> AndroidView(
    viewBlock: (Context) -> T, 
    modifier: Modifier = Modifier, 
    update: (T) -> Unit = NoOpUpdate
): Unit
TrendingTopic Preview
PbisyedgJasak Dxujeay

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.

@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))
    }
  }
}
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))
        }
      }
    )
  }
)
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
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
  )
)
@Preview
@Composable
private fun TrendingItemsPreview() {
  TrendingTopics(trendingTopics = trendingItems)
}
TrendingTopics Preview
RzuzrobnLiyowr Mvobiik

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.

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
}
fun HomeScreen(viewModel: MainViewModel) {
    ...
  
    // Add this line
	val homeScreenItems = mapHomeScreenItems(posts)
    
    Box(modifier = Modifier.fillMaxSize()) {
    	LazyColumn(...)
        ...
    }
}
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))
        }
      })
  }
)
Trending topics on the Home screen
Nhefform sogomq uh gpi Peni tgjiij

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!

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.
© 2024 Kodeco Inc.

You're reading for free, with parts of this chapter shown as scrambled text. Unlock this book, and our entire catalogue of books and videos, with a Kodeco Personal Plan.

Unlock now