I have an Android programming question. Using the code below I want to validate a string match. It validates fine but LogCat is showing that the TextWatcher methods are firing twice per a keystroke and I can't figure out why. I would like for the firing to only occur once per keystroke.
Do you know why it's doing this?
I thought it might be because I change the color of the text but after commenting it out it didn't make a difference.
LogCat Output
03-31 03:37:25.269: I/BeforeText(676): Hit
03-31 03:37:25.269: I/OnText(676): Hit
03-31 03:37:25.269: I/AfterText(676): Hit
03-31 03:37:25.274: I/InvalidText(676): Incorrect Text.
03-31 03:37:25.274: I/Text Value(676): a
03-31 03:37:25.404: I/BeforeText(676): Hit
03-31 03:37:25.404: I/OnText(676): Hit
03-31 03:37:25.404: I/AfterText(676): Hit
03-31 03:37:25.404: I/InvalidText(676): Incorrect Text.
03-31 03:37:25.404: I/Text Value(676): a
Activity Code
public void onCreate(Bundle savedInstanceState) {
//...omitted
//Create Answer Field
textField = (EditText)this.findViewById(R.id.textField);
//Add validation to TextField
textField.addTextChangedListener(new TextWatcher(){
public void afterTextChanged(Editable s){
Log.i("AfterText","Hit");
if(textField.getText().toString().trim().equalsIgnoreCase("hello")){
Log.i("ValidText", "Text matched.");
answerField.setTextColor(Color.GREEN);
}
else{
Log.i("InvalidText", "Incorrect text.");
Log.i("Text Value", textField.getText().toString());
textField.setTextColor(Color.RED);
}
}
public void beforeTextChanged(CharSequence s, int start, int count, int after){
//Do nothing
Log.i("BeforeText", "Hit");
}
public void onTextChanged(CharSequence s, int start, int before, int count){
//Do nothing
Log.i("OnText","Hit");
}
});
}
As your Question is for TextWatcher methods are firing twice per a keystroke. You have use TextWather for Make watch on EditText for Validate String and set Color .
You can refer Document for TextWatcher in developer site here. http://developer.android.com/reference/android/text/TextWatcher.html.
As when you make press keystore it will make change in EditText text that way TextWatcher method onTextChanged call ,when you press any key for EditText method beforeTextChanged this will call when we start edit EditText.
One More thing that is when you enter one character in EditText ,it will call all this three method of Textwather.Just there sequence for Call are different.and also refre this SO Question Android TextWatcher.afterTextChanged vs TextWatcher.onTextChanged
So there is nothing wrong will call twice for Text Change in EditText.
Hope you get understand.
I don't think if this would help you but in my case it seems to be the swipe keyboard.
The point is, when the swipe tries to make a suggestion the onchange methods seems to be called twice (one from EditText and another from swipe keyboard).
I don't know if this is feasible in your code, but just try this on your EditText to see what happen:
android:inputType="textNoSuggestions"
You can check the next site talking about this issue:
http://support.swiftkey.net/forums/116693-2-bug-reports/suggestions/2994580-span-exclusive-exclusive-spans-cannot-have-a-zero-
I will edit this answer if I found out a solution.
My conclusion is that I can't avoid the intermediate events and I can't make the difference between a real change event from the user or a swipe change event.
Depending on your real problem you should try a different workaround. One workaround could be to save the information updated in a ConcurrentHashMap and schedule a task 100 milliseconds after the event is received. At the same time you should setup a "lastModified" date.
In the scheduled task you should ask "has elapsed 100 millisecond from last update?", if the answer is no --> do nothing (this means that another event has arrived so another updating task will be executed with the last value).
Acompany all with a ReentrantLock to reach atomicity.
Something like this:
private final ReentrantLock lock = new ReentrantLock();
private final ScheduledExecutorService scheduleExecutor = new ScheduledThreadPoolExecutor(1);
private Date lastModification = null;
private static final long UPDATE_WINDOW_IN_MILLISECONDS = 100;
protected class UpdateBusinessLogicTask implements Runnable {
private final String newVal;
public UpdateBusinessLogicTask(String newVal) {
super();
this.newVal = newVal;
}
@Override
public void run() {
//check current date
Date now = new Date();
//get lock to achieve atomicity
lock.lock();
// calculate elapsed time
long elapsedTime = lastModification.getTime() - now.getTime();
// free the lock --> you could free the lock after business logic, depends on your scenario
lock.unlock();
//if the time is less than the updating window (100 milliseconds)
if (elapsedTime < (UPDATE_WINDOW_IN_MILLISECONDS - 1)) {
// DO NOTHING!!
Log.d("TEST", "Dismiss update "+newVal);
} else {
Log.d("TEST", "Updating business with newVal: "+newVal);
// TODO implement business logic
}
}
};
protected void onCreate(Bundle savedInstanceState) {
EditText view = findViewById(R.id.nameEditText);
TextWatcher tw = new TextWatcher() {
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
Log.d("TEST", "New text event received, new val: " + s.toString());
lock.lock();
lastModification = new Date();
scheduleExecutor.schedule(new UpdateBusinessLogicTask(s.toString()), UPDATE_WINDOW_IN_MILLISECONDS, TimeUnit.MILLISECONDS);
lock.unlock();
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void afterTextChanged(Editable s) {
}
};
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With