📌 Intro
이번에 소개할 뷰 바인딩(view binding)은 안드로이드 아키텍처 구성요소로서 앱 모듈별로 설정하여 사용할 수 있는 기능이다. 뷰 바인딩 기능을 사용하면 뷰를 제어하는 코드를 쉽게 작성할 수 있다. (항상 그렇지만 익숙해지는데까지가 제일 어려운 것 같다.)
📌 View Binding
뷰 바인딩을 사용하면 뷰와 상호작용하는 코드를 쉽게 작성할 수 있는데, 모듈에서 사용 설정된 뷰 바인딩은 각 레이아웃 XML파일과 바인딩 클래스를 생성한다. 이 바인딩 클래스에는 레이아웃 XML파일에서 ID가 있는 모든 뷰를 참조하는 참조변수가 포함된다.
때문에 findViewById()를 사용하지 않고도 뷰를 다룰 수 있게된다.
앞서 말했던 것처럼 바인딩 클래스는 자동으로 생성되기 때문에 만들어지는 이름에 규칙이 있다. 그 규칙은 레이아웃 XML파일명 뒤에 Binding이 붙는 것인데 예를 보면 바로 이해할 수 있다.
ex1) activity_main.xml → ActivityMainBinding
ex1) activity_second.xml → ActivitySecondBinding
ex1) result_profile.xml → ResultProfileBinding
activity_main.xml파일에는 ID가 있는 TextView, Button이 있고, ID가 없는 ImageView가 있다고 가정해보자. 코드는 다음과 같을 것이다.
<LinearLayout ... >
<TextView android:id="@+id/name" />
<ImageView android:cropToPadding="true" />
<Button android:id="@+id/button"
android:background="@drawable/rounded_button" />
</LinearLayout>
이 때 생성되는 바인딩 클래스의 이름은 ActivityMainBinding이 된다.(앞에서 살펴본 규칙 때문에)
TextView와 Button은 ID가 있기 때문에 바인딩 클래스에서 참조가 가능하지만, ImageView는 ID가 없기 때문에 바인딩 클래스에서 참조가 불가능하다.
또 모든 바인딩 클래스에는 그에 대응하는 레이아웃 XML파일의 루트 뷰에 관한 참조를 제공하는 getRoot() 메서드가 포함된다. 즉, ActivityMainBinding 클래스의 getRoot() 메서드는 activity_main.xml 의 루트 뷰인 LinearLayout 을 반환하는 것이다.
📌 사용 방법
뷰 바인딩을 사용하기 위해서는 어떻게 해야 되는지 알아보도록 하자.
STEP 1
뷰바인딩을 사용하기 위해서는 아래 코드를 통해 Gradle(그래들)에서 활성화 시켜줘야 한다.
android {
...
buildFeatures {
viewBinding true // 뷰 바인딩 활성화
}
...
}
위 그림처럼 Gradle에 viewBinding true 코드를 추가해주고, 우측 상단에 보이는 Sync Now를 꼭 클릭해주자.
STEP 2
private var binding: ResultProfileBinding? = null
override fun onCreate(savedInstanceState: Bundle) {
super.onCreate(savedInstanceState)
// setContentView(R.layout.activity_main) 기존에 있던 setContentView()
binding = ResultProfileBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
}
- 생성된 바인딩 클래스에 포함된 inflate() 메서드를 호출한다. 그러면 사용할 결합 클래스 인스턴스가 생성된다.
- getRoot() 메서드를 호출하여 루트 뷰 참조를 가져온다. 코틀린에서는 ResultProfileBinding.root 으로 가져올 수 있다.
- 루트 뷰를 setContentView()에 전달하여 화면상의 active 뷰로 만든다.
STEP 3
XML파일은 위에서 예로 들었던 activity_main.xml이라고 본다.
private var binding: ResultProfileBinding? = null
override fun onCreate(savedInstanceState: Bundle) {
super.onCreate(savedInstanceState)
binding = ResultProfileBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
binding.name.text = "Hello"
binding.button.setOnClickListener(View.OnClickListener {...})
}
TextView의 ID는 name이었기 때문에 binding.name을 통해서 해당 뷰에 접근하여 text를 변경할 수 있다.
마찬가지로 Buttonw의 ID는 button이었기 때문에 binding.button을 통해서 해당 뷰에 접근하여 클릭리스너를 추가할 수 있다.
📌 findViewById 와의 차이
뷰 바인딩은 findViewById에 비해 여러 장점이 있다.
1. Null Safety
뷰 바인딩은 뷰의 직접 참조를 생성하기 때문에 유효하지 않은 뷰 ID로 인해 null 포인터 예외가 발생할 위험이 없다. (또한 레이아웃의 일부 구성에만 뷰가 있는 경우 결합 클래스에서 참조를 포함하는 필드가 @Nullable로 표시된다.)
2. Type Safety
각 바인딩 클래스에 있는 필드의 유형이 XML 파일에서 참조하는 뷰와 같다. 즉, 클래스 변환 예외가 발생할 위험이 없다.
결국 레이아웃과 코드 사이의 비호환성이 있는 경우 실행할 때가 아닌 컴파일을 진행할 때 빌드가 실패한다. (실행되기 전에 오류가 발생하기 때문에 더 쉽고 빠르게 오류를 해결 할 수 있다.)
📌 Memo
프래그먼트에서도 뷰 바인딩을 사용하는 방법이 있지만 아직 사용해보지 않았기 때문에 정리는 하지 않았다. 만약 나중에 사용하는 날이 오면 그 때 더 깊은 이해를 하며 정리하고 사용하도록 하겠다.
참고
[1] https://kitesoft.tistory.com/108
[2] https://duckssi.tistory.com/42
[3] https://developer.android.com/topic/libraries/view-binding?hl=ko