Interoperability with Jetpack Compose
Learn how to use Compose Interoperability in your Android app. By Eric Duenes.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Interoperability with Jetpack Compose
15 mins
You’re close to completing your android application, and it’s all written using Android Views. You want to start using JetPack Compose, but don’t want to rewrite all your layout.xml files. Or, maybe you started a project using Jetpack Compose, but want to leverage existing Android Views. What can you do?
You’re in luck — Jetpack Compose offers Interoperability. You can take advantage of this modern toolkit one composable at a time and migrate in and out of your Android Views at your own pace.
In this tutorial, you’ll explore Compose interoperability. You’ll use composables to complete a scorekeeper app that was created using Android Views. You’ll change the playing field and add Android Views to an activity using Jetpack Compose. As you complete this tutorial, you’ll learn about:
- Jetpack Compose Interoperability APIs
- Composable Lifecycles
- Composition Strategy
- Recomposition
The match is about to start — may the best team win.
Getting Started
Download the starter project by clicking the Download Materials button at the top or bottom of the tutorial.
Open the project with Android Studio. You’ll see the following file structure:
The AndroidManifest.xml declares .MainActivity
as the activity to launch after the app starts, and it also contains .ComposeActivity
— which currently does nothing. Using intent-filter
attributes, you’ll be able to launch the main activity for the first half of the tutorial and the Compose activity for the second half. Here’s an overview of the two activities:
- MainActivity.kt was developed using Android XML layout views and view binding. The activity inflates activity_main.xml layout view, and it provides the ability to keep score for the home team. These are the files you’ll be manipulating in the first half of the tutorial.
- ComposeActivity.kt was developed using Jetpack Compose. It will leverage components from VisitorTeamView.kt and provides the ability to keep score for the home team. You’ll be using these files in the second part of the tutorial.
Sync the project. Then, build and run. The app will look like this:
The starter project gives your user the ability to keep score for the home team — but sports matches are usually played by two teams. Using Jetpack Compose interoperability, you’ll add functionality to keep score for the visiting team.
Introducing Recomposition Strategies
Before you start keeping score, consider the lifecycle of a composable.
A composable can go through many changes during the lifecycle of your application. It’s drawn on your screen when initialized and re-drawn whenever the state changes. Additionally, when using Interoperability API, a composable can become detached and disposed of Android Views at different stages of the view lifecycle. As a result, it’s best to identify and set the correct ViewCompositionStrategy
.
By setting the correct view composition strategy, you’re ensuring two things:
- Your components don’t become disposed of while still on the screen. That would be a bad user experience.
- Your components do become disposed of at the right time. Retaining components unnecessarily will cause memory leaks in your application.
There are three view composition strategy options available:
-
DisposeOnDetachedFromWindow
: This is the default strategy, and the component will be disposed when the view is detached. This strategy can cause a memory leak if you callcreateComposition()
. In this scenario, the view will get created but might never be attached, so it won’t have the opportunity to be disposed. -
DisposeOnLifecycleDestroyed
: With this strategy, the component will be destroyed when the owner of the lifecycle is destroyed. -
DisposeOnViewTreeLifecycleDestroyed
: In this strategy, the component will be destroyed when the parent’s lifecycle is destroyed. This strategy is ideal when your component is in a Fragment.
Adding Composables
From the starter project, open activity_main.xml. You’ll add the Visitor Team section using ComposeView
.
After the divider view, add the following code:
<androidx.compose.ui.platform.ComposeView
android:id="@+id/compose_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="40dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@id/divider"
/>
You’ve just given yourself an entry point to add composables to your Android View.
Now, open MainActivity.kt and inside onCreate()
, use view binding to apply the composable like this:
//1
binding.composeView.apply {
//2
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnDetachedFromWindow)
//3
setContent {
//4
val visitorScore = remember { mutableStateOf(0) }
//5
VisitorScreenView(
visitorScore = visitorScore.value,
decreaseScore = { visitorScore.value-- },
increaseScore = { visitorScore.value++ })
}
}
Score one for the good guys… yes, that’s a sports reference. :]
You’ve now applied VisitorScreenView
composable to the Android View. Here’s what you did:
- Using view binding, you’re able to get a handle to
ComposeView
. Remember? You added that to your layout XML file. - Here, you’re setting the composition strategy to
DisposeOnDetachedFromWindow
. This strategy is safe to use here since your composables will be displayed in an activity, and you won’t be callingcreateComposition()
anywhere in your code. - This is the block where you’ll add your composables.
- You’ll keep track of the score with
visitorScore
. This will be stored as state and will be used byVisitorScreenView
. To learn more about preserving the UI state of an Android application, check out Managing State in Jetpack Compose. -
VisitorScreenView
is a composable function that has already been created for you. It contains the view for the visiting team and takes three parameters:-
visitorScore
variable accepts an integer value that will be used to display the score for the visiting team. -
decreaseScore
parameter accepts a lambda function. It will be used to decrease the visiting team score. -
increaseScore
parameter accepts a lambda function. It will be used to increase the visiting team’s score.
-
Build and run the application. You’ll see this screen, and you’ll be able to modify the visiting team’s score.
Abstracting Composables
You’ve now successfully added composables to your view, and you’re keeping score like a champ. But as the game progresses, you realize something… you can track the score, but with your Android Views and composables in the same file, how can you track where Android Views end and composables begin?
To keep things clean, you should separate your composables from your Android Views. You can use AbstractComposeView
to achieve this.
Open VisitorTeamView.kt and add the following code:
class VisitorTeamView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 ) : AbstractComposeView(context, attrs, defStyleAttr) { //1 private val visitorScore = mutableStateOf(0) //2 @Composable override fun Content() { setViewCompositionStrategy( ViewCompositionStrategy.DisposeOnDetachedFromWindow ) VisitorScreenView( visitorScore = visitorScore.value, decreaseScore = { visitorScore.value-- }, increaseScore = { visitorScore.value++ }) } }
VisitorTeamView
class implements AbstractComposeView
. The code should look familiar to you because it’s similar to what you implemented in the previous step.
- Your
visitorScore
variable will hold the score value as state. - Inside
Content()
is where you’ll set your composition strategy and call your composable functions.
VisitorTeamView
class directly from the layout file.
Open activity_main.xml
and replace androidx.compose.ui.platform.ComposeView
declaration with com.yourcompany.skeeper.ui.VisitorTeamView
:
<com.yourcompany.skeeper.ui.VisitorTeamView android:id="@+id/compose_view" ... />
Finally, to complete the cleanup, go back to MainActivity
and remove binding.composeView.apply {}
along with all the lines of code inside of it.
There you have it. All your composables are in the com.yourcompany.skeeper.ui
package.
Build and run your application to see that it looks and behaves the same way.
Switching to Composable UI
At the halftime of a game, the teams usually switch sides to keep a fair playing field. Now, you’ll change the playing field as well — and this time, you’ll integrate Android Views into your composables. Within the same Skeeper app, you’ll find ComposeActivity.kt, and it’s written purely in Compose. This UI only has the Home View completed. You’ll complete the visitors section using Android Views.
Set up the Skeeper app to start with ComposeActvity
. To do this, open AndroidManifest.xml and uncomment intent-filter
of .ComposeActivity
. Next, you’ll comment out intent-filter
of .MainActivity
. The next time you run your application, it will display ComposeActivity
.
Your Manifest file will look like this:
... <!-- Main Activity --> <activity android:name="com.yourcompany.skeeper.MainActivity" android:exported="true" android:theme="@style/SplashTheme"> //1 <!-- <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> --> </activity> <!-- Compose Activity --> <activity android:name="com.yourcompany.skeeper.ComposeActivity" android:exported="true" android:theme="@style/AppTheme.NoActionBar" > //2 <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity>
- By commenting out
intent-filter
, the main activity will no longer be launched when the application starts. - By uncommenting
intent-filter
, the Compose activity will be launched when the application starts.
Build and run your application to see that ComposeActivity
is launched when the application is started.
Your application will look like this, and you’ll only be able to change the home team’s score.
Composing Android Views
Two APIs give you the ability to add Android Views into Compose Views:
- AndroidView
- AndroidViewBinding
You’ll implement AndroidViewBinding — but before you do that, here’s an overview of AndroidView.
Reviewing AndroidView
In the same way that you would add a Button
or Text
composable to your user interface, you can add AndroidView
composable. This is a special type of composable that has three things: a modifier, factory and update. Here’s an example:
AndroidView( modifier = Modifier.fillMaxSize(), //1 factory = { context -> MyView(context).apply { // Set listeners here } }, //2 update = { view -> //Recomposed when state changes } )
- The factory block will be executed only once and will run on the UI Thread. Use the factory to perform initializations such as
onClickListener
. - The update block is the set of code that will be recomposed when state changes. Add your UI components there.
If you have an XML layout file containing your Android View, it’s much easier to inflate it within a composable.
Implementing AndroidViewBinding
This API allows you to use view binding and simply inflate an existing XML layout file.
Wouldn’t you know it, the Skeeper app conveniently has visitor_team_xml_view.xml. This layout file uses Android Views and contains the visitors view. Take a glance at it and see that it has a couple of TextViews and Buttons.
Now, open ComposeActivity.kt and add a composable function called VisitorTeamXmlView
.
@Composable fun VisitorTeamXmlView(visitorScore: Int, decreaseScore: () -> Unit, increaseScore: () -> Unit){ //1 AndroidViewBinding(VisitorTeamXmlViewBinding::inflate){ //2 visitorScoreText.text = visitorScore.toString() decrementVisitorButton.setOnClickListener { decreaseScore() } incrementVisitorButton.setOnClickListener { increaseScore() } } }
-
VisitorTeamXmlView
will use AndroidViewBinding API to inflate your layout file. - You can set
clickListeners
and any other attributes you need insideAndroidViewBinding
declaration.
The last thing left to do is call VisitorTeamXmlView
composable function to display your layout.
In ComposeActivity, find setContent()
where HomeTeamComposeView
and Divider
composables are being called.
Call the VisitorTeamXmlView
function:
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { ... VisitorTeamXmlView(visitorScore = visitorScore.value, decreaseScore = { visitorScore.value-- }, increaseScore = { visitorScore.value++ }) } }
Build and run the application to see visitor_team_xml_view.xml get inflated and rendered alongside your composables.
Score one more for the good guys!
Where to Go From Here?
Download the completed project files by clicking the Download Materials button at the top or bottom of the tutorial.
In this tutorial, you learned how to:
- Identify a recomposition strategy
- Add composables to Android Views
- Add Android Views to composables
Interoperability with Jetpack Compose gives you a lot of flexibility with your user interface. If you want to learn more about Jetpack Compose, check out Jetpack Compose Tutorial for Android: Getting Started or Jetpack Compose by Tutorials.
We hope you enjoyed this tutorial. If you have any questions or comments, please join the forum discussion below!