Hey there :slightly_smiling_face: Does anyone know...
# compose
a
Hey there 🙂 Does anyone know a neat way to make screenshots of composables?
d
Afaik there are multiple ways. For example: • Manual: You could save the Preview in Android Studio (when using Jetpack Compose) as image • Automated: Integrate it in an app and do screenshot testing
c
Screenshot of a single composable?
a
@Chachako yes, for example a drawing app which allows the user to save the canvas as an image
@dbaelz thanks, the aim is however not on testing, but on allowing the user to save a composable as an image. I'm wondering if it is possible to use something similar to screenshot testing in production code to allow the user to do that.
👍 1
c
Unfortunately, this is not supported at present. Related issue: https://issuetracker.google.com/issues/180881629 However, you can temporarily solve this problem by obtaining a screenshot of ComposeView, but there is a potential problem. Sometimes
composeView.draw
does not get the latest appearance, like: https://kotlinlang.slack.com/archives/CJLTWPH7S/p1629271246339900?thread_ts=1629193140.247600&cid=CJLTWPH7S
a
@Chachako that seems to be a good solution. Is there currently a normal way to convert compose function to composeView?
c
Copy code
Bitmap(..).applyCanvas { canvas ->
  LocalView.current.draw(canvas)
}
and then you need to crop the bitmap according to the region of the widget. This is not a good solution, but it may be the only one at present 🙏
a
Thank you, my current code does not have any xml layout. I believe that solution also requires defining an XML layout to wrap the compose code inside the ComposeView. Am I right?
c
No, the LocalView gets what the current composable renders, so you don't need to wrap it in XML again
a
If anyone has an example code of this, they can put it here
a
This is what I'm trying:
Copy code
val view = LocalView.current
    view.measure(View.MeasureSpec.EXACTLY, View.MeasureSpec.EXACTLY)
    val bmp = Bitmap.createBitmap(
        view.measuredWidth,
        view.measuredHeight,
        Bitmap.Config.ARGB_8888).applyCanvas {
        LocalView.current.draw(this)
    }
    bmp.let {
        File(LocalContext.current.filesDir, "screenshot.png")
            .writeBitmap(bmp, Bitmap.CompressFormat.PNG, 85)
    }
...

private fun File.writeBitmap(bitmap: Bitmap, format: Bitmap.CompressFormat, quality: Int) {
    outputStream().use { out ->
        bitmap.compress(format, quality, out)
        out.flush()
    }
}
which follows by the following error:
Copy code
Process: com.etrusted.android.mars.debug, PID: 11369
    java.lang.IllegalArgumentException: width and height must be > 0
t
view.measuredWidth/height are probably not ready yet when createBitmap executes so that could be causing the error…is there a way to either take the Composable’s size constraints instead?
a
@Tash that indeed seems to be the issue, however, setting them manually also outputs an empty image.
@Tash @Abdalla Hassanin Ok, this worked for me 🚀 :
Copy code
val view = LocalView.current
    val context = LocalContext.current

    val handler = Handler(Looper.getMainLooper())
    handler.postDelayed(Runnable {
        val bmp = Bitmap.createBitmap(view.width, view.height,
            Bitmap.Config.ARGB_8888).applyCanvas {
            view.draw(this)
        }
        bmp.let {
            File(context.filesDir, "screenshot.png")
                .writeBitmap(bmp, Bitmap.CompressFormat.PNG, 85)
        }
    }, 1000)
a 1-second delay is applied by the runner to wait for view to finishe measuring. this way, the
view.width
and `view.height`return correctly Stack Overflow: https://stackoverflow.com/a/68839490/3481187
t
oh, i see, so the measurement on the View wasn’t done yet. however, you should be able to grab the measurements from the composition side of things… which type of composable are you calling this code in? wondering if using something like
BoxWithConstraints
can be used where you can get the container’s width/height to pass down to
createBitmap
a
@Chachako thanks a lot for your help. The issue is almost fixed LocalView seems to return the whole view with all of composables visible on the screen which is quite useful. One last thing that I wonder is how would it be possible to get the view for a certain composable.
c
Get the current composable coordinates through
Modifier.onGloballyPositioned {it.boundsInRoot().. }
, and then use them to crop the bitmap. In addition, yesterday I seem to have thought of a better way to take a screenshot of a single composable. I am going to create a demo to try it out. I will publish it here if it goes well 🤯.
a
Of course 😄 don’t know why I didn’t think about it at first. Thanks a lot @Chachako 👍
Hi @Chachako do you maybe know a way to exclude the composables in the generated screenshot?
c
Yes, I thought of a way before, but I need to take it to try it, this may be a field that has not been explored 🤯, but I have a little busy recently. Once I have any progress, I will share more information in this thread.
a
For anyone trying this method and getting errors, I found the following gitHub issue useful: https://github.com/coil-kt/coil/issues/159