💡 Intro
데이터 바인딩은 Ui요소와 데이터를 프로그래밍적인 방식으로 연결하지 않고, 선언적 형식으로 결합할 수 있도록 도와주는 라이브러리다.
데이터 바인딩은 뷰 객체를 참조하는 것이 아니라 뷰 객체가 보여주는 데이터를 가진 변수를 제어한다. 즉, 뷰에 데이터를 가지고 있는 변수를 처음에 설정해주면 그 변수의 값이 보여질 것이고, 버튼을 클릭하는 등의 이벤트가 발생했을 때 뷰들을 참조하여 제어하는 것이 아니라 뷰에 설정되어 있는 변수의 값을 변경하면 그 뷰가 보여주는 데이터가 바뀌고 화면도 바뀌게 된다.
체감하기 가장 좋은 이점은 뷰 객체를 제어할 필요가 없기 때문에 findViewById() 함수를 사용하지 않아도 되고, 이 덕분에 코드가 줄어드는 부분이다.
영화 극장 선택 미션을 시작하면서 데이터바인딩에 대해 배웠고 적용하기 시작했다. 이 때까지만 해도 "어떻게 하는거야!!" 하는 생각이 많이 들었다. 테코톡 주제기도 했고, 시간이 많이 지난 지금은 그 때보다는 더 깊은 이해를 하고 있기에 어떻게 사용하는지와, 내가 알고 있는 정보들을 기록해두려고 한다.
DataBinding 사용방법
1. Build.gradle 수정
build.gradle 파일에 다음과 같이 데이터바인딩을 사용한다는 코드를 추가한다.
2. <layout> 태그
데이터바인딩을 사용하려는 레이아웃 파일(xml)에 최상단에 <layout> 태그를 작성한다.
여담으로
binding class는 데이터 바인딩을 사용하는 클래스의 이름에 따라 각 레이아웃 파일에 대해 뒤에 "Binding"이라는 suffix가 붙은 Camel Case로 자동 생성된다.
즉, activity_main.xml 에 대해 자동으로 생성된 바인딩 클래스의 이름은 ActivityMainBinding이 될 것이다.
3. setContentView()
액티비티를 생성하게 되면 다음과 같은 onCreate()함수가 자동으로 생성된다.
우리는 데이터 바인딩을 사용할 것이기 때문에 setContentView() 함수를 수정해줄 것이다. 이에 2가지 방법이 있다.
DataBindingUtil
DataBindingUtil class의 객체를 생성하고, 기존의 setContentView()를 DataBindingUtil.setContentView()로 대체한다.
다음에 나올 바인딩 클래스의 inflate() 과 가장 큰 차이는 inflate하는 레이아웃의 아이디가 인자로 필요한가 필요하지 않은가이다.
Binding.inflate
Binding 클래스의 inflate()를 통해 바인딩 객체를 생성할 때 전달 인자로 inflater만 넘겨준다.
setContentView() 함수의 인자로 생성한 바인딩 객체의 root뷰를 넘겨준다.
특수한 경우가 아니라면 되도록 일반적인 Binding class를 사용하는 것을 공식문서에서 권장하고 있다.
xml 파일에 변수 추가하기
클래스 추가
이름과 나이를 갖는 데이터 클래스 User를 만들어 주었다.
xml 수정
데이터 바인딩에서의 레이아웃 xml은 최상위 root로 <layout>을 사용하고, 그 안에 크게 2개의 요소가 배치된다.
- <data> 요소 : 뷰들과 연결되어 보여줄 데이터를 가진 데이터 클래스를 지정해주고, 레이아웃 파일 안에서 사용할 변수명을 정해주는 요소다.
- 레이아웃 root 뷰 요소 : 기존 레이아웃 xml파일에 있던 ConstraintLayout이나 LinearLayout등의 뷰다.
<?xml version="1.0" encoding="utf-8"?>
<layout
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">
<!-- 1. 레이아웃뷰와 바인딩할 데이터들 명칭과 클래스지정 -->
<data>
<!-- 데이터바인딩으로 연결할 User클래스 객체를 이 레이아웃에서는 user 라는 이름으로 참조하여 사용하겠다는 설정 -->
<variable
name="user"
type="com.example.databindingexample.User" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/sample_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.name}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
<data> 태그 안에 <variable>을 사용하여 레이아웃 내에서 사용할 변수의 이름과 클래스를 지정할 수 있다.
레이아웃 파일에서는 앞서 만들었던 User 클래스를 이 레이아웃에서 user라는 변수명으로 사용하겠다고 선언하였고, TextView에서 @{user.name}과 같은 방법으로 접근할 수 있다.
Activity 코드
레이아웃 파일에 user라는 이름의 변수를 사용하겠다고 정의해두었기 때문에 binding. 을 통해 해당 변수에 접근할 수 있다.
그리고 새로 만들어준 객체를 해당 변수에 연결해주었다. 그리고 앱을 실행시키면?!
이로써 우리는 더이상 액티비티 코드에서 뷰 객체에 참조를 하지 않고도 뷰에 뿌려주는 정보를 전달할 수 있다.
Observable 변수
그런데 어떤 이벤트가 발생했을 때, 혹은 특정 시점에 뷰에 연결한 객체의 데이터를 변경하고 이를 바로 뷰에 적용하고 싶다면 어떻게 할 수 있을까?
위에서 만들었던 User클래스의 이름을 var로 바꾸고, 변경하면 되지 않을까?! 하고 단순하게 생각할 수 있지만 객체의 데이터를 바꾸었다고 해서 그 내용이 바로 뷰까지 적용되지 않는다.
Observable이라는 친구가 이를 도와준다.
Observable?
Observable은 단순히 콜백을 등록하고 해제하는 함수를 가진 인터페이스에 불과하다.
아래쪽에 있는 콜백은 이름에서 알 수 있듯이 이 Observable이 관측하고 있는 값이 변경되었음을 알리는 콜백을 던져주는 일을 한다.
Observable을 통해 클래스(모델)를 만들면 변경에 대해 xml쪽으로 알려줄 수 있다.
이를 직접 구현하기는 까다롭기 때문에 구글이 친절하게 BaseObservable이라는 클래스를 만들어두었고, 이것을 사용하면 더 쉽게 모델을 구성할 수 있다.
User 클래스 변경
하지만! BaseObservable보다 쉽게 사용할 수 있도록 구글에서 기본형들에 대해 Observable 클래스를 만들어두었다.
잘 보면 String이 없는 것을 볼 수 있는데, 이는 ObservableField를 사용해야 한다. ObservableField는 제네릭 타입으로 되어 있으며 아래처럼 정의해서 사용할 수 있다.
이를 사용해서 User class를 바꿀 것이다.
String을 사용하는 name은 ObservableField<String>, Int를 사용하는 age는 ObservableInt로 바꿔주었다.
그리고 버튼을 추가하고 클릭 리스너를 달아주기 위해 함수 하나도 추가해줬다.
레이아웃 수정
레이아웃에 버튼을 추가했고, 클릭 리스너 역시 xml에서 연결해줬다.
Activity 코드
Activity 코드는 크게 달라지지 않는다. User클래스를 변경해주었기 때문에 그에 맞게 생성하도록 코드를 변경해주면된다.
실행
Binding Class
이대로 끝내기 아쉽기에 binding class에 대해 알아보자.
xml을 빌드하는 시점에 Binding Class가 생성된다. 그럼 빌드해보자.
앞서 말했던 것처럼 ActivityMainBinding이라는 이름의 바인딩 클래스가 생성되었다.
이 친구는 버튼 하나, 텍스트뷰 하나, User 클래스 하나를 가지고 있다.
그리고 변수 mUser에 값을 넣어주기 위한 setUser 함수도 가지고 있다.
그런데 코드를 좀 자세히 보면 ActivityMainBinding클래스와 setUser함수 모두 abstract인 것을 볼 수 있다.
사실 빌드 시점에 ActivityMainBinding abstract 클래스와 이를 구현하는 ActivityMainBindingImpl 클래스가 생성된다. 그리고 실제 구현인 이 클래스에서 진행한다.
그리고 실제로 만들어진 바인딩 클래스를 잘 살펴보면 setText를 해주는 것을 볼 수 있다. 즉, 우리가 해야하는 작업들을 데이터 바인딩의 특정한 규칙에 따라 위와 같은 코드를 생성하는 것이다.
특정한 규칙에는 다음과 같은 세 종류가 있다. 시간이 되면 다음의 내용들도 다뤄보도록 하겠다.
- BindingAdapter
- Set Method
- BindingModel
정리
내가 데이터 바인딩을 사용하면서 느꼈던 장점은 다음과 같다.
- findViewById()를 호출하지 않아도, xml에 있는 뷰들을 만들어준다.
- data가 바뀌면 뷰 객체를 참조하지 않아도 View를 변경할 수 있다.
- xml 리소스를 보면 뷰에 어떤 데이터가 들어갈지 예상할 수 있다.
- 코드 가독성이 좋아지고, 불필요한 코드가 줄어든다.