Debugging log · Compose
Compose @Preview fails, but the app runs fine
My screen built, launched and worked perfectly on a device. But the Android Studio preview pane just showed a red render error. It's a strange feeling — the code is clearly fine, yet the tool that's supposed to show it a design-time snapshot refuses. The reason is that a preview is not your app, and it can reach things a running app quietly provides.
The short version: a @Preview renders your composable in isolation, with no real Activity, no ViewModel graph and no live data. If the composable needs any of those to draw, the preview crashes while the real app is fine. The fix is to make previews use fake data and avoid the app's runtime plumbing.
Why the preview is different
When your app runs, a whole environment exists: an Activity, a navigation graph, dependency injection, a ViewModel holding state, maybe a repository fetching data. The preview renderer has none of that. It calls your composable directly in a stripped-down sandbox. So anything the composable pulls from that environment — a viewModel(), a LocalContext cast to your Activity, a resource that isn't there at design time — has nothing to resolve to, and rendering fails.
The usual culprits
- A ViewModel inside the previewed composable.
viewModel()or an injected VM can't be built at preview time. - Casting
LocalContext.currentto an Activity. In a preview the context isn't your Activity, so the cast throws. - Real data or I/O at composition. Reading a database, network or DataStore while composing has nothing to read from.
- Non-preview-safe resources — fonts, drawables or themes that only resolve inside the full app setup.
The fix: split the stateful and stateless parts
The clean pattern is to separate the composable that holds state from the one that just draws. Preview the stateless one with plain sample values.
// Stateful: reaches the ViewModel. Don't preview this one.
@Composable
fun ProfileScreen(vm: ProfileViewModel = viewModel()) {
val state by vm.state.collectAsStateWithLifecycle()
ProfileContent(state)
}
// Stateless: just draws from its inputs. Preview THIS.
@Composable
fun ProfileContent(state: ProfileState) { /* UI only */ }
@Preview
@Composable
fun ProfileContentPreview() {
ProfileContent(
state = ProfileState(name = "Sample User", followers = 42)
)
}
Now the preview draws real UI from fake data, touching none of the runtime graph. This split is good architecture anyway — it makes the drawing layer easy to test and reuse.
When you must preview something context-bound
Guard the runtime-only bits with LocalInspectionMode, which is true only inside a preview:
@Composable
fun Banner() {
if (LocalInspectionMode.current) {
// draw a placeholder for the preview
} else {
// do the real, context-dependent work
}
}
Use this sparingly — the split above is cleaner — but it's handy when a composable genuinely needs to behave differently at design time.
Before assuming your code is wrong, open the preview's error detail and read the exception. A preview failure is a render-time crash with a real stack trace — it usually names the exact ViewModel, cast or resource that isn't available.
Quick checklist
- Preview a stateless composable with sample inputs, not the screen that wires up the ViewModel.
- Don't build ViewModels, read data, or cast the context inside anything you preview.
- Provide fake but realistic parameters so the layout reflects real content.
- Reach for
LocalInspectionModeonly when a composable must differ at design time.
Class and property names above are illustrative — the pattern matters more than the names.