Migrating from Leanback to Jetpack Compose in Android TV
When Google introduced Leanback, it solved a hard problem: building focusable, remote-friendly UIs for Android TV. But Leanback was built on Fragments, Presenters, and XML themes — patterns that don’t scale well in 2025.
With Jetpack Compose for TV, we finally get:
- A declarative focus model.
- Composable theming instead of XML overrides.
- Reusable UI that works across Mobile, Tablet, and TV.
But migrating a production TV app isn’t as simple as replacing a BrowseSupportFragment. You need a strategy.
This post is a real-world migration guide — not just code snippets.
🚀 Migration Philosophy
Instead of rewriting everything at once, think bottom-up + modular:
- Extract a design system → one source of truth for colors, typography, dimensions.
- Replace leaf components first → cards, buttons, headers.
- Introduce Compose inside Leanback (hybrid approach).
- Migrate screens one by one → Browse → Details → Search → Player.
- Switch navigation layer last → Fragments → Navigation-Compose.
🎨 Theming – From XML to Composable
Leanback theming is XML-driven:
@style/BrowseTitleView
@style/RowHeader
“> ” width=”300″ height=”78″ data-mce-src=”https://www.tothenew.com/blog/wp-ttn-blog/uploads/2025/09/Screenshot-from-2025-09-09-16-34-58-300×78.png”>xml themeThis worked, but it was rigid.
In Compose-TV, theming is Kotlin-driven:

compose theme
✅ You now control theme per screen or per component, not just app-wide.
✅ Easy to experiment with focus states and high-contrast accessibility themes.
🧩 Modular Migration Strategy
Let’s say your current structure looks like this:
:app
├── ui (Leanback fragments, presenters)
├── data
├── domain
You want to evolve into:
:app
├── ui-leanback (legacy fragments, presenters)
├── ui-compose (new Compose screens)
├── design-system (colors, typography, dimensions)
├── domain
└── data
- design-system → A pure Kotlin module containing ColorPalette, Typography, spacing constants. Both XML and Compose can consume this.
- ui-compose → All new TV screens in Compose.
- ui-leanback → Old Leanback screens until migration is done.
This way, you can:
- Ship hybrid builds (Compose + Leanback) in production.
- Roll out Compose screens gradually (safe migration).
🎬 Screen Migration Example
Leanback BrowseSupportFragment

Leanback BrowseSupportFragment
Compose Equivalent

compose example
🎯 Difference:
- No presenters.
- No fragment boilerplate.
- Full control over focus UI.
🎮 Focus and Remote Navigation
Leanback managed focus magically, but often in a rigid way.
In Compose-TV:
- Use Modifier.focusable() for all interactive elements.
- Group elements with FocusGroup for better navigation.
- Use onPreviewKeyEvent for custom DPAD overrides (e.g., skip sections).
Modifier.onPreviewKeyEvent { keyEvent ->
if (keyEvent.key == Key.DirectionDown) {
// Handle custom navigation
true
} else false
}
📊 Performance Considerations
Leanback was optimized for large rows. In Compose:
Use LazyRow / LazyColumn for virtualization.
Combine with Paging 3 for infinite scrolling:
val movies = pager.collectAsLazyPagingItems()
LazyRow {
items(movies.itemCount) { index ->
movies[index]?.let { MovieCard(it) }
}
}
Test on real devices: Emulator doesn’t always reflect true DPAD latency.
🧪 Pitfalls & Lessons Learned
- Theme clashes: If Leanback fragments still use XML themes, ensure Compose surfaces are wrapped in TvAppTheme.
- Focus loops: Compose focus isn’t 1:1 with Leanback; test “edge cases” like end-of-row navigation.
- Hybrid apps: Don’t try to rewrite everything at once. A ComposeView inside Leanback is your friend during transition.
✅ Migration Checklist
- Extract theme + design system into shared module.
- Migrate leaf components (cards, buttons).
- Start hybrid: Use ComposeView inside Leanback.
- Replace rows/fragments screen by screen.
- Switch navigation to Compose last.
- Remove Leanback dependency.
Conclusion
Migrating an Android TV app from Leanback to Jetpack Compose is not just a UI migration. It’s a re-architecture:
- From XML themes → to Composable theming.
- From fragment-heavy code → to state-driven navigation.
- From rigid templates → to flexible layouts.
- Do it gradually, starting from lower modules and shared design system. By the end, you’ll have a cleaner, modern, and scalable
- TV app that’s easier to maintain and future-proof.