web-dev-qa-db-fra.com

Comment définir une erreur sur EditText à l'aide de DataBinding Framework MVVM

J'utilise Android Framework de liaison de données, je suppose un EditText pour le formulaire de connexion avec le nom d'utilisateur comme ci-dessous

<EditText
        Android:id="@+id/etext_uname"
        style="@style/login_edittext"
        Android:hint="@string/hint_username"
        Android:inputType="textEmailAddress" />

J'ai également défini LoginViewModel mais j'ai besoin d'aide pour définir l'erreur dans edittext lorsque l'utilisateur saisit une mauvaise adresse e-mail dans certains cas, disons à l'intérieur

public void afterTextChanged(@NonNull final Editable editable)

Parce que pour autant que je sache dans l'approche traditionnelle Android, nous pouvons le faire par programmation via la méthode et.setError () mais je ne veux pas créer d'objet edittext via Activity ou Fragment.

14
Pranav

Si vous voulez faire quelque chose comme la fonction EditText.setError() avec la liaison de données, voici deux méthodes.

Méthode 1

Utilisé la vue EditText finale générée à partir de la liaison de données ( https://developer.Android.com/topic/libraries/data-binding/index.html#views_with_ids )

Vous pouvez appeler le EditText directement sans le créer manuellement car il est généré automatiquement après avoir défini l'ID de la vue (également vrai pour la disposition incluse) .

MainActivityBinding.etext_uname.setError("Wrong email format");

Ou

MainActivityBinding.etext_uname.addTextChangedListener(new MyOwnTextWatcher());

Méthode 2

Si vous souhaitez utiliser la méthode de liaison avec xml comme George l'a mentionné ( https://medium.com/google-developers/Android-data-binding-custom-setters-55a25a7aea47#.su88ujqrn )

Vous devez d'abord définir votre propre méthode de reliure. Suggérez de créer une autre classe pour toute la méthode de liaison.

La méthode doit être statique, avec l'annotation @BindingAdapter et le nom de la méthode de liaison correspondante (l'espace de noms et le nom de la méthode peuvent être personnalisés)

1. Définissez le TextWatcher personnalisé

public class MyOwnBindingUtil {
    public interface StringRule {
        public boolean validate(Editable s);
    }
    @BindingAdapter("Android:watcher")
    public static void bindTextWatcher(EditText pEditText, TextWatcher pTextWatcher) {
        pEditText.addTextChangedListener(pTextWatcher);
    }
    @BindingAdapter(value = {"email:rule", "email:errorMsg"}, requireAll = true)
    public static void bindTextChange(final EditText pEditText, final StringRule pStringRule, final String msg) {
        pEditText.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) {
                }
                @Override
                public void afterTextChanged(Editable s) {
                    if (!pStringRule.validate(s)) {
                        pEditText.setError(msg);
                }
            }
        });
    }
    /*
    Your other custom binding method
     */
}

Si vous souhaitez configurer votre propre TextWatcher avec une action personnalisée, comme Toast montré, Dialogue montré. Vous devez utiliser la méthode "Android: watcher"

mBinding.setWatcher(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) {
    }
    @Override
    public void afterTextChanged(Editable s) {
    }
});

En xml,

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:Android="http://schemas.Android.com/apk/res/Android"
        xmlns:email="http://schemas.Android.com/tools"
    >

    <data>
        <variable
            name="watcher"
            type="Android.text.TextWatcher"/>
        <variable
            name="emailRule"
            type="example.com.testerapplication.MyOwnBindingUtil.StringRule"/>
        <variable
            name="errorMsg"
            type="Java.lang.String"/>
    </data>
    <EditText
        Android:layout_width="wrap_content"
        Android:layout_height="wrap_content"
        Android:hint="Input Email"
        Android:watcher="@{watcher}
        />

2. Configurez votre propre règle de validation et message d'erreur

Si vous souhaitez utiliser la fonction setError et ne laisser que le errorMsg et la logique de validation à personnaliser. Vous pouvez définir le xml comme suit.

En xml,

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:Android="http://schemas.Android.com/apk/res/Android"
        xmlns:email="http://schemas.Android.com/tools"
    >

    <data>
        <variable
            name="watcher"
            type="Android.text.TextWatcher"/>
        <variable
            name="emailRule"
            type="example.com.testerapplication.MyOwnBindingUtil.StringRule"/>
        <variable
            name="errorMsg"
            type="Java.lang.String"/>
    </data>
    <EditText
        Android:layout_width="wrap_content"
        Android:layout_height="wrap_content"
        Android:hint="Input Email"
        email:rule="@{emailRule}"
        email:errorMsg="@{errorMsg}"
        />

Code d'activité

mBinding.setErrorMsg("Wrong type");
mBinding.setEmailRule(new MyOwnBindingUtil.StringRule() {
    @Override
    public boolean validate(Editable s) {
        // check if the length of string is larger than 18  
        return s.toString().length() > 18;
    }
});

N'hésitez pas à modifier mon code pour que la liaison soit plus générique pour le développeur.

6
Long Ranger

Fondamentalement, vous avez besoin d'un moyen d'implémenter des champs dépendants. L'erreur dépend de la valeur du texte. Vous souhaitez que la valeur d'erreur soit mise à jour lorsque le texte change.

J'ai trouvé deux façons d'y parvenir:

Définir l'attribut à l'aide de l'expression de liaison de données

<EditView
    Android:text="@={viewModel.email}"
    Android:error="@={viewModel.emailRule.check(email)} />

La liaison de données garantit que la fonction check est invoquée chaque fois que email est modifié.

Utilisez RxJava pour convertir d'un champ à un autre

J'ai écrit un utilitaire pour convertir entre ObservableField et Observable. Voir FieldUtils.Java

En utilisant cela, vous pouvez implémenter dans votre code ViewModel/Model.

public class ViewModel {
    ObservableField<String> email = new ObservableField<>();
    ObservableField<String> emailError = toField(toObservable(email).map(new Func1<String, String>() {
            @Override
            public String call(String email) {
                return FormUtils.checkEmail(email) ? null : "Invalid Email";
            }
        }));
}

Problème avec EditText

EditText efface l'erreur lorsque l'utilisateur tape. La liaison de données s'attend à ce que la valeur de l'attribut soit conservée après l'appel de setter. Ainsi, il n'invoque plus le setter si la valeur ne change pas. Par conséquent, dès que vous tapez, si la valeur d'erreur calculée est la même, la liaison de données n'appellera pas setter et, par conséquent, l'erreur disparaîtra. Ce type rend l'attribut error incompatible avec la liaison de données.

Je préfère utiliser TextInputLayout fourni par la bibliothèque de conception. Il a un champ d'erreur persistant et semble également meilleur.

3
Manas Chaudhari

Je veux juste partager ma modification de la réponse de Long Ranger pour Android Arch viewModel:

    public class StringValidationRules {

    public static StringRule NOT_EMPTY = new StringRule() {
        @Override
        public boolean validate(Editable s) {
            return TextUtils.isEmpty(s.toString());
        }
    };

    public static StringRule EMAIL = new StringRule() {
        @Override
        public boolean validate(Editable s) {
            return !Android.util.Patterns.EMAIL_ADDRESS.matcher(s).matches();

        }
    };

    public static StringRule PASSWORD = new StringRule() {
        @Override
        public boolean validate(Editable s) {
            return s.length() < 8;
        }
    };

    public interface StringRule {
        boolean validate(Editable s);
    }
}

la vueModèle ...

    public class LoginViewModel extends ViewModel {
...
@BindingAdapter({"app:validation", "app:errorMsg"})
    public static void setErrorEnable(EditText editText, StringValidationRules.StringRule stringRule, final String errorMsg) {
        editText.addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {

            }

            @Override
            public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {

            }

            @Override
            public void afterTextChanged(Editable editable) {
                if (stringRule.validate(editText.getText())) {
                    editText.setError(errorMsg);
                } else {
                    editText.setError(null);
                }
            }
        });
    }

...

et le XML:

<?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"
    xmlns:tools="http://schemas.Android.com/tools"
    xmlns:bind="http://schemas.Android.com/apk/res-auto"
    >
    <data>
        <variable name="viewModel" type="com.fernandonovoa.sapmaterialstockoverview.login.LoginViewModel"/>
        <import type="com.fernandonovoa.sapmaterialstockoverview.utils.StringValidationRules" />
    </data>

...

<EditText
                Android:id="@+id/etEmail"
                Android:layout_width="match_parent"
                Android:layout_height="wrap_content"
                Android:hint="Ingrese su email"
                Android:inputType="textEmailAddress"
                Android:drawableLeft="@drawable/ic_email"
                Android:drawableStart="@drawable/ic_email"
                app:validation="@{StringValidationRules.EMAIL}"
                app:errorMsg='@{"Email no válido"}'
                style="@style/AppTheme.Widget.TextInputLayoutLogin"
                />

<EditText
                Android:id="@+id/etPassword"
                Android:layout_width="match_parent"
                Android:layout_height="wrap_content"
                Android:hint="Ingrese su contraseña"
                Android:inputType="textPassword"
                Android:drawableLeft="@drawable/ic_lock"
                Android:drawableStart="@drawable/ic_lock"
                app:validation="@{StringValidationRules.PASSWORD}"
                app:errorMsg='@{"Contraseña no válida"}'
                style="@style/AppTheme.Widget.TextInputLayoutLogin"
                />

Vous pouvez également ajouter une validation sur le texte de modification comme celui-ci.

Fichier de mise en page

<?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"
    xmlns:tools="http://schemas.Android.com/tools">

    <data>

        <variable
            name="viewModel"
            type="com.example.app.ui.login.LoginViewModel" />

        <import type="com.example.app.ui.ValidationRule" />

        <variable
            name="watcher"
            type="Android.text.TextWatcher" />

        <import type="com.example.app.utils.ValidationUtils" />

    </data>

    <RelativeLayout
        Android:id="@+id/login"
        Android:layout_width="match_parent"
        Android:layout_height="match_parent"
        Android:orientation="vertical"
        Android:padding="16dp"
        tools:context=".ui.login.LoginFragment">

        <LinearLayout
            Android:layout_width="match_parent"
            Android:layout_height="wrap_content"
            Android:layout_centerInParent="true"
            Android:orientation="vertical">

            <com.google.Android.material.textfield.TextInputLayout
                Android:layout_width="match_parent"
                Android:layout_height="wrap_content"
                Android:hint="username"
                Android:watcher="@{watcher}"
                app:error="@{@string/validation_error_msg_email}"
                app:rule="@{ValidationRule.EMPTY}">

                <com.google.Android.material.textfield.TextInputEditText
                    Android:layout_width="match_parent"
                    Android:layout_height="wrap_content"
                    Android:text="@={viewModel.usernameObs}" />
            </com.google.Android.material.textfield.TextInputLayout>


            <com.google.Android.material.textfield.TextInputLayout
                Android:layout_width="match_parent"
                Android:layout_height="wrap_content"
                Android:hint="password"
                Android:watcher="@{watcher}"
                app:error="@{@string/validation_error_msg_password}"
                app:rule="@{ValidationRule.PASSWORD}">

                <com.google.Android.material.textfield.TextInputEditText
                    Android:layout_width="match_parent"
                    Android:layout_height="wrap_content"
                    Android:inputType="textPassword"
                    Android:text="@={viewModel.passwordObs}" />
            </com.google.Android.material.textfield.TextInputLayout>

            <com.google.Android.material.button.MaterialButton
                Android:layout_width="wrap_content"
                Android:layout_height="wrap_content"
                Android:layout_gravity="center_horizontal"
                Android:layout_marginTop="16dp"
                Android:background="?colorAccent"
                Android:enabled="@{ValidationUtils.isValidEmail(viewModel.usernameObs) &amp;&amp; ValidationUtils.isValidPassword(viewModel.passwordObs)}"
                Android:onClick="@{() -> viewModel.login()}"
                Android:text="Login"
                Android:textColor="?android:textColorPrimaryInverse" />
        </LinearLayout>
    </RelativeLayout>
</layout>

BindingUtils

object BindingUtils {
        @BindingAdapter(value = ["error", "rule", "Android:watcher"], requireAll = true)
        @JvmStatic
        fun watcher(textInputLayout: com.google.Android.material.textfield.TextInputLayout, errorMsg: String, rule: ValidationRule, watcher: TextWatcher) {
            textInputLayout.editText?.addTextChangedListener(object : TextWatcher {
                override fun afterTextChanged(p0: Editable?) {
                }

                override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
                }

                override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
                    textInputLayout.error = null
                    if (rule == ValidationRule.EMPTY && !ValidationUtils.isValidEmail(p0.toString())) textInputLayout.error = errorMsg
                    if (rule == ValidationRule.PASSWORD && !ValidationUtils.isValidPassword(p0.toString())) textInputLayout.error = errorMsg
                }
            })
        }
    }

Règle de validation

enum class ValidationRule{
    EMPTY, EMAIL, PASSWORD
}

N'oubliez pas de mettre l'observateur en fragment ou en activité comme celle-ci

binding.watcher = object : TextWatcher {
        override fun afterTextChanged(p0: Editable?) {
        }

        override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
        }

        override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
        }
    }
0
Rajesh Khadka