Interop with the View System
Almost no real app gets to start from a blank Compose slate. RecipeBox itself, as seen across the Android (Java) and Android (Kotlin) tracks, already has working XML-based screens — rewriting every one of them in Compose on day one isn't realistic, and some third-party components (like a map SDK) may not offer a Compose version at all. Compose was deliberately designed to interoperate with the traditional View system in both directions, which is exactly what makes a gradual, screen-by-screen migration possible.
AndroidView: Embedding a Traditional View Inside Compose
AndroidView is a composable that wraps a traditional View, letting it live
inside an otherwise all-Compose screen. This matters most for the exact case Compose doesn't have a
native equivalent for — say, RecipeBox showing a recipe's origin on a map using a View-based
MapView SDK:
RecipeLocationMap.kt
@Composable
fun RecipeLocationMap(latitude: Double, longitude: Double) {
AndroidView(
modifier = Modifier.fillMaxWidth().height(220.dp),
factory = { context ->
MapView(context).apply {
onCreate(null)
onResume()
}
},
update = { mapView ->
mapView.getMapAsync { map ->
val position = LatLng(latitude, longitude)
map.moveCamera(CameraUpdateFactory.newLatLngZoom(position, 14f))
}
}
)
}
factory runs exactly once, the first time this composable enters the composition — it
constructs the actual View instance. update runs every time this
composable recomposes with new parameters — here, whenever latitude/longitude
change — and is where you push Compose state changes onto the wrapped View, since the View itself
has no idea Compose or recomposition exist.
factory, and put everything that depends on a Compose parameter in update — code that belongs in update but is accidentally left in factory simply won't react when that parameter changes later.ComposeView: Embedding Compose Inside a Traditional Screen
The reverse direction is ComposeView — a real android.view.View subclass
that hosts Compose content, droppable into an existing XML layout exactly like any other View. This
is the tool that makes migrating RecipeBox's Android (Java)/Android (Kotlin) screens toward Compose
one screen at a time realistic, instead of an all-or-nothing rewrite:
fragment_recipe_detail.xml — one View among the rest of the XML layout
<androidx.compose.ui.platform.ComposeView
android:id="@+id/recipeDetailComposeView"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
RecipeDetailFragment.kt — setting Compose content on it
class RecipeDetailFragment : Fragment(R.layout.fragment_recipe_detail) {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val composeView = view.findViewById<ComposeView>(R.id.recipeDetailComposeView)
composeView.setContent {
RecipeDetailScreen(recipeId = arguments?.getString("recipeId").orEmpty())
}
}
}
Everything else in fragment_recipe_detail.xml — a toolbar, a bottom nav bar, whatever
surrounding chrome already exists — stays exactly as it is, written in XML, untouched. Only the
recipe detail content itself, inside that one ComposeView, is Compose. A team can move
through an app's screens this way, one at a time, without ever needing a single all-at-once
"big bang" rewrite, and without the app being in a broken half-migrated state at any point along
the way.