Data Binding — More

hongbeom
hongbeomi dev
Published in
9 min readNov 20, 2022

--

데이터 바인딩 라이브러리를 조금 더 잘 활용해 봅시다.

Photo by Michael Dziedzic on Unsplash

💡 Data Binding

데이터 바인딩은 선언적 형식으로 레이아웃의 컴포넌트와 앱의 데이터 결합을 지원하는 라이브러리입니다. 2015년 Android Dev Summit에서 처음 발표되어 지금까지 안드로이드 앱을 구현할 때 흔히 사용되고 있습니다.

데이터 바인딩의 원리는 아래 그림과 같습니다.

우리가 Run 버튼을 눌러서 빌드를 시작하면 컴파일러는 앱 코드를 컴파일하고 리소스 파일을 모으게 됩니다.

이 때 레이아웃 파일 프로세서의 작업이 시작되는데, 데이터 바인딩에 관한 모든 것들이 제거됩니다. (layout 태그, data 태그, 표현식 등등..) 그리고 이 제거된 것들에 대한 문법 검사를 실시하며, 예외가 발생할 경우 에러를 우리에게 표시합니다.

이제 자바 코드로 컴파일 되는 동안, 어노테이션 프로세서는 레이아웃 파일에서 파싱한 코드를 분석하기 시작합니다. 위 예시에서의 isAdmin이 메서드인지 혹은 필드인지 알아내려고 노력하며, 이후 setter를 검색하고 필요한 의존성을 가져다 줍니다.

그런데 Viewid를 우리가 명시적으로 작성하지 않은 경우, APT는 어떻게 View를 구분하여 데이터를 바인딩 시켜줄 수 있을까요?

간단히 말하자면 아래와 같은 데이터 바인딩 레이아웃 파일이 존재할 경우,

Google I/O 2016

컴파일 시 모든 데이터 바인딩 관련 정보가 제거됩니다.

Google I/O 2016

그리고 각각의 뷰에 태그가 들어가게 됩니다. 바로 이것이 각각의 Viewid를 넣지 않아도 레이아웃이 inflate 되었을 때 해당하는 View를 찾을 수 있는 이유입니다.

Google I/O 2016

👀 데이터 바인딩 더 잘 써보기

이제 데이터 바인딩을 조금 더 잘 활용해 볼 수 있는 방법 몇 가지를 살펴보겠습니다.

동적인 변수 활용하기

데이터 바인딩을 사용 할 때, 구체적인 데이터의 타입을 모르는 경우가 있을 수 있습니다. 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}"/>

위의 경우, 다른 Viewid를 참조하여 중복 코드를 줄일 수 있습니다.

<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 활용하기

어떤 상황에서는 특정 타입 간에 변환이 필요할 수 있습니다. 예를 들어 Viewandroid: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) 메서드를 자동으로 검색합니다. 프로퍼티의 네임스페이스는 고려되지 않으며 메서드 검색 시 프로퍼티의 이름 과 타입만 사용됩니다.

예를 들어 우리가 만든 CustomViewsetName이라는 메서드가 public으로 존재할 경우 아래와 같은 바인딩 표현식을 바로 사용할 수 있습니다.

<CustomView
...
android:name="@{item.name}"/>

데이터 바인딩 라이브러리가 내부적으로 어떻게 작동하는지 안다면, 데이터 바인딩을 효율적으로 사용하고 UI 성능을 높이는 데 큰 도움이 될 것 같습니다. 🍺

참고

https://developer.android.com/topic/libraries/data-binding

--

--