Skip to main content

Take Screenshot

Last updated: June 18, 2026 | 3 minutes read

This example demonstrates how to capture a screenshot of the map view and preview it inside the app. The user pans, zooms, rotates or tilts the map to the desired view, taps the Take Screenshot button, and the current map frame is captured and shown in a bottom sheet preview.

Press the button to take a screenshot of the map
Preview the captured screenshot

Map Setup

MainActivity overrides onCreate, which inflates the view binding, registers the SDK listeners, wires up the Take Screenshot button, and checks for an internet connection.

MainActivity.ktView on Github
override fun onCreate(savedInstanceState: Bundle?) {
enableEdgeToEdge()
super.onCreate(savedInstanceState)

binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)

// Keep status bar icons light so they remain visible over the map and the toolbar.
WindowCompat.getInsetsController(window, window.decorView).isAppearanceLightStatusBars = false

registerSdkListeners()
setupScreenshotButton()

if (!Util.isInternetConnected(this)) {
showErrorDialog(getString(R.string.internet_required))
}
}

Requesting a Screenshot

The screenshot is not captured directly when the button is tapped. Instead, the tap sets a takeScreenshot flag and asks the map surface to render a new frame with gemScreen?.needsRender(). Performing the capture inside the render loop guarantees that the bytes read back match a frame that has actually been drawn.

MainActivity.ktView on Github
private fun setupScreenshotButton() {
binding.takeScreenshotButton.setOnClickListener {
// Request a new frame; the capture is performed in onDrawFrameCustom.
takeScreenshot = true
SdkCall.runSynced {
binding.gemSurface.gemScreen?.needsRender()
}
}
}

The onDrawFrameCustom listener runs on every rendered frame. When the takeScreenshot flag is set, it clears the flag (so the capture happens only once) and captures the frame that was just drawn.

MainActivity.ktView on Github
// Capture happens inside the render loop, once the frame requested by the button is drawn.
binding.gemSurface.onDrawFrameCustom = {
if (takeScreenshot) {
takeScreenshot = false
captureScreenshot()
}
}

Capturing the Map Image

captureScreenshot() calls mapView.captureAsImage(dataBuffer, Rect()) to render the current map view into a reusable DataBuffer. An empty Rect() captures the whole view; a non-empty rectangle would capture only that region. The captured bytes are then decoded into a Bitmap and scaled to the surface size. The commented-out variant shows how captureAsImage can write the image straight to a file instead.

MainActivity.ktView on Github
private fun captureScreenshot() {
/*
// capture to a file
val path = GemSdk.internalStoragePath + File.separator + "test.jpeg"
binding.gemSurface.mapView?.captureAsImage(path, Rect())
*/

binding.gemSurface.mapView?.captureAsImage(dataBuffer, Rect())

// Decode the captured bytes and scale to the surface size, reporting any failure.
val bytes = dataBuffer.bytes
val decoded = bytes?.let { BitmapFactory.decodeByteArray(it, 0, it.size) }
if (decoded == null) {
runOnAliveUi { showErrorDialog(getString(R.string.screenshot_failed)) }
return
}
val bitmap = decoded.scale(binding.gemSurface.width, binding.gemSurface.height)

runOnAliveUi { showScreenshotPreview(bitmap) }
}

Previewing the Screenshot

Once the bitmap is ready, showScreenshotPreview() displays it in an expanded BottomSheetDialog. The sheet is capped at 75% of the screen height so it never covers the whole screen, and its close button dismisses the preview.

MainActivity.ktView on Github
private fun showScreenshotPreview(bitmap: Bitmap) {
if (!isActivityAlive()) return

// Cap the preview sheet to 75% of the screen height so it never covers the whole screen.
val maxSheetHeight = (resources.displayMetrics.heightPixels * 0.75).toInt()

val dialog = BottomSheetDialog(this)
val previewBinding = ScreenshotPreviewLayoutBinding.inflate(layoutInflater).apply {
title.text = getString(R.string.screenshot_preview)
screenshotImage.setImageBitmap(bitmap)
closeButton.setOnClickListener { dialog.dismiss() }
}

dialog.apply {
behavior.state = BottomSheetBehavior.STATE_EXPANDED
behavior.isDraggable = false
setCancelable(true)
setContentView(previewBinding.root)
previewBinding.root.layoutParams.height = maxSheetHeight
show()
}
}