Android - view binding (안드로이드 - 뷰 결합)

K_DRAGON·2022년 1월 31일
2

android developers에서 view binding에 대해 읽어보았다.
이전부터 view binding을 사용해오긴 했지만 항상 똑같이 가져다 쓰기만 했지 이해하고 쓰진 않았다. 항상 아래처럼(액티비티라면) 매크로처럼 썼던 것 같다.

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

이제는 제대로 알고 쓰고 싶어서 한 번 찾아보았다.

모듈에 뷰 바인딩을 사용하도록 설정되면 모듈에 포함된 각 XML 레이아웃 파일의 바인딩 클래스가 생성된다. 각 바인딩 클래스에는 루트 뷰 및 ID가 있는 모든 뷰의 참조가 포함된다.(그래서 id가 없으면 참조되지 않는다)

또한 모든 바인딩 클래스에는 상응하는 레이아웃 파일의 루트 뷰에 관한 직접 참조를 제공하는 getRoot() 메서드가 포함된다.
아래 예에선 ActivityMainBinding클래스의 getRoot() 메서드가 ConstraintLayout 루트 뷰를 반환한다.

<MainActivity의 xml파일>

<androidx.constraintlayout ... >
	 	<TextView android:id="@+id/tv_login_id" ... />
        <EditText android:id="@+id/et_login_id" ... />
        <ImageView android:id="@+id/iv_logo" ... />
        <Button android:id="@+id/btn_login" ... />
</androidx.constraintlayout.widget.ConstraintLayout>



액티비티에서 뷰 바인딩 사용

액티비티에 사용할 바인딩 클래스 인스턴스를 설정하려면 액티비티의 onCreate() 메서드에서 다음 단계를 따른다.
1. 생성된 바인딩 클래스에 포함된 정적 inflate() 메서드를 호출하면 액티비티에서 사용할 바인딩 클래스 인스턴스가 생성된다.
2. getRoot() 메서드를 호출하거나 Kotlin 속성 구문을 사용하여 루트 뷰 참조를 가져온다.
3. 루트 뷰를 setContentView()에 전달하여 화면상의 활성 뷰로 만든다.

    private lateinit var binding: ActivityMainBinding

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

뷰 모델을 사용할 때

binding.tvLoginId.text = viewModel.loginId
binding.button.setOnClickListener { viewModel.loginClicked() }



프래그먼트에서 뷰 바인딩 사용

프래그먼트에 사용할 바인딩 클래스 인스턴스를 설정하려면 프래그먼트의 onCreateView() 메서드에서 다음 단계를 따른다.
1. 생성된 바인딩 클래스에 포함된 정적 inflate() 메서드를 호출하면 프래그먼트에서 사용할 바인딩 클래스 인스턴스가 생성된다.
2. getRoot() 메서드를 호출하거나 Kotlin 속성 구문을 사용하여 루트 뷰 참조를 가져온다.
3. onCreateView() 메서드에서 루트 뷰를 반환하여 화면상의 활성 뷰로 만든다.



findViewById와의 차이점

뷰 바인딩에는 findViewById를 사용하는 것에 비해 아래와 같은 중요한 장점이 있다.

  • Null Safety: 뷰 바인딩은 뷰의 직접 참조를 생성하므로 유효하지 않은 뷰 ID로 인해 null 포인터 예외가 발생할 위험이 없다. 또한 레이아웃의 일부 구성에만 뷰가 있는 경우 바인딩 클래스에서 참조를 포함하는 필드가 @Nullable로 표시된다.
  • Type Safety : 각 바인딩 클래스에 있는 필드의 유형이 xml 파일에서 참조하는 뷰와 일치한다. 즉, 클래스 변환 예외가 발생할 위험이 없다.

이런 차이점은 레이아웃과 코드 사이의 비호환성으로 인해 런타임이 아닌 컴파일 시간에 빌드가 실패하게 된다는 것을 의미한다.
즉, 컴파일에서 빌드가 실패하므로 우리가 오류를 찾기 더 쉽다.



데이터 바인딩과 비교

뷰 바인딩과 데이터 바인딩은 모두 뷰를 직접 참조하는데 사용할 수 있는 바인딩 클래스를 생성한다. 하지만 뷰 바인딩은 보다 단순한 사용 사례를 처리하기 위한 것이며 데이터 바인딩에 비해 다음과 같은 이점을 제공한다.

  • 더 빠른 컴파일: 뷰 바인딩에는 주석(annotation) 처리가 필요하지 않으므로 컴파일 시간이 더 짧다. (데이터 바인딩에서 @{}를 말하는듯 하다. 그래도 데이터바인딩은 쓰는게 )
  • 사용 편의성: 뷰 바인딩에는 특별히 태그된 xml 레이아웃 파일이 필요하지 않으므로 앱에서 더 신속하게 채택할 수 있다. 모듈에서 뷰 바인딩을 사용 설정하면 모듈의 모든 레이아웃에 뷰 바인딩이 자동으로 적용된다.

반대로 뷰 바인딩에는 데이터 바인딩과 비교할 때 아래와 같은 제한사항이 있다.

  • 뷰 바인딩은 레이아웃 변수 또는 레이아웃 표현식을 지원하지 않으므로 xml 레이아웃 파일에서 직접 동적 UI콘텐츠를 선언하는데 사용할 수 없습니다. (코틀린 파일에서 TextView, Button과 같은 요소들을 추가할 수 없다? 정도로 이해했음)
  • 뷰 바인딩은 양방향 데이터 바인딩을 지원하지 않는다.(데이터 바인딩은 지원한다.)

이렇게 비교를 해봤지만 사실 뷰 바인딩과 데이터 바인딩을 모두 사용하는 것이 가장 좋은 것 같다.



추가로 궁금했던 것들

항상 inflate(), setContentView() 메서드에 대해 궁금했었다. 매크로처럼 이녀석들을 항상 호출하면서 대체 뭘까하며 생각했었다.
그래서 조금 더 찾아봤다.

inflate()

위에 1번에서 말했듯이 inflate() 메서드를 호출하면 액티비티에서 사용할 바인딩 클래스 인스턴스가 생성된다.

즉, inflate()는 xml에 표기된 레이아웃들을 메모리에 객체화시킨다. xml 파일을 객체화해서 코드에서 사용하기 위함이다.
xml 파일은 단순한 디자인 정보로 이 정보를 가지고 실제로 화면에 띄우기 위해서는 xml에 정의된 속성들에 맞게 메모리에 올려야한다.


setContentView()

setContentView() 함수는 첫 번째 인자로 넘겨주는 xml 레이아웃 리소스 id에 해당하는 파일을 파싱하여 뷰(view)를 생성, 속성을 지정, 상하관계에 맞춰 배치한다.(xml 파일을 객체화시킨다.)
이런 일련의 과정을 전개(inflate)라고 부른다.

그래서 우리는 setContentView() 메서드 밑에서 xml에 있는 UI 요소들을 끌어와 쓸 수 있는 것이다.
바로 메모리에 올라가 객체화 되었기 때문이다.

setContentView() 메서드는 xml 문서를 inflate하기 위해 내부적으로 LayoutInflater 클래스를 참조한다.


inflater

xml 파일을 inflate하기 위해서 LayoutInflater 클래스를 제공한다.
LayoutInflater를 생성하는 여러가지 방법이 있다.

(액티비티에서는 아래처럼 쉽게 할 수 있긴 하지만 그래도 자세히 알아봤다.)

binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)



context.getSystemService()
context에서 LayoutInflater를 가져오는 방법

val inflater : LayoutInfalter = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE)




Activity에서 getLayoutInflater
액티비티에서는 LayoutInflater를 쉽게 얻어올 수 있도록 getLayoutInflater()를 제공한다. 액티비티는 자신 window의 LayoutInflater를 사용한다.

val inflater : LayoutInflater = getLayoutInflater()



LayoutInflater.from()
LayoutInflater에 static으로 정의되어있는 LayoutInflater.from을 통해 LayoutInflater를 만드는 방법이다.내부적으로 context.getSystemService를 호출하고 있으며, 같은 context에서는 같은 객체를 리턴하기 때문에 굳이 멤버 변수로 선언해 놓지 않고 필요할 때마다 호출해서 사용해도 괜찮다.

val inflater : LayoutInflater = LayoutInflater.from(context)




근데.. 그럼 우리가 액티비티에서 사용하던 아래 layoutInflater는 무엇일까?

binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)

layoutInflaterctrl + 커서를 해보면 아래와 같이 나온다.

Activity public LayoutInflater getLayoutInflater()

즉 우리가 위에서 봤던 getLayoutInflater()이다.



참고 출처
https://developer.android.com/topic/libraries/view-binding?hl=ko
https://developer.android.com/reference/android/view/LayoutInflater
https://soo0100.tistory.com/1017
https://lktprogrammer.tistory.com/156
https://velog.io/@appletorch/%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C-Inflate%EC%9D%B4%EB%9E%80
https://medium.com/vingle-tech-blog/android-layoutinflater-b6e44c265408
https://blog.naver.com/pistolcaffe/221285539895

profile
개발자가 되어보자!

1개의 댓글

comment-user-thumbnail
2022년 12월 20일

선생님 너무 유익합니다.

답글 달기