Android

Replace findViewById with View Binding

smomo 2021. 8. 6. 12:57

View Binding 기능을 사용하면 뷰와 상호작용하는 코드를 쉽게 작성할 수 있다. (대부분의 경우 view binding으로 findViewById가 대체 가능하다)

View Binding은 Android Studio 3.6 Canary 11 이상에서 사용할 수 있다.

설정

build.gradle(module:app) 파일에 kotlin-android-extensions 플러그인을 삭제하고 다음 dependencies를 추가한다.

android {
        ...
        buildFeatures.viewBinding true
    }

binding class를 생성하는 동안 레이아웃 파일을 무시하려면 tools:viewBindingIgnore="true" 속성을 레이아웃 파일의 root view에 추가한다.

사용

모듈에 view binding을 사용하도록 설정되면 모듈에 포함된 각 XML 레이아웃 파일의 binding class가 생성된다. 각 binding class에는 root view 및 ID가 있는 모든 view의 참조가 포함된다. binding class의 이름은 XML 파일의 이름을 카멜 표기법으로 변환하고 끝에 'Binding'을 추가하여 생성된다.

예) 레이아웃 파일 이름이 result_profile.xml 이면 binding class이름은 ResultProfileBinding

모든 binding class에는 레이아웃 파일의 root view에 관한 직접 참조를 제공하는 getRoot() 메서드가 포함된다.

Activity

Activity에 사용할 binding class 인스턴스를 설정하려면 Activity onCreate() 메서드에서 아래와 같이 설정한다.

private lateinit var binding: ResultProfileBinding

override fun onCreate(savedInstanceState: Bundle) {
	super.onCreate(savedInstanceState)
    binding = ResultProfileBinding.inflate(layoutInflater)
    val view = binding.root
    setContentView(view)
}

아래처럼 binding class 인스턴스를 사용하여 view를 참조할 수 있다.

    binding.name.text = viewModel.name
    binding.button.setOnClickListener { viewModel.userClicked() }

Fragment

Fragment에 사용할 binding class 인스턴스는 fragment의 onCreateView() 메서드에서 아래와 같이 설정한다.

    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
    }
참고 : Fragment의 onDestroyView() 메서드에서 data class 인스턴스 참조를 정리해야 한다.

RecyclerView Adapter

RecyclerView Adapter에 사용할 binding class 인스턴스는 아래와 같이 설정한다.

class BookcaseAdapter: RecyclerView.Adapter<BookcaseAdapter.ViewHolder>() {
    override fun onCreateViewHolder(private val dataSet: ArrayList<List<String>>): ViewHolder {
        val binding = ItemBookcaseListBinding.inflate(LayoutInflater.from(parent.context))
        return ViewHolder(binding)
    }	
    
    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        holder.bind(dataSet[position])
    }
    
    
    ....
    
    class ViewHolder(private val binding: ListItemBinding) : RecyclerView.ViewHolder(binding.root){
       fun bind(data:List<String>) {
            binding.title.text = data[0]
            binding.author.text = data[1]
        }
    }
}

findViewById와의 차이점

findViewById 사용과 비교했을 때 다음과 같은 장점이 있다.

  • Null 안전 : view binding은 view의 직접 참조를 생성하므로 유효하지 않은 view ID로 인해 null 포인터 예외가 발생할 위험이 없다. 
  • 유형안전 : 각 binding class에 있는 필드의 유형이 XML 파일에서 참조하는 view와 일치한다.

데이터 결합과 비교

view binding과 data binding은 모두 view를 직접 참조하는 데 사용할 수 있는 binding class를 생성한다.

data bindg에 비해 view binding은 컴파일 시간이 짧고 사용 편의성이 좋지만 XML 레이아웃 파일에서 직접 동적 UI 컨텐츠를 선언할 수 없고 양방향 data binding을 지원하지 않는다.

 

 

References