Skip to content

Instantly share code, notes, and snippets.

@evanisnor
Created November 8, 2022 13:52
Show Gist options
  • Save evanisnor/94a5b137e8a74e0ca6017a3ae10da9ea to your computer and use it in GitHub Desktop.
Save evanisnor/94a5b137e8a74e0ca6017a3ae10da9ea to your computer and use it in GitHub Desktop.
EmojiCompat TextWatcher that supports rendering glyphs when text is replaced in an EditText widget - https://issuetracker.google.com/issues/257225296
import android.annotation.SuppressLint
import android.text.Editable
import android.text.Selection
import android.text.Spannable
import android.text.TextWatcher
import android.widget.EditText
import androidx.emoji2.text.EmojiCompat
import androidx.emoji2.text.EmojiCompat.InitCallback
import java.lang.ref.Reference
import java.lang.ref.WeakReference
/** Shameless copy of Androidx's EmojiTextWatcher, fixes EmojiCompat process when replacing text with Emoji glyphs */
class EmojiTextWatcherReplacementSupport(private val editText: EditText) : TextWatcher {
private val initCallback: InitCallback by lazy { InitCallbackImpl(WeakReference(editText)) }
@SuppressLint("CheckResult")
override fun onTextChanged(text: CharSequence?, start: Int, before: Int, after: Int) {
if (before <= after) {
// Google's EmojiTextWatcher handles this case, so we don't need to.
return
}
when (EmojiCompat.get().loadState) {
EmojiCompat.LOAD_STATE_SUCCEEDED -> {
EmojiCompat.get().process(text, start, start + after, maxEmojiCount, emojiReplaceStrategy)
}
EmojiCompat.LOAD_STATE_LOADING,
EmojiCompat.LOAD_STATE_DEFAULT -> EmojiCompat.get().registerInitCallback(initCallback)
EmojiCompat.LOAD_STATE_FAILED -> {}
else -> {}
}
}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
// do nothing
}
override fun afterTextChanged(s: Editable?) {
// do nothing
}
private class InitCallbackImpl(private val editTextReference: Reference<EditText>) :
InitCallback() {
@SuppressLint("CheckResult")
override fun onInitialized() {
val editText = editTextReference.get()
if (editText?.isAttachedToWindow == true) {
val text: Editable = editText.editableText
val selectionStart = Selection.getSelectionStart(text)
val selectionEnd = Selection.getSelectionEnd(text)
EmojiCompat.get().process(text)
updateSelection(text, selectionStart, selectionEnd)
}
}
override fun onFailed(throwable: Throwable?) {
super.onFailed(throwable)
throw RuntimeException(throwable)
}
fun updateSelection(spannable: Spannable?, start: Int, end: Int) {
if (start >= 0 && end >= 0) {
Selection.setSelection(spannable, start, end)
} else if (start >= 0) {
Selection.setSelection(spannable, start)
} else if (end >= 0) {
Selection.setSelection(spannable, end)
}
}
}
companion object {
private const val maxEmojiCount = Int.MAX_VALUE
private const val emojiReplaceStrategy = EmojiCompat.REPLACE_STRATEGY_ALL
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment