ViewBinding

Grow Up!·2024년 1월 23일
0

Dive in Android

목록 보기
3/4

목차

  1. ViewBinding 이 뭐고 왜 필요해요?
  2. ViewBinding 클래스 어느정도 깊이로 들어가보자
  3. 요약은 깔끔하게

1. ViewBinding 이 뭐고 왜 필요해요?

Developer Android 문서에 따르면 ViewBinding 을 아래와 같이 설명하고 있습니다.

View binding is a feature that makes it easier to 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.

요약하면 module level 의 Gradle 파일에 ViewBinding 을 활성화 하면 module 에 포함된 모든 Layout 파일에 대해 binding 클래스가 생성됩니다. binding 클래스에는 Layout 파일에 선언한 View(ID 속성이 선언된) 참조가 포함되어있습니다.

ViewBinding 을 사용하면 findViewById() 를 통해 View 인스턴스의 참조를 얻거나, Jake Wharton 님이 개발하신 butterknife 라이브러리를 사용하지 않고도 간단히 View 인스턴스 참조를 얻을 수 있습니다.

findViewById() 와 비교해 ViewBinding 의 장점은 아래와 같습니다.

View binding has important advantages over using findViewById:

  • Null safety: since view binding creates direct references to views, there's no risk of a null pointer exception due to an invalid view ID. Additionally, when a view is only present in some configurations of a layout, the field containing its reference in the binding class is marked with @Nullable.
  • Type safety: the fields in each binding class have types matching the views they reference in the XML file. This means there's no risk of a class cast exception.

These differences mean incompatibilities between your layout and your code result in your build failing at compile time rather than at runtime.



2. ViewBinding 클래스 어느정도 깊이로 들어가보자

분석에 사용된 소스코드와 Layout 파일은 아래와 같습니다.

class MainActivity : AppCompatActivity() {
    private lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        binding.changeNameButton.setOnClickListener {
            Toast.makeText(this, "click", Toast.LENGTH_LONG).show()
        }
    }
}
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/name_text_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/change_name_button"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        app:layout_constraintTop_toBottomOf="@id/name_text_view"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>


  1. 생성된 binding 클래스의 inflate() 메서드를 호출합니다.
    • 매개변수로 전달받은 LayoutInflater 인스턴스의 inflate() 메서드를 호출하여 Layout 파일을 inflate 합니다.
    • attachToParent flag 는 false 이므로 inflate 한 View 를 Parent ViewGroup 에 추가하지 않습니다.
public final class ActivityMainBinding implements ViewBinding {
		@NonNull
		public static ActivityMainBinding inflate(@NonNull LayoutInflater inflater) {
				return inflate(inflater, null, false);
		}

		@NonNull
		public static ActivityMainBinding inflate(@NonNull LayoutInflater inflater,
				@Nullable ViewGroup parent, boolean attachToParent) {
				View root = inflater.inflate(R.layout.activity_main, parent, false);
				if (attachToParent) {
						parent.addView(root);
				}
				return bind(root);
		}
}


  1. 생성된 binding 클래스의 bind() 메서드를 호출합니다.
    • missingId Label Blcok 내에서 ViewBindingsfindChildViewById() 메서드를 호출하여 Layout 파일에 선언한 ID 속성이 있는 View 인스턴스의 참조를 획득합니다. 참조를 획득하지 못했다면 missingId Label Block 을 탈출하여 NullPointerException 예외를 발생시킵니다.
    • findChildViewById() 메서드에서는 매개변수로 전달받은 rootView(ConstraintLayout) 의 자식 View 갯수만큼 반복을 하며 매개변수로 전달받은 ID 를 통해 findViewById() 메서드를 호출합니다. ID 가 일치하는 View 인스턴스를 찾았다면 반환하며 종료합니다.
    • 획득한 View 인스턴스 참조들을 binding 클래스의 생성자에 전달하고 생성된 binding 클래스의 인스턴스를 반환하며 종료됩니다.
    • 개발자는 반환된 binding 클래스의 인스턴스를 통해 Layout 에 선언된 View 인스턴스에 직접 접근할 수 있게 됩니다.
public final class ActivityMainBinding implements ViewBinding {
		@NonNull
		public static ActivityMainBinding bind(@NonNull View rootView) {
		    int id;
		    missingId: {
						id = R.id.change_name_button;
						Button changeNameButton = ViewBindings.findChildViewById(rootView, id);
						if (changeNameButton == null) {
								break missingId;
						}

						id = R.id.name_text_view;
						TextView nameTextView = ViewBindings.findChildViewById(rootView, id);
						if (nameTextView == null) {
								break missingId;
						}

						return new ActivityMainBinding((ConstraintLayout) rootView, changeNameButton, nameTextView);
				}
				String missingId = rootView.getResources().getResourceName(id);
		    throw new NullPointerException("Missing required view with ID: ".concat(missingId));
	  }
}
public class ViewBindings {
		@Nullable
    public static <T extends View> T findChildViewById(View rootView, @IdRes int id) {
        if (!(rootView instanceof ViewGroup)) {
            return null;
        }
        final ViewGroup rootViewGroup = (ViewGroup) rootView;
        final int childCount = rootViewGroup.getChildCount();
        for (int i = 0; i < childCount; i++) {
            final T view = rootViewGroup.getChildAt(i).findViewById(id);
            if (view != null) {
                return view;
            }
        }
        return null;
    }
}


  1. binding 클래스의 생성자를 호출합니다.
    • binding 클래스의 bind() 메서드에서 획득한 View 참조들을 binding 클래스에 미리 선언된 필드에 저장합니다.
public final class ActivityMainBinding implements ViewBinding {
		@NonNull
		private final ConstraintLayout rootView;

		@NonNull
		public final Button changeNameButton;

		@NonNul
		public final TextView nameTextView;

		private ActivityMainBinding(@NonNull ConstraintLayout rootView, @NonNull Button changeNameButton,
				@NonNull TextView nameTextView) {
				this.rootView = rootView;
				this.changeNameButton = changeNameButton;
				this.nameTextView = nameTextView;
		}
}


3. 요약은 깔끔하게

ViewBinding 라이브러리에서 Layout 파일에 선언된 ID 가 존재하는 View 를 모두 검색하여 binding 클래스를 생성하며, 개발자가 생성된 binding 클래스의 inflate() 메서드를 호출하면 inflate()bind()constructor() 순서로 메서드가 호출되어 binding 클래스의 인스턴스를 생성하여 반환합니다.

0개의 댓글

관련 채용 정보