17

I have an EditText View which is edited by setText() from my code and by the user via (soft/hard) keyboard and - if possible by speech input. I want to handle input made by my code in another way than normal user input: if a user input happens, a internal event shall be fired. But I don't know how to differentiate between both. Previously - when developing on the emulator - I used the onKeyDown() method to catch user input. However, when testing on a real device, I found out, that onKeyDown() isn't triggered from soft keyboard input. Additionally speech input wouldn't be recognized by this, though I consider this to be a minor flaw. So that solution is no option for me.

On the other hand there is the onTextChanged() method, but this is triggered by both setText() and keyboard input. So how can I differentiate between both or which method is only called by user input, but not when using setText(), so can I overwrite it?

Andro Selva
  • 53,910
  • 52
  • 193
  • 240
ubuntudroid
  • 3,680
  • 6
  • 36
  • 60

5 Answers5

7

I finally solved the problem by implementing a InputConnectionWrapper (see this question and particularly the answer for an implementation example) which has various methods to get the input from a soft-keyboard. I return my InputConnectionWrapper in the method EditText.onCreateInputConnection(). For hard keyboards I use EditText.onPreIme(). All these methods are overwritten and route their input through my framework which handles the text input and updates the View accordingly. This means, in all these overwritten methods (except for onCreateInputConnection()) the super method is not called cause I update the View myself. This prevents inconsistencies between my data model and the View.

Community
  • 1
  • 1
ubuntudroid
  • 3,680
  • 6
  • 36
  • 60
4

There are already a lot of good workarounds here! I wanted to add what worked for me to give options to whoever might be having this issue in the future.

I used the TextWatcher and simply relied on checking for what element currently has focus when the EditText is being edited. Note that this would work if in your app, the user has to give focus to the EditText (by clicking on it for instance) before entering a text, and you're sure that another element will have the focus when you're using setText in your code.

Something like this

yourEditText.addTextChangedListener(
            new TextWatcher() {
                @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 (yourEditText.hasFocus) {
                        //this is a user input
                    }
                }

                @Override
                public void afterTextChanged(Editable s) {

                }
            }
    );
ribbit
  • 1,183
  • 11
  • 14
4

I believe android do not let you to distinguash between programmatically and manually entered text. The only workaround fo you as to use some sla that will indicate when test was set by your code, because you are always know when you call setText().

woodshy
  • 4,085
  • 3
  • 22
  • 21
  • 1
    Yeah, I also thought of a boolean indicator variable, but setText() and user input may happen at the same time, so that wouldn't work properly. – ubuntudroid Jun 15 '11 at 14:28
  • but why do you need to distinguish different sources of input? – woodshy Jun 15 '11 at 14:32
  • As I explained above I want to fire an event when the user changed some text. However this event must not be fired if the text was changed by code. – ubuntudroid Jun 15 '11 at 14:35
  • you can't implement what you need without locking EditText, but I'm not sure it is allowed. – woodshy Jun 15 '11 at 14:48
  • I cannot lock the EditText View as the text changes by code need to be able to occur in parallel to the changes by the user. This is vital for my app. I hope there is another solution to this - already spent a whole day thinking and hacking on this^^ – ubuntudroid Jun 15 '11 at 14:56
  • 1
    Try to combine onTextChangedListener and flag. As far as all edits goes inside of one thread onTextChangedListener events will be queued and you could try to analyse flags. – woodshy Jun 15 '11 at 15:02
  • You convinced me to try it^^ - I will post my findings here. Thanks! – ubuntudroid Jun 15 '11 at 15:08
  • I haven't tested concurrent input yet, but everything works as it should! :) I will post concurrent test results tomorrow. Thank you, woodshy! – ubuntudroid Jun 15 '11 at 17:00
  • Concurrent input works! Thanks for convincing me to try, woodshy! ;) – ubuntudroid Jun 16 '11 at 14:52
  • Okay, seems there are some problems with this solution - exact concurrent input triggers many problems with the resulting text... Seems I have to subclass the Editable class with special methods for input by code and overwrite the replace() and insert() methods and force my EditText subclass to use this Editable by calling setText() during initialization in addition to overwriting setText() to cast the parameter to my Editable and getText() to return my Editable - slightly confusing, indeed^^ I will post the complete solution once I'm done with that. – ubuntudroid Jul 09 '11 at 15:48
2

You can use a flag to differentiate.

((EditText) rootView.findViewById(R.id.editText1)).addTextChangedListener(new TextWatcher() {
        public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) {
        }

        public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) {
        }

        public void afterTextChanged(Editable editable) {
            update(true);
        }
    });            

private boolean updating = false;

private void update(boolean multiply) {
    if(updating){
        return;
    }
    updating = true;
    EditText editText1 = (EditText) getView().findViewById(R.id.editText1);
    editText1.setText("XXX");
    updating = false;
}
Daniel De León
  • 13,196
  • 5
  • 87
  • 72
0

I was having this issue when rotating the device. My editText is inside a dialog. Here's how I solved it:

editText.addTextChangedListener(
    new TextWatcher() {

        @Override
        public void afterTextChanged(Editable s) {
            String newValue = s.toString();

           String oldValue = (String) editText.getTag();

            if (newValue.length() > 0) {
                editText.setTag(newValue);
            }

            boolean itReallyChanged = oldValue != null && !newValue.equals(oldValue) && !newValue.isEmpty();
            if (itReallyChanged) {
                // Pretty sure the user genuinely changed this value,
                // not the device rotation
            }
        }
    }
);
Patrick
  • 1,227
  • 14
  • 17