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.