Ilhasoft / data-binding-validator

Android fields validation library based on data binding adapters.
Apache License 2.0
344 stars 53 forks source link

TextInputLayout error message flickering issue #37

Open roncbird opened 4 years ago

roncbird commented 4 years ago

I'm running into an UI issue, causing the error message under a TextInputLayout to flicker if multiple rules are used. Please see the gif below.

datatbinding_validation_ui_issue

What is happening is, if multiple rules are used, for instance MaxLengthRule and MinLengthRule, when the user starts typing, MaxLengthRule is valid, so the validation library disables the TextInputLayout error, then proceeds to validate MinLengthRule. This rule is not valid right when the user starts typing so the validation library adds the error back. This is happening each time the user types.

I downloaded the sample app for the data-binding-validator library provided in this repository, and reduced activity_main.xml to this:

`<?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto">

<ScrollView
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:focusableInTouchMode="true"
        android:padding="8dp"
        android:orientation="vertical">

        <com.google.android.material.textfield.TextInputLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content">

            <com.google.android.material.textfield.TextInputEditText
                android:id="@+id/name"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:hint="Name"
                app:validateMaxLength="@{10}"
                app:validateMaxLengthMessage="@{@string/custom_error_max_length}"
                app:validateMinLength="@{4}"
                app:validateMinLengthMessage="@{@string/custom_error_min_length}"
                app:validateEmpty="@{true}"
                />

        </com.google.android.material.textfield.TextInputLayout>

    </LinearLayout>
</ScrollView>

`

Then in the MainActivity

`public class MainActivity extends AppCompatActivity implements Validator.ValidationListener {

private static final String TAG = "MainActivity";

private ActivityMainBinding binding;
private Validator validator;

@Override
public void onValidationSuccess() {
    saveToDatabase();
}

@Override
public void onValidationError() {
    Toast.makeText(MainActivity.this, "Dados inválidos!", Toast.LENGTH_SHORT).show();
}

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    binding = DataBindingUtil.setContentView(this, R.layout.activity_main);

    binding.name.addTextChangedListener(new TextWatcher() {

        public void afterTextChanged(Editable s) {
            validator.validate(binding.name);
        }

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

        }

        public void onTextChanged(CharSequence s, int start, int before, int count) {

        }
    });

    validator = new Validator(binding);
    validator.setValidationListener(this);
    validator.enableFormValidationMode();
}

private View.OnClickListener onValidateNameClickListener = new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        validator.validate(binding.name);
    }
};

private View.OnClickListener onValidateAllClickListener = new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        if (validator.validate()) {
            saveToDatabase();
        } else {
            Toast.makeText(MainActivity.this, "Dados inválidos!", Toast.LENGTH_SHORT).show();
        }
    }
};

private View.OnClickListener onValidateAllWithListenerClickListener = new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        validator.toValidate();
    }
};

private void saveToDatabase() {
    Log.i(TAG, "Salvar os dados no banco de dados");
}

} `

If you then run the app, you will be able to see the flickering. After playing around with it some more, I believe I may have found a solution for the issue. Please see the details below.

In MinLengthRule.java and MaxLengthRule.java I included the additional logic below in each of their onValidationSucceeded() methods:

@Override public void onValidationSucceeded(TextView view) { TextInputLayout textInputLayout = EditTextHandler.getTextInputLayout(view); if(null != textInputLayout.getError() && textInputLayout.getError().equals(errorMessage)) { EditTextHandler.removeError(view); } }

This only allows the rule to dismiss its own error, by checking to make sure the current error being displayed, matches the rules error, that is trying to dismiss the error. This eliminates the flickering.

This may not be the best approach to fix this issue, so I'm completely open to other ideas. If you feel this is a viable fix, or determine a better way of resolving the flicker, would you be able to update the library with a solution? We use this library extensively in our project here at work, so it would be great of we could continue to use it, instead of having to find another option.

Thanks any advance for any help you are able to provide.

dbland87 commented 4 years ago

+1

roncbird commented 4 years ago

I wanted to follow up on this issue and see if any one has had a chance to look into it.