2022. 1. 20. 21:46ㆍ개발을 파헤치다/Android
Data Binding은 XML 레이아웃에서 데이터를 사용할 수 있게 해 주는 라이브러리입니다.
MVC, MVP, MVVM에서 V는 View를 의미하는데요. 단순히 XML 레이아웃뿐 아니라 Activity, Fragment 등과 같이 레이아웃에 관련된 부분을 모두 View로 통칭하고 있습니다. Data Binding을 사용하면 기존에 View 레벨에서 코드로 작성하던 것을 대부분 XML 레이아웃에서 처리할 수 있는데요. 이를 통해 View 레벨 코드가 굉장히 깔끔해지고 유지보수 측면에도 꽤 편리하게 개발을 할 수 있습니다.
gradle 설정
먼저 Data Binding 설정부터 활성화시켜줘야 합니다.
App 레벨의 build.gradle에서 아래와 같이 수정해줍니다.
android {
compileSdkVersion 30
buildToolsVersion "30.0.3"
defaultConfig {
applicationId "com.example.jejuand"
minSdkVersion 19
targetSdkVersion 30
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
// 추가
dataBinding{
enabled = true
}
}
이렇게 설정하면 kotlin-kapt 플러그인이 적용되어야한다고 알림이 뜨게 됩니다.
이럴 경우 아래와 같이 Plugin id를 적용시켜줍니다.
마찬가지로 app 레벨의 build.gradle을 수정합니다.
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'kotlin-kapt' // 추가
}
Layout 설정
Data Binding을 사용하는 layout에는 특별히 레이아웃 설정을 추가해주어야 합니다.
기존 레이아웃과는 조금 다릅니다. XML상에서 View(Activity나 Fragment)에 의해 전달된 이벤트를 처리하는 표현식을 사용하기 때문입니다. 그래서 특정한 형식으로 구성됩니다.
아래와 같이 layout을 루트 태그로 시작하고 사용한 데이터들이 data 태그에 선언됩니다.
그러고 나서 흔히 사용하는 view 태그들이 나타나게 됩니다.
이렇게 Data Binding을 사용하기 위한 형식으로 구성을 해주어야 Data Binding 라이브러리에서
해당 레이아웃을 클래스 형태로 자동 생성해줄 수 있습니다. 이 Binding 클래스를 통해 변숫값들을 설정해서 레이아웃에 변화를 줄 수 있기 때문에 꼭 아래 형식으로 레이아웃을 만들어줍니다.
<?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"
>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Jejuand"
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>
Layout에서 데이터 다루기
변수 설정하기
<?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"
>
<data>
<import type="android.view.View" />
<variable
name="isEmpty"
type="Boolean"
/>
<variable
name="likeProductVal"
type="Integer"
/>
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/no_like_product_msg"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:fontFamily="@font/nanum_gothic"
android:textStyle="bold"
android:textSize="13.3sp"
android:textColor="#000000"
android:lineSpacingExtra="10.7sp"
android:text="@{likeProductVal}"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintVertical_bias="0.4"
android:visibility="@{ isEmpty ? View.VISIBLE : View.GONE, default=gone}"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
레이아웃에서 사용할 변수를 설정하려면 layout 태그 밑에 data 태그를 선언해줍니다.
그리고 그 밑에 variable 태그를 위와 같이 설정해주면 됩니다.
variable 태그에는 아래 두 가지 속성 값을 반드시 입력해주어야 합니다.
- name : 레이아웃에서 사용할 변수 이름을 의미합니다.
- type: 변수의 타입을 설정합니다. 직접 구현한 클래스부터 기본 타입까지 모두 설정이 가능합니다.
변수를 사용할 때에는 아래와 같은 양식으로 사용합니다.
android:text="@{변수명}"
기본 값 설정하기
<?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"
>
<data>
<import type="android.view.View" />
<variable
name="isEmpty"
type="Boolean"
/>
<variable
name="likeProductVal"
type="Integer"
/>
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/no_like_product_msg"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:fontFamily="@font/nanum_gothic"
android:textStyle="bold"
android:textSize="13.3sp"
android:textColor="#000000"
android:lineSpacingExtra="10.7sp"
android:text="@string/no_like_product_list_msg"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintVertical_bias="0.4"
android:visibility="@{ isEmpty ? View.VISIBLE : View.GONE, default=gone}"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
View의 요소에 기본값을 적용하려면 아래와 같이 사용합니다.
android:visibility="@{ isEmpty ? View.VISIBLE : View.GONE, default=gone}"
이 경우 visibility 요소에 대해 기본값을 정의하는 것이기 때문에 gone으로 해주었습니다. 이렇게 하려면 data 태그에 Android View가 import 돼있어야 합니다. 만약 visibility가 아닌 text인 경우에는 기본값으로 문자열을 설정할 수 있습니다. 즉, 요소에 따라 기본값이 달라지게 되니 그에 맞는 값을 넣어주어야 한다는 것입니다.
함수 사용하기
<?xml version="1.0" encoding="utf-8"?>
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<!-- Data Binding 선언 -->
<data>
<variable
name="product"
type="com.leoncorp.jejuand.model.store.Product"
/>
<variable
name="image"
type="com.leoncorp.jejuand.model.store.ProductImage"
/>
<variable
name="viewModel"
type="com.leoncorp.jejuand.viewmodel.RecyclerViewModel"
/>
<variable
name="adapter"
type="com.leoncorp.jejuand.ui.main.ProductListAdapter"
/>
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView
android:id="@+id/horizontal_product_discount_rate"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:text="@{adapter.calculateSaleRate(product.price, product.salePrice)}"
android:textStyle="bold"
android:textColor="#f2a530"
android:textSize="16.7sp"
android:fontFamily="@font/nanum_gothic"
app:layout_constraintStart_toStartOf="@id/horizontal_product_description"
app:layout_constraintEnd_toStartOf="@id/horizontal_product_discount_price"
app:layout_constraintTop_toBottomOf="@id/horizontal_product_description"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintHorizontal_chainStyle="packed"
android:layout_marginTop="@dimen/layout_default_margin"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
Data Binding에는 직접 구현한 클래스도 변수로 적용할 수 있습니다.
여기에서는 RecyclerView의 Adapter 클래스를 변수로 받아 내부 메서드를 호출하는 방법을 알아봅니다.
android:text="@{adapter.calculateSaleRate(product.price, product.salePrice)}"
이렇게 text 속성 값에 함수의 리턴 값을 설정할 수도 있습니다. 이 함수를 구현할 때 반드시 String을 리턴해야 합니다.
타입이 맞지 않으면 에러가 발생합니다.
조건문 사용하기
<?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"
>
<data>
<import type="android.view.View" />
<variable
name="isEmpty"
type="Boolean"
/>
<variable
name="likeProductVal"
type="Integer"
/>
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/main_like_tap_product_num"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/nanum_gothic"
android:textStyle="bold"
android:textSize="13.3sp"
android:textColor="#000000"
android:lineSpacingExtra="10.7sp"
android:text='@{"전체 " + String.valueOf(likeProductVal != null ? likeProductVal : 0) + "개"}'
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/main_like_tap_label"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintVertical_bias="0"
android:layout_marginTop="18.4dp"
android:layout_marginStart="16.6dp"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
Data Binding에서는 if문을 쓸 수는 없는데요. 대신 삼항 연산자를 통해 조건문을 구현할 수 있습니다.
android:text='@{"전체 " + String.valueOf(likeProductVal != null ? likeProductVal : 0) + "개"}'
위처럼 likeProductVal이 null값이 아닐 때에만 적용할 수 있게 조건을 적용할 수 있습니다. 이때 주의할 점은 데이터 타입인데요. 위의 조건으로 리턴되는 데이터 타입이 Integer이기 때문에 아래와 같은 에러가 날 수 있습니다.
failed to call Observer method
text 속성의 경우 string 타입이 적용되는 것이 맞습니다. 따라서 삼항 연산자를 통해 리턴된 값을 String으로 타입 변경해주면 됩니다.
Binding Adapter 사용하기
<?xml version="1.0" encoding="utf-8"?>
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<!-- Data Binding 선언 -->
<data>
<variable
name="product"
type="com.leoncorp.jejuand.model.store.Product"
/>
<variable
name="image"
type="com.leoncorp.jejuand.model.store.ProductImage"
/>
<variable
name="viewModel"
type="com.leoncorp.jejuand.viewmodel.RecyclerViewModel"
/>
<variable
name="adapter"
type="com.leoncorp.jejuand.ui.main.ProductListAdapter"
/>
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/horizontal_product_thumbnail"
android:layout_height="@dimen/home_tab_horizontal_product_height"
android:layout_width="@dimen/home_tab_horizontal_product_height"
android:scaleType="fitXY"
app:imageUrl="@{image.imageUrl}"
app:layout_constraintBottom_toTopOf="@id/horizontal_product_name"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0"
app:layout_constraintVertical_chainStyle="packed"
android:layout_marginEnd="12.3dp"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
Binding Adapter는 View의 특정 속성이 변경됐을 때 메서드가 알아서 실행되게 해 줍니다.
예를 들어, ImageView의 imageUrl 속성이 바뀌었을 때 이미지 라이브러리를 통해 URL을 로드해서 View에 적용하는 메서드를 자동 실행되게 구현해볼 수 있겠죠.
// Layout에서 이미지 URL값이 변동되었을 떄 호출되는 Binding Adapter
@BindingAdapter("app:imageUrl")
@JvmStatic
fun loadImage(targetView: ImageView, url: String?){
url?.let {
Glide.with(targetView.context)
.load(url)
.fitCenter()
.into(targetView)
}
}
BindingAdapter 어노테이션에 변경을 감지할 속성을 적어줍니다. 하나도 되고 여러 개가 될 수도 있습니다.
JvmStatic 어노테이션이 적용돼야 제대로 메서드가 실행될 수 있습니다.
첫 번째 파라미터에는 해당 View가 그다음에는 Binding Adapter에 설정한 속성 값이 오게 됩니다.
흔히 겪는 문제들
Unresolved BR 문제 해결하기
Data Binding을 사용하게 되면 BR이라는 클래스를 자동으로 생성합니다. 이 클래스 안에는 Data Binding에 사용된 결합 리소스들의 ID 정보가 포함되어있는데요. 간혹 Android Studio에서 이 BR 클래스를 제대로 인식하지 못하는 에러가 발생합니다.
이 경우 두 가지 문제가 있는지 살펴보아야 합니다.
- kotlin-kapt 플러그인 미설치
- 패키지가 제대로 import 되지 않은 경우
먼저 첫 번째의 경우 Kotlin을 사용한다는 전제하에 반드시 필요한 작업입니다.
App 단위의 build.gradle에 아래와 같이 플러그인 추가가 꼭 선행되어야 합니다.
plugins {
id 'com.android.application'
id 'kotlin-android'
// 아래 플러그인이 추가되어있어야함
id 'kotlin-kapt'
}
위와 같이 설정하고 Sync now를 눌렀는데도 문제가 해결이 되지 않는다면
패키지가 제대로 import 되지 않아서 문제가 발생할 확률이 큽니다.
이 경우 아래와 같이 직접 사용하는 클래스에 BR 클래스를 import 해줍니다.
// 직접 BR 클래스를 import
import androidx.databinding.library.baseAdapters.BR
abstract class BaseActivity<Binding: ViewDataBinding, ViewModel: BaseViewModel>
(@LayoutRes private val layout: Int) : AppCompatActivity() {
lateinit var binding: Binding
abstract val viewModel: ViewModel
...
View Binding을 인식하지 못하는 경우
class MainActivity : BaseActivity<ActivityMainBinding, MainViewModel>
(R.layout.activity_main) {
override val viewModel = MainViewModel()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
override fun subscribeObservers() {
TODO("Not yet implemented")
}
}
Databinding을 사용하게 되면 기존의 Layout을 View 클래스 형태로 만든 Binding 클래스들을 사용하게 됩니다. layout 이름에 Binding이 붙는 형식으로 클래스가 자동 생성되는데요.
예를 들어 activity_main.xml이라면 이와 대응되는 클래스는 ActivityMainBinding입니다.
가끔 안드로이드 스튜디오에서 이 Binding 클래스들이 인식이 안될 때가 있습니다.
이럴 경우에는 아래와 같이 대응하면 됩니다.
- 다시 빌드하기
- Layout 형식 수정
대부분의 경우 Build > Clean Project를 하고 Build > Rebuild Project를 하면 문제가 해결됩니다. 하지만 이렇게 했는데도 여전히 인식을 못한다면 Layout 형식을 다시 살펴봐야 합니다.
<?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"
>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.leoncorp.jejuand.ui.main.MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
Databinding을 사용하는 경우 데이터를 xml에서 사용하기 위해 특정 형식이 존재합니다.
layout 태그를 root에 적용시켜주는 것인데요. 이것이 돼있지 않으면 Binding 클래스가 제대로 생성이 되지 않습니다. 만약 미적용된 경우 이를 적용하고 다시 Build > Rebuild Project를 하면 제대로 Binding 클래스가 인식되는 것을 확인할 수 있습니다.
제 글을 끝까지 읽어주신 당신을 위해 선물을 준비했습니다.
6년간 풀스택 개발자로 일하면서 얻은 면접 노하우를 이 전자책에 담았습니다.
개발자 H 카카오 채널 친추하시면 무료로 바로 보내드리니 놓치지 말고 받아가세요. 아래 배너 클릭하시면 됩니다.
'개발을 파헤치다 > Android' 카테고리의 다른 글
[Android] 로컬 데이터베이스 Room 사용법 총정리 (0) | 2022.01.22 |
---|---|
[Android] Expandable List View 사용하기 (0) | 2022.01.21 |
[Android] Paging 라이브러리와 SwipeRefreshLayout 적용 가이드(feat. MVV) (0) | 2022.01.19 |
[Android] MVVM 아키텍어 View Model을 설계하는 꿀팁 (feat. 현업 풀스택 개발자) (0) | 2022.01.18 |
[Android] WebView 활용하기 - 고급편 (0) | 2022.01.13 |