[Android] View Binding, Data Binding

Minji Jeong·2022년 4월 20일
1

Android

목록 보기
2/39
post-thumbnail

처음엔 그게 그거인줄 알았다 🤣

안드로이드를 제대로 배우기 시작한지 얼마 안되었을 당시, 나는 액티비티나 프래그먼트에서 findViewById()를 사용해 일일이 view의 id를 찾아주고 있었다. 그런데 뷰가 한두개도 아니고, 갯수가 많아지면 많아질수록 findViewById()를 호출하는 횟수가 늘어나 코드가 길어진다. 또한 findViewById() 사용시 잘못된 id를 입력한다면 널 포인터 에러가 발생해 앱이 비정상 종료되기도 한다 :-D 여튼, 이러한 불필요한 방법들을 취업 공고를 보기 전까지는 놀랍게도 모르고 있었는데.. 취업 공고 여러개를 보다보니 공통적으로 요구하는 자격 요건이 있었다. LiveData, Coroutine Retrofit, DataBinding 등등 여러개가 있었는데, DataBinding이 뭔지 궁금해서 찾아보다가 내가 그동안 저 불편한 것들을 모르고 코딩을 하고 있었구나-라고 깨닫게 된 것이다.

일단, DataBinding을 사용하면 내가 앞서 말했던 문제들을 해결해주기는 한다. 크게,
1. 코드를 간결하게 작성할 수 있다.
2. null safety, 즉 널 포인터 에러를 방지할 수 있다.

Data Binding ❓

🤨 근데 너.. Binding이 뭔지는 아니?
그동안 나는 '바인딩' 한다는 건 단순히 '데이터를 결합해주는 것'이라고만 생각해왔다. 하지만 오늘 이렇게 바인딩 관련 게시물을 작성하는 걸 기회로 삼아, Binding class가 어떻게 생성되는지부터 알아보자.

개인적인 생각인데, 안드로이드 개발자 문서는 설명을 굉장히 잘해놓았다. 근데 설명을 잘 해놓았다는건 영문 기준이지, 한글 버전은 별로다. 번역이 좀 요상하게 되어있어서 오히려 읽기 불편할 때가 있는 것 같다..(물론 나도 영어를 잘하는 편은 아니나, 문서가 전달하고자 하는 의미를 그대로 받아들이기 위해서는 영문 버전을 보는 게 훨 좋은 것 같다.)

어쨌든 개발자 문서에서는 Binding class에 대해 이렇게 설명하고 있다.

<요약> DataBinding 라이브러리는 레이아웃 변수 및 뷰에 접근하기 위해 사용되는 binding class를 생성합니다. 생성된 binding class는 레이아웃 내의 뷰와, 액티비티 또는 프래그먼트 내의 레이아웃 변수들을 연결해줍니다. binding class의 클래스명과 패키지는 커스터마이징이 가능합니다. binding class는 각 레이아웃 파일마다 생성됩니다. 기본적으로, 클래스명은 레이아웃 파일명을 따릅니다(레이아웃 파일명이 activity_main.xml이라면 클래스명은 ActivityMainBinding). 이렇게 생성된 binding class는 레이아웃 프로퍼티에서 레이아웃의 뷰 위젯들까지 모든 바인딩을 가지고 있으며, 바인딩 요소들에 값을 할당할 수 있습니다.

그러니까 Binding이란, 레이아웃 내의 뷰들(Button, TextView, ImageView와 같은)과 액티비티/프래그먼트에서 레이아웃 내의 뷰들을 참조하기 위한 변수들을 '연결'해주는 행위다. 이런 역할을 해주는 Binding class들을 가지고 귀찮은 과정 없이(findViewById()) 뷰들을 바로 참조할 수도 있는거고, 뷰들에 데이터를 바로 넣어줄 수도 있는거다.

🙄 그래서, DataBinding이 뭔데?
먼저 DataBindingXML에서 직접 뷰에 데이터를 할당하기 위해 사용하는 Android Jetpack 라이브러리의 구성 요소다. 맨 처음 DataBinding에 대해 알아보기 위해 검색했던 글들은 DataBinding을 다음과 같이 소개하고 있었다.

DataBinding을 사용하지 않으면 findViewById와 같은 메서드를 사용해서 실시간으로 모든 리소스 트리를 뒤져서 해당 데이터를 찾아가야하는데, 이는 프로그램의 리소스를 많이 잡아먹을 뿐만 아니라 코드 길이가 불필요하게 길어지는 문제를 초래합니다.

예제에서도 findViewById()를 DataBinding으로 대체해 사용하고 있었기 때문에, 나는 DataBinding이 findViewById()를 대체하기 위해서 태어난 아인줄 알았다. 물론 위의 설명은 맞는말이다! DataBinding으로 액티비티나 프래그먼트에서 뷰 참조가 가능하다. 나는 여태 그런 방식으로 DataBinding을 사용하고 있었다.

<?xml version="1.0" encoding="utf-8"?>
<layout>

    <androidx.constraintlayout.widget.ConstraintLayout>
      
        ...
      
    </androidx.constraintlayout.widget.ConstraintLayout>
  
</layout>
이렇게 XML에서 layout 태그를 사용하면 데이터바인딩을 사용할 수 있다.
binding.sample_text.setText(ViewModel.getName())

여튼 이런 방식으로 계속 사용하고 있었는데, DataBinding과 함께 ViewBinding의 존재도 알게 되었다. 둘 이름이 비슷하네? 얘는 어디에 쓰는거지? ViewBinding은 무엇인지 당장 검색해보았다.

View Binding ❓

ViewBinding은 액티비티 또는 프래그먼트에서 xml의 뷰를 직접 참조하기 위해 사용하는 라이브러리다. View Binding은 다음과 같은 장점이 있다.

  1. null safety 하다.
  2. type safety - 뷰 타입이 일치함으로 캐스팅 에러가 발생할 가능성 또한 없다.

장점을 찾아보니 의문이 들었다. ViewBinding과 DataBinding은 당연히 차이가 있을 거라고 생각했는데, 장점을 찾아보니 내가 DataBinding을 사용하는 이유가 곧 ViewBinding의 장점과 동일했기 때문이다. 어, 그러면 두가지 방식이 어떤 차이가 있는 걸까?

정말 바보같게도 나는 혼란에 빠질 수 밖에 없었다. 그야 당연한게, 개발 속도에만 치중해서 블로그 몇개만 대충 훑어보고 아! DataBinding은 이럴때 쓰는거구나! 로 단정지어 버렸던 것이다. 두 개의 방식은 내가 앞서 개념에 대해 살짝 언급했던 것처럼, 사용 목적이 약간 다르다. 약간 다르다는게, DataBinding이 ViewBinding의 기능도 포함한다고 해야할까? 개념을 다시 한 번 짚고 넘어가자.

DataBinding
XML에서 뷰에 직접 데이터를 할당하기 위해 사용한다.

ViewBinding
액티비티 또는 프래그먼트에서 XML의 뷰를 직접 참조하기 위해 사용한다.

더 정확한 차이를 알기 위해 StackOverFlow 에서 검색해봤다. 내가 궁금한 것을 가장 잘 알려주는 질문글이었다.
https://stackoverflow.com/questions/58040778/android-difference-between-databinding-and-viewbinding

답변은 이렇다.

해석해보면,
ViewBinding -> 오직 코드에 뷰를 바인딩하기 위해 사용합니다.
DataBinding -> 뷰 바인딩의 기능 뿐만 아니라, 데이터를 직접 뷰에 바인딩합니다.
답변자가 말해주는 ViewBinding과 DataBinding에서의 3가지 중요한 차이를 번역해봤다.
1. ViewBinding은 XML에서 layout 태그를 사용할 필요가 없습니다.
2. XML에서 뷰에 데이터를 바인딩하기 위해 ViewBinding을 사용할 수 없습니다. ViewBinding은 양방향 바인딩 방식이 아닙니다.
3. ViewBinding의 주요 장점은 빠르고 효율적이라는 것입니다. DataBinding과 관련한 오버헤드 및 성능 이슈를 피할 수 있기 때문에 빌드에 시간이 적게 소요됩니다.

그러니까 난 지금까지 굳이 DataBinding을 사용하지 않아도 되는 부분에서 DataBinding을 사용하고 있었던 것이다. DataBinding을 사용하지 않아도 되는 부분이란건 바로 액티비티나 프래그먼트에서 뷰를 참조해야하는 상황을 말한다. 이땐 굳이 XML에서 뷰들을 layout 태그로 묶을 필요도 없이, 그냥 빠른 ViewBinding 방식으로 간단하게 레이아웃을 inflate해서 사용하면 되는 거였는데, 나는 굳이 DataBinding을 사용해서 빌드 시간만 더 걸리게 했던 것이다.

🔓 결론

뷰를 단순히 참조만 하고 싶을 땐 ViewBinding을 사용하고, RecyclerView + Adapter를 사용하여(예시) 데이터를 바로 뷰에 바인딩하고 싶을 땐 DataBinding을 사용하면 된다. 앞으론 두 가지 방식을 적절히 사용해서 코드를 더욱 깔끔하게 써야겠다..!

코틀린에서의 ViewBinding과 DataBinding 사용 예시
ViewBinding
class MainFragment : Fragment() {

	private lateinit var binding : FragmentMainBinding
    
        override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        binding= FragmentMainBinding.inflate(inflater)
        return binding.root
    }
    
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        
        binding.mainText.text="TAG"
        
        }
}
DataBinding
✉ 나는 보통 DataBinding을 RecyclerView + Adapter를 쓸 때 많이 사용한다. 사용자로부터 데이터를 입력받고, 리스트에 바인딩 할 때 한꺼번에 할 수 있어서 편하기 때문이다.
DataBinding을 사용해야 하는 XML을 layout 태그로 묶어준 후, 뷰에 바인딩 해줄 데이터 클래스를 data - variable 태그로 묶어준다. 데이터 클래스는 name 속성에서 간단하게 표현할 수 있다.
    <TextView
        android:id="@+id/food_name"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintEnd_toStartOf="@id/food_calorie"
        android:gravity="center"
        android:text="@{food.name}"
        android:textColor="@color/black"
        tools:ignore="MissingConstraints" />

    <TextView
        android:id="@+id/food_calorie"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        app:layout_constraintStart_toEndOf="@id/food_name"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        android:text="@{food.calorie}"
        android:gravity="center"
        android:textColor="@color/black"
        tools:ignore="MissingConstraints" />

</androidx.constraintlayout.widget.ConstraintLayout>
바인딩 해줄 데이터 클래스다. 사용자로부터 입력받은 데이터를 데이터클래스에 넘겨주고, 이 데이터 클래스에 저장된 데이터들을 바로 뷰에 바인딩 해주는 것이다.
data class FoodItemInList(var name: String, var calorie: String)
어댑터 클래스다. 데이터바인딩 작업은 뷰홀더 클래스에서 이루어진다.

class Adapter(val context: Context) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
    var list=ArrayList<DiaryItemInList>()
    private lateinit var mealBinding: MainpageItemBinding

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        val inflater=LayoutInflater.from(context)
        mealBinding= DataBindingUtil.inflate(inflater,R.layout.mainpage_item,parent,false)
                val view=mealBinding.root
                Holder(view)
        }

    override fun onBindViewHolder(holder: Holder, position: Int) {
       holder.onBind(list[position])
            }

    override fun getItemCount(): Int { return list.size }

    fun addAll(d: ArrayList<DiaryItemInList>){ list.addAll(d) }

    fun removeAll(){list.clear()}

    inner class Holder(val view: View): RecyclerView.ViewHolder(view){
        fun onBind(data: DiaryItemInList){
            mealBinding.diary=data
        }
    }
}
만약 데이터바인딩을 사용하지 않는다면 실제 코드는 이렇다.
class Adapter(val context: Context) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
    var list=ArrayList<DiaryItemInList>()

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        val inflater=LayoutInflater.from(context)
        val view=inflater.inflate(R.layout.layout_name, parent, false)
                Holder(view)
        }

    override fun onBindViewHolder(holder: Holder, position: Int) {
       holder.onBind(list[position])
            }

    override fun getItemCount(): Int { return list.size }

    fun addAll(d: ArrayList<DiaryItemInList>){ list.addAll(d) }

    fun removeAll(){list.clear()}

    inner class Holder(val view: View): RecyclerView.ViewHolder(view){
    val food=view.findViewById<TextView>(R.id.food_name)
    val calorie=view.findViewById<TextView>(R.id.food_calorie)
        fun onBind(data: DiaryItemInList){
            food.text=data.name
            food.calorie=data.calorie
        }
    }
}
🟡 도움이 되었던 블로그들입니다

https://developer.android.com/codelabs/android-databinding?hl=ko#3
https://salix97.tistory.com/243
https://developer.android.com/topic/libraries/data-binding/generated-binding

profile
Mobile Software Engineer

0개의 댓글