I am using an AndroidView composable to show a non...
# compose
n
I am using an AndroidView composable to show a non-Compose canvas in my Compose UI. In my non-Compose onDraw (), I can save my canvas as a bitmap successfully. The problem is that I want to save the bitmap from the onclick of a Button composable in my UI. Saving works but the bitmap is black. What could I be missing?
z
how are you drawing the composable into the canvas?
n
The button composable? It's below the canvas
z
i think i asked the wrong question. How are you getting the composable into your saved file? I assumed you were explicitly asking the
ComposeView
to draw into a canvas
n
Basically, I have an ordinary Kotlin class that I override with an onDraw method. Then I insert that canvas in a Compose column using
AndroidView
. When I move around the objects on the canvas, I want to save the bitmap. It works from the ordinary Kotlin class. Maybe I should consider using
ComposeView
in my ordinary Kotlin class? Sorry if I don't make much sense. I am still a bit confused with Compose Interop.
I can show some code if needed.
r
It helps to not think of
Canvas
as “the thing you save”
What
Canvas
really is is an interface to draw primitives onto a backing buffer
The way Compose interop works is pretty simple really: both Compose and View components render in the same backing buffer (your window’s
Surface
)
(it’s a bit more complicated in practice because our
Canvas
only records commands when you draw to the window, etc.)
Anyway, all that to say that you never “save” a
Canvas
. What you do is ask a View/Composable/whatever to issue
Canvas
commands that are target to a different backing buffer: a
Bitmap
n
Thanks, but then why is it that when I issue the bitmap saving command from my Button Composable it is black, whereas when I issue it from my onDraw method the drawing is there?
I am passing the bitmap from the View class to the Button Composable like this:
Copy code
saveBitmapToStorage(touchEventsClass.mutableBitmap, context)
z
What is
saveBitmapToStorage
?
n
This is my saving method
it's quite long but I can post it.
Copy code
fun saveBitmapToStorage(bitmap: Bitmap, context: Context) {
    val filename = "${System.currentTimeMillis()}.jpg"
    var fos: OutputStream? = null
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        context.contentResolver?.also { resolver ->
            val contentValues = ContentValues().apply {
                put(MediaStore.MediaColumns.DISPLAY_NAME, filename)
                put(MediaStore.MediaColumns.MIME_TYPE, "image/jpg")
                put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_PICTURES)
            }
            val imageUri: Uri? = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)
            fos = imageUri?.let { resolver.openOutputStream(it) }
        }
    } else {
        val imagesDir = Environment.DIRECTORY_PICTURES
        val image = File(imagesDir, filename)
        fos = FileOutputStream(image)
    }
    fos?.use {
        bitmap.compress(Bitmap.CompressFormat.JPEG, 100, it)
        Toast.makeText(context, "Saved to Pictures!", Toast.LENGTH_SHORT).show()
    }
    fos?.close()
}
z
Where does
touchEventsClass.mutableBitmap
come from?
n
In my View class I have:
Copy code
private val bitmap: Bitmap = Bitmap.createBitmap(bitmapWidth, bitmapHeight, Bitmap.Config.ARGB_8888)
    val mutableBitmap: Bitmap = bitmap.copy(Bitmap.Config.ARGB_8888, true)
    private val bitmapCanvas = Canvas(mutableBitmap)
then in my onDraw I have:
Copy code
override fun onDraw(canvas: Canvas?) {
        canvas?.apply {
            if (collision) {
                bitmapCanvas.drawColor(Color.RED)
            } else {
                bitmapCanvas.drawColor(Color.GREEN)
            }

            bitmapCanvas.drawRect(rect1, paint)
            bitmapCanvas.drawRect(rect2, paint)

        }
        canvas?.drawBitmap(mutableBitmap, 0f, 0f, paint)
    }
TouchEvents() is the View class and to get it I do:
Copy code
val touchEventsClass = TouchEvents(context)
r
Why do you
copy
the
Bitmap
you just created?
n
Because apparently that's the only way to save the bitmap. This is what I have found on StackOverflow anyway. 🙂
z
If i understand correctly (and I probably don’t since Romain is asking completely different questions), what this does is only ask your
AndroidView
that’s sitting inside your composition to draw itself. The reason the composable isn’t showing up is because you’re not actually asking compose to draw anything into the canvas. What might work is to ask the
ComposeView
that hosts your composition to draw itself into the Canvas, but of course then you will get the whole composition and not just the button composable.
r
@natario1 Hmm no, you definitely don’t need to copy it
👋 1
Your
onDraw
method is also unnecessarily complex and goes through the software renderer
You could just draw everything to the
Canvas
you receive
And when you want to “save”, simply call
onDraw
again but with the
Canvas
that wraps your
Bitmap
n
lol, I'm not a gifted dev, it's just a hobby...
r
Don’t blame yourself, this is not obvious stuff 🙂
n
Thanks to you both, I'll try to get to it with the info that you provided.
r
As to why your Bitmap is black, it could be because your
onDraw
method hasn’t been executed yet at the point you save the bitmap maybe?
n
hmm, but then it would not show on my screen
z
If your tree looks like this:
Copy code
1. Activity
    2. ComposeView
        3. AndroidView
        4. Button composable
Then if you’re only asking (3) to draw itself, only it will draw itself. The composition won’t draw anything. If you want to draw (4) into the canvas, you need to ask the compose view (2) to draw.
n
Ok, thanks for this, I'll see what I can do and will get back to you if I get stuck again! 🙂