* 본 내용은 스스로 학습한 내용을 정리한 게시글입니다. *
데이터바인딩(DataBinding)이란?
기존에는 레이아웃의 뷰를 참조하기 위해서는 아래와 같이 findViewById() 메서드를 사용하였다.
val button: Button = findViewById(R.id.my_button)
하지만 이러한 방식은 같은 한 화면에는 여러 개의 뷰가 존재하는 액티비티 클래스에
동일한 메서드를 너무나도 많이 호출시키게 만들어 클린 코드에 적합하지 못하다고 판단하였다.
그래서 등장하게 된 것이 바로 데이터바인딩(DataBinding)이다.
데이터바인딩을 사용했을 때의 장점
1. findViewById를 연속적으로 사용할 필요 없이 binding 객체 하나만 있으면 뷰를 참조할 수 있다.
2. RecyclerView의 Item에 DTO 모델을 bind 해주기만 하면 된다.
3. Observer 패턴을 적용하여 실시간으로 데이터를 업데이트할 수 있다.
설정 방법
데이터 바인딩을 사용하기 위해서는 build.gradle(app level)로 들어가 아래와 같이 작성해준다.
android {
...
dataBinding {
enabled = true
}
...
}
만약 코틀린을 사용한다면 아래의 코드를 추가로 작성해준다.
여기서 gradleVersion에는 본인 프로젝트의 gradle 버전을 넣어준다.
plugins {
id 'kotlin-kapt'
}
...
dependencies {
...
kapt 'com.android.databinding:compiler:$gradleVersion'
...
}
위 코드를 작성하고 Sync Now를 눌렀을 때 정상적으로 빌드가 되었다면 성공이다.
이제부터 바인딩을 시작해보자!
우선 XML로 이동하여 상위 뷰 태그에 커서를 대고 Ctrl+Enter를 눌러보면 위와 같은 옵션들이 나오는데
'Convert to data binding layout'을 선택해준다.
<!--activity_main.xml-->
<?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>
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
그럼 이렇게 <layout/> 태그로 한 번 더 감싸지는 모습을 볼 수 있다.
그 하위에 <data/> 태그에 대해서는 아래에서 자세히 다루도록 하겠다.
이제 Activity로 이동해서 바인딩 객체를 생성해주어야 한다.
package com.practice.buna.mvvm
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.databinding.DataBindingUtil
import com.practice.buna.mvvm.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity() {
lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 기존의 setContentView 사용 안 함.
//setContentView(R.layout.activity_main)
// 데이터 바인딩 객체 생성
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
}
}
데이터바인딩을 하면서 기본으로 작성되어 있던 setContentView() 메서드는 더 이상 사용하지 않는다.
대신에 DatabindingUtil.setContentView()를 호출해주는데, 첫 번째 인자로는 Activity 객체, 두 번째로는 레이아웃 id를 입력한다.
그럼 사용하기 위한 세팅은 모두 끝이 났다.
이제 예제를 통해 살펴보자!
<?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>
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity"
android:background="#FFF27F">
<ImageView
android:id="@+id/camera_button"
android:layout_width="100dp"
android:layout_height="100dp"
android:src="@android:drawable/ic_menu_camera"
android:clickable="true"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintBottom_toTopOf="@+id/camera_name"/>
<TextView
android:id="@+id/camera_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:textColor="@color/black"
android:textSize="20sp"
android:text="카메라 버튼"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
위 xml은 중앙에 이미지 뷰 하나와 텍스트뷰 하나가 있는 코드이다.
각각 id를 camera_button, camera_name로 지정하였다.
그럼 이렇게 지정한 뷰 ID는 '_ (Under Scope)'를 기준으로 대문자로 바뀌어, 레이아웃에 접근할 수 있도록 설정된다.
코틀린의 with안에 binding을 넣어주어, 기존의 길고 보기 싫던 findViewById를 반복해서 사용할 필요가 없어졌다.
만약 카메라 버튼을 눌렀을 때 cameraName 텍스트뷰의 문자가 바뀌게 하기 위해서는 어떻게 해야 할까??
그러기 위해서는 여러 방법이 있겠지만, 조금 더 MVVM 패턴스럽게 접근하기 위해
위에서 언급했던 <data/> 태그와 ViewModel, LiveData를 활용해보자.
dependencies {
...
def lifecycle_version = "2.3.0"
// ViewModel
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
// LiveData
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
...
}
이 기능들을 사용하기 위해 아까처럼 gradle(app level) 파일로 이동하여 dependencies 태그 안에 위 코드를 삽입해준다.
package com.practice.buna.mvvm.feature.viewmodel
import android.view.View
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
class MainViewModel : ViewModel() {
// 카메라 버튼이 눌렸는지 확인하기 위한 MutableLiveData
var isPressed = MutableLiveData<Boolean>(false)
// 카메라 눌림 감지를 메서드
fun onClickCameraBtn(v: View) {
// 버튼 눌림 상태 Toggle
isPressed.value = isPressed.value?.not()
}
}
부모 클래스로 ViewModel()을 사용하는 뷰 모델을 만들어준다.
그리고 버튼이 눌렸는지 감지하기 위한 isPressed 옵저버 변수를 하나 생성해준다.
만약 버튼이 눌리면 onClickCameraBtn() 메서드가 호출되어 변수를 토글(Toggle)시켜줄 것이다.
<?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>
<variable
name="viewModel"
type="com.practice.buna.mvvm.feature.viewmodel.MainViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".feature.MainActivity"
android:background="#FFF27F">
<ImageView
android:id="@+id/camera_button"
android:layout_width="100dp"
android:layout_height="100dp"
android:src="@android:drawable/ic_menu_camera"
android:clickable="true"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintBottom_toTopOf="@+id/camera_name"
android:onClick="@{viewModel::onClickCameraBtn}"/>
...
</layout>
xml로 이동하여 data 태그 안에 뷰 모델을 작성해준다.
'name'에는 xml 내에서 사용할 객체의 변수명으로 본인이 사용하기 편한 이름을 작성해주면 된다.
'type'에는 사용하고자 하는 클래스를 작성해준다.
그리고 ImageView 맨 아래 부분을 보면 알 수 있듯이, onClick에 뷰 모델의 onClickCameraBtn()를 추가하였다.
만약 이 ImageView를 클릭하면 해당 함수를 실행시키겠다. 라는 의미이다.
마지막으로 Activity로 이동하여 ViewModel을 생성하고 xml에 전달해주어야 할 일이 남아있다.
class MainActivity : AppCompatActivity() {
lateinit var binding: ActivityMainBinding
lateinit var mainViewModel: MainViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 기존의 setContentView 사용 안 함.
//setContentView(R.layout.activity_main)
// 데이터 바인딩 객체 생성
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
// 뷰모델 생성
mainViewModel = ViewModelProvider(this).get(MainViewModel::class.java)
// isPressed 변수 관찰
mainViewModel.isPressed.observe(this) { isPressed ->
if(isPressed) {
binding.cameraName.text = "카메라 버튼 눌림!"
} else {
binding.cameraName.text = "카메라 버튼 뗌!"
}
}
with(binding) {
viewModel = mainViewModel // xml의 viewModel과 View에서 생성한 viewModel을 바인딩
}
}
}
ViewModel 객체를 생성하는 데에는 다양한 방법이 있지만, 이 포스팅에서는 ViewModelProvider를 사용하여 생성해주겠다.
그리고 뷰 모델의 isPressed 변수를 관찰하면 되는데, 버튼을 누를 때마다 눌림 상태가 변경되어 텍스트를 업데이트해준다!
이렇게 생성한 뷰 모델을 레이아웃의 viewModel로 전달해주면 끝이다.
한 번 AVD를 실행시켜보자!
![]() |
![]() |
예상대로 클릭 전에는 "카메라 버튼 뗌!" 이라는 텍스트가 나오고,
버튼을 한 번 클릭하면 "카메라 버튼 눌림!" 텍스트로 업데이트된다.
이번 포스팅에서는 LiveData와 ViewModel, DataBinding의 기본적인 기능에 대해 다루어보았습니다.
내용에 오류가 있거나, 질문이 있으신 분들은 댓글을 남겨주시면 감사하겠습니다! 😊
'Android > MVVM' 카테고리의 다른 글
[Android][0] MVVM 패턴이란? (1) | 2021.11.28 |
---|