일단, DataBinding을 사용하면 내가 앞서 말했던 문제들을 해결해주기는 한다. 크게,
1. 코드를 간결하게 작성할 수 있다.
2. null safety, 즉 널 포인터 에러를 방지할 수 있다.
🤨 근데 너.. Binding이 뭔지는 아니?
그동안 나는 '바인딩' 한다는 건 단순히 '데이터를 결합해주는 것'이라고만 생각해왔다. 하지만 오늘 이렇게 바인딩 관련 게시물을 작성하는 걸 기회로 삼아, Binding class가 어떻게 생성되는지부터 알아보자.
어쨌든 개발자 문서에서는 Binding class에 대해 이렇게 설명하고 있다.
그러니까 Binding이란, 레이아웃 내의 뷰들(Button, TextView, ImageView와 같은)과 액티비티/프래그먼트에서 레이아웃 내의 뷰들을 참조하기 위한 변수들을 '연결'해주는 행위다. 이런 역할을 해주는 Binding class들을 가지고 귀찮은 과정 없이(findViewById()) 뷰들을 바로 참조할 수도 있는거고, 뷰들에 데이터를 바로 넣어줄 수도 있는거다.
🙄 그래서, DataBinding이 뭔데?
먼저 DataBinding은 XML에서 직접 뷰에 데이터를 할당하기 위해 사용하는 Android Jetpack 라이브러리의 구성 요소다. 맨 처음 DataBinding에 대해 알아보기 위해 검색했던 글들은 DataBinding을 다음과 같이 소개하고 있었다.
예제에서도 findViewById()를 DataBinding으로 대체해 사용하고 있었기 때문에, 나는 DataBinding이 findViewById()를 대체하기 위해서 태어난 아인줄 알았다. 물론 위의 설명은 맞는말이다! DataBinding으로 액티비티나 프래그먼트에서 뷰 참조가 가능하다. 나는 여태 그런 방식으로 DataBinding을 사용하고 있었다.
<?xml version="1.0" encoding="utf-8"?>
<layout>
<androidx.constraintlayout.widget.ConstraintLayout>
...
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
binding.sample_text.setText(ViewModel.getName())
여튼 이런 방식으로 계속 사용하고 있었는데, DataBinding과 함께 ViewBinding의 존재도 알게 되었다. 둘 이름이 비슷하네? 얘는 어디에 쓰는거지? ViewBinding은 무엇인지 당장 검색해보았다.
ViewBinding은 액티비티 또는 프래그먼트에서 xml의 뷰를 직접 참조하기 위해 사용하는 라이브러리다. View Binding은 다음과 같은 장점이 있다.
장점을 찾아보니 의문이 들었다. 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을 사용하면 된다. 앞으론 두 가지 방식을 적절히 사용해서 코드를 더욱 깔끔하게 써야겠다..!
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"
}
}
<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