日々是好日

プログラミングについてのあれこれ、ムダ知識など

MVVM 完全に理解した - 3

今回は Data Binding について。
といってもなかなか一筋縄ではいかないので、本記事ではデータをバインドするところまで。

ちょっと長いので目次付けてみる。

Data Binding Library の概要

https://developer.android.com/topic/libraries/data-binding/?hl=endeveloper.android.com

The Data Binding Library is a support library that allows you to bind UI components in your layouts to data sources in your app using a declarative format rather than programmatically.

うーん、一文が長い←
実例で見てみます。

findViewById<TextView>(R.id.sample_text).apply {
    text = viewModel.userName
}

よくある UI ( TextView ) への値のセットですね。Kotlin だからスコープ関数( apply )使ってますが。
これが Data Binding を使うと───

<TextView
    android:text="@{viewmodel.userName}" />

こう書けるよと。むむ。
@{ } 構文で ViewModel オブジェクトを参照できる、みたいな感じ。

Binding components in the layout file lets you remove many UI framework calls in your activities, making them simpler and easier to maintain.

レイアウトファイルにコンポーネントをバインドさせることで、Activity から UI に関係するメソッドコールを除外することができ、簡潔かつメンテもしやすくなるよと。すごい。

ライブラリの導入

app モジュールの build.gradle を開き、次の項目を追記します。

android {
    ...
    dataBinding {
        enabled = true
    }
}

たったこれだけ。

レイアウトファイルへの記述

ここからはこっちのページ。

https://developer.android.com/topic/libraries/data-binding/expressionsdeveloper.android.com

バインドする場合、レイアウトファイルの書き方が通常のそれとは異なってきます。

<?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
       ...
       <TextView android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:text="@{user.firstName}"/>
       ...
   </LinearLayout>
</layout>

Data binding layout files are slightly different and start with a root tag of layout followed by a data element and a view root element. This view element is what your root would be in a non-binding layout file.

ポイントは次のとおり。

  1. ルートタグは layout
  2. layout の直下に data 要素を記述。namexml 内で使う名称、type がバインドするクラス
  3. data 要素の次に通常の Layout を記述
  4. @{ } 構文を用いて、プロパティ参照みたいな書き方で値を取得

上記の例では User という Data Class をバインドしているようです。
"Data binding layout file" と "non-binding layout file" とで、そもそもレイアウトファイルが区別されてるっぽい。
生成後は通常不変なクラスである Data Class をバインドしていることに違和感を禁じえませんが、とりあえずここは次へ行きます。

データをバインドする

では Activity 側の記述を見てみます。

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    val binding: ActivityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main)
    //または次の書き方も可
    val binding: ActivityMainBinding = DataBindingUtil.inflate(getLayoutInflater())

    binding.user = User("Test", "User")
}

まず急に出てきた ActivityMainBinding クラスは、"Data binding layout file" を作ると自動生成されます。layoutdata 要素もった xml ね。
場所はココ↓

f:id:kcpoipoi:20181203223131p:plain
Binding クラスの場所

もし作られてなかったらリビルドしてみてください。
Data Binding file を扱う場合、この DataBindingUtil.setContentView または inflate を使って View を生成します。

そしてこの返り値である xxxBindig は、name で定義したプロパティ*1をもっており、上記では user というプロパティが参照できます!天才かこれ!

これで xml ファイルで定義したデータと実際の処理の記述が紐付けられました。
生成方法からして、もし data 要素を修正した場合はリビルド必須ですね。バインディングでバグったらとりあえずやってみましょう。

ダメ押しで Fragment, ListView, RecyclerView adapter の場合

Fragment, ListView, RecyclerView のアイテムにデータバインディングを使う場合は、次のように記述します。

val listItemBinding = ListItemBinding.inflate(layoutInflater, viewGroup, false)
// or
val listItemBinding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false)

バインディングクラスの inflate か、DataBindingUtil.inflate か。お好みでどうぞ。

とりあえずまとめ

不変なデータクラスの Binding がメインでしたが、表面だけ Data Binding 理解した!
次は双方向データバインディングと、そろそろアプリ考え始める。

*1:正確には getUser/setUser メソッドです。Kotlinだとプロパティっぽく呼べる