DataBinding은 UI를 layout xml에서 declarative format을 이용하여 bind하도록 해주는 라이브러리 입니다. 즉, 아래와 같은 programmatically한 코드를
findViewById<TextView>(R.id.sample_text).apply {
text = viewModel.userName
}
xml에 어떻게 View에 data를 bind할지 알려주면 되도록 바꿀 수 있습니다.
<TextView
android:text="@{viewmodel.userName}" />
ViewBinding과 마찬가지로 DataBinding을 사용하기 위해서는 module-level build.gradle파일에 아래와 같이 dataBinding option을 true로 설정해주어야 합니다.
android {
...
buildFeatures {
dataBinding true
}
}
DataBinding을 사용하기 위해서는 일반 layout 파일과 달리 root tag를 <layout>
으로 설정하고 binding할 data 변수를 <data>
를 이용하여 선언하여야 합니다.
<?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>
전체적인 layout을 <layout>를 이용하여 감싸주고, <data>내에서 <variable>
을 이용하여 data binding에서 사용할 변수명과 변수의 type을 정해주어야 합니다. <data>내에서 참조해야할 클래스가 존재한다면 <import>
를 이용하여 아래와 같이 import 해줄 수도 있습니다.
<data>
<import type="java.util.List"/>
<!-- binding expression 내에서 < 문자 대신 <를 사용해야 한다. -->
<variable name="list" type="List<String>"/>
</data>
Binding expression은 Java와 비슷하며 data binding 변수를 이용하여 View에 어떻게 반영할지 선언할 수 있습니다. Binding expression 사용 시 주의할 점은 위에서도 언급했듯이 expression내에서 <
를 사용할 때 <
로 사용하여야 합니다. 일반적인 기능은 위 링크를 참조해주시기 바랍니다.
다음의 연산들은 binding expression에서 사용할 수 없습니다.
Null coalescing operator은 Kotlin의 elvis operator와 동일하며 왼쪽 operand가 null이 아니면 왼쪽 operand의 값을 사용하고 null일 경우 오른쪽 operand의 값을 사용합니다.
android:text="@{user.displayName ?? user.lastName}"
위의 예제에서 user.displayName이 null이면 user.lastName을 대신 사용합니다.
클래스내의 property와 field, getter, ObservableField에 아래와 같이 접근할 수 있습니다.
android:text="@{user.lastName}"
자동으로 생성된 data binding 코드는 null 값을 확인하며 null pointer exception을 방지합니다. 예를 들어 @{user.name}
에서 user가 String이고 null일 경우 user.name에 "null"이 할당됩니다.
Binding expression은 data binding 변수뿐만이 아니라 layout내의 다른 View값을 참조할 수 있습니다. 참조는 View의 id로 가능합니다.
<EditText
android:id="@+id/example_text"
android:layout_height="wrap_content"
android:layout_width="match_parent"/>
<TextView
android:id="@+id/example_output"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{exampleText.text}"/>
위의 예제에서 TextView가 EditText를 참조하여 EditText의 text값이 변경될 경우 같이 변경됩니다.
<data>
<import type="android.util.SparseArray"/>
<import type="java.util.Map"/>
<import type="java.util.List"/>
<variable name="list" type="List<String>"/>
<variable name="sparse" type="SparseArray<String>"/>
<variable name="map" type="Map<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]}"
Array, List, SparseArray, Map과 같은 일반적인 collection들은 []
연산자를 이용하여 접근이 가능합니다.
DataBinding은 어떻게 동작할까요?
https://github.com/googlecodelabs/android-databinding
위 링크의 SolutionActivity를 이용하여 설명하겠습니다.
우선 layout xml파일을 읽어 abstract한 Binding 클래스와 구현체인 BindingImpl 클래스를 생성해냅니다. 즉, solution.xml
을 읽어 SolutionBinding.java
와 SolutionBindingImpl.java
를 생성하게 됩니다.
public abstract class SolutionBinding extends ViewDataBinding
public class SolutionBindingImpl extends SolutionBinding implements com.example.android.databinding.basicsample.generated.callback.OnClickListener.Listener
생성된 클래스의 상속 관계를 그림으로 나타내면 아래와 같습니다.
SolutionBinding은 BaseObservable을 상속받는 ViewDataBinding을 상속받습니다. Observer 패턴을 이용하여 data binding 변수가 변경되면 아래와 같은 일이 일어나게 됩니다.
public void setViewmodel(@Nullable com.example.android.databinding.basicsample.data.SimpleViewModelSolution Viewmodel) {
this.mViewmodel = Viewmodel;
synchronized(this) {
mDirtyFlags |= 0x10L;
}
notifyPropertyChanged(BR.viewmodel); // notify change
super.requestRebind();
}
구현체 클래스(SolutionBindingImpl.java) 내부에는 mDirtyFlags라는 정수형 field가 존재합니다. Dirty flag는 아래와 같이 어떤 observable한 변수가 변했는지 나타내는 flag입니다.
해당 변수가 변하게 되면 flag가 1로 설정이 되며 아래와 같이 dirty flag를 이용하여 view를 update합니다.
// when all or viewmodel, viewmodel.popularity changed
if ((dirtyFlags & 0x38L) != 0) {
com.example.android.databinding.basicsample.util.BindingAdaptersKt.popularityIcon(this.imageView, viewmodelPopularityGetValue);
com.example.android.databinding.basicsample.util.BindingAdaptersKt.tintPopularity(this.progressBar, viewmodelPopularityGetValue);
}
// when all or viewmodel, viewmodel.lastname changed
if ((dirtyFlags & 0x31L) != 0) {
androidx.databinding.adapters.TextViewBindingAdapter.setText(this.lastname, viewmodelLastNameGetValue);
}
// when all changed
if ((dirtyFlags & 0x20L) != 0) {
this.likeButton.setOnClickListener(mCallback2);
}
// when all or viewmodel, viewmodel.likes changed
if ((dirtyFlags & 0x32L) != 0) {
androidx.databinding.adapters.TextViewBindingAdapter.setText(this.likes, integerToStringViewmodelLikes);
com.example.android.databinding.basicsample.util.BindingAdaptersKt.hideIfZero(this.progressBar, androidxDatabindingViewDataBindingSafeUnboxViewmodelLikesGetValue);
com.example.android.databinding.basicsample.util.BindingAdaptersKt.setProgress(this.progressBar, androidxDatabindingViewDataBindingSafeUnboxViewmodelLikesGetValue, 100);
}
// when all or viewmodel, viewmodel.name changed
if ((dirtyFlags & 0x34L) != 0) {
androidx.databinding.adapters.TextViewBindingAdapter.setText(this.name, viewmodelNameGetValue);
}
[1] "Data Binding Library," Android Developers, last modified Oct 27, 2021, accessed May 11, 2022, https://developer.android.com/topic/libraries/data-binding.