This article explains how and why Jetpack Compose UI can be rendered off-screen (for example, into a Bitmap) and what actually works in real projects today.
It avoids fantasy APIs and clearly separates supported approaches from fragile hacks.
The real problem (not the romantic one)
When developers say “off-screen rendering in Compose”, they usually want one of these:
- Generate a Bitmap of a composable for sharing
- Create thumbnails / previews
- Export UI to PDF or printing
- Apply post-processing effects (blur, filters)
- Capture UI without showing it
Compose, however, was designed with this assumption:
UI exists to be drawn to a window, not to a file.
That single assumption is why this topic feels like “black magic”.
How Compose actually draws UI
In Jetpack Compose, rendering is a pipeline:
- Composition – what UI exists
- Layout – how big it is
- Draw – emit drawing commands to a
Canvas
When UI is on screen:
- Android creates the canvas
- Compose draws into it
- You never touch this process
Off-screen rendering means:
You must provide the canvas yourself
and still trigger all three phases correctly.
What does not work (important)
Let’s eliminate myths early:
❌ “Compose has a renderToBitmap API”
❌ “Just call draw() manually”
❌ “graphicsLayer automatically gives you a bitmap”
None of these are true.
Compose intentionally does not expose a stable off-screen renderer.
What does work reliably today
There are two practical approaches:
1. ComposeView + Bitmap (most reliable)
2. Offscreen buffering for effects (not capture)
This article focuses on #1, because it is:
- Stable
- Understandable
- Used in production
Practical example: Render a composable into a Bitmap
Goal
Render this composable without showing it on screen:
@Composable
fun GreetingCard(name: String) {
Box(
modifier = Modifier
.size(300.dp)
.background(Color(0xFF6200EE)),
contentAlignment = Alignment.Center
) {
Text(
text = "Hello, $name",
color = Color.White,
fontSize = 20.sp
)
}
}
Step-by-step: The working approach
Step 1: Create a ComposeView manually
ComposeView is still the bridge between Compose and Android’s rendering system.
val composeView = ComposeView(context).apply {
setContent {
GreetingCard("Compose")
}
}
At this point:
- Nothing is on screen
- No drawing has happened yet
Step 2: Measure and layout manually
Compose will not draw unless layout has happened.
val width = 300
val height = 300
composeView.measure(
View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY),
View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY)
)
composeView.layout(0, 0, width, height)
This step is non-optional.
Step 3: Draw into a Bitmap
Now we provide our own canvas.
val bitmap = Bitmap.createBitmap(
width,
height,
Bitmap.Config.ARGB_8888
)
val canvas = Canvas(bitmap)
composeView.draw(canvas)
That’s it.
The composable has now rendered off-screen.
Step 4: Use the Bitmap
FileOutputStream(file).use { out ->
bitmap.compress(Bitmap.CompressFormat.PNG, 100, out)
}
You can now:
- Share it
- Save it
- Print it
- Process it
Why this works (and why it’s not fake magic)
This approach works because:
ComposeViewis officially supported- Compose already knows how to draw into a
Canvas - We are not touching internal APIs
- Android Views already support off-screen drawing
This is boring magic — the good kind.
Where graphicsLayer(offscreen) fits (and where it doesn’t)
Modifier.graphicsLayer {
compositingStrategy = CompositingStrategy.Offscreen
}
This:
- Forces Compose to draw into an intermediate buffer
- Enables blend modes and render effects
It does NOT:
- Give you a bitmap
- Capture pixels automatically
It is useful inside off-screen rendering, not as a replacement.
Why calls this “black magic”
Because once you go beyond the example above:
- You fight lifecycle timing
- You fight frame clocks
- You fight performance
- You fight Compose version changes
This is powerful, but fragile if abused.
When you should use this
✔ Share images
✔ Thumbnails
✔ Printing
✔ Static previews
✔ Export pipelines
When you should NOT
✘ Every frame
✘ Animations
✘ Lists
✘ Large surfaces
✘ Without profiling

