Android Jetpack의 구성요소 중 하나.
지난 번에 다뤘던 ViewBinding과 같이 쓰인다고 생각하면 된다.
공식문서에는 레이아웃에 선언되어 있는 뷰에 직접 구성요소를 결합한다고 정의되어있다.
원래 view에 특정한 객체의 변수를 보여주려면, 소스 코드에서 다음과 같이 정의했어야 했다.
(TextView라고 가정해보자.)
- findViewById / Kotlin Extension으로 텍스트뷰 객체를 가져온다.
- setText / text = model.(변수이름)
데이터 바인딩을 사용하면, 위와 같은 내용을 모두 생략할 수 있게되어서 다음과 같은 이점들이 생기게 된다.
- 코드에서 따로 뷰에 객체의 원소 or 특정 값을 집어넣는 일이 사라져서 코드가 간결해진다.
- 앱 성능이 향상되고 메모리 누수가 사라진다.
- null 위험성이 낮아진다.
App 수준의 build.gradle에서 dataBinding enabled = true로 설정해준다.
viewBinding도 같이 쓸 예정이므로 viewBinding도 enabled 해준다.
그리고 Sync Now를 해준다.
(물론, ViewBinding은 필수는 아니다. DataBindingUtil 객체를 사용하면 되긴 한다.)
이제 코드를 살펴보자.
지난 번 뷰 바인딩을 했을 때와 비슷한 구조이다.
이미지가 추가됐고, 레이아웃 파일에 사용할 뷰모델 클래스가 추가됐다.
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
메인 액티비티 화면구성.
프래그먼트를 담을 container 레이아웃만 존재한다.
<?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">
<data>
<import type="androidx.core.content.ContextCompat"/>
<variable
name="view_model"
type="com.khs.viewbindingdatabindingexample.model.ViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/image_view"
android:layout_width="125dp"
android:layout_height="125dp"
android:src="@{ContextCompat.getDrawable(context, view_model.image)}"
app:layout_constraintVertical_chainStyle="packed"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toTopOf="@id/edit_text"/>
<EditText
android:id="@+id/edit_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:layout_marginHorizontal="20dp"
android:text="@={view_model.content}"
app:layout_constraintVertical_chainStyle="packed"
app:layout_constraintTop_toBottomOf="@+id/image_view"
app:layout_constraintBottom_toTopOf="@id/button"/>
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:text="Click Here"
app:layout_constraintVertical_chainStyle="packed"
app:layout_constraintTop_toBottomOf="@+id/edit_text"
app:layout_constraintBottom_toTopOf="@+id/text_view"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<TextView
android:id="@+id/text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:text="@{view_model.content}"
android:textColor="@color/purple_500"
android:textSize="21sp"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/button" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
프래그먼트 레이아웃 파일을 살펴보면, 원래는 제약 레이아웃으로 감싸는 구조가
<layout></layout> 태그가 추가되었다.
이는 <data></data> 태그를 사용하기 위함인데,
이 <data>태그 안에서 내가 레이아웃에 사용할 데이터를 선언하고 사용할 수 있다.
내가 만들어 놓은 ViewModel 클래스를 사용할 수 있을 뿐만 아니라, 안드로이드에서 제공하는 라이브러리
도 사용이 가능하다.
나같은 경우에는, 뷰모델 객체에 Int형 image ID(R.drawable.xxx...형태)를 만들어놓고
이 이미지 ID를 바로 뷰에 나타내기 위해 Drawable로 변환해주려고 ContextCompat을 import했다.
그러면 ViewModel 클래스는 어떤 형태인가?
현재 다음과 같은 형태로 구성되어 있다.
image는 위에서 설명했듯이 ResId를 넣어줄 것이고,
content는 문자열이다.
이 때, 양방향 데이터 바인딩 사용을 위해 MutableLiveData를 사용해 볼 것이다.
LiveData에 관해서는 나중에 추가적으로 살펴볼 예정이다.
그리고 ImageView, TextView를 살펴보자.
특히 ImageView에 경우, 처음 보는 사람은 보기 힘들 수 있지만 ContextCompat을
선언한 view_model.image를 getDrawable()메서드의 매개변수로 넘겨주고 있다.
원래같았으면 findViewById 사용, 혹은 ViewBinding을 사용해서 이미지뷰 객체를 가져온 후에
소스 코드에서 src를 설정해줬어야 했을 것이다.
데이터 바인딩을 사용할 경우 그럴 필요 없이 Layout에서 바로 대입할 수가 있다.
텍스트뷰도 마찬가지이다.
view_model의 content 변수를 text에 바로 대입한다.
데이터 바인딩에서 <variable> 태그로 선언한 변수에 접근하는 방식은
"@{ 접근할 변수명 }" 으로 접근할 수 있다.
위 코드처럼 만들고 끝낼 수 있지만, 데이터 바인딩에서 가장 많이 사용하는 기능인 visibillity를 조작하는 법을 코드에 추가해보겠다.
버튼을 누를 때마다 이미지를 보이게하고 안보이게 하는 기능을 추가해 보겠다.
데이터 클래스 ViewModel에 showImage라는 Boolean형 변수를 추가했다.
해당 변수가 true일 때는, 이미지가 보이게하고 false일 때엔 이미지를 보이지 않게 한다.
버튼을 눌렀을 때 이 변수를 바꿔줄 것이다.
onClick() 메서드를 다음과 같이 만들어준다.
LiveData에서는 변수명.value = 값 / 변수명.postValue(값) 으로 값을 할당할 수 있다.
동기, 비동기와 연관이 있는 부분인데 나중에 LiveData에 대해 정리할 때 더 자세히 알아보겠다.이 코드에서는 어느 코드를 써도 큰 차이는 없다.
다시 프래그먼트 레이아웃 xml파일로 돌아와서
레이아웃 XML 파일에서 android.view.View를 Import한다. View.VISIBLE, View.INVISIBLE 변수를 사용하기 위함이다.
이미지뷰에 visibillity 관련 속성을 추가해준다. 뷰모델의 showImage 변수에 따라 보여줄지 숨길 지 결정한다. 이는 삼항 연산자로 구현한다.
그리고 버튼의 onClick 속성에 뷰모델에 정의했던 onClick을 선언해준다.
소스 코드로 넘어가기 전에 마지막으로 Edittext를 살펴보자.
Edittext에는 다른 것들과는 다르게 @ 옆에 =이 붙어있다.
이는 양방향 데이터 바인딩으로,
Edittext -> 변수
변수 -> Edittext
로 값이 둘 다 넘길 수가 있게 구현하는 것이다.
위 문서에 설명이 되어 있지만 여기서는 자세하게는 다뤄보지 않겠다.
그냥 간단하게 위에서 설명했듯이
View ←→ ViewModel의 값이 양방향으로 전달되는 것이다.
이 때, 헷갈리면 안되는게 ViewModel은 View를 몰라야 한다. (MVVM에 대해 나중에 정리하겠다.)
View에서 ViewModel에 값을 넣어줄 수 있다는 게 추가됐다는 점이다.
(원래는 View가 ViewModel의 값을 받아서 보여주기만, 또는 특정 동작을 하는게 전부였다.)
package com.khs.viewbindingdatabindingexample
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import com.khs.viewbindingdatabindingexample.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity() {
lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
setFragment()
}
private fun setFragment() {
supportFragmentManager.beginTransaction().replace(binding.container.id, MainFragment()).commit()
}
}
메인 액티비티 소스코드.
특별한 것은 없고, supportFragmentManager를 통해 최초 프래그먼트만 띄워준다.
package com.khs.viewbindingdatabindingexample
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.fragment.app.Fragment
import androidx.lifecycle.MutableLiveData
import com.khs.viewbindingdatabindingexample.databinding.FragmentMainBinding
import com.khs.viewbindingdatabindingexample.model.ViewModel
class MainFragment: Fragment() {
lateinit var binding: FragmentMainBinding
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = FragmentMainBinding.inflate(inflater, container, false)
setUpView()
setUpObserver()
return binding.root
}
private fun setUpView() {
binding.viewModel = ViewModel(R.drawable.image, MutableLiveData(""), MutableLiveData(true))
}
private fun setUpObserver() {
binding.viewModel?.content?.observe(viewLifecycleOwner) {
binding.invalidateAll()
}
binding.viewModel?.showImage?.observe(viewLifecycleOwner) {
binding.invalidateAll()
}
}
}
최초 프래그먼트 소스 코드.
binding 객체를 초기화하고, setUpView() 메서드를 호출한다.
setUpView() 메서드는 다음과 같은 로직이다.
- 뷰모델 객체 viewModel을 초기화한다. 매개변수로 (image, 최초 Edittext의 값, 최초 이미지를 보여줄건지에 대한 Boolean 값) 을 넘겨준다. 나는 (image, "", true)로 넘겨줬다.
라이브 데이터는 observe라는 기능을 사용해서 만일 해당 값이 바뀌었을 경우, 특정 코드가 실행되도록 만들 수 있다.
역시 지금 자세하게는 다루지 않고 그냥 이런게 있구나로 넘어가겠다.
값이 바뀔 때 마다 binding 객체의 invalidateAll() 메서드를 호출해서 인플레이션된 레이아웃을 다시 갱신한다.
이렇게하면
버튼을 눌렀을 때 showImage 변수가 바뀌므로 observe가 작동해서 뷰가 다시 갱신될 것이고 이미지가 나타났다가 사라졌다가를 할것이다.
Edittext에서 글자를 입력할 때마다 content 값이 바뀌므로 observe가 작동해서 뷰가 다시 갱신될 것이고 EditText에 쓰여진 글자가 TextView에 나타날 것이다.
이렇게 데이터 바인딩에 대해서 알아봤다.
만일 위 코드에서 나온 기능들을 구현하려면 원래는
findViewByID로 수정할 뷰를 찾고 View의 기능을 사용해서 버튼의 onClickListener를 구현해서 만들었거나
Edittext의 onChangedListener를 구현해서 만들어야 했을 것이다.
데이터 바인딩을 사용하면 이렇게 쉽게 구현할 수 있다.
사실 invalidateAll()를 사용하는 것은 무식한 방법이다.
더 좋은 방법은 그냥 해당 값이 바뀌었을 때 직접 UI를 바꿔주는 게 좋다고 한다.
viewLifeCycleOwner = this로 설정하면, LiveData의 값 변화를 DataBinding에서 자동으로 인지해준다.
해당 부분은 나중에 LiveData를 정리할 때 정리할 것으로 이 프로젝트에서는 다루지 않았다.
데이터 바인딩은 지금 안드로이드를 하는 개발자라면 필수적으로 알아야 할 기능이다.
나도 아직도 데이터 바인딩을 유연하게 사용하지 못하고 있다.
(컴포즈 기능이 추가돼서 사실 언제까지 데이터 바인딩이 유행일 지는 모르겠다.)
샘플 프로젝트는 위에서 확인이 가능하다.
'안드로이드 > 개발 관련' 카테고리의 다른 글
[Kotlin] 안드로이드 리사이클러 뷰 새로고침 기능 사용해보기. SwipeRefreshLayout 사용. (1) | 2021.10.14 |
---|---|
[Kotlin] 안드로이드 리사이클러뷰 안에 리사이클러뷰 사용해보기, 이중 리사이클러뷰 (Feat. 뷰 바인딩, 데이터 바인딩) (0) | 2021.10.13 |
[Kotlin] 안드로이드 뷰 바인딩, 뷰 결합, View Binding (2) | 2021.09.29 |
[Android] 정리해야 할 개발기술 목록 - 계속 업데이트중 (0) | 2021.08.11 |
[Android][Java] 프래그먼트 뒤로가기 누르면 원상복구 + 프래그먼트 데이터 전달 (0) | 2021.08.01 |