Kotlinで書いたアプリにData Bindingを導入してみたので、そのメモとなります。ButterKnifeの代わりに使ったり、XML側に動的な値を設定できる便利なやつですね。
準備
- build.gradleでdataBindingを有効にします。
android {
・・・
dataBinding {
enabled = true
}
}
- こちらの記事を参考にkaptでcompilerを追加します。これがないとDataBindingで生成された中間クラスがKotlinから見えません。
使い方
- bindingさせたいViewのレイアウトファイルを開いて以下のようにlayoutタグで全体を囲みます。この状態でビルドするとBinding用のファイルが作成されます。
activity_main.xml
ならActivityMainBinding
クラスが生成されます。
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
// もともとのLayout
</layout>
- Kotlin側ではJavaで書く時と同じように使えます。
Activityで使う場合
Activity.kt
override fun onCreate(savedInstanceState: Bundle?) {
val binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
// binding経由でViewを参照できる
binding.hogeButton.setOnClickListener { v ->
// ボタンを押した時の処理
}
}
Fragmentで使う場合
Fragment.kt
override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View? {
// ジェネレートされたBindingクラスからViewをinflateできる
val binding = FragmentListBinding.inflate(inflater, container, false)
// このようにも書ける
// val binding = DataBindingUtil.inflate<FragmentListBinding>(inflater, R.layout.fragment_list, container, false)
...
// 返すのはbindingではなくview
return binding.root
}
ListのAdapter内で使う場合
Adapter.kt
override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View? {
return convertView?.apply {
// Viewが生成済なら使い回す
val binding = DataBindingUtil.getBinding<ListItemBinding>(this)
bindEntity(binding, getItem(position))
} ?: let {
// Viewが未生成なら新しくbindingを作成してからViewを返す
val binding = ListItemBinding.inflate(mLayoutInflater, parent, false)
bindEntity(binding, getItem(position))
return binding.root
}
}
private fun bindEntity(binding: ListItemBinding , entity: Entity) {
// 画面に表示する
}
Kotlin側から値をbindする
- このユーザー情報の名前を表示するという体
UserEntity
data class UserEntity(val name: String)
fragment.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:bind="http://schemas.android.com/tools">
<!-- bindさせたいクラスを指定する -->
<data>
<variable
name="userEntity"
type="me.rei_m.sample.entities.UserEntity" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<!-- dataで定義したインスタンスを参照できる。いまのところIDE上ではサジェストされないので `userEntity.name` を手打ちする必要はある -->
<android.support.v7.widget.AppCompatTextView
android:id="@+id/fragment_text_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{userEntity.name + " さん"}" />
</LinearLayout>
</layout>
Fragment.kt
override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val binding = FragmentBinding.inflate(inflater, container, false)
// xmlのdataタグに定義したプロパティが生えているので値をセットする
binding.userEntity = UserEntity(name = "hogehoge")
...
return binding.root
}
- これでFragment側のTextViewには
hogehoge さん
が表示されるようになります。今までのようにクラス側で表示したいViewを探してtext属性にセットみたいなことはやらなくてよくなります。
xml内で別のxmlをincludeしている場合
- includeする側でidをふればBindingから参照できるようになります。また、includeされる側もlayoutタグで囲んでおけば同様にBindingクラスがジェネレートされます。下の例だと
binding.layoutSubView
で参照できるようになってlayoutSubViewの型はLayoutSubViewBinding
になります - includeにbind属性をつけると読み込まれる側にbindできるのでKotlin側でDataをセットする手間が省けます。includeする側とされる側で同じデータを扱う場合はこのように書いたほうがいいでしょう。
fragment.xml
・・・
<include
android:id="@+id/layout_sub_view"
layout="@layout/layout_sub_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
bind:userEntity="@{userEntity}" />
・・・
書いていて困ったこと
### xml側でstaticメソッドが使えなかった
-
これ出来たので修正しました。
-
campanion objectに生やす形だとJavaからは○○クラスのCampanionに生えている形になるので見えません。
class HogeUtil private constructor() {
// これはxmlから見えない
companion object {
fun fuga() {
・・・
- object構文にJvmStaticアノテーションをつけた関数を生やしてxmlでimportすると見えるようにります。BindingAdapterも同様の形式でいけました。
object HogeUtil {
// これはxmlから見える
@JvmStatic
fun fuga() {
・・・
### xml側で条件式が使えなかった
- 同じく公式のコードから引用してますが、bindしたDataのプロパティに応じて表示、非表示を切り替えるみたいなコードを書いたのですが、ビルドは通りませんでした。
- こちらで解消するみたいです。
ビルドが不安定?
- これが一番不思議(自分の理解不足なだけかも。。。)なのですが、ビルドが安定しませんでした。具体的に起きた事象としては生成されたはずのBindingクラスがビルド後にAndroid Studio上で見えたり見えなくなったりしました。具体的には以下のパターン。
- 生成されたBindingクラスのinflateを使おうとするとinflateメソッド自体が見えなくなっていて、参照できるのは親クラスのViewDataBindingのメソッド・プロパティだけになる。DataBindingUtilのinflateは怒られない。
- DataBindingUtilのinflateすらできなくなる。生成されたクラスそのものが見えない。
- 上記の現象は何回かビルドを繰り返したり、buildディレクトリをまるごと削除してAndroidStudioを再起動してビルドし直したりすると直りました。この辺の問題が自分だけの問題かがわからないのですが知見を持っている方がいればコメントいただけると幸いです。