[Android] ViewBinding

ErroredPasta·2022년 5월 6일
1

Android

목록 보기
5/5

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 사용

bulid.gradle

ViewBinding을 사용하기 위해서는 module-level build.gradle 파일에 아래와 같이 option을 true로 설정해주어야 합니다.

android {
    ...
    buildFeatures {
        viewBinding true
    }
}

Binding 클래스 생성 제외

특정 layout 파일의 binding 클래스를 생성하는 것을 제외하려면 root view에 tools:viewBindingIgnore="true" 속성을 추가해주어야 합니다.

<LinearLayout
        ...
        tools:viewBindingIgnore="true" >
    ...
</LinearLayout>

동일한 화면에 대해 여러 layout 파일을 정의할 때

기기의 상태에 따라 화면을 달리 출력해야 할 때, 같은 화면에 대해 여러개의 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는 TextViewEditText의 공통 상위 클래스인 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를 상속 받는 클래스를 지정해주어야 합니다.
  • tag의 상위 클래스를 지정해주어야 합니다. 즉, tag는 TextViewtools:viewBindingType="ImageView"와 같이 사용하면 안됩니다.
  • 모든 상태에서 type이 일관되도록 하여야합니다.

Fragment에서 ViewBinding 사용

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를 해제해주지 않으면 메모리 누수가 발생할 수 있기 때문입니다.

생성된 binding 클래스

<?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를 호출하여 모든 ViewfindChildViewById를 이용하여 찾은 뒤 reference들을 생성자의 parameter로 넘겨주어 field로 저장하는 것을 볼 수 있습니다.

findViewById와 차이점

ViewBindingfindViewById와 비교하였을 때, 다음과 같은 장점들이 있습니다.

  • Null safety
    ViewBinding은 모든 View의 direct reference를 생성하므로 findViewById를 사용할 때, 잘못된 id를 넘겨서 원하는 View를 찾지못해 발생하는 null point exception의 위험이 없습니다.

  • Type safety
    ViewBinding 클래스 내부의 field들은 가르키고 있는 View의 알맞은 type을 가지고 있으므로 type casting을 잘못하여 발생하는 class cast exception의 위험이 없습니다.

DataBinding과의 차이점

ViewBindingDataBinding 둘 다 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을 지원하지 않습니다.

Reference

[1] "View Binding," Android Developers, last modified Mar 24, 2022, accessed May 11, 2022, https://developer.android.com/topic/libraries/view-binding.

profile
Hola, Mundo

0개의 댓글