I would like to bind a error message directly to a android.support.design.widget.TextInputLayout
. I cannot find a way to set the error through the layout. Is this even possible?
This is how I imagined it working:
<?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> <import type="android.view.View" /> <variable name="error" type="String" /> </data> <android.support.v7.widget.LinearLayoutCompat android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> <android.support.design.widget.TextInputLayout android:layout_width="match_parent" android:layout_height="wrap_content" app:errorEnabled="true" app:errorText="@{error}"> <EditText android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="@string/username" android:inputType="textEmailAddress" /> </android.support.design.widget.TextInputLayout> </android.support.v7.widget.LinearLayoutCompat> </layout>
You can use the TextInputLayout to display error messages according to the material design guidelines using the setError and setErrorEnabled methods. In order to show the error below the EditText use: TextInputLayout til = (TextInputLayout) findViewById(R. id.
Now you can simply do input. setError(..) for new error and input. setErrorEnabled(false) to remove it.
Just use: TextInputLayout textInputLayout = findViewById(R. id. custom_end_icon); String text = textInputLayout.
As of writing this answer (May 2016), there is no XML attribute corresponding to setError()
method, so you cannot set error message directly in your XML, which is bit odd knowing errorEnabled
is there. But this ommision can be easily fixed by creating Binding Adapter that would fill the gap and provide missing implementation. Something like this:
@BindingAdapter("app:errorText") public static void setErrorMessage(TextInputLayout view, String errorMessage) { view.setError(errorMessage); }
See official binding docs, section "Attribute Setters" especially "Custom Setters".
EDIT
Possibly dumb question, but where should i put this? Do I need to extend
TextInputLayout
and put this in there?
It's actually not a dumb question at all, simply because you cannot get complete answer by reading the official documentation. Luckily it is pretty simple: you do not need to extend anything - just put that method anywhere in your projects. You can create separate class (i.e. DataBindingAdapters
) or just add this method to any existing class in your project - it does not really matter. As long as you annotate this method with @BindingAdapter
, and ensure it is public
and static
it does not matter what class it lives in.
I have made a binding like my answer on How to set error on EditText using DataBinding Framwork MVVM. But this time it used TextInputLayout as sample, like the previous one.
Purposes of this idea:
Of course, you can make you own validation and set it using the <variable>
tag in xml
First, implements the static binding method and the related String validation rules for preparation.
Binding
@BindingAdapter({"app:validation", "app:errorMsg"}) public static void setErrorEnable(TextInputLayout textInputLayout, StringRule stringRule, final String errorMsg) { }
StringRule
public static class Rule { public static StringRule NOT_EMPTY_RULE = s -> TextUtils.isEmpty(s.toString()); public static StringRule EMAIL_RULE = s -> s.toString().length() > 18; } public interface StringRule { boolean validate(Editable s); }
Second, put the default validation and error message in the TextInputLayout and make it easy to know the validation, binding in xml
<android.support.design.widget.TextInputLayout android:id="@+id/imageUrlValidation" android:layout_width="match_parent" android:layout_height="wrap_content" app:validation="@{Rule.NOT_EMPTY_RULE}" app:errorMsg='@{"Cannot be empty"}' > <android.support.design.widget.TextInputEditText android:id="@+id/input_imageUrl" android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="Image Url" android:text="@={feedEntry.imageUrl}" /> </android.support.design.widget.TextInputLayout>
Thirdly, when the click button trigger, you can use the predefined id in TextInputLayout(e.g. imageUrlValidation) to do the final validation on activity
Button button = ((AlertDialog) dialog).getButton(AlertDialog.BUTTON_POSITIVE); button.setOnClickListener(view1 -> { // TODO Do something //to trigger auto error enable FeedEntry inputFeedEntry = dialogFeedEntryBinding.getFeedEntry(); Boolean[] validations = new Boolean[]{ dialogFeedEntryBinding.imageUrlValidation.isErrorEnabled(), dialogFeedEntryBinding.titleValidation.isErrorEnabled(), dialogFeedEntryBinding.subTitleValidation.isErrorEnabled() }; boolean isValid = true; for (Boolean validation : validations) { if (validation) { isValid = false; } } if (isValid) { new AsyncTask<FeedEntry, Void, Void>() { @Override protected Void doInBackground(FeedEntry... feedEntries) { viewModel.insert(feedEntries); return null; } }.execute(inputFeedEntry); dialogInterface.dismiss(); } });
The complete code is following:
dialog_feedentry.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"> <data> <import type="com.example.common.components.TextInputEditTextBindingUtil.Rule" /> <variable name="feedEntry" type="com.example.feedentry.repository.bean.FeedEntry" /> </data> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:padding="10dp" android:orientation="vertical"> <android.support.design.widget.TextInputLayout android:id="@+id/imageUrlValidation" android:layout_width="match_parent" android:layout_height="wrap_content" app:validation="@{Rule.NOT_EMPTY_RULE}" app:errorMsg='@{"Cannot be empty"}' > <android.support.design.widget.TextInputEditText android:id="@+id/input_imageUrl" android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="Image Url" android:text="@={feedEntry.imageUrl}" /> </android.support.design.widget.TextInputLayout> <android.support.design.widget.TextInputLayout android:id="@+id/titleValidation" android:layout_width="match_parent" android:layout_height="wrap_content" app:validation="@{Rule.NOT_EMPTY_RULE}" app:errorMsg='@{"Cannot be empty"}' > <android.support.design.widget.TextInputEditText android:id="@+id/input_title" android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="Title" android:text="@={feedEntry.title}" /> </android.support.design.widget.TextInputLayout> <android.support.design.widget.TextInputLayout android:id="@+id/subTitleValidation" android:layout_width="match_parent" android:layout_height="wrap_content" app:validation="@{Rule.NOT_EMPTY_RULE}" app:errorMsg='@{"Cannot be empty"}' > <android.support.design.widget.TextInputEditText android:id="@+id/input_subtitle" android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="Subtitle" android:text="@={feedEntry.subTitle}" /> </android.support.design.widget.TextInputLayout> </LinearLayout> </layout>
TextInputEditTextBindingUtil.java
package com.example.common.components; import android.databinding.BindingAdapter; import android.support.design.widget.TextInputLayout; import android.text.Editable; import android.text.TextUtils; import android.text.TextWatcher; /** * Created by Charles Ng on 7/9/2017. */ public class TextInputEditTextBindingUtil { @BindingAdapter({"app:validation", "app:errorMsg"}) public static void setErrorEnable(TextInputLayout textInputLayout, StringRule stringRule, final String errorMsg) { textInputLayout.getEditText().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) { textInputLayout .setErrorEnabled(stringRule.validate(textInputLayout.getEditText().getText())); if (stringRule.validate(textInputLayout.getEditText().getText())) { textInputLayout.setError(errorMsg); } else { textInputLayout.setError(null); } } }); textInputLayout .setErrorEnabled(stringRule.validate(textInputLayout.getEditText().getText())); if (stringRule.validate(textInputLayout.getEditText().getText())) { textInputLayout.setError(errorMsg); } else { textInputLayout.setError(null); } } public static class Rule { public static StringRule NOT_EMPTY_RULE = s -> TextUtils.isEmpty(s.toString()); public static StringRule EMAIL_RULE = s -> s.toString().length() > 18; } public interface StringRule { boolean validate(Editable s); } }
FeedActivity.java
public class FeedActivity extends AppCompatActivity { private FeedEntryListViewModel viewModel; @SuppressLint("StaticFieldLeak") @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_feed); viewModel = ViewModelProviders.of(this) .get(FeedEntryListViewModel.class); viewModel.init(this); ViewPager viewPager = findViewById(R.id.viewpager); setupViewPager(viewPager); // Set Tabs inside Toolbar TabLayout tabs = findViewById(R.id.tabs); tabs.setupWithViewPager(viewPager); Toolbar toolbar = findViewById(R.id.toolbar); setSupportActionBar(toolbar); FloatingActionButton fab = findViewById(R.id.fab); fab.setOnClickListener(view -> { //insert sample data by button click final DialogFeedentryBinding dialogFeedEntryBinding = DataBindingUtil .inflate(LayoutInflater.from(this), R.layout.dialog_feedentry, null, false); FeedEntry feedEntry = new FeedEntry("", ""); feedEntry.setImageUrl("http://i.imgur.com/DvpvklR.png"); dialogFeedEntryBinding.setFeedEntry(feedEntry); final Dialog dialog = new AlertDialog.Builder(FeedActivity.this) .setTitle("Create a new Feed Entry") .setView(dialogFeedEntryBinding.getRoot()) .setPositiveButton("Submit", null) .setNegativeButton("Cancel", (dialogInterface, i) -> dialogInterface.dismiss()) .create(); dialog.setOnShowListener(dialogInterface -> { Button button = ((AlertDialog) dialog).getButton(AlertDialog.BUTTON_POSITIVE); button.setOnClickListener(view1 -> { // TODO Do something //to trigger auto error enable FeedEntry inputFeedEntry = dialogFeedEntryBinding.getFeedEntry(); Boolean[] validations = new Boolean[]{ dialogFeedEntryBinding.imageUrlValidation.isErrorEnabled(), dialogFeedEntryBinding.titleValidation.isErrorEnabled(), dialogFeedEntryBinding.subTitleValidation.isErrorEnabled() }; boolean isValid = true; for (Boolean validation : validations) { if (validation) { isValid = false; } } if (isValid) { new AsyncTask<FeedEntry, Void, Void>() { @Override protected Void doInBackground(FeedEntry... feedEntries) { viewModel.insert(feedEntries); return null; } }.execute(inputFeedEntry); dialogInterface.dismiss(); } }); }); dialog.show(); }); } }
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