0

I have a form where the user can enter some data in EditText fields. One of those EditText widgets is for the email address. I am using a TextWatcher to make sure that the text is always lowercase as follows:

txtEmail.addTextChangedListener(new TextWatcher()
    {
        String prevString = "";
        boolean delAction = false;

        @Override
        public void beforeTextChanged(CharSequence s, int start, int count, int after)
        {

        }

        @Override
        public void onTextChanged(CharSequence s, int start, int before, int count)
        {
            if (s.length() < prevString.length())
                delAction = true;
            else
                delAction = false;
        }

        @Override
        public void afterTextChanged(Editable s)
        {
            if (!delAction)
            {
                String temp = s.toString().toLowerCase();
                if (!temp.equals(prevString))
                {
                    prevString = temp;
                    txtEmail.setText(temp); // Recursive
                    txtEmail.setSelection(temp.length());
                }
            }
            else
            {
                prevString = s.toString();
            }
        }
    });

In the onTextChanged(...) I am also making a comparison to make sure that deleting works properly.

Now to the problem. txtEmail.setText(temp); is causing the whole Watcher to run recursively. I can control the caret position to go to the end of the EditText by adding txtEmail.setSelection(temp.length()); and escape the recursive loop with the if but I cannot find a way to keep my caret at a specific point. If for example I wrote "myema(il missing)@something.com and want to go back to make my correction at each letter typed the caret goes at the end of the string.

Now the weird part. I tried keeping the position of the caret before the beforeTextChanged(...) or in onTextChanged(...). The moment I type something the caret position is properly changed in every case. The moment, though, we enter the recursive call the position of the caret is reported as 0. I am guessing that when I actually type, the caret movement is also registered but because in the recursive call there is no caret movement rather than "pasting" of text in the EditText I get no position change.

And thus the question: How can I keep the position of the caret? The thought I had was to actually sub string the text. Get everything to the caret make the changes throw it there with the txtEmail.setSelection(temp.length()); and then append the rest of the string in the else step (not tried as of yet). Is there any other (simpler/efficient) way I could handle that with inbuilt tools?

Thanks in advance!

MadDim
  • 543
  • 6
  • 22

1 Answers1

2

Maybe you have approached this the hard way. Why not use

android:digits="qwertyuiopasdfghjklzxcvbnm_,.@.," 

for the EditText for email? If you allow the user to input any other characters, just add those to the android:digits field.

EDIT BY OP:

After following Alexandru's advice (see comments) to unregister and register the TextWatcher again, the code changed and works as follows:

    txtEmail.addTextChangedListener(new TextWatcher()
    {
        String prevString = "";
        boolean delAction = false;
        int caretPos = 0;

        @Override
        public void beforeTextChanged(CharSequence s, int start, int count, int after)
        {
            caretPos = txtEmail.getSelectionStart();
        }

        @Override
        public void onTextChanged(CharSequence s, int start, int before, int count)
        {
            if (s.length() < prevString.length())
                delAction = true;
            else
                delAction = false;
        }

        @Override
        public void afterTextChanged(Editable s)
        {
            if (!delAction)
            {
                prevString = s.toString().toLowerCase();
                txtEmail.removeTextChangedListener(this);
                txtEmail.setText(prevString);
                txtEmail.addTextChangedListener(this);
                txtEmail.setSelection(caretPos + 1);
            }
            else
            {
                txtEmail.setSelection(caretPos - 1);
                prevString = s.toString();
            }
        }
    });
MadDim
  • 543
  • 6
  • 22
  • The biggest problem with this approach is that if I type caps then the characters do not register at all. Plus I need to basically put the whole keyboard in `android:digits="..."` to cover all possible combinations according to [email address formal definitions](https://en.wikipedia.org/wiki/Email_address#Syntax). I searched long and hard for the kind of functionality I want and I think this is the only way to achieve it. I'd love to be proven wrong though ;) – MadDim Apr 20 '17 at 15:14
  • Aha, so if the user types capital 'A' you must show lowercase 'a'? In order to avoid setting the cursor manual, won't it help to just unregister the TextWatcher before setting the text with `txtEmail.setText(temp)` and then register it again? This way you graciously avoid the recursive call, I guess. – Alexandru Dascălu Apr 20 '17 at 15:29
  • Aaaand you rock... Never thought of trying to unregister the TextWatcher. It actually seemed really weird, unregistering while inside, when I read your comment! Apparently Java/Android doesn't care (`this` is truly magical some times!!!) and it works!!! If you don't mind I will edit your answer with the changes I made and I will mark it. – MadDim Apr 20 '17 at 16:11
  • Sure, glad it helped. It is actually not weird at all. You can check out the [source code](https://github.com/android/platform_frameworks_base/blob/master/core/java/android/widget/TextView.java) for TextView which is the parent class for EditText. You can see that the TextWatcher is a usual listener, nothing fancier. You can check [here the listener(observer)](http://www.vogella.com/tutorials/DesignPatternObserver/article.html#observer) pattern if you are unfamiliar with it. – Alexandru Dascălu Apr 20 '17 at 18:21