Data Binding은 UI 구성요소와 앱의 데이터 소스를 선언적으로 연결할 수 있게 하는 라이브러리입니다.
레이아웃 파일에서 UI 구성요소를 앱 데이터와 연결하면 액티비티에서 UI 프레임워크의 호출을 줄일 수 있어서 코드가 간결해지고 유지관리가 쉬워진다는 장점이 있습니다. 또한 앱 성능이 향상되며, 메모리 누수 및 Null Pointer 예외를 방지할 수 있습니다.
Data Binding 라이브러리는 레이아웃의 뷰를 데이터 객체와 결합하는데 필요한 클래스를 자동으로 생성합니다.
Data Binding을 사용하는 레이아웃 파일은 layout
이라는 루트 태그로 시작하고 그 안에 해당 레이아웃에서 사용할 데이터를 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>
앱의 데이터는 @{}
구문을 통해 뷰의 특정 속성에 지정됩니다.
@{}
표현식에는 this
, super
, new
, 명시적 제네릭 호출을 제외한 여러 연산자와 키워드를 사용할 수 있습니다.
<TextView
android:text="@{String.valueOf(index + 1)}"
android:visibility="@{age > 13 ? View.GONE : View.VISIBLE}" />
또한 표현식에는 Null 병합 연산자를 사용할 수 있습니다.
<TextView
android:text="@{user.displayName ?? user.lastName}" />
Null 병합 연산자
왼쪽 피연산자가 NULL이 아니면 왼쪽 피연산자를 선택, NULL이면 오른쪽 피연산자를 선택
표현식은 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}"/>
Data Binding도 각 레이아웃 파일의 바인딩 클래스를 생성하는데요, 이 클래스는 View Binding처럼 레이아웃 파일 이름을 파스칼 표기법으로 변환한 뒤, Binding이라는 접미사를 추가한 이름을 갖습니다.
이 바인딩 클래스에는 레이아웃 속성(데이터 변수 등)에서부터 레이아웃 뷰까지 모든 바인딩을 갖고 있으며, 어떻게 바인딩 표현식의 값을 할당할지도 알고 있습니다.
권장되는 결합 생성 방법은 레이아웃이 inflating
을 하는 동안에 생성하는 것입니다.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding: ActivityMainBinding = DataBindingUtil.setContentView(
this, R.layout.activity_main)
binding.user = User("Test", "User")
}
다른 방법으로는 LayoutInflater
를 이용하는 것이 있습니다.
val binding: ActivityMainBinding = ActivityMainBinding.inflate(getLayoutInflater())
Fragment
, ListView
, RecyclerView
어댑터 내에서 Data Binding을 사용한다면 inflate()
메서드를 사용하여 바인딩 객체를 생성할 수 있습니다.
val listItemBinding = ListItemBinding.inflate(layoutInflater, viewGroup, false)
// or
val listItemBinding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false)
Data Binding을 사용하면 뷰에서 전달되는 표현식 처리 이벤트를 작성할 수 있습니다. 이벤트 속성의 이름은 대부분 리스너 메서드의 이름에 따라 결정됩니다.
Data Binding에서 표현식이 메서드 참조로 계산되면 리스너에서 메서드 참조 및 소유자 객체를 래핑하고, 타겟 뷰에서 이 리스너를 설정합니다.
이벤트는 android:onClick
이 액티비티의 메서드에 할당되는 방식과 유사하게 핸들러 메서드에 직접 결합될 수 있는데요, Data Binding의 메서드 참조는 표현식이 컴파일 타임에 처리된다는 장점이 있습니다.
class MyHandlers {
fun onClickFriend(view: View) { ... }
}
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="handlers" type="com.example.MyHandlers" />
<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}"
android:onClick="@{handlers::onClickFriend}" />
</LinearLayout>
</layout>
메서드의 매개변수는 이벤트 리스너의 매개변수와 일치해야 합니다.
메서드 참조이 리스너 결합과 다른 점은 실제 리스너 구현이 이벤트가 트리거될 때가 아닌, 데이터가 결합될 때 생성된다는 것입니다. 따라서 이벤트가 발생할 때 표현식을 계산하려면 리스너 결합을 사용해야 합니다.
리스너 결합은 메서드 참조와 달리 이벤트가 발생할 때 실행되는 결합 표현식이며, 메서드와 이벤트 리스너의 반환 값만 일치하면 됩니다.
class Presenter {
fun onSaveClick(task: Task){}
}
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="task" type="com.android.example.Task" />
<variable name="presenter" type="com.android.example.Presenter" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:onClick="@{() -> presenter.onSaveClick(task)}" />
</LinearLayout>
</layout>
표현식에 콜백을 사용하면 데이터 결합은 필요한 리스너를 자동으로 생성하여 이벤트에 등록합니다.
리스너 결합에서는 모든 매개변수를 무시하거나, 모든 매개변수의 이름을 지정하여 매개변수를 선택할 수 있습니다. 매개변수 이름을 지정하면 표현식에 매개변수를 사용할 수 있습니다.
<Button
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:onClick="@{(theView) -> presenter.onSaveClick(theView, task)}" />
class Presenter {
fun onSaveClick(view: View, task: Task){}
}
import
, variable
, include
import
를 사용하면 레이아웃 파일 내에서 클래스를 참조할 수 있습니다. variable
을 사용하면 결합 표현식에 사용할 수 있는 속성을 설명할 수 있습니다.include
를 사용하면 앱 전체에서 복잡한 레이아웃을 재사용할 수 있습니다. import
data
태그 내에 0개 이상의 import
요소를 사용할 수 있습니다.
<data>
<import type="android.view.View" />
</data>
<TextView
android:text="@{user.lastName}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="@{user.isAdult ? View.VISIBLE : View.GONE}" />
이처럼 import
를 통해 클래스를 가져오면 표현식에서 해당 클래스를 참조할 수 있습니다.
별칭을 사용하여 클래스 이름 충돌을 해결할 수 있습니다.
<import type="android.view.View"/>
<import type="com.example.real.estate.View"
alias="Vista"/>
가져온 클래스는 변수 및 표현식에서 유형 참조로 사용할 수도 있습니다
<data>
<import type="com.example.User" />
<import type="java.util.List" />
<variable name="user" type="User" />
<variable name="userList" type="List<User>" />
</data>
형변환 또한 가능합니다.
<TextView
android:text="@{((User)(user.connection)).lastName}"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
variable
data
태그 내에 여러 variable
요소를 사용할 수 있습니다.
<data>
<import type="android.graphics.drawable.Drawable"/>
<variable name="user" type="com.example.User"/>
<variable name="image" type="Drawable"/>
<variable name="note" type="String"/>
</data>
include
속성에 앱 네임스페이스 및 변수 이름을 사용함으로써 포함하는 레이아웃에서 포함된 레이아웃의 결합으로 변수를 전달할 수 있습니다.
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:bind="http://schemas.android.com/apk/res-auto">
<data>
<variable name="user" type="com.example.User"/>
</data>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<include
layout="@layout/name"
bind:user="@{user}"/>
<include
layout="@layout/contact"
bind:user="@{user}"/>
</LinearLayout>
</layout>