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.

Rule of thumb: put one-time setup in 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.

Note: This is correct, real Compose/View interop code — but this website's own project doesn't run a live Android Studio build, so neither direction actually renders on this page.
Next up: This is the final concept lesson in the track. Jetpack Compose Best Practices ties everything from state hoisting through stability and testing together into one closing checklist.