View binding is a feature that allows you to more easily write code that interacts with views. Once view binding is enabled in a module, it generates a binding class for each XML layout file present in that module. An instance of a binding class contains direct references to all views that have an ID in the corresponding layout. [1]
ViewBinding은 XML layout 파일들을 나타내는 binding 클래스들을 생성합니다. 각각의 layout의 binding 클래스는 해당 layout 내의 모든 View에 대한 direct reference를 가지고 있어서 findViewById를 대체하며 View들과 상호작용하는 코드들을 더 쉽게 작성하도록 해줍니다.
ViewBinding을 사용하기 위해서는 module-level build.gradle 파일에 아래와 같이 option을 true로 설정해주어야 합니다.
android {
...
buildFeatures {
viewBinding true
}
}
특정 layout 파일의 binding 클래스를 생성하는 것을 제외하려면 root view에 tools:viewBindingIgnore="true" 속성을 추가해주어야 합니다.
<LinearLayout
...
tools:viewBindingIgnore="true" >
...
</LinearLayout>
기기의 상태에 따라 화면을 달리 출력해야 할 때, 같은 화면에 대해 여러개의 layout 파일을 정의하는 경우가 있고 그에 따라 각각의 요소가 다른 View type을 가지는 경우가 존재합니다.
# in res/layout/example.xml
<TextView android:id="@+id/user_bio" />
# in res/layout-land/example.xml
<EditText android:id="@+id/user_bio" />
위의 경우 userBio field는 TextView와 EditText의 공통 상위 클래스인 TextView를 가져야 하지만 생성된 binding 클래스에서는 userBio의 type이 View입니다. 이를 해결하기 위해 tools:viewBindingType 속성을 사용하여 어떤 type으로 binding 클래스를 생성할지 컴파일러에게 알려줄 수 있습니다.
# in res/layout/example.xml (unchanged)
<TextView android:id="@+id/user_bio" />
# in res/layout-land/example.xml
<EditText android:id="@+id/user_bio" tools:viewBindingType="TextView" />
tools:viewBindingType를 이용하여 type을 지정해 줄 때, 아래와 같은 주의사항들을 지켜야 합니다.
android.view.View를 상속 받는 클래스를 지정해주어야 합니다.TextView에 tools:viewBindingType="ImageView"와 같이 사용하면 안됩니다.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 객체 생성
_binding = ResultProfileBinding.inflate(inflater, container, false)
val view = binding.root
return view
}
override fun onDestroyView() {
super.onDestroyView()
// binding 객체 reference 해제
_binding = null
}
Fragment에서 ViewBinding을 사용하기 위해서는 위와 같이 사용해야합니다. 그 이유는 Fragment는 자신의 View보다 생명주기가 길어서 위와같이 onDestroyView에서 reference를 해제해주지 않으면 메모리 누수가 발생할 수 있기 때문입니다.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
tools:ignore="UseCompoundDrawables">
<ImageView
android:id="@+id/listItemImageView"
android:layout_width="120dp"
android:layout_height="120dp"
tools:ignore="ContentDescription" />
<TextView
android:id="@+id/listItemTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="20sp"
tools:text="sample text" />
</LinearLayout>
ViewBinding을 이용하여 위와같이 정의된 XML layout 파일의 생성된 binding 클래스를 보면 아래와 같습니다.
public final class ListViewItemBinding implements ViewBinding {
@NonNull
private final LinearLayout rootView;
@NonNull
public final ImageView listItemImageView;
@NonNull
public final TextView listItemTextView;
private ListViewItemBinding(@NonNull LinearLayout rootView, @NonNull ImageView listItemImageView,
@NonNull TextView listItemTextView) {
this.rootView = rootView;
this.listItemImageView = listItemImageView;
this.listItemTextView = listItemTextView;
}
@Override
@NonNull
public LinearLayout getRoot() {
return rootView;
}
@NonNull
public static ListViewItemBinding inflate(@NonNull LayoutInflater inflater) {
return inflate(inflater, null, false);
}
@NonNull
public static ListViewItemBinding inflate(@NonNull LayoutInflater inflater,
@Nullable ViewGroup parent, boolean attachToParent) {
View root = inflater.inflate(R.layout.list_view_item, parent, false);
if (attachToParent) {
parent.addView(root);
}
return bind(root);
}
@NonNull
public static ListViewItemBinding bind(@NonNull View rootView) {
// The body of this method is generated in a way you would not otherwise write.
// This is done to optimize the compiled bytecode for size and performance.
int id;
missingId: {
id = R.id.listItemImageView;
ImageView listItemImageView = ViewBindings.findChildViewById(rootView, id);
if (listItemImageView == null) {
break missingId;
}
id = R.id.listItemTextView;
TextView listItemTextView = ViewBindings.findChildViewById(rootView, id);
if (listItemTextView == null) {
break missingId;
}
return new ListViewItemBinding((LinearLayout) rootView, listItemImageView, listItemTextView);
}
String missingId = rootView.getResources().getResourceName(id);
throw new NullPointerException("Missing required view with ID: ".concat(missingId));
}
}
inflate를 호출하면 자체적으로 LayoutInflator를 이용하여 root view를 inflate한 뒤, bind를 호출하여 모든 View를 findChildViewById를 이용하여 찾은 뒤 reference들을 생성자의 parameter로 넘겨주어 field로 저장하는 것을 볼 수 있습니다.
ViewBinding은 findViewById와 비교하였을 때, 다음과 같은 장점들이 있습니다.
Null safety
ViewBinding은 모든 View의 direct reference를 생성하므로 findViewById를 사용할 때, 잘못된 id를 넘겨서 원하는 View를 찾지못해 발생하는 null point exception의 위험이 없습니다.
Type safety
ViewBinding 클래스 내부의 field들은 가르키고 있는 View의 알맞은 type을 가지고 있으므로 type casting을 잘못하여 발생하는 class cast exception의 위험이 없습니다.
ViewBinding과 DataBinding 둘 다 layout 파일의 모든 View들의 reference를 담고 있는 binding 클래스를 생성합니다. ViewBinding은 간단한 경우에 적합하며 DataBinding과 비교했을 때, 아래와 같은 이점이 있습니다.
Faster compilation
Annnocation processing이 필요하지 않으므로, 컴파일이 더 빠릅니다.
Ease of use
DataBinding과 달리 layout 태그를 작성하지 않아도 되기때문에 ViewBinding을 활성화 시키기만 하면 모든 layout 파일에 적용되므로 사용하기 쉽습니다.
하지만 DataBinding과 비교하여 다음과 같은 제한사항들이 있습니다.
ViewBinidng은 layout variable과 layout expression을 지원하지 않아서 XML layout 파일에서 동적인 UI를 작성할 수 없습니다.ViewBinding은 two-way data binding을 지원하지 않습니다.[1] "View Binding," Android Developers, last modified Mar 24, 2022, accessed May 11, 2022, https://developer.android.com/topic/libraries/view-binding.