web-dev-qa-db-ja.com

エラーメッセージをTextInputLayoutにバインドできますか?

エラーメッセージを直接Android.support.design.widget.TextInputLayoutにバインドしたいと思います。レイアウトでエラーを設定する方法が見つかりません。これは可能ですか?

これは私がそれが機能することを想像した方法です:

<?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>
24
Theyouthis

この回答を書いている時点では、setError()メソッドに対応するXML属性がないため、XMLに直接エラーメッセージを設定することはできません。これは、errorEnabledが存在することを知っているのは少し奇妙です。しかし、この不作法は、ギャップを埋め、不足している実装を提供する Binding Adapter を作成することで簡単に修正できます。このようなもの:

@BindingAdapter("app:errorText")
public static void setErrorMessage(TextInputLayout view, String errorMessage) {
   view.setError(errorMessage);
}

公式バインディングドキュメント の「属性セッター」セクション、特に「カスタムセッター」を参照してください。


[〜#〜]編集[〜#〜]

おそらくばかげた質問ですが、これはどこに置くべきですか? TextInputLayoutを拡張して、そこに配置する必要がありますか?

公式のドキュメントを読んで完全な答えを得ることができないからといって、実際にはまったくばかげた質問ではありません。幸いなことに、これは非常に単純です。何も拡張する必要はありません。そのメソッドanywhereをプロジェクトに追加するだけです。個別のクラス(つまりDataBindingAdapters)を作成するか、このメソッドをプロジェクト内の既存のクラスに追加するだけでかまいません。実際には問題ありません。このメソッドに@BindingAdapterの注釈を付ける限り、publicおよびstaticであることを確認してくださいそれが住んでいるクラス。

44
Marcin Orlowski

DataBinding Framwork MVVMを使用してEditTextにエラーを設定する方法 のように、バインディングを作成しました。しかし今回は、前の例と同様に、TextInputLayoutをサンプルとして使用しました。

このアイデアの目的:

  1. XMLをできるだけ読みやすく独立したものにする
  2. アクティビティ側の検証とXML側の検証を個別に行う

もちろん、独自の検証を作成し、xmlの<variable>タグを使用して設定することができます

最初に、準備のために静的バインディングメソッドと関連する文字列検証ルールを実装します。

バインド

  @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);
  }

2番目に、デフォルトの検証とエラーメッセージをTextInputLayoutに入れて、検証をわかりやすくし、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>

3番目に、クリックボタンがトリガーされたときに、TextInputLayoutの事前定義されたIDを使用して(imageUrlValidationなど)、アクティビティの最終検証を行うことができます

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_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();

    });
  }
}
4
Long Ranger