Android DataBinding の使い方
codelab android databinding
Android DevSummit 2019 の codelabs が google codelabs に追加されていたので、 DataBinding を改めて勉強しました。
そもそも DataBinding を使う理由としては、 codelabs には以下のように書かれています。
findViewById()
を使うのはよくない- Button の
android:onClick
attribute に直接 onClick 時に実行する method 名を書くのは良くない- コンパイル時に例外検査がないので実行時にクラッシュしてしまう
- また、 rename 時に見逃しやすい
そのため、 DataBinding を使ってない場合は、現在動いているコードは積極的に書き換えなくても良い気がしますが、新たに追加する画面などでは DataBinding を使っていく感じにしたほうが良さそうです。
DataBinding 使用方法
build.gradle
build.gradle
に以下のコードを追加して dDataBinding を有効化します
android { ... dataBinding { enabled true } }
layout xml
layout xml はルートタグが <layout>
タグであれば DataBinding が使用できます。
<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"> <androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" android:layout_height="match_parent"> ... </androidx.constraintlayout.widget.ConstraintLayout> </layout>
AndroidStudio では、一番上の親要素にカーソルを合わせると以下の画像のようにダイアログが出てくるので Convert to data binding layout
を選択すると自動的に DataBinding が使用できる状態に変換してくれます。
Activity で使うには onCreate 内の setContentView() を以下のように置き換えます
class MainActivity : AppCompatActivity() { private lateinit var binding: ActivityMainBinding override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = DataBindingUtil.setContentView(this, R.layout.activity_main) } }
これで DataBinding を使用する準備はできました。
レイアウト変数/レイアウト式
DataBinding を使用すると xml 上で変数と式を使用することができるようになります。
- 変数は data タグ内で宣言します
- data タグ内では import 文を書くことが出来て、 import するとレイアウト式で import したクラスのメソッドが使用することができます。
- レイアウト式を使うには
android:text=@{text}
という形で書きます。 - レイアウト式ではラムダ式も使えるため、
android:onClick="@{() -> viewModel.onClick()}"
という形で書くことが出来ます。
<layout> <data> <import type="android.view.View" /> <variable name="viewModel" type="net.nshiba.databindingsample.MainViewModel" /> </data> <androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{viewModel.name + String.valueOf(viewModel.age)}" android:visibility="@{viewModel.age < 13 ? View.GONE : View.VISIBLE}" android:onClick="@{() -> viewModel.onClick()}" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout> </layout>
あとは Activity 側でデータを設定してあげるだけです。
class MainActivity : AppCompatActivity() { private lateinit var binding: ActivityMainBinding private val viewModel: MainViewModel by viewModels() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = DataBindingUtil.setContentView(this, R.layout.activity_main) ここで viewModel を設定 binding.viewModel = viewModel } }
またここでは、 ViewModel を使用しています。
ViewModel を使用するとデータを View に反映する前段階の処理と状態の保持を一箇所にまとめることができます。
ちなみにここで使っている MainViewModel の中身は以下の感じです。
class MainViewModel : ViewModel() { val name: String = "nshiba" val age: Int = 15 fun onClick() { // do nothing } }
注意点
DataBinding はとても便利です。 しかし、 layout xml に複雑なロジックを書いてしまうと、可読性とメンテナンス性が下がってしまうためバランスをとって書きましょう
LiveData
LiveData を使うとデータの変更を監視して、変更があったら自動的にUIに反映させることが可能です。
使い方は簡単で、以下のように MutableLiveData
を使って値を宣言し layout に設定するだけです。
class MainViewModel(): ViewModel() { ... private val likes = MutableLiveData(0) fun onClick() { likes.value = (likes.value ?: 0) + 1 } }
... <TextView android:id="@+id/likes" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="16dp" android:text="@{String.valueOf(viewModel.likes)}" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toBottomOf="@id/button" /> ...
また LiveData は LifeCycle を知っている必要があるので Activity/Fragment で DataBinding に対して LifeCycleOwner を設定してあげる必要があります
override fun onCreate(savedInstanceState: Bundle?) { ... binding.lifecycleOwner = this }
こうすると、最初に作った Button のクリックリスナーに viewMode.onClick()
が設定されているため、ボタンを押すと like が増えていくようになりました。
他にも LiveData には Transformations.map()
というメソッドを使うと、データに変更があったらそのデータを加工して違うデータにすることが可能です。
class MainViewModel(): ViewModel() { ... val popularity = Transformations.map(likes) { when { it > 9 -> "START" it > 4 -> "POPULAR" else -> "NORMAL" } } ... }
BindingAdapter
BindingAdapter を使うと簡単にカスタム attributes を作ることができます。
まず、 BindingAdapter を使うには build.gradle に apply plugin: 'kotlin-kapt'
を追記してください。
次に BindingExt.kt
ファイルを作り @BindingAdapter
annotation がついたメソッドを作ります。
@BindingAdapter("app:visibleFromLike") fun View.setVisibleFromLike(likes: Int) { visibility = if (likes < 4) { View.GONE } else { View.VISIBLE } }
@BindingAdapter
に指定した引数を layout xml で使用することができます
<TextView android:id="@+id/popularity" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="16dp" android:text="@{viewModel.popularity}" app:visibleFromLike="@{viewModel.likes}" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toBottomOf="@id/likes" />
これで lives < 4
のときに現れる View を作ることができました。
また、 BindingAdapter は複数の attributes を設定することができます
@BindingAdapter(value = ["app:visibleFromLike", "app:visibleFromLikeMax"], requireAll = false) fun View.setVisibleFromLike(likes: Int?, max: Int?) { ... }
value に設定する項目を配列にして、メソッドの引数を配列のサイズと同じにします。
ここで requireAll = true
を指定すると、すべての attributes が View に設定されていなければコンパイルエラーが発生するようになります。
まとめ
DataBinding を使うと Activity/Fragment 側で UI 変更のコードがなくなりすっきりしていいですね。
Android アプリ開発において DataBinding を使うかどうかは結構好みが分かれると思いますが、がっつり使うとかなり便利になるので個人的にはおすすめです。
ただ、 findViewById()
の置き換えとして使うだけだとビルド時間も伸びるので、その場合は ViewBinding を使うほうが良いと思います。
DataBinding についてもっと詳しくやりたい方は、とりあえず実際に codelabs をやってみると良いと思います。
https://codelabs.developers.google.com/codelabs/android-databinding/#0
今回作成したサンプルプロジェクトはこちらです。
https://github.com/nshiba/DataBindingSample