Android の Data Binding (Beta Release) を使う

More than 3 years have passed since last update.

注意 本稿は、2015-06-23 現在の Data Binding のドキュメントをもとに書いてあります。仕様や挙動は将来変更される可能性があるので気をつけてください。


Data Binding

Google I/O 2015 で M Preview が発表され、他にも Android の新機能が発表されました。そのうちの1つが Data Binding です。

この Data Binding は、名前の通り、メモリ上のデータを XML で表されている View に結び付けるための仕組みです。

これまで、たとえばリストを表示するには、プログラム上で各 View とデータを結び付けるという作業が必要でした。今回の機能を利用することで、そのような処理を XML 内で宣言的に書けるようになります。


参考文献


インストール

Data Binding を利用するためには build.gradle に下記を記述します。


build.gradle

dependencies {

classpath "com.android.tools.build:gradle:1.3.0-beta1"
classpath "com.android.databinding:dataBinder:1.0-rc0"
}

また jcenter がレポジトリに追加されていることも確認しておきましょう。


build.gradle

allprojects {

repositories {
jcenter()
}
}

各モジュールの build.gradle では以下の記述が必要です。


app/build.gradle

apply plugin: com.android.application'

apply plugin: 'com.android.databinding'


前提

gradle プラグインのバージョンは 1.3.0-beta1 以上である必要があります。また Android Studio 1.3 以上でないとコード補完やレイアウト表示がサポートされていません。

上記のサポートライブラリがコンパイル時に入っていれば、ランタイム時の Android のバージョンに制限はありません。


基本的な使い方

ここでは、POJO の User 型オブジェクトを表示する例を考えます。まず、レイアウトファイルを記述します。


レイアウトファイル

layout 要素をルートにして、直下に data 要素と通常のビューのルート要素を記述します。data 要素には使用するデータオブジェクトとその型を記述します。


src/main/res/layout/activity_main.xml

<?xml version="1.0" encoding="utf-8"?>

<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="user" type="com.example.User"/>
</data>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.firstName}"/>
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.lastName}"/>
</LinearLayout>
</layout>

通常ならリソース ID やテキストを記述する箇所で @{user.firstName} のように User 型オブジェクトのフィールドを指定していることに注意してください。これによって、このビューにバインドした User オブジェクトの firstName フィールドの値が TextView に表示されます。


バインド

さて、次にデータオブジェクトを View にバインドします。バインドするには以下のコードを実行します。


src/main/java/com/example/MainActivity.java

@Override

protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
User user = new User("Test", "User");
binding.setUser(user);
}

以上で終わりです。MainActivity を起動してみれば、表示を確認できるでしょう。


レイアウトファイル内の式

レイアウトファイル内では、@{...} という形式でバインドされるデータの値を記述しますが、ここには式を記述することが可能です。


使用可能な演算子


  • 数値演算子 + - / * %

  • 文字列連結 +

  • 論理演算子 && ||

  • ビット演算子 & | ^

  • 単項演算子 + - ! ~

  • シフト演算子 >> >>> <<

  • 比較演算子 == > < >= <=

  • instanceof

  • グルーピング ()

  • リテラル: character, String, numeric, null

  • キャスト

  • メソッド呼び出し

  • フィールドアクセス

  • 配列アクセス []

  • 三項演算子 ?:


いくつか例を示します。式内でダブルクォート " を使うときは属性値をシングルクォート ' で囲むか、ダブルクォートを &quot; でエスケープするか、ダブルクォートの代わりにバッククォート ` を使うかします。

android:text="@{String.valueOf(index + 1)}"

android:visibility="@{age &lt; 13 ? View.GONE : View.VISIBLE}"
android:transitionName='@{"image_" + id}'
android:text="@{&quot;First: &quot; + user.firstName + `, Last: ` + user.lastName}"


Null 合体演算子 (Null Coalescing Operator)

Java では使えない Null 合体演算子 (Null Coalescing Operator)も利用可能です。

android:text="@{user.displayName ?? user.lastName}"

上記の例では、左の user.displayNamenull なら、右の user.lastName が使われます。


NullPointerException の回避

式内では NullPointerException が起きないようになっています。たとえば、下記の例で usernull の場合は、式全体の user.namenull になります。また、int 型の user.age はデフォルト値、すなわち 0 になります。

android:text="@{user.name}"

android:text="@{user.age}"


リスト、配列およびマップのアクセス

リスト、配列およびマップにアクセスするには [ ] 演算子を用います。

<data>

<variable name="list" type="java.util.List&lt;String>"/>
<variable name="sparse" type="android.util.SparseArray&lt;String>"/>
<variable name="map" type="java.util.Map&lt;String, String>"/>
<variable name="index" type="int"/>
<variable name="key" type="String"/>
</data>
...
android:text="@{list[index]}"
...
android:text="@{sparse[index]}"
...
android:text="@{map[key]}"


リソースの使用

@{...} 式内では、リソース ID を使用して、リソースにアクセスすることも可能です。また String フォーマットや Plurals リソースを使うこともできます。

android:padding="@{large? @dimen/largePadding : @dimen/smallPadding}"

android:text="@{@string/nameFormat(firstName, lastName)}"
android:text="@{@plurals/banana(bananaCount)}"

なお、通常の Plurals リソースを使う場合と同様に、Plurals リソース内に String フォーマットがある場合は、引数を二つ渡さなければなりません。

<plurals name="orange">

<item quantity="one">Have an orange.</item>
<item quantity="other">Have %d oranges.</item>
</plurals>

android:text="@{@plurals/orange(orangeCount, orangeCount)}"


Observable オブジェクト

実はデータバインディングでは、データオブジェクトを Observable にすることで、動的に View の更新をすることができます(ここで言う Observable は Rx の Observable とはまったくの無関係です)。

上記に示した手順では、たとえば、User オブジェクトの setFirstName(String) メソッドを呼んだときにバインドされている TextView 側に変更は通知されません。User クラスを Observable にすることで、その通知が可能になります。

任意のデータクラスを Observable にするには3つの方法があります。いずれかの方法を使うことで、データオブジェクトを更新したときに動的に View も更新されるようになります。


1. BaseObservable を継承させる

データクラスが BaseObservable を継承し、Setter に @Bindable アノテーションを、Getter 内で notifyPropertyChanged(View) を呼ぶことで、そのデータオブジェクトの変更が View に通知されるようになります。


User.java

private static class User extends BaseObservable {

private String firstName;
private String lastName;
@Bindable
public String getFirstName() {
return this.firstName;
}
@Bindable
public String getFirstName() {
return this.lastName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
notifyPropertyChanged(BR.firstName);
}
public void setLastName(String lastName) {
this.lastName = lastName;
notifyPropertyChanged(BR.lastName);
}
}


2. ObservableField を利用する

上記の方法はちょっと手間です。もっと簡単に行いたい場合は、ObservableField を使う方法もあります。ただし、この場合は変更を監視したいフィールドのクラスがすべて ObservableField になってしまいます。


User.java

private static class User extends BaseObservable {

public final ObservableField<String> firstName =
new ObservableField<>();
public final ObservableField<String> lastName =
new ObservableField<>();
public final ObservableInt age = new ObservableInt();
}


3. ObservableMap を利用する

もっと簡単に行いたい、マップで十分、という場合は ObservableMap を使うこともできます。


MainActivity.java

ObservableArrayMap<String, Object> user = new ObservableArrayMap<>();

user.put("firstName", "Google");
user.put("lastName", "Inc.");
user.put("age", 17);


View へのアクセス

Data Binding を使ってうれしいもう1つのところは、コード内での View へのアクセスが容易になるところです。以下のように、いままで findViewById(int) で View へのアクセスを得ていたところを、以下のようにできます。

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

binding.firstName.setText(... // <- TextView

これを使うと、カスタム Adapter 内で ViewHolder の代わりに使うことができます。


app/src/main/java/com/example/UserAdapter.java

    @Override

public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
final ItemArticleBinding binding = ItemArticleBinding.inflate(LayoutInflater.from(this.getContext()));
convertView = binding.getRoot();
convertView.setTag(binding);
}

final ItemArticleBinding binding = (ItemArticleBinding) convertView.getTag();
Article article = this.getItem(position);
binding.setArticle(article);
Picasso.with(this.getContext()).load(article.getUser().getProfileImageUrl()).into(binding.userIcon);
return convertView;
}



さらに深く利用するために

Data Binding では、さらに様々な機能が用意されています。たとえば、生成されるクラスやメソッド名を変更したり、カスタムバインディングを定義することで、データオブジェクトの画像 URL を XML 内で指定したら自動的に画像をロードするといったこともできます。

また、実際の利用については、すでにいくつか罠があることが知られています。それらについては、sys1yagi さんのブログに詳しくまとめられています。

参考: Data Binding カテゴリーの記事一覧 - visible true


Tips


  • Data Binding のエラーは分かりにくいので、もしエラーを見ても分からない場合は --debug オプションを付けましょう。


まとめ


  • コードを生成するだけなので、ランタイム時に Android のバージョンに依存しないのは素晴らしい

  • とりあえず、ButterKnife の代わりにはなりそう