Be careful about Clipboard operations in Jetpack Compose

Adrian Tache
Android Ideas
Published in
2 min readApr 5, 2024

--

I’m currently working on a desktop (Windows/macOS and Android) version of my ChatGPT app using Kotlin Multiplatform (KMP) and Compose UI. You can see the full source code of the WIP app using the link below:

As part of its functionality, my testers have requested that they can copy the text of the replies from ChatGPT, which is a simple request, and only requires wrapping the Text element inside a SelectionContainer :

SelectionContainer {
Text(
modifier = Modifier.fillMaxWidth().padding(16.dp),
text = message.content,
color = textColor,
)
}

While this solves most use cases, especially on desktop, we face a new issue: for whatever reason, when we receive a particularly long reply from ChatGPT, and we try to manually selecting by dragging up, the LazyList doesn’t actually scroll, to allow us to get the whole text.

So, I just naively prototyped a quick hack to see if the normal Jetpack Compose clipboard interaction works on desktop:

// Don't do this.

val clipboard: ClipboardManager = LocalClipboardManager.current

SelectionContainer {
Text(
modifier = Modifier.fillMaxWidth().padding(16.dp)
.clickable { clipboard.setText(AnnotatedString(message.content)) },
text = message.content,
color = textColor,
)
}

And, to my surprise, it didn’t work at all on desktop. So I started looking around, and found a different way to put things on the clipboard (using Java AWT), which I’m including here for reference:

val clipboard = Toolkit.getDefaultToolkit().systemClipboard
clipboard.setContents(StringSelection(message.content), null)

And, to my disappointment, this also didn’t work. So I went down a deep rabbit hole, ending up with some C code that I had no idea how to interop with current KMP code…

But I noticed something: if I clicked multiple times, sometimes the message would actually be copied to the clipboard! So, through trial and error, I realised that the actual problem is the fact that I was inside the selection container itself!

And then, by simply moving this component outside that SelectionContainer , the clipboard operation worked as expected:

SelectionContainer {
Text(
modifier = Modifier.fillMaxWidth().padding(16.dp),
text = message.content,
color = textColor,
)
}

//...

Image(
modifier = Modifier
.clickable { clipboard.setText(AnnotatedString(message.content)) }
.requiredSize(36.dp).padding(8.dp),
painter = painterResource(Res.drawable.copy),
contentDescription = "Copy",
colorFilter = ColorFilter.tint(AppColor.onBackground()),
)

I’ve tested it on Android, Windows and macOS, and it works great on all of them!

I know it’s a simple article, but since there aren’t so many fresh pieces of information about this on the web, I hope it will help someone out one day!

Thanks for reading this article. You can connect with me on LinkedIn.

I’m currently looking for a job in London, UK, so please contact me if you know someone in need of a Senior Android Developer with Java/Kotlin and Compose experience.

If you liked this article, please hit the clap icon 👏 to show your support.

--

--