Compose ↔ View Interop — AndroidView and ComposeView
You don't need to rewrite your entire app to use Compose. Modern Android supports gradual migration through interop.
AndroidView: Embedding Views in Compose
Basic Pattern
AndroidView(
factory = { context ->
MapView(context).apply {
// Initialize
}
},
modifier = Modifier.fillMaxSize(),
update = { mapView ->
// Update when recomposed
}
)
Real Example: Google Maps
AndroidView(
factory = { context ->
MapView(context)
},
update = { mapView ->
mapView.getMapAsync { googleMap ->
googleMap.addMarker(
MarkerOptions().position(LatLng(37.7749, -122.4194))
)
}
},
modifier = Modifier.fillMaxSize()
)
WebView
AndroidView(
factory = { context ->
WebView(context).apply {
settings.javaScriptEnabled = true
loadUrl("https://example.com")
}
}
)
ComposeView: Embedding Compose in Views
In a Fragment
class LegacyFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
return ComposeView(requireContext()).apply {
setContent {
MyComposeScreen()
}
}
}
}
In XML Layout
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.appbarcompat.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<androidx.compose.ui.platform.ComposeView
android:id="@+id/compose_content"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
</LinearLayout>
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val composeView = findViewById<ComposeView>(R.id.compose_content)
composeView.setContent {
MyComposeContent()
}
}
}
AndroidViewBinding: Type-Safe Access
AndroidView(
factory = { context ->
MapBinding.inflate(LayoutInflater.from(context)).root
},
update = { view ->
val binding = MapBinding.bind(view)
binding.mapView.getMapAsync { /* ... */ }
}
)
State Sharing: View → Compose
class SharedViewModel : ViewModel() {
val location = MutableLiveData<LatLng>()
}
// In Compose Fragment
class MapsComposeFragment : Fragment() {
private val viewModel: SharedViewModel by viewModels()
override fun onCreateView(inflater: LayoutInflater, ...): View {
return ComposeView(requireContext()).apply {
setContent {
val location by viewModel.location.observeAsState()
ComposeMapScreen(location)
}
}
}
}
// In View Fragment
class MapsViewFragment : Fragment() {
private val viewModel: SharedViewModel by viewModels()
override fun onCreateView(inflater: LayoutInflater, ...): View {
val binding = MapBinding.inflate(inflater)
binding.mapView.getMapAsync { googleMap ->
googleMap.setOnCameraIdleListener {
viewModel.location.value = googleMap.cameraPosition.target
}
}
return binding.root
}
}
Migration Strategy
Start with new screens in Compose
Add Compose gradual inside existing screens usingComposeView
Keep shared ViewModel for state
Use AndroidView sparingly (only for complex views like maps)
Migrate screens incrementally — don't wait for 100% completion
Summary
AndroidView: Legacy View → Compose (factory + update pattern)
ComposeView: Compose → XML layout or Fragment
State sharing: ViewModel + LiveData/StateFlow
Migration: Gradual adoption, mixing both frameworks simultaneously
Your app doesn't need to be 100% Compose to benefit. Start interop today.
Interested in mobile app development? Check out 8 Android app templates on Gumroad!
SOCIAL SHARE CARD GENERATOR