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
text.You can unlock the rest of this book, and our entire catalogue of books and videos, with a raywenderlich.com Professional subscription.
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.
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.
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:
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.
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.*
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
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! :]
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) {
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)
)
}
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(
factory: (Context) -> T,
modifier: Modifier = Modifier,
update: (T) -> Unit = NoOpUpdate
): Unit
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,
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))
}
}
}
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.foundation.lazy.itemsIndexed
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 TrendingTopicsPreview() {
TrendingTopics(trendingTopics = trendingItems)
}
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 HomeScreenItem
s.
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))
}
})
}
)
Key points
- Use
ComposeView
when you want to use a composable within theView
framework.ComposeView
is aView
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 AndroidView
. -
AndroidView()
composes an AndroidView
obtained fromfactory()
.factory()
will be called exactly once to obtain theView
to compose. It’s also guaranteed to be invoked on the UI thread. - The
update()
block of theAndroidView
can be run multiple times (on the UI thread) due to recomposition. It’s the right place to setView
properties that depend on state.
Where to go from here?
Congratulations, you just completed the last chapter of this book!