Data BindingはViewシステムを用いている場合に、Viewの表示と状態(データ)を結合する仕組みです。
結合とは「状態の変化に連動して、表示を更新する」ことです。
Googleは「利点がある」と述べています。しかし、私は利点に感じないので、積極的な利用をしていません。
Googleが提供するAndroidアプリのサンプルで頻繁に登場し、よく見かけます。
ですので、ここに備忘録として、まとめます。
※環境:Android Studio Ladybug Feature Drop | 2024.2.2
Kotlin 2.0.0
(Viewシステムのプロジェクトは1.9.24が選ばれる)
(Composeのプロジェクトは2.0.0が選ばれる)
目次
Data Vindingとは
Data BindingはViewシステム(レイアウトファイルで画面構成を定義する環境)を用いている場合に、Viewの表示と状態(データ)を結合する仕組みです。
結合とは「状態の変化に連動して、表示を更新する」ことです。
AAC(Androidアーキテクチャコンポーネント)の「UIの分離」を意図したプログラミング手法です。
Data Bindingあり・なしを比較すれば、違いと利点がわかると思います。
Data Bindingなし(UIとロジックの関係が密)
表示を更新する場合、ロジック側は対象のViewを把握して、そのView固有のセッター関数で、新たな状態を設定する必要があります。
ロジック側でfindViewByIdを用いて、Viewのインスタンスを取得するのは、そのためです。
UIとロジックの関係はとても「密(みつ)」になります。
しかも、Viewの操作はメインスレッドで行う必要があります。
Data Bindingなし
Data Bindingあり(UIとロジックの関係が疎)
状態を保持する変数はUI側に持ちます。状態はViewと結合されていて、状態の変化が直ちにViewの表示に反映されます。
表示を更新する場合、ロジック側は状態を保持する変数へ、新たな状態を書き込むだけです。対象のViewを把握する必要はありません。
UIとロジックの関係は「疎(そ)」になります。
しかも、Viewの操作を行うわけでは無いので、状態の書き込みはワーカースレッドで行うことができます。
Data Bindingあり
環境設定
Data Bindingを利用するための環境設定は、以下の3つが必要です。
- (1)Data Bindingの有効化
- (2)Viewシステムの採用
- (3)レイアウトファイルの変換
特に「(3)レイアウトファイルの変換」を行わないと、(1)と(2)が出来ていても、機能(Bindingの動作)は無反応です。注意して下さい。
Data Bindingの有効化
Data Bindingはモジュール毎に有効・無効を指定します。
有効化の設定
有効にするために、「app/build.gradle」へ次の設定を行います。
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
id("org.jetbrains.kotlin.kapt")
}
android {
...
buildFeatures {
dataBinding = true
}
...
}
Data BindingはKapt(Kotlin Anotation processing tools)を使います。Ksp(Kotlin Symbole Processor)はサポートしていないので、使えません。
Kotlin 2の対応
現在(2025.01)、Kotlinは1.9.xから2.0.xへ移行を果たしています。それに伴い、KaptもKspへ移行することが推奨されています。KaptはKotlin 1.9.x世代のプラグインなのです。
ですが、下記の設定をgradele.propertiesへ行うと、KaptはKotlin 2をサポートします。
: : # KapeのKotlin 2対応を有効化 kapt.use.k2=true
ただし、「サポートは実験段階です。評価目的のみに使用してください。」とドキュメントに書かれています。
上記の事項が承諾できないならば、Kotlinを1.9.xに戻してData Bindingをも用いる必要があります。
ちなみに、最新版のKaptはメンテナンス モードという位置づけでリリースされています。今後、新機能を実装する予定はなく、KotlinやJavaのバージョンアップに対応するのみになるようです。「いつまで対応が続くか?!」は不明です。
Viewシステムの採用
Data BindingはViewシステムを用いている場合の機能です。
現状はJetpack Composeによる開発が主流になっているので、Viewシステムを意図的に選択しなければなりません。
新規プロジェクトの作成時に、「Views Activity」を選んでください。

レイアウトファイルの変換
レイアウトファイル(XXX.xml)をData Binding対応へ変換します。
<layout>タグで全体を囲み、レイアウトファイル中のルートViewと並列に<data>タグを設けてください。
<?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>
<!-- ここにBinding変数を定義 -->
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/txtMesg"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
android:textAllCaps="false"
android:textSize="48sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
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"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/txtMesg"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
android:textAllCaps="false"
android:textSize="48sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
この変換作業ですが、Android Studioに自動変換する機能が付いています。こちらを使うと簡単です。

Binding動作
Data Bindingの有効化が行われると、Android Studioはレイアウトファイルを認識する際に、Bindingクラス(例:ActivityMainBinding)を自動生成します。


Bindingクラスのクラス名は、レイアウトファイル名(例:activity_main.xml)を図のように変換したものになります。

Bindingクラス#infulate()を実行すれば、BindingクラスのプロパティにBinding変数のインスタンスが登録されます。※方法2のやり方もあります。
そして、そのプロパティを使って、Binding変数へアクセスできるようになります。
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root) // トップViewをコンテンツとして指定
binding.message = "Hello Android!" // Binding変数(message)の参照
binding.color = Color.WHITE // Binding変数(color)の参照
// DataBindingUtilを用いた方法
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
binding.message = "Hello Android!" // Binding変数(message)の参照
binding.color = Color.WHITE // Binding変数(color)の参照

状態(データ)の結合
Binding変数は<data>タグ内の<variable>タグで定義します。「name」が変数名、「type」が変数の型です。
Binding変数はロジック側からのアクセスが監視され、書き込みが行われたらViewの関数を実行するように、Viewシステムが制御します。

Binding変数と実行するViewの関数は、図のように関連付けられます。
右辺がBinding式(”@{“と”}”で囲まれた式)の場合は、左辺をViewの関数として扱います。その他の場合はViewの属性として扱います。

Viewの関数は図のようにBinnding式の評価結果を引数にして実行されます。
Binding式は構文があるので、詳細は「レイアウトとバインディング式/式言語」を参照してください。

この時、レイアウト中に記載する関数名は省略形になっています。省略形のルールは図の通りです。

プリミティブ型
Binding変数がプリミティブ型である場合の例です。
<?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>
<variable name="message" type="String" />
<variable name="color" type="Integer" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/txtMesg"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{message}"
android:textAllCaps="false"
android:textColor="@{color}"
android:textSize="34sp"
... />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
...
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.message = "Hello Android!"
binding.color = Color.WHITE
// 別スレッドで5秒後にWHITE->GREENへ変更
lifecycleScope.launch(Dispatchers.Default) {
delay(5000) // 5秒待つ
binding.color = Color.GREEN // WHITE->RED
}
}
非同期(5秒後)にWHITE⇒GREENへ変更しています。非同期であってもData Bindingは問題なく動作します。

一般的なクラス
Binding変数が一般的なクラスである場合の例です。
型はフルパス(パッケージ名全体)を書かなければなりません。
data class Message (
var text: String = "Hello Android !",
@ColorInt var color: Int = Color.WHITE
)
<?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>
<variable name="message" type="com.example.sample.Message" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/txtMesg"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{message.text}"
android:textAllCaps="false"
android:textColor="@{message.color}"
android:textSize="34sp"
... />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
...
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.message = Message(color = Color.WHITE)
// 別スレッドで5秒後にWHITE->GREENへ変更
lifecycleScope.launch(Dispatchers.Default) {
delay(5000) // 5秒待つ
binding.message = Message(color = Color.GREEN) // WHITE->GREEN(OK)
}
}
非同期(5秒後)にWHITE⇒GREENに変更しています。非同期であってもData Bindingは問題なく動作します。
実行結果は「プリミティブ型」と同じです。省略します。
Binding変数の書き込みの有無により、表示が変更される場合とされない場合があるので注意が必要です。
lifecycleScope.launch(Dispatchers.Default) {
delay(5000) // 5秒待つ
binding.message?.let { // WHITE->GREEN(NG)
it.color = Color.GREEN
}
}
lifecycleScope.launch(Dispatchers.Default) {
delay(5000) // 5秒待つ
val _message = binding.message?.apply { // WHITE->GREEN(OK)
color = Color.GREEN
}
binding.message = _message
}
ちなみに、Binding変数はNullalbe(Null許容型)で、初期値はNullです。
※Binding式でNullの場合の代替値が指定できます(”??”を使う、Binding式の構文のドキュメント参照)。
イベントの結合
イベントハンドラー(実行される関数)を関数オブジェクトの形でリスナーへ登録します(setOnClockListener関数などを用いる)。
ただし、Binding変数の型に関数オブジェクトの型は指定できません。ですので、イベントハンドラーが属するクラスを型にします。
class Handlers {
fun clickedMesg(v: View) { // ⇐ イベントハンドラー
//
// イベントが発生した時に実行する処理
//
}
}

Viewのリスナーへ登録する関数(setOnClockListener関数など)は、図のようにBinnding式の評価結果を引数にして実行されます。
Binding式が「クラスに属する関数オブジェクト」になっている点がポイントです。

この時、レイアウト中に記載する関数名は省略形になっています。省略形のルールは図の通りです。

イベントハンドラー(関数オブジェクト)の表現方法は、次の2つがあります。
- (1)メソッド参照
- (2)リスナーバインディング
- ※(1)は上記の図に出てきた表現方法
メソッド参照
「メソッド参照」は関数名で関数オブジェクトを表現する方法です。
このサンプルはハンドラーをMainActivityに実装しています。ですので、Binding変数の型はMainActivityです。
<?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>
<variable name="message" type="com.example.sample.Message" />
<variable name="activity" type="com.example.sample.MainActivity" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".BasicActivity">
<LinearLayout ...>
<TextView
android:id="@+id/txtMesg"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{message.text}"
android:textColor="@{message.color}"
android:onClick="@{activity::clickedMesg}"
android:textAllCaps="false"
android:textSize="34sp" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
...
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.message = Message(color = Color.WHITE)
binding.activity = this
}
fun clickedMesg() {
binding.message = binding.message?.apply { color = Color.YELLOW }
}
}
クリックでWHITE⇒YELLOWへ変更しています。

クリックイベントは引数にViewを持ったハンドラーを起動しようとします。
ですので、ハンドラーの引数にViewが必要です。また、ハンドラーはView以外の引数を持てません。
起動する側とされる側で、引数の整合性が必要だからです。
リスナーバインディング
「リスナーバインディング」はラムダ式で関数オブジェクトを表現する方法です。そのラムダ式から、ハンドラーを名指しで起動します。
このサンプルはハンドラーをMainActivityに実装しています。ですので、Binding変数の型はMainActivityです。
<?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>
<variable name="message" type="com.example.sample.Message" />
<variable name="activity" type="com.example.sample.MainActivity" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".BasicActivity">
<LinearLayout ...>
<TextView
android:id="@+id/txtMesg"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{message.text}"
android:textColor="@{message.color}"
android:onClick="@{() -> activity.clickedMesg()}"
android:textAllCaps="false"
android:textSize="34sp" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
...
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.message = Message(color = Color.WHITE)
binding.activity = this
}
fun clickedMesg() {
binding.message = binding.message?.apply { color = Color.YELLOW }
}
}
<?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>
<variable name="message" type="com.example.sample.Message" />
<variable name="activity" type="com.example.sample.MainActivity" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".BasicActivity">
<LinearLayout ...>
<TextView
android:id="@+id/txtMesg"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{message.text}"
android:textColor="@{message.color}"
android:onClick="@{() -> activity.clickedMesg(message)}"
android:textAllCaps="false"
android:textSize="34sp" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
...
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.message = Message(color = Color.WHITE)
binding.activity = this
}
fun clickedMesg(m: Message) {
binding.message = binding.message?.apply { color = Color.YELLOW }
// mを使った処理
}
}
<?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>
<variable name="message" type="com.example.sample.Message" />
<variable name="activity" type="com.example.sample.MainActivity" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".BasicActivity">
<LinearLayout ...>
<TextView
android:id="@+id/txtMesg"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{message.text}"
android:textColor="@{message.color}"
android:onClick="@{(view) -> activity.clickedMesg(view)}"
android:textAllCaps="false"
android:textSize="34sp" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
...
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.message = Message(color = Color.WHITE)
binding.activity = this
}
fun clickedMesg(v: View) {
binding.message = binding.message?.apply { color = Color.YELLOW }
// vを使った処理
}
}
<?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>
<variable name="message" type="com.example.sample.Message" />
<variable name="activity" type="com.example.sample.MainActivity" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".BasicActivity">
<LinearLayout ...>
<TextView
android:id="@+id/txtMesg"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{message.text}"
android:textColor="@{message.color}"
android:onClick="@{(view) -> activity.clickedMesg(view, message)}"
android:textAllCaps="false"
android:textSize="34sp" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
...
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.message = Message(color = Color.WHITE)
binding.activity = this
}
fun clickedMesg(v: View, m: Message) {
binding.message = binding.message?.apply { color = Color.YELLOW }
// mとvを使った処理
}
}
クリックでWHITE⇒YELLOWへ変更しています。
実行結果は「プリミティブ型」と同じです。省略します。
ラムダ式からハンドラーを名指しで起動する際に、ハンドラーへView以外の引数を設けることが可能です。ただし、引数にできるのはBinding変数です。
これが、この表現方法の利点になります。
関連記事:
