Data Binding - leekiljae2019/MyKotlinDocs GitHub Wiki
Android API Level 7 ์ดํ๋ถํฐ๋ ๋ฐ์ดํฐ ๋ฐ์ธ๋ฉ์ ์ง์ํ๋ค. DataBinding์ด๋ xml์ ๋ฐ์ดํฐ๋ฅผ ๋ฐ์ธ๋ฉํ์ฌ ๋ถํ์ํ ์ฝ๋๋ฅผ ์ค์ด๋ ๋ฐฉ๋ฒ์ผ๋ก, ๋ณดํต MVP or MVVM ํจํด์ ๊ตฌํ ํ ๋ ์ฌ์ฉํ๋ค. DataBinding ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ ํ๋ก๊ทธ๋๋งคํฑ ๋ฐฉ์์ด ์๋๋ผ ์ ์ธ์ ํ์์ผ๋ก ๋ ์ด์์์ UI ๊ตฌ์ฑ์์๋ฅผ ์ฑ์ ๋ฐ์ดํฐ ์์ค์ ๊ฒฐํฉํ ์ ์๋ ์ง์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ด๋ค. ๋ ์ด์์์ ํํ UI ํ๋ ์์ํฌ ๋ฉ์๋๋ฅผ ํธ์ถํ๋ ์ฝ๋๊ฐ ํฌํจ๋ ํ๋์์ ์ ์๋๋ค. ์๋ฅผ ๋ค์ด ์๋ ์ฝ๋๋ findViewById()๋ฅผ ํธ์ถํ์ฌ TextView ์์ ฏ์ ์ฐพ์ viewModel ๋ณ์์ userName ์์ฑ์ ๊ฒฐํฉํ๋ ์์ด๋ค.
findViewById(R.id.sample_text).apply {
text = viewModel.userName
}
ํ์ง๋ง DataBinding ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ๋ฉด ์์ Kotlin ์ฝ๋๋ฅผ ํธ์ถํ ํ์๊ฐ ์๋ค.
๋ ์ด์์ ํ์ผ์์ ๊ตฌ์ฑ์์๋ฅผ ๊ฒฐํฉํ๋ฉด ํ๋์์ ๋ง์ UI ํ๋ ์์ํฌ ํธ์ถ์ ์ญ์ ํ ์ ์์ด ํ์ผ์ด ๋์ฑ ๋จ์ํ๋๊ณ ์ ์ง๊ด๋ฆฌ ๋ํ ์ฌ์์ง๋ฉฐ ์ฑ ์ฑ๋ฅ์ด ํฅ์๋๊ณ ๋ฉ๋ชจ๋ฆฌ ๋์ ๋ฐ null ํฌ์ธํฐ ์์ธ๋ฅผ ๋ฐฉ์งํ ์ ์๋ค.
2.1. DataBinding ์ค์ build.gradle ์๋จ์ ๋ฐ์ดํฐ ๋ฐ์ธ๋ฉ ์ฌ์ฉ์ ์ํ ์๋ ์ฝ๋๋ฅผ ์ถ๊ฐํ๋ค.
apply plugin: "kotlin-kapt"
android {
....
dataBinding {
enabled = true
}
}
xml์ ์ฐ๋ํ ViewModel.kt ํ์ผ์ ์๋์ ๊ฐ์ด ์์ฑํ๋ค.
class ViewModel {
val text = ObservableField("")
fun showText(view: View) {
Toast.makeText(view.context, "${text.get()}", Toast.LENGTH_SHORT).show()
}
}
๋ทฐ๋ชจ๋ธ ํด๋์ค์ ์์ฑํด๋ ํจ์๋ xml์์ ํธ์ถ ํ ์ ์๋ค. ๋ค๋ง, ์ ๊ทผ ์ ํ์๊ฐ private๋ฉด xml์์ ํจ์๋ฅผ ์ฐธ์กฐํ ์ ์๋ค.
๋ฐ์ดํฐ ๋ฐ์ธ๋ฉ์ ์ํด์ ๋ฃจํธ ํ๊ทธ๋ฅผ layout์ผ๋ก ๋ฐ๊ฟ ์ฃผ๊ณ variable ํ๊ทธ๋ฅผ ํตํด ๋ ์ด์์์ ์ฐ๋ํ ViewModel์ ์ง์ ํด์ค๋ค.
ViewModel์ ๋ฐ์ดํฐ์ ์ ๊ทผ ํ ๋์๋, @{} ์์ ์ฐธ์กฐ ํ ๋ฐ์ดํฐ๋ฅผ ์์ฑํ๋ค. DataBinding์ ์ฌ์ฉํ๋ฉด ๋ ์ด์์์์ ๊ฐ๋จํ ์์ ์ฌ์ฉํ ์ ์๋ค. ์ ์ฝ๋์ android:visiblity๊ฐ ๊ฐ๋จํ ์๋ค.
DataBinding์์๋ android:onClick๋ฑ์ ์ด๋ฒคํธ๊ฐ ๋ฐ์ํ์ ๋ ์ํํ ๋์์ ๋ฐ์ธ๋ฉ ํ ์ ์๋ค. ํจ์๋ฅผ ๋ฐ์ธ๋ฉ ํ ๋์๋, @{() -> vm.doSomething()} ๊ฐ์ด ๋๋ค์์ ์ฌ์ฉํด์ ํธ์ถํ๋ค. ์ด ๋ ํจ์๋ ์ด๋ฒคํธ์ ํ๋ผ๋ฏธํฐ๋ฅผ ๋ฐ๊ฑฐ๋ context ๋ฅผ ํ๋ผ๋ฏธํฐ๋ก ๋ฐ์์ ์ฌ์ฉ ํ ์ ์๋ค.
๋ฐ์ธ๋ฉ์ ์ํด Activity ํ์ผ์ ์๋์ ์ฝ๋๋ฅผ ์์ฑํ๋ค.
val binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
binding.vm = ViewModel()
setContentView๋ DataBinding์ ํ๋ฉฐ ์จ์ฃผ๊ธฐ ๋๋ฌธ์, ์์ฑ ํ ํ์๊ฐ ์์ผ๋ ์ง์์ค๋ค. ๋ฐ์ดํฐ๋ฐ์ธ๋ฉ ํด๋์ค๋ ์ ๋ ์ด์์ ํ์ผ์ ์ด๋ฆ์ ๋ฐ๋ผ์ ์๋์ผ๋ก ์นด๋ฉ ์ผ์ด์ค๋ก ๋ณํ๋๊ณ , ๊ทธ ๋ค์ Binding์ ๋ถ์ฌ์ ์์ฑ๋๋ค. ex) activity_main โ ActivityMainBinding binding.vm ๊ณผ ๊ฐ์ ๋ฐฉ๋ฒ์ผ๋ก ๋ ์ด์์์ ๋ฐ์ดํฐ์ ์ ๊ทผํ๋ค. binding.vm = ViewModel() ๋ก ๋ทฐ๋ชจ๋ธ์ ๋ฐ์ธ๋ฉํด์ค๋ค.
๋ฐ์ดํฐ ๊ฒฐํฉ์ ์ฌ์ฉํ๋ฉด ๋ทฐ์์ ์ ๋ฌ๋๋ ํํ์ ์ฒ๋ฆฌ ์ด๋ฒคํธ๋ฅผ ์์ฑํ ์ ์๋ค(์: onClick() ๋ฉ์๋). ์ด๋ฒคํธ ์์ฑ ์ด๋ฆ์ ๋ช ๊ฐ์ง ์์ธ๋ฅผ ์ ์ธํ๊ณ ๋ฆฌ์ค๋ ๋ฉ์๋์ ์ด๋ฆ์ ๋ฐ๋ผ ๊ฒฐ์ ๋๋ค. ์๋ฅผ ๋ค์ด View.OnClickListener์๋ onClick() ๋ฉ์๋๊ฐ ์์ผ๋ฏ๋ก ์ด ์ด๋ฒคํธ์ ์์ฑ์ android:onClick์ด๋ค. ํด๋ฆญ ์ด๋ฒคํธ์๋ ์ถฉ๋์ ๋ฐฉ์งํ๊ธฐ ์ํด android:onClick ์ด์ธ์ ๋ค๋ฅธ ์์ฑ์ด ํ์ํ ํน์ํ ์ด๋ฒคํธ ํธ๋ค๋ฌ๊ฐ ์๋ค. ๊ฐ๋ฐ์๋ ์๋ 2๊ฐ ๋ฉ์ปค๋์ฆ์ ์ฌ์ฉํ์ฌ ์ด๋ฒคํธ๋ฅผ ์ฒ๋ฆฌํ ์ ์๋ค.
- Method reference: ํํ์์์ ๋ฆฌ์ค๋ ๋ฉ์๋์ ์๋ช ๊ณผ ์ผ์นํ๋ ๋ฉ์๋๋ฅผ ์ฐธ์กฐํ ์ ์๋ค. ํํ์์ด ๋ฉ์๋ ์ฐธ์กฐ๋ก ๊ณ์ฐ๋๋ฉด ๋ฐ์ดํฐ ๊ฒฐํฉ์ ๋ฆฌ์ค๋์์ ๋ฉ์๋ ์ฐธ์กฐ ๋ฐ ์์ ์ ๊ฐ์ฒด๋ฅผ ๋ํํ๊ณ ํ๊ฒ ๋ทฐ์์ ์ด ๋ฆฌ์ค๋๋ฅผ ์ค์ ํ๋ค. ํํ์์ด null๋ก ๊ณ์ฐ๋๋ฉด ๋ฐ์ดํฐ ๊ฒฐํฉ์ ๋ฆฌ์ค๋๋ฅผ ์์ฑํ์ง ์๊ณ null ๋ฆฌ์ค๋๋ฅผ ์ค์ ํ๋ค.
- Listener binding: ์ด๋ฒคํธ๊ฐ ๋ฐ์ํ ๋ ๊ณ์ฐ๋๋ ๋๋ค ํํ์์ด๋ค. ๋ฐ์ดํฐ ๊ฒฐํฉ์ ํญ์ ๋ฆฌ์ค๋๋ฅผ ์์ฑํ์ฌ ๋ทฐ์์ ์ค์ ํ๋ค. ์ด๋ฒคํธ๊ฐ ์ ๋ฌ๋๋ฉด ๋ฆฌ์ค๋๋ ๋๋ค ํํ์์ ๊ณ์ฐํ๋ค.
์ด๋ฒคํธ๋ android:onClick์ด ํ๋์ ๋ฉ์๋์ ํ ๋น๋๋ ๋ฐฉ์๊ณผ ์ ์ฌํ๊ฒ ํธ๋ค๋ฌ ๋ฉ์๋์ ์ง์ ๊ฒฐํฉ๋ ์ ์๋ค. View onClick ์์ฑ๊ณผ ๋น๊ตํ์ ๋ ์ฃผ์ ์ด์ ์ ํํ์์ด ์ปดํ์ผ ํ์์ ์ฒ๋ฆฌ๋๋ฏ๋ก ๋ฉ์๋๊ฐ ์๊ฑฐ๋ ์๋ช ์ด ์ฌ๋ฐ๋ฅด์ง ์์ผ๋ฉด ์ปดํ์ผ ํ์ ์ค๋ฅ๊ฐ ๋ฐ์ํ๋ค๋ ์ ์ด๋ค. Method reference์ Listener binding์ ์ฃผ์ ์ฐจ์ด์ ์ ์ค์ ๋ฆฌ์ค๋ ๊ตฌํ์ด ์ด๋ฒคํธ๊ฐ ํธ๋ฆฌ๊ฑฐ๋ ๋๊ฐ ์๋๋ผ ๋ฐ์ดํฐ๊ฐ ๊ฒฐํฉ๋ ๋ ์์ฑ๋๋ค๋ ์ ์ด๋ค. ์ด๋ฒคํธ๊ฐ ๋ฐ์ํ ๋ ํํ์์ ๊ณ์ฐํ๋ ค๋ฉด Listener binding์ ์ฌ์ฉํด์ผ ํ๋ค. ํธ๋ค๋ฌ์ ์ด๋ฒคํธ๋ฅผ ํ ๋นํ๋ ค๋ฉด ํธ์ถํ ๋ฉ์๋ ์ด๋ฆ์ด ๋ ๊ฐ์ ์ฌ์ฉํ์ฌ ์ผ๋ฐ ๊ฒฐํฉ ํํ์์ ์ฌ์ฉํด์ผ ํ๋ค.
class MyHandlers {
fun onClickFriend(view: View) { ... }
}
// ๊ฒฐํฉ ํํ์์ ๋ค์๊ณผ ๊ฐ์ด ๋ทฐ์ ํด๋ฆญ ๋ฆฌ์ค๋๋ฅผ onClickFriend() ๋ฉ์๋์ ํ ๋นํ ์ ์๋ค.
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.firstName}"
android:onClick="@{handlers::onClickFriend}"/>
Listener binding์ ์ด๋ฒคํธ๊ฐ ๋ฐ์ํ ๋ ์คํ๋๋ ๊ฒฐํฉ ํํ์์ด๋ค. Listener binding์ Method reference์ ๋น์ทํ๋ค. ํ์ง๋ง Listener binding์ ์ฌ์ฉํ๋ฉด ์์์ ๋ฐ์ดํฐ ๊ฒฐํฉ ํํ์์ ์คํํ ์ ์๋ค. ์ด ๊ธฐ๋ฅ์ Gradle ๋ฒ์ 2.0 ์ด์์ ์ํ Android Gradle ํ๋ฌ๊ทธ์ธ์ผ๋ก ์ฌ์ฉํ ์ ์๋ค. Methon reference์์ ๋ฉ์๋์ ๋งค๊ฐ๋ณ์๋ ์ด๋ฒคํธ ๋ฆฌ์ค๋์ ๋งค๊ฐ๋ณ์์ ์ผ์นํด์ผ ํ๋ค. Listener binding์์๋ ๋ฐํ ๊ฐ๋ง ๋ฆฌ์ค๋์ ์์ ๋ฐํ ๊ฐ๊ณผ ์ผ์นํ๋ฉด ๋๋ค.(void๊ฐ ์์๋์ง ์๋ ํ).
class Presenter {
fun onSaveClick(task: Task){}
}
// ๋ค์๊ณผ ๊ฐ์ด ํด๋ฆญ ์ด๋ฒคํธ๋ฅผ onSaveClick() ๋ฉ์๋์ ๊ฒฐํฉํ ์ ์๋ค.
<Button android:layout_width="wrap_content" android:layout_height="wrap_content"
android:onClick="@{() -> presenter.onSaveClick(task)}" />
ํํ์์ ์ฝ๋ฐฑ์ ์ฌ์ฉํ๋ฉด Data Binding์ ํ์ํ ๋ฆฌ์ค๋๋ฅผ ์๋์ผ๋ก ์์ฑํ์ฌ ์ด๋ฒคํธ์ ๋ฑ๋กํ๋ค. ๋ทฐ์์ ์ด๋ฒคํธ๊ฐ ๋ฐ์ํ๋ฉด Data Binding์ ์ฃผ์ด์ง ํํ์์ ๊ณ์ฐํ๋ค. ์ผ๋ฐ ๊ฒฐํฉ ํํ์์์์ ๊ฐ์ด ์ด๋ฌํ ๋ฆฌ์ค๋ ํํ์์ด ๊ณ์ฐ๋๋ ๋์ ๊ณ์ Data Binding์ null ๋ฐ ์ค๋ ๋ ์์ ์ฑ์ด ํ๋ณด๋๋ค. ์์ ์์์๋ onClick(View)์ ์ ๋ฌ๋๋ view ๋งค๊ฐ๋ณ์๊ฐ ์ ์๋์ง ์์๋ค. Listener binding์์๋ ๋ ๊ฐ์ง ๋ฐฉ์(๋ชจ๋ ๋งค๊ฐ๋ณ์๋ฅผ ๋ฌด์, ๋ชจ๋ ๋งค๊ฐ๋ณ์์ ์ด๋ฆ์ ์ง์ )์ผ๋ก ๋ฆฌ์ค๋ ๋งค๊ฐ๋ณ์๋ฅผ ์ ํํ ์ ์๋ค. ๋งค๊ฐ๋ณ์ ์ด๋ฆ ์ง์ ์ ์ ํํ๋ฉด ํํ์์ ๋งค๊ฐ๋ณ์๋ฅผ ์ฌ์ฉํ ์ ์์ต๋๋ค. ์๋ฅผ ๋ค์ด ์์ ํํ์์ ๋ค์๊ณผ ๊ฐ์ด ์์ฑํ ์ ์๋ค.
android:onClick="@{(view) -> presenter.onSaveClick(task)}"
class Presenter {
fun onSaveClick(view: View, task: Task){}
}
android:onClick="@{(theView) -> presenter.onSaveClick(theView, task)}"
์๋์ ๊ฐ์ด ๋ ์ด์์ ๋งค๊ฐ๋ณ์์ ํจ๊ป ๋๋ค ํํ์์ ์ฌ์ฉํ ์๋ ์๋ค.
class Presenter {
fun onCompletedChanged(task: Task, completed: Boolean){}
}
<CheckBox android:layout_width="wrap_content" android:layout_height="wrap_content"
android:onCheckedChanged="@{(cb, isChecked) -> presenter.completeChanged(task, isChecked)}" />
์์ ์ค์ธ ์ด๋ฒคํธ๊ฐ void๊ฐ ์๋ ์ ํ์ ๊ฐ์ ๋ฐํํ๋ฉด ํํ์๋ ๊ฐ์ ์ ํ์ ๊ฐ์ ๋ฐํํด์ผ ํ๋ค. ์๋ฅผ ๋ค์ด '๊ธธ๊ฒ ํด๋ฆญ' ์ด๋ฒคํธ๋ฅผ ์์ ๋๊ธฐํ๋ ค๋ฉด ํํ์์์ Boolean์ ๋ฐํํด์ผ ํ๋ค.
class Presenter {
fun onLongClick(view: View, task: Task): Boolean { }
}
android:onLongClick="@{(theView) -> presenter.onLongClick(theView, task)}"
null ๊ฐ์ฒด๋ก ์ธํด ํํ์์ ๊ณ์ฐํ ์ ์์ผ๋ฉด ๋ฐ์ดํฐ ๊ฒฐํฉ์ ๊ฐ๊ธฐ ํด๋นํ๋ ์ ํ์ ๊ธฐ๋ณธ๊ฐ์ ๋ฐํํ๋ค. ์๋ฅผ ๋ค์ด ์ฐธ์กฐ ์ ํ์ null์, int๋ 0์, boolean์ false๋ฅผ ๊ธฐ๋ณธ๊ฐ์ผ๋ก ๋ฐํํ๋ค. ์กฐ๊ฑด์์ ํจ๊ป ํํ์(์: ์ผํญ)์ ์ฌ์ฉํด์ผ ํ๋ค๋ฉด void๋ฅผ ๊ธฐํธ๋ก ์ฌ์ฉํ ์ ์๋ค.
android:onClick="@{(v) -> v.isVisible() ? doSomething() : void}"
Data Binding ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ Imports, variables, and includes๊ณผ ๊ฐ์ ๊ธฐ๋ฅ์ ์ ๊ณตํ๋ค. Imports๋ฅผ ์ฌ์ฉํ๋ฉด ๋ ์ด์์ ํ์ผ ๋ด์์ ํด๋์ค๋ฅผ ์ฝ๊ฒ ์ฐธ์กฐํ ์ ์๋ค. variables๋ฅผ ์ฌ์ฉํ๋ฉด ๊ฒฐํฉ ํํ์์ ์ฌ์ฉํ ์ ์๋ ์์ฑ์ ์ค๋ช ํ ์ ์๋ค. includes์ ์ฌ์ฉํ๋ฉด ์ฑ ์ ์ฒด์์ ๋ณต์กํ ๋ ์ด์์์ ์ฌ์ฌ์ฉํ ์ ์๋ค.
Imports๋ฅผ ์ฌ์ฉํ๋ฉด ๊ด๋ฆฌํ ์ฝ๋์์์ ๊ฐ์ด ๋ ์ด์์ ํ์ผ ๋ด์์ ํด๋์ค๋ฅผ ์ฝ๊ฒ ์ฐธ์กฐํ ์ ์๋ค. 0๊ฐ ์ด์์ import ์์๋ฅผ data ์์ ๋ด์์ ์ฌ์ฉํ ์ ์๋ค. ์๋ ์ฝ๋ ์๋ View ํด๋์ค๋ฅผ ๋ ์ด์์ ํ์ผ๋ก ๊ฐ์ ธ์จ๋ค.
// View ํด๋์ค๋ฅผ ๊ฐ์ ธ์ค๋ฉด ๊ฒฐํฉ ํํ์์์ ์ฐธ์กฐํ ์ ์๋ค.
<TextView
android:text="@{user.lastName}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="@{user.isAdult ? View.VISIBLE : View.GONE}"/>
ํด๋์ค ์ด๋ฆ ์ถฉ๋์ด ๋ฐ์ํ๋ฉด ํด๋์ค ์ค ํ๋์ ์ด๋ฆ์ ๋ณ์นญ์ผ๋ก ๋ฐ๊ฟ ์ ์๋ค. ์๋ ์ฝ๋ ์๋ com.example.real.estate ํจํค์ง์ View ํด๋์ค ์ด๋ฆ์ Vista๋ก ๋ณ๊ฒฝํ๋ค.
์ด์ Vista๋ฅผ ์ฌ์ฉํ์ฌ com.example.real.estate.View๋ฅผ ์ฐธ์กฐํ ์ ์๋ค. ๊ทธ๋ฆฌ๊ณ ๋ ์ด์์ ํ์ผ ๋ด์์ android.view.View๋ฅผ ์ฐธ์กฐํ๋ ๋ฐ View๋ฅผ ์ฌ์ฉํ ์ ์๋ค.
๊ฐ์ ธ์จ ์ ํ์ ๋ณ์ ๋ฐ ํํ์์์ ์ ํ ์ฐธ์กฐ๋ก ์ฌ์ฉํ ์ ์๋ค.
์๋ ์๋ ๋ณ์์ ์ ํ์ผ๋ก ์ฌ์ฉ๋๋ User ๋ฐ List๋ฅผ ๋ณด์ฌ์ค๋ค.โจ
์ฃผ์: Android ์คํ๋์ค์์๋ ์์ง ๊ฐ์ ธ์ค๊ธฐ๋ฅผ ์ฒ๋ฆฌํ์ง ๋ชปํ๋ฏ๋ก ๊ฐ์ ธ์จ ๋ณ์์ ์๋ ์์ฑ์ด IDE์์ ์๋ํ์ง ์์ ์ ์๋ค. ๊ทธ๋ฌ๋ ์ฑ์ ์ฌ์ ํ ์ปดํ์ผ๋๋ค. ๊ทธ๋ฆฌ๊ณ ๋ณ์ ์ ์์์ ์ ๊ทํ๋ ์ด๋ฆ์ ์ฌ์ฉํ์ฌ IDE ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ ์ ์๋ค.
๋ํ ๊ฐ์ ธ์จ ์ ํ์ ์ฌ์ฉํ์ฌ ํํ์์ ์ผ๋ถ๋ฅผ ๋ณํํ ์๋ ์์ต๋๋ค. ๋ค์ ์๋ connection ์์ฑ์ User ์ ํ์ผ๋ก ๋ณํํ๋ค.
๋ํ ํํ์์์ ์ ์ ํ๋ ๋ฐ ๋ฉ์๋๋ฅผ ์ฐธ์กฐํ ๋ ๊ฐ์ ธ์จ ์ ํ์ ์ฌ์ฉํ ์๋ ์๋ค. ์๋ ์ฝ๋๋ MyStringUtils ํด๋์ค๋ฅผ ๊ฐ์ ธ์์ capitalize ๋ฉ์๋๋ฅผ ์ฐธ์กฐํ๋ค.
โฆ
data ์์ ๋ด์์ ์ฌ๋ฌ variable ์์๋ฅผ ์ฌ์ฉํ ์ ์๋ค. ๊ฐ variable ์์๋ ๋ ์ด์์ ํ์ผ ๋ด ๊ฒฐํฉ ํํ์์ ์ฌ์ฉ๋ ๋ ์ด์์์์ ์ค์ ํ ์ ์๋ ์์ฑ์ ์ค๋ช ํ๋ค. ์๋ ์๋ user, image ๋ฐ note ๋ณ์๋ฅผ ์ ์ธํ๋ค.
๋ณ์ ์ ํ์ ์ปดํ์ผ ํ์์ ๊ฒ์ฌ๋๋ค. ๋ฐ๋ผ์ ๋ณ์๊ฐ Observable์ ๊ตฌํํ๊ฑฐ๋ ์๋ณ ๊ฐ๋ฅํ ์ปฌ๋ ์ ์ด๋ผ๋ฉด ์ ํ์ ๋ฐ์๋๋ค. ๋ณ์๊ฐ Observable ์ธํฐํ์ด์ค๋ฅผ ๊ตฌํํ์ง ์๋ ๊ธฐ๋ณธ ํด๋์ค ๋๋ ์ธํฐํ์ด์ค๋ผ๋ฉด ๋ณ์๋ค์ด ๊ด์ฐฐ๋์ง ์๋๋ค. ๋ค์ํ ๊ตฌ์ฑ(์: ๊ฐ๋ก ๋ชจ๋ ๋๋ ์ธ๋ก ๋ชจ๋)์ ๋ ์ด์์ ํ์ผ์ด ์๋ก ๋ค๋ฅผ ๋ ๋ณ์๊ฐ ๊ฒฐํฉ๋๋ค. ์ด๋ฌํ ๋ ์ด์์ ํ์ผ ๊ฐ์ ์ถฉ๋ํ๋ ๋ณ์ ์ ์๊ฐ ์์ด์๋ ์๋๋ค. ์์ฑ๋ ๊ฒฐํฉ ํด๋์ค์๋ ์ค๋ช ๋ ๊ฐ ๋ณ์์ setter ๋ฐ getter๊ฐ ์๋ค. ๋ณ์๋ setter๊ฐ ํธ์ถ๋ ๋๊น์ง ๊ธฐ๋ณธ ๊ด๋ฆฌํ ์ฝ๋ ๊ฐ์ ์ฌ์ฉํ๋ค. ์๋ฅผ ๋ค์ด ์ฐธ์กฐ ์ ํ์ null์, int๋ 0์, boolean์ false๋ฅผ ๊ธฐ๋ณธ๊ฐ์ผ๋ก ์ฌ์ฉํ๋ค. ํ์์ ๋ฐ๋ผ ๊ฒฐํฉ ํํ์์ ์ฌ์ฉํ๊ธฐ ์ํด context๋ผ๋ ์ด๋ฆ์ ํน์ ๋ณ์๋ฅผ ์์ฑํ๋ค. context์ ๊ฐ์ ๋ฃจํธ ๋ทฐ์ getContext() ๋ฉ์๋์์ ์จ Context ๊ฐ์ฒด์ด๋ค. context ๋ณ์๊ฐ ์ด ์ด๋ฆ์ ์ฌ์ฉํ๋ ๋ช ์์ ๋ณ์ ์ ์ธ์ผ๋ก ์ฌ์ ์๋๋ค.
์์ฑ์ ์ฑ ๋ค์์คํ์ด์ค ๋ฐ ๋ณ์ ์ด๋ฆ์ ์ฌ์ฉํจ์ผ๋ก์จ ํฌํจํ๋ ๋ ์ด์์์์ ํฌํจ๋ ๋ ์ด์์์ ๊ฒฐํฉ์ผ๋ก ๋ณ์๋ฅผ ์ ๋ฌํ ์ ์๋ค. ๋ค์ ์๋ name.xml ๋ฐ contact.xml ๋ ์ด์์ ํ์ผ๋ก๋ถํฐ ํฌํจ๋ user ๋ณ์๋ฅผ ๋ณด์ฌ์ค๋ค.
Data binding์ Includes๋ฅผ ๋ณํฉ ์์์ ์ง์ ํ์ ์์๋ก ์ง์ํ์ง ์๋๋ค. ์๋ฅผ ๋ค์ด ์๋ ๋ ์ด์์์ ์ง์๋์ง ์๋๋ค.
Binding adapter๋ ์ ์ ํ ํ๋ ์์ํฌ๋ฅผ ํธ์ถํ์ฌ ๊ฐ์ ์ค์ ํ๋ ์์ ์ ๋ด๋นํ๋ค. ํ ๊ฐ์ง ์๋ก setText() ๋ฉ์๋๋ฅผ ํธ์ถํ๋ ๊ฒ๊ณผ ๊ฐ์ด ์์ฑ ๊ฐ์ ์ค์ ํ๋ ์์ ์ ๋ค ์ ์๋ค. ๋ ๋ค๋ฅธ ์๋ก๋ setOnClickListener() ๋ฉ์๋๋ฅผ ํธ์ถํ๋ ๊ฒ๊ณผ ๊ฐ์ด ์ด๋ฒคํธ ๋ฆฌ์ค๋๋ฅผ ์ค์ ํ๋ ์์ ์ด ์๋ค. Data Binding ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ๋ฉด ๊ฐ์ ์ค์ ํ๊ธฐ ์ํด ํธ์ถ๋๋ ๋ฉ์๋๋ฅผ ์ง์ ํ๊ณ ๊ณ ์ ํ Binding ๋ก์ง์ ์ ๊ณตํ๋ฉฐ ์ด๋ํฐ๋ฅผ ์ฌ์ฉํจ์ผ๋ก์จ ๋ฐํ๋ ๊ฐ์ฒด์ ์ ํ์ ์ง์ ํ ์ ์๋ค.
๊ฒฐํฉ๋ ๊ฐ์ด ๋ณ๊ฒฝ๋ ๋๋ง๋ค ์์ฑ๋ ๊ฒฐํฉ ํด๋์ค๋ ๊ฒฐํฉ ํํ์์ ์ฌ์ฉํ์ฌ ๋ทฐ์์ setter ๋ฉ์๋๋ฅผ ํธ์ถํด์ผ ํ๋ค. Data Binding ๋ผ์ด๋ธ๋ฌ๋ฆฌ์์ ๋ฉ์๋๋ฅผ ์๋์ผ๋ก ๊ฒฐ์ ํ๊ฑฐ๋ ๋ฉ์๋๋ฅผ ๋ช ์์ ์ผ๋ก ์ ์ธํ๊ฑฐ๋ ๋ง์ถค ๋ก์ง์ ์ ๊ณตํด ๋ฉ์๋๋ฅผ ์ ํํ๋๋ก ํ์ฉํ ์ ์๋ค.
์ด๋ฆ์ด example์ธ ์์ฑ์ ๊ฒฝ์ฐ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ ํธํ ๊ฐ๋ฅํ ์ ํ์ ์ธ์๋ก ํ์ฉํ๋ setExample(arg) ๋ฉ์๋๋ฅผ ์๋์ผ๋ก ์ฐพ์ผ๋ ค๊ณ ํ๋ค. ์์ฑ์ ๋ค์์คํ์ด์ค๋ ๊ณ ๋ ค๋์ง ์์ผ๋ฉฐ ๋ฉ์๋ ๊ฒ์ ์ ์์ฑ ์ด๋ฆ ๋ฐ ์ ํ๋ง ์ฌ์ฉ๋๋ค. ์๋ฅผ ๋ค์ด android:text="@{user.name}" ํํ์์ด ์๋ค๊ณ ํ๋ค๋ฉด ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ user.getName()์์ ๋ฐํํ ์ ํ์ ํ์ฉํ๋ setText(arg) ๋ฉ์๋๋ฅผ ์ฐพ๋๋ค. user.getName()์ ๋ฐํ ์ ํ์ด String์ด๋ฉด ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ String ์ธ์๋ฅผ ํ์ฉํ๋ setText() ๋ฉ์๋๋ฅผ ์ฐพ๋๋ค. ํํ์์ด int๋ฅผ ๋์ ๋ฐํํ๋ฉด ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ int ์ธ์๋ฅผ ํ์ฉํ๋ setText() ๋ฉ์๋๋ฅผ ๊ฒ์ํ๋ค. ํํ์์ ์ฌ๋ฐ๋ฅธ ์ ํ์ ๋ฐํํด์ผ ํ๋ค. ํ์ํ๋ค๋ฉด ๋ฐํ ๊ฐ์ ๋ณํํ ์ ์๋ค. ์ง์ ๋ ์ด๋ฆ์ ์์ฑ์ด ์๋๋ผ๋ ๋ฐ์ดํฐ ๊ฒฐํฉ์ ์๋ํ๋ค. ๊ทธ๋๋ ๋ฐ์ดํฐ ๊ฒฐํฉ์ ์ฌ์ฉํ์ฌ setter์ ํ์ํ ์์ฑ์ ์์ฑํ ์ ์๋ค. ์๋ฅผ ๋ค์ด ์ง์ ํด๋์ค DrawerLayout์๋ ์ด๋ค ์์ฑ๋ ์์ง๋ง ๋ง์ setter๊ฐ ์๋ค. ์๋ ๋ ์ด์์์ ์๋์ผ๋ก setScrimColor(int) ๋ฐ setDrawerListener(DrawerListener) ๋ฉ์๋๋ฅผ ๊ฐ๊ฐapp:scrimColor ๋ฐ app:drawerListener ์์ฑ์ setter๋ก ์ฌ์ฉํ๋ค.
์ผ๋ถ ์์ฑ์๋ ์ด๋ฆ์ด ์ผ์นํ์ง ์๋ setter๊ฐ ์๋ค. ์ด๋ฌํ ์ํฉ์์ ์์ฑ์ BindingMethods ์ฃผ์์ ์ฌ์ฉํ์ฌ setter์ ์ฐ๊ฒฐ๋ ์ ์๋ค. ์ฃผ์์ ํด๋์ค์ ํจ๊ป ์ฌ์ฉ๋๋ฉฐ ์ด๋ฆ์ด ๋ฐ๋ ๊ฐ ๋ฉ์๋์ ํ๋์ฉ ์ฌ๋ฌ BindingMethod ์ฃผ์์ ํฌํจํ ์ ์๋ค. ๊ฒฐํฉ ๋ฉ์๋๋ ์ฑ์ ์ด๋ค ํด๋์ค์๋ ์ถ๊ฐํ ์ ์๋ ์ฃผ์์ด๋ค. ์๋ ์์์ android:tint ์์ฑ์ setTint() ๋ฉ์๋๊ฐ ์๋ setImageTintList(ColorStateList) ๋ฉ์๋์ ์ฐ๊ฒฐ๋๋ค.
@BindingMethods(value = [
BindingMethod(
type = android.widget.ImageView::class,
attribute = "android:tint",
method = "setImageTintList")])
์ผ๋ฐ์ ์ผ๋ก Android ํ๋ ์์ํฌ ํด๋์ค์์ setter์ ์ด๋ฆ์ ๋ฐ๊ฟ ํ์๊ฐ ์๋ค. ์ด๋ฆ ๊ท์น์ ์ฌ์ฉํ์ฌ ์ผ์นํ๋ ๋ฉ์๋๋ฅผ ์๋์ผ๋ก ์ฐพ๋ ์์ฑ์ด ์ด๋ฏธ ๊ตฌํ๋์ด์๋ค.
์ผ๋ถ ์์ฑ์๋ ๋ง์ถค ๊ฒฐํฉ ๋ก์ง์ด ํ์ํ๋ค. ์๋ฅผ ๋ค์ด android:paddingLeft ์์ฑ์๋ ์ฐ๊ฒฐ๋ setter๊ฐ ์๋ ๋์ setPadding(left, top, right, bottom) ๋ฉ์๋๊ฐ ์ ๊ณต๋๋ค. BindingAdapter ์ฃผ์์ด ์๋ ์ ์ ๊ฒฐํฉ ์ด๋ํฐ ๋ฉ์๋๋ฅผ ์ฌ์ฉํ๋ฉด ์์ฑ์ setter๊ฐ ํธ์ถ๋๋ ๋ฐฉ์์ ๋ง์ถค ์ค์ ํ ์ ์๋ค. Android ํ๋ ์์ํฌ ํด๋์ค์ ์์ฑ์๋ BindingAdapter ์ฃผ์์ด ์ด๋ฏธ ์์ฑ๋์ด ์๋ค. ์๋ ์ฝ๋๋ paddingLeft ์์ฑ์ ๊ฒฐํฉ ์ด๋ํฐ๋ฅผ ๋ณด์ฌ์ค๋ค.
@BindingAdapter("android:paddingLeft")
fun setPaddingLeft(view: View, padding: Int) {
view.setPadding(padding,
view.getPaddingTop(),
view.getPaddingRight(),
view.getPaddingBottom())
}
๋งค๊ฐ๋ณ์ ์ ํ์ ์ค์ํ๋ค. ์ฒซ ๋ฒ์งธ ๋งค๊ฐ๋ณ์๋ ์์ฑ๊ณผ ์ฐ๊ฒฐ๋ ๋ทฐ์ ์ ํ์ ๊ฒฐ์ ํ๋ค. ๋ ๋ฒ์งธ ๋งค๊ฐ๋ณ์๋ ์ง์ ๋ ์์ฑ์ ๊ฒฐํฉ ํํ์์์ ํ์ฉ๋๋ ์ ํ์ ๊ฒฐ์ ํ๋ค. BindingAdapter๋ ๋ค๋ฅธ ์ ํ์ ๋ง์ถค์ค์ ์ ์ ์ฉํ๋ค. ์๋ฅผ ๋ค์ด ๋ง์ถค ๋ก๋๋ ์์ ์ ์ค๋ ๋์์ ํธ์ถ๋์ด ์ด๋ฏธ์ง๋ฅผ ๋ก๋ํ ์ ์๋ค. ๊ฐ๋ฐ์๊ฐ ์ ์ํ๋ BindingAdapter๋ ์ถฉ๋์ด ๋ฐ์ํ๋ฉด Android ํ๋ ์์ํฌ์์ ์ ๊ณตํ๋ ๊ธฐ๋ณธ ์ด๋ํฐ๋ณด๋ค ์ฐ์ ์ ์ฉ๋๋ค. ๋ํ ์๋ ์์์์ ๊ฐ์ด ์ฌ๋ฌ ์์ฑ์ ๋ฐ๋ ์ด๋ํฐ๋ ์์ ์ ์๋ค.
@BindingAdapter("imageUrl", "error")
fun loadImage(view: ImageView, url: String, error: Drawable) {
Picasso.get().load(url).error(error).into(view)
}
์ BindingAdapter๋ ๋ ์ด์์์์ ์๋์ ๊ฐ์ด ์ฌ์ฉํ ์ ์๋ค. ์ฌ๊ธฐ์ @drawable/venueError๋ ์ฑ์ ๋ฆฌ์์ค๋ฅผ ๋ํ๋ธ๋ค. ๋ฆฌ์์ค๋ฅผ @{}๋ก ๋ฌถ์ผ๋ฉด ์ ํจํ ๊ฒฐํฉ ํํ์์ด ๋๋ค.
imageUrl๊ณผ error๊ฐ ๋ชจ๋ ImageView ๊ฐ์ฒด์ ์ฌ์ฉ๋๋๋ฐ imageUrl์ ๋ฌธ์์ด์ด๊ณ error๋ Drawable์ด๋ผ๋ฉด ์ด๋ํฐ๊ฐ ํธ์ถ๋๋ค. ์ด๋ค ์์ฑ์ด๋ผ๋ ์ค์ ๋ ๋ ์ด๋ํฐ๋ฅผ ํธ์ถํ๋ ค๋ฉด ๋ค์ ์์์์ ๊ฐ์ด ์ด๋ํฐ์ requireAll ํ๋๊ทธ(์ ํ์ฌํญ)๋ฅผ false๋ก ์ค์ ํ๋ฉด ๋๋ค.
@BindingAdapter(value = ["imageUrl", "placeholder"], requireAll = false)
fun setImageUrl(imageView: ImageView, url: String?, placeHolder: Drawable?) {
if (url == null) {
imageView.setImageDrawable(placeholder);
} else {
MyImageLoader.loadInto(imageView, url, placeholder);
}
}
BindingAdapter ๋ฉ์๋๋ ์ ํ์ ์ผ๋ก ํธ๋ค๋ฌ์ ์ด์ ๊ฐ์ ์ฌ์ฉํ ์ ์๋ค. ์ด์ ๊ฐ๊ณผ ์ ๊ฐ์ ์ฌ์ฉํ๋ ๋ฉ์๋๋ ์๋ ์์์์ ๊ฐ์ด ์์ฑ์ ๋ชจ๋ ์ด์ ๊ฐ์ ๋จผ์ ์ ์ธํ ํ ์ ๊ฐ์ ์ ์ธํด์ผ ํ๋ค.
@BindingAdapter("android:paddingLeft")
fun setPaddingLeft(view: View, oldPadding: Int, newPadding: Int) {
if (oldPadding != newPadding) {
view.setPadding(padding,
view.getPaddingTop(),
view.getPaddingRight(),
view.getPaddingBottom())
}
}
์ด๋ฒคํธ ํธ๋ค๋ฌ๋ ์์์์ ๊ฐ์ด ํ๋์ ์ถ์ ๋ฉ์๋๊ฐ ์๋ ์ธํฐํ์ด์ค ๋๋ ์ถ์ ํด๋์ค์์๋ง ์ฌ์ฉํ ์ ์๋ค.
๋ฆฌ์ค๋์ ์ฌ๋ฌ ๋ฉ์๋๊ฐ ์์ผ๋ฉด ์ฌ๋ฌ ๋ฆฌ์ค๋๋ก ๋ถํ ํด์ผ ํ๋ค. ์๋ฅผ ๋ค์ด View.OnAttachStateChangeListener์๋ onViewAttachedtoWindow(View) ๋ฐ onViewDetachedFromWindow(View) 2๊ฐ ๋ฉ์๋๊ฐ ์๋ค. ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ 2๊ฐ์ ์ธํฐํ์ด์ค๋ฅผ ์ ๊ณตํ์ฌ ์ด๋ฌํ ๋ฉ์๋์ ์์ฑ ๋ฐ ํธ๋ค๋ฌ๋ฅผ ๊ตฌ๋ณํ๋ค.
// Translation from provided interfaces in Java:
@TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
interface OnViewDetachedFromWindow {
fun onViewDetachedFromWindow(v: View)
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
interface OnViewAttachedToWindow {
fun onViewAttachedToWindow(v: View)
}
ํ๋์ ๋ฆฌ์ค๋๋ฅผ ๋ณ๊ฒฝํ๋ฉด ๋ค๋ฅธ ๋ฆฌ์ค๋์๋ ์ํฅ์ ์ค ์ ์์ผ๋ฏ๋ก ์ด๋ ํ ์์ฑ ๋๋ ๋ ๋ค์์ ์๋ํ๋ ์ด๋ํฐ๊ฐ ํ์ํ๋ค. ์๋ ์์์์ ๊ฐ์ด ์ฃผ์์์ requireAll์ false๋ก ์ค์ ํ์ฌ ๋ชจ๋ ์์ฑ์ ๊ฒฐํฉ ํํ์์ ํ ๋นํ ํ์๋ ์๋ค๋ ๊ฒ์ ์ง์ ํ ์ ์๋ค.
@BindingAdapter(
"android:onViewDetachedFromWindow",
"android:onViewAttachedToWindow",
requireAll = false
)
fun setListener(view: View, detach: OnViewDetachedFromWindow?, attach: OnViewAttachedToWindow?) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1) {
val newListener: View.OnAttachStateChangeListener?
newListener = if (detach == null && attach == null) {
null
} else {
object : View.OnAttachStateChangeListener {
override fun onViewAttachedToWindow(v: View) {
attach.onViewAttachedToWindow(v)
}
override fun onViewDetachedFromWindow(v: View) {
detach.onViewDetachedFromWindow(v)
}
}
}
val oldListener: View.OnAttachStateChangeListener? =
ListenerUtil.trackListener(view, newListener, R.id.onAttachStateChangeListener)
if (oldListener != null) {
view.removeOnAttachStateChangeListener(oldListener)
}
if (newListener != null) {
view.addOnAttachStateChangeListener(newListener)
}
}
}
Object๊ฐ ๊ฒฐํฉ ํํ์์์ ๋ฐํ๋๋ฉด ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ ์์ฑ ๊ฐ์ ์ค์ ํ๋ ๋ฐ ์ฌ์ฉ๋๋ ๋ฉ์๋๋ฅผ ์ ํํ๋ค. Object๋ ์ ํ๋ ๋ฉ์๋์ ๋งค๊ฐ๋ณ์ ์ ํ์ผ๋ก ๋ณํ๋๋ค. ์ด ๋์์ ์๋ ์์์์ ๊ฐ์ด ObservableMapํด๋์ค๋ฅผ ์ฌ์ฉํ์ฌ ๋ฐ์ดํฐ๋ฅผ ์ ์ฅํ๋ ์ฑ์์ ์ ์ฉํ๋ค.
์ฐธ๊ณ : object.key ํ๊ธฐ๋ฒ์ ์ฌ์ฉํ์ฌ ๋งต์์ ๊ฐ์ ์ฐธ์กฐํ ์๋ ์๋ค. ์๋ฅผ ๋ค์ด ์์ ์์์ @{userMap["lastName"]}์ @{userMap.lastName}์ผ๋ก ๋์ฒดํ ์ ์๋ค.
์ด๋ค ์ํฉ์์๋ ํน์ ์ ํ ๊ฐ์ ๋ง์ถค ๋ณํ์ด ํ์ํ๋ค. ๋ทฐ์ android:background ์์ฑ์ Drawable์ด ํ์ํ๋ฐ ์ง์ ๋ color ๊ฐ์ด ์ ์์ธ ์ํฉ์ ์๋ก ๋ค ์ ์๋ค. ์๋ ์๋ Drawable์ด ํ์ํ๋ฐ ์ ์๊ฐ ๋์ ์ง์ ๋ ์์ฑ์ ๋ณด์ฌ์ค๋ค.
Drawable์ด ํ์ํ๋ฐ ์ ์๊ฐ ๋ฐํ๋ ๋๋ง๋ค int๊ฐ ColorDrawble๋ก ๋ณํ๋์ด์ผ ํ๋ค. ์๋์ ๊ฐ์ด BindingConversion ์ฃผ์์ด ์๋ ์ ์ ๋ฉ์๋๋ฅผ ์ฌ์ฉํ์ฌ ๋ณํ์ ์คํํ ์ ์๋ค.
@BindingConversion
fun convertColorToDrawable(color: Int) = ColorDrawable(color)
๋จ, ๊ฒฐํฉ ํํ์์ ์ง์ ํ๋ ๊ฐ ์ ํ์ ์ผ๊ด๋์ด์ผ ํ๋ฏ๋ก ์๋ ์์์์ ๊ฐ์ด ๋์ผํ ํํ์์ ์๋ก ๋ค๋ฅธ ์ ํ์ ์ฌ์ฉํ ์ ์๋ค.
AndroidX ๋ผ์ด๋ธ๋ฌ๋ฆฌ์๋ ์ฑ๋ฅ์ด ๋ฐ์ด๋๊ณ ํ ์คํธ์ ์ ์ง๊ด๋ฆฌ๊ฐ ์ฌ์ด ์ฑ์ ๋์์ธํ๋ ๋ฐ ์ฌ์ฉํ ์ ์๋ Architecture Components๊ฐ ํฌํจ๋์ด ์๋ค. Data binding ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ Architecture Components์ ์ํํ๊ฒ ์ฐ๋ํ์ฌ UI ๊ฐ๋ฐ์ ๋์ฑ ๋จ์ํํ๋ค. ์ฑ์ ๋ ์ด์์์ ์ด๋ฏธ UI ์ปจํธ๋กค๋ฌ ์๋ช ์ฃผ๊ธฐ๋ฅผ ๊ด๋ฆฌํ๊ณ ๋ฐ์ดํฐ์ ๋ณ๊ฒฝ์ ์๋ฆฌ๋๋ก ๋๋ ์ํคํ ์ฒ ๊ตฌ์ฑ์์์ ๋ฐ์ดํฐ์ ๊ฒฐํฉํ ์ ์๋ค. ํด๋น ํญ๋ชฉ์์๋ ์ฑ์ Architecture Components๋ฅผ ํตํฉํ์ฌ Data binding ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ฌ์ฉ์ ์ด์ ์ ๋ ๊ฐํํ๋ ๋ฐฉ๋ฒ์ ์ค๋ช ํ๋ค.
LiveData ๊ฐ์ฒด๋ฅผ ๋ฐ์ดํฐ ๊ฒฐํฉ ์์ค๋ก ์ฌ์ฉํ์ฌ ๋ฐ์ดํฐ ๋ณ๊ฒฝ์ UI์ ์๋์ผ๋ก ์๋ฆด ์ ์๋ค. Observable fields์ ๊ฐ์ด Observable์ ๊ตฌํํ๋ ๊ฐ์ฒด์ ๋ฌ๋ฆฌ LiveData ๊ฐ์ฒด๋ ๋ฐ์ดํฐ ๋ณ๊ฒฝ์ ๊ตฌ๋ ํ๋ ๊ด์ฐฐ์์ ์๋ช ์ฃผ๊ธฐ๋ฅผ ์๊ณ ์๋ค. ์ด ์๋ช ์ฃผ๊ธฐ๋ฅผ ์๋ฉด LivaData์ ์ด์ ์ ํ์ฉํ ์ ์๋ค. Android ์คํ๋์ค ๋ฒ์ 3.1 ์ด์์์๋ ๋ฐ์ดํฐ ๊ฒฐํฉ ์ฝ๋์์ Observable fields๋ฅผ LiveData ๊ฐ์ฒด๋ก ๋ฐ๊ฟ ์ ์๋ค. ๊ฒฐํฉ ํด๋์ค์ ํจ๊ป LiveData ๊ฐ์ฒด๋ฅผ ์ฌ์ฉํ๋ ค๋ฉด ์๋ช ์ฃผ๊ธฐ ์์ ์๋ฅผ ์ง์ ํ์ฌ LiveData ๊ฐ์ฒด์ ๋ฒ์๋ฅผ ์ ์ํด์ผ ํ๋ค. ์๋ ์์์๋ ๊ฒฐํฉ ํด๋์ค๋ฅผ ์ธ์คํด์คํํ ํ ํ๋์ ์๋ช ์ฃผ๊ธฐ ์์ ์๋ก ์ง์ ํ๋ค.
class ViewModelActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
// Inflate view and obtain an instance of the binding class.
val binding: UserBinding = DataBindingUtil.setContentView(this, R.layout.user)
// Specify the current activity as the lifecycle owner.
binding.setLifecycleOwner(this)
}
}
ViewModel ๊ตฌ์ฑ์์๋ฅผ ์ฌ์ฉํ์ฌ ๋ฐ์ดํฐ๋ฅผ ๋ ์ด์์์ ๊ฒฐํฉํ ์ ์๊ณ LiveData ๊ฐ์ฒด๋ฅผ ์ฌ์ฉํ์ฌ ๋ฐ์ดํฐ๋ฅผ ๋ณํํ๊ฑฐ๋ ์ฌ๋ฌ ๋ฐ์ดํฐ ์์ค๋ฅผ ๋ณํฉํ ์ ์๋ค. ์๋ ์๋ ViewModel์์ ๋ฐ์ดํฐ๋ฅผ ๋ณํํ๋ ๋ฐฉ๋ฒ์ ๋ณด์ฌ์ค๋ค.
class ScheduleViewModel : ViewModel() {
val userName: LiveData
init {
val result = Repository.userName
userName = Transformations.map(result) { result -> result.value }
}
}
Data binding ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ ViewModel ๊ตฌ์ฑ์์์ ์ํํ๊ฒ ์ฐ๋ํ๋ค. ์ด ๊ตฌ์ฑ์์๋ ๋ ์ด์์์ด ๊ด์ฐฐํ๊ณ ๋ณ๊ฒฝ์ฌํญ์ ๋ฐ์ํ๋ ๋ฐ์ดํฐ๋ฅผ ๋ ธ์ถํ๋ค. Data binding ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ํจ๊ป ViewModel ๊ตฌ์ฑ์์๋ฅผ ์ฌ์ฉํ๋ฉด UI ๋ก์ง์ ๋ ์ด์์์์ ๊ตฌ์ฑ์์๋ก ์ฝ๊ฒ ์ด๋ํ ์ ์์ผ๋ฏ๋ก ํ ์คํธํ๊ธฐ๊ฐ ๋ ์ฝ๋ค. Data binding ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ๋ฉด ํ์ํ ๋ ๋ทฐ๋ฅผ ๋ฐ์ดํฐ ์์ค์ ๊ฒฐํฉํ๊ณ ๋ฐ์ดํฐ ์์ค์์ ๊ฒฐํฉ ํด์ ํ ์ ์๋ค. Data binding ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ๋ ๊ทธ ๋ฐ์ ์์ ์ ๋๋ถ๋ถ ์ ์ ํ ๋ฐ์ดํฐ๋ฅผ ๋ ธ์ถํ๊ณ ์๋์ง ํ์ธํ๋ ๊ฒ์ด๋ค. Data binding ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ํจ๊ป ViewModel ๊ตฌ์ฑ์์๋ฅผ ์ฌ์ฉํ๋ ค๋ฉด ViewModel ํด๋์ค์์ ์์๋ฐ๋ ๊ตฌ์ฑ์์๋ฅผ ์ธ์คํด์คํํ๊ณ ๊ฒฐํฉ ํด๋์ค์ ์ธ์คํด์ค๋ฅผ ๊ฐ์ ธ์ ๊ฒฐํฉ ํด๋์ค์ ์์ฑ์ ViewModel ๊ตฌ์ฑ์์๋ฅผ ํ ๋นํด์ผ ํ๋ค. ์๋ ์๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ํจ๊ป ๊ตฌ์ฑ์์๋ฅผ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ์ ๋ณด์ฌ์ค๋ค.
class ViewModelActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
// Obtain the ViewModel component.
UserModel userModel = ViewModelProviders.of(getActivity())
.get(UserModel.class)
// Inflate view and obtain an instance of the binding class.
val binding: UserBinding = DataBindingUtil.setContentView(this, R.layout.user)
// Assign the component to a property in the binding class.
binding.viewmodel = userModel
}
}
์๋ ์์์์ ๊ฐ์ด ๋ ์ด์์์์ ๊ฒฐํฉ ํํ์์ ์ฌ์ฉํ์ฌ ์ ์ ํ ๋ทฐ์ ViewModel ๊ตฌ์ฑ์์์ ์์ฑ ๋ฐ ๋ฉ์๋๋ฅผ ํ ๋นํ๋ค.
Observable์ ๊ตฌํํ๋ ViewModel ๊ตฌ์ฑ์์๋ฅผ ์ฌ์ฉํ๋ฉด LiveData ๊ฐ์ฒด๋ฅผ ์ฌ์ฉํ๋ ๋ฐฉ์๊ณผ ์ ์ฌํ๊ฒ ๋ฐ์ดํฐ ๋ณ๊ฒฝ์ ๋ค๋ฅธ ์ฑ ๊ตฌ์ฑ์์์ ์๋ฆด ์ ์๋ค. LiveData์ ์๋ช ์ฃผ๊ธฐ ๊ด๋ฆฌ ๊ธฐ๋ฅ์ด ์์ค๋์๋๋ผ๋ LiveData ๊ฐ์ฒด๋ฅผ ์ฌ์ฉํ๋ ๊ฒ๋ณด๋ค Observable ์ธํฐํ์ด์ค๋ฅผ ๊ตฌํํ๋ ViewModel ๊ตฌ์ฑ์์๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ด ๋ ์ข์ ์ํฉ๋ ์๋ค. Observable์ ๊ตฌํํ๋ ViewModel ๊ตฌ์ฑ์์๋ฅผ ์ฌ์ฉํ๋ฉด ์ฑ์ ๊ฒฐํฉ ์ด๋ํฐ๋ฅผ ๋ ์ธ๋ฐํ๊ฒ ์ ์ดํ ์ ์๋ค. ์๋ฅผ ๋ค์ด ์ด ํจํด์ ์ฌ์ฉํ๋ฉด ๋ฐ์ดํฐ๊ฐ ๋ณ๊ฒฝ๋ ๋ ์๋ฆผ์ ๋ ์ธ๋ฐํ๊ฒ ์ ์ดํ ์ ์์ผ๋ฉฐ ๋ง์ถค ๋ฉ์๋๋ฅผ ์ง์ ํ์ฌ ์๋ฐฉํฅ ๋ฐ์ดํฐ ๊ฒฐํฉ์ ์์ฑ ๊ฐ์ ์ค์ ํ ์๋ ์๋ค. ๊ด์ฐฐ ๊ฐ๋ฅํ ViewModel ๊ตฌ์ฑ์์๋ฅผ ๊ตฌํํ๋ ค๋ฉด ViewModel ํด๋์ค์์ ์์๋ฐ๊ณ Observable ์ธํฐํ์ด์ค๋ฅผ ๊ตฌํํ๋ ํด๋์ค๋ฅผ ์์ฑํด์ผ ํ๋ค. ๊ด์ฐฐ์๊ฐ addOnPropertyChangedCallback() ๋ฐ removeOnPropertyChangedCallback() ๋ฉ์๋๋ฅผ ์ฌ์ฉํ์ฌ ์๋ฆผ์ ๊ตฌ๋ ํ๊ฑฐ๋ ๊ตฌ๋ ์ทจ์ํ ๋ ๋ง์ถค ๋ก์ง์ ์ ๊ณตํ ์ ์๋ค. ๋ํ notifyPropertyChanged() ๋ฉ์๋์์ ์์ฑ์ด ๋ณ๊ฒฝ๋ ๋ ์คํ๋๋ ๋ง์ถค ๋ก์ง์ ์ ๊ณตํ ์๋ ์๋ค. ์๋ ์ฝ๋ ์๋ ๊ด์ฐฐ ๊ฐ๋ฅํ ViewModel์ ๊ตฌํํ๋ ๋ฐฉ๋ฒ์ ๋ณด์ฌ์ค๋ค.
/**
* A ViewModel that is also an Observable,
* to be used with the Data Binding Library.
*/
open class ObservableViewModel : ViewModel(), Observable {
private val callbacks: PropertyChangeRegistry = PropertyChangeRegistry()
override fun addOnPropertyChangedCallback(
callback: Observable.OnPropertyChangedCallback) {
callbacks.add(callback)
}
override fun removeOnPropertyChangedCallback(
callback: Observable.OnPropertyChangedCallback) {
callbacks.remove(callback)
}
/**
* Notifies observers that all properties of this instance have changed.
*/
fun notifyChange() {
callbacks.notifyCallbacks(this, 0, null)
}
/**
* Notifies observers that a specific property has changed. The getter for the
* property that changes should be marked with the @Bindable annotation to
* generate a field in the BR class to be used as the fieldId parameter.
*
* @param fieldId The generated BR id for the Bindable field.
*/
fun notifyPropertyChanged(fieldId: Int) {
callbacks.notifyCallbacks(this, fieldId, null)
}
}
์๋ฐฉํฅ ๋ฐ์ดํฐ ๊ฒฐํฉ์ ์ด ํ๋ก์ธ์ค์ ๋ฐ๋ก๊ฐ๊ธฐ๋ฅผ ์ ๊ณตํ๋ค. '=' ๊ธฐํธ๊ฐ ํฌํจ๋ @={} ํ๊ธฐ๋ฒ์ ์์ฑ๊ณผ ๊ด๋ จ๋ ๋ฐ์ดํฐ ๋ณ๊ฒฝ์ฌํญ์ ๋ฐ๋ ๋์์ ์ฌ์ฉ์ ์ ๋ฐ์ดํธ๋ฅผ ์์ ํ๋ค.
โจ
๋ฐฑ์
๋ฐ์ดํฐ์ ๋ณ๊ฒฝ์ ๋์ํ๊ธฐ ์ํด ๋ค์ ์ฝ๋ ์ค๋ํซ์์์ ๊ฐ์ด ๋ ์ด์์ ๋ณ์๋ฅผ Observable ์ผ๋ฐ์ ์ผ๋ก
BaseObservable์ ๊ตฌํ์ผ๋ก ๋ง๋ค๊ณ @Bindable ์ฃผ์์ ์ฌ์ฉํ ์ ์์ต๋๋ค.
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)
}
}
}
๊ฒฐํฉ ๊ฐ๋ฅํ ์์ฑ์ getter ๋ฉ์๋๋ getRememberMe()๋ผ๊ณ ํ๋ฏ๋ก ์์ฑ์ ์์ํ๋ setter ๋ฉ์๋๋ ์๋์ผ๋ก setRememberMe()๋ผ๋ ์ด๋ฆ์ ์ฌ์ฉํ๋ค.
ํ๋ซํผ์ ์ฑ์ ์ผ๋ถ๋ก ์ฌ์ฉํ ์ ์๋ ๊ฐ์ฅ ์ผ๋ฐ์ ์ธ ์๋ฐฉํฅ ์์ฑ ๋ฐ ๋ณ๊ฒฝ ๋ฆฌ์ค๋์ ์๋ฐฉํฅ ๋ฐ์ดํฐ ๊ฒฐํฉ ๊ตฌํ์ ์ ๊ณตํ๋ค. ๋ง์ถค ์์ฑ์ผ๋ก ์๋ฐฉํฅ ๋ฐ์ดํฐ ๊ฒฐํฉ์ ์ฌ์ฉํ๋ ค๋ฉด @InverseBindingAdapter ๋ฐ @InverseBindingMethod๋ฅผ ํ์ฉํด์ผ ํ๋ค. ์๋ฅผ ๋ค์ด MyView๋ผ๋ ๋ง์ถค ๋ทฐ์์ "time" ์์ฑ์ ์๋ฐฉํฅ ๋ฐ์ดํฐ ๊ฒฐํฉ์ ์ฌ์ฉํ๋ ค๋ฉด ์๋ ๋จ๊ณ๋ฅผ ์๋ฃํด์ผ ํฉ๋๋ค.
[Step 1] ์ด๊ธฐ ๊ฐ์ ์ค์ ํ๊ณ ๊ฐ์ด ๋ณ๊ฒฝ๋ ๋ ์ ๋ฐ์ดํธํ๋ ๋ฉ์๋์ @BindingAdapter๋ฅผ ์ฌ์ฉํ์ฌ ์ฃผ์์ ์ถ๊ฐํ๋ค.
โจ@BindingAdapter("time")
@JvmStatic fun setTime(view: MyView, newValue: Time) {
// Important to break potential infinite loops.
if (view.time != newValue) {
view.time = newValue
}
}
[Step 2] ๋ทฐ์์ ๊ฐ์ ์ฝ๋ ๋ฉ์๋์ @InverseBindingAdapter๋ฅผ ์ฌ์ฉํ์ฌ ์ฃผ์์ ์ถ๊ฐํ๋ค.
@InverseBindingAdapter("time")
@JvmStatic fun getTime(view: MyView) : Time {
return view.getTime()
}
์ด ์์ ์์ ๋ฐ์ดํฐ ๊ฒฐํฉ์ ๋ฐ์ดํฐ๊ฐ ๋ณ๊ฒฝ๋ ๋ ํด์ผ ํ ์์ (@BindingAdapter๋ฅผ ์ฌ์ฉํ์ฌ ์ฃผ์์ ์ถ๊ฐํ ๋ฉ์๋ ํธ์ถ)๊ณผ ๋ทฐ ์์ฑ์ด ๋ณ๊ฒฝ๋ ๋ ํธ์ถํ ๋์(InverseBindingListener ํธ์ถ)์ ์๊ณ ์์ง๋ง ์์ฑ์ด ์ธ์ ์ด๋ป๊ฒ ๋ณ๊ฒฝ๋๋์ง๋ ์ ์ ์์ผ๋ฏ๋ก ์์ฑ์ ๋ณ๊ฒฝ ์๊ธฐ ๋๋ ๋ฐฉ์์ ์๊ธฐ ์ํด์๋ ๋ทฐ์ ๋ฆฌ์ค๋๋ฅผ ์ค์ ํด์ผ ํ๋ค. ๋ฆฌ์ค๋๋ ๋ง์ถค ๋ทฐ์ ์ฐ๊ฒฐ๋ ๋ง์ถค ๋ฆฌ์ค๋์ด๊ฑฐ๋ ํฌ์ปค์ค ์์ค ๋๋ ํ ์คํธ ๋ณ๊ฒฝ๊ณผ ๊ฐ์ ์ ๋ค๋ฆญ ์ด๋ฒคํธ์ผ ์ ์๋ค. ๋ค์๊ณผ ๊ฐ์ด ์์ฑ ๋ณ๊ฒฝ์ ๋ฆฌ์ค๋๋ฅผ ์ค์ ํ๋ ๋ฉ์๋์ @BindingAdapter ์ฃผ์์ ์ถ๊ฐํ๋ค.
@BindingAdapter("app:timeAttrChanged")
@JvmStatic fun setListeners(
view: MyView,
attrChange: InverseBindingListener
) {
// Set a listener for click, focus, touch, etc.
}
๋ฆฌ์ค๋์๋ InverseBindingListener๊ฐ ๋งค๊ฐ๋ณ์๋ก ํฌํจ๋๋ฉฐ InverseBindingListener๋ฅผ ์ฌ์ฉํ๋ฉด ๋ฐ์ดํฐ ๊ฒฐํฉ ์์คํ ์ ์์ฑ์ด ๋ณ๊ฒฝ๋์์์ ์๋ฆด ์ ์๋ค. ๊ทธ๋ฌ๋ฉด ์์คํ ์ @InverseBindingAdapter ๋ฑ์ ์ฌ์ฉํ์ฌ ์ฃผ์์ด ์ถ๊ฐ๋ ๋ฉ์๋ ํธ์ถ์ ์์ํ ์ ์๋ค.
View ๊ฐ์ฒด์ ๊ฒฐํฉ๋ ๋ณ์๋ฅผ ํ์ํ๊ธฐ ์ ์ ๋จผ์ ํ์ ์ง์ , ๋ณํ ๋๋ ๋ณ๊ฒฝ์ ํด์ผ ํ๋ฉด Converter ๊ฐ์ฒด๋ฅผ ์ฌ์ฉํ๋ฉด ๋๋ค. ์๋ฅผ ๋ค์ด ๋ ์ง๋ฅผ ๋ณด์ฌ์ฃผ๋ EditText ๊ฐ์ฒด๋ฅผ ์ฌ์ฉํ๋ค.
viewmodel.birthDate ์์ฑ์๋ Long ์ ํ์ ๊ฐ์ด ํฌํจ๋์ด ์์ผ๋ฏ๋ก ๋ณํ๊ธฐ๋ฅผ ์ฌ์ฉํ์ฌ ํ์์ ์ง์ ํด์ผ ํ๋ค. ๋ํ ์๋ฐฉํฅ ํํ์์ ์ฌ์ฉ ์ค์ด๋ฏ๋ก ์ฌ์ฉ์๊ฐ ์ ๊ณตํ ๋ฌธ์์ด์ ๋ฐฑ์ ๋ฐ์ดํฐ ์ ํ(์ด ์ฌ๋ก์์๋ Long)์ผ๋ก ๋ค์ ๋ณํํ๋ ๋ฐฉ๋ฒ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ์๋ ค์ฃผ๋ ์ญ๋ณํ๊ธฐ๋ ์์ด์ผ ํ๋ค. ์ด ํ๋ก์ธ์ค๋ ๋ณํ๊ธฐ ์ค ํ๋์ @InverseMethod๋ฅผ ์ถ๊ฐํ๊ณ ์ด ์ฃผ์์ด ์ญ๋ณํ๊ธฐ๋ฅผ ์ฐธ์กฐํ๋๋ก ํจ์ผ๋ก์จ ์๋ฃํ ์ ์๋ค. ์๋ ์ฝ๋ ์ค๋ํซ์ ์ด ๊ตฌ์ฑ์ ์๋ฅผ ๋ณด์ฌ์ค๋ค.
object Converter {
@InverseMethod("stringToDate")
fun dateToString(
view: EditText, oldValue: Long,
value: Long
): String {
// Converts long to String.
}
fun stringToDate(
view: EditText, oldValue: String,
value: String
): Long {
// Converts String to long.
}
}
์๋ฐฉํฅ ๋ฐ์ดํฐ ๊ฒฐํฉ์ ์ฌ์ฉํ ๋ ๋ฌดํ ๋ฃจํ๊ฐ ๋ฐ์ํ์ง ์๋๋ก ์ฃผ์ํด์ผ ํ๋ค. ์ฌ์ฉ์๊ฐ ์์ฑ์ ๋ณ๊ฒฝํ๋ฉด @InverseBindingAdapter๋ฅผ ์ฌ์ฉํ์ฌ ์ฃผ์์ด ์ถ๊ฐ๋ ๋ฉ์๋๊ฐ ํธ์ถ๋๊ณ ๊ฐ์ด backing ์์ฑ์ ํ ๋น๋๋ฉด ๊ฒฐ๊ณผ์ ์ผ๋ก @BindingAdapter๋ฅผ ์ฌ์ฉํ์ฌ ์ฃผ์์ด ์ถ๊ฐ๋ ๋ฉ์๋๊ฐ ํธ์ถ๋์ด @InverseBindingAdapter๋ฅผ ์ฌ์ฉํ์ฌ ์ถ๊ฐ๋ ๋ฉ์๋์ ๋ ๋ค๋ฅธ ํธ์ถ์ด ํธ๋ฆฌ๊ฑฐ ๋๋ค. ์ด๋ฌํ ์ด์ ๋ก @BindingAdapter๋ฅผ ์ฌ์ฉํ์ฌ ์ฃผ์์ด ์ถ๊ฐ๋ ๋ฉ์๋์ ์ ๊ฐ๊ณผ ์ด์ ๊ฐ์ ๋น๊ตํจ์ผ๋ก์จ ๋ฐ์ ๊ฐ๋ฅํ ๋ฌดํ ๋ฃจํ๋ฅผ ๋๋ ๊ฒ์ด ์ค์ํ๋ค.
์ํด ํ์ ์์ฑ์ ์ฌ์ฉํ ๋ ํ๋ซํผ์ ๊ธฐ๋ณธ์ ์ผ๋ก ์๋ฐฉํฅ ๋ฐ์ดํฐ ๊ฒฐํฉ์ ์ง์ํ๋ค. ํ๋ซํผ์์ ์ด ์ง์์ ์ ๊ณตํ๋ ๋ฐฉ์์ ๊ดํ ์์ธํ ๋ด์ฉ์ ํด๋นํ๋ ๊ฒฐํฉ ์ด๋ํฐ์ ๊ตฌํ์ ์ฐธ์กฐ! ํด๋์ค ์์ฑ ๊ฒฐํฉ ์ด๋ํฐ AdapterView android:selectedItemPosition android:selection AdapterViewBindingAdapter CalendarView android:date CalendarViewBindingAdapter CompoundButton android:checked CompoundButtonBindingAdapter DatePicker android:year android:month android:day DatePickerBindingAdapter NumberPicker android:value NumberPickerBindingAdapter RadioButton android:checkedButton RadioGroupBindingAdapter RatingBar android:rating RatingBarBindingAdapter SeekBar android:progress SeekBarBindingAdapter TabHost android:currentTab TabHostBindingAdapter TextView android:text TextViewBindingAdapter TimePicker android:hour android:minute TimePickerBindingAdapter
https://medium.com/@PaperEd/android-how-to-databinding-169c78e7dc28 https://developer.android.com/topic/libraries/data-binding/expressions