본문 바로가기
안드로이드/코틀린

(android - jetPack) bindingAdapter

by 나이아카 2021. 6. 6.

 이 글의 주제는 BindingAdapter의 사용법에 대해서 정리하는 것입니다. 사실 얼마전까지 이 bindingAdapter가 어떤 원리로 구동되는지 명확하게 와닿지 않아서 제대로 사용을 못하고 있었는데, 얼마전에 겨우 조금 실마리가 잡힌 것 같아 정리를 하려고 합니다.

 먼저 bindingAdapter에 대해서 짧게 설명하자면, xml에서 속성값을 이용해 adapter를 부착하려는 것이 목표입니다. 물론 속성값을 통해 데이터를 바인딩하는 만큼, 꼭 리사이클러뷰가 아니라 일반 textView나 Button에도 부착할 수 있지만, 저는 현재 recyclerView에서 사용하고 있기 때문에 그와 관련된 내용을 다루겠습니다. 혹시 일반 View의 속성값에 바인딩어댑터를 사용하고 싶다면, 그런 글을 참고하는 것도 좋습니다만, 이 글을 보시고 난 후에도 응용을 통해 충분히 참고는 가능할 것 같습니다.

 먼저 속성값이 무엇인지에 대해서 코드를 통해 보여드리겠습니다.

object BindingAdapter {

    @BindingAdapter("setDataList")
    @JvmStatic
    fun attributeBinding(recyclerView: RecyclerView, dataList: ArrayList<DTO>){
        if(recyclerView.adapter == null) recyclerView.adapter = ListAdapter(recyclerView.context, dataList)
        else //데이터 업데이트 상태
    }
}

 위의 코드는 바인딩 어댑터를 구성하는 기본적인 코드입니다. @BindingAdapter의 파라미터를 통해 xml에서 연결해주는 역할을 하고, xml에서 이 속성값을 통해 지정한 변수의 업데이트가 일어나는 경우(LiveData 이용) recyclerView의 adapter를 조정하는 코드를 만드는 것인데, 내부 코드는 각 프로젝트별로 원하는 방식대로 구성하면 됩니다.

<?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"
    xmlns:tools="http://schemas.android.com/tools">

    <data>
        <variable
            name="Vm"
            type="PackageName.ViewModelClassName" />
    </data>

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

        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/noticeBranchRecyclerView"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            setDataList="@{Vm.dataList}"
            app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

 xml에서 RecyclerView의 속성에 setDataList가 존재하는 것을 확인할 수 있습니다. 그리고 @{Vm.dataList}로 값이 연결되어 있습니다. 이때 ViewModel의 dataList의 값이 변화하게 되는 경우 setDataList가 호출된다는 사실을 알 수 있습니다.

 데이터는 아래와 같이 ViewModel에서 적용할 수 있습니다.

class Vm : ViewModel() {
    val dataList = MutableLiveData(ArrayList<DTO>())
    
    
    fun setData(newDataList: ArrayList<DTO>) {
        dataList.postValue(newDataList)
        //or
        //dataList.value = newDataList
    }
    
    
    
}

 postValue의 경우, 백그라운드에서 값의 변경을 실행하는 코드이고, 코틀린에서 value로 값을 넣는 setValue의 경우는 메인 쓰레드에서 동작하는 코드입니다. 그래서 백그라운드 코루틴이나 쓰레드에서 setValue를 동작시키는 경우 오류가 날 수 있다고 하는데, 실제로 일반 변수의 경우 두 변수를 제대로 이해하지 못하고 사용하더라도 뷰가 변경되지 않거나 exception이 발생하는 등의 문제는 아직 경험해본 적이 없지만(일부러 다르게 사용해본 적은 없어서 테스트가 필요합니다), ArrayList의 경우 내부 값의 추가 및 수정이 존재하더라도 setValue를 통해 값을 변경하는 경우, adapter의 이동이 없어 변수는 존재하지만 RecyclerView는 아직 그대로인 등 여러가지 디자인 문제가 있는 것으로 파악되었습니다.

 물론 자신의 프로젝트에서 아무런 문제가 없이 List가 잘 수정되고 View 역시 그에 맞춰 존재한다면 아무런 문제가 없지만, 혹여나 setValue를 통해 List의 값을 변경시켰음에도 따로 adapter.notifyDataSetChanged()를 사용하고 있다면 postValue를 통해 값을 전달하고 BindingAdapter를 이용해 수정하는 과정으로 변화시켜보는 것을 추천드립니다.

 사실 BindingAdapter를 사용하지 않으려던 이유도 이 setValue에 있습니다. 분명 프로젝트에서 setValue를 통해 데이터 값을 변경하는 여러가지 방식(ArrayList에 데이터를 추가 및 수정하는 방식과 ArrayList를 아예 바꿔버리는 방법, MutableList를 사용하는 방법 등 다양한 방법을 시도해보았습니다.)을 사용했지만 모든 List가 제대로 동작하지 않았습니다. 결국 데이터를 추가하고 난 후, adapter.notifyDataSetChanged()를 통해 수동적으로 어댑터를 관리하고 있었는데 이번에 postValue를 통해 데이터를 변화시켰을 때 문제 없이 BindingAdapter가 작동하게 되었습니다. 아마 모든 프로젝트에서 setValue가 문제를 일으키지는 않을 것이고(그렇다면 이미 jetPack에서 문제에 대해 알리고 수정했을 겁니다.) postValue가 안되는 프로젝트도 있을 겁니다. 그래서 이 부분에 대해서는 시간이 날 때 조금 더 테스트해보고 어떤 부분이 문제를 일으킨 것인지 파악해봐야 할 것 같습니다.

댓글