The Android clipboard-based framework for copying and pasting supports primitive and complex data types, including:
- Text strings
- Complex data structures
- Text and binary stream data
- Application assets
Simple text data is stored directly in the clipboard, while complex data is stored as a reference that the pasting application resolves with a content provider.
Copying and pasting works both within an application and between applications that implement the framework.
Because part of the framework uses content providers, this document assumes some familiarity with the Android Content Provider API.
Work with text
Some components support copying and pasting text out of the box, as shown in the following table.
Component | Copying text | Pasting text |
---|---|---|
BasicTextField | ✅ | ✅ |
TextField | ✅ | ✅ |
SelectionContainer | ✅ |
For example, you can copy the text in the card to the clipboard
in the following snippet and paste the copied text to the TextField
.
You display the menu to paste the text by a
touch & hold on the TextField
, or by tapping the cursor handle.
val textFieldState = rememberTextFieldState()
Column {
Card {
SelectionContainer {
Text("You can copy this text")
}
}
BasicTextField(state = textFieldState)
}
You can paste the text with the following keyboard shortcut: Ctrl+V . The keyboard shortcut is also available by default. Refer to Handle keyboard actions for details.
Copy with ClipboardManager
You can copy texts to the clipboard with ClipboardManager
.
Its setText() method copies
the passed String object to the clipboard.
The following snippet copies "Hello, clipboard"
to the clipboard when the user clicks the button.
// Retrieve a ClipboardManager object
val clipboardManager = LocalClipboardManager.current
Button(
onClick = {
// Copy "Hello, clipboard" to the clipboard
clipboardManager.setText("Hello, clipboard")
}
) {
Text("Click to copy a text")
}
The following snippet does the same thing, but gives you more granular control.
A common use case is copying sensitive content,
such as password. ClipEntry
describes an item on the clipboard.
It contains a ClipData
object that describes data on the clipboard.
ClipData.newPlainText()
method is a convenience method to
create a ClipData
object from a String object.
You can set the created ClipEntry
object to clipboard
by calling the setClip() method
over the ClipboardManager
object.
// Retrieve a ClipboardManager object
val clipboardManager = LocalClipboardManager.current
Button(
onClick = {
val clipData = ClipData.newPlainText("plain text", "Hello, clipboard")
val clipEntry = ClipEntry(clipData)
clipboardManager.setClip(clipEntry)
}
) {
Text("Click to copy a text")
}
Paste with ClipboardManager
You can access the text copied to the clipboard
by calling getText()
method
over the ClipboardManager
.
Its getText()
method returns an AnnotatedString
object
when a text is copied in the clipboard.
The following snippet appends text in the clipboard
to the text in the TextField
.
var textFieldState = rememberTextFieldState()
Column {
TextField(state = textFieldState)
Button(
onClick = {
// The getText method returns an AnnotatedString object or null
val annotatedString = clipboardManager.getText()
if(annotatedString != null) {
// The pasted text is placed on the tail of the TextField
textFieldState.edit {
append(text.toString())
}
}
}
) {
Text("Click to paste the text in the clipboard")
}
}
Work with rich content
Users love images, videos, and other expressive content.
Your app can enable the user to copy rich content with
ClipboardManager
and ClipEntry
.
The contentReceiver
modifier helps you to implement pasting rich content.
Copy rich content
Your app can't copy rich content directly to the clipboard.
Instead, your app passes a URI
object to the clipboard
and provides access to the content with a ContentProvider
.
The following code snippet shows how to copy a JPEG image to clipboard.
Refer to Copy data streams for details.
// Get a reference to the context
val context = LocalContext.current
Button(
onClick = {
// URI of the copied JPEG data
val uri = Uri.parse("content://your.app.authority/0.jpg")
// Create a ClipData object from the URI value
// A ContentResolver finds a proper ContentProvider so that ClipData.newUri can set appropriate MIME type to the given URI
val clipData = ClipData.newUri(context.contentResolver, "Copied", uri)
// Create a ClipEntry object from the clipData value
val clipEntry = ClipEntry(clipData)
// Copy the JPEG data to the clipboard
clipboardManager.setClip(clipEntry)
}
) {
Text("Copy a JPEG data")
}
Paste a rich content
With the contentReceiver
modifier, you can handle pasting rich content
to BasicTextField
in the modified component.
The following code snippet adds the pasted URI of an image data
to a list of Uri
objects.
// A URI list of images
val imageList by remember{ mutableListOf<Uri>() }
// Remember the ReceiveContentListener object as it is created inside a Composable scope
val receiveContentListener = remember {
ReceiveContentListener { transferableContent ->
// Handle the pasted data if it is image data
when {
// Check if the pasted data is an image or not
transferableContent.hasMediaType(MediaType.Image)) -> {
// Handle for each ClipData.Item object
// The consume() method returns a new TransferableContent object containging ignored ClipData.Item objects
transferableContent.consume { item ->
val uri = item.uri
if (uri != null) {
imageList.add(uri)
}
// Mark the ClipData.Item object consumed when the retrieved URI is not null
uri != null
}
}
// Return the given transferableContent when the pasted data is not an image
else -> transferableContent
}
}
}
val textFieldState = rememberTextFieldState()
BasicTextField(
state = textFieldState,
modifier = Modifier
.contentReceiver(receiveContentListener)
.fillMaxWidth()
.height(48.dp)
)
The contentReceiver
modifier takes a ReceiveContentListener
object
as its argument and calls onReceive
method of the passed object when the user pastes data
to the BasicTextField
inside the modified component.
A TransferableContent
object is passed to the onReceive method,
which describes the data that can be transferred between apps
by pasting in this case.
You can access the ClipEntry
object by referring to the clipEntry
attribute.
A ClipEntry
object can have several ClipData.Item
objects
when the user selects several images and copies them to the clipboard
for example.
You should mark consumed or ignored for each ClipData.Item
object,
and return a TransferableContent
containing
the ignored ClipData.Item
objects
so that the closest ancestor contentReceiver
modifier can receive it.
The TransferableContent.hasMediaType()
method can help you determine
whether the TransferableContent
object can provide an item
with the media type.
For example, the following method call returns true
if the TransferableContent
object can provide an image.
transferableContent.hasMediaType(MediaType.Image)
Work with complex data
You can copy complex data to the clipboard in the same manner you do for the rich content. Refer to Use content providers to copy complex data for details.
You can also handle the pastes of complex data
in the same manner for the rich content.
You can receive a URI of the pasted data.
The actual data can be retrieved from a ContentProvider
.
Refer to Retrieve data from the provider for more information.
Feedback to copying content
Users expect feedback when they copy content to the clipboard, so in addition to the framework that powers copy and paste, Android shows a default UI to users when they copy in Android 13 (API level 33) and higher. Due to this feature, there is a risk of duplicate notification. You can learn more about this edge case in Avoid duplicate notifications.
Manually provide feedback to users when copying in Android 12L (API level 32) and lower. See the recommendation.
Sensitive content
If you choose to have your app let the user copy sensitive content to clipboard, such as passwords, your app must let the system know so that the system can avoid displaying the copied sensitive content in the UI (figure 2).
You must add a flag to ClipDescription
in ClipData
before calling setClip()
method over the ClipboardManager
object:
// If your app is compiled with the API level 33 SDK or higher.
clipData.apply {
description.extras = PersistableBundle().apply {
putBoolean(ClipDescription.EXTRA_IS_SENSITIVE, true)
}
}
// If your app is compiled with a lower SDK.
clipData.apply {
description.extras = PersistableBundle().apply {
putBoolean("android.content.extra.IS_SENSITIVE", true)
}
}