I have a Fragment
within a ViewPager
.
In that fragment, I have a couple of EditText
whose OnTextChanged listeners (added in the onViewCreated
method) do some validation work and call a function to set/remove an error such as:
// Sets or removes an error message in the nameField EditText
public void validateUsername(String message)
{
if(message == null)
{
nameField.setError(null);
}
else
{
nameField.setError(message,errorIcon);
}
}
The errorIcon is a Drawable
instanciated onCreate
like this:
errorIcon = getResources().getDrawable(R.drawable.ic_wrong_input);
errorIcon.setBounds(new Rect(0, 0, errorIcon.getIntrinsicWidth(), errorIcon.getIntrinsicHeight()));
When I type something in the EditText, the validation works fine: my error message is shown, with my custom icon.
But when I rotate the device, the validateUsername()
function gets called (expected behavior), the nameField.setError(message,errorIcon)
in the else
branch gets executed but the EditText
gets Android's default error icon instead of mine (and yes, when debugging I can see that my errorIcon Drawable was instantiated). After that, if I type something invalid in that EditText
, the validation method gets called again and this time I get my custom error icon again. If I rotate the device again, same thing: default icon on re-create, custom icon after I type something invalid...
Can anyone explain this behavior? Why am I getting Android's default error icon after a re-create caused by a device rotation even though I'm explicitly setting a custom one?
EDIT:
I've tried instantiating the Drawable in my validateUsername()
method but it didn't work.
EDIT 2:
I want to use a custom error icon so that it blends better with my UI and also because I'm using another custom icon (a green one) that shows up as the EditText
s' right Drawable
when their input is valid and I want them to be consistent with each other.
For now I have resigned and I'm letting Android use the default error icon (i.e. I'm not specifying any Drawable
on the setError()
method). When the input is valid, I set the EditText's right Drawable to my custom "input is valid" icon, which I modified to have the same size as Android's indicator_input_error.png, for consistency sake.
Still, I don't like this approach (e.g. in future versions, the indicator_input_error.png icon might be different) and I can't understand why do I keep getting the default icon when the Fragment
is re-created. Strangely enough, the first time the Fragment
is re-created, if the EditText validation fails and validateUsername() gets called with a non-null message, I get my custom icon. On the second time (and so forth) that the Fragment
is re-created, I get Android's default icon. If I input some invalid text, I get my custom icon again, which is very strange.
What a hassle! I ran into the same issue. I have a custom icon to represent warnings in addition to the built-in Error icon, and will display the appropriate icon depending on the condition.
As you ran in to, the setError() runs amok when onSave/onRestoreInstanceState() is called within your activity (ie: when the screen is rotated).
It turns out that most (all?) stock Android widgets create their own parcelable state to retain their marbles during rotation. This means that many simple layouts do not need to implement their own parcelable/serializable objects, as you've most likely experienced.
If you drill down into the TextView class, you'll see at the end of its onRestoreInstanceState() implementation the following:
if (ss.error != null) {
final CharSequence error = ss.error;
// Display the error later, after the first layout pass
post(new Runnable() {
public void run() {
setError(error);
}
});
As you can see, ss.error (which was saved during TextView's onSaveInstanceState() ) is only checked to see if it's not null. If so, it's restored with the call to setError(error). In short, your custom icon is not parceled during the save, so it's not restored by the widget -- resulting in the display of the standard red error icon. Given the age of the TextView code, I suspect this is a Google oversight.
You could try resetting the error text and icon in your own overridden onRestoreInstanceState(), but since the widget is queueing its setError() call (by way of the call to post() ), your direct call will outrun it, resulting in your icon showing for a split second, only to be overwritten once the queue processing occurs.
I resolved this by placing my own calls to setError() within my view's queue (also using post() ). This may not be academically proper and squeaky-clean synchronized (as I'm depending on the message queue processing in order), but it solves the issue.
In short, do something like this in your onRestoreInstanceState():
protected void onRestoreInstanceState(Bundle savedInstanceState)
{
// Call super first, to ensure that all the widget updates get in the queue first...
super.onRestoreInstanceState(savedInstanceState);
View view = getWindow().getDecorView().getRootView();
view.post(new Runnable()
{
public void run()
{
myEditText.setError(...);
.
.
.
}
});
}
That was a lot to digest. Hope this helps!