Using TextWatchers the right way, case study: Naira TextWatcher
I have seen lots of android apps with not-so-good user experience when it comes to editing entered amounts and to be honest, I have contributed my fair share to this as well. In case you are wondering what I mean, see gif below
The problem: The cursor should always be kept in place when users make edits but in the above gif, it is not; it moves to the end each time a user makes an edit, the poor user now has to re-position the cursor 🤦 🤦
I chose to have a go at it and I think I did pretty well. Here is mine:
How I did it
I had to check out the documentation of TextWatcher as the amount formatting done in the TextWatcher was the reason for the misplaced cursor. This is what I found:
Short explanation of the beforeTextChanged() and onTextChanged() parameters:
start is the position or index before the first character that is to be changed. Start has the same value in beforeTextChanged() and onTextChanged() so you can take the value from any.
s contains the whole text in the editText. In onBeforeTextChanged(), s contains the original text in editText before edit was done; let’s say editText had 4000, then the user adds another 0, s in onBeforeTextChanged() will contain 4000, while that in onTextChanged() will contain 40000 (I know! the method names are that descriptive 🙂)
after is the length of the new set of characters that are to be added. It is usually either 1 or 0; 1 when you typed in a new character or 0 if you removed /deleted a character. It can be more than 1 for copy-paste operations. Say editText contains 210 and a user copies over 560 and pastes it after digit 1 to become 215600, after becomes the number of characters in 560 i.e 3
before contains the length of characters that have been replaced after the edit has been done. It normally has values 1, when a character is removed/deleted or changed to another character or 0 if a new character was simply added without changing or deleting any existing character. It can have values greater than 1 if for instance, given characters 654500 in an EditText, if 545 is highlighted and replaced with 0 (either by user tapping 0 from keyboard or by a paste operation), before will have a value of 3 (number of characters in 545).
count: in beforeTextChanged(), count gives the number of characters that are about to be changed/replaced. If you are adding a new character e.g 400 to 4500, count is always zero(0). If you are however, deleting a character, say 4500 to 400, count is 1 as what you are really doing is replacing 5 with an empty character. If you are changing a character to another e.g 4500 to 4600 (let’s say you highlighted 5 in 4500 and replaced it with 6), then count, before and after will read 1.
In case you have not noticed, count in beforeTextChanged() is equal to before in onTextChanged() and after in beforeTextChanged() equals count in onTextChanged()
After studying the character behaviour inside the Textwatcher, I noticed a pattern: characters to the right of the cursor remain unchanged in all cases (deletion, addition of new characters and character modification). I then came up with a strategy for maintaining the cursor position across edits
characters to the right of the cursor remain unchanged in all cases (deletion, addition of new characters and character modification).
- Get the cursor position before edit has been done, i.e start + before. The cursor is always at the end of the number of characters that are to be changed.
- Get the number of digits(numbers only, you could include decimal points if need be) to the right of the cursor. To do this, you need to get the original text in the EditText before the edit is done i.e s in beforeTextChanged(), call it c0.
- Format the new text i.e with the Naira sign and commas
- Get the position of the c0th digit, counting from the end of the new text, call it p1. Your new cursor position after edit is p1. The method I created to get p1 is shown below:
That’s about it! The complete code is here in this gist.
If you enjoyed this, I could use the claps, please clap 😆. Drop comments for questions and/or suggestions