DataBinding(데이터바인딩)은 간단하게 xml파일에 Data를 연결(binding)해서 사용할 수 있게 도와주며 Android JetPack 라이브러리의 하나의 기능 입니다.
즉, 데이터바인딩은 애플리케이션 로직과 레이아웃을 binding하는 데 필요한 글루 코드를 최소화합니다.
세팅방법:
MVVM AAC Databinding 사용법(1.세팅편)
데이터 바인딩을 위해서는 최소 3 영역에 대한 수정이 필요하다
1. (뷰 모델을 이해하도록) 레이아웃
2. (데이터 바인딩 레이아웃을 사용하도록) View에서 레이아웃을 초기화하는 코드
(내부 데이터와 동기화될 수 있는 객체인) 뷰 모델
이 최상위 요소로 추가되어야한다. (gradle에서 databinding을 활성화 해야한다)
<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.ViewModel" />
</data>
<ConstraintLayout... /> <!-- UI layout's root element -->
</layout>
엑티비티에서 데이터 바인딩을 할 레이아웃을 사용하기 위해 초기화하는(inflate) 방식도 바꿔야한다.
//기존은 setContentView 메서드를 onCreate에서 호출하는 형태입니다.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
//데이터 바인딩을 사용할때는 다른 형식으로 바꿔줘야한다.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding: ActivityMainBinding = DatabindingUtil.setContentView(this,
R.layout.activity_main)
}
ActivityMainBinding은 R.layout.activity_main으로부터 자동 생성이 되는 파일이다. activity_main이었던 이름을 ActivityMain의 형태로 바꾸고 뒤에 Binding을 붙여 객체를 생성한다.
뷰 모델 객체 생성
inner class ViewModel{
var title = ObservableField<String>()
}
ObservableField는 값이 바뀌면 연동된 DataBinding에게 정보를 전달하고 이를 이용해 UI를 업데이트할 수 있다.
레이아웃에서 뷰 모델을 참조하기 위해서는 변수를 만들어야한다. 아래 를 넣고 그 아리 변수들을 의 형태로 넣는다. variable은 두 가지 속성을 가지는데 name 과 type을 갖고있다.(위 레이아웃 코드 참조)
그리고 레이아웃이 참조하는 변수는 자동으로 대입되지 않기 때문에 MainActivity (데이터바인딩을 할 Activity)의 onCreate으로 돌아와 연결시켜야한다.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding: ActivityMainBinding = DatabindingUtil.setContentView(this,R.layout.activity_main)
binding.viewModel = ViewModel()
}
뷰 모델 데이터를 조금 더 자유자재로 변경하기 위해서는 뷰 모델을 액티비니틔 필드로 바꿀 수 있다.
private val viewModel: ViewModel by lazy{
ViewModel()
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding: ActivityMainBinding = DatabindingUtil.setContentView(this,R.layout.activity_main)
binding.viewModel = viewModel
}
여기서 lazy {코드}은 해당 변수/필드가 실제로 사용될 때 코드 블록의 내용으로 초기화를 진행한다. viewModel이란 필드에 실제로 접근하게 될때 lazy {ViewModel()} 의 코드 블록인 viewModel()이 호출되어 그 값이 사용된다.
lazy 블록은 쵝화 시점과 상관없이 필드의 정의에 초기화 코드를 보여줄 수 있어 인식하기 쉽다.
뷰 모델에 값 설정하기
binding.viewModel = viewModel
viewModel.title.set("Hello World")
직관적으로 문자열 viewModel.title = "Hello World"로 대입할 것처럼 보이지만 String 타입이 아닌 ObservableField 타입이기 때문에 set 메서드로 설정해야 한다.
뷰 모댈에 넣은 Hello World 글자를 화면에 출력하기
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{viewModel.title}"
android:onClick="sayHello"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
뷰 모델 값을 대입하기 위해 @{}를 사용한다.
또한 onClick 핸들러에 등록하면 액티비니에 이벤트를 처리할 메서드를 추가해 사용할 수 있다.
fun sayHello(view: View){
Toast.makeText(this,"Hello".Toast.LENGTH_SHORT).show()
}
이벤트 핸들러가 액티비티에 있을 필요는 없다. 즉 이벤트 핸들러를 뷰 모델로 옮길 수 있다.
inner class ViewModel{
private var count = 0
var title: ObservableField<String> = ObservableField()
fun sayHello(){
count++
title.set("안녕하세요" + count + "번째 클릭입니다.")
}
}
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{viewModel.title}"
android:onClick="@{()->viewModel.sayHello()}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
LiveData는 Data의 변경을 관찰 할 수 있는 Data Holder 클래스 입니다.
일반적인 Observable과는 다르게 LiveData는 안드로이드 생명주기(LifeCycle)를 알고 있습니다. (Lifecycle-Aware)
즉, 액티비티나, 프레그먼트, 서비스 등과 같은 안드로이드 컴포넌트의 생명주기(Lifecycle)를 인식하며 그에따라
LiveData는 활성상태(active)일때만 데이터를 업데이트(Update) 합니다.
활성상태란 STARTED 또는 RESUMED 를 의미합니다.
또한 LiveData 객체는 Observer 객체와 함께 사용됩니다. LiveData가 가지고 있는 데이터에 어떠한 변화가 일어날 경우, LiveData는 등록된 Observer 객체에 변화를 알려주고, Observer의 onChanged() 메소드가 실행되게 됩니다.
LiveData is an observable data holder.
This allows the components in your app to be able to observe LiveData objects for changes without creating explicit and rigid dependency paths between them.
//1
val liveData: MutableLiveData<Any>()
//2
liveData.value = "Hello"
//3
liveData.observe(this, Observer {
//UI update
})
예제:
class TestLiveDataViewModel : ViewModel() {
// String 타입의 MutableLiveData 생성, by lazy로 초기화는 뒤에
val textValue: MutableLiveData<String> by lazy {
MutableLiveData<String>()
}
}
class MainActivity : AppCompatActivity() {
// 전역 변수로 ViewModel lateinit 세팅
private lateinit var model: TestLiveDataViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// ViewModel을 가져옵니다.
model = ViewModelProvider(this).get(TestLiveDataViewModel::class.java)
// Observer를 생성한 뒤 UI에 업데이트 시켜 줍니다.
val testObserver = Observer<String> { textValue ->
// 현재 MainActivity에는 TextView가 하나만 존재합니다.
// 다른 데이터를 받는 UI 컴포넌트가 있다면 같이 세팅 해줍니다.
tv_livedata_test.text = textValue
}
// LiveData를 Observer를 이용해 관찰하고
// 현재 Activity 및 Observer를 LifecycleOwner로 전달합니다.
model.textValue.observe(this, testObserver)
}
}
화면에서 버튼을 클릭하면 viewModel로 연결되어 counter의 값을 올려주고 그것을 MainActivity에서 observe해서 UI를 갱신하는 샘플
class MainActivity : AppCompatActivity() {
lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
val viewModel = ViewModelProvider(this)[MainViewModel::class.java]
binding.viewModel = viewModel
binding.lifecycleOwner = this
viewModel.counter.observe(this, Observer {
counter -> binding.text.text = counter.toString()
})
}
}
class MainViewModel: ViewModel() {
var counter : MutableLiveData<Int> = MutableLiveData()
//counter 초기화
init {counter.value = 0}
fun clickBtn(){
counter.value = counter.value?.plus(1)
println(counter.value)
}
}
<?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.example.mvvmlivedata.MainViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="click"
android:onClick="@{()->viewModel.clickBtn()}"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>