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:
1 2 3 4 5 6 7 |
<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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
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
1 2 3 4 5 6 7 |
<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.
1 2 3 4 5 6 7 8 9 10 11 12 |
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
1 2 3 4 5 6 7 |
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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
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.