구글은 최근에 findViewById와 JetBrain의 Synthetic library의 문제점을 보완하기 위하여 ViewBinding을 발표하였다.  이 라이브러리는 DataBinding과는 다른 라이브러리이다. 다만 findViewById를 대신하여 뷰에 대한 레퍼런스를 생성된 클래스에서 가져다 쓰는 방식은 같다. 하지만 ViewBinding은 DataBinding과는 다르게 레이아웃 파일의 시작 태그를 바꿀 필요가 없다. 

반면 DataBind만약 activity_main.xml 이라는 레이아웃 파일이 있다면, 이는 컴파일 되어 ActivityMainBiding이라는 히든 클래스를 생성하며 findViewById를 대신하여 사용할 수 있다.


아래는 구굴의 개발자 문서에 있는 샘플코드이다.


private var _binding: ResultProfileBinding? = null
// This property is only valid between onCreateView and
// onDestroyView.
private val binding get() = _binding!!

override fun onCreateView(
    inflater
: LayoutInflater,
    container
: ViewGroup?,
    savedInstanceState
: Bundle?
): View? {
   
_binding = ResultProfileBinding.inflate(inflater, container, false)
   
val view = binding.root
   
return view
}

override fun onDestroyView() {
   
super.onDestroyView()
   
_binding = null
}


ResultProfile은 프레그먼트이다. 이제 binding 속성을 통해 레이아웃에 있는 뷰들을 참조할 수 있다. 그런데, 한가지 눈여겨 볼 점이 있다. _binding이라는 nullable 변수를 사용하고 있는 점이다. 그리고  onDestroyView에서 _binding = null로 만들고 있다.

프레그먼트에서 ViewBinding을 사용할 경우에는 라이프사이클로 인해 binding 변수를 nullable로 만들어 주지 않으면 메모리 누수가 발생할 수 있다.


Note: Fragments outlive their views. Make sure you clean up any references to the binding class instance in the fragment's onDestroyView() method.


Fragments outlives their views? 


구글은 Fragment의 재사용을 위해 View들을 메모리에 보관하도록 Fragment의 동작을 변경하였다. 이 때문에 onDestroy - OnDestoryView가 호출되고 나서도 ViewBinding에 대한 레퍼런스가 가비지 컬렉터가 가비지 컬렉션을 할 수 있도록 명확하게 해줄 필요가 생겼다. 


여기에는 몇가지 해결책이 있다. 첫번째는,  ViewBiding을 사용하지 않고 findViewById를 사용하는 것이다. 물론 타입세이프하지도 않고, 퍼포먼스 상으로도 손해가 되는 부분이 있다. 하지만 구글의 이런 종류의 이슈에는 자유로울 수 있다.

다른 방법으로는 onCreateView또는 onViewCreated에서 로컬로만 ViewBinding을 참조하는 것이다.  여기에서 필요한 뷰에 대한 참조를 끝내는 것이다. 비슷하게는 rootView의 tag에 viewBinding에 대한 참조를 가지도록 해주는 것이다.

private lateinit var textView: TextViewfun onViewCreated (view: View, savedInstanceState: Bundle) {
   val binding = ResultProfileBinding.bind(view)
   textView = binding.textView}

리스트뷰의 뷰홀더 패턴처럼 루트뷰의 태그에 바인딩에 대한 참조를 저장하여 사용하는 방법도 있다. 이렇게 하면 별다른 코드 없이도 널로 세팅해야 하는 번거로움이 사라진다..
private val binding: ResultProfileBinding
   get() = requireView().tag as ResultProfileBindingfun onViewCreated (view: View, savedInstanceState: Bundle) {
   view.tag = ResultProfileBinding.bind(view)
}


이 방법은 자바나 코틀린이나 다 사용할 수 있고, 다른 솔류션처럼 라이프사이클을 처리해야 하는 추가 부담이 없다.
BaseFragemt가 존재한다면, 거기에 공통 함수를 만들어 사용할 수도 있을 것이다.

다른 방법으로는 https://github.com/kirich1409/ViewBindingPropertyDelegate 와 같이 Kotlin의 delegation을 사용하는 것이다.
private val viewBinding by viewBinding<ResultProfileBinding>(ResultProfileBinding::bind)


다른 방법은 구글이 자신들의 샘플앱에 이미 사용하고 있는 방법인데, AutoClearedValue라는 별도의 클래스를 이용하는 것이다.
ViewBindingPropertyDelegate

class ResultProfile: Fragment(R.layout.result_profile) {
   private var viewBinding by autoCleared<ResultProfileBinding>()   fun onViewCreated(view: View, saveInstance: Bundle) {
         viewBinding = ResultProfileBinding.bind(view)
   }}