Data Binding — More
데이터 바인딩 라이브러리를 조금 더 잘 활용해 봅시다.
💡 Data Binding
데이터 바인딩은 선언적 형식으로 레이아웃의 컴포넌트와 앱의 데이터 결합을 지원하는 라이브러리입니다. 2015년 Android Dev Summit에서 처음 발표되어 지금까지 안드로이드 앱을 구현할 때 흔히 사용되고 있습니다.
데이터 바인딩의 원리는 아래 그림과 같습니다.
우리가 Run
버튼을 눌러서 빌드를 시작하면 컴파일러는 앱 코드를 컴파일하고 리소스 파일을 모으게 됩니다.
이 때 레이아웃 파일 프로세서의 작업이 시작되는데, 데이터 바인딩에 관한 모든 것들이 제거됩니다. (layout
태그, data
태그, 표현식 등등..) 그리고 이 제거된 것들에 대한 문법 검사를 실시하며, 예외가 발생할 경우 에러를 우리에게 표시합니다.
이제 자바 코드로 컴파일 되는 동안, 어노테이션 프로세서는 레이아웃 파일에서 파싱한 코드를 분석하기 시작합니다. 위 예시에서의 isAdmin
이 메서드인지 혹은 필드인지 알아내려고 노력하며, 이후 setter
를 검색하고 필요한 의존성을 가져다 줍니다.
그런데 View
의 id
를 우리가 명시적으로 작성하지 않은 경우, APT는 어떻게 View
를 구분하여 데이터를 바인딩 시켜줄 수 있을까요?
간단히 말하자면 아래와 같은 데이터 바인딩 레이아웃 파일이 존재할 경우,
컴파일 시 모든 데이터 바인딩 관련 정보가 제거됩니다.
그리고 각각의 뷰에 태그가 들어가게 됩니다. 바로 이것이 각각의 View
에 id
를 넣지 않아도 레이아웃이 inflate
되었을 때 해당하는 View
를 찾을 수 있는 이유입니다.
👀 데이터 바인딩 더 잘 써보기
이제 데이터 바인딩을 조금 더 잘 활용해 볼 수 있는 방법 몇 가지를 살펴보겠습니다.
동적인 변수 활용하기
데이터 바인딩을 사용 할 때, 구체적인 데이터의 타입을 모르는 경우가 있을 수 있습니다. RecyclerView
를 사용할 때, 데이터 바인딩 전용 BindingHolder
를 하나 만들어두었다고 가정해봅시다.
그리고 onBindViewHolder
에서 데이터를 바인딩 시켜주어야 하는데, item
이 제네릭 타입이라 바인딩이 불가능해지는 문제가 발생하게 됩니다.
override fun onBindViewHolder(holder: BindingHolder, position: Int) {
val item: T = itemList.getOrNull(position) ?: return
// item이 제네릭 타입이라 바인딩 불가능
// holder.binding.item = item
holder.binding.executePendingBindings()
}
이 때, 해당 RecyclerView
의 아이템으로 사용되는 레이아웃에 item
변수를 선언하고, BR
클래스를 활용하여 아래와 같이 처리할 수 있습니다.
override fun onBindViewHolder(holder: BindingHolder, position: Int) {
val item: T = itemList.getOrNull(position) ?: return
holder.binding.setBR(BR.item, item)
holder.binding.executePendingBindings()
}
데이터 바인딩 라이브러리는 모듈 패키지에 BR
이라는 클래스를 생성하는데, 이 클래스에는 데이터 바인딩에 사용된 리소스의 id가 포함되어 있습니다.
중복된 표현식을 피하고 최대한 간결하게 작성하기
바인딩 표현식을 사용 할 때, 아래 코드처럼 특정 바인딩 표현식이 완전하게 중복되는 경우가 있을 수 있습니다.
<TextView
android:id="@+id/tvError"
android:visibility="@{item.success ? View.GONE : View.VISIBLE}"/>
<TextView
android:id="@+id/tvEmpty"
android:visibility="@{item.success ? View.GONE : View.VISIBLE}"/>
위의 경우, 다른 View
의 id
를 참조하여 중복 코드를 줄일 수 있습니다.
<TextView
android:id="@+id/tvError"
android:visibility="@{item.success ? View.GONE : View.VISIBLE}"/>
<TextView
android:id="@+id/tvEmpty"
android:visibility="@{tvError.visibility}"/>
⚠️ 바인딩 클래스는 ID를 카멜 표기법으로 변환합니다.
그리고 표현식이 한 눈에 알아보기 힘들고, 복잡한 로직이 들어가 있는 경우가 있을 수 있습니다.
<TextView
android:text="@{user.age > 18 ? user.name.substring(0,1) : @string/empty" />
우리는 이런 바인딩 표현식을 처음 봤을 때 어떤 의미인지 한 번에 이해하기가 쉽지 않을 것입니다. 메서드나 필드를 추가하여 바인딩 표현식을 간단하게 만들 필요가 있습니다.
<TextView
android:text="@{user.age > 18 ? user.shortName : @string/empty" />
내장된 BindingAdapter를 최대한 활용하기
커스텀 BindingAdapter
는 뷰에 커스텀 기능을 쉽게 추가할 수 있는 좋은 방법이지만, View
에 기본으로 내장된 BindingAdapter
를 최대한 활용하는 것이 좋습니다.
기본적으로 내장된 BindingAdapter
⬇️
이유는 아래와 같습니다.
- 커스텀
BindingAdapter
를 무작정 활용하는 것은 관리 측면에서 좋지 않습니다.BindingAdapter
가 많아짐에 따라, 해당 함수를 관리하는 것은 쉽지 않습니다. 모든BindingAdapter
함수가 하나의 파일에 들어있게 될 수 있습니다. - 커스텀
BindingAdapter
내부에서measure
/layout
작업은 신중해야 합니다. 반복적으로 호출되는BindingAdapter
로 사용 될 경우, 성능에 영향을 끼칠 수 있습니다.
커스텀 BindingAdapter
를 효율적으로 만드는 여러 방법 중 하나로, double 파라미터를 활용 해볼 수 있습니다.
BindingAdapter
함수는 옵션으로 해당 함수에서 이전 값을 사용할 수 있습니다. 이전 값과 새로운 값을 사용하는 메서드는 아래 코드처럼 파라미터에 이전 값을 먼저 선언하고, 새로운 값을 선언해주어야 합니다.
@BindingAdapter("android:paddingLeft")
fun setPaddingLeft(view: View, oldPadding: Int, newPadding: Int) {
if (oldPadding != newPadding) {
view.setPadding(newPadding,
view.getPaddingTop(),
view.getPaddingRight(),
view.getPaddingBottom())
}
}
💎 Bonus
Custom conversions 활용하기
어떤 상황에서는 특정 타입 간에 변환이 필요할 수 있습니다. 예를 들어 View
의 android:background
옵션은 Drawable
이 필요한데, 정수 타입의 color
값을 지정하였다면 예외를 일으키게 됩니다.
<View android:background="@{isError ? @color/red : @color/white}"/>
이 때 BindingConversion
어노테이션을 활용해 볼 수 있습니다. BindingConversion
어노테이션이 달린 함수는 바인딩 표현식에서 setter
의 파라미터 타입과 다른 입력값이 들어왔을 때 탐색되어 사용됩니다.
@BindingConversion
fun convertColorToDrawable(color: Int) = ColorDrawable(color)
⚠️ 바인딩 표현식에 사용되는 값 타입이 일관적이지 않으면 사용할 수 없습니다.
자동 메서드 선택 기능 활용하기
이름이 example
인 프로퍼티의 경우 데이터 바인딩 라이브러리는 해당 View
에서 호환 가능한 타입을 파라미터로 허용하는 setExample(arg)
메서드를 자동으로 검색합니다. 프로퍼티의 네임스페이스는 고려되지 않으며 메서드 검색 시 프로퍼티의 이름 과 타입만 사용됩니다.
예를 들어 우리가 만든 CustomView
에 setName
이라는 메서드가 public
으로 존재할 경우 아래와 같은 바인딩 표현식을 바로 사용할 수 있습니다.
<CustomView
...
android:name="@{item.name}"/>
데이터 바인딩 라이브러리가 내부적으로 어떻게 작동하는지 안다면, 데이터 바인딩을 효율적으로 사용하고 UI 성능을 높이는 데 큰 도움이 될 것 같습니다. 🍺