Adopting Jetpack Compose with Interop API
Are you learning Jetpack compose but worried about migrating your current code without breaking it? Here is interoperability APIs to make your life easier
Register Service
We have mainly these three APIs for interoperability
- ComposeView : It is basically a view which gets declared in your traditional xml layout file & with the help of its id you can reference it as any traditional android view & write jetpack compose code directly.
- AbstractComposeView : It provides you the capability to make your own custom views directly into compose & later on you can include them in your xml layout file.
- AndroidView : it provides you the capability to use any Views which are not yet available in compose like MapView, AdvView or even your customViews in compose & start using them right away.
Code time :
So far in theory we know about these interoperability APIs, now let’s see how they are used practically in code
ComposeVIew:
<androidx.compose.ui.platform.ComposeView android:id="@+id/compose_chips" android:layout_width="0dp" android:layout_height="wrap_content" app:layout_constraintBottom_toTopOf="@id/rv" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/compose_toolbar" />
As we can see here i have simply declared in my xml, Now let’s see how to write our compose code with it
lateinit var binding: ActivityMainBinding lateinit var categoryList: MutableState<List> @ExperimentalMaterialApi override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityMainBinding.inflate(layoutInflater) binding.composeChips.setContent { categoryList = rememberSaveable { mutableStateOf(emptyList()) } LazyRow( modifier = Modifier .fillMaxWidth() .padding(10.dp) ) { if (categoryList.value.isNotEmpty()) items(items = categoryList.value) { k -> ShowChips(category = k) } } } } @Composable private fun ShowChips(category: String) { Surface( modifier = Modifier .padding(end = 10.dp), onClick = { if (binding.composeToolbar.text.value != category) { binding.composeToolbar.text.value = category getNews() } }, shape = MaterialTheme.shapes.medium, color = Color.Cyan,border = BorderStroke( 1.dp, Color.Black ) ) { Text( text = category, color = Color.Black, style = MaterialTheme.typography.subtitle2, modifier = Modifier.padding(5.dp) ) } }
Now the key thing to note here is that the setContent block for our composeChips,In that block now we can write the compose code that we want. For instance I have called my Composable function which has code for creating a Chip design & its content is displayed in composeChips.
AbstractComposeView
class ComposedToolbar @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 ) : AbstractComposeView(context, attrs, defStyleAttr) { lateinit var text: MutableState lateinit var onImageClick: () -> Unit @Composable override fun Content() { TopAppBar( modifier = Modifier .fillMaxWidth(), elevation = 7.dp ) { TextField( value = text.value, modifier = Modifier.fillMaxWidth(), onValueChange = { text.value = it }, label = { Text( text = "Search", style = TextStyle(color = MaterialTheme.colors.onPrimary) ) }, trailingIcon = { Image( imageVector = Icons.Default.Search, contentDescription = "News Image", modifier = Modifier.clickable(true, onClick = { onImageClick() }) ) }, textStyle = MaterialTheme.typography.subtitle1 ) } } }
Here I have my class extending AbstractComposeView & created a Toolbar which has a search bar design having one trailing icon with onClickListener, along with that my two variables for searching the text & my function which will execute on the click of the trailing icon.
Now we can include this class in our xml file
<com.example.interop.ComposedToolbar android:id="@+id/compose_toolbar" android:layout_width="0dp" android:layout_height="wrap_content" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" />
So far so good, now we can reference this in the kotlin file & set up our variables for final use.
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) binding.composeToolbar.apply { text = mutableStateOf("") onImageClick = { getNews() } } } Private fun getNews(){ /* Api call stuff /* }
With the reference from xml we can now set up our class variables
AndroidView
We are now going to use our CustomView with compose for that i have created Compose Activity & my CustomVIew which works like EmailValidator
constructor(context: Context?,label:String,hint:String,imgRight:Drawable,imgWrong:Drawable) : super(context) { this.hint =hint this.imgRight=imgRight this.imgWrong=imgWrong this.label=label init(context, null, 0) }
So this is my CustomView. I changed the attribute’s value to be an assignment from the constructor directly as we don’t have xml in Compose to pass our attributes.Now let’s add this to our Compose Activity
class AndroidViewActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { InteropTheme { // A surface container using the 'background' color from the theme Surface(color = MaterialTheme.colors.background) { GetEmailValidator() } } } } } @Preview @Composable fun GetEmailValidator(){ AndroidView(modifier = Modifier.fillMaxWidth(), factory = { context -> CustomEmailValidator( context, hint = "Enter email id", label = "Email Validator", imgRight = ContextCompat.getDrawable( context, R.drawable.ic_launcher_foreground )!!, imgWrong = ContextCompat.getDrawable( context, R.drawable.ic_launcher_background )!! ).apply { layoutParams = ViewGroup.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT ) } }) }
So here in my GetEmailValidator composable function I am using AndroidView & after passing parameters like modifiers, in the factory parameter block I am constructing my CustomEmailValidator & in the constructor I am passing necessary parameters.
Output:
Here is link to the repository if you want to explore the app
Conclusion
These interoperability APIs surely will come handy in your journey of Jetpack Compose & knowing these APIs will encourage you to start adapting compose in your current code base.
I hope you had a good time reading the article.