Search code examples
androidandroid-jetpack-composeandroid-jetpack

Take screenshot of a composable fun programmatically in Jetpack Compose


I would like to capture the UI emitted by Jetpack compose as a Bitmap. In XML this was done like this:

Basically takes a view as an input parameter and returns it as a Bitmap.

//take screenshot of the view added as an input argument
fun takeScreenShot(view: View) : Bitmap {
    val bitmap = Bitmap.createBitmap(
        view.width,
        view.height,
        Bitmap.Config.ARGB_8888
    )
    val canvas = Canvas(bitmap)
    view.draw(canvas)
    return bitmap
}

What is the equivalent of this in Jetpack compose?


Solution

  • Update

    There is a new multiplatform Kotlin library (also supporting Android Views and Compose Multiplatform): Comshot


    Taking screenshots from a composable is possible in tests.
    For taking screenshots in production code see this question and this issue.

    First, make sure you have the following dependency in your build script (along with other required Compose dependencies):

    debugImplementation("androidx.compose.ui:ui-test-manifest:<version>")
    

    Note: Instead of the above dependency, you can simply add AndroidManifest.xml in androidTest directory and in the manifest>application element add this: <activity android:name="androidx.activity.ComponentActivity"/>.
    Refer to this answer.

    Here is a complete example for saving, reading, and comparing screenshots:
    (Please refer to this post for setting up write permissions and so on for the tests)

    class ScreenshotTest {
    
        @get:Rule val composeTestRule = createComposeRule()
    
        @Test fun takeAndSaveScreenshot() {
            composeTestRule.setContent { MyComposableFunction() }
            val node = composeTestRule.onRoot()
            val screenshot = node.captureToImage().asAndroidBitmap()
            saveScreenshot("screenshot.png", screenshot)
        }
    
        @Test fun readAndCompareScreenshots() {
            composeTestRule.setContent { MyComposableFunction() }
            val node = composeTestRule.onRoot()
            val screenshot = node.captureToImage().asAndroidBitmap()
    
            val context = InstrumentationRegistry.getInstrumentation().targetContext
            val path = context.getExternalFilesDir(Environment.DIRECTORY_PICTURES)
            val file = File(path, "screenshot.png")
            val saved = readScreenshot(file)
    
            println("Are screenshots the same: ${screenshot.sameAs(saved)}")
        }
    
        private fun readScreenshot(file: File) = BitmapFactory.decodeFile(file.path)
    
        private fun saveScreenshot(filename: String, screenshot: Bitmap) {
            val context = InstrumentationRegistry.getInstrumentation().targetContext
            // Saves in /Android/data/your.package.name.test/files/Pictures on external storage
            val path = context.getExternalFilesDir(Environment.DIRECTORY_PICTURES)
            val file = File(path, filename)
            file.outputStream().use { stream ->
                screenshot.compress(Bitmap.CompressFormat.PNG, 100, stream)
            }
        }
    }
    

    Thanks to Google Codelabs for this.