Recompose when Clipboard contents change

Viewed 116

Jetpack Compose has a ClipboardManager, allowing us to get or set the value on the clipboard. I notice however that getText() does not return a State, or any observable value.

@Composable fun ClipboardContents() {
  val cb = LocalClipboardManager.current
  Text(cb.getText())
}

How can I make sure that my Composable function responds to changes in the Clipboard?

2 Answers

You need to create State<AnnotatedString> that will be updated each time ClipboardManager clip has changed. This doesn't come out of the box you have to implement a functions that will observe for primary clip changes, then utilize that to create a state object that will trigger the recomposition.

@Composable
fun rememberClipboardText(): State<AnnotatedString?> {
    val clipboardManager = LocalClipboardManager.current
    val text = remember { mutableStateOf(clipboardManager.getText()) }
    onClipDataChanged {
        text.value = clipboardManager.getText()
    }
    return text
}

@SuppressLint("ComposableNaming")
@Composable
fun onClipDataChanged(onPrimaryClipChanged: ClipData?.() -> Unit) {
    val clipboardManager =
        LocalContext.current.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
    val callback = remember {
        ClipboardManager.OnPrimaryClipChangedListener {
            onPrimaryClipChanged(clipboardManager.primaryClip)
        }
    }
    DisposableEffect(clipboardManager) {
        clipboardManager.addPrimaryClipChangedListener(callback)
        onDispose {
            clipboardManager.removePrimaryClipChangedListener(callback)
        }
    }
}

then the usage will be something like this:

@Composable
fun ClipboardContents() {
    val clipBoardText by rememberClipboardText()
    clipBoardText?.let { Text(it) }
}

All the important info, i.e., business logic that your app depends on should be inside a lifecycle aware component like a ViewModel, not in a remembered state inside an @Composable invocation. This ensures proper disposal of the resources when the app is destroyed, and above everything keeps your data secure.

Have a var initialised and observed inside a viewModel like so

var clipText by mutableStateOf<String?>(null)
fun initialiseModel(context) {
  // Do the stuff you want
  ...
  with(context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager) {
    addPrimaryClipChangedListener{
      clipText = getText().toStrint()
    }
  }
}

There may be a cleverer way, but I'm typing this on a mobile device, so can't experiment.

Petition for a Compose Playground app to run composable code on the fly,

Related