Kevin van Mierlo
Kevin van Mierlo

Reputation: 9814

Jetpack Compose how to get changed text, index and length in TextField?

I'm trying to create a mentions feature in Jetpack Compose. I found a library which uses an EditText and I of course could use that, but I really want to create this in Jetpack Compose. The only problem is, in some cases it's hard to keep track of the changes to the text. For example if a person moves the cursor to a word and the keyboard shows suggestions and the user clicks it. When using EditText you can use beforeTextChanged and onTextChanged and it tells the start of the change, the length before the change and the length after the change.

So my question is, is there a somewhat equal method for Jetpack Compose TextField or a way to get these values?

Upvotes: 1

Views: 5827

Answers (2)

Kevin van Mierlo
Kevin van Mierlo

Reputation: 9814

After trying some possibilities I feel like I found the way to get the diff of which text has changed when typing in a TextField in Jetpack Compose. I will explain it with some code and also provide the full source if you want to use mentions in Jetpack Compose.

First thing we have to do is find the first difference between two strings. I use this code:

// Find first difference between two strings
private fun String.indexOfDifference(otherString: String): Int {
    if (this.contentEquals(otherString)) {
        return -1
    }
    for (i in 0 until min(this.length, otherString.length)) {
        if (this[i] != otherString[i]) {
            return i
        }
    }
    if (this.length != otherString.length) {
        return min(this.length, otherString.length)
    }
    return -1
}

One fault of this code, which for me doesn't really matter is if there are multiple of the same characters in a row. For example if you have a space and add a space before that space, it thinks the change happened at the second space instead of the first.

After knowing the first difference of the text we need to know the length of the diff in the old and new string:

    // Go through the text and find where the texts differentiate
    private fun getDiffRange(indexOfDiffStart: Int, oldText: String, newText: String): Pair<IntRange, IntRange> {
        val newLastIndex = max(0, newText.length)
        val newStartIndex = min(indexOfDiffStart, newLastIndex)

        val oldLastIndex = max(0, oldText.length)
        val oldStartIndex = min(indexOfDiffStart, oldLastIndex)
        var loopIndex = oldStartIndex
        var oldTextIndex = -1
        while(loopIndex <= oldLastIndex) {
            // From where texts differentiates, loop through old text to find at what index the texts will be the same again
            oldTextIndex = newText.indexOf(oldText.substring(loopIndex, oldLastIndex))
            if(oldTextIndex >= 0) {
                break
            }
            loopIndex++
        }
        if(oldTextIndex >= 0) {
            return Pair(first = oldStartIndex .. loopIndex, second = newStartIndex .. max(0, oldTextIndex))
        }
        return Pair(first = oldStartIndex .. oldLastIndex, second = newStartIndex .. newLastIndex)
    }

The method above will loop through the old text until we can find the entire remaining of the old text in the new text. This way we know the location and length of what has changed in the old text and also immediately know the same for the new text.

In the onValueChange method of the TextField you can check for the first difference:

if (oldTextFieldValue.text.contentEquals(newTextFieldValue.text)) {
    // Content stayed the same, probably cursor change
} else {
    val indexOfDiff = oldTextFieldValue.text.indexOfDifference(newTextFieldValue.text)
    if (indexOfDiff >= 0) {
        val (oldDiffRange, newDiffRange) = getDiffRange(indexOfDiff, oldTextFieldValue.text, newTextFieldValue.text)
    }
}

This was the information I needed to handle the mentions. Maybe this already helps somebody that has the same problem. But if you want to see my full mentions implementation you can find it here: https://gist.github.com/kevinvanmierlo/4bd011479c66eed598852ffeacdc0156

Upvotes: 0

Gabriele Mariotti
Gabriele Mariotti

Reputation: 363905

You can use the onValueChange property:

var text by remember { mutableStateOf("") }

TextField(
    value = text,
    onValueChange = {
       // text= oldValue
       // it = newValue
    }
)

Upvotes: 1

Related Questions