Android

[Android] DataBinding - BindingAdapter 활용하기

728x90

 

 

 DataBinding이란 xml 파일에 data를 연결(binding)해서 사용할 수 있게 도와주는 Android Jetpack 라이브러리에서 제공하는 기능 중 하나로써, 보통 MVVM 패턴을 구현할 때 LiveData와 함께 많이 사용하는 편이다.

 그런데 데이터 바인딩을 하는데 내가 원하는 속성이 없으면 어떻게 할까? 예를 들면, ImageView에 Glide를 사용해서 이미지 url을 연결해주고 싶다면 기본적으로 제공하는 속성들로는 연결해줄 수 없다.

 이런 경우에 BindingAdapter를 사용해서 처리할 수 있다. 또한, 여러 곳에서 사용하는 공통 로직을 모듈화 시켜서 코드 중복을 최소화할 수 있다.

 

 

1. BindingAdapter 생성

 예제로 서버로부터 프로필 URL을 받아와서 ImageView에 연결시켜주는 코드를 작성해볼 것이다. ImageView에 연결해줄 때는 Glide 라이브러리를 사용하며, 프로필 사진이기 때문에 사진을 원형으로 감싸주는 옵션이 필요하다.

 이러한 요구조건을 BindingAdapter로 작성하면 아래 코드와 같다.

object ImageViewBind {
    
    @JvmStatic
    @BindingAdapter(
            value = ["profileUrl", "profilePlaceHolder"],
            requireAll = false
    )
    fun setProfileUrl(imageView: ImageView, url: String?, placeHolder: Drawable?) {
        val ph = placeHolder ?: ContextCompat.getDrawable(imageView.context, R.drawable.ic_default_profile)

        Glide.with(imageView.context)
                .load(url)
                .placeholder(ph)
                .error(ph)
                .apply(RequestOptions.circleCropTransform())
                .into(imageView)
    }
}

 BindingAdapter는 Java의 static 함수로 되어 있기 때문에 @JvmStatic 어노테이션을 붙여줘야 한다. 만약 코틀린으로 BindingAdapter를 만든다면 @JvmStatic 어노테이션을 붙이지 않고 확장 함수를 활용하는 방법도 있다. 이 방법은 아래에서 설명하도록 하겠다.

 @BindingAdapter에는 2개의 field가 있다. value는 xml에서 사용하고 싶은 속성이라고 생각하면 된다. 위의 예시에서는 profileUrl 속성 값으로 String 값을, profilePlaceHolder 속성 값으로 Drawable 값을 받아온다. 선언한 value의 개수와 인자의 개수는 같다고 생각하면 된다.

 requireAll은 BindingAdapter를 사용할 때 모든 속성을 사용할지에 대한 값이다. 위의 예시에서는 false로 주었기 때문에 만약 placeHolder 값이 없다고 해도 오류를 발생하지 않는다. requireAll은 기본값이 true이기 때문에 별도로 설정을 해주지 않으면 반드시 모든 값을 연결해줘야 한다.

 BindingAdapter 함수는 Java 코드로 되어 있기 때문에 코틀린의 기본값 설정을 적용할 수 없다. 만약 requireAll을 false로 설정하고 특정 인자에 값을 넣어주지 않는다면, 그 값은 null, false 혹은 0으로 설정된다. 따라서 기본값을 설정해주고 싶다면 위의 예시와 같이 변수를 따로 받아서 기본값을 설정해줘야 한다.

 

 

 위의 예시 코드를 코틀린의 확장 함수를 사용해서 다시 작성하면 아래와 같게 된다.

@BindingAdapter(
        value = ["profileUrl", "profilePlaceHolder"],
        requireAll = false
)
fun ImageView.setProfileUrl(url: String?, placeHolder: Drawable?) {
    val ph = placeHolder ?: ContextCompat.getDrawable(context, R.drawable.ic_default_profile)

    Glide.with(context)
            .load(url)
            .placeholder(ph)
            .apply(RequestOptions.circleCropTransform())
            .into(this)
}

 확장 함수를 사용하면 @JvmStatic 어노테이션을 붙이지 않아도 되고, object로 감싸지 않아도 된다.

 

 

2. xml에 적용

 BindingAdapter를 만들었으면 이제 xml에서 가져다 쓰기만 하면 된다. 이제 어디서든 프로필 사진을 로딩할 때 profileUrl 옵션 한 줄이면 적용할 수 있게 되었다.

<?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">

    <data>

        <variable
            name="item"
            type="com.leveloper.bindingAdapter.model.SampleItem" />
    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <ImageView
            android:layout_width="@dimen/md_24"
            android:layout_height="@dimen/md_24"
            profileUrl="@{item.profileUrl}"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"/>
    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

 

 

참고

stackoverflow.com/questions/50755614/how-provide-default-value-in-bindingadapter-method-in-databinding-android

 

728x90