----------
Android Jetpack의 라이브러리 일부로, 레이아웃 내부의 UI 구성요소를 데이터 소스와 결합시켜주는 역할을 한다.
코드를 거쳐와서 데이터를 표시한다기보다 사용자가 직접 선언해서 데이터를 가져오는 방식이다.
💡
findViewById()
를 통한 View Binding처럼 안드로이드를 초기에 배울 때 사용하는 방식과 달리@{}
를 사용한다.
----------
View Binding
는 매번 뷰의 계층구조를 탐색해야 하는 과정이 있으나,Data Binding
은 이를 생략할 수 있다.- 코드가 직관적으로 짤막하게 보여 가독성이 증가한다.
- 따로 변수를 관측할 필요 없이 자동으로 업데이트해
LiveData
와 함께 사용하기 용이하다.양방향 데이터 바인딩
을 지원한다.
위와 같은 다양한 장점들 때문에 우리는 Data Binding
을 사용하는 것이다!!
간단한 프로젝트에서는 View Binding이 사용될 수 있으나, 대규모 프로젝트나 MVVM 패턴이 적용된다면 Data Binding을 사용하는 것이 권장된다.
----------
Data Binding
은 View Binding의 상위 개념으로 앞서 말한 것처럼 사용자가 직접적으로 선언해 데이터와 연결해준다.
View Binding
은 xml에서 기본적으로 우리가 볼 수 있는 뷰를 만들고 .kt 파일을 참조하여 데이터를 설정한다. 안드로이드 3.6 이후에 도입된 기술임을 참고로 알아두자.
----------
1. build.gradle 파일로 이동해 다음과 같은 코드를 추가한다.
plugins {
id 'kotlin-kapt'
}
android {
buildFeatures {
dataBinding = true
}
}
2. xml의 최상위 부분을 <layout> </layout>
형태로 만들고 기존에 사용하던 viewModel
과 연동해준다.
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="viewModel"
type="com.myapp.data.HomeViewModel" />
</data>
<ConstraintLayout>
<!-- 기존에 사용하던 UI 항목들 -->
</ConstraintLayout>
</layout>
3. 예시로 한 TextView에 적용해보자!
<TextView
android:id="@+id/tvAppTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text='@{viewModel.title}'
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
위의 android:text
에서 Data Binding이 적용됨을 알 수 있다. 이제 HomeViewModel의 title이라는 변수에 변동이 생길 때마다 tvAppTitle이라는 id를 가진 TextView의 text또한 변화할 것이다.
💡 여기까지가 대부분 알고 있는 Data Binding의 기초이다.
----------
바인딩 어댑터는 값을 설정하기 위해 적절한 프레임워크를 호출합니다. 공식문서의 예시로는 setOnClickListener()
메서드를 호출하여 이벤트 리스너를 설정하는 것 등이 있습니다.
데이터 바인딩 라이브러리를 사용하면 어댑터를 사용하여 값을 설정하는 데 호출되는 메서드를 지정하고, 자체적인 바인딩 로직을 제공하고, 반환된 객체의 유형을 지정할 수 있습니다.
----------
자체적인 바인딩 로직을 사용할 때, 일반적으로 안드로이드 프레임워크에서 규격화된 네이밍을 사용하여 호환 메서드를 자동으로 찾도록 구현되어 있습니다.
<androidx.drawerlayout.widget.DrawerLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:scrimColor="@{@color/scrim}"
app:drawerListener="@{fragment.drawerListener}">
하지만 그렇지 않은 경우에는 다음과 같이 @BindingMethods
주석을 사용하여 속성을 연결할 수 있습니다. 바인딩 메서드는 앱의 어떤 클래스에도 추가할 수 있는 주석입니다.
@BindingMethods(value = [
BindingMethod(
type = android.widget.ImageView::class,
attribute = "android:tint",
method = "setImageTintList")])
----------
하지만 안드로이드가 모든 어댑터를 지원해줄 수는 없죠!! 이럴때는 바인딩 어댑터를 직접 커스텀해줘야 합니다.
예를 들어, android:paddingLeft
속성에 대한 연결된 setter가 없습니다. 대신에 setPadding(left, top, right, bottom)
메서드가 제공됩니다. BindingAdapter 주석을 사용한 정적 바인딩 어댑터 메서드를 사용하면 속성에 대한 setter가 호출되는 방식을 사용자 지정하여 다음과 같이 사용할 수 있습니다.
@BindingAdapter("android:paddingLeft")
fun setPaddingLeft(view: View, padding: Int) {
view.setPadding(padding,
view.getPaddingTop(),
view.getPaddingRight(),
view.getPaddingBottom())
}
다음과 같이 여러 속성을 수신할 수도 있습니다.
@BindingAdapter("imageUrl", "error")
fun loadImage(view: ImageView, url: String, error: Drawable) {
Picasso.get().load(url).error(error).into(view)
}
레이아웃에서 어댑터를 사용할 수 있습니다. @drawable/venueError는 앱 내의 리소스를 가리킵니다. 리소스를 @{}로 둘러싸면 바인딩 표현식이 됩니다.
<ImageView app:imageUrl="@{venue.imageUrl}" app:error="@{@drawable/venueError}" />
그 밖에도 다양한 사용 형태가 있는데, 용도에 따라 구글링을 하는 것이 빠를듯 합니다!
아래는 몇 가지 Custom Binding Adapter에 대한 참고사항입니다.
- 무작정 바인딩 어댑터를 커스텀하는 것은 바인딩 어댑터의 증가로 해당 함수를 관리해야 하는 유지보수 측면에서 불리할 수 있습니다.
- 바인딩 어댑터에서 반복되는 코드는 최대한 지양해야 합니다.
- 바인딩 어댑터는 충돌이 발생할 때 기본 데이터 바인딩 어댑터를 무시합니다.
----------
단방향 바인딩을 사용하면 다음과 같이 속성에 값을 설정하고 해당 속성의 변경에 반응하는 리스너를 설정할 수 있습니다.
<CheckBox
android:id="@+id/rememberMeCheckBox"
android:checked="@{viewmodel.rememberMe}"
android:onCheckedChanged="@{viewmodel.rememberMeChanged}"
/>
양방향 바인딩을 사용하면 다음과 같이 코드를 수정할 수 있습니다.
<CheckBox
android:id="@+id/rememberMeCheckBox"
android:checked="@={viewmodel.rememberMe}"
/>
어디가 바뀌었는지 보이시나요?! 잘 보시면 @={}
로 바뀐 것을 확인하실 수 있을겁니다. 이 선언은 속성의 데이터 변경을 받아오고 동시에 사용자 업데이트를 수신합니다.
즉, 단방향 데이터 바인딩은 대응되는 데이터를 읽어들이는 것이고, 양방향은 대응되는 데이터를 갱신해주는 역할을 합니다.
----------
안드로이드 공식문서에는 다음과 같은 예시 코드가 있습니다. 위에서 나온 CheckBox의 코드와 연결됩니다.
class LoginViewModel : BaseObservable {
// val data = ...
@Bindable
fun getRememberMe(): Boolean {
return data.rememberMe
}
fun setRememberMe(value: Boolean) {
// Avoids infinite loops.
if (data.rememberMe != value) {
data.rememberMe = value
// React to the change.
saveData()
// Notify observers of a new value.
notifyPropertyChanged(BR.remember_me)
}
}
}
백업 데이터의 변경에 반응하기 위해 레이아웃 변수를 Observable
의 구현체로 만들고 BaseObservable 하위에 @Bindable
주석을 사용했습니다.
이 부분은 아직 공부가 부족하여 추가적인 공부가 필요할 듯합니다. 이 게시글에서는 단방향 데이터 바인딩 뿐만 아니라 양방향도 있구나~! 정도만 알아가도록 하겠습니다.
추가적인 정보가 필요하시다면 다음은 안드로이드 공식문서에 나온 내용입니다.
Observable 데이터 객체와 함께 작업하는 방법에 대한 자세한 내용은 BaseObservable 및 @Bindable을 참조하십시오.
----------
아래 내용은 추후 게시글에서 다시 다루도록 하겠습니다!!