[Android] 뷰 바인딩(View Binding) in Activity, Fragment, RecyclerView
View Binding에 대한 지식은 아래의 이전 포스팅을 참고하자!
이번 포스팅은 View Binding을 이용한 구현 방법을 알아볼 예정이다.
Activity, Fragment, RecyclerView, Dialog에서의 사용법을 순서대로 설명하며, 앞으로 여러 화면에서의 예시를 추가할 예정이다!
View Binding을 사용하기에 앞서 build.gradle 파일에 아래 코드를 추가하여 sync 해야한다.
android {
...
buildFeatures{
viewBinding = true
}
}
Activity에서 View Binding 사용하기
- 생성된 바인딩 클래스의 inflate() 메서드를 사용하여 Activity에서 사용할 바인딩 클래스의 인스턴스를 생성함
- 바인딩 클래스의 getRoot() 메서드를 통해 레이아웃 내 최상위 뷰의 인스턴스를 얻음
- setContentView() 메서드에 최상위 뷰의 인스턴스를 넘겨줌
class MainActivity : AppCompatActivity() {
private lateinit var binding : ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
...
}
- root(=xml 전체를 감싸는 최상단의 부모)는 setContentView의 인자로 넘겨주어 사용함 (필수적인 요소)
- view binding과 기존의 setContentView(R.layout.~) 방식을 사용하면 2번의 layout inflate가 발생 → root layout object는 서로 다른 객체이므로 각각 가리키는 view가 달라짐 (오류)
🤷♀️ inflate는 어떤 개념일까?
→ 레이아웃 인플레이션에 대한 자세한 설명은 아래의 이전 포스팅 참고!
간단하게 설명하자면 XMl 레이아웃의 내용이 메모리에 객체화 되는 과정을 인플레이션(inflation)이라고 한다!!
Fragment에서 View Binding 사용하기
fragment는 뷰에 비해 수명이 더 길다. 바인딩 클래스는 뷰에 대한 참조를 가지고 있으므로, 뷰가 제거될 때 호출되는 onDestroyView() 콜백 내에서 바인딩 클래스의 인스턴스도 함께 정리해야 함.
* DialogFragment를 사용하는 경우에도 동일하게 사용하면 된다.
class HomeFragment : Fragment() {
private var _binding: FragmentHomeBinding? = null
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
_binding = FragmentHomeBinding.inflate(inflater, container, false)
return binding.root
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
onDestroyView()를 사용하는 이유는
- Activity와 다르게 Fragment는 2개의 lifecycle이 존재한다.
- Fragment Lifecycle과 Fragment View Lifecycle이 달라져서 생기는 문제인 것!
- 기본적으로 Fragment는 Fragment가 가지는 View보다 오래 유지됨
- View Binding은 Data Binding과 다르게 lifecycle을 모르기 때문에 Binding 객체를 적절히 메모리에서 초기화 작업을 해야 함
- 명시적으로 초기화 작업을 하지 않으면 메모리 누수가 발생하게 됨
🤔 하지만.. 모든 Fragment에 onDestoryView()를 만들어서 바인딩 인스턴스를 해제하는 것은 너무 번거롭다!
github에 kirich1409님이 솔루션을 올려주셨는데 많은 분들이 이 방법을 사용하고 있는 것 같다..!
RecyclerView에서 View Binding 사용하기
RecyclerView의 Adapter에서 View Binding을 사용하기 위해서는 아래와 같이 적용할 수 있음
onBindViewHolder에서 item의 위젯을 사용하고 싶다면 holder.binding.~로 사용할 수 있다.
class CategoryAdapter() : ListAdapter<>(...){
// recyclerview의 아이템으로 사용할 레이아웃을 가져옴
class CategoryViewHolder(val binding: ItemCategoryBinding): RecyclerView.ViewHolder(binding.root)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CategoryViewHolder {
// 여기서 inflate
val binding = ItemCategoryBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return CategoryViewHolder(binding)
}
override fun onBindViewHolder(holder: CategoryViewHolder, position: Int) {
val category = getItem(position)
with(holder.binding) {
...
}
}
}