Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can i bind an error message to a TextInputLayout?

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> 
like image 828
BillHaggerty Avatar asked May 06 '16 15:05

BillHaggerty


People also ask

How do I show error in TextInputLayout?

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.

How do I remove TextInputLayout error?

Now you can simply do input. setError(..) for new error and input. setErrorEnabled(false) to remove it.

How do I get TextInputLayout from text?

Just use: TextInputLayout textInputLayout = findViewById(R. id. custom_end_icon); String text = textInputLayout.


2 Answers

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.

like image 182
Marcin Orlowski Avatar answered Oct 20 '22 10:10

Marcin Orlowski


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:

  1. Make the xml as readable as possible and independent
  2. Make the activity-side validation and xml-side validation independently

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();      });   } } 
like image 34
Long Ranger Avatar answered Oct 20 '22 10:10

Long Ranger