Android: ViewBinding & DataBinding

rivermoon·2025년 3월 25일
post-thumbnail

📘 Introduction

안드로이드에서는 레이아웃 XML에 정의된 뷰를 코드에서 조작해야 할 일이 많습니다.
과거에는 findViewById()를 통해 수동으로 뷰를 참조했지만, 타입 안전성 부족, 반복적인 코드 등의 문제가 있었죠.

이런 문제를 해결하기 위해 Android에서 두 가지 주요한 바인딩 기법을 제공 한답니다.

🔹 ViewBinding

  • 컴파일 타임에 레이아웃 파일에 연결된 바인딩 클래스를 생성
  • findViewById() 없이 안전하게 뷰에 접근 가능
  • 타입 안전 + Null 안전성 확보
  • 코드와 XML의 1:1 매핑을 자동화하여 유지보수를 단순화

🔹 DataBinding

  • ViewBinding의 모든 기능 포함 + XML에서 데이터 직접 바인딩 가능
  • 양방향 바인딩, LiveData 연동, 표현식 사용 등 MVVM 아키텍처에 최적화된 기능 제공
  • XML에 비즈니스 로직이 들어가므로, ViewModel과 View 간의 결합을 최소화할 수 있음



🔧 ViewBinding 사용법

✅ 설정 방법

android {
     buildFeatures {
        viewBinding = true
    }
}

✅ 사용 예

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <TextView
        android:id="@+id/helloText"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
</LinearLayout>
class MainActivity : AppCompatActivity() {
    private lateinit var binding: ActivityMainBinding

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

        binding.helloText.text = "Hello, World!"
    }
}

이러한 예시코드를 작성하면 내부 코드 구조는 어떻게 될까요?

🔍 내부 코드 구조

ActivityMainBinding 클래스가 컴파일 시 자동 생성된 실제 코드를 보면 다음과 같습니다.

public final class ActivityMainBinding implements ViewBinding {
  @NonNull
  private final LinearLayout rootView;

  @NonNull
  public final TextView helloText;

  @NonNull
  public final LinearLayout main;

  private ActivityMainBinding(@NonNull LinearLayout rootView, @NonNull TextView helloText,
      @NonNull LinearLayout main) {
    this.rootView = rootView;
    this.helloText = helloText;
    this.main = main;
  }

  @Override
  @NonNull
  public LinearLayout getRoot() {
    return rootView;
  }

  @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);
  }

  @NonNull
  public static ActivityMainBinding bind(@NonNull View rootView) {
    int id;
    missingId: {
      id = R.id.helloText;
      TextView helloText = ViewBindings.findChildViewById(rootView, id);
      if (helloText == null) {
        break missingId;
      }

      LinearLayout main = (LinearLayout) rootView;
      return new ActivityMainBinding((LinearLayout) rootView, helloText, main);
    }
    String missingId = rootView.getResources().getResourceName(id);
    throw new NullPointerException("Missing required view with ID: ".concat(missingId));
  }
}

📝 간략한 설명

  • helloText 및 main 뷰에 대한 타입 안전한 참조 제공
  • 내부에서 ViewBindings.findChildViewById()를 사용하여 안전하게 뷰를 찾음
  • 누락된 ID가 있으면 런타임 예외 발생시켜 디버깅 용이
  • bind() 메서드는 최적화된 바이트코드를 생성하도록 특수한 방식으로 작성됨

빌드 시 이러한 코드가 자동으로 생성되게 되는데요.
동작흐름을 설명하자면 아래와 같습니다.

🔄 ViewBinding 내부 동작 흐름

이 흐름 덕분에 직접 findViewById 없이도 안전하게 뷰에 접근할 수 있습니다.

🔗 DataBinding 사용법

✅ 설정 방법

android {
     buildFeatures {
        dataBinding = true
    }
}

✅ 사용 예

<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
        <variable
            name="user"
            type="com.example.bindingtest.User" />
    </data>
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@{user.name}" />
</layout>
data class User(
    val name: String,
)
class MainActivity : AppCompatActivity() {
    private lateinit var binding: ActivityMainBinding
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
        binding.user = User("Android")
    }
}

🔍 DataBinding 컴파일 결과 및 구조 분석

DataBinding을 적용하면 컴파일 시 다음과 같은 클래스들이 자동 생성됩니다.

📦 1. DataBindingTriggerClass

@androidx.databinding.BindingBuildInfo
public class DataBindingTriggerClass {}
  • 컴파일 시 바인딩 생성 트리거 역할을 하는 더미 클래스
  • 내부 기능은 없지만, 빌드 시스템이 바인딩 코드를 생성할 수 있도록 힌트?를 제공해줌

📄 2. ActivityMainBinding (추상 클래스)

public abstract class ActivityMainBinding extends ViewDataBinding {
  @Bindable
  protected User mUser;

  protected ActivityMainBinding(Object _bindingComponent, View _root, int _localFieldCount) {
    super(_bindingComponent, _root, _localFieldCount);
  }

  public abstract void setUser(@Nullable User user);

  @Nullable
  public User getUser() {
    return mUser;
  }

  public static ActivityMainBinding inflate(@NonNull LayoutInflater inflater) {
    return inflate(inflater, null, false);
  }

  public static ActivityMainBinding inflate(@NonNull LayoutInflater inflater,
      @Nullable ViewGroup parent, boolean attachToRoot) {
    return inflate(inflater, parent, attachToRoot, DataBindingUtil.getDefaultComponent());
  }

  public static ActivityMainBinding bind(@NonNull View view) {
    return bind(view, DataBindingUtil.getDefaultComponent());
  }

  @Deprecated
  public static ActivityMainBinding bind(@NonNull View view, @Nullable Object component) {
    return (ActivityMainBinding)bind(component, view, R.layout.activity_main);
  }
}

해당 클래스는 ViewDataBinding을 상속하며 다음 기능을 제공함

  • setUser() 및 getUser()로 사용자 데이터 바인딩
  • @Bindable 키워드로 자동 옵저버 연결
  • inflate/bind 메서드는 내부적으로 ViewDataBinding.inflateInternal() 호출

🔄 DataBinding 내부 동작 흐름

이 구조 덕분에 LiveData나 ObservableField를 통해 값이 변경되면 UI도 자동으로 갱신됩니다.



⚙️ 내부 동작 원리 비교

🔍 ViewBinding

  • 뷰 ID를 캐싱한 바인딩 클래스를 생성
  • null-safe, 타입-safe
  • 성능 빠르고 Reflection 없음

🔍 DataBinding

  • XML 내 표현식을 컴파일 시 분석 후 코드 생성
  • executeBindings() 자동 생성
  • LiveData, ObservableField 등 자동 옵저빙
  • @BindingAdapter로 커스텀 속성 처리 가능



⚖️ ViewBinding vs DataBinding 비교

항목ViewBindingDataBinding
XML 변수 바인딩 지원✅ (@{} 사용 가능)
양방향 바인딩✅ (@={} 구문)
LiveData 자동 연동
코드 생성 시점컴파일 타임컴파일 타임
성능빠름 (경량)무거움 (Expression 처리)
사용 난이도쉬움약간 복잡
MVVM 아키텍처에 적합보통매우 적합
IDE 자동 완성 지원부분적 (표현식 제외)
디버깅 난이도낮음높음



🤔 언제 어떤 걸 써야 할까?

상황추천 바인딩
단순 UI, 버튼 클릭 위주 화면ViewBinding
MVVM 아키텍처, 상태 바인딩 필요DataBinding
성능이 중요한 리스트/카드 구성ViewBinding
복잡한 조건, LiveData 연동이 필요한 화면DataBinding
팀원이 DataBinding 미숙하거나 표현식 싫어함ViewBinding



🧪 실전 팁 & 주의사항

ViewBinding 주의점

// Fragment에서는 메모리 누수 방지를 위해 onDestroyView에서 바인딩 해제
override fun onDestroyView() {
    super.onDestroyView()
    binding = null
}

DataBinding 주의점

  • @BindingAdapter는 재사용 가능하지만 남용 시 유지보수 어려움
  • 너무 많은 표현식은 빌드 시간 증가 및 디버깅 난이도 상승
  • 테스트 코드는 ViewModel 테스트로 한정될 수 있음



✅ 마무리 요약

  1. ViewBinding과 DataBinding은 모두 findViewById()를 대체하며 타입 안전하고 깔끔한 UI 접근을 도와준다.

  2. ViewBinding은 간단하고 성능 좋은 방식이며, DataBinding은 MVVM 구조나 동적 표현에 강력한 도구다.

  3. UI 복잡도, 아키텍처, 성능 요구, 팀 성향에 따라 선택해서 사용하길!

profile
Android Developer

0개의 댓글